[DRE-commits] [chef] 02/17: Imported Upstream version 11.8.2

Stefano Rivera stefano at rivera.za.net
Wed Jan 15 15:55:28 UTC 2014


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

stefanor pushed a commit to branch master
in repository chef.

commit 3fb53e67260c483b466b9f10b865ffbf60ed3590
Author: Stefano Rivera <stefanor at debian.org>
Date:   Thu Jan 9 15:18:44 2014 +0200

    Imported Upstream version 11.8.2
---
 CONTRIBUTING.md                                    |  155 ++
 README.md                                          |   89 +
 README.rdoc                                        |  177 --
 Rakefile                                           |  142 ++
 bin/chef-apply                                     |   25 +
 bin/chef-client                                    |    4 +-
 bin/chef-service-manager                           |   37 +
 bin/chef-shell                                     |   37 +
 bin/chef-solo                                      |    4 +-
 bin/knife                                          |    6 +-
 bin/shef                                           |   11 +-
 distro/arch/etc/rc.d/chef-client                   |    4 +-
 distro/arch/etc/rc.d/chef-server                   |    4 +-
 distro/arch/etc/rc.d/chef-server-webui             |    4 +-
 distro/arch/etc/rc.d/chef-solr                     |    4 +-
 distro/common/html/chef-client.8.html              |   10 +-
 distro/common/html/chef-expander.8.html            |    8 +-
 distro/common/html/chef-expanderctl.8.html         |    8 +-
 distro/common/html/chef-server-webui.8.html        |    8 +-
 distro/common/html/chef-server.8.html              |    8 +-
 distro/common/html/chef-shell.1.html               |  286 +++
 distro/common/html/chef-solo.8.html                |   30 +-
 distro/common/html/chef-solr.8.html                |    8 +-
 distro/common/html/knife-bootstrap.1.html          |   10 +-
 distro/common/html/knife-client.1.html             |    8 +-
 distro/common/html/knife-configure.1.html          |   28 +-
 distro/common/html/knife-cookbook-site.1.html      |    8 +-
 distro/common/html/knife-cookbook.1.html           |   25 +-
 distro/common/html/knife-data-bag.1.html           |   17 +-
 distro/common/html/knife-environment.1.html        |   14 +-
 distro/common/html/knife-exec.1.html               |   18 +-
 distro/common/html/knife-index.1.html              |    8 +-
 distro/common/html/knife-node.1.html               |    8 +-
 distro/common/html/knife-recipe.1.html             |   92 -
 distro/common/html/knife-role.1.html               |    8 +-
 distro/common/html/knife-search.1.html             |    8 +-
 distro/common/html/knife-ssh.1.html                |    8 +-
 distro/common/html/knife-status.1.html             |    8 +-
 distro/common/html/knife-tag.1.html                |    8 +-
 distro/common/html/knife.1.html                    |   23 +-
 distro/common/html/shef.1.html                     |  283 ---
 distro/common/man/man1/README.md                   |   58 +
 distro/common/man/man1/chef-shell.1                |  115 +
 distro/common/man/man1/knife-bootstrap.1           |  354 +--
 distro/common/man/man1/knife-client.1              |  449 +++-
 distro/common/man/man1/knife-configure.1           |  232 +-
 distro/common/man/man1/knife-cookbook-site.1       |  598 +++--
 distro/common/man/man1/knife-cookbook.1            |  931 +++++---
 distro/common/man/man1/knife-data-bag.1            |  598 ++++-
 distro/common/man/man1/knife-delete.1              |  132 ++
 distro/common/man/man1/knife-deps.1                |  219 ++
 distro/common/man/man1/knife-diff.1                |  215 ++
 distro/common/man/man1/knife-download.1            |  220 ++
 distro/common/man/man1/knife-edit.1                |  126 +
 distro/common/man/man1/knife-environment.1         |  472 ++--
 distro/common/man/man1/knife-exec.1                |  355 ++-
 distro/common/man/man1/knife-index-rebuild.1       |  115 +
 distro/common/man/man1/knife-index.1               |   29 -
 distro/common/man/man1/knife-list.1                |  170 ++
 distro/common/man/man1/knife-node.1                |  690 +++++-
 distro/common/man/man1/knife-raw.1                 |  170 ++
 distro/common/man/man1/knife-recipe-list.1         |  133 ++
 distro/common/man/man1/knife-role.1                |  448 +++-
 distro/common/man/man1/knife-search.1              |  510 ++--
 distro/common/man/man1/knife-show.1                |  138 ++
 distro/common/man/man1/knife-ssh.1                 |  307 ++-
 distro/common/man/man1/knife-status.1              |  226 +-
 distro/common/man/man1/knife-tag.1                 |  215 +-
 distro/common/man/man1/knife-upload.1              |  239 ++
 distro/common/man/man1/knife-user.1                |  317 +++
 distro/common/man/man1/knife-xargs.1               |  166 ++
 distro/common/man/man1/knife.1                     |  514 ++---
 distro/common/man/man1/shef.1                      |  256 --
 distro/common/man/man8/chef-client.8               |  333 ++-
 distro/common/man/man8/chef-expander.8             |   97 -
 distro/common/man/man8/chef-expanderctl.8          |   62 -
 distro/common/man/man8/chef-server-webui.8         |  155 --
 distro/common/man/man8/chef-server.8               |  147 --
 distro/common/man/man8/chef-solo.8                 |  276 +--
 distro/common/man/man8/chef-solr.8                 |  122 -
 distro/common/markdown/man1/chef-shell.mkd         |  195 ++
 distro/common/markdown/man1/knife-bootstrap.mkd    |    3 +
 distro/common/markdown/man1/knife-configure.mkd    |   22 +-
 distro/common/markdown/man1/knife-cookbook.mkd     |    8 +-
 distro/common/markdown/man1/knife-exec.mkd         |   17 +-
 distro/common/markdown/man1/knife.mkd              |   17 +-
 distro/common/markdown/man1/shef.mkd               |  189 --
 distro/common/markdown/man8/chef-client.mkd        |    3 -
 distro/common/markdown/man8/chef-expander.mkd      |    2 +-
 distro/common/markdown/man8/chef-expanderctl.mkd   |    2 +-
 distro/debian/etc/init.d/chef-client               |   10 +-
 distro/debian/etc/init.d/chef-expander             |    4 +-
 distro/debian/etc/init.d/chef-server               |    4 +-
 distro/debian/etc/init.d/chef-server-webui         |    4 +-
 distro/debian/etc/init.d/chef-solr                 |    4 +-
 distro/debian/etc/init/chef-client.conf            |    2 +-
 distro/debian/etc/init/chef-expander.conf          |    2 +-
 distro/debian/etc/init/chef-server-webui.conf      |    2 +-
 distro/debian/etc/init/chef-server.conf            |    2 +-
 distro/debian/etc/init/chef-solr.conf              |    2 +-
 distro/redhat/etc/init.d/chef-client               |    2 +-
 distro/redhat/etc/init.d/chef-server               |    4 +-
 distro/redhat/etc/init.d/chef-server-webui         |    4 +-
 distro/redhat/etc/init.d/chef-solr                 |    4 +-
 distro/windows/service_manager.rb                  |  148 +-
 lib/chef.rb                                        |    8 +-
 lib/chef/api_client.rb                             |  161 +-
 lib/chef/api_client/registration.rb                |  126 +
 lib/chef/application.rb                            |  171 +-
 lib/chef/application/agent.rb                      |    4 +-
 lib/chef/application/apply.rb                      |  162 ++
 lib/chef/application/client.rb                     |  148 +-
 lib/chef/application/knife.rb                      |   14 +-
 lib/chef/application/solo.rb                       |  101 +-
 lib/chef/application/windows_service.rb            |  213 +-
 lib/chef/application/windows_service_manager.rb    |  187 ++
 lib/chef/applications.rb                           |    1 +
 lib/chef/certificate.rb                            |  161 --
 lib/chef/checksum.rb                               |  167 --
 lib/chef/checksum/storage.rb                       |    4 +-
 lib/chef/checksum/storage/filesystem.rb            |    4 +-
 lib/chef/checksum_cache.rb                         |  190 --
 lib/chef/chef_fs.rb                                |    9 +
 lib/chef/chef_fs/chef_fs_data_store.rb             |  371 +++
 lib/chef/chef_fs/command_line.rb                   |  285 +++
 lib/chef/chef_fs/config.rb                         |  160 ++
 lib/chef/chef_fs/data_handler/acl_data_handler.rb  |   26 +
 .../chef_fs/data_handler/client_data_handler.rb    |   37 +
 .../chef_fs/data_handler/container_data_handler.rb |   29 +
 .../chef_fs/data_handler/cookbook_data_handler.rb  |   38 +
 .../data_handler/data_bag_item_data_handler.rb     |   56 +
 lib/chef/chef_fs/data_handler/data_handler_base.rb |  128 +
 .../data_handler/environment_data_handler.rb       |   40 +
 .../chef_fs/data_handler/group_data_handler.rb     |   51 +
 lib/chef/chef_fs/data_handler/node_data_handler.rb |   36 +
 lib/chef/chef_fs/data_handler/role_data_handler.rb |   40 +
 lib/chef/chef_fs/data_handler/user_data_handler.rb |   27 +
 lib/chef/chef_fs/file_pattern.rb                   |  312 +++
 lib/chef/chef_fs/file_system.rb                    |  426 ++++
 lib/chef/chef_fs/file_system/acl_dir.rb            |   64 +
 lib/chef/chef_fs/file_system/acl_entry.rb          |   58 +
 lib/chef/chef_fs/file_system/acls_dir.rb           |   68 +
 .../chef_fs/file_system/already_exists_error.rb    |   31 +
 lib/chef/chef_fs/file_system/base_fs_dir.rb        |   47 +
 lib/chef/chef_fs/file_system/base_fs_object.rb     |  180 ++
 .../chef_repository_file_system_acls_dir.rb        |   37 +
 .../chef_repository_file_system_cookbook_dir.rb    |   93 +
 .../chef_repository_file_system_cookbook_entry.rb  |   86 +
 .../chef_repository_file_system_cookbooks_dir.rb   |   70 +
 .../chef_repository_file_system_data_bags_dir.rb   |   36 +
 .../chef_repository_file_system_entry.rb           |   88 +
 .../chef_repository_file_system_root_dir.rb        |  128 +
 .../chef_fs/file_system/chef_server_root_dir.rb    |  120 +
 lib/chef/chef_fs/file_system/cookbook_dir.rb       |  224 ++
 lib/chef/chef_fs/file_system/cookbook_file.rb      |   82 +
 .../chef_fs/file_system/cookbook_frozen_error.rb   |   31 +
 lib/chef/chef_fs/file_system/cookbook_subdir.rb    |   54 +
 lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb  |   41 +
 lib/chef/chef_fs/file_system/cookbooks_dir.rb      |  164 ++
 lib/chef/chef_fs/file_system/data_bag_dir.rb       |   69 +
 lib/chef/chef_fs/file_system/data_bags_dir.rb      |   73 +
 ...default_environment_cannot_be_modified_error.rb |   36 +
 lib/chef/chef_fs/file_system/environments_dir.rb   |   60 +
 lib/chef/chef_fs/file_system/file_system_entry.rb  |  100 +
 lib/chef/chef_fs/file_system/file_system_error.rb  |   33 +
 .../chef_fs/file_system/file_system_root_dir.rb    |   31 +
 lib/chef/chef_fs/file_system/memory_dir.rb         |   52 +
 lib/chef/chef_fs/file_system/memory_file.rb        |   17 +
 lib/chef/chef_fs/file_system/memory_root.rb        |   21 +
 lib/chef/chef_fs/file_system/multiplexed_dir.rb    |   49 +
 .../file_system/must_delete_recursively_error.rb   |   31 +
 lib/chef/chef_fs/file_system/nodes_dir.rb          |   55 +
 .../chef_fs/file_system/nonexistent_fs_object.rb   |   36 +
 lib/chef/chef_fs/file_system/not_found_error.rb    |   31 +
 .../chef_fs/file_system/operation_failed_error.rb  |   42 +
 .../file_system/operation_not_allowed_error.rb     |   48 +
 lib/chef/chef_fs/file_system/rest_list_dir.rb      |  115 +
 lib/chef/chef_fs/file_system/rest_list_entry.rb    |  175 ++
 lib/chef/chef_fs/knife.rb                          |  133 ++
 lib/chef/chef_fs/parallelizer.rb                   |  129 ++
 lib/chef/chef_fs/path_utils.rb                     |   95 +
 lib/chef/client.rb                                 |  341 ++-
 lib/chef/config.rb                                 |  555 +++--
 lib/chef/config_fetcher.rb                         |   79 +
 lib/chef/cookbook/chefignore.rb                    |    5 +-
 lib/chef/cookbook/cookbook_version_loader.rb       |    4 +-
 lib/chef/cookbook/file_vendor.rb                   |   10 +-
 lib/chef/cookbook/metadata.rb                      |    6 +-
 lib/chef/cookbook/remote_file_vendor.rb            |    6 +-
 lib/chef/cookbook/synchronizer.rb                  |  218 ++
 lib/chef/cookbook/syntax_check.rb                  |   86 +-
 lib/chef/cookbook_loader.rb                        |   65 +-
 lib/chef/cookbook_site_streaming_uploader.rb       |   12 +-
 lib/chef/cookbook_uploader.rb                      |   77 +-
 lib/chef/cookbook_version.rb                       |  470 +---
 lib/chef/cookbook_version_selector.rb              |  168 --
 lib/chef/couchdb.rb                                |  246 --
 lib/chef/daemon.rb                                 |   71 +-
 lib/chef/data_bag.rb                               |  130 +-
 lib/chef/data_bag_item.rb                          |   73 +-
 lib/chef/deprecation/mixin/template.rb             |   49 +
 lib/chef/deprecation/provider/cookbook_file.rb     |   55 +
 lib/chef/deprecation/provider/file.rb              |  197 ++
 lib/chef/deprecation/provider/remote_file.rb       |   86 +
 lib/chef/deprecation/provider/template.rb          |   63 +
 lib/chef/deprecation/warnings.rb                   |   38 +
 lib/chef/digester.rb                               |   73 +
 lib/chef/dsl.rb                                    |    6 +
 lib/chef/dsl/data_query.rb                         |   71 +
 lib/chef/dsl/include_attribute.rb                  |   63 +
 lib/chef/dsl/include_recipe.rb                     |   45 +
 lib/chef/dsl/platform_introspection.rb             |  218 ++
 lib/chef/dsl/recipe.rb                             |   87 +
 lib/chef/dsl/registry_helper.rb                    |   59 +
 lib/chef/encrypted_data_bag_item.rb                |  332 ++-
 lib/chef/environment.rb                            |  225 +-
 lib/chef/event_dispatch/base.rb                    |  314 +++
 lib/chef/event_dispatch/dispatcher.rb              |   42 +
 lib/chef/exceptions.rb                             |  151 +-
 lib/chef/file_access_control.rb                    |    9 +-
 lib/chef/file_access_control/unix.rb               |  211 +-
 lib/chef/file_access_control/windows.rb            |   94 +-
 lib/chef/file_cache.rb                             |    6 +-
 lib/chef/file_content_management/content_base.rb   |   56 +
 lib/chef/file_content_management/deploy.rb         |   38 +
 lib/chef/file_content_management/deploy/cp.rb      |   48 +
 lib/chef/file_content_management/deploy/mv_unix.rb |   77 +
 .../file_content_management/deploy/mv_windows.rb   |   95 +
 lib/chef/file_content_management/tempfile.rb       |   61 +
 lib/chef/formatters/base.rb                        |  250 ++
 lib/chef/formatters/doc.rb                         |  236 ++
 lib/chef/formatters/error_descriptor.rb            |   67 +
 lib/chef/formatters/error_inspectors.rb            |   19 +
 .../error_inspectors/api_error_formatting.rb       |  111 +
 .../error_inspectors/compile_error_inspector.rb    |  106 +
 .../cookbook_resolve_error_inspector.rb            |  166 ++
 .../cookbook_sync_error_inspector.rb               |   80 +
 .../error_inspectors/node_load_error_inspector.rb  |  125 +
 .../registration_error_inspector.rb                |  141 ++
 .../error_inspectors/resource_failure_inspector.rb |  117 +
 .../run_list_expansion_error_inspector.rb          |  118 +
 lib/chef/formatters/error_mapper.rb                |   85 +
 lib/chef/formatters/minimal.rb                     |  235 ++
 lib/chef/handler.rb                                |    8 +
 lib/chef/handler/error_report.rb                   |    2 +-
 lib/chef/handler/json_file.rb                      |    4 +-
 lib/chef/http.rb                                   |  386 ++++
 lib/chef/http/auth_credentials.rb                  |   57 +
 lib/chef/http/authenticator.rb                     |   89 +
 lib/chef/http/basic_client.rb                      |  118 +
 lib/chef/http/cookie_jar.rb                        |   31 +
 lib/chef/http/cookie_manager.rb                    |   56 +
 lib/chef/http/decompressor.rb                      |  137 ++
 lib/chef/http/http_request.rb                      |  172 ++
 lib/chef/http/json_input.rb                        |   53 +
 lib/chef/http/json_output.rb                       |   69 +
 lib/chef/http/json_to_model_output.rb              |   34 +
 lib/chef/http/simple.rb                            |   16 +
 lib/chef/http/ssl_policies.rb                      |  129 ++
 lib/chef/index_queue.rb                            |   29 -
 lib/chef/index_queue/amqp_client.rb                |  116 -
 lib/chef/index_queue/consumer.rb                   |   76 -
 lib/chef/index_queue/indexable.rb                  |  109 -
 lib/chef/json_compat.rb                            |   99 +-
 lib/chef/knife.rb                                  |  201 +-
 lib/chef/knife/bootstrap.rb                        |   85 +-
 lib/chef/knife/bootstrap/archlinux-gems.erb        |   38 +-
 lib/chef/knife/bootstrap/centos5-gems.erb          |   50 +-
 lib/chef/knife/bootstrap/chef-full.erb             |   56 +-
 lib/chef/knife/bootstrap/fedora13-gems.erb         |   35 +-
 lib/chef/knife/bootstrap/ubuntu10.04-apt.erb       |   31 +-
 lib/chef/knife/bootstrap/ubuntu10.04-gems.erb      |   42 +-
 lib/chef/knife/bootstrap/ubuntu12.04-gems.erb      |   39 +-
 lib/chef/knife/client_create.rb                    |    3 +-
 lib/chef/knife/client_reregister.rb                |    9 +-
 lib/chef/knife/client_show.rb                      |    7 +-
 lib/chef/knife/configure.rb                        |   51 +-
 lib/chef/knife/cookbook_create.rb                  |  226 +-
 lib/chef/knife/cookbook_download.rb                |   22 +-
 lib/chef/knife/cookbook_metadata.rb                |    1 +
 lib/chef/knife/cookbook_metadata_from_file.rb      |    4 +-
 lib/chef/knife/cookbook_show.rb                    |    6 +-
 lib/chef/knife/cookbook_site_install.rb            |   33 +-
 lib/chef/knife/cookbook_site_list.rb               |    4 +-
 lib/chef/knife/cookbook_site_search.rb             |    6 +-
 lib/chef/knife/cookbook_site_share.rb              |    3 +-
 lib/chef/knife/cookbook_site_show.rb               |   17 +-
 lib/chef/knife/cookbook_test.rb                    |    6 +-
 lib/chef/knife/cookbook_upload.rb                  |  111 +-
 lib/chef/knife/core/bootstrap_context.rb           |   22 +-
 lib/chef/knife/core/cookbook_scm_repo.rb           |   12 +
 lib/chef/knife/core/generic_presenter.rb           |   55 +-
 lib/chef/knife/core/node_editor.rb                 |   52 +-
 lib/chef/knife/core/node_presenter.rb              |    3 +-
 lib/chef/knife/core/subcommand_loader.rb           |   59 +-
 lib/chef/knife/core/text_formatter.rb              |   60 +-
 lib/chef/knife/core/ui.rb                          |   47 +-
 lib/chef/knife/data_bag_create.rb                  |   12 +-
 lib/chef/knife/data_bag_delete.rb                  |    6 +-
 lib/chef/knife/data_bag_edit.rb                    |   18 +-
 lib/chef/knife/data_bag_from_file.rb               |   18 +-
 lib/chef/knife/data_bag_list.rb                    |    4 +-
 lib/chef/knife/data_bag_show.rb                    |   16 +-
 lib/chef/knife/delete.rb                           |  108 +
 lib/chef/knife/deps.rb                             |  141 ++
 lib/chef/knife/diff.rb                             |   69 +
 lib/chef/knife/download.rb                         |   69 +
 lib/chef/knife/edit.rb                             |   73 +
 lib/chef/knife/environment_from_file.rb            |   49 +-
 lib/chef/knife/environment_show.rb                 |    2 +
 lib/chef/knife/exec.rb                             |   47 +-
 lib/chef/knife/help_topics.rb                      |    2 +-
 lib/chef/knife/index_rebuild.rb                    |   98 +-
 lib/chef/knife/list.rb                             |  155 ++
 lib/chef/knife/node_run_list_set.rb                |   66 +
 lib/chef/knife/node_show.rb                        |    8 +-
 lib/chef/knife/raw.rb                              |   90 +
 lib/chef/knife/role_show.rb                        |    6 +-
 lib/chef/knife/search.rb                           |   73 +-
 lib/chef/knife/show.rb                             |   57 +
 lib/chef/knife/ssh.rb                              |  162 +-
 lib/chef/knife/status.rb                           |   31 +-
 lib/chef/knife/tag_delete.rb                       |    2 +-
 lib/chef/knife/upload.rb                           |   71 +
 lib/chef/knife/user_create.rb                      |   93 +
 lib/chef/knife/user_delete.rb                      |   46 +
 lib/chef/knife/user_edit.rb                        |   53 +
 lib/chef/knife/user_list.rb                        |   42 +
 lib/chef/knife/user_reregister.rb                  |   59 +
 lib/chef/knife/user_show.rb                        |   49 +
 lib/chef/knife/xargs.rb                            |  267 +++
 lib/chef/log.rb                                    |    4 +-
 lib/chef/mixin/check_helper.rb                     |   31 -
 lib/chef/mixin/checksum.rb                         |   10 +-
 lib/chef/mixin/command.rb                          |   63 +-
 lib/chef/mixin/convert_to_class_name.rb            |   16 +-
 lib/chef/mixin/create_path.rb                      |   18 +-
 lib/chef/mixin/deep_merge.rb                       |  243 +-
 lib/chef/mixin/deprecation.rb                      |   35 +
 .../mixin/enforce_ownership_and_permissions.rb     |   10 +-
 lib/chef/mixin/file_class.rb                       |   37 +
 lib/chef/mixin/from_file.rb                        |   12 +-
 lib/chef/mixin/language.rb                         |  245 +-
 lib/chef/mixin/language_include_attribute.rb       |   46 +-
 lib/chef/mixin/language_include_recipe.rb          |   42 +-
 lib/chef/mixin/params_validate.rb                  |   68 +-
 lib/chef/mixin/recipe_definition_dsl_core.rb       |   71 +-
 lib/chef/mixin/securable.rb                        |   83 +-
 lib/chef/mixin/shell_out.rb                        |    9 +
 lib/chef/mixin/template.rb                         |  179 +-
 lib/chef/mixin/why_run.rb                          |  332 +++
 lib/chef/mixin/windows_architecture_helper.rb      |   91 +
 lib/chef/mixin/xml_escape.rb                       |   20 +-
 lib/chef/mixins.rb                                 |    6 +-
 lib/chef/monkey_patches/dir.rb                     |   36 -
 lib/chef/monkey_patches/file.rb                    |   26 +
 lib/chef/monkey_patches/fileutils.rb               |   65 +
 lib/chef/monkey_patches/moneta.rb                  |   50 -
 lib/chef/monkey_patches/net-ssh-multi.rb           |  140 ++
 lib/chef/monkey_patches/net_http.rb                |   22 +
 lib/chef/monkey_patches/numeric.rb                 |    2 +-
 lib/chef/monkey_patches/regexp.rb                  |    8 +-
 lib/chef/monkey_patches/securerandom.rb            |   44 +
 lib/chef/monkey_patches/string.rb                  |    6 +-
 lib/chef/monkey_patches/tempfile.rb                |    4 +-
 lib/chef/monologger.rb                             |   93 +
 lib/chef/node.rb                                   |  459 ++--
 lib/chef/node/attribute.rb                         |  823 +++----
 lib/chef/node/attribute_collections.rb             |  206 ++
 lib/chef/node/immutable_collections.rb             |  186 ++
 lib/chef/openid_registration.rb                    |  187 --
 lib/chef/platform.rb                               |  455 +---
 lib/chef/platform/provider_mapping.rb              |  554 +++++
 lib/chef/platform/query_helpers.rb                 |   42 +
 lib/chef/provider.rb                               |  159 +-
 lib/chef/provider/batch.rb                         |   35 +
 lib/chef/provider/breakpoint.rb                    |   12 +-
 lib/chef/provider/cookbook_file.rb                 |   80 +-
 lib/chef/provider/cookbook_file/content.rb         |   49 +
 lib/chef/provider/cron.rb                          |   67 +-
 lib/chef/provider/cron/aix.rb                      |   48 +
 lib/chef/provider/cron/solaris.rb                  |   46 +-
 lib/chef/provider/cron/unix.rb                     |   76 +
 lib/chef/provider/deploy.rb                        |  228 +-
 lib/chef/provider/deploy/revision.rb               |   27 +
 lib/chef/provider/deploy/timestamped.rb            |    8 +-
 lib/chef/provider/directory.rb                     |  102 +-
 lib/chef/provider/erl_call.rb                      |   61 +-
 lib/chef/provider/execute.rb                       |   38 +-
 lib/chef/provider/file.rb                          |  406 +++-
 lib/chef/provider/file/content.rb                  |   39 +
 lib/chef/provider/git.rb                           |  221 +-
 lib/chef/provider/group.rb                         |   97 +-
 lib/chef/provider/group/dscl.rb                    |   24 +-
 lib/chef/provider/group/gpasswd.rb                 |   13 +-
 lib/chef/provider/group/groupadd.rb                |   25 +-
 lib/chef/provider/group/groupmod.rb                |  120 +
 lib/chef/provider/group/pw.rb                      |   27 +-
 lib/chef/provider/group/suse.rb                    |   13 +-
 lib/chef/provider/group/usermod.rb                 |   29 +-
 lib/chef/provider/group/windows.rb                 |   18 +-
 lib/chef/provider/http_request.rb                  |  135 +-
 lib/chef/provider/ifconfig.rb                      |  185 +-
 lib/chef/provider/ifconfig/aix.rb                  |   99 +
 lib/chef/provider/ifconfig/debian.rb               |   71 +
 lib/chef/provider/ifconfig/redhat.rb               |   47 +
 lib/chef/provider/link.rb                          |  104 +-
 lib/chef/provider/log.rb                           |    8 +-
 lib/chef/provider/lwrp_base.rb                     |  150 ++
 lib/chef/provider/mdadm.rb                         |   61 +-
 lib/chef/provider/mount.rb                         |   82 +-
 lib/chef/provider/mount/aix.rb                     |  179 ++
 lib/chef/provider/mount/mount.rb                   |   53 +-
 lib/chef/provider/mount/windows.rb                 |    5 +-
 lib/chef/provider/ohai.rb                          |   21 +-
 lib/chef/provider/package.rb                       |  131 +-
 lib/chef/provider/package/aix.rb                   |  146 ++
 lib/chef/provider/package/apt.rb                   |   63 +-
 lib/chef/provider/package/dpkg.rb                  |   59 +-
 lib/chef/provider/package/freebsd.rb               |    4 +-
 lib/chef/provider/package/ips.rb                   |  101 +
 lib/chef/provider/package/macports.rb              |    2 +-
 lib/chef/provider/package/pacman.rb                |   27 +-
 lib/chef/provider/package/portage.rb               |   13 +-
 lib/chef/provider/package/rpm.rb                   |   49 +-
 lib/chef/provider/package/rubygems.rb              |   72 +-
 lib/chef/provider/package/smartos.rb               |   83 +-
 lib/chef/provider/package/solaris.rb               |   47 +-
 lib/chef/provider/package/yum-dump.py              |   24 +-
 lib/chef/provider/package/yum.rb                   |   76 +-
 lib/chef/provider/package/zypper.rb                |  100 +-
 lib/chef/provider/powershell_script.rb             |   77 +
 lib/chef/provider/registry_key.rb                  |  156 ++
 lib/chef/provider/remote_directory.rb              |   49 +-
 lib/chef/provider/remote_file.rb                   |  109 +-
 .../provider/remote_file/cache_control_data.rb     |  165 ++
 lib/chef/provider/remote_file/content.rb           |   75 +
 lib/chef/provider/remote_file/fetcher.rb           |   43 +
 lib/chef/provider/remote_file/ftp.rb               |  184 ++
 lib/chef/provider/remote_file/http.rb              |  117 +
 lib/chef/provider/remote_file/local_file.rb        |   48 +
 lib/chef/provider/resource_update.rb               |   55 +
 lib/chef/provider/route.rb                         |   39 +-
 lib/chef/provider/ruby_block.rb                    |   20 +-
 lib/chef/provider/script.rb                        |   22 +-
 lib/chef/provider/service.rb                       |   93 +-
 lib/chef/provider/service/arch.rb                  |    7 +-
 lib/chef/provider/service/debian.rb                |   80 +-
 lib/chef/provider/service/freebsd.rb               |   63 +-
 lib/chef/provider/service/gentoo.rb                |   26 +-
 lib/chef/provider/service/init.rb                  |   23 +-
 lib/chef/provider/service/insserv.rb               |    4 +-
 lib/chef/provider/service/invokercd.rb             |    2 +-
 lib/chef/provider/service/macosx.rb                |   55 +-
 lib/chef/provider/service/redhat.rb                |   43 +-
 lib/chef/provider/service/simple.rb                |   95 +-
 lib/chef/provider/service/solaris.rb               |   12 +-
 lib/chef/provider/service/systemd.rb               |   17 +-
 lib/chef/provider/service/upstart.rb               |   40 +-
 lib/chef/provider/service/windows.rb               |    4 +
 lib/chef/provider/subversion.rb                    |   59 +-
 lib/chef/provider/template.rb                      |   86 +-
 lib/chef/provider/template/content.rb              |   61 +
 lib/chef/provider/template_finder.rb               |   61 +
 lib/chef/provider/user.rb                          |  128 +-
 lib/chef/provider/user/dscl.rb                     |   66 +-
 lib/chef/provider/user/pw.rb                       |   22 +-
 lib/chef/provider/user/solaris.rb                  |   90 +
 lib/chef/provider/user/useradd.rb                  |  140 +-
 lib/chef/provider/user/windows.rb                  |   14 +-
 lib/chef/provider/windows_script.rb                |   73 +
 lib/chef/providers.rb                              |   25 +
 lib/chef/recipe.rb                                 |   41 +-
 lib/chef/resource.rb                               |  439 ++--
 lib/chef/resource/apt_package.rb                   |    6 +-
 lib/chef/resource/bash.rb                          |    6 +-
 lib/chef/resource/batch.rb                         |   31 +
 lib/chef/resource/bff_package.rb                   |   36 +
 lib/chef/resource/breakpoint.rb                    |    6 +-
 lib/chef/resource/conditional.rb                   |   12 +
 .../resource/conditional_action_not_nothing.rb     |   48 +
 lib/chef/resource/cookbook_file.rb                 |    1 +
 lib/chef/resource/cron.rb                          |   15 +-
 lib/chef/resource/csh.rb                           |    6 +-
 lib/chef/resource/deploy.rb                        |   31 +-
 lib/chef/resource/deploy_revision.rb               |   12 +-
 lib/chef/resource/directory.rb                     |    6 +
 lib/chef/resource/dpkg_package.rb                  |    8 +-
 lib/chef/resource/easy_install_package.rb          |    2 +-
 lib/chef/resource/env.rb                           |    5 +
 lib/chef/resource/erl_call.rb                      |    5 +-
 lib/chef/resource/execute.rb                       |    5 +-
 lib/chef/resource/file.rb                          |   49 +
 lib/chef/resource/freebsd_package.rb               |    8 +-
 lib/chef/resource/group.rb                         |   28 +-
 lib/chef/resource/http_request.rb                  |   18 +-
 lib/chef/resource/ifconfig.rb                      |   13 +-
 lib/chef/resource/ips_package.rb                   |   42 +
 lib/chef/resource/link.rb                          |   44 +-
 lib/chef/resource/log.rb                           |   55 +-
 lib/chef/resource/lwrp_base.rb                     |  132 ++
 lib/chef/resource/macports_package.rb              |    4 +-
 lib/chef/resource/mdadm.rb                         |    5 +
 lib/chef/resource/mount.rb                         |   57 +-
 lib/chef/resource/ohai.rb                          |   20 +-
 lib/chef/resource/package.rb                       |   18 +-
 lib/chef/resource/pacman_package.rb                |    8 +-
 lib/chef/resource/perl.rb                          |    6 +-
 lib/chef/resource/portage_package.rb               |    8 +-
 lib/chef/resource/powershell_script.rb             |   31 +
 lib/chef/resource/python.rb                        |    6 +-
 lib/chef/resource/registry_key.rb                  |   86 +
 lib/chef/resource/remote_directory.rb              |   16 +-
 lib/chef/resource/remote_file.rb                   |   87 +-
 lib/chef/resource/route.rb                         |   15 +-
 lib/chef/resource/rpm_package.rb                   |    4 +-
 lib/chef/resource/ruby.rb                          |    6 +-
 lib/chef/resource/ruby_block.rb                    |   21 +-
 lib/chef/resource/scm.rb                           |   12 +
 lib/chef/resource/script.rb                        |   13 +-
 lib/chef/resource/service.rb                       |   29 +-
 lib/chef/resource/smartos_package.rb               |   14 +-
 lib/chef/resource/solaris_package.rb               |   15 +-
 lib/chef/resource/subversion.rb                    |    9 +-
 lib/chef/resource/template.rb                      |  145 ++
 lib/chef/resource/timestamped_deploy.rb            |    8 +-
 lib/chef/resource/user.rb                          |   30 +-
 lib/chef/resource/windows_script.rb                |   62 +
 lib/chef/resource/yum_package.rb                   |    4 +-
 lib/chef/resource_collection.rb                    |  100 +-
 lib/chef/resource_collection/stepable_iterator.rb  |   44 +-
 lib/chef/resource_definition.rb                    |   16 +-
 lib/chef/resource_definition_list.rb               |    4 +-
 lib/chef/resource_reporter.rb                      |  316 +++
 lib/chef/resources.rb                              |    7 +
 lib/chef/rest.rb                                   |  460 +---
 lib/chef/rest/auth_credentials.rb                  |   72 -
 lib/chef/rest/cookie_jar.rb                        |   31 -
 lib/chef/rest/rest_request.rb                      |  229 --
 lib/chef/role.rb                                   |  126 +-
 lib/chef/run_context.rb                            |  218 +-
 lib/chef/run_context/cookbook_compiler.rb          |  280 +++
 lib/chef/run_list.rb                               |    6 +-
 lib/chef/run_list/run_list_expansion.rb            |   66 +-
 lib/chef/run_lock.rb                               |  149 ++
 lib/chef/run_status.rb                             |    5 +-
 lib/chef/runner.rb                                 |   68 +-
 lib/chef/sandbox.rb                                |  163 +-
 lib/chef/scan_access_control.rb                    |  138 ++
 lib/chef/search/query.rb                           |    4 +-
 lib/chef/server_api.rb                             |   41 +
 lib/chef/shef.rb                                   |  327 ---
 lib/chef/shef/ext.rb                               |  556 +----
 lib/chef/shef/model_wrapper.rb                     |  120 -
 lib/chef/shef/shef_rest.rb                         |   28 -
 lib/chef/shef/shef_session.rb                      |  284 ---
 lib/chef/shell.rb                                  |  313 +++
 lib/chef/shell/ext.rb                              |  593 +++++
 lib/chef/shell/model_wrapper.rb                    |  120 +
 lib/chef/shell/shell_rest.rb                       |   28 +
 lib/chef/shell/shell_session.rb                    |  298 +++
 lib/chef/shell_out.rb                              |    7 +
 lib/chef/solr_query.rb                             |  187 --
 lib/chef/solr_query/lucene.treetop                 |  150 --
 lib/chef/solr_query/lucene_nodes.rb                |  285 ---
 lib/chef/solr_query/query_transform.rb             |   65 -
 lib/chef/solr_query/solr_http_request.rb           |  132 --
 lib/chef/streaming_cookbook_uploader.rb            |   56 +-
 lib/chef/tasks/chef_repo.rake                      |   34 +-
 lib/chef/user.rb                                   |  182 ++
 lib/chef/util/backup.rb                            |   85 +
 lib/chef/util/diff.rb                              |  188 ++
 lib/chef/util/file_edit.rb                         |    2 +-
 lib/chef/util/selinux.rb                           |  100 +
 lib/chef/util/windows.rb                           |    4 +-
 lib/chef/util/windows/net_group.rb                 |    4 +-
 lib/chef/util/windows/net_use.rb                   |    4 +-
 lib/chef/util/windows/net_user.rb                  |   19 +-
 lib/chef/util/windows/volume.rb                    |   10 +-
 lib/chef/version.rb                                |    6 +-
 lib/chef/version/platform.rb                       |   42 +
 lib/chef/version_class.rb                          |    2 +-
 lib/chef/version_constraint.rb                     |   11 +-
 lib/chef/version_constraint/platform.rb            |   26 +
 lib/chef/webui_user.rb                             |  231 --
 lib/chef/win32/api/file.rb                         |   10 +-
 lib/chef/win32/api/process.rb                      |    1 +
 lib/chef/win32/api/security.rb                     |   46 +-
 lib/chef/win32/api/synchronization.rb              |   89 +
 lib/chef/win32/handle.rb                           |    9 +-
 lib/chef/win32/mutex.rb                            |  117 +
 lib/chef/win32/registry.rb                         |  382 +++
 lib/chef/win32/security.rb                         |   19 +
 lib/chef/win32/security/ace.rb                     |    2 +-
 lib/chef/win32/security/sid.rb                     |    2 +-
 lib/chef/win32/version.rb                          |   35 +-
 metadata.yml                                       | 2436 +++++++++++++-------
 .../apt/chef-integration-test-1.0/debian/changelog |    5 +
 .../apt/chef-integration-test-1.0/debian/compat    |    1 +
 .../apt/chef-integration-test-1.0/debian/control   |   13 +
 .../apt/chef-integration-test-1.0/debian/copyright |   34 +
 .../apt/chef-integration-test-1.0/debian/files     |    1 +
 .../apt/chef-integration-test-1.0/debian/rules     |   13 +
 .../chef-integration-test-1.0/debian/source/format |    1 +
 .../apt/chef-integration-test-1.1/debian/changelog |   11 +
 .../apt/chef-integration-test-1.1/debian/compat    |    1 +
 .../apt/chef-integration-test-1.1/debian/control   |   13 +
 .../apt/chef-integration-test-1.1/debian/copyright |   34 +
 .../apt/chef-integration-test-1.1/debian/files     |    1 +
 .../apt/chef-integration-test-1.1/debian/rules     |   13 +
 .../chef-integration-test-1.1/debian/source/format |    1 +
 .../apt/chef-integration-test_1.0-1_amd64.changes  |   22 +
 .../data/apt/chef-integration-test_1.0-1_amd64.deb |  Bin 0 -> 1680 bytes
 .../data/apt/chef-integration-test_1.0.orig.tar.gz |  Bin 0 -> 237 bytes
 .../apt/chef-integration-test_1.1-1_amd64.changes  |   22 +
 .../data/apt/chef-integration-test_1.1-1_amd64.deb |  Bin 0 -> 1722 bytes
 .../data/apt/chef-integration-test_1.1.orig.tar.gz |  Bin 0 -> 237 bytes
 spec/data/apt/var/www/apt/conf/distributions       |    7 +
 spec/data/apt/var/www/apt/conf/incoming            |    4 +
 spec/data/apt/var/www/apt/conf/pulls               |    3 +
 spec/data/apt/var/www/apt/db/checksums.db          |  Bin 0 -> 16384 bytes
 spec/data/apt/var/www/apt/db/contents.cache.db     |  Bin 0 -> 16384 bytes
 spec/data/apt/var/www/apt/db/packages.db           |  Bin 0 -> 16384 bytes
 spec/data/apt/var/www/apt/db/references.db         |  Bin 0 -> 16384 bytes
 spec/data/apt/var/www/apt/db/release.caches.db     |  Bin 0 -> 20480 bytes
 spec/data/apt/var/www/apt/db/version               |    4 +
 spec/data/apt/var/www/apt/dists/sid/Release        |   19 +
 .../www/apt/dists/sid/main/binary-amd64/Packages   |   16 +
 .../apt/dists/sid/main/binary-amd64/Packages.gz    |  Bin 0 -> 394 bytes
 .../www/apt/dists/sid/main/binary-amd64/Release    |    5 +
 .../www/apt/dists/sid/main/binary-i386/Packages    |    0
 .../chef-integration-test_1.0-1_amd64.deb          |  Bin 0 -> 1680 bytes
 .../chef-integration-test_1.1-1_amd64.deb          |  Bin 0 -> 1722 bytes
 spec/data/bad-config.rb                            |    1 +
 spec/data/big_json.json                            |    2 +
 spec/data/big_json_plus_one.json                   |    2 +
 spec/data/bootstrap/encrypted_data_bag_secret      |    1 +
 spec/data/bootstrap/no_proxy.erb                   |    2 +
 spec/data/bootstrap/secret.erb                     |    9 +
 spec/data/bootstrap/test-hints.erb                 |   12 +
 spec/data/bootstrap/test.erb                       |    1 +
 spec/data/cb_version_cookbooks/tatft/README.rdoc   |    3 +
 .../tatft/attributes/default.rb                    |    1 +
 .../tatft/definitions/runit_service.rb             |    1 +
 .../tatft/files/default/giant_blob.tgz             |    1 +
 .../cb_version_cookbooks/tatft/libraries/ownage.rb |    1 +
 .../cb_version_cookbooks/tatft/providers/lwp.rb    |    1 +
 .../cb_version_cookbooks/tatft/recipes/default.rb  |    1 +
 .../cb_version_cookbooks/tatft/resources/lwr.rb    |    1 +
 .../tatft/templates/default/configuration.erb      |    0
 spec/data/checksum/random.txt                      |    1 +
 ...p-chef-rendered-template20100929-10863-600hhz-0 |    1 +
 ...p-chef-rendered-template20100929-10863-6m8zdk-0 |    0
 ...p-chef-rendered-template20100929-10863-ahd2gq-0 |    1 +
 ...p-chef-rendered-template20100929-10863-api8ux-0 |    1 +
 ...p-chef-rendered-template20100929-10863-b0r1m1-0 |    1 +
 ...p-chef-rendered-template20100929-10863-bfygsi-0 |    1 +
 ...p-chef-rendered-template20100929-10863-el14l6-0 |    1 +
 ...p-chef-rendered-template20100929-10863-ivrl3y-0 |    1 +
 ...p-chef-rendered-template20100929-10863-kkbs85-0 |    1 +
 ...p-chef-rendered-template20100929-10863-ory1ux-0 |    1 +
 ...p-chef-rendered-template20100929-10863-pgsq76-0 |    1 +
 ...p-chef-rendered-template20100929-10863-ra8uim-0 |    1 +
 ...mp-chef-rendered-template20100929-10863-t7k1g-0 |    1 +
 ...p-chef-rendered-template20100929-10863-t8g0sv-0 |    1 +
 ...p-chef-rendered-template20100929-10863-ufy6g3-0 |    1 +
 ...p-chef-rendered-template20100929-10863-x2d6j9-0 |    1 +
 ...p-chef-rendered-template20100929-10863-xi0l6h-0 |    1 +
 spec/data/config.rb                                |    6 +
 spec/data/cookbooks/angrybash/recipes/default.rb   |    8 +
 .../files/default/apache2_module_conf_generate.pl  |    2 +
 spec/data/cookbooks/apache2/recipes/default.rb     |    3 +
 spec/data/cookbooks/borken/recipes/default.rb      |    2 +
 .../cookbooks/borken/templates/default/borken.erb  |    2 +
 spec/data/cookbooks/chefignore                     |    8 +
 spec/data/cookbooks/ignorken/recipes/default.rb    |    1 +
 spec/data/cookbooks/ignorken/recipes/ignoreme.rb   |    2 +
 .../cookbooks/java/files/default/java.response     |    2 +
 spec/data/cookbooks/openldap/attributes/default.rb |   16 +
 spec/data/cookbooks/openldap/attributes/smokey.rb  |    1 +
 spec/data/cookbooks/openldap/definitions/client.rb |    5 +
 spec/data/cookbooks/openldap/definitions/server.rb |    5 +
 .../data/cookbooks/openldap/files/default/.dotfile |    1 +
 .../cookbooks/openldap/files/default/.ssh/id_rsa   |    1 +
 .../remotedir/.a_dotdir/.a_dotfile_in_a_dotdir     |    1 +
 .../files/default/remotedir/remote_dir_file1.txt   |    3 +
 .../files/default/remotedir/remote_dir_file2.txt   |    3 +
 .../default/remotedir/remotesubdir/.a_dotfile      |    1 +
 .../remotedir/remotesubdir/remote_subdir_file1.txt |    3 +
 .../remotedir/remotesubdir/remote_subdir_file2.txt |    3 +
 .../the_subsubdir/some_file.txt                    |    3 +
 spec/data/cookbooks/openldap/metadata.rb           |    8 +
 spec/data/cookbooks/openldap/recipes/default.rb    |    3 +
 spec/data/cookbooks/openldap/recipes/gigantor.rb   |    3 +
 spec/data/cookbooks/openldap/recipes/one.rb        |   15 +
 .../templates/default/all_windows_line_endings.erb |    4 +
 .../openldap/templates/default/helper_test.erb     |    1 +
 .../templates/default/helpers_via_partial_test.erb |    1 +
 .../templates/default/no_windows_line_endings.erb  |    4 +
 .../templates/default/openldap_stuff.conf.erb      |    1 +
 .../default/openldap_variable_stuff.conf.erb       |    1 +
 .../default/some_windows_line_endings.erb          |    4 +
 .../cookbooks/openldap/templates/default/test.erb  |    1 +
 .../preseed/files/default/preseed-file.seed        |    1 +
 .../preseed/files/default/preseed-template.seed    |    4 +
 .../templates/default/preseed-template.seed        |    1 +
 spec/data/definitions/test.rb                      |    5 +
 spec/data/environment-config.rb                    |    5 +
 .../file-providers-method-snapshot-chef-11-4.json  |  127 +
 spec/data/fileedit/blank                           |    0
 spec/data/fileedit/hosts                           |    4 +
 spec/data/gems/chef-integration-test-0.1.0.gem     |  Bin 0 -> 7680 bytes
 spec/data/git_bundles/example-repo.gitbundle       |  Bin 0 -> 1214 bytes
 .../sinatra-test-app-with-callback-files.gitbundle |  Bin 0 -> 3527 bytes
 .../sinatra-test-app-with-symlinks.gitbundle       |  Bin 0 -> 2330 bytes
 spec/data/git_bundles/sinatra-test-app.gitbundle   |  Bin 0 -> 2053 bytes
 spec/data/kitchen/chefignore                       |    6 +
 spec/data/kitchen/openldap/attributes/default.rb   |    3 +
 spec/data/kitchen/openldap/attributes/robinson.rb  |    3 +
 spec/data/kitchen/openldap/definitions/client.rb   |    3 +
 .../kitchen/openldap/definitions/drewbarrymore.rb  |    3 +
 spec/data/kitchen/openldap/recipes/gigantor.rb     |    3 +
 spec/data/kitchen/openldap/recipes/ignoreme.rb     |    3 +
 spec/data/kitchen/openldap/recipes/woot.rb         |    3 +
 .../.chef/plugins/knife/example_home_subcommand.rb |    0
 .../plugins/knife/example_subcommand.rb            |    0
 .../knife_subcommand/test_explicit_category.rb     |    7 +
 spec/data/knife_subcommand/test_name_mapping.rb    |    4 +
 spec/data/knife_subcommand/test_yourself.rb        |   21 +
 spec/data/lwrp/providers/buck_passer.rb            |    3 +
 spec/data/lwrp/providers/buck_passer_2.rb          |   10 +
 .../embedded_resource_accesses_providers_scope.rb  |   16 +
 spec/data/lwrp/providers/inline_compiler.rb        |   26 +
 spec/data/lwrp/providers/monkey_name_printer.rb    |    5 +
 spec/data/lwrp/providers/paint_drying_watcher.rb   |    7 +
 spec/data/lwrp/providers/thumb_twiddler.rb         |    7 +
 spec/data/lwrp/resources/bar.rb                    |    1 +
 spec/data/lwrp/resources/foo.rb                    |    3 +
 .../resources_with_default_attributes/nodeattr.rb  |    1 +
 spec/data/lwrp_const_scoping/resources/conflict.rb |    0
 spec/data/lwrp_override/providers/buck_passer.rb   |   10 +
 spec/data/lwrp_override/resources/foo.rb           |    4 +
 spec/data/metadata/quick_start/metadata.rb         |   19 +
 spec/data/nodes/default.rb                         |   15 +
 spec/data/nodes/test.example.com.rb                |   17 +
 spec/data/nodes/test.rb                            |   15 +
 spec/data/null_config.rb                           |    1 +
 spec/data/object_loader/environments/test.json     |    5 +
 spec/data/object_loader/environments/test.rb       |    2 +
 .../environments/test_json_class.json              |    6 +
 spec/data/object_loader/nodes/test.json            |    5 +
 spec/data/object_loader/nodes/test.rb              |    2 +
 spec/data/object_loader/nodes/test_json_class.json |    6 +
 spec/data/object_loader/roles/test.json            |    5 +
 spec/data/object_loader/roles/test.rb              |    2 +
 spec/data/object_loader/roles/test_json_class.json |    6 +
 spec/data/old_home_dir/my-dot-emacs                |    0
 spec/data/old_home_dir/my-dot-vim                  |    0
 spec/data/partial_one.erb                          |    1 +
 spec/data/recipes/test.rb                          |    7 +
 .../data/remote_directory_data/remote_dir_file.txt |    1 +
 .../remote_subdirectory/remote_subdir_file.txt     |    1 +
 spec/data/remote_file/nyan_cat.png                 |  Bin 0 -> 15202 bytes
 spec/data/remote_file/nyan_cat.png.gz              |  Bin 0 -> 14944 bytes
 .../cookbooks/circular-dep1/attributes/default.rb  |    4 +
 .../circular-dep1/definitions/circular_dep1_res.rb |    1 +
 .../cookbooks/circular-dep1/libraries/lib.rb       |    2 +
 .../cookbooks/circular-dep1/metadata.rb            |    2 +
 .../cookbooks/circular-dep1/providers/provider.rb  |    1 +
 .../cookbooks/circular-dep1/recipes/default.rb     |    0
 .../cookbooks/circular-dep1/resources/resource.rb  |    1 +
 .../cookbooks/circular-dep2/attributes/default.rb  |    3 +
 .../circular-dep2/definitions/circular_dep2_res.rb |    1 +
 .../cookbooks/circular-dep2/libraries/lib.rb       |    2 +
 .../cookbooks/circular-dep2/metadata.rb            |    2 +
 .../cookbooks/circular-dep2/providers/provider.rb  |    1 +
 .../cookbooks/circular-dep2/recipes/default.rb     |    0
 .../cookbooks/circular-dep2/resources/resource.rb  |    1 +
 .../cookbooks/dependency1/attributes/aa_first.rb   |    2 +
 .../cookbooks/dependency1/attributes/default.rb    |    2 +
 .../cookbooks/dependency1/attributes/zz_last.rb    |    3 +
 .../dependency1/definitions/dependency1_res.rb     |    1 +
 .../cookbooks/dependency1/libraries/lib.rb         |    2 +
 .../cookbooks/dependency1/providers/provider.rb    |    1 +
 .../cookbooks/dependency1/recipes/default.rb       |    0
 .../cookbooks/dependency1/resources/resource.rb    |    1 +
 .../cookbooks/dependency2/attributes/default.rb    |    3 +
 .../dependency2/definitions/dependency2_res.rb     |    1 +
 .../cookbooks/dependency2/libraries/lib.rb         |    2 +
 .../cookbooks/dependency2/providers/provider.rb    |    1 +
 .../cookbooks/dependency2/recipes/default.rb       |    0
 .../cookbooks/dependency2/resources/resource.rb    |    1 +
 .../cookbooks/no-default-attr/attributes/server.rb |    3 +
 .../definitions/no_default-attr_res.rb             |    1 +
 .../no-default-attr/providers/provider.rb          |    1 +
 .../cookbooks/no-default-attr/recipes/default.rb   |    0
 .../no-default-attr/resources/resource.rb          |    1 +
 .../test-with-circular-deps/attributes/default.rb  |    3 +
 .../definitions/test_with-circular-deps_res.rb     |    1 +
 .../test-with-circular-deps/libraries/lib.rb       |    2 +
 .../cookbooks/test-with-circular-deps/metadata.rb  |    2 +
 .../test-with-circular-deps/providers/provider.rb  |    1 +
 .../test-with-circular-deps/recipes/default.rb     |    0
 .../test-with-circular-deps/resources/resource.rb  |    1 +
 .../cookbooks/test-with-deps/attributes/default.rb |    3 +
 .../definitions/test_with-deps_res.rb              |    1 +
 .../cookbooks/test-with-deps/libraries/lib.rb      |    1 +
 .../cookbooks/test-with-deps/metadata.rb           |    3 +
 .../cookbooks/test-with-deps/providers/provider.rb |    1 +
 .../cookbooks/test-with-deps/recipes/default.rb    |    0
 .../cookbooks/test-with-deps/recipes/server.rb     |    0
 .../cookbooks/test-with-deps/resources/resource.rb |    1 +
 .../cookbooks/test/attributes/default.rb           |    0
 .../cookbooks/test/attributes/george.rb            |    1 +
 .../cookbooks/test/definitions/new_animals.rb      |    9 +
 .../cookbooks/test/definitions/new_cat.rb          |    5 +
 .../cookbooks/test/definitions/test_res.rb         |    1 +
 .../cookbooks/test/providers/provider.rb           |    1 +
 .../run_context/cookbooks/test/recipes/default.rb  |    5 +
 .../data/run_context/cookbooks/test/recipes/one.rb |    7 +
 .../data/run_context/cookbooks/test/recipes/two.rb |    7 +
 .../cookbooks/test/resources/resource.rb           |    1 +
 spec/data/run_context/nodes/run_context.rb         |    5 +
 spec/data/search_queries_to_transform.txt          |   98 +
 spec/data/shef-config.rb                           |   10 +
 spec/data/ssl/5e707473.0                           |   18 +
 spec/data/ssl/chef-rspec.cert                      |   27 +
 spec/data/ssl/chef-rspec.key                       |   27 +
 spec/data/ssl/key.pem                              |   15 +
 spec/data/ssl/private_key.pem                      |   27 +
 spec/data/ssl/private_key_with_whitespace.pem      |   32 +
 spec/data/templates/seattle.txt                    |    1 +
 spec/data/trusted_certs/example.crt                |   22 +
 spec/data/trusted_certs/intermediate.pem           |   27 +
 spec/data/trusted_certs/opscode.pem                |   38 +
 spec/data/trusted_certs/root.pem                   |   22 +
 spec/functional/assets/PkgA.1.0.0.0.bff            |  Bin 0 -> 3584 bytes
 spec/functional/assets/PkgA.2.0.0.0.bff            |  Bin 0 -> 3584 bytes
 spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm |  Bin 0 -> 972 bytes
 spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm |  Bin 0 -> 972 bytes
 spec/functional/assets/mytest-1.0-1.noarch.rpm     |  Bin 0 -> 2126 bytes
 spec/functional/assets/mytest-2.0-1.noarch.rpm     |  Bin 0 -> 2149 bytes
 spec/functional/dsl/registry_helper_spec.rb        |   63 +
 .../deploy_strategies_spec.rb                      |  238 ++
 spec/functional/knife/cookbook_delete_spec.rb      |  157 ++
 spec/functional/knife/exec_spec.rb                 |   57 +
 spec/functional/knife/smoke_test.rb                |   34 +
 spec/functional/knife/ssh_spec.rb                  |  268 +++
 .../remote_file/cache_control_data_spec.rb         |  101 +
 spec/functional/resource/base.rb                   |   41 +
 spec/functional/resource/batch_spec.rb             |   37 +
 spec/functional/resource/bff_spec.rb               |  122 +
 spec/functional/resource/cookbook_file_spec.rb     |   81 +
 spec/functional/resource/cron_spec.rb              |  147 ++
 spec/functional/resource/deploy_revision_spec.rb   |  695 ++++++
 spec/functional/resource/directory_spec.rb         |   43 +
 spec/functional/resource/file_spec.rb              |  119 +
 spec/functional/resource/git_spec.rb               |  259 +++
 spec/functional/resource/group_spec.rb             |  204 ++
 spec/functional/resource/ifconfig_spec.rb          |  163 ++
 spec/functional/resource/link_spec.rb              |  608 +++++
 spec/functional/resource/mount_spec.rb             |  207 ++
 spec/functional/resource/package_spec.rb           |  375 +++
 spec/functional/resource/powershell_spec.rb        |  187 ++
 spec/functional/resource/registry_spec.rb          |  562 +++++
 spec/functional/resource/remote_directory_spec.rb  |  220 ++
 spec/functional/resource/remote_file_spec.rb       |  165 ++
 spec/functional/resource/rpm_spec.rb               |  122 +
 spec/functional/resource/template_spec.rb          |  212 ++
 spec/functional/resource/user_spec.rb              |  548 +++++
 spec/functional/run_lock_spec.rb                   |  286 +++
 spec/functional/shell_spec.rb                      |  122 +
 spec/functional/tiny_server_spec.rb                |   78 +
 spec/functional/version_spec.rb                    |   35 +
 spec/functional/win32/registry_helper_spec.rb      |  632 +++++
 spec/functional/win32/security_spec.rb             |   37 +
 spec/functional/win32/service_manager_spec.rb      |  269 +++
 spec/functional/win32/versions_spec.rb             |   78 +
 spec/integration/client/client_spec.rb             |  146 ++
 spec/integration/knife/chef_fs_data_store_spec.rb  |  353 +++
 spec/integration/knife/chef_repo_path_spec.rb      |  858 +++++++
 .../knife/chef_repository_file_system_spec.rb      |  276 +++
 spec/integration/knife/chefignore_spec.rb          |  271 +++
 spec/integration/knife/common_options_spec.rb      |  103 +
 spec/integration/knife/delete_spec.rb              |  944 ++++++++
 spec/integration/knife/deps_spec.rb                |  648 ++++++
 spec/integration/knife/diff_spec.rb                |  536 +++++
 spec/integration/knife/download_spec.rb            |  998 ++++++++
 spec/integration/knife/list_spec.rb                |  633 +++++
 spec/integration/knife/raw_spec.rb                 |  229 ++
 spec/integration/knife/redirection_spec.rb         |   57 +
 spec/integration/knife/show_spec.rb                |  158 ++
 spec/integration/knife/upload_spec.rb              | 1076 +++++++++
 spec/integration/solo/solo_spec.rb                 |  112 +
 spec/rcov.opts                                     |    2 +
 spec/spec_helper.rb                                |  179 ++
 spec/stress/win32/file_spec.rb                     |   37 +
 spec/stress/win32/memory_spec.rb                   |   22 +
 spec/stress/win32/security_spec.rb                 |   69 +
 spec/support/chef_helpers.rb                       |   95 +
 spec/support/lib/chef/provider/easy.rb             |   35 +
 spec/support/lib/chef/provider/snakeoil.rb         |   40 +
 spec/support/lib/chef/resource/cat.rb              |   39 +
 .../lib/chef/resource/one_two_three_four.rb        |   41 +
 spec/support/lib/chef/resource/with_state.rb       |   37 +
 spec/support/lib/chef/resource/zen_master.rb       |   44 +
 spec/support/lib/library_load_order.rb             |   22 +
 spec/support/matchers/leak.rb                      |   96 +
 spec/support/mock/constant.rb                      |   52 +
 spec/support/mock/platform.rb                      |   18 +
 spec/support/platform_helpers.rb                   |   97 +
 spec/support/platforms/prof/gc.rb                  |   54 +
 spec/support/platforms/prof/win32.rb               |   46 +
 spec/support/platforms/win32/spec_service.rb       |   59 +
 spec/support/shared/functional/diff_disabled.rb    |   10 +
 .../shared/functional/directory_resource.rb        |  176 ++
 spec/support/shared/functional/file_resource.rb    |  950 ++++++++
 spec/support/shared/functional/knife.rb            |   37 +
 .../shared/functional/securable_resource.rb        |  533 +++++
 .../securable_resource_with_reporting.rb           |  385 ++++
 spec/support/shared/functional/windows_script.rb   |   48 +
 .../shared/integration/integration_helper.rb       |  149 ++
 spec/support/shared/integration/knife_support.rb   |  171 ++
 spec/support/shared/unit/api_error_inspector.rb    |  192 ++
 spec/support/shared/unit/execute_resource.rb       |  125 +
 spec/support/shared/unit/file_system_support.rb    |   70 +
 spec/support/shared/unit/platform_introspector.rb  |  162 ++
 spec/support/shared/unit/provider/file.rb          |  609 +++++
 .../unit/provider/useradd_based_user_provider.rb   |  407 ++++
 spec/support/shared/unit/script_resource.rb        |   52 +
 .../support/shared/unit/windows_script_resource.rb |   48 +
 spec/tiny_server.rb                                |  204 ++
 spec/unit/api_client/registration_spec.rb          |  172 ++
 spec/unit/api_client_spec.rb                       |  260 +++
 spec/unit/application/agent_spec.rb                |    0
 spec/unit/application/apply.rb                     |   76 +
 spec/unit/application/client_spec.rb               |  149 ++
 spec/unit/application/knife_spec.rb                |  169 ++
 spec/unit/application/server_spec.rb               |    0
 spec/unit/application/solo_spec.rb                 |  127 +
 spec/unit/application_spec.rb                      |  320 +++
 spec/unit/checksum/storage/filesystem_spec.rb      |   70 +
 spec/unit/chef_fs/diff_spec.rb                     |  328 +++
 spec/unit/chef_fs/file_pattern_spec.rb             |  526 +++++
 .../file_system/operation_failed_error_spec.rb     |   47 +
 spec/unit/chef_fs/file_system_spec.rb              |  135 ++
 spec/unit/chef_spec.rb                             |   25 +
 spec/unit/client_spec.rb                           |  465 ++++
 spec/unit/config_fetcher_spec.rb                   |   98 +
 spec/unit/config_spec.rb                           |  363 +++
 spec/unit/cookbook/chefignore_spec.rb              |   39 +
 spec/unit/cookbook/metadata_spec.rb                |  627 +++++
 spec/unit/cookbook/synchronizer_spec.rb            |  306 +++
 spec/unit/cookbook/syntax_check_spec.rb            |  174 ++
 spec/unit/cookbook_loader_spec.rb                  |  224 ++
 spec/unit/cookbook_manifest_spec.rb                |  554 +++++
 spec/unit/cookbook_site_streaming_uploader.rb      |  200 ++
 spec/unit/cookbook_spec.rb                         |   84 +
 spec/unit/cookbook_version_spec.rb                 |  323 +++
 spec/unit/daemon_spec.rb                           |  175 ++
 spec/unit/data_bag_item_spec.rb                    |  280 +++
 spec/unit/data_bag_spec.rb                         |  174 ++
 spec/unit/deprecation_spec.rb                      |   86 +
 spec/unit/digester_spec.rb                         |   50 +
 spec/unit/dsl/data_query_spec.rb                   |   66 +
 spec/unit/dsl/platform_introspection_spec.rb       |  130 ++
 spec/unit/dsl/regsitry_helper_spec.rb              |   55 +
 spec/unit/encrypted_data_bag_item_spec.rb          |  318 +++
 spec/unit/environment_spec.rb                      |  460 ++++
 spec/unit/exceptions_spec.rb                       |   78 +
 spec/unit/file_access_control_spec.rb              |  302 +++
 spec/unit/file_cache_spec.rb                       |  114 +
 .../unit/file_content_management/deploy/cp_spec.rb |   46 +
 .../file_content_management/deploy/mv_unix_spec.rb |  103 +
 .../deploy/mv_windows_spec.rb                      |  179 ++
 .../compile_error_inspector_spec.rb                |  202 ++
 .../cookbook_resolve_error_inspector_spec.rb       |  129 ++
 .../cookbook_sync_error_inspector_spec.rb          |   43 +
 .../node_load_error_inspector_spec.rb              |   27 +
 .../registration_error_inspector_spec.rb           |   27 +
 .../resource_failure_inspector_spec.rb             |  183 ++
 .../run_list_expansion_error_inspector_spec.rb     |   93 +
 spec/unit/handler/json_file_spec.rb                |   64 +
 spec/unit/handler_spec.rb                          |  216 ++
 spec/unit/http/ssl_policies_spec.rb                |  170 ++
 spec/unit/json_compat_spec.rb                      |   69 +
 spec/unit/knife/bootstrap_spec.rb                  |  383 +++
 spec/unit/knife/client_bulk_delete_spec.rb         |   78 +
 spec/unit/knife/client_create_spec.rb              |   74 +
 spec/unit/knife/client_delete_spec.rb              |   40 +
 spec/unit/knife/client_edit_spec.rb                |   40 +
 spec/unit/knife/client_list_spec.rb                |   34 +
 spec/unit/knife/client_reregister_spec.rb          |   62 +
 spec/unit/knife/client_show_spec.rb                |   42 +
 spec/unit/knife/config_file_selection_spec.rb      |  135 ++
 spec/unit/knife/configure_client_spec.rb           |   83 +
 spec/unit/knife/configure_spec.rb                  |  242 ++
 spec/unit/knife/cookbook_bulk_delete_spec.rb       |   87 +
 spec/unit/knife/cookbook_create_spec.rb            |  260 +++
 spec/unit/knife/cookbook_delete_spec.rb            |  239 ++
 spec/unit/knife/cookbook_download_spec.rb          |  238 ++
 spec/unit/knife/cookbook_list_spec.rb              |   88 +
 .../unit/knife/cookbook_metadata_from_file_spec.rb |   65 +
 spec/unit/knife/cookbook_metadata_spec.rb          |  179 ++
 spec/unit/knife/cookbook_show_spec.rb              |  223 ++
 spec/unit/knife/cookbook_site_download_spec.rb     |  151 ++
 spec/unit/knife/cookbook_site_install_spec.rb      |  148 ++
 spec/unit/knife/cookbook_site_share_spec.rb        |  146 ++
 spec/unit/knife/cookbook_site_unshare_spec.rb      |   77 +
 spec/unit/knife/cookbook_test_spec.rb              |   84 +
 spec/unit/knife/cookbook_upload_spec.rb            |  187 ++
 spec/unit/knife/core/bootstrap_context_spec.rb     |  160 ++
 spec/unit/knife/core/cookbook_scm_repo_spec.rb     |  187 ++
 spec/unit/knife/core/object_loader_spec.rb         |   81 +
 spec/unit/knife/core/subcommand_loader_spec.rb     |   74 +
 spec/unit/knife/core/ui_spec.rb                    |  468 ++++
 spec/unit/knife/data_bag_create_spec.rb            |  110 +
 spec/unit/knife/data_bag_edit_spec.rb              |   93 +
 spec/unit/knife/data_bag_from_file_spec.rb         |  196 ++
 spec/unit/knife/data_bag_show_spec.rb              |  112 +
 spec/unit/knife/environment_create_spec.rb         |   91 +
 spec/unit/knife/environment_delete_spec.rb         |   71 +
 spec/unit/knife/environment_edit_spec.rb           |   79 +
 spec/unit/knife/environment_from_file_spec.rb      |   89 +
 spec/unit/knife/environment_list_spec.rb           |   54 +
 spec/unit/knife/environment_show_spec.rb           |   52 +
 spec/unit/knife/index_rebuild_spec.rb              |  128 +
 spec/unit/knife/knife_help.rb                      |   92 +
 spec/unit/knife/node_bulk_delete_spec.rb           |   97 +
 spec/unit/knife/node_delete_spec.rb                |   68 +
 spec/unit/knife/node_edit_spec.rb                  |  115 +
 spec/unit/knife/node_from_file_spec.rb             |   59 +
 spec/unit/knife/node_list_spec.rb                  |   63 +
 spec/unit/knife/node_run_list_add_spec.rb          |  125 +
 spec/unit/knife/node_run_list_remove_spec.rb       |   74 +
 spec/unit/knife/node_run_list_set_spec.rb          |  140 ++
 spec/unit/knife/node_show_spec.rb                  |   48 +
 spec/unit/knife/role_bulk_delete_spec.rb           |   80 +
 spec/unit/knife/role_create_spec.rb                |   80 +
 spec/unit/knife/role_delete_spec.rb                |   67 +
 spec/unit/knife/role_edit_spec.rb                  |   79 +
 spec/unit/knife/role_from_file_spec.rb             |   69 +
 spec/unit/knife/role_list_spec.rb                  |   56 +
 spec/unit/knife/ssh_spec.rb                        |  283 +++
 spec/unit/knife/status_spec.rb                     |   43 +
 spec/unit/knife/tag_create_spec.rb                 |   23 +
 spec/unit/knife/tag_delete_spec.rb                 |   25 +
 spec/unit/knife/tag_list_spec.rb                   |   23 +
 spec/unit/knife/user_create_spec.rb                |   86 +
 spec/unit/knife/user_delete_spec.rb                |   39 +
 spec/unit/knife/user_edit_spec.rb                  |   42 +
 spec/unit/knife/user_list_spec.rb                  |   32 +
 spec/unit/knife/user_reregister_spec.rb            |   53 +
 spec/unit/knife/user_show_spec.rb                  |   41 +
 spec/unit/knife_spec.rb                            |  367 +++
 spec/unit/log_spec.rb                              |   24 +
 spec/unit/lwrp_spec.rb                             |  298 +++
 spec/unit/mash_spec.rb                             |   51 +
 spec/unit/mixin/checksum_spec.rb                   |   41 +
 spec/unit/mixin/command_spec.rb                    |  105 +
 spec/unit/mixin/convert_to_class_name_spec.rb      |   50 +
 spec/unit/mixin/deep_merge_spec.rb                 |  351 +++
 spec/unit/mixin/deprecation_spec.rb                |   57 +
 .../enforce_ownership_and_permissions_spec.rb      |   96 +
 spec/unit/mixin/params_validate_spec.rb            |  407 ++++
 spec/unit/mixin/path_sanity_spec.rb                |   80 +
 spec/unit/mixin/securable_spec.rb                  |  242 ++
 spec/unit/mixin/shell_out_spec.rb                  |  109 +
 spec/unit/mixin/template_spec.rb                   |  269 +++
 .../unit/mixin/windows_architecture_helper_spec.rb |   83 +
 spec/unit/mixin/xml_escape_spec.rb                 |   54 +
 spec/unit/monkey_patches/string_spec.rb            |   37 +
 spec/unit/node/attribute_spec.rb                   | 1168 ++++++++++
 spec/unit/node/immutable_collections_spec.rb       |  139 ++
 spec/unit/node_spec.rb                             |  828 +++++++
 spec/unit/platform_spec.rb                         |  255 ++
 spec/unit/provider/breakpoint_spec.rb              |   54 +
 spec/unit/provider/cookbook_file/content_spec.rb   |   40 +
 spec/unit/provider/cookbook_file_spec.rb           |   57 +
 spec/unit/provider/cron/unix_spec.rb               |  121 +
 spec/unit/provider/cron_spec.rb                    |  830 +++++++
 spec/unit/provider/deploy/revision_spec.rb         |  111 +
 spec/unit/provider/deploy/timestamped_spec.rb      |   40 +
 spec/unit/provider/deploy_spec.rb                  |  634 +++++
 spec/unit/provider/directory_spec.rb               |  188 ++
 spec/unit/provider/env_spec.rb                     |  232 ++
 spec/unit/provider/erl_call_spec.rb                |   86 +
 spec/unit/provider/execute_spec.rb                 |   89 +
 spec/unit/provider/file/content_spec.rb            |   93 +
 spec/unit/provider/file_spec.rb                    |   55 +
 spec/unit/provider/git_spec.rb                     |  558 +++++
 spec/unit/provider/group/dscl_spec.rb              |  294 +++
 spec/unit/provider/group/gpasswd_spec.rb           |  108 +
 spec/unit/provider/group/groupadd_spec.rb          |  175 ++
 spec/unit/provider/group/groupmod_spec.rb          |  134 ++
 spec/unit/provider/group/pw_spec.rb                |  140 ++
 spec/unit/provider/group/usermod_spec.rb           |   98 +
 spec/unit/provider/group/windows_spec.rb           |  108 +
 spec/unit/provider/group_spec.rb                   |  259 +++
 spec/unit/provider/http_request_spec.rb            |  137 ++
 spec/unit/provider/ifconfig/aix_spec.rb            |  180 ++
 spec/unit/provider/ifconfig/debian_spec.rb         |   89 +
 spec/unit/provider/ifconfig/redhat_spec.rb         |   71 +
 spec/unit/provider/ifconfig_spec.rb                |  180 ++
 spec/unit/provider/link_spec.rb                    |  252 ++
 spec/unit/provider/log_spec.rb                     |   81 +
 spec/unit/provider/mdadm_spec.rb                   |  131 ++
 spec/unit/provider/mount/aix_spec.rb               |  237 ++
 spec/unit/provider/mount/mount_spec.rb             |  431 ++++
 spec/unit/provider/mount/windows_spec.rb           |  137 ++
 spec/unit/provider/mount_spec.rb                   |  170 ++
 spec/unit/provider/ohai_spec.rb                    |   85 +
 spec/unit/provider/package/aix_spec.rb             |  171 ++
 spec/unit/provider/package/apt_spec.rb             |  310 +++
 spec/unit/provider/package/dpkg_spec.rb            |  216 ++
 spec/unit/provider/package/easy_install_spec.rb    |  112 +
 spec/unit/provider/package/freebsd_spec.rb         |  287 +++
 spec/unit/provider/package/ips_spec.rb             |  209 ++
 spec/unit/provider/package/macports_spec.rb        |  203 ++
 spec/unit/provider/package/pacman_spec.rb          |  206 ++
 spec/unit/provider/package/portage_spec.rb         |  320 +++
 spec/unit/provider/package/rpm_spec.rb             |  152 ++
 spec/unit/provider/package/rubygems_spec.rb        |  639 +++++
 spec/unit/provider/package/smartos_spec.rb         |  102 +
 spec/unit/provider/package/solaris_spec.rb         |  181 ++
 spec/unit/provider/package/yum_spec.rb             | 1856 +++++++++++++++
 spec/unit/provider/package/zypper_spec.rb          |  221 ++
 spec/unit/provider/package_spec.rb                 |  427 ++++
 spec/unit/provider/powershell_spec.rb              |   38 +
 spec/unit/provider/registry_key_spec.rb            |  269 +++
 spec/unit/provider/remote_directory_spec.rb        |  222 ++
 .../remote_file/cache_control_data_spec.rb         |  211 ++
 spec/unit/provider/remote_file/content_spec.rb     |  230 ++
 spec/unit/provider/remote_file/fetcher_spec.rb     |   75 +
 spec/unit/provider/remote_file/ftp_spec.rb         |  219 ++
 spec/unit/provider/remote_file/http_spec.rb        |  303 +++
 spec/unit/provider/remote_file/local_file_spec.rb  |   61 +
 spec/unit/provider/remote_file_spec.rb             |   62 +
 spec/unit/provider/route_spec.rb                   |  243 ++
 spec/unit/provider/ruby_block_spec.rb              |   46 +
 spec/unit/provider/script_spec.rb                  |   96 +
 spec/unit/provider/service/arch_service_spec.rb    |  330 +++
 spec/unit/provider/service/debian_service_spec.rb  |  344 +++
 spec/unit/provider/service/freebsd_service_spec.rb |  379 +++
 spec/unit/provider/service/gentoo_service_spec.rb  |  144 ++
 spec/unit/provider/service/init_service_spec.rb    |  235 ++
 spec/unit/provider/service/insserv_service_spec.rb |   76 +
 .../provider/service/invokercd_service_spec.rb     |  212 ++
 spec/unit/provider/service/macosx_spec.rb          |  253 ++
 spec/unit/provider/service/redhat_spec.rb          |  156 ++
 spec/unit/provider/service/simple_service_spec.rb  |  171 ++
 .../provider/service/solaris_smf_service_spec.rb   |  143 ++
 spec/unit/provider/service/systemd_service_spec.rb |  239 ++
 spec/unit/provider/service/upstart_service_spec.rb |  314 +++
 spec/unit/provider/service/windows_spec.rb         |  235 ++
 spec/unit/provider/service_spec.rb                 |  169 ++
 spec/unit/provider/subversion_spec.rb              |  281 +++
 spec/unit/provider/template/content_spec.rb        |   78 +
 spec/unit/provider/template_spec.rb                |   88 +
 spec/unit/provider/user/dscl_spec.rb               |  480 ++++
 spec/unit/provider/user/pw_spec.rb                 |  235 ++
 spec/unit/provider/user/solaris_spec.rb            |   80 +
 spec/unit/provider/user/useradd_spec.rb            |   40 +
 spec/unit/provider/user/windows_spec.rb            |  178 ++
 spec/unit/provider/user_spec.rb                    |  460 ++++
 spec/unit/provider_spec.rb                         |  169 ++
 spec/unit/recipe_spec.rb                           |  270 +++
 spec/unit/registry_helper_spec.rb                  |  376 +++
 spec/unit/resource/apt_package_spec.rb             |   43 +
 spec/unit/resource/bash_spec.rb                    |   40 +
 spec/unit/resource/batch_spec.rb                   |   48 +
 spec/unit/resource/breakpoint_spec.rb              |   43 +
 spec/unit/resource/chef_gem_spec.rb                |   49 +
 .../conditional_action_not_nothing_spec.rb         |   45 +
 spec/unit/resource/conditional_spec.rb             |  147 ++
 spec/unit/resource/cookbook_file_spec.rb           |   89 +
 spec/unit/resource/cron_spec.rb                    |  181 ++
 spec/unit/resource/csh_spec.rb                     |   40 +
 spec/unit/resource/deploy_revision_spec.rb         |   47 +
 spec/unit/resource/deploy_spec.rb                  |  273 +++
 spec/unit/resource/directory_spec.rb               |   82 +
 spec/unit/resource/dpkg_package_spec.rb            |   38 +
 spec/unit/resource/easy_install_package_spec.rb    |   48 +
 spec/unit/resource/env_spec.rb                     |   85 +
 spec/unit/resource/erl_call_spec.rb                |   81 +
 spec/unit/resource/execute_spec.rb                 |   26 +
 spec/unit/resource/file_spec.rb                    |  116 +
 spec/unit/resource/freebsd_package_spec.rb         |   39 +
 spec/unit/resource/gem_package_spec.rb             |   49 +
 spec/unit/resource/git_spec.rb                     |   46 +
 spec/unit/resource/group_spec.rb                   |  157 ++
 spec/unit/resource/http_request_spec.rb            |   59 +
 spec/unit/resource/ifconfig_spec.rb                |  105 +
 spec/unit/resource/ips_package_spec.rb             |   43 +
 spec/unit/resource/link_spec.rb                    |  119 +
 spec/unit/resource/log_spec.rb                     |   73 +
 spec/unit/resource/macports_package_spec.rb        |   37 +
 spec/unit/resource/mdadm_spec.rb                   |  102 +
 spec/unit/resource/mount_spec.rb                   |  195 ++
 spec/unit/resource/ohai_spec.rb                    |   62 +
 spec/unit/resource/package_spec.rb                 |   80 +
 spec/unit/resource/pacman_package_spec.rb          |   38 +
 spec/unit/resource/perl_spec.rb                    |   40 +
 spec/unit/resource/portage_package_spec.rb         |   38 +
 spec/unit/resource/powershell_spec.rb              |   48 +
 spec/unit/resource/python_spec.rb                  |   40 +
 spec/unit/resource/registry_key_spec.rb            |  171 ++
 spec/unit/resource/remote_directory_spec.rb        |   97 +
 spec/unit/resource/remote_file_spec.rb             |  163 ++
 spec/unit/resource/route_spec.rb                   |  107 +
 spec/unit/resource/rpm_package_spec.rb             |   38 +
 spec/unit/resource/ruby_block_spec.rb              |   61 +
 spec/unit/resource/ruby_spec.rb                    |   40 +
 spec/unit/resource/scm_spec.rb                     |  172 ++
 spec/unit/resource/script_spec.rb                  |   46 +
 spec/unit/resource/service_spec.rb                 |  176 ++
 spec/unit/resource/smartos_package_spec.rb         |   38 +
 spec/unit/resource/solaris_package_spec.rb         |   57 +
 spec/unit/resource/subversion_spec.rb              |   58 +
 spec/unit/resource/template_spec.rb                |  211 ++
 spec/unit/resource/timestamped_deploy_spec.rb      |   28 +
 spec/unit/resource/user_spec.rb                    |  129 ++
 spec/unit/resource/yum_package_spec.rb             |   85 +
 .../resource_collection/stepable_iterator_spec.rb  |  144 ++
 spec/unit/resource_collection_spec.rb              |  286 +++
 spec/unit/resource_definition_spec.rb              |  119 +
 spec/unit/resource_platform_map_spec.rb            |  164 ++
 spec/unit/resource_reporter_spec.rb                |  631 +++++
 spec/unit/resource_spec.rb                         |  913 ++++++++
 spec/unit/rest/auth_credentials_spec.rb            |  325 +++
 spec/unit/rest_spec.rb                             |  632 +++++
 spec/unit/role_spec.rb                             |  328 +++
 spec/unit/run_context/cookbook_compiler_spec.rb    |  174 ++
 spec/unit/run_context_spec.rb                      |  119 +
 spec/unit/run_list/run_list_expansion_spec.rb      |  129 ++
 spec/unit/run_list/run_list_item_spec.rb           |  117 +
 spec/unit/run_list/versioned_recipe_list_spec.rb   |  123 +
 spec/unit/run_list_spec.rb                         |  312 +++
 spec/unit/run_lock_spec.rb                         |   40 +
 spec/unit/run_status_spec.rb                       |  145 ++
 spec/unit/runner_spec.rb                           |  402 ++++
 spec/unit/scan_access_control_spec.rb              |  184 ++
 spec/unit/search/query_spec.rb                     |   99 +
 spec/unit/shell/model_wrapper_spec.rb              |   97 +
 spec/unit/shell/shell_ext_spec.rb                  |  153 ++
 spec/unit/shell/shell_session_spec.rb              |  154 ++
 spec/unit/shell_out_spec.rb                        |   18 +
 spec/unit/shell_spec.rb                            |  161 ++
 spec/unit/user_spec.rb                             |  255 ++
 spec/unit/util/backup_spec.rb                      |  142 ++
 spec/unit/util/diff_spec.rb                        |  579 +++++
 spec/unit/util/file_edit_spec.rb                   |  135 ++
 spec/unit/util/selinux_spec.rb                     |  172 ++
 spec/unit/version/platform_spec.rb                 |   61 +
 spec/unit/version_class_spec.rb                    |  172 ++
 spec/unit/version_constraint/platform_spec.rb      |   46 +
 spec/unit/version_constraint_spec.rb               |  139 ++
 spec/unit/windows_service_spec.rb                  |   54 +
 tasks/rspec.rb                                     |   75 +
 1259 files changed, 117331 insertions(+), 16253 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..523fadc
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,155 @@
+# Contributing to Chef
+
+We are glad you want to contribute to Chef! The first step is the desire to improve the project.
+
+You can find the answers to additional frequently asked questions [on the wiki](http://wiki.opscode.com/display/chef/How+to+Contribute).
+
+## Quick-contribute
+
+*   Create an account on our [bug tracker](http://tickets.opscode.com)
+*   Sign our contributor agreement (CLA) [
+online](https://secure.echosign.com/public/hostedForm?formid=PJIF5694K6L)
+    (keep reading if you're contributing on behalf of your employer)
+* Create a ticket for your change on the [bug tracker](http://tickets.opscode.com)
+* Link to your patch as a rebased git branch or pull request from the ticket
+* Resolve the ticket as fixed
+
+We regularly review contributions and will get back to you if we have any suggestions or concerns.
+
+## The Apache License and the CLA/CCLA
+
+Licensing is very important to open source projects, it helps ensure the software continues to be available under the terms that the author desired.
+Chef uses the Apache 2.0 license to strike a balance between open contribution and allowing you to use the software however you would like to.
+
+The license tells you what rights you have that are provided by the copyright holder. It is important that the contributor fully understands what rights
+they are licensing and agrees to them. Sometimes the copyright holder isn't the contributor, most often when the contributor is doing work for a company.
+
+To make a good faith effort to ensure these criteria are met, Opscode requires a Contributor License Agreement (CLA) or a Corporate Contributor License
+Agreement (CCLA) for all contributions. This is without exception due to some matters not being related to copyright and to avoid having to continually
+check with our lawyers about small patches.
+
+It only takes a few minutes to complete a CLA, and you retain the copyright to your contribution.
+
+You can complete our contributor agreement (CLA) [
+online](https://secure.echosign.com/public/hostedForm?formid=PJIF5694K6L).  If you're contributing on behalf of your employer, have
+your employer fill out our [Corporate CLA](https://secure.echosign.com/public/hostedForm?formid=PIE6C7AX856) instead.
+
+## Ticket Tracker (JIRA)
+
+The [ticket tracker](http://tickets.opscode.com) is the most important documentation for the code base. It provides significant historical information,
+such as:
+
+* Which release a bug fix is included in
+* Discussion regarding the design and merits of features
+* Error output to aid in finding similar bugs
+
+Each ticket should aim to fix one bug or add one feature.
+
+## Using git
+
+You can get a quick copy of the chef repository by running `git clone git://github.com/opscode/chef.git`.
+
+For collaboration purposes, it is best if you create a Github account and fork the repository to your own account.
+Once you do this you will be able to push your changes to your Github repository for others to see and use.
+
+### Branches and Commits
+
+You should submit your patch as a git branch named after the ticket, such as CHEF-1337.
+This is called a _topic branch_ and allows users to associate a branch of code with the ticket.
+
+It is a best practice to have your commit message have a _summary line_ that includes the ticket number,
+followed by an empty line and then a brief description of the commit. This also helps other contributors
+understand the purpose of changes to the code.
+
+    CHEF-3435: Create deploy dirs before calling scm_provider
+
+    The SCM providers have an assertation that requires the deploy directory to
+    exist. The deploy provider will create missing directories, we don't converge
+    the actions before we call run_action against the SCM provider, so it is not
+    yet created. This ensures we run any converge actions waiting before we call
+    the SCM provider.
+
+Remember that not all users use Chef in the same way or on the same operating systems as you, so it is
+helpful to be clear about your use case and change so they can understand it even when it doesn't apply to them.
+
+### Github and Pull Requests
+
+All of Opscode's open source projects are available on [Github](http://www.github.com/opscode).
+
+We don't require you to use Github, and we will even take patch diffs attached to tickets on the tracker.
+However Github has a lot of convenient features, such as being able to see a diff of changes between a
+pull request and the main repository quickly without downloading the branch.
+
+If you do choose to use a pull request, please provide a link to the pull request from the ticket __and__
+a link to the ticket from the pull request. Because pull requests only have two states, open and closed,
+we can't easily filter pull requests that are waiting for a reply from the author for various reasons.
+
+### More information
+
+Additional help with git is available on the [Working with Git](http://wiki.opscode.com/display/chef/Working+with+Git) wiki page.
+
+## Functional and Unit Tests
+
+There are rspec unit tests in the 'spec' directory. If you don't have rspec already installed, you can use the 'bundler'
+gem to help you get the necessary prerequisites by running `sudo gem install bundler` and then `bundle install` from
+the chef respository. You can run the chef client spec tests by running `rspec spec/*` or `rake spec` from the chef
+directory of the chef repository.
+
+These tests should pass successfully on Ruby 1.8 and 1.9 on all of the platforms that Chef runs on. It is good to run the tests
+once on your system before you get started to ensure they all pass so you have a valid baseline. After you write your patch,
+run the tests again to see if they all pass.
+
+If any don't pass, investigate them before submitting your patch.
+
+These tests don't modify your system, and sometimes tests fail because a command that would be run has changed because of your
+patch. This should be a simple fix. Other times the failure can show you that an important feature no longer works because of
+your change.
+
+Any new feature should have unit tests included with the patch with good code coverage to help protect it from future changes.
+Similarly, patches that fix a bug or regression should have a _regression test_. Simply put, this is a test that would fail
+without your patch but passes with it. The goal is to ensure this bug doesn't regress in the future. Consider a regular
+expression that doesn't match a certain pattern that it should, so you provide a patch and a test to ensure that the part
+of the code that uses this regular expression works as expected. Later another contributor may modify this regular expression
+in a way that breaks your use cases. The test you wrote will fail, signalling to them to research your ticket and use case
+and accounting for it.
+
+## Code Review
+
+Opscode regularly reviews code contributions and provides suggestions for improvement in the code itself or the implementation.
+
+We find contributions by searching the ticket tracker for _resolved_ tickets with a status of _fixed_. If we have feedback we will
+reopen the ticket and you should resolve it again when you've made the changes or have a response to our feedback. When we believe
+the patch is ready to be merged, we update the status to _Fix Reviewed_.
+
+Depending on the project, these tickets are then merged within a week or two, depending on the current release cycle. At this
+point the ticket status will be updated to _Fix Committed_ or _Closed_.
+
+Please see the [Code Review](http://wiki.opscode.com/display/chef/Code+Review) page on the wiki for additional information.
+
+## Release Cycle
+
+The versioning for the Chef project is X.Y.Z.
+
+* X is a major release, which may not be fully compatible with prior major releases
+* Y is a minor release, which adds both new features and bug fixes
+* Z is a patch release, which adds just bug fixes
+
+Major releases and have historically been once a year. Minor releases for Chef average every two months and patch releases come as needed.
+
+There are usually beta releases and release candidates (RC) of major and minor releases announced on
+the [chef-dev mailing list](http://lists.opscode.com/sympa/info/chef-dev). Once an RC is released, we wait at least three
+days to allow for testing for regressions before the final release. If a blocking regression is found then another RC is made containing
+the fix and the timer is reset.
+
+Once the official release is made, the release notes are available on the [Opscode blog](http://www.opscode.com/blog).
+
+## Working with the community
+
+These resources will help you learn more about Chef and connect to other members of the Chef community:
+
+* [chef](http://lists.opscode.com/sympa/info/chef) and [chef-dev](http://lists.opscode.com/sympa/info/chef-dev) mailing lists
+* #chef and #chef-hacking IRC channels on irc.freenode.net
+* [Community Cookbook site](http://community.opscode.com)
+* [Chef wiki](http://wiki.opscode.com/display/chef)
+* Opscode Chef [product page](http://www.opscode.com/chef)
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..78328c4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,89 @@
+# Chef
+
+* Documentation: [http://docs.opscode.com](http://docs.opscode.com)
+* Source: [http://github.com/opscode/chef/tree/master](http://github.com/opscode/chef/tree/master)
+* Tickets/Issues: [http://tickets.opscode.com](http://tickets.opscode.com)
+* IRC: `#chef` and `#chef-hacking` on Freenode
+* Mailing list: [http://lists.opscode.com](http://lists.opscode.com)
+
+Chef is a configuration management tool designed to bring automation to your
+entire infrastructure.
+
+The [Chef Wiki](http://wiki.opscode.com/display/chef/Home) is the definitive
+source of user documentation.
+
+This README focuses on developers who want to modify Chef source code. For
+users who just want to run the latest and greatest Chef development version in
+their environment, see the
+[Installing Chef from HEAD](http://wiki.opscode.com/display/chef/Installing+Chef+from+HEAD)
+page on the wiki.
+
+## Contributing/Development
+
+Before working on the code, if you plan to contribute your changes, you need to
+read the
+[Opscode Contributing document](http://wiki.opscode.com/display/chef/How+to+Contribute).
+
+You will also need to set up the repository with the appropriate branches. We
+document the process on the
+[Working with Git](http://wiki.opscode.com/display/chef/Working+with+git) page
+of the Chef wiki.
+
+Once your repository is set up, you can start working on the code. We do use
+TDD with RSpec, so you'll need to get a development environment running.
+
+### Requirements
+
+Ruby 1.8.7+ (As of 2012-05-25 Ruby 1.8.6 should still work, except for CHEF-2329.)
+
+### Environment
+
+In order to have a development environment where changes to the Chef code can
+be tested, we'll need to install a few things after setting up the Git
+repository.
+
+#### Non-Gem Dependencies
+
+Install these via your platform's preferred method; for example apt, yum,
+ports, emerge, etc.
+
+* [Git](http://git-scm.com/)
+* GCC and C Standard Libraries, header files, etc. (i.e., build-essential on
+debian/ubuntu)
+* Ruby development package
+
+#### Runtime Rubygem Dependencies
+
+First you'll need [bundler](http://github.com/carlhuda/bundler) which can
+be installed with a simple `gem install bundler`. Afterwords, do the following:
+
+    bundle install
+
+## Testing
+
+We use RSpec for unit/spec tests. It is not necessary to start the development
+environment to run the specs--they are completely standalone.
+
+    rake spec
+
+# License
+
+Chef - A configuration management system
+
+|                      |                                          |
+|:---------------------|:-----------------------------------------|
+| **Author:**          | Adam Jacob (<adam at opscode.com>)
+| **Copyright:**       | Copyright (c) 2008-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.
diff --git a/README.rdoc b/README.rdoc
deleted file mode 100644
index 43feffe..0000000
--- a/README.rdoc
+++ /dev/null
@@ -1,177 +0,0 @@
-= Chef
-
-== DESCRIPTION:
-
-Chef is a configuration management tool designed to bring automation to your entire infrastructure. 
-
-The Chef Wiki is the definitive source of user documentation. 
-
-* http://wiki.opscode.com/display/chef/Home
-
-This README focuses on developers who want to modify Chef source code. For users who just want to run the latest and greatest Chef development version in their environment, see:
-
-* http://wiki.opscode.com/display/chef/Installing+Chef+from+HEAD
-
-== DEVELOPMENT:
-
-Before working on the code, if you plan to contribute your changes, you need to read the Opscode Contributing document.
-
-* http://wiki.opscode.com/display/opscode/Contributing
-
-You will also need to set up the repository with the appropriate branches. We document the process on the Chef Wiki.
-
-* http://wiki.opscode.com/display/opscode/Working+with+Git
-
-Once your repository is set up, you can start working on the code. We do use BDD/TDD with RSpec and Cucumber, so you'll need to get a development environment running.
-
-== REQUIREMENTS:
-
-Ruby 1.8.7+ [1]
-
-[1] As of 2012-05-25 Ruby 1.8.6 should still work, except for CHEF-2329.
-
-== ENVIRONMENT:
-
-In order to have a development environment where changes to the Chef code can be tested, we'll need to install a few things after setting up the Git repository.
-
-=== Non-Gem Dependencies
-
-Install these via your platform's preferred method; for example apt, yum, ports, emerge, etc.
-
-* Git[http://git-scm.com/]
-* Erlang/OTP[http://www.erlang.org/]
-* CouchDB[http://couchdb.apache.org/]
-* RabbitMQ[http://www.rabbitmq.com/]
-* GCC and C Standard Libraries, header files, etc. (i.e., build-essential on debian/ubuntu)
-* Ruby development package
-
-=== Runtime Rubygem Dependencies
-==== Chef Client and Solo
-* ohai
-* bunny
-* erubis
-* highline
-* json (1.4.4 - 1.4.6)
-* mixlib-authentication
-* mixlib-cli
-* mixlib-config
-* mixlib-log
-* moneta
-* rest-client
-* uuidtools
-* merb-core
-* net-ssh
-* fog
-
-==== Chef Server, WebUI and Solr
-All of the above, plus the following:
-* coderay
-* haml
-* merb-assets
-* merb-core
-* merb-haml
-* merb-helpers
-* merb-param-protection
-* ruby-openid
-* thin
-
-=== Development Rubygem Dependencies
-* rake[http://rake.rubyforge.org/]
-* rspec[http://rspec.info/]
-* cucumber[http://cukes.info/]
-
-Ohai is also by Opscode and available on GitHub, http://github.com/opscode/ohai/tree/master.
-
-== Starting the Environment:
-
-=== On Mac OS X:
-For ease of debugging, Chef includes a script to start each of the required
-daemons in a separate Terminal.app tab via applescript:
-
-  scripts/mac-dev-start features
-
-=== On Linux and BSD
-
-run the dev:features rake task. You may need to run it as root depending on how
-your system is configured.
-
-  rake dev:features
-
-=== Daemons
-After starting the environment, you should have the following processes running:
-* couchdb             listening on port 5984
-* rabbitmq            listening on port 5672
-* solr                listening on port 8983
-* chef-solr-indexer   connected as a client to rabbitmq
-* chef-server         listening on port 4000
-* chef-server-webui   listening on port 4040
-
-You'll know its running when you see:
-
-    merb : chef-server (api) : worker (port 4000) ~ Starting Thin at port 4000
-    merb : chef-server (api) : worker (port 4000) ~ Using Thin adapter on host 0.0.0.0 and port 4000.
-    merb : chef-server (api) : worker (port 4000) ~ Successfully bound to port 4000
-
-You'll want to leave this terminal running the dev environment.
-
-=== Web Interface:
-
-With the dev environment running, you can now access the web interface via http://localhost:4040/.
-
-== Spec testing:
-
-We use RSpec for unit/spec tests. It is not necessary to start the development
-environment to run the specs--they are completely standalone.
-
-  rake spec
-
-== Integration testing:
-
-We test integration with Cucumber. To run the full suite, run the rake task:
-
-  rake features
-
-Subsets of the integration tests can be run with the various tasks in the
-features namespace. To see the full list, run
-
-  rake -T
-
-To run individual feature tests, you can take advantage of cucumber's tagging
-support. Tag the feature you wish to run (tags are denoted with a leading `@'
-sign), then use the cucumber command:
-
-  cucumber -t @my_tag
-
-== LINKS:
-
-Source:
-
-* http://github.com/opscode/chef/tree/master
-
-Tickets/Issues:
-
-* http://tickets.opscode.com/
-
-Documentation:
-
-* http://wiki.opscode.com/display/chef/Home/
-
-= LICENSE:
-
-Chef - A configuration management system
-
-Author:: Adam Jacob (<adam at opscode.com>)
-Copyright:: Copyright (c) 2008-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.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..7e4d5bc
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,142 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require File.dirname(__FILE__) + '/lib/chef/version'
+
+require 'rubygems'
+require 'rubygems/package_task'
+require 'rdoc/task'
+require './tasks/rspec.rb'
+
+GEM_NAME = "chef"
+
+# This has to be here or else the docs get generated *after* the gem is created
+task :gem => 'docs:all'
+
+Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
+  gemspec = eval(IO.read(gemspec_path))
+  Gem::PackageTask.new(gemspec).define
+end
+
+begin
+  require 'sdoc'
+
+  Rake::RDocTask.new do |rdoc|
+    rdoc.title = "Chef Ruby API Documentation"
+    rdoc.main = "README.rdoc"
+    rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
+    rdoc.template = 'direct' # lighter template
+    rdoc.rdoc_files.include("README.rdoc", "LICENSE", "spec/tiny_server.rb", "lib/**/*.rb")
+    rdoc.rdoc_dir = "rdoc"
+  end
+rescue LoadError
+  puts "sdoc is not available. (sudo) gem install sdoc to generate rdoc documentation."
+rescue TypeError
+  puts "sdoc is not working on ruby-2.0.0 and throwing an odd TypeError, rdoc generation will be disabled on ruby 2.0 until that gets fixed."
+end
+
+task :install => :package do
+  sh %{gem install pkg/#{GEM_NAME}-#{Chef::VERSION}.gem --no-rdoc --no-ri}
+end
+
+task :uninstall do
+  sh %{gem uninstall #{GEM_NAME} -x -v #{Chef::VERSION} }
+end
+
+desc "Build it, tag it and ship it"
+task :ship => :gem do
+  sh("git tag #{Chef::VERSION}")
+  sh("git push opscode --tags")
+  Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
+    sh("gem push #{built_gem}")
+  end
+end
+
+RONN_OPTS = "--manual='Chef Manual' --organization='Chef #{Chef::VERSION}' --date='#{Time.new.strftime('%Y-%m-%d')}'"
+
+namespace :docs do
+  desc "Regenerate HTML manual from markdown"
+  task :html
+
+  desc "Regenerate help topics from man pages"
+  task :list do
+    topics = Array.new
+
+    Dir['distro/common/man/man1/*.1'].each do |man|
+      topics << File.basename(man, '.1')
+    end
+
+    File.open('lib/chef/knife/help_topics.rb', 'w') do |f|
+      f.puts "# Do not edit this file by hand"
+      f.puts "# This file is autogenerated by the docs:list rake task from the available manpages\n\n"
+
+      f.puts "HELP_TOPICS = #{topics.inspect}"
+    end
+  end
+
+  # we can have ronn in the path, but not in the bundle, require both
+  ronn_in_bundle = true
+  begin
+    require 'ronn'
+  rescue LoadError
+    ronn_in_bundle = false
+  end
+
+  if ronn_in_bundle && system('which ronn > /dev/null')
+    ['distro/common/markdown/man1/*.mkd', 'distro/common/markdown/man8/*.mkd'].each do |dir|
+      Dir[dir].each do |mkd|
+        basename = File.basename(mkd, '.mkd')
+        if dir =~ /man1/
+          htmlfile = "distro/common/html/#{basename}.1.html"
+        elsif dir =~ /man8/
+          htmlfile = "distro/common/html/#{basename}.8.html"
+        end
+
+         file(htmlfile => [mkd, 'lib/chef/version.rb']) do
+           sh "ronn -5 #{RONN_OPTS} --style=toc #{mkd} --pipe > #{htmlfile}"
+         end
+         task :html => htmlfile
+      end
+    end
+  else
+    puts "get with the program and install ronn"
+  end
+
+  task :all => [:list, :html]
+end
+
+task :docs => "docs:all"
+
+begin
+  require 'yard'
+  DOC_FILES = [ "README.rdoc", "LICENSE", "spec/tiny_server.rb", "lib/**/*.rb" ]
+  namespace :yard do
+    desc "Create YARD documentation"
+
+    YARD::Rake::YardocTask.new(:html) do |t|
+      t.files = DOC_FILES
+      t.options = ['--format', 'html']
+    end
+  end
+
+rescue LoadError
+  puts "yard is not available. (sudo) gem install yard to generate yard documentation."
+end
+
+
diff --git a/bin/chef-apply b/bin/chef-apply
new file mode 100755
index 0000000..c617129
--- /dev/null
+++ b/bin/chef-apply
@@ -0,0 +1,25 @@
+#!/usr/bin/env ruby
+#
+# ./chef-apply - Run a single chef recipe
+#
+# Author:: Bryan W. Berry (<bryan.berry at gmail.com>)
+# Copyright:: Copyright (c) 2012 Bryan W. Berry
+# 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 'rubygems'
+$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
+require 'chef/application/apply'
+
+Chef::Application::Apply.new.run
diff --git a/bin/chef-client b/bin/chef-client
index bfd5544..5b2b058 100755
--- a/bin/chef-client
+++ b/bin/chef-client
@@ -9,9 +9,9 @@
 # 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.
diff --git a/bin/chef-service-manager b/bin/chef-service-manager
new file mode 100755
index 0000000..3d48f20
--- /dev/null
+++ b/bin/chef-service-manager
@@ -0,0 +1,37 @@
+#!/usr/bin/env ruby
+#
+# ./chef-service-manager - Control chef-service on Windows platforms.
+#
+# Author:: Serdar Sutay (serdar at opscode.com)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'rubygems'
+$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
+require 'chef'
+require 'chef/application/windows_service_manager'
+
+if Chef::Platform.windows?
+  chef_client_service = {
+    :service_name => "chef-client",
+    :service_display_name => "Chef Client Service",
+    :service_description => "Runs Opscode Chef Client on regular, configurable intervals.",
+    :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../lib/chef/application/windows_service.rb'))
+  }
+  Chef::Application::WindowsServiceManager.new(chef_client_service).run
+else
+  puts "chef-service-manager is only available on Windows platforms."
+end
+
diff --git a/bin/chef-shell b/bin/chef-shell
new file mode 100755
index 0000000..f334635
--- /dev/null
+++ b/bin/chef-shell
@@ -0,0 +1,37 @@
+#!/usr/bin/env ruby
+#
+# ./chef-shell - Run the Chef REPL (Shell)
+#
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009
+# 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.
+
+begin
+  require "rubygems"
+rescue LoadError
+end
+
+require "irb"
+require "irb/completion"
+require 'irb/ext/save-history'
+
+$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
+
+require "chef/shell"
+
+# On Windows only, enable irb --noreadline because of input problems --
+# See CHEF-3284.
+IRB.conf[:USE_READLINE] = false if Chef::Platform::windows?
+Shell.start
diff --git a/bin/chef-solo b/bin/chef-solo
index 69891ac..958ab21 100755
--- a/bin/chef-solo
+++ b/bin/chef-solo
@@ -9,9 +9,9 @@
 # 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.
diff --git a/bin/knife b/bin/knife
index 1e27699..6173aad 100755
--- a/bin/knife
+++ b/bin/knife
@@ -1,6 +1,6 @@
 #!/usr/bin/env ruby
 #
-# ./knife - Chef CLI interface 
+# ./knife - Chef CLI interface
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Copyright:: Copyright (c) 2009 Opscode, Inc.
@@ -9,9 +9,9 @@
 # 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.
diff --git a/bin/shef b/bin/shef
index 9f42aa2..0862198 100755
--- a/bin/shef
+++ b/bin/shef
@@ -1,6 +1,6 @@
 #!/usr/bin/env ruby
 #
-# ./shef - Run the shef REPL
+# ./chef-shell - Run the Chef REPL (Shell)
 #
 # Author:: Daniel DeLeo (<dan at kallistec.com>)
 # Copyright:: Copyright (c) 2009
@@ -9,9 +9,9 @@
 # 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.
@@ -29,6 +29,7 @@ require 'irb/ext/save-history'
 
 $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
 
-require "chef/shef"
+require "chef/shell"
 
-Shef.start
+Chef::Log.warn("DEPRECATED: The 'shef' program is renamed to 'chef-shell'")
+Shell.start
diff --git a/distro/arch/etc/rc.d/chef-client b/distro/arch/etc/rc.d/chef-client
index 36f00ba..6e2feb2 100644
--- a/distro/arch/etc/rc.d/chef-client
+++ b/distro/arch/etc/rc.d/chef-client
@@ -5,9 +5,9 @@
 # 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.
diff --git a/distro/arch/etc/rc.d/chef-server b/distro/arch/etc/rc.d/chef-server
index 59f926c..1ffbf8d 100644
--- a/distro/arch/etc/rc.d/chef-server
+++ b/distro/arch/etc/rc.d/chef-server
@@ -5,9 +5,9 @@
 # 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.
diff --git a/distro/arch/etc/rc.d/chef-server-webui b/distro/arch/etc/rc.d/chef-server-webui
index 79a9b77..bec1851 100644
--- a/distro/arch/etc/rc.d/chef-server-webui
+++ b/distro/arch/etc/rc.d/chef-server-webui
@@ -5,9 +5,9 @@
 # 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.
diff --git a/distro/arch/etc/rc.d/chef-solr b/distro/arch/etc/rc.d/chef-solr
index a3682a4..10bd15e 100644
--- a/distro/arch/etc/rc.d/chef-solr
+++ b/distro/arch/etc/rc.d/chef-solr
@@ -5,9 +5,9 @@
 # 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.
diff --git a/distro/common/html/chef-client.8.html b/distro/common/html/chef-client.8.html
index 8d2ac63..f8ae043 100644
--- a/distro/common/html/chef-client.8.html
+++ b/distro/common/html/chef-client.8.html
@@ -92,8 +92,6 @@
 <dt><code>-l</code>, <code>--log_level LEVEL</code></dt><dd>Set the log level (debug, info, warn, error, fatal)</dd>
 <dt><code>-L</code>, <code>--logfile LOGLOCATION</code></dt><dd>Set the log file location, defaults to STDOUT - recommended for
 daemonizing</dd>
-<dt><code>-V</code>, <code>--verbose</code></dt><dd>Ensures logging goes to STDOUT as well as  to  other  configured
-log location(s).</dd>
 <dt><code>-N</code>, <code>--node-name NODE_NAME</code></dt><dd>The node name for this client</dd>
 <dt><code>-o</code>, <code>--override-runlist</code></dt><dd>Replace current run list with specified items</dd>
 <dt><code>-K</code>, <code>--validation_key KEY_FILE</code></dt><dd>Set the validation key file location, used for registering new clients</dd>
@@ -126,9 +124,9 @@ wiki, http://wiki.opscode.com/display/chef/Home.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>Chef was written by Adam Jacob <a href="&#x6d;&#x61;ilto&#x3a;&#x61;d&#x61;m&#x40;ospc&#x6f;&#x64;&#x65;.c&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;&#x61;m@&#x6f;&#x73;&#x70;co&#x64;&#x65;.&#x63;om</a> of Opscode
+<p>Chef was written by Adam Jacob <a href="ma&#x69;lto&#x3a;ad&#x61;m&#x40;o&#x73;&#x70;c&#x6f;&#x64;e.c&#x6f;m" data-bare-link="true">&#x61;&#x64;a&#x6d;@o&#x73;pc&#x6f;&#x64;e&#x2e;&#x63;o&#x6d;</a> of Opscode
 (http://www.opscode.com),  with contributions from the community.  This
-manual page was written by Joshua Timberman  <a href="&#x6d;ai&#x6c;to&#x3a;j&#x6f;s&#x68;&#x75;&#x61;@&#x6f;p&#x73;co&#x64;e.&#x63;om" data-bare-link="true">j&#x6f;&#x73;hu&#x61;&#x40;o&#x70;sc&#x6f;&#x64;&#x65;&#x2e;com</a>  with
+manual page was written by Joshua Timberman  <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;:jo&#x73;hua&#x40;o&#x70;sco&#x64;&#x65;&#x2e;&#x63;&#x6f;m" data-bare-link="true">&#x6a;o&#x73;hu&#x61;@&#x6f;p&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a>  with
 help2man.  Permission  is  granted  to copy, distribute and / or modify
 this document under the terms of the Apache 2.0 License.</p>
 
@@ -137,8 +135,8 @@ found in /usr/share/common-licenses/Apache-2.0.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>chef-client(8)</li>
   </ol>
 
diff --git a/distro/common/html/chef-expander.8.html b/distro/common/html/chef-expander.8.html
index 079204f..2701124 100644
--- a/distro/common/html/chef-expander.8.html
+++ b/distro/common/html/chef-expander.8.html
@@ -143,9 +143,9 @@ wiki, http://wiki.opscode.com/display/chef/Home.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>Chef was written by Adam Jacob <a href="&#x6d;&#x61;ilto&#x3a;&#x61;d&#x61;m&#x40;ospc&#x6f;&#x64;&#x65;.c&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;&#x61;m@&#x6f;&#x73;&#x70;co&#x64;&#x65;.&#x63;om</a> of Opscode
+<p>Chef was written by Adam Jacob <a href="ma&#x69;lto&#x3a;ad&#x61;m&#x40;o&#x73;&#x70;c&#x6f;&#x64;e.c&#x6f;m" data-bare-link="true">&#x61;&#x64;a&#x6d;@o&#x73;pc&#x6f;&#x64;e&#x2e;&#x63;o&#x6d;</a> of Opscode
 (http://www.opscode.com),  with contributions from the community. This
-manual page was created by Nuo Yan <a href="&#x6d;ai&#x6c;to&#x3a;n&#x75;o&#x40;&#x6f;&#x70;s&#x63;o&#x64;e.&#x63;om" data-bare-link="true">&#x6e;uo@&#x6f;&#x70;sc&#x6f;&#x64;e&#x2e;co&#x6d;</a>. Permission is
+manual page was created by Nuo Yan <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;:nu&#x6f;@op&#x73;c&#x6f;de.&#x63;&#x6f;&#x6d;" data-bare-link="true">&#x6e;&#x75;o&#x40;o&#x70;sc&#x6f;d&#x65;.&#x63;&#x6f;&#x6d;</a>. Permission is
 granted to copy, distribute and / or modify this document under the
 terms of the Apache 2.0 License.</p>
 
@@ -154,8 +154,8 @@ found in /usr/share/common-licenses/Apache-2.0.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>chef-expander(8)</li>
   </ol>
 
diff --git a/distro/common/html/chef-expanderctl.8.html b/distro/common/html/chef-expanderctl.8.html
index 7986749..83460fd 100644
--- a/distro/common/html/chef-expanderctl.8.html
+++ b/distro/common/html/chef-expanderctl.8.html
@@ -125,9 +125,9 @@ wiki, http://wiki.opscode.com/display/chef/Home.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>Chef was written by Adam Jacob <a href="mai&#x6c;&#x74;o:a&#x64;&#x61;&#x6d;&#x40;o&#x73;&#x70;co&#x64;e&#x2e;&#x63;om" data-bare-link="true">adam&#x40;o&#x73;&#x70;c&#x6f;de.com</a> of Opscode
+<p>Chef was written by Adam Jacob <a href="&#x6d;ai&#x6c;t&#x6f;:a&#x64;a&#x6d;&#x40;osp&#x63;od&#x65;.c&#x6f;m" data-bare-link="true">a&#x64;&#x61;&#x6d;&#x40;ospcod&#x65;.c&#x6f;m</a> of Opscode
 (http://www.opscode.com),  with contributions from the community. This
-manual page was created by Nuo Yan <a href="m&#x61;&#x69;&#x6c;&#x74;&#x6f;:&#x6e;&#x75;o@o&#x70;sc&#x6f;&#x64;e&#x2e;c&#x6f;m" data-bare-link="true">n&#x75;&#x6f;@&#x6f;&#x70;s&#x63;&#x6f;de&#x2e;&#x63;om</a>. Permission is
+manual page was created by Nuo Yan <a href="&#x6d;&#x61;ilto&#x3a;&#x6e;&#x75;&#x6f;@&#x6f;&#x70;&#x73;&#x63;o&#x64;&#x65;&#x2e;c&#x6f;&#x6d;" data-bare-link="true">&#x6e;&#x75;&#x6f;&#x40;op&#x73;cod&#x65;.&#x63;&#x6f;&#x6d;</a>. Permission is
 granted to copy, distribute and / or modify this document under the
 terms of the Apache 2.0 License.</p>
 
@@ -136,8 +136,8 @@ found in /usr/share/common-licenses/Apache-2.0.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>chef-expanderctl(8)</li>
   </ol>
 
diff --git a/distro/common/html/chef-server-webui.8.html b/distro/common/html/chef-server-webui.8.html
index dbc53b3..c79a0a0 100644
--- a/distro/common/html/chef-server-webui.8.html
+++ b/distro/common/html/chef-server-webui.8.html
@@ -163,9 +163,9 @@ is located on the Chef wiki, http://wiki.opscode.com/display/chef/Home.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>Chef was written by Adam Jacob <a href="mai&#x6c;&#x74;&#x6f;:ada&#x6d;@&#x6f;spc&#x6f;de.com" data-bare-link="true">a&#x64;am@osp&#x63;ode.com</a> of Opscode
+<p>Chef was written by Adam Jacob <a href="&#x6d;ai&#x6c;t&#x6f;:a&#x64;a&#x6d;&#x40;osp&#x63;od&#x65;.c&#x6f;m" data-bare-link="true">a&#x64;&#x61;&#x6d;&#x40;ospcod&#x65;.c&#x6f;m</a> of Opscode
 (http://www.opscode.com), with contributions from the  community. This
-manual page was written by Joshua Timberman <a href="ma&#x69;lt&#x6f;:j&#x6f;sh&#x75;&#x61;@&#x6f;&#x70;scode.c&#x6f;&#x6d;" data-bare-link="true">&#x6a;&#x6f;&#x73;&#x68;u&#x61;&#x40;op&#x73;co&#x64;&#x65;&#x2e;c&#x6f;m</a> with
+manual page was written by Joshua Timberman <a href="&#x6d;&#x61;ilto&#x3a;&#x6a;&#x6f;&#x73;h&#x75;&#x61;&#x40;&#x6f;p&#x73;&#x63;&#x6f;d&#x65;&#x2e;&#x63;&#x6f;&#x6d;" data-bare-link="true">&#x6a;os&#x68;ua@&#x6f;p&#x73;&#x63;&#x6f;de.&#x63;&#x6f;&#x6d;</a> with
 help2man for the Debian project (but may be used by others). Permission
 is granted to copy, distribute and / or modify this document under the
 terms of the Apache 2.0 License.</p>
@@ -175,8 +175,8 @@ found in /usr/share/common-licenses/Apache-2.0.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>chef-server-webui(8)</li>
   </ol>
 
diff --git a/distro/common/html/chef-server.8.html b/distro/common/html/chef-server.8.html
index c7a1720..2db4f04 100644
--- a/distro/common/html/chef-server.8.html
+++ b/distro/common/html/chef-server.8.html
@@ -161,9 +161,9 @@ wiki, http://wiki.opscode.com/display/chef/Home.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>Chef was written by Adam Jacob <a href="&#x6d;&#x61;ilto&#x3a;&#x61;d&#x61;m&#x40;ospc&#x6f;&#x64;&#x65;.c&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;&#x61;m@&#x6f;&#x73;&#x70;co&#x64;&#x65;.&#x63;om</a> of Opscode
+<p>Chef was written by Adam Jacob <a href="&#x6d;ai&#x6c;t&#x6f;:a&#x64;a&#x6d;&#x40;osp&#x63;od&#x65;.c&#x6f;m" data-bare-link="true">a&#x64;&#x61;&#x6d;&#x40;ospcod&#x65;.c&#x6f;m</a> of Opscode
 (http://www.opscode.com),  with contributions from the community.  This
-manual page was written by Joshua Timberman  <a href="&#x6d;ai&#x6c;to&#x3a;j&#x6f;s&#x68;&#x75;&#x61;@&#x6f;p&#x73;co&#x64;e.&#x63;om" data-bare-link="true">j&#x6f;&#x73;hu&#x61;&#x40;o&#x70;sc&#x6f;&#x64;&#x65;&#x2e;com</a>  with
+manual page was written by Joshua Timberman  <a href="&#x6d;&#x61;ilto&#x3a;&#x6a;&#x6f;&#x73;h&#x75;&#x61;&#x40;&#x6f;p&#x73;&#x63;&#x6f;d&#x65;&#x2e;&#x63;&#x6f;&#x6d;" data-bare-link="true">&#x6a;os&#x68;ua@&#x6f;p&#x73;&#x63;&#x6f;de.&#x63;&#x6f;&#x6d;</a>  with
 help2man.  Permission  is  granted  to copy, distribute and / or modify
 this document under the terms of the Apache 2.0 License.</p>
 
@@ -172,8 +172,8 @@ found in /usr/share/common-licenses/Apache-2.0.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>chef-server(8)</li>
   </ol>
 
diff --git a/distro/common/html/chef-shell.1.html b/distro/common/html/chef-shell.1.html
new file mode 100644
index 0000000..bc444d5
--- /dev/null
+++ b/distro/common/html/chef-shell.1.html
@@ -0,0 +1,286 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv='content-type' value='text/html;charset=utf8'>
+  <meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
+  <title>chef-shell(1) - Interactive Chef Console</title>
+  <style type='text/css' media='all'>
+  /* style: man */
+  body#manpage {margin:0}
+  .mp {max-width:100ex;padding:0 9ex 1ex 4ex}
+  .mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
+  .mp h2 {margin:10px 0 0 0}
+  .mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
+  .mp h3 {margin:0 0 0 4ex}
+  .mp dt {margin:0;clear:left}
+  .mp dt.flush {float:left;width:8ex}
+  .mp dd {margin:0 0 0 9ex}
+  .mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
+  .mp pre {margin-bottom:20px}
+  .mp pre+h2,.mp pre+h3 {margin-top:22px}
+  .mp h2+pre,.mp h3+pre {margin-top:5px}
+  .mp img {display:block;margin:auto}
+  .mp h1.man-title {display:none}
+  .mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
+  .mp h2 {font-size:16px;line-height:1.25}
+  .mp h1 {font-size:20px;line-height:2}
+  .mp {text-align:justify;background:#fff}
+  .mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
+  .mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
+  .mp u {text-decoration:underline}
+  .mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
+  .mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
+  .mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
+  .mp b.man-ref {font-weight:normal;color:#434241}
+  .mp pre {padding:0 4ex}
+  .mp pre code {font-weight:normal;color:#434241}
+  .mp h2+pre,h3+pre {padding-left:0}
+  ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
+  ol.man-decor {width:100%}
+  ol.man-decor li.tl {text-align:left}
+  ol.man-decor li.tc {text-align:center;letter-spacing:4px}
+  ol.man-decor li.tr {text-align:right;float:right}
+  </style>
+  <style type='text/css' media='all'>
+  /* style: toc */
+  .man-navigation {display:block !important;position:fixed;top:0;left:113ex;height:100%;width:100%;padding:48px 0 0 0;border-left:1px solid #dbdbdb;background:#eee}
+  .man-navigation a,.man-navigation a:hover,.man-navigation a:link,.man-navigation a:visited {display:block;margin:0;padding:5px 2px 5px 30px;color:#999;text-decoration:none}
+  .man-navigation a:hover {color:#111;text-decoration:underline}
+  </style>
+</head>
+<!--
+  The following styles are deprecated and will be removed at some point:
+  div#man, div#man ol.man, div#man ol.head, div#man ol.man.
+
+  The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
+  .man-navigation should be used instead.
+-->
+<body id='manpage'>
+  <div class='mp' id='man'>
+
+  <div class='man-navigation' style='display:none'>
+    <a href="#NAME">NAME</a>
+    <a href="#SYNOPSIS">SYNOPSIS</a>
+    <a href="#DESCRIPTION">DESCRIPTION</a>
+    <a href="#SYNTAX">SYNTAX</a>
+    <a href="#PRIMARY-MODE">PRIMARY MODE</a>
+    <a href="#RECIPE-MODE">RECIPE MODE</a>
+    <a href="#EXAMPLES">EXAMPLES</a>
+    <a href="#BUGS">BUGS</a>
+    <a href="#SEE-ALSO">SEE ALSO</a>
+    <a href="#AUTHOR">AUTHOR</a>
+    <a href="#DOCUMENTATION">DOCUMENTATION</a>
+    <a href="#CHEF">CHEF</a>
+  </div>
+
+  <ol class='man-decor man-head man head'>
+    <li class='tl'>chef-shell(1)</li>
+    <li class='tc'>Chef Manual</li>
+    <li class='tr'>chef-shell(1)</li>
+  </ol>
+
+  <h2 id="NAME">NAME</h2>
+<p class="man-name">
+  <code>chef-shell</code> - <span class="man-whatis">Interactive Chef Console</span>
+</p>
+
+<h2 id="SYNOPSIS">SYNOPSIS</h2>
+
+<p><strong>chef-shell</strong> [<em>named configuration</em>] <em>(options)</em></p>
+
+<dl>
+<dt><code>-S</code>, <code>--server CHEF_SERVER_URL</code></dt><dd>The chef server URL</dd>
+<dt><code>-z</code>, <code>--client</code></dt><dd>chef-client mode</dd>
+<dt><code>-c</code>, <code>--config CONFIG</code></dt><dd>The configuration file to use</dd>
+<dt><code>-j</code>, <code>--json-attributes JSON_ATTRIBS</code></dt><dd>Load attributes from a JSON file or URL</dd>
+<dt><code>-l</code>, <code>--log-level LOG_LEVEL</code></dt><dd>Set the logging level</dd>
+<dt><code>-s</code>, <code>--solo</code></dt><dd>chef-solo session</dd>
+<dt><code>-a</code>, <code>--standalone</code></dt><dd>standalone session</dd>
+<dt><code>-v</code>, <code>--version</code></dt><dd>Show chef version</dd>
+<dt><code>-h</code>, <code>--help</code></dt><dd>Show command options</dd>
+</dl>
+
+
+<p>When no --config option is specified, chef-shell attempts to load a
+default configuration file:</p>
+
+<ul>
+<li>If a <em>named configuration</em> is given, chef-shell will load ~/.chef/<em>named
+configuration</em>/chef_shell.rb</li>
+<li>If no <em>named configuration</em> is given chef-shell will load
+~/.chef/chef_shell.rb if it exists</li>
+<li>chef-shell falls back to loading /etc/chef/client.rb or
+/etc/chef/solo.rb if -z or -s options are given and no chef_shell.rb
+can be found.</li>
+<li>The --config option takes precedence over implicit configuration
+paths.</li>
+</ul>
+
+
+<h2 id="DESCRIPTION">DESCRIPTION</h2>
+
+<p><code>chef-shell</code> is an <span class="man-ref">irb<span class="s">(1)</span></span> (interactive ruby) session customized for Chef.
+<code>chef-shell</code> serves two primary functions: it provides a means to
+interact with a Chef Server interactively using a convenient DSL; it
+allows you to define and run Chef recipes interactively.</p>
+
+<h2 id="SYNTAX">SYNTAX</h2>
+
+<p>chef-shell uses irb's subsession feature to provide multiple modes of
+interaction. In addition to the primary mode which is entered on start,
+<code>recipe</code> and <code>attributes</code> modes are available.</p>
+
+<h2 id="PRIMARY-MODE">PRIMARY MODE</h2>
+
+<p>The following commands are available in the primary
+session:</p>
+
+<dl>
+<dt class="flush"><code>help</code></dt><dd>Prints a list of available commands</dd>
+<dt class="flush"><code>version</code></dt><dd>Prints the Chef version</dd>
+<dt class="flush"><code>recipe</code></dt><dd>Switches to <code>recipe</code> mode</dd>
+<dt><code>attributes</code></dt><dd>Switches to <code>attributes</code> mode</dd>
+<dt><code>run_chef</code></dt><dd>Initiates a chef run</dd>
+<dt class="flush"><code>reset</code></dt><dd>reinitializes chef-shell session</dd>
+<dt><code>echo :on|:off</code></dt><dd>Turns irb's echo function on or off. Echo is <em>on</em> by default.</dd>
+<dt><code>tracing :on|:off</code></dt><dd>Turns irb's function tracing feature on or off. Tracing is extremely
+verbose and expected to be of interest primarily to developers.</dd>
+<dt class="flush"><code>node</code></dt><dd>Returns the <em>node</em> object for the current host. See <span class="man-ref">knife-node<span class="s">(1)</span></span>
+for more information about nodes.</dd>
+<dt class="flush"><code>ohai</code></dt><dd>Prints the attributes of <em>node</em></dd>
+</dl>
+
+
+<p>In addition to these commands, chef-shell provides a DSL for accessing
+data on the Chef Server. When working with remote data in chef-shell, you
+chain method calls in the form <em>object type</em>.<em>operation</em>, where
+<em>object type</em> is in plural form. The following object types are
+available:</p>
+
+<ul>
+<li><code>nodes</code></li>
+<li><code>roles</code></li>
+<li><code>data_bags</code></li>
+<li><code>clients</code></li>
+<li><code>cookbooks</code></li>
+</ul>
+
+
+<p>For each <em>object type</em> the following operations are available:</p>
+
+<dl>
+<dt><em>object type</em>.all(<em>&block</em>)</dt><dd>Loads all items from the server. If the optional code <em>block</em> is
+given, each item will be passed to the block and the results
+returned, similar to ruby's <code>Enumerable#map</code> method.</dd>
+<dt><em>object type</em>.show(<em>object name</em>)</dt><dd><p>Aliased as <em>object type</em>.load</p>
+
+<p>Loads the singular item identified by <em>object name</em>.</p></dd>
+<dt><em>object type</em>.search(<em>query</em>, <em>&block</em>)</dt><dd><p>Aliased as <em>object type</em>.find</p>
+
+<p>Runs a search against the server and returns the matching items. If
+the optional code <em>block</em> is given each item will be passed to the
+block and the results returned.</p>
+
+<p>The <em>query</em> may be a Solr/Lucene format query given as a String, or
+a Hash of conditions. If a Hash is given, the options will be ANDed
+together. To join conditions with OR, use negative queries, or any
+advanced search syntax, you must provide give the query in String
+form.</p></dd>
+<dt><em>object type</em>.transform(:all|<em>query</em>, <em>&block</em>)</dt><dd><p>Aliased as <em>object type</em>.bulk_edit</p>
+
+<p>Bulk edit objects by processing them with the (required) code <em>block</em>.
+You can edit all objects of the given type by passing the Symbol
+<code>:all</code> as the argument, or only a subset by passing a <em>query</em> as the
+argument. The <em>query</em> is evaluated in the same way as with
+<strong>search</strong>.</p>
+
+<p>The return value of the code <em>block</em> is used to alter the behavior
+of <code>transform</code>. If the value returned from the block is <code>nil</code> or
+<code>false</code>, the object will not be saved. Otherwise, the object is
+saved after being passed to the block. This behavior can be
+exploited to create a dry run to test a data transformation.</p></dd>
+</dl>
+
+
+<h2 id="RECIPE-MODE">RECIPE MODE</h2>
+
+<p>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:</p>
+
+<ul>
+<li><a href="http://wiki.opscode.com/display/chef/Resources" data-bare-link="true">http://wiki.opscode.com/display/chef/Resources</a></li>
+<li><a href="http://wiki.opscode.com/display/chef/Recipes" data-bare-link="true">http://wiki.opscode.com/display/chef/Recipes</a></li>
+</ul>
+
+
+<p>Once you have defined resources in the recipe, you can trigger a
+convergence run via <code>run_chef</code></p>
+
+<h2 id="EXAMPLES">EXAMPLES</h2>
+
+<ul>
+<li><p>A "Hello World" interactive recipe</p>
+
+<p>chef > recipe
+chef:recipe > echo :off
+chef:recipe > file "/tmp/hello_world"
+chef:recipe > run_chef
+[Sat, 09 Apr 2011 08:56:56 -0700] INFO: Processing file[/tmp/hello_world] action create ((irb#1) line 2)
+[Sat, 09 Apr 2011 08:56:56 -0700] INFO: file[/tmp/hello_world] created file /tmp/hello_world
+chef:recipe > pp ls '/tmp'
+[".",
+"..",
+"hello_world"]</p></li>
+<li><p>Search for <em>nodes</em> by role, and print their IP addresses</p>
+
+<p>chef > nodes.find(:roles => 'monitoring-server') {|n| n[:ipaddress] }
+=> ["10.254.199.5"]</p></li>
+<li><p>Remove the role <em>obsolete</em> from every node in the system</p>
+
+<p>chef > nodes.transform(:all) {|n| n.run_list.delete('role[obsolete]') }
+ => [node[chef098b2.opschef.com], node[ree-woot], node[graphite-dev], node[fluke.localdomain], node[ghost.local], node[kallistec]]</p></li>
+</ul>
+
+
+<h2 id="BUGS">BUGS</h2>
+
+<p><code>chef-shell</code> often does not perfectly replicate the context in which
+<span class="man-ref">chef-client<span class="s">(8)</span></span> configures a host, which may lead to discrepancies in
+observed behavior.</p>
+
+<p><code>chef-shell</code> has to duplicate much code from chef-client's internal
+libraries and may become out of sync with the behavior of those
+libraries.</p>
+
+<h2 id="SEE-ALSO">SEE ALSO</h2>
+
+<p>  <span class="man-ref">chef-client<span class="s">(8)</span></span> <span class="man-ref">knife<span class="s">(1)</span></span>
+  <a href="http://wiki.opscode.com/display/chef/Chef+Shell" data-bare-link="true">http://wiki.opscode.com/display/chef/Chef+Shell</a></p>
+
+<h2 id="AUTHOR">AUTHOR</h2>
+
+<p>   Chef was written by Adam Jacob <a href="ma&#x69;lto&#x3a;&#x61;&#x64;a&#x6d;&#x40;ops&#x63;o&#x64;&#x65;&#x2e;c&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;am&#x40;op&#x73;c&#x6f;&#x64;&#x65;&#x2e;&#x63;om</a> with many
+   contributions from the community. chef-shell was written by Daniel
+   DeLeo.</p>
+
+<h2 id="DOCUMENTATION">DOCUMENTATION</h2>
+
+<p>   This manual page was written by Daniel DeLeo <a href="ma&#x69;lto:da&#x6e;&#x40;&#x6f;&#x70;s&#x63;o&#x64;&#x65;.c&#x6f;&#x6d;" data-bare-link="true">dan&#x40;&#x6f;&#x70;s&#x63;&#x6f;d&#x65;.com</a>.
+   Permission is granted to copy, distribute and / or modify this
+   document under the terms of the Apache 2.0 License.</p>
+
+<h2 id="CHEF">CHEF</h2>
+
+<p>   chef-shell is distributed with Chef. <a href="http://wiki.opscode.com/display/chef/Home" data-bare-link="true">http://wiki.opscode.com/display/chef/Home</a></p>
+
+
+  <ol class='man-decor man-foot man foot'>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
+    <li class='tr'>chef-shell(1)</li>
+  </ol>
+
+  </div>
+</body>
+</html>
diff --git a/distro/common/html/chef-solo.8.html b/distro/common/html/chef-solo.8.html
index 63fd899..9814578 100644
--- a/distro/common/html/chef-solo.8.html
+++ b/distro/common/html/chef-solo.8.html
@@ -109,11 +109,9 @@ To do this, the complete cookbook needs to be present on disk.</p>
 This configuration file has two required variables: file_cache_path and
 cookbook_path.</p>
 
-<p>For example:</p>
-
-<pre><code>file_cache_path "/var/chef-solo"
-cookbook_path "/var/chef-solo/cookbooks"
-</code></pre>
+<p>For example:
+    file_cache_path "/var/chef-solo"
+    cookbook_path "/var/chef-solo/cookbooks"</p>
 
 <p>For your own systems, you can change this to reflect any directory you like,
 but you'll need to specify absolute paths and the cookbook_path directory
@@ -122,11 +120,9 @@ should be a subdirectory of the file_cache_path.</p>
 <p>You can also specify cookbook_path as an array, passing multiple locations
 to search for cookbooks.</p>
 
-<p>For example:</p>
-
-<pre><code>file_cache_path "/var/chef-solo"
-cookbook_path ["/var/chef-solo/cookbooks", "/var/chef-solo/site-cookbooks"]
-</code></pre>
+<p>For example:
+    file_cache_path "/var/chef-solo"
+    cookbook_path ["/var/chef-solo/cookbooks", "/var/chef-solo/site-cookbooks"]</p>
 
 <p>Note that earlier entries are now overridden by later ones.</p>
 
@@ -154,10 +150,8 @@ attributes and use the run_list from the JSON file specified.</p>
 <p>You can use -c to specify the path to the configuration file (if you don't want
 chef-solo to use the default). You can also specify -r for a cookbook tarball.</p>
 
-<p>For example:</p>
-
-<pre><code>chef-solo -c ~/solo.rb -j ~/node.json  -r http://www.example.com/chef-solo.tar.gz
-</code></pre>
+<p>For example:
+    chef-solo -c ~/solo.rb -j ~/node.json  -r http://www.example.com/chef-solo.tar.gz</p>
 
 <p>In the above case, chef-solo would extract the tarball to your specified
 cookbook_path, use ~/solo.rb as the configuration file, and apply attributes
@@ -170,9 +164,9 @@ http://wiki.opscode.com/display/chef/Home.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>Chef was written by Adam Jacob <a href="mai&#x6c;&#x74;&#x6f;:ada&#x6d;@&#x6f;spc&#x6f;de.com" data-bare-link="true">a&#x64;am@osp&#x63;ode.com</a> of Opscode
+<p>Chef was written by Adam Jacob <a href="m&#x61;&#x69;&#x6c;&#x74;o:&#x61;d&#x61;m&#x40;&#x6f;&#x73;pc&#x6f;de.com" data-bare-link="true">&#x61;&#x64;&#x61;m@osp&#x63;&#x6f;&#x64;e.&#x63;om</a> of Opscode
 (http://www.opscode.com),  with contributions from the community.  This
-manual page was written by Joshua Timberman  <a href="ma&#x69;lt&#x6f;:j&#x6f;sh&#x75;&#x61;@&#x6f;&#x70;scode.c&#x6f;&#x6d;" data-bare-link="true">&#x6a;&#x6f;&#x73;&#x68;u&#x61;&#x40;op&#x73;co&#x64;&#x65;&#x2e;c&#x6f;m</a>  with
+manual page was written by Joshua Timberman  <a href="m&#x61;i&#x6c;t&#x6f;&#x3a;j&#x6f;&#x73;&#x68;&#x75;&#x61;&#x40;&#x6f;pscode.&#x63;&#x6f;&#x6d;" data-bare-link="true">j&#x6f;sh&#x75;a@op&#x73;c&#x6f;d&#x65;.&#x63;o&#x6d;</a>  with
 help2man.  Permission  is  granted  to copy, distribute and / or modify
 this document under the terms of the Apache 2.0 License.</p>
 
@@ -181,8 +175,8 @@ found in /usr/share/common-licenses/Apache-2.0.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>chef-solo(8)</li>
   </ol>
 
diff --git a/distro/common/html/chef-solr.8.html b/distro/common/html/chef-solr.8.html
index 3021c97..c6a4809 100644
--- a/distro/common/html/chef-solr.8.html
+++ b/distro/common/html/chef-solr.8.html
@@ -144,9 +144,9 @@ wiki, http://wiki.opscode.com/display/chef/Home.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>Chef was written by Adam Jacob <a href="mai&#x6c;&#x74;&#x6f;:ada&#x6d;@&#x6f;spc&#x6f;de.com" data-bare-link="true">a&#x64;am@osp&#x63;ode.com</a> of Opscode
+<p>Chef was written by Adam Jacob <a href="m&#x61;&#x69;&#x6c;&#x74;o:&#x61;d&#x61;m&#x40;&#x6f;&#x73;pc&#x6f;de.com" data-bare-link="true">&#x61;&#x64;&#x61;m@osp&#x63;&#x6f;&#x64;e.&#x63;om</a> of Opscode
 (http://www.opscode.com),  with contributions from the community.  This
-manual page was written by Joshua Timberman  <a href="ma&#x69;lt&#x6f;:j&#x6f;sh&#x75;&#x61;@&#x6f;&#x70;scode.c&#x6f;&#x6d;" data-bare-link="true">&#x6a;&#x6f;&#x73;&#x68;u&#x61;&#x40;op&#x73;co&#x64;&#x65;&#x2e;c&#x6f;m</a>  with
+manual page was written by Joshua Timberman  <a href="m&#x61;i&#x6c;t&#x6f;&#x3a;j&#x6f;&#x73;&#x68;&#x75;&#x61;&#x40;&#x6f;pscode.&#x63;&#x6f;&#x6d;" data-bare-link="true">j&#x6f;sh&#x75;a@op&#x73;c&#x6f;d&#x65;.&#x63;o&#x6d;</a>  with
 help2man.  Permission  is  granted  to copy, distribute and / or modify
 this document under the terms of the Apache 2.0 License.</p>
 
@@ -155,8 +155,8 @@ found in /usr/share/common-licenses/Apache-2.0.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>chef-solr(8)</li>
   </ol>
 
diff --git a/distro/common/html/knife-bootstrap.1.html b/distro/common/html/knife-bootstrap.1.html
index ed8a1aa..0436a9d 100644
--- a/distro/common/html/knife-bootstrap.1.html
+++ b/distro/common/html/knife-bootstrap.1.html
@@ -99,6 +99,8 @@
 <dt class="flush"><code>--sudo</code></dt><dd>Execute the bootstrap via sudo</dd>
 <dt><code>-d</code>, <code>--distro DISTRO</code></dt><dd>Bootstrap a distro using a template</dd>
 <dt><code>--[no-]host-key-verify</code></dt><dd>Enable host key verification, which is the default behavior.</dd>
+<dt><code>--hint HINT_NAME[=HINT_FILE]</code></dt><dd>Provide the name of a hint (with option JSON file) to set for use by
+Ohai plugins.</dd>
 </dl>
 
 
@@ -216,11 +218,11 @@ to other users via the process list using tools such as <span class="man-ref">ps
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mailto&#x3a;a&#x64;a&#x6d;&#x40;&#x6f;&#x70;&#x73;c&#x6f;d&#x65;.&#x63;&#x6f;m" data-bare-link="true">&#x61;&#x64;&#x61;m&#x40;&#x6f;p&#x73;&#x63;o&#x64;e.&#x63;&#x6f;m</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="ma&#x69;lto&#x3a;&#x61;&#x64;a&#x6d;&#x40;ops&#x63;o&#x64;&#x65;&#x2e;c&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;am&#x40;op&#x73;c&#x6f;&#x64;&#x65;&#x2e;&#x63;om</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;lto:&#x6a;oshu&#x61;&#x40;o&#x70;s&#x63;&#x6f;&#x64;&#x65;&#x2e;co&#x6d;" data-bare-link="true">&#x6a;o&#x73;&#x68;&#x75;a@&#x6f;&#x70;s&#x63;ode.c&#x6f;m</a>.
+<p>   This manual page was written by Joshua Timberman <a href="ma&#x69;lto:jo&#x73;&#x68;&#x75;&#x61;@&#x6f;p&#x73;&#x63;od&#x65;&#x2e;com" data-bare-link="true">&#x6a;&#x6f;&#x73;h&#x75;&#x61;@&#x6f;pscode.c&#x6f;m</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -229,8 +231,8 @@ to other users via the process list using tools such as <span class="man-ref">ps
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-bootstrap(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-client.1.html b/distro/common/html/knife-client.1.html
index f724e55..19c08d2 100644
--- a/distro/common/html/knife-client.1.html
+++ b/distro/common/html/knife-client.1.html
@@ -196,11 +196,11 @@ setting up a host for management with Chef.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mailto&#x3a;a&#x64;a&#x6d;&#x40;&#x6f;&#x70;&#x73;c&#x6f;d&#x65;.&#x63;&#x6f;m" data-bare-link="true">&#x61;&#x64;&#x61;m&#x40;&#x6f;p&#x73;&#x63;o&#x64;e.&#x63;&#x6f;m</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="ma&#x69;lto&#x3a;&#x61;&#x64;a&#x6d;&#x40;ops&#x63;o&#x64;&#x65;&#x2e;c&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;am&#x40;op&#x73;c&#x6f;&#x64;&#x65;&#x2e;&#x63;om</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;lto:&#x6a;oshu&#x61;&#x40;o&#x70;s&#x63;&#x6f;&#x64;&#x65;&#x2e;co&#x6d;" data-bare-link="true">&#x6a;o&#x73;&#x68;&#x75;a@&#x6f;&#x70;s&#x63;ode.c&#x6f;m</a>.
+<p>   This manual page was written by Joshua Timberman <a href="ma&#x69;lto:jo&#x73;&#x68;&#x75;&#x61;@&#x6f;p&#x73;&#x63;od&#x65;&#x2e;com" data-bare-link="true">&#x6a;&#x6f;&#x73;h&#x75;&#x61;@&#x6f;pscode.c&#x6f;m</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -209,8 +209,8 @@ setting up a host for management with Chef.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-client(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-configure.1.html b/distro/common/html/knife-configure.1.html
index e20e6e2..ceac8c4 100644
--- a/distro/common/html/knife-configure.1.html
+++ b/distro/common/html/knife-configure.1.html
@@ -119,16 +119,16 @@ create an administrator and knife configuration file. Leave the
 field blank to accept the default value. On most systems, the
 default values are acceptable.</p>
 
-<p>user at host$ knife configure -i<br />
-Please enter the chef server URL: [http://localhost:4000]<br />
-Please enter a clientname for the new client: [username]<br />
-Please enter the existing admin clientname: [chef-webui]<br />
-Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]<br />
-Please enter the validation clientname: [chef-validator]<br />
-Please enter the location of the validation key: [/etc/chef/validation.pem]<br />
-Please enter the path to a chef repository (or leave blank):<br />
-Creating initial API user...<br />
-Created (or updated) client[username]<br />
+<p>user at host$ knife configure -i
+Please enter the chef server URL: [http://localhost:4000]
+Please enter a clientname for the new client: [username]
+Please enter the existing admin clientname: [chef-webui]
+Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]
+Please enter the validation clientname: [chef-validator]
+Please enter the location of the validation key: [/etc/chef/validation.pem]
+Please enter the path to a chef repository (or leave blank):
+Creating initial API user...
+Created (or updated) client[username]
 Configuration file written to /home/username/.chef/knife.rb</p>
 
 <p>This creates a new administrator client named <em>username</em>, writes
@@ -147,11 +147,11 @@ may need to modify that setting after copying to a remote host.</p></li>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="&#x6d;a&#x69;&#x6c;&#x74;&#x6f;&#x3a;&#x61;da&#x6d;&#x40;o&#x70;&#x73;c&#x6f;&#x64;&#x65;.c&#x6f;m" data-bare-link="true">&#x61;&#x64;&#x61;&#x6d;&#x40;o&#x70;&#x73;&#x63;o&#x64;e&#x2e;co&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="m&#x61;il&#x74;o&#x3a;ad&#x61;&#x6d;&#x40;o&#x70;&#x73;c&#x6f;&#x64;&#x65;.c&#x6f;m" data-bare-link="true">ad&#x61;&#x6d;&#x40;opscod&#x65;&#x2e;co&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="mai&#x6c;to:j&#x6f;&#x73;&#x68;u&#x61;&#x40;o&#x70;s&#x63;od&#x65;&#x2e;&#x63;&#x6f;&#x6d;" data-bare-link="true">j&#x6f;&#x73;h&#x75;a@&#x6f;p&#x73;co&#x64;e.&#x63;om</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;l&#x74;o&#x3a;&#x6a;&#x6f;&#x73;hu&#x61;&#x40;o&#x70;&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;o&#x6d;" data-bare-link="true">&#x6a;&#x6f;&#x73;&#x68;&#x75;a@o&#x70;s&#x63;&#x6f;de&#x2e;&#x63;&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -160,8 +160,8 @@ may need to modify that setting after copying to a remote host.</p></li>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-configure(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-cookbook-site.1.html b/distro/common/html/knife-cookbook-site.1.html
index 3d4115f..b37575e 100644
--- a/distro/common/html/knife-cookbook-site.1.html
+++ b/distro/common/html/knife-cookbook-site.1.html
@@ -218,11 +218,11 @@ configuration file.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mailto&#x3a;a&#x64;a&#x6d;&#x40;&#x6f;&#x70;&#x73;c&#x6f;d&#x65;.&#x63;&#x6f;m" data-bare-link="true">&#x61;&#x64;&#x61;m&#x40;&#x6f;p&#x73;&#x63;o&#x64;e.&#x63;&#x6f;m</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="m&#x61;il&#x74;o&#x3a;ad&#x61;&#x6d;&#x40;o&#x70;&#x73;c&#x6f;&#x64;&#x65;.c&#x6f;m" data-bare-link="true">ad&#x61;&#x6d;&#x40;opscod&#x65;&#x2e;co&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;lto:&#x6a;oshu&#x61;&#x40;o&#x70;s&#x63;&#x6f;&#x64;&#x65;&#x2e;co&#x6d;" data-bare-link="true">&#x6a;o&#x73;&#x68;&#x75;a@&#x6f;&#x70;s&#x63;ode.c&#x6f;m</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;l&#x74;o&#x3a;&#x6a;&#x6f;&#x73;hu&#x61;&#x40;o&#x70;&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;o&#x6d;" data-bare-link="true">&#x6a;&#x6f;&#x73;&#x68;&#x75;a@o&#x70;s&#x63;&#x6f;de&#x2e;&#x63;&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -231,8 +231,8 @@ configuration file.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-cookbook-site(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-cookbook.1.html b/distro/common/html/knife-cookbook.1.html
index 6e9a999..7979c5b 100644
--- a/distro/common/html/knife-cookbook.1.html
+++ b/distro/common/html/knife-cookbook.1.html
@@ -162,12 +162,9 @@ option.</dd>
 to the Chef Server. Only files that don't yet exist on the server will
 be uploaded.</p>
 
-<p>As the command parses the name args as 1..n cookbook names:</p>
-
-<pre><code>`knife cookbook upload COOKBOOK COOKBOOK ...`
-</code></pre>
-
-<p>works for one to many cookbooks.</p>
+<p>As the command parses the name args as 1..n cookbook names:
+    <code>knife cookbook upload COOKBOOK COOKBOOK ...</code>
+works for one to many cookbooks.</p>
 
 <h2 id="DOWNLOAD">DOWNLOAD</h2>
 
@@ -220,9 +217,9 @@ regular expression (<em>regex</em>) should be in quotes, not in //'s.</p>
 <dl>
 <dt><code>-o</code>, <code>--cookbook-path path</code></dt><dd>the directory where the cookbook will be created</dd>
 <dt><code>-r</code>, <code>--readme-format format</code></dt><dd>format of the readme file md, mkd, txt, rdoc</dd>
-<dt><code>-c</code>, <code>--copyright copyright</code></dt><dd>name of copyright holder</dd>
+<dt><code>-C</code>, <code>--copyright copyright</code></dt><dd>name of copyright holder</dd>
 <dt><code>-i</code>, <code>--license license</code></dt><dd>license for cookbook, apachev2 or none</dd>
-<dt><code>-e</code>, <code>--email email</code></dt><dd>email address of cookbook maintainer</dd>
+<dt><code>-m</code>, <code>--email email</code></dt><dd>email address of cookbook maintainer</dd>
 </dl>
 
 
@@ -248,7 +245,7 @@ named cookbook.</p>
 readme file will be written with the specified extension and a set of
 helpful starting headers.</p>
 
-<p>specify <code>-c</code> or <code>--copyright</code> with the name of the copyright holder as
+<p>specify <code>-C</code> or <code>--copyright</code> with the name of the copyright holder as
 your name or your company/organization name in a quoted string. if this
 value is not specified an all-caps string <code>your_company_name</code> is used
 which can be easily changed with find/replace.</p>
@@ -261,7 +258,7 @@ the cookbook and follow any restrictions they describe. when using
 are pre-filled. the <code>none</code> license will be treated as
 non-redistributable.</p>
 
-<p>specify <code>-e</code> or <code>--email</code> with the email address of the cookbook's
+<p>specify <code>-m</code> or <code>--email</code> with the email address of the cookbook's
 maintainer. if this value is not specified, an all-caps string
 <code>your_email</code> is used which can easily be changed with find/replace.</p>
 
@@ -361,11 +358,11 @@ cookbook.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mailto:a&#x64;am&#x40;&#x6f;pscod&#x65;&#x2e;com" data-bare-link="true">a&#x64;&#x61;&#x6d;&#x40;o&#x70;sc&#x6f;d&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="m&#x61;il&#x74;o&#x3a;ad&#x61;&#x6d;&#x40;o&#x70;&#x73;c&#x6f;&#x64;&#x65;.c&#x6f;m" data-bare-link="true">ad&#x61;&#x6d;&#x40;opscod&#x65;&#x2e;co&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="m&#x61;&#x69;&#x6c;&#x74;o&#x3a;&#x6a;o&#x73;&#x68;&#x75;a&#x40;&#x6f;p&#x73;c&#x6f;de.c&#x6f;&#x6d;" data-bare-link="true">jo&#x73;&#x68;&#x75;a&#x40;op&#x73;&#x63;od&#x65;.co&#x6d;</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;l&#x74;o&#x3a;&#x6a;&#x6f;&#x73;hu&#x61;&#x40;o&#x70;&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;o&#x6d;" data-bare-link="true">&#x6a;&#x6f;&#x73;&#x68;&#x75;a@o&#x70;s&#x63;&#x6f;de&#x2e;&#x63;&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -374,8 +371,8 @@ cookbook.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-cookbook(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-data-bag.1.html b/distro/common/html/knife-data-bag.1.html
index 5c2fa1e..9331440 100644
--- a/distro/common/html/knife-data-bag.1.html
+++ b/distro/common/html/knife-data-bag.1.html
@@ -197,12 +197,9 @@ passwords, API keys, etc.</p>
 
 <p><strong>CAVEATS:</strong> Keys are not encrypted; only values are encrypted. The "id"
 of a Data Bag Item is not encrypted, since it is used by Chef Server to
-store the item in its database. For example, given the following data bag item:</p>
-
-<pre><code>{"id": "important_passwords", "secret_password": "opensesame"}
-</code></pre>
-
-<p>The key "secret_password" will be visible to an evesdropper, but the
+store the item in its database. For example, given the following data bag item:
+    {"id": "important_passwords", "secret_password": "opensesame"}
+The key "secret_password" will be visible to an evesdropper, but the
 value "opensesame" will be protected. Both the key "id" and its value
 "important_passwords" will be visible to an evesdropper.</p>
 
@@ -215,11 +212,11 @@ encryption keys.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="&#x6d;a&#x69;&#x6c;&#x74;&#x6f;&#x3a;&#x61;da&#x6d;&#x40;o&#x70;&#x73;c&#x6f;&#x64;&#x65;.c&#x6f;m" data-bare-link="true">&#x61;&#x64;&#x61;&#x6d;&#x40;o&#x70;&#x73;&#x63;o&#x64;e&#x2e;co&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="m&#x61;il&#x74;o&#x3a;ad&#x61;&#x6d;&#x40;o&#x70;&#x73;c&#x6f;&#x64;&#x65;.c&#x6f;m" data-bare-link="true">ad&#x61;&#x6d;&#x40;opscod&#x65;&#x2e;co&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="mai&#x6c;to:j&#x6f;&#x73;&#x68;u&#x61;&#x40;o&#x70;s&#x63;od&#x65;&#x2e;&#x63;&#x6f;&#x6d;" data-bare-link="true">j&#x6f;&#x73;h&#x75;a@&#x6f;p&#x73;co&#x64;e.&#x63;om</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;l&#x74;o&#x3a;&#x6a;&#x6f;&#x73;hu&#x61;&#x40;o&#x70;&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;o&#x6d;" data-bare-link="true">&#x6a;&#x6f;&#x73;&#x68;&#x75;a@o&#x70;s&#x63;&#x6f;de&#x2e;&#x63;&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -228,8 +225,8 @@ encryption keys.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-data-bag(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-environment.1.html b/distro/common/html/knife-environment.1.html
index 70715bf..687890d 100644
--- a/distro/common/html/knife-environment.1.html
+++ b/distro/common/html/knife-environment.1.html
@@ -135,10 +135,8 @@ exits.</p>
 <h2 id="LIST">LIST</h2>
 
 <p><strong>knife environment list</strong> <em>(options)</em>
-  * <code>-w</code>, <code>--with-uri</code>:</p>
-
-<pre><code>Show the resource URI for each environment
-</code></pre>
+  * <code>-w</code>, <code>--with-uri</code>:
+    Show the resource URI for each environment</p>
 
 <h2 id="SHOW">SHOW</h2>
 
@@ -244,11 +242,11 @@ override_attributes "aws_s3_bucket" => "production"
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="m&#x61;i&#x6c;&#x74;o:&#x61;dam&#x40;o&#x70;sco&#x64;&#x65;&#x2e;&#x63;&#x6f;m" data-bare-link="true">&#x61;da&#x6d;@&#x6f;psc&#x6f;d&#x65;.c&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;a&#x69;lto&#x3a;&#x61;d&#x61;m&#x40;opscode&#x2e;com" data-bare-link="true">&#x61;d&#x61;m&#x40;ops&#x63;o&#x64;&#x65;.&#x63;o&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Daniel DeLeo <a href="&#x6d;&#x61;il&#x74;&#x6f;:&#x64;a&#x6e;@&#x6f;p&#x73;&#x63;&#x6f;d&#x65;.c&#x6f;m" data-bare-link="true">d&#x61;&#x6e;&#x40;op&#x73;&#x63;&#x6f;de&#x2e;co&#x6d;</a>.
+<p>   This manual page was written by Daniel DeLeo <a href="&#x6d;a&#x69;&#x6c;t&#x6f;&#x3a;d&#x61;&#x6e;@&#x6f;ps&#x63;&#x6f;de&#x2e;co&#x6d;" data-bare-link="true">da&#x6e;&#x40;&#x6f;p&#x73;c&#x6f;de&#x2e;&#x63;om</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -257,8 +255,8 @@ override_attributes "aws_s3_bucket" => "production"
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-environment(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-exec.1.html b/distro/common/html/knife-exec.1.html
index 6a12117..5bfd5f7 100644
--- a/distro/common/html/knife-exec.1.html
+++ b/distro/common/html/knife-exec.1.html
@@ -91,14 +91,14 @@
 
 <h2 id="DESCRIPTION">DESCRIPTION</h2>
 
-<p><code>knife exec</code> runs arbitrary ruby scripts in a context similar to that of
-the <span class="man-ref">shef<span class="s">(1)</span></span> DSL. See the shef documentation for a description of the
-commands available.</p>
+<p><code>knife exec</code> runs arbitrary ruby scripts in a context similar to that
+of the <span class="man-ref">chef-shell<span class="s">(1)</span></span> DSL. See the chef-shell documentation for a
+description of the commands available.</p>
 
 <h2 id="EXAMPLES">EXAMPLES</h2>
 
 <dl>
-<dt>Make an API call against an arbitrary endpoint</dt><dd>knife exec -E 'api.get("nodes/fluke.localdomain/cookbooks")'<br />
+<dt>Make an API call against an arbitrary endpoint</dt><dd>knife exec -E 'api.get("nodes/fluke.localdomain/cookbooks")'
 => list of cookbooks for the node <em>fluke.localdomain</em></dd>
 <dt>Remove the role <em>obsolete</em> from all nodes</dt><dd>knife exec -E 'nodes.transform(:all){|n| n.run_list.delete("role[obsolete]")}'</dd>
 <dt>Generate the expanded run list for hosts in the <code>webserver</code> role</dt><dd>knife exec -E 'nodes.find(:roles => "webserver") {|n| n.expand!; n[:recipes]}'</dd>
@@ -107,15 +107,15 @@ commands available.</p>
 
 <h2 id="SEE-ALSO">SEE ALSO</h2>
 
-<p>   <strong><span class="man-ref">shef<span class="s">(1)</span></span></strong></p>
+<p>   <strong><span class="man-ref">chef-shell<span class="s">(1)</span></span></strong></p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="m&#x61;i&#x6c;&#x74;o:&#x61;dam&#x40;o&#x70;sco&#x64;&#x65;&#x2e;&#x63;&#x6f;m" data-bare-link="true">&#x61;da&#x6d;@&#x6f;psc&#x6f;d&#x65;.c&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;a&#x69;lto&#x3a;&#x61;d&#x61;m&#x40;opscode&#x2e;com" data-bare-link="true">&#x61;d&#x61;m&#x40;ops&#x63;o&#x64;&#x65;.&#x63;o&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;il&#x74;&#x6f;:&#x6a;o&#x73;h&#x75;a&#x40;&#x6f;&#x70;s&#x63;od&#x65;.c&#x6f;&#x6d;" data-bare-link="true">&#x6a;os&#x68;&#x75;&#x61;@o&#x70;sc&#x6f;&#x64;&#x65;&#x2e;com</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;a&#x69;&#x6c;t&#x6f;&#x3a;j&#x6f;&#x73;h&#x75;a@&#x6f;&#x70;sc&#x6f;de&#x2e;co&#x6d;" data-bare-link="true">&#x6a;&#x6f;s&#x68;u&#x61;@o&#x70;&#x73;cod&#x65;.c&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -124,8 +124,8 @@ commands available.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-exec(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-index.1.html b/distro/common/html/knife-index.1.html
index a206eb7..59b4e3a 100644
--- a/distro/common/html/knife-index.1.html
+++ b/distro/common/html/knife-index.1.html
@@ -102,11 +102,11 @@ time for all objects to be indexed and available for search.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="m&#x61;i&#x6c;&#x74;o:&#x61;dam&#x40;o&#x70;sco&#x64;&#x65;&#x2e;&#x63;&#x6f;m" data-bare-link="true">&#x61;da&#x6d;@&#x6f;psc&#x6f;d&#x65;.c&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;a&#x69;lto&#x3a;&#x61;d&#x61;m&#x40;opscode&#x2e;com" data-bare-link="true">&#x61;d&#x61;m&#x40;ops&#x63;o&#x64;&#x65;.&#x63;o&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;il&#x74;&#x6f;:&#x6a;o&#x73;h&#x75;a&#x40;&#x6f;&#x70;s&#x63;od&#x65;.c&#x6f;&#x6d;" data-bare-link="true">&#x6a;os&#x68;&#x75;&#x61;@o&#x70;sc&#x6f;&#x64;&#x65;&#x2e;com</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;a&#x69;&#x6c;t&#x6f;&#x3a;j&#x6f;&#x73;h&#x75;a@&#x6f;&#x70;sc&#x6f;de&#x2e;co&#x6d;" data-bare-link="true">&#x6a;&#x6f;s&#x68;u&#x61;@o&#x70;&#x73;cod&#x65;.c&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -115,8 +115,8 @@ time for all objects to be indexed and available for search.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-index(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-node.1.html b/distro/common/html/knife-node.1.html
index 713f36d..9237539 100644
--- a/distro/common/html/knife-node.1.html
+++ b/distro/common/html/knife-node.1.html
@@ -227,11 +227,11 @@ run list, the correct syntax is "role[ROLE_NAME]"</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mailto:a&#x64;am&#x40;&#x6f;pscod&#x65;&#x2e;com" data-bare-link="true">a&#x64;&#x61;&#x6d;&#x40;o&#x70;sc&#x6f;d&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;ailt&#x6f;&#x3a;ad&#x61;&#x6d;@o&#x70;sco&#x64;e&#x2e;&#x63;o&#x6d;" data-bare-link="true">a&#x64;&#x61;m@o&#x70;&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;o&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="m&#x61;&#x69;&#x6c;&#x74;o&#x3a;&#x6a;o&#x73;&#x68;&#x75;a&#x40;&#x6f;p&#x73;c&#x6f;de.c&#x6f;&#x6d;" data-bare-link="true">jo&#x73;&#x68;&#x75;a&#x40;op&#x73;&#x63;od&#x65;.co&#x6d;</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;i&#x6c;t&#x6f;:j&#x6f;&#x73;hua@&#x6f;p&#x73;&#x63;&#x6f;d&#x65;.&#x63;o&#x6d;" data-bare-link="true">j&#x6f;s&#x68;&#x75;&#x61;&#x40;&#x6f;&#x70;s&#x63;od&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -240,8 +240,8 @@ run list, the correct syntax is "role[ROLE_NAME]"</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-node(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-recipe.1.html b/distro/common/html/knife-recipe.1.html
deleted file mode 100644
index 3f98e02..0000000
--- a/distro/common/html/knife-recipe.1.html
+++ /dev/null
@@ -1,92 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta http-equiv='content-type' value='text/html;charset=utf8'>
-  <meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
-  <title>knife-recipe(1) - List the recipes available on a Chef Server</title>
-  <style type='text/css' media='all'>
-  /* style: man */
-  body#manpage {margin:0}
-  .mp {max-width:100ex;padding:0 9ex 1ex 4ex}
-  .mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
-  .mp h2 {margin:10px 0 0 0}
-  .mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
-  .mp h3 {margin:0 0 0 4ex}
-  .mp dt {margin:0;clear:left}
-  .mp dt.flush {float:left;width:8ex}
-  .mp dd {margin:0 0 0 9ex}
-  .mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
-  .mp pre {margin-bottom:20px}
-  .mp pre+h2,.mp pre+h3 {margin-top:22px}
-  .mp h2+pre,.mp h3+pre {margin-top:5px}
-  .mp img {display:block;margin:auto}
-  .mp h1.man-title {display:none}
-  .mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
-  .mp h2 {font-size:16px;line-height:1.25}
-  .mp h1 {font-size:20px;line-height:2}
-  .mp {text-align:justify;background:#fff}
-  .mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
-  .mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
-  .mp u {text-decoration:underline}
-  .mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
-  .mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
-  .mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
-  .mp b.man-ref {font-weight:normal;color:#434241}
-  .mp pre {padding:0 4ex}
-  .mp pre code {font-weight:normal;color:#434241}
-  .mp h2+pre,h3+pre {padding-left:0}
-  ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
-  ol.man-decor {width:100%}
-  ol.man-decor li.tl {text-align:left}
-  ol.man-decor li.tc {text-align:center;letter-spacing:4px}
-  ol.man-decor li.tr {text-align:right;float:right}
-  </style>
-  <style type='text/css' media='all'>
-  /* style: toc */
-  .man-navigation {display:block !important;position:fixed;top:0;left:113ex;height:100%;width:100%;padding:48px 0 0 0;border-left:1px solid #dbdbdb;background:#eee}
-  .man-navigation a,.man-navigation a:hover,.man-navigation a:link,.man-navigation a:visited {display:block;margin:0;padding:5px 2px 5px 30px;color:#999;text-decoration:none}
-  .man-navigation a:hover {color:#111;text-decoration:underline}
-  </style>
-</head>
-<!--
-  The following styles are deprecated and will be removed at some point:
-  div#man, div#man ol.man, div#man ol.head, div#man ol.man.
-
-  The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
-  .man-navigation should be used instead.
--->
-<body id='manpage'>
-  <div class='mp' id='man'>
-
-  <div class='man-navigation' style='display:none'>
-    <a href="#NAME">NAME</a>
-    <a href="#SYNOPSIS">SYNOPSIS</a>
-  </div>
-
-  <ol class='man-decor man-head man head'>
-    <li class='tl'>knife-recipe(1)</li>
-    <li class='tc'>Chef Manual</li>
-    <li class='tr'>knife-recipe(1)</li>
-  </ol>
-
-  <h2 id="NAME">NAME</h2>
-<p class="man-name">
-  <code>knife-recipe</code> - <span class="man-whatis">List the recipes available on a Chef Server</span>
-</p>
-
-<h2 id="SYNOPSIS">SYNOPSIS</h2>
-
-<p><strong>knife</strong> <strong>recipe list [PATTERN]</strong></p>
-
-<p>List the recipes available on the server. The results shown can be limited with the optional PATTERN, which is a regular expression. PATTERN should be given in quotes, without slashes.</p>
-
-
-  <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 0.10.0</li>
-    <li class='tc'>April 2011</li>
-    <li class='tr'>knife-recipe(1)</li>
-  </ol>
-
-  </div>
-</body>
-</html>
diff --git a/distro/common/html/knife-role.1.html b/distro/common/html/knife-role.1.html
index ef71db5..663d1bd 100644
--- a/distro/common/html/knife-role.1.html
+++ b/distro/common/html/knife-role.1.html
@@ -177,11 +177,11 @@ run_list.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mailto:a&#x64;am&#x40;&#x6f;pscod&#x65;&#x2e;com" data-bare-link="true">a&#x64;&#x61;&#x6d;&#x40;o&#x70;sc&#x6f;d&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;ailt&#x6f;&#x3a;ad&#x61;&#x6d;@o&#x70;sco&#x64;e&#x2e;&#x63;o&#x6d;" data-bare-link="true">a&#x64;&#x61;m@o&#x70;&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;o&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="m&#x61;&#x69;&#x6c;&#x74;o&#x3a;&#x6a;o&#x73;&#x68;&#x75;a&#x40;&#x6f;p&#x73;c&#x6f;de.c&#x6f;&#x6d;" data-bare-link="true">jo&#x73;&#x68;&#x75;a&#x40;op&#x73;&#x63;od&#x65;.co&#x6d;</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;i&#x6c;t&#x6f;:j&#x6f;&#x73;hua@&#x6f;p&#x73;&#x63;&#x6f;d&#x65;.&#x63;o&#x6d;" data-bare-link="true">j&#x6f;s&#x68;&#x75;&#x61;&#x40;&#x6f;&#x70;s&#x63;od&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -190,8 +190,8 @@ run_list.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-role(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-search.1.html b/distro/common/html/knife-search.1.html
index 9f7ca21..cf9c21d 100644
--- a/distro/common/html/knife-search.1.html
+++ b/distro/common/html/knife-search.1.html
@@ -265,11 +265,11 @@ www.example.com:</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="m&#x61;i&#x6c;&#x74;o:&#x61;dam&#x40;o&#x70;sco&#x64;&#x65;&#x2e;&#x63;&#x6f;m" data-bare-link="true">&#x61;da&#x6d;@&#x6f;psc&#x6f;d&#x65;.c&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;ailt&#x6f;&#x3a;ad&#x61;&#x6d;@o&#x70;sco&#x64;e&#x2e;&#x63;o&#x6d;" data-bare-link="true">a&#x64;&#x61;m@o&#x70;&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;o&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;il&#x74;&#x6f;:&#x6a;o&#x73;h&#x75;a&#x40;&#x6f;&#x70;s&#x63;od&#x65;.c&#x6f;&#x6d;" data-bare-link="true">&#x6a;os&#x68;&#x75;&#x61;@o&#x70;sc&#x6f;&#x64;&#x65;&#x2e;com</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;i&#x6c;t&#x6f;:j&#x6f;&#x73;hua@&#x6f;p&#x73;&#x63;&#x6f;d&#x65;.&#x63;o&#x6d;" data-bare-link="true">j&#x6f;s&#x68;&#x75;&#x61;&#x40;&#x6f;&#x70;s&#x63;od&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -278,8 +278,8 @@ www.example.com:</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-search(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-ssh.1.html b/distro/common/html/knife-ssh.1.html
index 52570db..93c1f57 100644
--- a/distro/common/html/knife-ssh.1.html
+++ b/distro/common/html/knife-ssh.1.html
@@ -133,11 +133,11 @@ option.</dd>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="&#x6d;&#x61;i&#x6c;t&#x6f;&#x3a;a&#x64;&#x61;&#x6d;&#x40;&#x6f;p&#x73;code.&#x63;om" data-bare-link="true">&#x61;da&#x6d;&#x40;op&#x73;&#x63;&#x6f;&#x64;e&#x2e;&#x63;&#x6f;&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;ail&#x74;o:&#x61;da&#x6d;@op&#x73;co&#x64;e.&#x63;&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;&#x61;m&#x40;ops&#x63;&#x6f;d&#x65;.co&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="ma&#x69;l&#x74;&#x6f;&#x3a;jo&#x73;hua&#x40;op&#x73;c&#x6f;de&#x2e;&#x63;&#x6f;m" data-bare-link="true">&#x6a;&#x6f;sh&#x75;&#x61;@&#x6f;ps&#x63;o&#x64;&#x65;.c&#x6f;m</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;ai&#x6c;t&#x6f;&#x3a;j&#x6f;s&#x68;&#x75;&#x61;@&#x6f;p&#x73;co&#x64;e&#x2e;&#x63;&#x6f;m" data-bare-link="true">j&#x6f;sh&#x75;&#x61;&#x40;&#x6f;&#x70;&#x73;&#x63;od&#x65;.co&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -146,8 +146,8 @@ option.</dd>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-ssh(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-status.1.html b/distro/common/html/knife-status.1.html
index 1f7776d..185e036 100644
--- a/distro/common/html/knife-status.1.html
+++ b/distro/common/html/knife-status.1.html
@@ -105,11 +105,11 @@ may not be publicly reachable.</p>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="&#x6d;a&#x69;&#x6c;&#x74;&#x6f;&#x3a;&#x61;da&#x6d;&#x40;o&#x70;&#x73;c&#x6f;&#x64;&#x65;.c&#x6f;m" data-bare-link="true">&#x61;&#x64;&#x61;&#x6d;&#x40;o&#x70;&#x73;&#x63;o&#x64;e&#x2e;co&#x6d;</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;ail&#x74;o:&#x61;da&#x6d;@op&#x73;co&#x64;e.&#x63;&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;&#x61;m&#x40;ops&#x63;&#x6f;d&#x65;.co&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="mai&#x6c;to:j&#x6f;&#x73;&#x68;u&#x61;&#x40;o&#x70;s&#x63;od&#x65;&#x2e;&#x63;&#x6f;&#x6d;" data-bare-link="true">j&#x6f;&#x73;h&#x75;a@&#x6f;p&#x73;co&#x64;e.&#x63;om</a>.
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;ai&#x6c;t&#x6f;&#x3a;j&#x6f;s&#x68;&#x75;&#x61;@&#x6f;p&#x73;co&#x64;e&#x2e;&#x63;&#x6f;m" data-bare-link="true">j&#x6f;sh&#x75;&#x61;&#x40;&#x6f;&#x70;&#x73;&#x63;od&#x65;.co&#x6d;</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -118,8 +118,8 @@ may not be publicly reachable.</p>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-status(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife-tag.1.html b/distro/common/html/knife-tag.1.html
index 86ca275..9f48bb3 100644
--- a/distro/common/html/knife-tag.1.html
+++ b/distro/common/html/knife-tag.1.html
@@ -114,11 +114,11 @@
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mai&#x6c;&#x74;&#x6f;:ada&#x6d;@&#x6f;psc&#x6f;de.com" data-bare-link="true">a&#x64;am@ops&#x63;ode.com</a> with many contributions from the community.</p>
+<p>   Chef was written by Adam Jacob <a href="&#x6d;ail&#x74;o:&#x61;da&#x6d;@op&#x73;co&#x64;e.&#x63;&#x6f;&#x6d;" data-bare-link="true">&#x61;&#x64;&#x61;m&#x40;ops&#x63;&#x6f;d&#x65;.co&#x6d;</a> with many contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Daniel DeLeo <a href="ma&#x69;lt&#x6f;:d&#x61;n@&#x6f;&#x70;s&#x63;&#x6f;de.com" data-bare-link="true">d&#x61;&#x6e;&#x40;&#x6f;&#x70;&#x73;c&#x6f;&#x64;e.&#x63;om</a>.
+<p>   This manual page was written by Daniel DeLeo <a href="&#x6d;ai&#x6c;t&#x6f;&#x3a;d&#x61;n&#x40;&#x6f;&#x70;s&#x63;o&#x64;e.&#x63;o&#x6d;" data-bare-link="true">&#x64;&#x61;n@&#x6f;ps&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;&#x6f;m</a>.
    Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.</p>
 
 <h2 id="CHEF">CHEF</h2>
@@ -127,8 +127,8 @@
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife-tag(1)</li>
   </ol>
 
diff --git a/distro/common/html/knife.1.html b/distro/common/html/knife.1.html
index aa8f9f1..37e6173 100644
--- a/distro/common/html/knife.1.html
+++ b/distro/common/html/knife.1.html
@@ -132,11 +132,11 @@ documentation using <code>knife help TOPIC</code>.</p>
 <dt><code>-E</code>, <code>--environment ENVIRONMENT</code></dt><dd>Set the Chef environment</dd>
 <dt><code>-e</code>, <code>--editor</code> EDITOR</dt><dd>Set the editor to use for interactive commands</dd>
 <dt><code>-F</code>, <code>--format</code> FORMAT</dt><dd>Which format to use for output. See FORMATS for details.</dd>
-<dt><code>-V</code>, <code>--verbose</code></dt><dd>More verbose output.  Use twice for max verbosity</dd>
 <dt><code>-d</code>, <code>--disable-editing</code></dt><dd>Do not open EDITOR, just accept the data as is</dd>
 <dt><code>-u</code>, <code>--user</code> USER</dt><dd>API Client Username, corresponds to <code>Chef::Config</code> <code>node_name</code>.</dd>
 <dt><code>-p</code>, <code>--print-after</code></dt><dd>Show the data after a destructive operation</dd>
 <dt><code>-v</code>, <code>--version</code></dt><dd>Show chef version</dd>
+<dt><code>-V</code>, <code>--verbose</code></dt><dd>More verbose output. Use twice for max verbosity.</dd>
 <dt><code>-y</code>, <code>--yes</code></dt><dd>Say yes to all prompts for confirmation</dd>
 <dt><code>--defaults</code></dt><dd>Accept default values for all questions</dd>
 <dt><code>--[no-]color</code></dt><dd>Use colored output. Color enabled by default.</dd>
@@ -181,14 +181,9 @@ Private key file to authenticate to the Chef server. Corresponds to the
 <li><code>chef_server_url</code>:
 URL of the Chef server. Corresponds to the <code>-s</code> or <code>--server-url</code>
 option. This is requested from the user when running this sub-command.</li>
-<li><code>cache_type</code>:
-The type of cache to use. Default is BasicFile. This can be any type of
-Cache that moneta supports: BasicFile, Berkeley, Couch, DataMapper,
-File, LMC, Memcache, Memory, MongoDB, Redis, Rufus, S3, SDBM, Tyrant,
-Xattr, YAML.</li>
-<li><code>cache_options</code>:
-Specifies various options to use for caching. These options are
-dependent on the <code>cache_type</code>.</li>
+<li><code>syntax_check_cache_path</code>:
+Specifies the path to a directory where knife caches information
+about files that it has syntax checked.</li>
 <li><code>validation_client_name</code>:
 Specifies the name of the client used to validate new clients.</li>
 <li><code>validation_key</code>:
@@ -271,7 +266,7 @@ data editing entirely.</dd>
 
 <h2 id="SEE-ALSO">SEE ALSO</h2>
 
-<p>  <strong><span class="man-ref">chef-client<span class="s">(8)</span></span></strong> <strong><span class="man-ref">chef-server<span class="s">(8)</span></span></strong> <strong><span class="man-ref">shef<span class="s">(1)</span></span></strong></p>
+<p>  <strong><span class="man-ref">chef-client<span class="s">(8)</span></span></strong> <strong><span class="man-ref">chef-server<span class="s">(8)</span></span></strong> <strong><span class="man-ref">chef-shell<span class="s">(1)</span></span></strong></p>
 
 <p>  <strong><span class="man-ref">knife-bootstrap<span class="s">(1)</span></span></strong> <strong><span class="man-ref">knife-client<span class="s">(1)</span></span></strong> <strong><span class="man-ref">knife-configure<span class="s">(1)</span></span></strong>
   <strong><span class="man-ref">knife-cookbook-site<span class="s">(1)</span></span></strong> <strong><span class="man-ref">knife-cookbook<span class="s">(1)</span></span></strong> <strong><span class="man-ref">knife-data-bag<span class="s">(1)</span></span></strong>
@@ -291,12 +286,12 @@ data editing entirely.</dd>
 
 <h2 id="AUTHOR">AUTHOR</h2>
 
-<p>   Chef was written by Adam Jacob <a href="mailto&#x3a;a&#x64;a&#x6d;&#x40;&#x6f;&#x70;&#x73;c&#x6f;d&#x65;.&#x63;&#x6f;m" data-bare-link="true">&#x61;&#x64;&#x61;m&#x40;&#x6f;p&#x73;&#x63;o&#x64;e.&#x63;&#x6f;m</a> of Opscode
+<p>   Chef was written by Adam Jacob <a href="ma&#x69;lto&#x3a;ad&#x61;m&#x40;o&#x70;&#x73;c&#x6f;&#x64;e.c&#x6f;m" data-bare-link="true">&#x61;&#x64;a&#x6d;@o&#x70;sc&#x6f;&#x64;e&#x2e;&#x63;o&#x6d;</a> of Opscode
    (<a href="http://www.opscode.com" data-bare-link="true">http://www.opscode.com</a>), with contributions from the community.</p>
 
 <h2 id="DOCUMENTATION">DOCUMENTATION</h2>
 
-<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;lto:&#x6a;oshu&#x61;&#x40;o&#x70;s&#x63;&#x6f;&#x64;&#x65;&#x2e;co&#x6d;" data-bare-link="true">&#x6a;o&#x73;&#x68;&#x75;a@&#x6f;&#x70;s&#x63;ode.c&#x6f;m</a>.</p>
+<p>   This manual page was written by Joshua Timberman <a href="&#x6d;&#x61;&#x69;&#x6c;&#x74;&#x6f;:jo&#x73;hua&#x40;o&#x70;sco&#x64;&#x65;&#x2e;&#x63;&#x6f;m" data-bare-link="true">&#x6a;o&#x73;hu&#x61;@&#x6f;p&#x73;&#x63;&#x6f;&#x64;&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a>.</p>
 
 <h2 id="LICENSE">LICENSE</h2>
 
@@ -310,8 +305,8 @@ data editing entirely.</dd>
 
 
   <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
+    <li class='tl'>Chef 11.8.2</li>
+    <li class='tc'>December 2013</li>
     <li class='tr'>knife(1)</li>
   </ol>
 
diff --git a/distro/common/html/shef.1.html b/distro/common/html/shef.1.html
deleted file mode 100644
index dde36f6..0000000
--- a/distro/common/html/shef.1.html
+++ /dev/null
@@ -1,283 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta http-equiv='content-type' value='text/html;charset=utf8'>
-  <meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
-  <title>shef(1) - Interactive Chef Console</title>
-  <style type='text/css' media='all'>
-  /* style: man */
-  body#manpage {margin:0}
-  .mp {max-width:100ex;padding:0 9ex 1ex 4ex}
-  .mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
-  .mp h2 {margin:10px 0 0 0}
-  .mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
-  .mp h3 {margin:0 0 0 4ex}
-  .mp dt {margin:0;clear:left}
-  .mp dt.flush {float:left;width:8ex}
-  .mp dd {margin:0 0 0 9ex}
-  .mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
-  .mp pre {margin-bottom:20px}
-  .mp pre+h2,.mp pre+h3 {margin-top:22px}
-  .mp h2+pre,.mp h3+pre {margin-top:5px}
-  .mp img {display:block;margin:auto}
-  .mp h1.man-title {display:none}
-  .mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
-  .mp h2 {font-size:16px;line-height:1.25}
-  .mp h1 {font-size:20px;line-height:2}
-  .mp {text-align:justify;background:#fff}
-  .mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
-  .mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
-  .mp u {text-decoration:underline}
-  .mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
-  .mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
-  .mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
-  .mp b.man-ref {font-weight:normal;color:#434241}
-  .mp pre {padding:0 4ex}
-  .mp pre code {font-weight:normal;color:#434241}
-  .mp h2+pre,h3+pre {padding-left:0}
-  ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
-  ol.man-decor {width:100%}
-  ol.man-decor li.tl {text-align:left}
-  ol.man-decor li.tc {text-align:center;letter-spacing:4px}
-  ol.man-decor li.tr {text-align:right;float:right}
-  </style>
-  <style type='text/css' media='all'>
-  /* style: toc */
-  .man-navigation {display:block !important;position:fixed;top:0;left:113ex;height:100%;width:100%;padding:48px 0 0 0;border-left:1px solid #dbdbdb;background:#eee}
-  .man-navigation a,.man-navigation a:hover,.man-navigation a:link,.man-navigation a:visited {display:block;margin:0;padding:5px 2px 5px 30px;color:#999;text-decoration:none}
-  .man-navigation a:hover {color:#111;text-decoration:underline}
-  </style>
-</head>
-<!--
-  The following styles are deprecated and will be removed at some point:
-  div#man, div#man ol.man, div#man ol.head, div#man ol.man.
-
-  The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
-  .man-navigation should be used instead.
--->
-<body id='manpage'>
-  <div class='mp' id='man'>
-
-  <div class='man-navigation' style='display:none'>
-    <a href="#NAME">NAME</a>
-    <a href="#SYNOPSIS">SYNOPSIS</a>
-    <a href="#DESCRIPTION">DESCRIPTION</a>
-    <a href="#SYNTAX">SYNTAX</a>
-    <a href="#PRIMARY-MODE">PRIMARY MODE</a>
-    <a href="#RECIPE-MODE">RECIPE MODE</a>
-    <a href="#EXAMPLES">EXAMPLES</a>
-    <a href="#BUGS">BUGS</a>
-    <a href="#SEE-ALSO">SEE ALSO</a>
-    <a href="#AUTHOR">AUTHOR</a>
-    <a href="#DOCUMENTATION">DOCUMENTATION</a>
-    <a href="#CHEF">CHEF</a>
-  </div>
-
-  <ol class='man-decor man-head man head'>
-    <li class='tl'>shef(1)</li>
-    <li class='tc'>Chef Manual</li>
-    <li class='tr'>shef(1)</li>
-  </ol>
-
-  <h2 id="NAME">NAME</h2>
-<p class="man-name">
-  <code>shef</code> - <span class="man-whatis">Interactive Chef Console</span>
-</p>
-
-<h2 id="SYNOPSIS">SYNOPSIS</h2>
-
-<p><strong>shef</strong> [<em>named configuration</em>] <em>(options)</em></p>
-
-<dl>
-<dt><code>-S</code>, <code>--server CHEF_SERVER_URL</code></dt><dd>The chef server URL</dd>
-<dt><code>-z</code>, <code>--client</code></dt><dd>chef-client mode</dd>
-<dt><code>-c</code>, <code>--config CONFIG</code></dt><dd>The configuration file to use</dd>
-<dt><code>-j</code>, <code>--json-attributes JSON_ATTRIBS</code></dt><dd>Load attributes from a JSON file or URL</dd>
-<dt><code>-l</code>, <code>--log-level LOG_LEVEL</code></dt><dd>Set the logging level</dd>
-<dt><code>-s</code>, <code>--solo</code></dt><dd>chef-solo shef session</dd>
-<dt><code>-a</code>, <code>--standalone</code></dt><dd>standalone shef session</dd>
-<dt><code>-v</code>, <code>--version</code></dt><dd>Show chef version</dd>
-<dt><code>-h</code>, <code>--help</code></dt><dd>Show command options</dd>
-</dl>
-
-
-<p>When no --config option is specified, shef attempts to load a default configuration file:</p>
-
-<ul>
-<li>If a <em>named configuration</em> is given, shef will load ~/.chef/<em>named
-configuration</em>/shef.rb</li>
-<li>If no <em>named configuration</em> is given shef will load ~/.chef/shef.rb if it exists</li>
-<li>Shef falls back to loading /etc/chef/client.rb or /etc/chef/solo.rb if -z or
--s options are given and no shef.rb can be found.</li>
-<li>The --config option takes precedence over implicit configuration
-paths.</li>
-</ul>
-
-
-<h2 id="DESCRIPTION">DESCRIPTION</h2>
-
-<p><code>shef</code> is an <span class="man-ref">irb<span class="s">(1)</span></span> (interactive ruby) session customized for Chef.
-<code>shef</code> serves two primary functions: it provides a means to
-interact with a Chef Server interactively using a convenient DSL; it
-allows you to define and run Chef recipes interactively.</p>
-
-<h2 id="SYNTAX">SYNTAX</h2>
-
-<p>Shef uses irb's subsession feature to provide multiple modes of
-interaction. In addition to the primary mode which is entered on start,
-<code>recipe</code> and <code>attributes</code> modes are available.</p>
-
-<h2 id="PRIMARY-MODE">PRIMARY MODE</h2>
-
-<p>The following commands are available in the primary
-session:</p>
-
-<dl>
-<dt class="flush"><code>help</code></dt><dd>Prints a list of available commands</dd>
-<dt class="flush"><code>version</code></dt><dd>Prints the Chef version</dd>
-<dt class="flush"><code>recipe</code></dt><dd>Switches to <code>recipe</code> mode</dd>
-<dt><code>attributes</code></dt><dd>Switches to <code>attributes</code> mode</dd>
-<dt><code>run_chef</code></dt><dd>Initiates a chef run</dd>
-<dt class="flush"><code>reset</code></dt><dd>reinitializes shef</dd>
-<dt><code>echo :on|:off</code></dt><dd>Turns irb's echo function on or off. Echo is <em>on</em> by default.</dd>
-<dt><code>tracing :on|:off</code></dt><dd>Turns irb's function tracing feature on or off. Tracing is extremely
-verbose and expected to be of interest primarily to developers.</dd>
-<dt class="flush"><code>node</code></dt><dd>Returns the <em>node</em> object for the current host. See <span class="man-ref">knife-node<span class="s">(1)</span></span>
-for more information about nodes.</dd>
-<dt class="flush"><code>ohai</code></dt><dd>Prints the attributes of <em>node</em></dd>
-</dl>
-
-
-<p>In addition to these commands, shef provides a DSL for accessing data on
-the Chef Server. When working with remote data in shef, you chain method
-calls in the form <em>object type</em>.<em>operation</em>, where <em>object type</em> is in
-plural form. The following object types are available:</p>
-
-<ul>
-<li><code>nodes</code></li>
-<li><code>roles</code></li>
-<li><code>data_bags</code></li>
-<li><code>clients</code></li>
-<li><code>cookbooks</code></li>
-</ul>
-
-
-<p>For each <em>object type</em> the following operations are available:</p>
-
-<dl>
-<dt><em>object type</em>.all(<em>&block</em>)</dt><dd>Loads all items from the server. If the optional code <em>block</em> is
-given, each item will be passed to the block and the results
-returned, similar to ruby's <code>Enumerable#map</code> method.</dd>
-<dt><em>object type</em>.show(<em>object name</em>)</dt><dd><p>Aliased as <em>object type</em>.load</p>
-
-<p>Loads the singular item identified by <em>object name</em>.</p></dd>
-<dt><em>object type</em>.search(<em>query</em>, <em>&block</em>)</dt><dd><p>Aliased as <em>object type</em>.find</p>
-
-<p>Runs a search against the server and returns the matching items. If
-the optional code <em>block</em> is given each item will be passed to the
-block and the results returned.</p>
-
-<p>The <em>query</em> may be a Solr/Lucene format query given as a String, or
-a Hash of conditions. If a Hash is given, the options will be ANDed
-together. To join conditions with OR, use negative queries, or any
-advanced search syntax, you must provide give the query in String
-form.</p></dd>
-<dt><em>object type</em>.transform(:all|<em>query</em>, <em>&block</em>)</dt><dd><p>Aliased as <em>object type</em>.bulk_edit</p>
-
-<p>Bulk edit objects by processing them with the (required) code <em>block</em>.
-You can edit all objects of the given type by passing the Symbol
-<code>:all</code> as the argument, or only a subset by passing a <em>query</em> as the
-argument. The <em>query</em> is evaluated in the same way as with
-<strong>search</strong>.</p>
-
-<p>The return value of the code <em>block</em> is used to alter the behavior
-of <code>transform</code>. If the value returned from the block is <code>nil</code> or
-<code>false</code>, the object will not be saved. Otherwise, the object is
-saved after being passed to the block. This behavior can be
-exploited to create a dry run to test a data transformation.</p></dd>
-</dl>
-
-
-<h2 id="RECIPE-MODE">RECIPE MODE</h2>
-
-<p>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:</p>
-
-<ul>
-<li><a href="http://wiki.opscode.com/display/chef/Resources" data-bare-link="true">http://wiki.opscode.com/display/chef/Resources</a></li>
-<li><a href="http://wiki.opscode.com/display/chef/Recipes" data-bare-link="true">http://wiki.opscode.com/display/chef/Recipes</a></li>
-</ul>
-
-
-<p>Once you have defined resources in the recipe, you can trigger a
-convergence run via <code>run_chef</code></p>
-
-<h2 id="EXAMPLES">EXAMPLES</h2>
-
-<ul>
-<li><p>A "Hello World" interactive recipe</p>
-
-<p>chef > recipe<br />
-chef:recipe > echo :off<br />
-chef:recipe > file "/tmp/hello_world"<br />
-chef:recipe > run_chef<br />
-[Sat, 09 Apr 2011 08:56:56 -0700] INFO: Processing file[/tmp/hello_world] action create ((irb#1) line 2)<br />
-[Sat, 09 Apr 2011 08:56:56 -0700] INFO: file[/tmp/hello_world] created file /tmp/hello_world<br />
-chef:recipe > pp ls '/tmp'<br />
-[".",<br />
-"..",<br />
-"hello_world"]</p></li>
-<li><p>Search for <em>nodes</em> by role, and print their IP addresses</p>
-
-<p>chef > nodes.find(:roles => 'monitoring-server') {|n| n[:ipaddress] }<br />
-=> ["10.254.199.5"]</p></li>
-<li><p>Remove the role <em>obsolete</em> from every node in the system</p>
-
-<p>chef > nodes.transform(:all) {|n| n.run_list.delete('role[obsolete]') }<br />
- => [node[chef098b2.opschef.com], node[ree-woot], node[graphite-dev], node[fluke.localdomain], node[ghost.local], node[kallistec]]</p></li>
-</ul>
-
-
-<h2 id="BUGS">BUGS</h2>
-
-<p>The name <code>shef</code> is clever in print but is confusing when spoken aloud.
-Pronouncing <code>shef</code> as <code>chef console</code> is an imperfect workaround.</p>
-
-<p><code>shef</code> often does not perfectly replicate the context in which
-<span class="man-ref">chef-client<span class="s">(8)</span></span> configures a host, which may lead to discrepancies in
-observed behavior.</p>
-
-<p><code>shef</code> has to duplicate much code from chef-client's internal libraries
-and may become out of sync with the behavior of those libraries.</p>
-
-<h2 id="SEE-ALSO">SEE ALSO</h2>
-
-<p>  <span class="man-ref">chef-client<span class="s">(8)</span></span> <span class="man-ref">knife<span class="s">(1)</span></span>
-  <a href="http://wiki.opscode.com/display/chef/Shef" data-bare-link="true">http://wiki.opscode.com/display/chef/Shef</a></p>
-
-<h2 id="AUTHOR">AUTHOR</h2>
-
-<p>   Chef was written by Adam Jacob <a href="mailto:a&#x64;am&#x40;&#x6f;pscod&#x65;&#x2e;com" data-bare-link="true">a&#x64;&#x61;&#x6d;&#x40;o&#x70;sc&#x6f;d&#x65;&#x2e;&#x63;&#x6f;&#x6d;</a> with many
-   contributions from the community. Shef was written by Daniel DeLeo.</p>
-
-<h2 id="DOCUMENTATION">DOCUMENTATION</h2>
-
-<p>   This manual page was written by Daniel DeLeo <a href="m&#x61;&#x69;&#x6c;&#x74;o&#x3a;&#x64;a&#x6e;&#x40;&#x6f;p&#x73;&#x63;o&#x64;e&#x2e;com" data-bare-link="true">d&#x61;&#x6e;@o&#x70;&#x73;&#x63;o&#x64;e.&#x63;&#x6f;m</a>.
-   Permission is granted to copy, distribute and / or modify this
-   document under the terms of the Apache 2.0 License.</p>
-
-<h2 id="CHEF">CHEF</h2>
-
-<p>   Shef is distributed with Chef. <a href="http://wiki.opscode.com/display/chef/Home" data-bare-link="true">http://wiki.opscode.com/display/chef/Home</a></p>
-
-
-  <ol class='man-decor man-foot man foot'>
-    <li class='tl'>Chef 10.12.0</li>
-    <li class='tc'>June 2012</li>
-    <li class='tr'>shef(1)</li>
-  </ol>
-
-  </div>
-</body>
-</html>
diff --git a/distro/common/man/man1/README.md b/distro/common/man/man1/README.md
new file mode 100644
index 0000000..9a915fb
--- /dev/null
+++ b/distro/common/man/man1/README.md
@@ -0,0 +1,58 @@
+# Man pages for Knife
+
+The source of the Chef Documentation is located at
+http://docs.opscode.com/.
+
+This README documents how the man pages for all of the Knife subcommands
+that are built into the chef-client are managed.
+
+## Source Files
+
+The source files are located in the chef-docs repository:
+https://github.com/opscode/chef-docs
+
+Each Knife subcommand has its own source folder. The folder naming
+pattern begins with man_.
+
+Each man page is a single file called index.html.
+
+In the conf.py file, the following settings are unique to each man page:
+
+`today` setting is used to define the Chef version. This is because we
+don't want an arbitrary date populated in the file, yet we still need a
+version number. For example: `today = 'Chef 11.8`. 
+
+`project` setting is set to be the same as the name of the subcommand.
+For example: `project = u'knife-foo'`.
+
+`Options for man page output` settings are set to be similar across all
+man pages, but each one needs to be tailored specifically for the name
+of the man page.
+
+All of the other settings in the General Configuration section should be
+left alone. These exist to ensure that all of the doc builds are sharing
+the right common elements and have the same overall presentation.
+
+## Building Docs
+
+The docs are built using Sphinx and must be set to the `-b man` output.
+Currently, the man pages are built locally and then added to the Chef
+builds in chef-master.
+
+## Editing
+
+These files should never be edited. All of the content is pulled in from
+elsewhere in the chef-docs repo at build time. If changes need to be
+made, those changes are done elsewhere and then the man pages must be
+rebuilt. This is to help ensure that all of the changes are made across
+all of the locations in which these documents need to live. For example,
+by design, every Knife subcommand with a man page also has an HTML doc
+at docs.opscode.com/knife_foo.html.
+
+## License
+
+[Creative Commons Attribution 3.0 Unported License](http://creativecommons.org/licenses/by/3.0/)
+
+## Questions?
+
+Open an [Issue](https://github.com/opscode/chef-docs/issues) and ask.
diff --git a/distro/common/man/man1/chef-shell.1 b/distro/common/man/man1/chef-shell.1
new file mode 100644
index 0000000..d865ce5
--- /dev/null
+++ b/distro/common/man/man1/chef-shell.1
@@ -0,0 +1,115 @@
+.TH "CHEF-SHELL" "1" "Chef 11.8.0" "" "chef-shell"
+.SH NAME
+chef-shell \- The man page for the chef-shell command line tool.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+chef\-shell is a recipe debugging tool that allows the use of breakpoints within recipes. chef\-shell runs as an Interactive Ruby (IRb) session. chef\-shell supports both recipe and attribute file syntax, as well as interactive debugging features.
+.IP Note
+chef\-shell is the new name for Shef as of Chef 11.x. chef\-shell is backwards compatible and aside from the name change, has the same set of functionality as with previous releases.
+.RE
+.sp
+The chef\-shell executable can be run as a command\-line tool.
+.SH MODES
+.sp
+chef\-shell is tool that allows Knife to be run using an Interactive Ruby (IRb) session. chef\-shell currently supports recipe and attribute file syntax, as well as interactive debugging features. chef\-shell has three run modes:
+.TS
+center;
+|l|l|.
+_
+T{
+Mode
+T}	T{
+Description
+T}
+_
+T{
+Standalone
+T}	T{
+No cookbooks are loaded, and the run list is empty. This mode is the default.
+T}
+_
+T{
+Solo
+T}	T{
+chef\-shell acts as a chef\-solo client. It attempts to load the chef\-solo configuration file and JSON attributes. If the JSON attributes set a run list, it will be honored. Cookbooks will be loaded in the same way that chef\-solo loads them. chef\-solo mode is activated with the \fB\-s\fP or \fB\-\-solo\fP command line option, and JSON attributes are specified in the same way as for chef\-solo, with \fB\-j /path/to/chef\-solo.json\fP.
+T}
+_
+T{
+Client
+T}	T{
+chef\-shell acts as a chef\-client. During startup, it reads the chef\-client configuration file and contacts the server to get attributes and cookbooks. The run list will be set in the same way as normal chef\-client runs. chef\-client mode is activated with the \fB\-z\fP or \fB\-\-client\fP options. You can also specify the configuration file with \fB\-c CONFIG\fP and the server URL with \fB\-S SERVER_URL\fP.
+T}
+_
+.TE
+.SH OPTIONS
+.sp
+This command has the following syntax:
+.sp
+.nf
+.ft C
+chef\-shell OPTION VALUE OPTION VALUE ...
+.ft P
+.fi
+.sp
+This command has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-standalone\fP
+Indicates that chef\-shell will be run in standalone mode.
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-j PATH\fP, \fB\-\-json\-attributes PATH\fP
+The path to a file that contains JSON data. Use this option to override attributes that are set from other locations, such as from within a cookbook or by a role.
+.TP
+.B \fB\-l LEVEL\fP, \fB\-\-log_level LEVEL\fP
+The level of logging that will be stored in a log file: \fBdebug\fP, \fBinfo\fP, \fBwarn\fP, \fBerror\fP, or \fBfatal\fP.
+.TP
+.B \fB\-s\fP, \fB\-\-solo\fP
+Indicates that chef\-shell will be run in chef\-solo mode.
+.TP
+.B \fB\-S CHEF_SERVER_URL\fP, \fB\-\-server CHEF_SERVER_URL\fP
+The URL for the server.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-z\fP, \fB\-\-client\fP
+Indicates that chef\-shell will be run in chef\-client mode.
+.UNINDENT
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-bootstrap.1 b/distro/common/man/man1/knife-bootstrap.1
index 7b52f30..b62031e 100644
--- a/distro/common/man/man1/knife-bootstrap.1
+++ b/distro/common/man/man1/knife-bootstrap.1
@@ -1,197 +1,197 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-BOOTSTRAP" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-bootstrap\fR \- Install Chef Client on a remote host
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBbootstrap\fR \fI(options)\fR
-.
+.TH "KNIFE-BOOTSTRAP" "1" "Chef 11.8.0" "" "knife bootstrap"
+.SH NAME
+knife-bootstrap \- The man page for the knife bootstrap subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+A bootstrap is a process that installs the chef\-client on a target system so that it can run as a chef\-client and communicate with a server.
+.sp
+The \fBknife bootstrap\fP subcommand is used run a bootstrap operation that installs the chef\-client on the target system. The bootstrap operation must specify the IP address or FQDN of the target system.
+.IP Note
+To bootstrap the chef\-client on Microsoft Windows machines, the \fI\%knife-windows\fP plugins is required, which includes the necessary bootstrap scripts that are used to do the actual installation.
+.RE
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-i\fR, \fB\-\-identity\-file IDENTITY_FILE\fR
-The SSH identity file used for authentication
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-N\fR, \fB\-\-node\-name NAME\fR
-The Chef node name for your new node
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-P\fR, \fB\-\-ssh\-password PASSWORD\fR
-The ssh password
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-x\fR, \fB\-\-ssh\-user USERNAME\fR
-The ssh username
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-p\fR, \fB\-\-ssh\-port PORT\fR
-The ssh port
-.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
 .TP
-\fB\-\-bootstrap\-version VERSION\fR
-The version of Chef to install
-.
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
 .TP
-\fB\-\-bootstrap\-proxy PROXY_URL\fR
-\fBThe proxy server for the node being bootstrapped\fR
-.
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
 .TP
-\fB\-\-prerelease\fR
-Install pre\-release Chef gems
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fB\-r\fR, \fB\-\-run\-list RUN_LIST\fR
-Comma separated list of roles/recipes to apply
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
 .TP
-\fB\-\-template\-file TEMPLATE\fR
-Full path to location of template to use
-.
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
 .TP
-\fB\-\-sudo\fR
-Execute the bootstrap via sudo
-.
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
 .TP
-\fB\-d\fR, \fB\-\-distro DISTRO\fR
-Bootstrap a distro using a template
-.
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
 .TP
-\fB\-\-[no\-]host\-key\-verify\fR
-Enable host key verification, which is the default behavior\.
-.
-.SH "DESCRIPTION"
-Performs a Chef Bootstrap on the target node\. The goal of the bootstrap is to get Chef installed on the target system so it can run Chef Client with a Chef Server\. The main assumption is a baseline OS installation exists\. This sub\-command is used internally by some cloud computing plugins\.
-.
-.P
-The bootstrap sub\-command supports supplying a template to perform the bootstrap steps\. If the distro is not specified (via \fB\-d\fR or \fB\-\-distro\fR option), an Ubuntu 10\.04 host bootstrapped with RubyGems is assumed\. The \fBDISTRO\fR value corresponds to the base filename of the template, in other words \fBDISTRO\fR\.erb\. A template file can be specified with the \fB\-\-template\-file\fR option in which case the \fBDISTRO\fR is not used\. The sub\-command looks in the followin [...]
-.
-.IP "\(bu" 4
-\fBbootstrap\fR directory in the installed Chef Knife library\.
-.
-.IP "\(bu" 4
-\fBbootstrap\fR directory in the \fB$PWD/\.chef\fR\.
-.
-.IP "\(bu" 4
-\fBbootstrap\fR directory in the users \fB$HOME/\.chef\fR\.
-.
-.IP "" 0
-.
-.P
-The default bootstrap templates are scripts that get copied to the target node (FQDN)\. The following distros are supported:
-.
-.IP "\(bu" 4
-centos5\-gems
-.
-.IP "\(bu" 4
-fedora13\-gems
-.
-.IP "\(bu" 4
-ubuntu10\.04\-gems
-.
-.IP "\(bu" 4
-ubuntu10\.04\-apt
-.
-.IP "" 0
-.
-.P
-The gems installations will use RubyGems 1\.3\.6 and Chef installed as a gem\. The apt installation will use the Opscode APT repository\.
-.
-.P
-In addition to handling the software installation, these bootstrap templates do the following:
-.
-.IP "\(bu" 4
-Write the validation\.pem per the local knife configuration\.
-.
-.IP "\(bu" 4
-Write a default config file for Chef (\fB/etc/chef/client\.rb\fR) using values from the \fBknife\.rb\fR\.
-.
-.IP "\(bu" 4
-Create a JSON attributes file containing the specified run list and run Chef\.
-.
-.IP "" 0
-.
-.P
-In the case of the RubyGems, the \fBclient\.rb\fR will be written from scratch with a minimal set of values; see \fBEXAMPLES\fR\. In the case of APT Package installation, \fBclient\.rb\fR will have the \fBvalidation_client_name\fR appended if it is not set to \fBchef\-validator\fR (default config value), and the \fBnode_name\fR will be added if \fBchef_node_name\fR option is specified\.
-.
-.P
-When this is complete, the bootstrapped node will have:
-.
-.IP "\(bu" 4
-Latest Chef version installed from RubyGems or APT Packages from Opscode\. This may be a later version than the local system\.
-.
-.IP "\(bu" 4
-Be validated with the configured Chef Server\.
-.
-.IP "\(bu" 4
-Have run Chef with its default run list if one is specfied\.
-.
-.IP "" 0
-.
-.P
-Additional custom bootstrap templates can be created and stored in \fB\.chef/bootstrap/DISTRO\.erb\fR, replacing \fBDISTRO\fR with the value passed with the \fB\-d\fR or \fB\-\-distro\fR option\. See \fBEXAMPLES\fR for more information\.
-.
-.SH "EXAMPLES"
-Setting up a custom bootstrap is fairly straightforward\. Create a \fB\.chef/bootstrap\fR directory in your Chef Repository or in \fB$HOME/\.chef/bootstrap\fR\. Then create the ERB template file\.
-.
-.IP "" 4
-.
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
 .nf
-
-mkdir ~/\.chef/bootstrap
-vi ~/\.chef/bootstrap/debian5\.0\-apt\.erb
-.
+.ft C
+$ knife bootstrap FQDN_or_IP_ADDRESS (options)
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-For example, to create a new bootstrap template that should be used when setting up a new Debian node\. Edit the template to run the commands, set up the validation certificate and the client configuration file, and finally to run chef\-client on completion\. The bootstrap template can be called with:
-.
-.IP "" 4
-.
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-A\fP, \fB\-\-forward\-agent\fP
+Indicates that SSH agent forwarding is enabled.
+.TP
+.B \fB\-\-bootstrap\-proxy PROXY_URL\fP
+The proxy server for the node that is the target of a bootstrap operation.
+.TP
+.B \fB\-\-bootstrap\-version VERSION\fP
+The version of the chef\-client to install.
+.TP
+.B \fB\-d DISTRO\fP, \fB\-\-distro DISTRO\fP
+The template file to be used during a bootstrap operation. The following distributions are supported: \fBchef\-full\fP (the default bootstrap), \fBcentos5\-gems\fP, \fBfedora13\-gems\fP, \fBubuntu10.04\-gems\fP, \fBubuntu10.04\-apt\fP, \fBubuntu12.04\-gems\fP, and the name of a custom bootstrap template file. When this option is used, Knife will search for the template file in the following order: the \fBbootstrap/\fP folder in the current working directory, the \fBbootstrap/\fP folder i [...]
+.TP
+.B \fB\-G GATEWAY\fP, \fB\-\-ssh\-gateway GATEWAY\fP
+The SSH tunnel or gateway that is used to run a bootstrap action on a machine that is not accessible from the workstation.
+.TP
+.B \fB\-\-hint HINT_NAME[=HINT_FILE]\fP
+An Ohai hint to be set on the target of the bootstrap. The hint is contained in a file and is formatted as JSON: \fB{"attribute":"value","attribute":"value"...}\fP. \fBHINT_NAME\fP is the name of the hint and \fBHINT_FILE\fP is the name of the hint file located at \fB/etc/chef/ohai/hints/HINT_FILE.json\fP. Use multiple \fB\-\-hint\fP options in the command to specify multiple hints.
+.TP
+.B \fB\-i IDENTITY_FILE\fP, \fB\-\-identity\-file IDENTITY_FILE\fP
+The SSH identity file used for authentication. Key\-based authentication is recommended.
+.TP
+.B \fB\-j JSON_ATTRIBS\fP, \fB\-\-json\-attributes JSON_ATTRIBS\fP
+A JSON string that is added to the first run of a chef\-client.
+.TP
+.B \fB\-N NAME\fP, \fB\-\-node\-name NAME\fP
+The name of the node.
+.TP
+.B \fB\-\-[no\-]host\-key\-verify\fP
+Use \fB\-\-no\-host\-key\-verify\fP to disable host key verification. Default setting: \fB\-\-host\-key\-verify\fP.
+.TP
+.B \fB\-p PORT\fP, \fB\-\-ssh\-port PORT\fP
+The SSH port.
+.TP
+.B \fB\-P PASSWORD\fP, \fB\-\-ssh\-password PASSWORD\fP
+The SSH password. This can be used to pass the password directly on the command line. If this option is not specified (and a password is required) Knife will prompt for the password.
+.TP
+.B \fB\-\-prerelease\fP
+Indicates that pre\-release gems should be installed.
+.TP
+.B \fB\-r RUN_LIST\fP, \fB\-\-run\-list RUN_LIST\fP
+A comma\-separated list of roles and/or recipes to be applied.
+.TP
+.B \fB\-\-secret SECRET\fP
+The encryption key that is used for values contained within a data bag.
+.TP
+.B \fB\-\-secret\-file FILE\fP
+The path to the file that contains the encryption key.
+.TP
+.B \fB\-\-sudo\fP
+Indicates that a bootstrap operation should be executed using sudo.
+.TP
+.B \fB\-\-template\-file TEMPLATE\fP
+The path to a template file that will be used during a bootstrap operation. Do not use the \fB\-\-distro\fP option when \fB\-\-template\-file\fP is specified.
+.TP
+.B \fB\-\-use\-sudo\-password\fP
+Indicates that a bootstrap operation is done using sudo, with the password specified by the \fB\-P\fP (or \fB\-\-ssh\-password\fP) option.
+.TP
+.B \fB\-x USERNAME\fP, \fB\-\-ssh\-user USERNAME\fP
+The SSH user name.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To pass an SSH password as part of the command:
+.sp
 .nf
-
-knife bootstrap mynode\.example\.com \-\-template\-file ~/\.chef/bootstrap/debian5\.0\-apt\.erb
-.
+.ft C
+$ knife bootstrap 192.168.1.1 \-x username \-P PASSWORD \-\-sudo
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-Or,
-.
-.IP "" 4
-.
+.sp
+To use a file that contains a private key:
+.sp
 .nf
-
-knife bootstrap mynode\.example\.com \-\-distro debian5\.0\-apt
-.
+.ft C
+$ knife bootstrap 192.168.1.1 \-x username \-i ~/.ssh/id_rsa \-\-sudo
+.ft P
 .fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.IP "" 0
-.
-.P
-The \fB\-\-distro\fR parameter will automatically look in the \fB~/\.chef/bootstrap\fR directory for a file named \fBdebian5\.0\-apt\.erb\fR\.
-.
-.P
-Templates provided by the Chef installation are located in \fBBASEDIR/lib/chef/knife/bootstrap/*\.erb\fR, where \fIBASEDIR\fR is the location where the package or Gem installed the Chef client libraries\.
-.
-.SH "BUGS"
-\fBknife bootstrap\fR is not capable of bootstrapping multiple hosts in parallel\.
-.
-.P
-The bootstrap script is passed as an argument to sh(1) on the remote system, so sensitive information contained in the script will be visible to other users via the process list using tools such as ps(1)\.
-.
-.SH "SEE ALSO"
-\fBknife\-ssh\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-client.1 b/distro/common/man/man1/knife-client.1
index 3fc078e..79d433d 100644
--- a/distro/common/man/man1/knife-client.1
+++ b/distro/common/man/man1/knife-client.1
@@ -1,99 +1,368 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.TH "KNIFE-CLIENT" "1" "Chef 11.8.0" "" "knife client"
+.SH NAME
+knife-client \- The man page for the knife client subcommand.
 .
-.TH "KNIFE\-CLIENT" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
+.nr rst2man-indent-level 0
 .
-.SH "NAME"
-\fBknife\-client\fR \- Manage Chef API Clients
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBclient\fR \fIsub\-command\fR \fI(options)\fR
-.
-.SH "SUB\-COMMANDS"
-Client subcommands follow a basic create, read, update, delete (CRUD) pattern\. The Following subcommands are available:
-.
-.SH "BULK DELETE"
-\fBknife client bulk delete\fR \fIregex\fR \fI(options)\fR
-.
-.P
-Delete clients where the client name matches the regular expression \fIregex\fR on the Chef Server\. The regular expression should be given as a quoted string, and not surrounded by forward slashes\.
-.
-.SH "CREATE"
-\fBknife client create\fR \fIclient name\fR \fI(options)\fR
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
 .
+.sp
+When a node runs the chef\-client for the first time, it generally does not yet have an API client identity, and so it cannot make authenticated requests to the server. This is where the validation client\-\-\-known as the chef\-validator\-\-\-comes in. When the chef\-client runs, it checks if it has a client key. If the client key does not exist, it then attempts to borrow the identity of the chef\-validator to register itself with the server. In order to register with the server, the p [...]
+.sp
+Once the chef\-client has registered itself with the server, it no longer uses the validation client for anything. It is recommended that you delete the private key for the chef\-validator from the host after the host has registered or use the \fBdelete_validation\fP recipe that can be found in the \fBchef\-client\fP cookbook (\fI\%https://github.com/opscode-cookbooks/chef-client\fP).
+.sp
+The \fBknife client\fP subcommand is used to manage an API client list and their associated RSA public key\-pairs. This allows authentication requests to be made to the server by any entity that uses the Chef Server API, such as the chef\-client and Knife.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client [ARGUMENT] (options)
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-a\fR, \fB\-\-admin\fR
-Create the client as an admin
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-f\fR, \fB\-\-file FILE\fR
-Write the key to a file
-.
-.P
-Create a new client\. This generates an RSA keypair\. The private key will be displayed on \fISTDOUT\fR or written to the named file\. The public half will be stored on the Server\. For \fIchef\-client\fR systems, the private key should be copied to the system as \fB/etc/chef/client\.pem\fR\.
-.
-.P
-Admin clients should be created for users that will use \fIknife\fR to access the API as an administrator\. The private key will generally be copied to \fB~/\.chef/client\e_name\.pem\fR and referenced in the \fBknife\.rb\fR configuration file\.
-.
-.SH "DELETE"
-\fBknife client delete\fR \fIclient name\fR \fI(options)\fR
-.
-.P
-Deletes a registered client\.
-.
-.SH "EDIT"
-\fBclient edit\fR \fIclient name\fR \fI(options)\fR
-.
-.P
-Edit a registered client\.
-.
-.SH "LIST"
-\fBclient list\fR \fI(options)\fR
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-w\fR, \fB\-\-with\-uri\fR
-Show corresponding URIs
-.
-.P
-List all registered clients\.
-.
-.SH "REREGISTER"
-\fBclient reregister\fR \fIclient name\fR \fI(options)\fR
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-f\fR, \fB\-\-file FILE\fR
-Write the key to a file
-.
-.P
-Regenerate the RSA keypair for a client\. The public half will be stored on the server and the private key displayed on \fISTDOUT\fR or written to the named file\. This operation will invalidate the previous keypair used by the client, preventing it from authenticating with the Chef Server\. Use care when reregistering the validator client\.
-.
-.SH "SHOW"
-\fBclient show\fR \fIclient name\fR \fI(options)\fR
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-a\fR, \fB\-\-attribute ATTR\fR
-Show only one attribute
-.
-.P
-Show a client\. Output format is determined by the \-\-format option\.
-.
-.SH "DESCRIPTION"
-Clients are identities used for communication with the Chef Server API, roughly equivalent to user accounts on the Chef Server, except that clients only communicate with the Chef Server API and are authenticated via request signatures\.
-.
-.P
-In the typical case, there will be one client object on the server for each node, and the corresponding client and node will have identical names\.
-.
-.P
-In the Chef authorization model, there is one special client, the "validator", which is authorized to create new non\-administrative clients but has minimal privileges otherwise\. This identity is used as a sort of "guest account" to create a client identity when initially setting up a host for management with Chef\.
-.
-.SH "SEE ALSO"
-\fBknife\-node\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH BULK DELETE
+.sp
+The \fBbulk delete\fP argument is used to delete any API client that matches a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (/).
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client bulk delete REGEX
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to create a new API client. This process will generate an RSA key pair for the named API client. The public key will be stored on the server and the private key will be displayed on \fBSTDOUT\fP or written to a named file.
+.INDENT 0.0
+.IP \(bu 2
+For the chef\-client, the private key should be copied to the system as /etc/chef/client.pem.
+.IP \(bu 2
+For Knife, the private key is typically copied to ~/.chef/client_name.pem and referenced in the knife.rb configuration file.
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client create CLIENT_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-admin\fP
+Indicates that a client will be created as an admin client. This is required when users of the open source server need to access the Chef Server API as an administrator. This option only works when used with the open source server and will have no effect when used with Hosted Chef or Private Chef.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To create a Chef Admin client with the name "exampleorg" and save its private key to a file, enter:
+.sp
+.nf
+.ft C
+$ knife client create exampleorg \-a \-f "/etc/chef/client.pem"
+.ft P
+.fi
+.sp
+When running the \fBcreate\fP argument on Hosted Chef or Private Chef, be sure to omit the \fB\-a\fP option:
+.sp
+.nf
+.ft C
+$ knife client create exampleorg \-f "/etc/chef/client.pem"
+.ft P
+.fi
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete a registered API client.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client delete CLIENT_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To delete a client with the name "client_foo", enter:
+.sp
+.nf
+.ft C
+$ knife client delete client_foo
+.ft P
+.fi
+.sp
+Type \fBY\fP to confirm a deletion.
+.SH EDIT
+.sp
+The \fBedit\fP argument is used to edit the details of a registered API client. When this argument is run, Knife will open $EDITOR to enable editing of the \fBadmin\fP attribute. (None of the other attributes should be changed using this argument.) When finished, Knife will update the server with those changes.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client edit CLIENT_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To edit a client with the name "exampleorg", enter:
+.sp
+.nf
+.ft C
+$ knife client edit exampleorg
+.ft P
+.fi
+.SH LIST
+.sp
+The \fBlist\fP argument is used to view a list of registered API client.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client list (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To verify the API client list for the server, enter:
+.sp
+.nf
+.ft C
+$ knife client list
+.ft P
+.fi
+.sp
+to return something similar to:
+.sp
+.nf
+.ft C
+exampleorg
+i\-12345678
+rs\-123456
+.ft P
+.fi
+.sp
+To verify that an API client can authenticate to the
+server correctly, try getting a list of clients using \fB\-u\fP and \fB\-k\fP options to specify its name and private key:
+.sp
+.nf
+.ft C
+$ knife client list \-u ORGNAME \-k .chef/ORGNAME.pem
+.ft P
+.fi
+.SH REREGISTER
+.sp
+The \fBreregister\fP argument is used to regenerate an RSA key pair for an API client. The public key will be stored on the server and the private key will be displayed on \fBSTDOUT\fP or written to a named file.
+.IP Note
+Running this argument will invalidate the previous RSA key pair, making it unusable during authentication to the server.
+.RE
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client reregister CLIENT_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To regenerate the RSA key pair for a client named "testclient" and save it to a file named "rsa_key", enter:
+.sp
+.nf
+.ft C
+$ knife client regenerate testclient \-f rsa_key
+.ft P
+.fi
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to show the details of an API client.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife client show CLIENT_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP
+The attribute (or attributes) to show.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view a client named "testclient", enter:
+.sp
+.nf
+.ft C
+$ knife client show testclient
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+admin:       false
+chef_type:   client
+json_class:  Chef::ApiClient
+name:        testclient
+public_key:
+.ft P
+.fi
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife role show devops \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-configure.1 b/distro/common/man/man1/knife-configure.1
index 85c72dc..e06793e 100644
--- a/distro/common/man/man1/knife-configure.1
+++ b/distro/common/man/man1/knife-configure.1
@@ -1,88 +1,150 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-CONFIGURE" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-configure\fR \- Generate configuration files for knife or Chef Client
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBconfigure\fR [client] \fI(options)\fR
-.
-.SH "DESCRIPTION"
-Generates a knife\.rb configuration file interactively\. When given the \-\-initial option, also creates a new administrative user\.
-.
-.SH "CONFIGURE SUBCOMMANDS"
-\fBknife configure\fR \fI(options)\fR
-.
+.TH "KNIFE-CONFIGURE" "1" "Chef 11.8.0" "" "knife configure"
+.SH NAME
+knife-configure \- The man page for the knife configure subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife configure\fP subcommand is used to create the knife.rb and client.rb files so that they can be distributed to workstations and nodes.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-i\fR, \fB\-\-initial\fR
-Create an initial API Client
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-r\fR, \fB\-\-repository REPO\fR
-The path to your chef\-repo
-.
-.P
-Create a configuration file for knife\. This will prompt for values to enter into the file\. Default values are listed in square brackets if no other entry is typed\. See \fBknife\fR(1) for a description of configuration options\.
-.
-.P
-\fBknife configure client\fR \fIdirectory\fR
-.
-.P
-Read the \fIknife\.rb\fR config file and generate a config file suitable for use in \fI/etc/chef/client\.rb\fR and copy the validation certificate into the specified \fIdirectory\fR\.
-.
-.SH "EXAMPLES"
-.
-.IP "\(bu" 4
-On a freshly installed Chef Server, use \fIknife configure \-i\fR to create an administrator and knife configuration file\. Leave the field blank to accept the default value\. On most systems, the default values are acceptable\.
-.
-.IP
-user at host$ knife configure \-i
-.
-.br
-Please enter the chef server URL: [http://localhost:4000]
-.
-.br
-Please enter a clientname for the new client: [username]
-.
-.br
-Please enter the existing admin clientname: [chef\-webui]
-.
-.br
-Please enter the location of the existing admin client\'s private key: [/etc/chef/webui\.pem]
-.
-.br
-Please enter the validation clientname: [chef\-validator]
-.
-.br
-Please enter the location of the validation key: [/etc/chef/validation\.pem]
-.
-.br
-Please enter the path to a chef repository (or leave blank):
-.
-.br
-Creating initial API user\.\.\.
-.
-.br
-Created (or updated) client[username]
-.
-.br
-Configuration file written to /home/username/\.chef/knife\.rb
-.
-.IP
-This creates a new administrator client named \fIusername\fR, writes a configuration file to \fI/home/username/\.chef/knife\.rb\fR, and the private key to \fI/home/username/\.chef/username\.pem\fR\. The configuration file and private key may be copied to another system to facilitate administration of the Chef Server from a remote system\. Depending on the value given for the Chef Server URL, you may need to modify that setting after copying to a remote host\.
-.
-.IP "" 0
-.
-.SH "SEE ALSO"
-\fBknife\fR(1) \fBknife\-client\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife configure (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-admin\-client\-name NAME\fP
+The name of the client, typically the name of the admin client.
+.TP
+.B \fB\-\-admin\-client\-key PATH\fP
+The path to the private key used by the client, typically a file named \fBadmin.pem\fP.
+.TP
+.B \fB\-i\fP, \fB\-\-initial\fP
+Use to create a API client, typically an administrator client on a freshly\-installed server.
+.TP
+.B \fB\-r REPO\fP, \fB\-\-repository REPO\fP
+The path to the chef\-repo.
+.TP
+.B \fB\-\-validation\-client\-name NAME\fP
+The name of the validation client.
+.TP
+.B \fB\-\-validation\-key PATH\fP
+The path to the validation key used by the client, typically a file named \fBvalidation.pem\fP.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To create a knife.rb file, enter:
+.sp
+.nf
+.ft C
+$ knife configure
+.ft P
+.fi
+.sp
+To configure a client.rb, enter:
+.sp
+.nf
+.ft C
+$ knife configure client \(aq/directory\(aq
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-cookbook-site.1 b/distro/common/man/man1/knife-cookbook-site.1
index b3267a2..8a2b535 100644
--- a/distro/common/man/man1/knife-cookbook-site.1
+++ b/distro/common/man/man1/knife-cookbook-site.1
@@ -1,145 +1,477 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-COOKBOOK\-SITE" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-cookbook\-site\fR \- Install and update open source cookbooks
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBcookbook site\fR \fIsub\-command\fR \fI(options)\fR
-.
-.SH "COOKBOOK SITE SUB\-COMMANDS"
-\fBknife cookbook site\fR provides the following subcommands:
-.
-.SH "INSTALL"
-\fBcookbook site install COOKBOOK [VERSION]\fR \fI(options)\fR
-.
+.TH "KNIFE-COOKBOOK-SITE" "1" "Chef 11.8.0" "" "knife cookbook site"
+.SH NAME
+knife-cookbook-site \- The man page for the knife cookbook site subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The Cookbooks Site API is used to provide access to the cookbooks community hosted at \fI\%https://cookbooks.opscode.com\fP. All of the cookbooks in the community are accessible through a REST API located at \fI\%https://cookbooks.opscode.com/api/v1/\fP by using any of the supported endpoints. In most cases, using Knife and the \fBknife cookbook site\fP sub\-command (and any of its arguments) is the recommended method of interacting with these cookbooks, but in some cases, using the REST [...]
+.sp
+The \fBknife cookbook site\fP subcommand is used to interact with cookbooks that are located at \fI\%https://cookbooks.opscode.com\fP. A user account is required for any community actions that write data to this site. The following arguments do not require a user account: \fBdownload\fP, \fBsearch\fP, \fBinstall\fP, and \fBlist\fP.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook site [ARGUMENT] (options)
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-D\fR, \fB\-\-skip\-dependencies\fR
-Skip automatic installation of dependencies\.
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-o\fR, \fB\-\-cookbook\-path PATH\fR
-Install cookbooks to PATH
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-B\fR, \fB\-\-branch BRANCH\fR
-Default branch to work with [defaults to master]
-.
-.P
-Uses git(1) version control in conjunction with the cookbook site to install community contributed cookbooks to your local cookbook repository\. Running \fBknife cookbook site install\fR does the following:
-.
-.IP "1." 4
-A new "pristine copy" branch is created in git for tracking the upstream;
-.
-.IP "2." 4
-All existing cookbooks are removed from the branch;
-.
-.IP "3." 4
-The cookbook is downloaded from the cookbook site in tarball form;
-.
-.IP "4." 4
-The downloaded cookbook is untarred, and its contents commited via git;
-.
-.IP "5." 4
-The pristine copy branch is merged into the master branch\.
-.
-.IP "" 0
-.
-.P
-By installing cookbook with this process, you can locally modify the upstream cookbook in your master branch and let git maintain your changes as a separate patch\. When an updated upstream version becomes available, you will be able to merge the upstream changes while maintaining your local modifications\.
-.
-.P
-Unless \fI\-\-skip\-dependencies\fR is specified, the process is applied recursively to all the cookbooks \fICOOKBOOK\fR depends on (via metadata \fIdependencies\fR)\.
-.
-.SH "DOWNLOAD"
-\fBknife cookbook site download COOKBOOK [VERSION]\fR \fI(options)\fR
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-f\fR, \fB\-\-file FILE\fR
-The filename to write to
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-\-force\fR
-Force download deprecated cookbook
-.
-.P
-Downloads a specific cookbook from the Community site, optionally specifying a certain version\.
-.
-.SH "LIST"
-\fBknife cookbook site list\fR \fI(options)\fR
-.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
 .TP
-\fB\-w\fR, \fB\-\-with\-uri\fR
-Show corresponding URIs
-.
-.P
-Lists available cookbooks from the Community site\.
-.
-.SH "SEARCH"
-\fBknife cookbook site search QUERY\fR \fI(options)\fR
-.
-.P
-Searches for available cookbooks matching the specified query\.
-.
-.SH "SHARE"
-\fBknife cookbook site share COOKBOOK CATEGORY\fR \fI(options)\fR
-.
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
 .TP
-\fB\-k\fR, \fB\-\-key KEY\fR
-API Client Key
-.
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
 .TP
-\fB\-u\fR, \fB\-\-user USER\fR
-API Client Username
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fB\-o\fR, \fB\-\-cookbook\-path PATH:PATH\fR
-A colon\-separated path to look for cookbooks in
-.
-.P
-Uploads the specified cookbook using the given category to the Opscode cookbooks site\. Requires a login user and certificate for the Opscode Cookbooks site\. By default, knife will use the username and API key you\'ve configured in your configuration file; otherwise you must explicitly set these values on the command line or use an alternate configuration file\.
-.
-.SH "UNSHARE"
-\fBknife cookbook site unshare COOKBOOK\fR
-.
-.P
-Stops sharing the specified cookbook on the Opscode cookbooks site\.
-.
-.SH "SHOW"
-\fBknife cookbook site show COOKBOOK [VERSION]\fR \fI(options)\fR
-.
-.P
-Shows information from the site about a particular cookbook\.
-.
-.SH "DESCRIPTION"
-The cookbook site, \fIhttp://community\.opscode\.com/\fR, is a cookbook distribution service operated by Opscode\. This service provides users with a central location to publish cookbooks for sharing with other community members\.
-.
-.P
-\fBknife cookbook site\fR commands provide an interface to the cookbook site\'s HTTP API\. For commands that read data from the API, no account is required\. In order to upload cookbooks using the \fBknife cookbook site share\fR command, you must create an account on the cookbook site and configure your credentials via command line option or in your knife configuration file\.
-.
-.SH "EXAMPLES"
-Uploading cookbooks to the Opscode cookbooks site:
-.
-.IP "" 4
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH DOWNLOAD
+.sp
+The \fBdownload\fP argument is used to download a cookbook from the community website. A cookbook will be downloaded as a tar.gz archive and placed in the current working directory. If a cookbook (or cookbook version) has been deprecated and the \fB\-\-force\fP option is not used, Knife will alert the user that the cookbook is deprecated and then will provide the name of the most recent non\-deprecated version of that cookbook.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
 .nf
-
-knife cookbook site share example Other \-k ~/\.chef/USERNAME\.pem \-u USERNAME
-.
+.ft C
+$ knife cookbook site download COOKBOOK_NAME [COOKBOOK_VERSION] (options)
+.ft P
 .fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBCOOKBOOK_VERSION\fP
+The version of a cookbook to be downloaded. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, Knife will prompt for a version.
+.TP
+.B \fB\-f\fP, \fB\-\-force\fP
+Indicates that an existing directory will be overwritten.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To download the cookbook "getting\-started", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook site download getting\-started
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+Downloading getting\-started from the cookbooks site at version 0.3.0 to
+  /Users/sdanna/opscodesupport/getting\-started\-0.3.0.tar.gz
+Cookbook saved: /Users/sdanna/opscodesupport/getting\-started\-0.3.0.tar.gz
+.ft P
+.fi
+.SH INSTALL
+.sp
+The \fBinstall\fP argument is used to install a cookbook that has been downloaded from the community site to a local git repository . This action uses the git version control system in conjunction with the \fI\%https://cookbooks.opscode.com\fP site to install community\-contributed cookbooks to the local chef\-repo. Using this argument does the following:
+.INDENT 0.0
+.INDENT 3.5
+.INDENT 0.0
+.IP 1. 3
+A new "pristine copy" branch is created in git for tracking the upstream.
+.IP 2. 3
+All existing versions of a cookbook are removed from the branch.
+.IP 3. 3
+The cookbook is downloaded from \fI\%https://cookbooks.opscode.com\fP in the tar.gz format.
+.IP 4. 3
+The downloaded cookbook is untarred and its contents are committed to git and a tag is created.
+.IP 5. 3
+The "pristine copy" branch is merged into the master branch.
+.UNINDENT
+.UNINDENT
+.UNINDENT
+.sp
+This process allows the upstream cookbook in the master branch to be modified while letting git maintain changes as a separate patch. When an updated upstream version becomes available, those changes can be merged while maintaining any local modifications.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook site install COOKBOOK_NAME [COOKBOOK_VERSION] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-b\fP, \fB\-\-use\-current\-branch\fP
+Indicates that the current branch will be used.
+.TP
+.B \fB\-B BRANCH\fP, \fB\-\-branch BRANCH\fP
+The name of the default branch. This will default to the master branch.
+.TP
+.B \fBCOOKBOOK_VERSION\fP
+The version of the cookbook to be installed. If a version is not specified, the most recent version of the cookbook will be installed.
+.TP
+.B \fB\-D\fP, \fB\-\-skip\-dependencies\fP
+Indicates that all cookbooks to which the installed cookbook has a dependency will not be installed.
+.TP
+.B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP
+The directory in which cookbook are created. This can be a colon\-separated path.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To install the cookbook "getting\-started", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook site install getting\-started
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+Installing getting\-started to /Users/sdanna/opscodesupport/.chef/../cookbooks
+Checking out the master branch.
+Creating pristine copy branch chef\-vendor\-getting\-started
+Downloading getting\-started from the cookbooks site at version 0.3.0 to
+  /Users/sdanna/opscodesupport/.chef/../cookbooks/getting\-started.tar.gz
+Cookbook saved: /Users/sdanna/opscodesupport/.chef/../cookbooks/getting\-started.tar.gz
+Removing pre\-existing version.
+Uncompressing getting\-started version /Users/sdanna/opscodesupport/.chef/../cookbooks.
+removing downloaded tarball
+1 files updated, committing changes
+Creating tag cookbook\-site\-imported\-getting\-started\-0.3.0
+Checking out the master branch.
+Updating 4d44b5b..b4c32f2
+Fast\-forward
+ cookbooks/getting\-started/README.rdoc              |    4 +++
+ cookbooks/getting\-started/attributes/default.rb    |    1 +
+ cookbooks/getting\-started/metadata.json            |   29 ++++++++++++++++++++
+ cookbooks/getting\-started/metadata.rb              |    6 ++++
+ cookbooks/getting\-started/recipes/default.rb       |   23 +++++++++++++++
+ .../templates/default/chef\-getting\-started.txt.erb |    5 +++
+ 6 files changed, 68 insertions(+), 0 deletions(\-)
+ create mode 100644 cookbooks/getting\-started/README.rdoc
+ create mode 100644 cookbooks/getting\-started/attributes/default.rb
+ create mode 100644 cookbooks/getting\-started/metadata.json
+ create mode 100644 cookbooks/getting\-started/metadata.rb
+ create mode 100644 cookbooks/getting\-started/recipes/default.rb
+ create mode 100644 cookbooks/getting\-started/templates/default/chef\-getting\-started.txt.erb
+Cookbook getting\-started version 0.3.0 successfully installed
+.ft P
+.fi
+.SH LIST
+.sp
+The \fBlist\fP argument is used to view a list of cookbooks that are currently available at \fI\%https://cookbooks.opscode.com\fP.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook site list
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view a list of cookbooks at \fI\%https://cookbooks.opscode.com\fP server, enter:
+.sp
+.nf
+.ft C
+$ knife cookbook site list
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+1password             homesick              rabbitmq
+7\-zip                 hostname              rabbitmq\-management
+AmazonEC2Tag          hosts                 rabbitmq_chef
+R                     hosts\-awareness       rackspaceknife
+accounts              htop                  radiant
+ack\-grep              hudson                rails
+activemq              icinga                rails_enterprise
+ad                    id3lib                redis\-package
+ad\-likewise           iftop                 redis2
+ant                   iis                   redmine
+[...truncated...]
+.ft P
+.fi
+.SH SEARCH
+.sp
+The \fBsearch\fP argument is used to search for a cookbook at \fI\%https://cookbooks.opscode.com\fP. A search query is used to return a list of cookbooks at \fI\%https://cookbooks.opscode.com\fP and uses the same syntax as the \fBknife search\fP sub\-command.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook site search SEARCH_QUERY (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To search for all of the cookbooks that can be used with Apache, enter:
+.sp
+.nf
+.ft C
+$ knife cookbook site search apache*
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+apache2:
+  cookbook:              http://cookbooks.opscode.com/api/v1/cookbooks/apache2
+  cookbook_description:  Installs and configures apache2 using Debian symlinks with helper definitions
+  cookbook_maintainer:   opscode
+  cookbook_name:         apache2
+instiki:
+  cookbook:              http://cookbooks.opscode.com/api/v1/cookbooks/instiki
+  cookbook_description:  Installs instiki, a Ruby on Rails wiki server under passenger+Apache2.
+  cookbook_maintainer:   jtimberman
+  cookbook_name:         instiki
+kickstart:
+  cookbook:              http://cookbooks.opscode.com/api/v1/cookbooks/kickstart
+  cookbook_description:  Creates apache2 vhost and serves a kickstart file.
+  cookbook_maintainer:   opscode
+  cookbook_name:         kickstart
+[...truncated...]
+.ft P
+.fi
+.SH SHARE
+.sp
+The \fBshare\fP argument is used to add a cookbook to \fI\%https://cookbooks.opscode.com\fP. This action will require a user account and a certificate for \fI\%http://community.opscode.com\fP. By default, Knife will use the user name and API key that is identified in the configuration file used during the upload; otherwise these values must be specified on the command line or in an alternate configuration file. If a cookbook already exists on \fI\%https://cookbooks.opscode.com\fP, then o [...]
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook site share COOKBOOK_NAME CATEGORY (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBCATEGORY\fP
+The cookbook category: \fBDatabases\fP, \fBWeb Servers\fP, \fBProcess Management\fP, \fBMonitoring and Trending\fP, \fBProgramming Languages\fP, \fBPackage Management\fP, \fBApplications\fP, \fBNetworking\fP, \fBOperations Systems and Virtualization\fP, \fBUtilities\fP, or \fBOther\fP.
+.TP
+.B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP
+The directory in which cookbook are created. This can be a colon\-separated path.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To share a cookbook named "apache2":
+.sp
+.nf
+.ft C
+$ knife cookbook site share "apache2" "Web Servers"
+.ft P
+.fi
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to view information about a cookbook on \fI\%https://cookbooks.opscode.com\fP.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook site show COOKBOOK_NAME [COOKBOOK_VERSION]
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBCOOKBOOK_VERSION\fP
+The version of a cookbook to be shown. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, a list of cookbook versions will be returned.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To show the details for a cookbook named "haproxy":
+.sp
+.nf
+.ft C
+$ knife cookbook site show haproxy
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+average_rating:
+category:        Networking
+created_at:      2009\-10\-25T23:51:07Z
+description:     Installs and configures haproxy
+external_url:
+latest_version:  http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_3
+maintainer:      opscode
+name:            haproxy
+updated_at:      2011\-06\-30T21:53:25Z
+versions:
+   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_3
+   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_2
+   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_1
+   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/1_0_0
+   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/0_8_1
+   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/0_8_0
+   http://cookbooks.opscode.com/api/v1/cookbooks/haproxy/versions/0_7_0
+.ft P
+.fi
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife role show devops \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.SH UNSHARE
+.sp
+The \fBunshare\fP argument is used to stop the sharing of a cookbook at \fI\%https://cookbooks.opscode.com\fP. Only the maintainer of a cookbook may perform this action.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook site unshare COOKBOOK_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To unshare a cookbook named "getting\-started", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook site unshare getting\-started
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.IP "" 0
-.
-.SH "SEE ALSO"
-\fBknife\-cookbook(1)\fR \fIhttp://community\.opscode\.com/cookbooks\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-cookbook.1 b/distro/common/man/man1/knife-cookbook.1
index 1353bb6..c9b4e80 100644
--- a/distro/common/man/man1/knife-cookbook.1
+++ b/distro/common/man/man1/knife-cookbook.1
@@ -1,345 +1,642 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-COOKBOOK" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-cookbook\fR \- upload and manage chef cookbooks
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBcookbook\fR \fIsub\-command\fR \fI(options)\fR
-.
-.SH "SUB\-COMMANDS"
-\fBknife cookbook\fR supports the following sub commands:
-.
-.SH "LIST"
-\fBknife cookbook list\fR \fI(options)\fR
-.
+.TH "KNIFE-COOKBOOK" "1" "Chef 11.8.0" "" "knife cookbook"
+.SH NAME
+knife-cookbook \- The man page for the knife cookbook subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+A cookbook is the fundamental unit of configuration and policy distribution. Each cookbook defines a scenario, such as everything needed to install and configure MySQL, and then it contains all of the components that are required to support that scenario, including:
+.INDENT 0.0
+.IP \(bu 2
+Attribute values that are set on nodes
+.IP \(bu 2
+Definitions that allow the creation of reusable collections of resources
+.IP \(bu 2
+File distributions
+.IP \(bu 2
+Libraries that extend the chef\-client and/or provide helpers to Ruby code
+.IP \(bu 2
+Recipes that specify which resources to manage and the order in which those resources will be applied
+.IP \(bu 2
+Custom resources and providers
+.IP \(bu 2
+Templates
+.IP \(bu 2
+Versions
+.IP \(bu 2
+Metadata about recipes (including dependencies), version constraints, supported platforms, and so on
+.UNINDENT
+.sp
+The \fBknife cookbook\fP subcommand is used to interact with cookbooks that are located on the server or the local chef\-repo.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook [ARGUMENT] (options)
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-a\fR, \fB\-\-all\fR
-show all versions of a cookbook instead of just the most recent
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-w\fR, \fB\-\-with\-uri\fR
-show corresponding uris
-.
-.P
-Lists the cookbooks available on the Chef server\.
-.
-.SH "SHOW"
-\fBknife cookbook show cookbook [version] [part] [filename]\fR \fI(options)\fR
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-f\fR, \fB\-\-fqdn fqdn\fR
-the fqdn of the host to see the file for
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-p\fR, \fB\-\-platform platform\fR
-the platform to see the file for
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-v\fR, \fB\-\-platform\-version version\fR
-the platform version to see the file for
-.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
 .TP
-\fB\-w\fR, \fB\-\-with\-uri\fR
-Show corresponding URIs
-.
-.P
-show a particular part of a \fIcookbook\fR for the specified \fIversion\fR\. \fIpart\fR can be one of:
-.
-.IP "\(bu" 4
-\fIattributes\fR
-.
-.IP "\(bu" 4
-\fIdefinitions\fR
-.
-.IP "\(bu" 4
-\fIfiles\fR
-.
-.IP "\(bu" 4
-\fIlibraries\fR
-.
-.IP "\(bu" 4
-\fIproviders\fR
-.
-.IP "\(bu" 4
-\fIrecipes\fR
-.
-.IP "\(bu" 4
-\fIresources\fR
-.
-.IP "\(bu" 4
-\fItemplates\fR
-.
-.IP "" 0
-.
-.SH "UPLOAD"
-\fBknife cookbook upload [cookbooks\.\.\.]\fR \fI(options)\fR
-.
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
 .TP
-\fB\-a\fR, \fB\-\-all\fR
-upload all cookbooks, rather than just a single cookbook
-.
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
 .TP
-\fB\-o\fR, \fB\-\-cookbook\-path path:path\fR
-a colon\-separated path to look for cookbooks in
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fB\-d\fR, \fB\-\-upload\-dependencies\fR
-Uploads additional cookbooks that this cookbook lists in as dependencies in its metadata\.
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
 .TP
-\fB\-E\fR, \fB\-\-environment ENVIRONMENT\fR
-An \fIENVIRONMENT\fR to apply the uploaded cookbooks to\. Specifying this option will cause knife to edit the \fIENVIRONMENT\fR to place a strict version constraint on the cookbook version(s) uploaded\.
-.
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
 .TP
-\fB\-\-freeze\fR
-Sets the frozen flag on the uploaded cookbook(s) Any future attempt to modify the cookbook without changing the version number will return an error unless \-\-force is specified\.
-.
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
 .TP
-\fB\-\-force\fR
-Overrides the frozen flag on a cookbook, allowing you to overwrite a cookbook version that has previously been uploaded with the \-\-freeze option\.
-.
-.P
-Uploads one or more cookbooks from your local cookbook repository(ies) to the Chef Server\. Only files that don\'t yet exist on the server will be uploaded\.
-.
-.P
-As the command parses the name args as 1\.\.n cookbook names:
-.
-.IP "" 4
-.
-.nf
-
-`knife cookbook upload COOKBOOK COOKBOOK \.\.\.`
-.
-.fi
-.
-.IP "" 0
-.
-.P
-works for one to many cookbooks\.
-.
-.SH "DOWNLOAD"
-\fBknife cookbook download cookbook [version]\fR \fI(options)\fR
-.
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
 .TP
-\fB\-d\fR, \fB\-\-dir download_directory\fR
-the directory to download the cookbook into
-.
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
 .TP
-\fB\-f\fR, \fB\-\-force\fR
-overwrite an existing directory with the download
-.
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
 .TP
-\fB\-n\fR, \fB\-\-latest\fR
-download the latest version of the cookbook
-.
-.P
-download a cookbook from the chef server\. if no version is specified and only one version exists on the server, that version will be downloaded\. if no version is specified and multiple versions are available on the server, you will be prompted for a version to download\.
-.
-.SH "DELETE"
-\fBknife cookbook delete cookbook [version]\fR \fI(options)\fR
-.
-.TP
-\fB\-a\fR, \fB\-\-all\fR
-delete all versions
-.
-.TP
-\fB\-p\fR, \fB\-\-purge\fR
-purge files from backing store\. this will disable any cookbook that contains any of the same files as the cookbook being purged\.
-.
-.P
-delete the specified \fIversion\fR of the named \fIcookbook\fR\. if no version is specified, and only one version exists on the server, that version will be deleted\. if multiple versions are available on the server, you will be prompted for a version to delete\.
-.
-.SH "BULK DELETE"
-\fBknife cookbook bulk delete regex\fR \fI(options)\fR
-.
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
 .TP
-\fB\-p\fR, \fB\-\-purge\fR
-purge files from backing store\. this will disable any cookbook that contains any of the same files as the cookbook being purged\.
-.
-.P
-delete cookbooks on the chef server based on a regular expression\. the regular expression (\fIregex\fR) should be in quotes, not in //\'s\.
-.
-.SH "COOKBOOK CREATE"
-\fBknife cookbook create cookbook\fR \fI(options)\fR
-.
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
 .TP
-\fB\-o\fR, \fB\-\-cookbook\-path path\fR
-the directory where the cookbook will be created
-.
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
 .TP
-\fB\-r\fR, \fB\-\-readme\-format format\fR
-format of the readme file md, mkd, txt, rdoc
-.
-.TP
-\fB\-c\fR, \fB\-\-copyright copyright\fR
-name of copyright holder
-.
-.TP
-\fB\-i\fR, \fB\-\-license license\fR
-license for cookbook, apachev2 or none
-.
-.TP
-\fB\-e\fR, \fB\-\-email email\fR
-email address of cookbook maintainer
-.
-.P
-this is a helper command that creates a new cookbook directory in the \fBcookbook_path\fR\. the following directories and files are created for the named cookbook\.
-.
-.IP "\(bu" 4
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH BULK DELETE
+.sp
+The \fBbulk delete\fP argument is used to delete cookbook files that match a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (/).
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook bulk delete REGEX (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-p\fP, \fB\-\-purge\fP
+Indicates that a cookbook (or cookbook version) will be removed entirely from the server. This action should be used carefully because only one copy of any single file is stored on the server. Consequently, purging a cookbook will disable any other cookbook that references one or more files from a cookbook that has been purged.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To bulk delete many cookbooks, use a regular expression to define the pattern:
+.sp
+.nf
+.ft C
+$ knife cookbook bulk delete "^[0\-9]{3}$" \-p
+.ft P
+.fi
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to create a new cookbook directory on the local machine, including the following directories and files:
+.INDENT 0.0
+.INDENT 3.5
+.INDENT 0.0
+.IP \(bu 2
 cookbook/attributes
-.
-.IP "\(bu" 4
+.IP \(bu 2
+cookbook/CHANGELOG.md
+.IP \(bu 2
 cookbook/definitions
-.
-.IP "\(bu" 4
+.IP \(bu 2
 cookbook/files/default
-.
-.IP "\(bu" 4
+.IP \(bu 2
 cookbook/libraries
-.
-.IP "\(bu" 4
-cookbook/metadata\.rb
-.
-.IP "\(bu" 4
+.IP \(bu 2
+cookbook/metadata.rb
+.IP \(bu 2
 cookbook/providers
-.
-.IP "\(bu" 4
-cookbook/readme\.md
-.
-.IP "\(bu" 4
-cookbook/recipes/default\.rb
-.
-.IP "\(bu" 4
+.IP \(bu 2
+cookbook/README.md (or .rdoc)
+.IP \(bu 2
+cookbook/recipes/default.rb
+.IP \(bu 2
 cookbook/resources
-.
-.IP "\(bu" 4
+.IP \(bu 2
 cookbook/templates/default
-.
-.IP "" 0
-.
-.P
-supported readme formats are \'md\' (default), \'mkd\', \'txt\', \'rdoc\'\. the readme file will be written with the specified extension and a set of helpful starting headers\.
-.
-.P
-specify \fB\-c\fR or \fB\-\-copyright\fR with the name of the copyright holder as your name or your company/organization name in a quoted string\. if this value is not specified an all\-caps string \fByour_company_name\fR is used which can be easily changed with find/replace\.
-.
-.P
-specify \fB\-i\fR or \fB\-\-license\fR with the license that the cookbook is distributed under for sharing with other people or posting to the opscode cookbooks site\. be aware of the licenses of files you put inside the cookbook and follow any restrictions they describe\. when using \fBnone\fR (default) or \fBapachev2\fR, comment header text and metadata file are pre\-filled\. the \fBnone\fR license will be treated as non\-redistributable\.
-.
-.P
-specify \fB\-e\fR or \fB\-\-email\fR with the email address of the cookbook\'s maintainer\. if this value is not specified, an all\-caps string \fByour_email\fR is used which can easily be changed with find/replace\.
-.
-.P
-the cookbook copyright, license, email and readme_format settings can be filled in the \fBknife\.rb\fR, for example with default values:
-.
-.IP "" 4
-.
+.UNINDENT
+.UNINDENT
+.UNINDENT
+.sp
+After the cookbook is created, it can be uploaded to the server using the \fBknife upload\fP argument.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
 .nf
-
-cookbook_copyright "your_company_name"
-cookbook_license "none"
-cookbook_email "your_email"
-readme_format "md"
-.
+.ft C
+$ knife cookbook create COOKBOOK_NAME (options)
+.ft P
 .fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-C COPYRIGHT_HOLDER\fP, \fB\-\-copyright COPYRIGHT_HOLDER\fP
+The name of the copyright holder. This option will place a copyright notice that contains the name of the copyright holder in each of the pre\-created files. If this option is not specified, a copyright name of "your_company_name" will be used instead; it can be easily modified later.
+.TP
+.B \fB\-I LICENSE\fP, \fB\-\-license LICENSE\fP
+The type of license under which a cookbook is distributed: \fBapachev2\fP, \fBgplv2\fP, \fBgplv3\fP, \fBmit\fP, or \fBnone\fP (default). This option will place the appropriate license notice in the pre\-created files. Be aware of the licenses for files inside of a cookbook and be sure to follow any restrictions they describe.
+.TP
+.B \fB\-m EMAIL\fP, \fB\-\-email EMAIL\fP
+The email address for the individual who maintains the cookbook. This option will place an email address in each of the pre\-created files. If this option is not specified, an email name of "your_email" will be used instead; it can be easily modified later.
+.TP
+.B \fB\-o PATH\fP, \fB\-\-cookbook\-path PATH\fP
+The directory in which cookbook are created. This can be a colon\-separated path.
+.TP
+.B \fB\-r FORMAT\fP, \fB\-\-readme\-format FORMAT\fP
+The document format of the readme file: \fBmd\fP (markdown) and \fBrdoc\fP (Ruby docs).
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To create a cookbook named "my_cookbook" with copyright, email, license, and readme format options specified, enter:
+.sp
+.nf
+.ft C
+$ knife cookbook create my_cookbook \-C "My Name" \-m "my at email.com" \-I apachev2 \-r md
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+** Creating cookbook my_cookbook
+** Creating README for cookbook: my_cookbook
+** Creating metadata for cookbook: my_cookbook
+.ft P
+.fi
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete a specified cookbook or cookbook version on the server (and not locally).
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook delete COOKBOOK_NAME [COOKBOOK_VERSION] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-all\fP
+Indicates that a cookbook and every version of that cookbook will be deleted.
+.TP
+.B \fBCOOKBOOK_VERSION\fP
+The version of a cookbook to be deleted. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, Knife will prompt for a version.
+.TP
+.B \fB\-p\fP, \fB\-\-purge\fP
+Indicates that a cookbook (or cookbook version) will be removed entirely from the server. This action should be used carefully because only one copy of any single file is stored on the server. Consequently, purging a cookbook will disable any other cookbook that references one or more files from a cookbook that has been purged.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To delete version "0.8" from a cookbook named "smartmon", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook delete smartmon 0.8
+.ft P
+.fi
+.sp
+Type \fBY\fP to confirm a deletion.
+.SH DOWNLOAD
+.sp
+The \fBdownload\fP argument is used to download a cookbook from the server to the current working directory.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook download COOKBOOK_NAME [COOKBOOK_VERSION] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-d DOWNLOAD_DIRECTORY\fP, \fB\-\-dir DOWNLOAD_DIRECTORY\fP
+The directory into which a cookbook will be downloaded.
+.TP
+.B \fB\-f\fP, \fB\-\-force\fP
+Indicates that an existing directory will be overwritten.
+.TP
+.B \fB\-N\fP, \fB\-\-latest\fP
+Indicates that the most recent version of a cookbook will be downloaded.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To download a cookbook named "smartmon", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook download smartmon
+.ft P
+.fi
+.SH LIST
+.sp
+The \fBlist\fP argument is used to view a list of cookbooks that are currently available on the server. The list will contain only the most recent version for each cookbook by default.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook list (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-all\fP
+Indicates that all available versions of each cookbook will be returned.
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view a list of cookbooks:
+.sp
+.nf
+.ft C
+$ knife cookbook list
+.ft P
+.fi
+.SH METADATA
+.sp
+The \fBmetadata\fP argument is used to generate the metadata for one or more cookbooks.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook metadata (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-all\fP
+Indicates that metadata should be generated for all cookbooks, and not just for a specified cookbook.
+.TP
+.B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP
+The directory in which cookbook are created. This can be a colon\-separated path.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To generate metadata for all cookbooks:
+.sp
+.nf
+.ft C
+$ knife cookbook metadata \-a
+.ft P
+.fi
+.SH METADATA FROM FILE
+.sp
+The \fBmetadata from file\fP argument is used to load the metadata for a cookbook from a file.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook metadata from file FILE
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To view cookbook metadata from a JSON file:
+.sp
+.nf
+.ft C
+$ knife cookbook metadta from file /path/to/file
+.ft P
+.fi
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to view information about a cookbook, parts of a cookbook (attributes, definitions, files, libraries, providers, recipes, resources, and templates), or a file that is associated with a cookbook (including attributes such as checksum or specificity).
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook show COOKBOOK_NAME [COOKBOOK_VERSION] [PART...] [FILE_NAME] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBCOOKBOOK_VERSION\fP
+The version of a cookbook to be shown. If a cookbook has only one version, this option does not need to be specified. If a cookbook has more than one version and this option is not specified, a list of cookbook versions will be returned.
+.TP
+.B \fB\-f FQDN\fP, \fB\-\-fqdn FQDN\fP
+The FQDN of the host.
+.TP
+.B \fBFILE_NAME\fP
+The name of a file that is associated with a cookbook.
+.TP
+.B \fB\-p PLATFORM\fP, \fB\-\-platform PLATFORM\fP
+The platform for which a cookbook is designed.
+.TP
+.B \fBPART\fP
+The part of the cookbook to show: \fBattributes\fP, \fBdefinitions\fP, \fBfiles\fP, \fBlibraries\fP, \fBproviders\fP, \fBrecipes\fP, \fBresources\fP, or \fBtemplates\fP. More than one part can be specified.
+.TP
+.B \fB\-V PLATFORM_VERSION\fP, \fB\-\-platform\-version PLATFORM_VERSION\fP
+The version of the platform.
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To get the list of available versions of a cookbook named "getting\-started", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook show getting\-started
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+getting\-started   0.3.0  0.2.0
+.ft P
+.fi
+.sp
+To show a list of data about a cookbook using the name of the cookbook and the version, enter:
+.sp
+.nf
+.ft C
+$ knife cookbook show getting\-started 0.3.0
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+attributes:
+  checksum:     fa0fc4abf3f6787aeb5c3c5c35de667c
+  name:         default.rb
+  path:         attributes/default.rb
+  specificity:  default
+  url:          https://somelongurlhere.com
+chef_type:      cookbook_version
+cookbook_name:  getting\-started
+definitions:    []
+files:          []
+frozen?:        false
+json_class:     Chef::CookbookVersion
+libraries:      []
+.ft P
+.fi
+.sp
+To only view data about "templates", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook show getting\-started 0.3.0 templates
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+checksum:     a29d6f254577b830091f140c3a78b1fe
+name:         chef\-getting\-started.txt.erb
+path:         templates/default/chef\-getting\-started.txt.erb
+specificity:  default
+url:          https://someurlhere.com
+.ft P
+.fi
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife role show devops \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.SH TEST
+.sp
+The \fBtest\fP argument is used to test a cookbook for syntax errors. This argument uses Ruby syntax checking to verify every file in a cookbook that ends in .rb and Embedded Ruby (ERB).
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook test COOKBOOK_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-all\fP
+Indicates that all cookbooks will be tested.
+.TP
+.B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP
+The directory in which cookbook are created. This can be a colon\-separated path.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To test a cookbook named "getting\-started", enter:
+.sp
+.nf
+.ft C
+$ knife cookbook test getting\-started
+.ft P
+.fi
+.SH UPLOAD
+.sp
+The \fBupload\fP argument is used to upload one or more cookbooks (and any files that are associated with those cookbooks) from a local repository to the server. Only files that do not already exist on the server will be uploaded.
+.IP Note
+Use a chefignore file to prevent the upload of specific files and file types, such as temporary files or files placed in folders by version control systems. The chefignore file must be located in the root of the cookbook repository and must use rules similar to filename globbing (as defined by the Ruby \fBFile.fnmatch\fP syntax).
+.RE
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife cookbook upload [COOKBOOK_NAME...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-all\fP
+Indicates that all cookbooks will be uploaded.
+.TP
+.B \fB\-d\fP, \fB\-\-include\-dependencies\fP
+Indicates that when a cookbook has a dependency on one (or more) cookbooks, those cookbooks will also be uploaded.
+.TP
+.B \fB\-\-force\fP
+Indicates that a cookbook should be updated even if the \fB\-\-freeze\fP flag has been set.
+.TP
+.B \fB\-\-freeze\fP
+Indicates that a cookbook cannot be modified; any changes to this cookbook must be included as a new version. Only the \fB\-\-force\fP option can override this setting.
+.TP
+.B \fB\-o PATH:PATH\fP, \fB\-\-cookbook\-path PATH:PATH\fP
+The directory in which cookbook are created. This can be a colon\-separated path.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To upload a cookbook named "getting\-started":
+.sp
+.nf
+.ft C
+$ knife cookbook upload getting\-started
+.ft P
+.fi
+.sp
+To upload a cookbook, and then prevent other users from being able to make changes to it, enter:
+.sp
+.nf
+.ft C
+$ knife cookbook upload redis \-\-freeze
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+Uploading redis...
+Upload completed
+.ft P
+.fi
+.sp
+If a cookbook is frozen and the \fB\-\-force\fP option is not specified, Knife will return an error message similar to the following:
+.sp
+.nf
+.ft C
+Uploading redis...
+ERROR: Version 0.1.6 of cookbook redis is frozen. Use \-\-force to override.
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.IP "" 0
-.
-.SH "METADATA"
-\fBknife cookbook metadata cookbook\fR \fI(options)\fR
-.
-.TP
-\fB\-a\fR, \fB\-\-all\fR
-generate metadata for all cookbooks, rather than just a single cookbook
-.
-.TP
-\fB\-o\fR, \fB\-\-cookbook\-path path:path\fR
-a colon\-separated path to look for cookbooks in
-.
-.P
-generate cookbook metadata for the named \fIcookbook\fR\. the \fIpath\fR used here specifies where the cookbooks directory is located and corresponds to the \fBcookbook_path\fR configuration option\.
-.
-.SH "METADATA FROM FILE"
-\fBknife cookbook metadata from file\fR \fI(options)\fR
-.
-.P
-load the cookbook metadata from a specified file\.
-.
-.SH "TEST"
-\fBknife cookbook test [cookbooks\.\.\.]\fR \fI(options)\fR
-.
-.TP
-\fB\-a\fR, \fB\-\-all\fR
-test all cookbooks, rather than just a single cookbook
-.
-.TP
-\fB\-o\fR, \fB\-\-cookbook\-path path:path\fR
-a colon\-separated path to look for cookbooks in
-.
-.P
-test the specified cookbooks for syntax errors\. this uses the built\-in ruby syntax checking option for files in the cookbook ending in \fB\.rb\fR, and the erb syntax check for files ending in \fB\.erb\fR (templates)\.
-.
-.SH "RECIPE LIST"
-\fBknife recipe list [PATTERN]\fR
-.
-.P
-List available recipes from the server\. Specify \fIPATTERN\fR as a regular expression to limit the results\.
-.
-.SH "DESCRIPTION"
-Cookbooks are the fundamental unit of distribution in Chef\. They encapsulate all recipes of resources and assets used to configure a particular aspect of the infrastructure\. The following sub\-commands can be used to manipulate the cookbooks stored on the Chef Server\.
-.
-.P
-On disk, cookbooks are directories with a defined structure\. The following directories may appear within a cookbook:
-.
-.TP
-COOKBOOK/attributes/
-Ruby files that define default parameters to be used in recipes
-.
-.TP
-COOKBOOK/definitions/
-Ruby files that contain \fIresource definitions\fR
-.
-.TP
-COOKBOOK/files/SPECIFICITY
-Files of arbitrary type\. These files may be downloaded by chef\-client(8) when configuring a host\.
-.
-.TP
-COOKBOOK/libraries/
-Ruby files that contain library code needed for recipes
-.
-.TP
-COOKBOOK/providers/
-Ruby files that contain Lightweight Provider definitions
-.
-.TP
-COOKBOOK/recipes/
-Ruby files that use Chef\'s recipe DSL to describe the desired configuration of a system
-.
-.TP
-COOKBOOK/resources/
-Ruby files that contain Lightweight Resource definitions
-.
-.TP
-COOKBOOK/templates/SPECIFICITY
-ERuby (ERb) template files\. These are referenced by \fIrecipes\fR and evaluated to dynamically generate configuration files\.
-.
-.P
-\fBSPECIFICITY\fR is a feature of \fIfiles\fR and \fItemplates\fR that allow you to specify alternate files to be used on a specific OS platform or host\. The default specificity setting is \fIdefault\fR, that is files in \fBCOOKBOOK/files/default\fR will be used when a more specific copy is not available\. Further documentation for this feature is available on the Chef wiki: \fIhttp://wiki\.opscode\.com/display/chef/File+Distribution#FileDistribution\-FileSpecificity\fR
-.
-.P
-Cookbooks also contain a metadata file that defines various properties of the cookbook\. The most important of these are the \fIversion\fR and the \fIdependencies\fR\. The \fIversion\fR is used in combination with environments to select which copy of a given cookbook is distributed to a node\. The \fIdependencies\fR are used by the server to determine which additional cookbooks must be distributed to a given host when it requires a cookbook\.
-.
-.SH "SEE ALSO"
-\fBknife\-environment(1)\fR \fBknife\-cookbook\-site(1)\fR \fIhttp://wiki\.opscode\.com/display/chef/Cookbooks\fR \fIhttp://wiki\.opscode\.com/display/chef/Metadata\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-data-bag.1 b/distro/common/man/man1/knife-data-bag.1
index 1fdb07e..b5fae17 100644
--- a/distro/common/man/man1/knife-data-bag.1
+++ b/distro/common/man/man1/knife-data-bag.1
@@ -1,136 +1,486 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-DATA\-BAG" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-data\-bag\fR \- Store arbitrary data on a Chef Server
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBdata bag\fR \fIsub\-command\fR \fI(options)\fR
-.
-.SH "DESCRIPTION"
-Data bags are stores of arbitrary JSON data\. Each data bag is a collection that may contain many items\. Data Bag Items are indexed by the Chef Server and can be searched via \fBknife\-search\fR(1)\.
-.
-.P
-Data bags are available to all nodes configured by \fBchef\-client\fR(8), and are therefore a convenient mechanism to store global information, such as lists of administrative accounts that should be configured on all hosts\.
-.
-.SH "DATA BAG SUB\-COMMANDS"
-.
-.SH "CREATE"
-\fBknife data bag create\fR \fIbag name\fR [item id] \fI(options)\fR
-.
+.TH "KNIFE-DATA-BAG" "1" "Chef 11.8.0" "" "knife data bag"
+.SH NAME
+knife-data-bag \- The man page for the knife data bag subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+A data bag is a global variable that is stored as JSON data and is accessible from a server. A data bag is indexed for searching and can be loaded by a recipe or accessed during a search. The contents of a data bag can vary, but they often include sensitive information (such as database passwords).
+.sp
+The contents of a data bag can be encrypted using \fI\%shared secret encryption\fP. This allows a data bag to store confidential information (such as a database password) or to be managed in a source control system (without plain\-text data appearing in revision history).
+.sp
+The \fBknife data bag\fP subcommand is used to manage arbitrary stores of globally available JSON data.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife data bag [ARGUMENT] (options)
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-s\fR, \fB\-\-secret SECRET\fR
-A secret key used to encrypt the data bag item\. See \fBencryption support\fR below\.
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-\-secret\-file SECRET_FILE\fR
-The path to a file containing the secret key to be used to encrypt the data bag item\.
-.
-.P
-If \fIitem id\fR is given, creates a new, empty data bag item and opens it for editing in your editor\. The data bag will be created if it does not exist\.
-.
-.P
-If \fIitem id\fR is not given, the data bag will be created\.
-.
-.SH "DELETE"
-\fBknife data bag delete\fR \fIbag name\fR [item id] \fI(options)\fR
-.
-.P
-Delete a data bag, or an item from a data bag\.
-.
-.SH "EDIT"
-\fBknife data bag edit\fR \fIbag name\fR \fIitem id\fR \fI(options)\fR
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-s\fR, \fB\-\-secret SECRET\fR
-A secret key used to encrypt the data bag item\. See \fBencryption support\fR below\.
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-\-secret\-file SECRET_FILE\fR
-The path to a file containing the secret key to be used to encrypt the data bag item\.
-.
-.P
-Edit an item in a data bag\.
-.
-.SH "FROM FILE"
-\fBknife data bag from file\fR \fIbag name\fR \fIfile\fR \fI(options)\fR
-.
-.P
-\fBknife data bag from file\fR \fIbag name\fR \fIfile1\fR \fIfile2\fR \fIfile3\fR \fI(options)\fR
-.
-.P
-\fBknife data bag from file\fR \fIbag name\fR \fIfolder\fR \fI(options)\fR
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-s\fR, \fB\-\-secret SECRET\fR
-A secret key used to encrypt the data bag item\. See \fBencryption support\fR below\.
-.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
 .TP
-\fB\-\-secret\-file SECRET_FILE\fR
-The path to a file containing the secret key to be used to encrypt the data bag item\.
-.
-.P
-Load a data bag item from a JSON file\. If \fIfile\fR is a relative or absolute path to the file, that file will be used\. Otherwise, the \fIfile\fR parameter is treated as the base name of a data bag file in a Chef repository, and \fBknife\fR will search for the file in \fB\./data_bags/bag_name/file\fR\. For example \fBknife data bag from file users dan\.json\fR would attempt to load the file \fB\./data_bags/users/dan\.json\fR\.
-.
-.SH "LIST"
-\fBknife data bag list\fR \fI(options)\fR
-.
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
 .TP
-\fB\-w\fR, \fB\-\-with\-uri\fR
-Show corresponding URIs
-.
-.P
-Lists the data bags that exist on the Chef Server\.
-.
-.SH "SHOW"
-\fBknife data bag show BAG [ITEM]\fR \fI(options)\fR
-.
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
 .TP
-\fB\-s\fR, \fB\-\-secret SECRET\fR
-A secret key used to encrypt the data bag item\. See \fBencryption support\fR below\.
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fB\-\-secret\-file SECRET_FILE\fR
-The path to a file containing the secret key to be used to encrypt the data bag item\.
-.
-.P
-Show a specific data bag or an item in a data bag\. The output will be formatted according to the \-\-format option\.
-.
-.SH "ENCRYPTION SUPPORT"
-Data Bag Items may be encrypted to keep their contents secret\. This may be desireable when storing sensitive information such as database passwords, API keys, etc\.
-.
-.P
-Data Bag Item encryption uses the AES\-256 CBC symmetric key algorithm\.
-.
-.P
-\fBCAVEATS:\fR Keys are not encrypted; only values are encrypted\. The "id" of a Data Bag Item is not encrypted, since it is used by Chef Server to store the item in its database\. For example, given the following data bag item:
-.
-.IP "" 4
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to add a data bag to the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
 .nf
-
-{"id": "important_passwords", "secret_password": "opensesame"}
-.
+.ft C
+$ knife data bag create DATA_BAG_NAME [DATA_BAG_ITEM] (options)
+.ft P
 .fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBDATA_BAG_ITEM\fP
+The name of a specific item within a data bag.
+.TP
+.B \fB\-\-secret SECRET\fP
+The encryption key that is used for values contained within a data bag.
+.TP
+.B \fB\-\-secret\-file FILE\fP
+The path to the file that contains the encryption key.
+.UNINDENT
+.IP Note
+For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both.
+.RE
+.sp
+\fBExamples\fP
+.sp
+To create a data bag named "admins", enter:
+.sp
+.nf
+.ft C
+$ knife data bag create admins
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+Created data_bag[admins]
+.ft P
+.fi
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete a data bag or a data bag item from a server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife data bag delete DATA_BAG_NAME [DATA_BAG_ITEM] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBDATA_BAG_ITEM\fP
+The name of a specific item within a data bag.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To a data bag named "admins", enter:
+.sp
+.nf
+.ft C
+$ knife data bag delete admins
+.ft P
+.fi
+.sp
+To delete an item named "charlie", enter:
+.sp
+.nf
+.ft C
+$ knife data bag delete admins charlie
+.ft P
+.fi
+.sp
+Type \fBY\fP to confirm a deletion.
+.SH EDIT
+.sp
+The \fBedit\fP argument is used to edit the data contained in a data bag. If encryption is being used, the data bag will be decrypted, the data will be made available in the $EDITOR, and then encrypted again before saving it to the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife data bag edit DATA_BAG_NAME [DATA_BAG_ITEM] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBDATA_BAG_ITEM\fP
+The name of a specific item within a data bag.
+.TP
+.B \fB\-\-secret SECRET\fP
+The encryption key that is used for values contained within a data bag.
+.TP
+.B \fB\-\-secret\-file FILE\fP
+The path to the file that contains the encryption key.
+.UNINDENT
+.IP Note
+For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both.
+.RE
+.sp
+\fBExamples\fP
+.sp
+To edit the contents of a data bag, enter:
+.sp
+.nf
+.ft C
+$ knife data bag edit admins
+.ft P
+.fi
+.sp
+To edit an item named "charlie" that is contained in a data bag named "admins", enter:
+.sp
+.nf
+.ft C
+$ knife data bag edit admins charlie
+.ft P
+.fi
+.sp
+to open the $EDITOR. Once opened, you can update the data before saving it to the server. For example, by changing:
+.sp
+.nf
+.ft C
+{
+   "id": "charlie"
+}
+.ft P
+.fi
+.sp
+to:
+.sp
+.nf
+.ft C
+{
+   "id": "charlie",
+   "uid": 1005,
+   "gid":"ops",
+   "shell":"/bin/zsh",
+   "comment":"Crazy Charlie"
+}
+.ft P
+.fi
+.SH FROM FILE
+.sp
+The \fBfrom file\fP argument is used to create a data bag on the server from a file. The path to the data bag file must specify one of the following:
+.INDENT 0.0
+.IP \(bu 2
+the name of a data bag
+.IP \(bu 2
+a relative or absolute path to a file
+.UNINDENT
+.sp
+If the name of a data bag is specified, Knife will search for the data bag in \fB./data_bags/bag_name/file\fP. Once opened, the JSON file should be a hash that contains at least an ID key which represents the name of the data bag item.
+.IP Warning
+A chef\-client must be version 11.6 (or higher) when using the \fBknife data bag from file\fP argument with the Enterprise Chef or Open Source Chef version 11 servers.
+.RE
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife data bag from file DATA_BAG_NAME_or_PATH
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-all\fP
+Indicates that all data bags found at the specified path will be uploaded.
+.TP
+.B \fB\-\-secret SECRET\fP
+The encryption key that is used for values contained within a data bag.
+.TP
+.B \fB\-\-secret\-file FILE\fP
+The path to the file that contains the encryption key.
+.UNINDENT
+.IP Note
+For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both.
+.RE
+.sp
+\fBExamples\fP
+.sp
+To create a data bag on the server from a file:
+.sp
+.nf
+.ft C
+$ knife data bag from file "path to JSON file"
+.ft P
+.fi
+.sp
+To create a data bag named "devops_data" that contains encrypted data, enter:
+.sp
+.nf
+.ft C
+$ knife data bag from file devops_data \-\-secret\-file "path to decryption file"
+.ft P
+.fi
+.SH LIST
+.sp
+The \fBlist\fP argument is used to view a list of data bags that are currently available on the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife data bag list
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+For example, to view a list of data bags on the server, enter:
+.sp
+.nf
+.ft C
+$ knife data bag list
+.ft P
+.fi
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to view the contents of a data bag.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife data bag show DATA_BAG_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fBDATA_BAG_ITEM\fP
+The name of a specific item within a data bag.
+.TP
+.B \fB\-\-secret SECRET\fP
+The encryption key that is used for values contained within a data bag.
+.TP
+.B \fB\-\-secret\-file FILE\fP
+The path to the file that contains the encryption key.
+.UNINDENT
+.IP Note
+For encrypted data bag items, use \fIeither\fP \fB\-\-secret\fP or \fB\-\-secret\-file\fP, not both.
+.RE
+.sp
+\fBExamples\fP
+.sp
+To show the contents of a data bag, enter:
+.sp
+.nf
+.ft C
+$ knife data bag show admins
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+charlie
+.ft P
+.fi
+.sp
+To show the contents of a specific item within data bag, enter:
+.sp
+.nf
+.ft C
+$ knife data bag show admins charlie
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+comment:  Crazy Charlie
+gid:      ops
+id:       charlie
+shell:    /bin/zsh
+uid:      1005
+.ft P
+.fi
+.sp
+To show the contents of a data bag named "passwords" with an item that contains encrypted data named "mysql", enter:
+.sp
+.nf
+.ft C
+$ knife data bag show passwords mysql
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+## sample:
+{
+  "id": "mysql",
+  "pass": "trywgFA6R70NO28PNhMpGhEvKBZuxouemnbnAUQsUyo=\en",
+  "user": "e/p+8WJYVHY9fHcEgAAReg==\en"
+}
+.ft P
+.fi
+.sp
+To show the decrypted contents of the same data bag, enter:
+.sp
+.nf
+.ft C
+$ knife data bag show \-\-secret\-file /path/to/decryption/file passwords mysql
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+## sample:
+{
+   "id": "mysql",
+   "pass": "thesecret123",
+   "user": "fred"
+}
+.ft P
+.fi
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife data bag show admins \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.IP "" 0
-.
-.P
-The key "secret_password" will be visible to an evesdropper, but the value "opensesame" will be protected\. Both the key "id" and its value "important_passwords" will be visible to an evesdropper\.
-.
-.P
-Chef Server does not provide a secure mechanism for distributing encryption keys\.
-.
-.SH "SEE ALSO"
-\fBknife\-search\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. http://wiki\.opscode\.com/display/chef/Home
diff --git a/distro/common/man/man1/knife-delete.1 b/distro/common/man/man1/knife-delete.1
new file mode 100644
index 0000000..7f1028f
--- /dev/null
+++ b/distro/common/man/man1/knife-delete.1
@@ -0,0 +1,132 @@
+.TH "KNIFE-DELETE" "1" "Chef 11.8.0" "" "knife delete"
+.SH NAME
+knife-delete \- The man page for the knife delete subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife delete\fP subcommand is used to delete an object from a server. This subcommand works similar to \fBknife cookbook delete\fP, \fBknife data bag delete\fP, \fBknife environment delete\fP, \fBknife node delete\fP, and \fBknife role delete\fP, but with a single verb (and a single action).
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife delete [PATTERN...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-both\fP
+Indicates that both local and remote copies of an object should be deleted. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-\-local\fP
+Indicates that only the local copy of an object should be deleted. (The remote copy will not be deleted.) Default: \fBfalse\fP.
+.TP
+.B \fB\-r\fP, \fB\-\-[no\-]recurse\fP
+Use \fB\-\-recurse\fP to delete directories recursively. Default: \fB\-\-no\-recurse\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.UNINDENT
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-deps.1 b/distro/common/man/man1/knife-deps.1
new file mode 100644
index 0000000..2f8056e
--- /dev/null
+++ b/distro/common/man/man1/knife-deps.1
@@ -0,0 +1,219 @@
+.TH "KNIFE-DEPS" "1" "Chef 11.8.0" "" "knife deps"
+.SH NAME
+knife-deps \- The man page for the knife deps subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife deps\fP subcommand is used to identify dependencies for a node, role, or cookbook.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife deps (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-\-[no\-]recurse\fP
+Use \fB\-\-recurse\fP to list dependencies recursively. This option can only be used when \fB\-\-tree\fP is set to \fBtrue\fP. Default: \fB\-\-no\-recurse\fP.
+.TP
+.B \fB\-\-remote\fP
+Indicates that dependencies will be determined from objects located on the server instead of the local chef\-repo. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.TP
+.B \fB\-\-tree\fP
+Indicates that dependencies are shown in a visual tree structure (including duplicates, if they exist). Default: \fBfalse\fP.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To find the dependencies for a node:
+.sp
+.nf
+.ft C
+$ knife deps nodes/node_name.json
+.ft P
+.fi
+.sp
+To find the dependencies for a role:
+.sp
+.nf
+.ft C
+$ knife deps roles/role_name.json
+.ft P
+.fi
+.sp
+To find the dependencies for a cookbook:
+.sp
+.nf
+.ft C
+$ knife deps cookbooks/cookbook_name.json
+.ft P
+.fi
+.sp
+To find the dependencies for an environment:
+.sp
+.nf
+.ft C
+$ knife deps environments/environment_name.json
+.ft P
+.fi
+.sp
+To find the dependencies for a combination of nodes, cookbooks, roles, and/or environments:
+.sp
+.nf
+.ft C
+$ knife deps cookbooks/git.json cookbooks/github.json roles/base.json environments/desert.json nodes/mynode.json
+.ft P
+.fi
+.sp
+To use a wildcard to return all the child nodes:
+.sp
+.nf
+.ft C
+$ knife deps environments/*.json
+.ft P
+.fi
+.sp
+Use the \fB\-\-tree\fP option to view the results with structure:
+.sp
+.nf
+.ft C
+$ knife deps roles/webserver.json
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+roles/webserver.json
+  roles/base.json
+    cookbooks/github
+      cookbooks/git
+    cookbooks/users
+  cookbooks/apache2
+.ft P
+.fi
+.sp
+To pass the output of \fBknife deps\fP to \fBknife upload\fP, do something like the following:
+.sp
+.nf
+.ft C
+$ knife upload \(gaknife deps nodes/*.json
+.ft P
+.fi
+.sp
+To pass the output of \fBknife deps\fP to \fBknife xargs\fP:
+.sp
+.nf
+.ft C
+$ knife deps nodes/*.json | xargs knife upload
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-diff.1 b/distro/common/man/man1/knife-diff.1
new file mode 100644
index 0000000..12fcf4b
--- /dev/null
+++ b/distro/common/man/man1/knife-diff.1
@@ -0,0 +1,215 @@
+.TH "KNIFE-DIFF" "1" "Chef 11.8.0" "" "knife diff"
+.SH NAME
+knife-diff \- The man page for the knife diff subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife diff\fP subcommand is used to compare the differences between files and directories on the server and in the chef\-repo. For example, to compare files on the server prior to an uploading or downloading files using the \fBknife download\fP and \fBknife upload\fP subcommands, or to ensure that certain files in multiple production environments are the same. This subcommand is similar to the \fBgit diff\fP command that can be used to diff what is in the chef\-repo with what is s [...]
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife diff [PATTERN...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-cookbook\-version VERSION\fP
+The version of a cookbook to be downloaded.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-\-diff\-filter=[(A|D|M|T)...[*]]\fP
+Indicates that files will be selected that have been added (\fBA\fP), deleted (\fBD\fP), modified (\fBM\fP), and/or have had their type changed (\fBT\fP). Any combination of filter characters may be used, including no filter characters. Use \fB*\fP to select all paths if a file matches other criteria in the comparison. Default value: \fBnil\fP.
+.TP
+.B \fB\-\-name\-only\fP
+Indicates that only the names of modified files will be shown.
+.TP
+.B \fB\-\-name\-status\fP
+Indicates that only the names of files with a status of \fBAdded\fP, \fBDeleted\fP, \fBModified\fP, or \fBType Changed\fP will be shown.
+.TP
+.B \fB\-\-no\-recurse\fP
+Use \fB\-\-no\-recurse\fP to disable listing a directory recursively. Default: \fB\-\-recurse\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.UNINDENT
+.sp
+\fBknife.rb File Settings\fP
+.sp
+In addition to the default settings in a knife.rb file, there are other subcommand\-specific settings that can be added. When a subcommand is run, Knife will use:
+.INDENT 0.0
+.IP 1. 3
+A value passed via the command\-line
+.IP 2. 3
+A value contained in the knife.rb file
+.IP 3. 3
+The default value
+.UNINDENT
+.sp
+A value passed via the command line will override a value in the knife.rb file; a value in a knife.rb file will override a default value.
+.sp
+The following \fBknife diff\fP settings can be added to the knife.rb file:
+.INDENT 0.0
+.TP
+.B \fBknife[:chef_repo_path]\fP
+Use to add the \fB\-\-chef\-repo\-path\fP option.
+.TP
+.B \fBknife[:concurrency]\fP
+Use to add the \fB\-\-concurrency\fP option.
+.TP
+.B \fBknife[:name_only]\fP
+Use to add the \fB\-\-name\-only\fP option.
+.TP
+.B \fBknife[:name_status]\fP
+Use to add the \fB\-\-name\-status\fP option.
+.TP
+.B \fBknife[:recurse]\fP
+Use to add the \fB\-\-recurse\fP option.
+.TP
+.B \fBknife[:repo_mode]\fP
+Use to add the \fB\-\-repo\-mode\fP option.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To compare the "base.json" role to a "webserver.json" role, enter:
+.sp
+.nf
+.ft C
+$ knife diff roles/base.json roles/webserver.json
+.ft P
+.fi
+.sp
+To compare the differences between the local chef\-repo and the files that are on the server, enter:
+.sp
+.nf
+.ft C
+$ knife diff
+.ft P
+.fi
+.sp
+To diff a node named \fBnode\-lb\fP and then only return files that have been added, deleted, modified, or changed, enter:
+.sp
+.nf
+.ft C
+$ knife diff \-\-name\-status node\-lb
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+node\-lb/recipes/eip.rb
+node\-lb/recipes/heartbeat\-int.rb
+node\-lb/templates/default/corpsite.conf.erb
+node\-lb/files/default/wildcard.node.com.crt
+node\-lb/files/default/wildcard.node.com.crt\-2009
+node\-lb/files/default/wildcard.node.com.key
+node\-lb/.gitignore
+node\-lb/Rakefile
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-download.1 b/distro/common/man/man1/knife-download.1
new file mode 100644
index 0000000..5ab5e78
--- /dev/null
+++ b/distro/common/man/man1/knife-download.1
@@ -0,0 +1,220 @@
+.TH "KNIFE-DOWNLOAD" "1" "Chef 11.8.0" "" "knife download"
+.SH NAME
+knife-download \- The man page for the knife download subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife download\fP subcommand is used to download roles, cookbooks, environments, nodes, and data bags from the server to the current working directory.. It can be used to back up data on the server, inspect the state of one or more files, or to extract out\-of\-process changes users may have made to files on the server, such as if a user made a change that bypassed version source control. This subcommand is often used in conjunction with \fBknife diff\fP, which can be used to see  [...]
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife download [PATTERN...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-\-force\fP
+Use \fB\-\-force\fP to download files even when the file on the hard drive is identical to the object on the server (role, cookbook, etc.).  By default, files are compared to see if they have equivalent content, and local files are only overwritten if they are different. Default: \fB\-\-no\-force\fP.
+.TP
+.B \fB\-n\fP, \fB\-\-dry\-run\fP
+Indicates that no action is taken and that results are only printed out. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-[no\-]diff\fP
+Indicates that only new and modified files will be downloaded. Set to \fBfalse\fP to download all files. Default: \fB\-\-diff\fP.
+.TP
+.B \fB\-\-[no\-]recurse\fP
+Use \fB\-\-no\-recurse\fP to disable downloading a directory recursively. Default: \fB\-\-recurse\fP.
+.TP
+.B \fB\-\-purge\fP
+Use \fB\-\-purge\fP to delete local files and directories that do not exist on the server.  By default, if a role, cookbook, etc. does not exist on the server, the local file for said role will be left alone and NOT deleted. Default: \fB\-\-no\-purge\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To download the entire chef\-repo from the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife download /
+.ft P
+.fi
+.sp
+To download the \fBcookbooks/\fP directory from the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife download cookbooks
+.ft P
+.fi
+.sp
+or from anywhere in the chef\-repo, enter:
+.sp
+.nf
+.ft C
+$ knife download /cookbooks
+.ft P
+.fi
+.sp
+To download the \fBenvironments/\fP directory from the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife download environments
+.ft P
+.fi
+.sp
+or from anywhere in the chef\-repo, enter:
+.sp
+.nf
+.ft C
+$ knife download /environments
+.ft P
+.fi
+.sp
+To download an environment named "production" from the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife download environments/production.json
+.ft P
+.fi
+.sp
+or from the \fBenvironments/\fP directory, enter:
+.sp
+.nf
+.ft C
+$ knife download production.json
+.ft P
+.fi
+.sp
+To download the \fBroles/\fP directory from the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife download roles
+.ft P
+.fi
+.sp
+or from anywhere in the chef\-repo, enter:
+.sp
+.nf
+.ft C
+$ knife download /roles
+.ft P
+.fi
+.sp
+To download all cookbooks that start with "apache" and belong to the "webserver" role, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$  knife download cookbooks/apache\e* roles/webserver.json
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-edit.1 b/distro/common/man/man1/knife-edit.1
new file mode 100644
index 0000000..fd44372
--- /dev/null
+++ b/distro/common/man/man1/knife-edit.1
@@ -0,0 +1,126 @@
+.TH "KNIFE-EDIT" "1" "Chef 11.8.0" "" "knife edit"
+.SH NAME
+knife-edit \- The man page for the knife edit subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife edit\fP subcommand is used to edit objects on the server. This subcommand works similar to \fBknife cookbook edit\fP, \fBknife data bag edit\fP, \fBknife environment edit\fP, \fBknife node edit\fP, and \fBknife role edit\fP, but with a single verb (and a single action).
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife edit (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-\-local\fP
+Use to show files in the local chef\-repo instead of a remote location. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.UNINDENT
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-environment.1 b/distro/common/man/man1/knife-environment.1
index aa6e734..c70556a 100644
--- a/distro/common/man/man1/knife-environment.1
+++ b/distro/common/man/man1/knife-environment.1
@@ -1,178 +1,324 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-ENVIRONMENT" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-environment\fR \- Define cookbook policies for the environments in your infrastructure
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBenvironment\fR \fIsub\-command\fR \fI(options)\fR
-.
-.SH "SUBCOMMANDS"
-Environment subcommands follow a basic create, read, update, delete (CRUD) pattern\. The following subcommands are available:
-.
-.SH "CREATE"
-\fBknife environment create\fR \fIenvironment\fR \fI(options)\fR
-.
+.TH "KNIFE-ENVIRONMENT" "1" "Chef 11.8.0" "" "knife environment"
+.SH NAME
+knife-environment \- The man page for the knife environment subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+An environment is a way to map an organization\(aqs real\-life workflow to what can be configured and managed when using server. Every organization begins with a single environment called the \fB_default\fP environment, which cannot be modified (or deleted). Additional environments can be created to reflect each organization\(aqs patterns and workflow. For example, creating \fBproduction\fP, \fBstaging\fP, \fBtesting\fP, and \fBdevelopment\fP environments. Generally, an environment is al [...]
+.sp
+The \fBknife environment\fP subcommand is used to manage environments within a single organization on the server.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife environment [ARGUMENT] (options)
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-d\fR, \fB\-\-description DESCRIPTION\fR
-The value of the description field\.
-.
-.P
-Create a new environment object on the Chef Server\. The envrionment will be opened in the text editor for editing prior to creation if the \-n option is not present\.
-.
-.SH "DELETE"
-\fBknife environment delete\fR \fIenvironment\fR \fI(options)\fR
-.
-.P
-Destroy an environment on the Chef Server\. A prompt for confirmation will be displayed if the \-y options is not given\.
-.
-.SH "EDIT"
-\fBknife environment edit\fR \fIenvironment\fR \fI(options)\fR
-.
-.P
-Fetch \fIenvironment\fR and display it in the text editor for editing\. The environment will be saved to the Chef Server when the editing session exits\.
-.
-.SH "FROM FILE"
-\fBknife environment from file\fR \fIfile\fR \fI(options)\fR
-.
-.P
-Create or update an environment from the JSON or Ruby format \fIfile\fR\. See \fBformat\fR for the proper format of this file\.
-.
-.SH "LIST"
-\fBknife environment list\fR \fI(options)\fR * \fB\-w\fR, \fB\-\-with\-uri\fR:
-.
-.IP "" 4
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to add an environment object to the server. When this argument is run, Knife will open $EDITOR to enable editing of the \fBENVIRONMENT\fP description field (unless a description is specified as part of the command). When finished, Knife will add the environment to the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
 .nf
-
-Show the resource URI for each environment
-.
+.ft C
+$ knife environment create ENVIRONMENT_NAME \-d DESCRIPTION
+.ft P
 .fi
-.
-.IP "" 0
-.
-.SH "SHOW"
-\fBknife environment show\fR \fIenvironment\fR \fI(options)\fR
-.
-.SH "DESCRIPTION"
-Environments provide a means to apply policies to hosts in your infrastructure based on business function\. For example, you may have a separate copy of your infrastructure called "dev" that runs the latest version of your application and should use the newest versions of your cookbooks when configuring systems, and a production instance of your infrastructure where you wish to update code and cookbooks in a more controlled fashion\. In Chef, this function is implemented with \fIenvironm [...]
-.
-.P
-Environments contain two major components: a set of cookbook version constraints and environment attributes\.
-.
-.SH "SYNTAX"
-A cookbook version constraint is comprised of a \fIcookbook name\fR and a \fIversion constraint\fR\. The \fIcookbook name\fR is the name of a cookbook in your system, and the \fIversion constraint\fR is a String describing the version(s) of that cookbook allowed in the environment\. Only one \fIversion constraint\fR is supported for a given \fIcookbook name\fR\.
-.
-.P
-The exact syntax used to define a cookbook version constraint varies depending on whether you use the JSON format or the Ruby format\. In the JSON format, the cookbook version constraints for an environment are represented as a single JSON object, like this:
-.
-.IP "" 4
-.
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-d DESCRIPTION\fP, \fB\-\-description DESCRIPTION\fP
+The description of the environment. This value will populate the description field for the environment on the server.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To create an environment named "dev" with a description of "The development environment.":
+.sp
 .nf
-
-{"apache2": ">= 1\.5\.0"}
-.
+.ft C
+$ knife environment create dev \-d "The development environment."
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-In the Ruby format, the cookbook version contraints for an environment are represented as a Ruby Hash, like this:
-.
-.IP "" 4
-.
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete an environment from a server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
 .nf
-
-{"apache2" => ">= 1\.5\.0"}
-.
+.ft C
+$ knife environment delete ENVIRONMENT_NAME
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-A \fIversion number\fR is a String comprised of two or three digits separated by a dot (\.) character, or in other words, strings of the form "major\.minor" or "major\.minor\.patch"\. "1\.2" and "1\.2\.3" are examples of valid version numbers\. Version numbers containing more than three digits or alphabetic characters are not supported\.
-.
-.P
-A \fIversion constraint\fR String is composed of an \fIoperator\fR and a \fIversion number\fR\. The following operators are available:
-.
-.TP
-\fB= VERSION\fR
-Equality\. Only the exact version specified may be used\.
-.
-.TP
-\fB> VERSION\fR
-Greater than\. Only versions greater than \fBVERSION\fR may be used\.
-.
-.TP
-\fB>= VERSION\fR
-Greater than or equal to\. Only versions equal to VERSION or greater may be used\.
-.
-.TP
-\fB< VERSION\fR
-Less than\. Only versions less than VERSION may be used\.
-.
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To delete an environment named "dev", enter:
+.sp
+.nf
+.ft C
+$ knife environment delete dev
+.ft P
+.fi
+.sp
+Type \fBY\fP to confirm a deletion.
+.SH EDIT
+.sp
+The \fBedit\fP argument is used to edit the attributes of an environment. When this argument is run, Knife will open $EDITOR to enable editing of \fBENVIRONMENT\fP attributes. When finished, Knife will update the server with those changes.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife environment edit ENVIRONMENT_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To edit an environment named "devops", enter:
+.sp
+.nf
+.ft C
+$ knife environment edit devops
+.ft P
+.fi
+.SH FROM FILE
+.sp
+The \fBfrom file\fP argument is used to add or update an environment using a JSON or Ruby DSL description. It must be run with the \fBcreate\fP or \fBedit\fP arguments.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife environment [create | edit] from file FILE (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
 .TP
-\fB<= VERSION\fR
-Less than or equal to\. Only versions lesser or equal to VERSION may be used\.
-.
+.B \fB\-a\fP, \fB\-\-all\fP
+Indicates that all environments found at the specified path will be uploaded.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To add an environment using data contained in a JSON file:
+.sp
+.nf
+.ft C
+$ knife environment create devops from file "path to JSON file"
+.ft P
+.fi
+.sp
+or:
+.sp
+.nf
+.ft C
+$ knife environment edit devops from file "path to JSON file"
+.ft P
+.fi
+.SH LIST
+.sp
+The \fBlist\fP argument is used to list all of the environments that are currently available on the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife environment list \-w
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
 .TP
-\fB~> VERSION\fR
-Pessimistic greater than\. Depending on the number of components in the given VERSION, the constraint will be optimistic about future minor or patch revisions only\. For example, \fB~> 1\.1\fR will match any version less than \fB2\.0\fR and greater than or equal to \fB1\.1\.0\fR, whereas \fB~> 2\.0\.5\fR will match any version less than \fB2\.1\.0\fR and greater than or equal to \fB2\.0\.5\fR\.
-.
-.SH "FORMAT"
-The JSON format of an envioronment is as follows:
-.
-.IP "" 4
-.
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view a list of environments:
+.sp
 .nf
-
-{
-  "name": "dev",
-  "description": "The development environment",
-  "cookbook_versions": {
-    "couchdb": "= 11\.0\.0"
-  },
-  "json_class": "Chef::Environment",
-  "chef_type": "environment",
-  "default_attributes": {
-    "apache2": { "listen_ports": [ "80", "443" ] }
-  },
-  "override_attributes": {
-    "aws_s3_bucket": "production"
-  }
-}
-.
+.ft C
+$ knife environment list \-w
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-The Ruby format of an environment is as follows:
-.
-.IP "" 4
-.
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to display information about the specified environment.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife environment show ENVIRONMENT_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To view information about the "dev" environment enter:
+.sp
 .nf
+.ft C
+$ knife environment show dev
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+% knife environment show dev
+chef_type:            environment
+cookbook_versions:
+default_attributes:
+description:
+json_class:           Chef::Environment
+name:                 dev
+override_attributes:
 
-name "dev"
-description "The development environment"
-cookbook_versions  "couchdb" => "= 11\.0\.0"
-default_attributes "apache2" => { "listen_ports" => [ "80", "443" ] }
-override_attributes "aws_s3_bucket" => "production"
-.
+\e\e
+\e\e
+\e\e
+\e\e
+.ft P
 .fi
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife role show devops \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.IP "" 0
-.
-.SH "SEE ALSO"
-\fBknife\-node(1)\fR \fBknife\-cookbook(1)\fR \fBknife\-role(1)\fR \fIhttp://wiki\.opscode\.com/display/chef/Environments\fR \fIhttp://wiki\.opscode\.com/display/chef/Version+Constraints\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Daniel DeLeo \fIdan at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-exec.1 b/distro/common/man/man1/knife-exec.1
index 7877847..962ec40 100644
--- a/distro/common/man/man1/knife-exec.1
+++ b/distro/common/man/man1/knife-exec.1
@@ -1,46 +1,325 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.TH "KNIFE-EXEC" "1" "Chef 11.8.0" "" "knife exec"
+.SH NAME
+knife-exec \- The man page for the knife exec subcommand.
 .
-.TH "KNIFE\-EXEC" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
+.nr rst2man-indent-level 0
 .
-.SH "NAME"
-\fBknife\-exec\fR \- Run user scripts using the Chef API DSL
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBexec\fR \fI(options)\fR
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
 .
+.sp
+The \fBknife exec\fP subcommand uses the Knife configuration file to execute Ruby scripts in the context of a fully configured chef\-client. This subcommand is most often used to run scripts that will only access server one time (or otherwise very infrequently). Use this subcommand any time that an operation does not warrant full usage of the Knife subcommand library.
+.sp
+For Ruby scripts that will be run using the \fBexec\fP subcommand, note the following:
+.INDENT 0.0
+.INDENT 3.5
+.INDENT 0.0
+.IP \(bu 2
+The Ruby script must be located on the system from which Knife is run (and not be located on any of the systems that Knife will be managing).
+.IP \(bu 2
+Shell commands will be run from a management workstation. For example, something like \fB%x[ls \-lash /opt/only\-on\-a\-node]\fP would give you the directory listing for the "opt/only\-on\-a\-node" directory or a "No such file or directory" error if the file does not already exist locally.
+.IP \(bu 2
+When the chef\-shell DSL is available, the chef\-client DSL will not be (unless the management workstation is also a chef\-client). Without the chef\-client DSL, a bash block cannot be used to run bash commands.
+.UNINDENT
+.UNINDENT
+.UNINDENT
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-E\fR, \fB\-\-exec CODE\fR
-Provide a snippet of code to evaluate on the command line
-.
-.SH "DESCRIPTION"
-\fBknife exec\fR runs arbitrary ruby scripts in a context similar to that of the shef(1) DSL\. See the shef documentation for a description of the commands available\.
-.
-.SH "EXAMPLES"
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-Make an API call against an arbitrary endpoint
-knife exec \-E \'api\.get("nodes/fluke\.localdomain/cookbooks")\'
-.
-.br
-=> list of cookbooks for the node \fIfluke\.localdomain\fR
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-Remove the role \fIobsolete\fR from all nodes
-knife exec \-E \'nodes\.transform(:all){|n| n\.run_list\.delete("role[obsolete]")}\'
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-Generate the expanded run list for hosts in the \fBwebserver\fR role
-knife exec \-E \'nodes\.find(:roles => "webserver") {|n| n\.expand!; n[:recipes]}\'
-.
-.SH "SEE ALSO"
-\fBshef(1)\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBAuthenticated API Requests\fP
+.sp
+The \fBknife exec\fP subcommand can be used to make authenticated API requests to the server using the following methods:
+.TS
+center;
+|l|l|.
+_
+T{
+Method
+T}	T{
+Description
+T}
+_
+T{
+\fBapi.delete\fP
+T}	T{
+Use to delete an object from the server.
+T}
+_
+T{
+\fBapi.get\fP
+T}	T{
+Use to get the details of an object on the server.
+T}
+_
+T{
+\fBapi.post\fP
+T}	T{
+Use to add an object to the server.
+T}
+_
+T{
+\fBapi.put\fP
+T}	T{
+Use to update an object on the server.
+T}
+_
+.TE
+.sp
+These methods are used with the \fB\-E\fP option, which executes that string locally on the workstation using chef\-shell. These methods have the following syntax:
+.sp
+.nf
+.ft C
+$ knife exec \-E \(aqapi.method(/endpoint)\(aq
+.ft P
+.fi
+.sp
+where:
+.INDENT 0.0
+.IP \(bu 2
+\fBapi.method\fP is the corresponding authentication method \-\-\- \fBapi.delete\fP, \fBapi.get\fP, \fBapi.post\fP, or \fBapi.put\fP
+.IP \(bu 2
+\fB/endpoint\fP is an endpoint in the Chef Server API
+.UNINDENT
+.sp
+For example, to get the data for a node named "Example_Node":
+.sp
+.nf
+.ft C
+$ knife exec \-E \(aqputs api.get("/nodes/Example_Node")\(aq
+.ft P
+.fi
+.sp
+and to ensure that the output is visible in the console, add the \fBputs\fP in front of the API authorization request:
+.sp
+.nf
+.ft C
+$ knife exec \-E \(aqputs api.get("/nodes/Example_Node")\(aq
+.ft P
+.fi
+.sp
+where \fBputs\fP is the shorter version of the \fB$stdout.puts\fP predefined variable in Ruby.
+.sp
+The following example shows how to add a client named "IBM305RAMAC" and the \fB/clients\fP endpoint, and then return the private key for that user in the console:
+.sp
+.nf
+.ft C
+$ client_desc = {
+    "name"  => "IBM305RAMAC",
+    "admin" => false
+  }
+
+  new_client = api.post("/clients", client_desc)
+  puts new_client["private_key"]
+.ft P
+.fi
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife exec SCRIPT (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-E CODE\fP, \fB\-\-exec CODE\fP
+A string of code that will be executed.
+.TP
+.B \fB\-p PATH:PATH\fP, \fB\-\-script\-path PATH:PATH\fP
+A colon\-separated path at which Ruby scripts are located.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+There are three ways to use \fBknife exec\fP to run Ruby script files. For example:
+.sp
+.nf
+.ft C
+$ knife exec /path/to/script_file
+.ft P
+.fi
+.sp
+Or:
+.sp
+.nf
+.ft C
+$ knife exec \-E \(aqRUBY CODE\(aq
+.ft P
+.fi
+.sp
+Or:
+.sp
+.nf
+.ft C
+$ knife exec
+RUBY CODE
+^D
+.ft P
+.fi
+.sp
+To check the status of Knife using a Ruby script named "status.rb" (which looks like):
+.sp
+.nf
+.ft C
+printf "%\-5s %\-12s %\-8s %s\en", "Check In", "Name", "Ruby", "Recipes"
+nodes.all do |n|
+   checkin = Time.at(n[\(aqohai_time\(aq]).strftime("%F %R")
+   rubyver = n[\(aqlanguages\(aq][\(aqruby\(aq][\(aqversion\(aq]
+   recipes = n.run_list.expand(_default).recipes.join(", ")
+   printf "%\-20s %\-12s %\-8s %s\en", checkin, n.name, rubyver, recipes
+end
+.ft P
+.fi
+.sp
+and is located in a directory named "scripts", enter:
+.sp
+.nf
+.ft C
+$ knife exec scripts/status.rb
+.ft P
+.fi
+.sp
+To show the available free memory for all nodes, enter:
+.sp
+.nf
+.ft C
+$ knife exec \-E \(aqnodes.all {|n| puts "#{n.name} has #{n.memory.total} free memory"}\(aq
+.ft P
+.fi
+.sp
+To list all of the available search indexes, enter:
+.sp
+.nf
+.ft C
+$ knife exec \-E \(aqputs api.get("search").keys\(aq
+.ft P
+.fi
+.sp
+To query a node for multiple attributes using a Ruby script named \fBsearch_attributes.rb\fP (which looks like):
+.sp
+.nf
+.ft C
+% cat scripts/search_attributes.rb
+query = ARGV[2]
+attributes = ARGV[3].split(",")
+puts "Your query: #{query}"
+puts "Your attributes: #{attributes.join(" ")}"
+results = {}
+search(:node, query) do |n|
+   results[n.name] = {}
+   attributes.each {|a| results[n.name][a] = n[a]}
+end
+
+puts results
+exit 0
+.ft P
+.fi
+.sp
+enter:
+.sp
+.nf
+.ft C
+% knife exec scripts/search_attributes.rb "hostname:test_system" ipaddress,fqdn
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+Your query: hostname:test_system
+Your attributes: ipaddress fqdn
+{"test_system.example.com"=>{"ipaddress"=>"10.1.1.200", "fqdn"=>"test_system.example.com"}}
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-index-rebuild.1 b/distro/common/man/man1/knife-index-rebuild.1
new file mode 100644
index 0000000..1dc7dc1
--- /dev/null
+++ b/distro/common/man/man1/knife-index-rebuild.1
@@ -0,0 +1,115 @@
+.TH "KNIFE-INDEX-REBUILD" "1" "Chef 11.8.0" "" "knife index rebuild"
+.SH NAME
+knife-index-rebuild \- The man page for the knife index rebuild subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife index rebuild\fP subcommand is used to rebuild the search indexes for the open source server. This operation is destructive and may take some time.
+.IP Note
+This subcommand ONLY works when run against the open source server version 10.x. This subcommand will NOT run against open source server 11, Enterprise Chef (including hosted Enterprise Chef), or Private Chef.
+.RE
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife index rebuild
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-index.1 b/distro/common/man/man1/knife-index.1
deleted file mode 100644
index 1a14e95..0000000
--- a/distro/common/man/man1/knife-index.1
+++ /dev/null
@@ -1,29 +0,0 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-INDEX" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-index\fR \- Rebuild the search index on a Chef Server
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBindex rebuild\fR \fI(options)\fR
-.
-.TP
-\fB\-y\fR, \fB\-\-yes\fR
-don\'t bother to ask if I\'m sure
-.
-.SH "DESCRIPTION"
-Rebuilds all the search indexes on the server\. This is accomplished by deleting all objects from the search index, and then forwarding each item in the database to \fBchef\-expander\fR(8) via \fBrabbitmq\-server\fR(1)\. Depending on the number of objects in the database, it may take some time for all objects to be indexed and available for search\.
-.
-.SH "SEE ALSO"
-\fBknife\-search\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-list.1 b/distro/common/man/man1/knife-list.1
new file mode 100644
index 0000000..2788e5b
--- /dev/null
+++ b/distro/common/man/man1/knife-list.1
@@ -0,0 +1,170 @@
+.TH "KNIFE-LIST" "1" "Chef 11.8.0" "" "knife list"
+.SH NAME
+knife-list \- The man page for the knife list subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife list\fP subcommand is used to view a list of objects on the server. This subcommand works similar to \fBknife cookbook list\fP, \fBknife data bag list\fP, \fBknife environment list\fP, \fBknife node list\fP, and \fBknife role list\fP, but with a single verb (and a single action).
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife list [PATTERN...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-1\fP
+Indicates that only one results column will be shown.
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-d\fP
+Indicates that a directory\(aqs children will not be shown when a directory matches a pattern. Default value: \fBfalse\fP.
+.TP
+.B \fB\-f\fP, \fB\-\-flat\fP
+Indicates that a list of file names will be shown. Set to \fBfalse\fP to view ls\-like output. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-local\fP
+Indicates that only contents of the local directory will be returned. Default: \fBfalse\fP.
+.TP
+.B \fB\-1\fP
+Indicates that only one column of results will be shown. Default: \fBfalse\fP.
+.TP
+.B \fB\-p\fP
+Indicates that trailing slashes (/) will be shown for directories. Default: \fBfalse\fP.
+.TP
+.B \fB\-R\fP
+Indicates that directories will be listed recursively. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+For example, to view a list of roles on the server:
+.sp
+.nf
+.ft C
+$ knife list roles/
+.ft P
+.fi
+.sp
+To view a list of roles and environments on the server:
+.sp
+.nf
+.ft C
+$ knife list roles/ environments/
+.ft P
+.fi
+.sp
+To view a list of absolutely everything on the server:
+.sp
+.nf
+.ft C
+$ knife list \-R /
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-node.1 b/distro/common/man/man1/knife-node.1
index 19ce2e5..394cf54 100644
--- a/distro/common/man/man1/knife-node.1
+++ b/distro/common/man/man1/knife-node.1
@@ -1,134 +1,578 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-NODE" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-node\fR \- Manage the hosts in your infrastructure
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBnode\fR \fIsub\-command\fR \fI(options)\fR
-.
-.SH "DESCRIPTION"
-Nodes are data structures that represent hosts configured with Chef\. Nodes have a \fBname\fR, a String that uniquely identifies the node, \fBattributes\fR, a nested Hash of properties that describe how the host should be configured, a \fBchef_environment\fR, a String representing the environment to which the node belongs, and a \fBrun_list\fR, an ordered list of \fBrecipes\fR or \fBroles\fR that chef\-client should apply when configuring a host\.
-.
-.P
-When a host communicates with a Chef Server, it authenticates using its \fBnode_name\fR for identification and signs its reqests with a private key\. The Server validates the request by looking up a \fBclient\fR object with a name identical to the \fBnode_name\fR submitted with the request and verifes the signature using the public key for that \fBclient\fR object\. \fBNOTE\fR that the \fBclient\fR is a different object in the system\. It is associated with a node by virtue of having a m [...]
-.
-.P
-By default \fBchef\-client\fR(8) will create a node using the FQDN of the host for the node name, though this may be overridden by configuration settings\.
-.
-.SH "NODE SUB\-COMMANDS"
-The following \fBnode\fR subcommands are available:
-.
-.SH "BULK DELETE"
-\fBknife node bulk delete\fR \fIregex\fR \fI(options)\fR
-.
-.P
-Deletes nodes for which the name matches the regular expression \fIregex\fR on the Chef Server\. The regular expression should be given in quotes, and should not be surrounded with forward slashes (as is typical of regular expression literals in scripting languages)\.
-.
-.SH "CREATE"
-\fBknife node create\fR \fIname\fR \fI(options)\fR
-.
-.P
-Create a new node\. Unless the \-\-disable\-editing option is given, an empty node object will be created and displayed in your text editor\. If the editor exits with a successful exit status, the node data will be posted to the Chef Server to create the node\.
-.
-.SH "DELETE"
-\fBknife node delete\fR \fIname\fR \fI(options)\fR
-.
-.P
-Deletes the node identified by \fIname\fR on the Chef Server\.
-.
-.SH "EDIT"
-\fBknife node edit\fR \fIname\fR \fI(options)\fR
-.
+.TH "KNIFE-NODE" "1" "Chef 11.8.0" "" "knife node"
+.SH NAME
+knife-node \- The man page for the knife node subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+A node is any physical, virtual, or cloud machine that is configured to be maintained by a chef\-client.
+.sp
+The \fBknife node\fP subcommand is used to manage the nodes that exist on a server.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node [ARGUMENT] (options)
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-a\fR, \fB\-\-all\fR
-Display all node data in the editor\. By default, default, override, and automatic attributes are not shown\.
-.
-.P
-Edit the node identified by \fIname\fR\. Like \fBknife node create\fR, the node will be displayed in your text editor unless the \-n option is present\.
-.
-.SH "FROM FILE"
-\fBknife node from file\fR \fIfile\fR \fI(options)\fR
-.
-.P
-Create a node from a JSON format \fIfile\fR\.
-.
-.SH "LIST"
-\fBknife node list\fR \fI(options)\fR
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-w\fR, \fB\-\-with\-uri\fR
-Show corresponding URIs
-.
-.P
-List all nodes\.
-.
-.SH "RUN_LIST ADD"
-\fBknife node run_list add\fR \fIname\fR \fIrun list item\fR \fI(options)\fR
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-a\fR, \fB\-\-after ITEM\fR
-Place the ENTRY in the run list after ITEM
-.
-.P
-Add the \fIrun list item\fR to the node\'s \fBrun_list\fR\. See Run list
-.
-.SH "RUN_LIST REMOVE"
-\fBknife node run_list remove\fR \fInode name\fR \fIrun list item\fR \fI(options)\fR
-.
-.P
-Remove the \fIrun list item\fR from the node\'s \fBrun_list\fR\.
-.
-.SH "SHOW"
-\fBknife node show\fR \fInode name\fR \fI(options)\fR
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-a\fR, \fB\-\-attribute [ATTR]\fR
-Show only one attribute
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-r\fR, \fB\-\-run\-list\fR
-Show only the run list
-.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
 .TP
-\fB\-F\fR, \fB\-\-format FORMAT\fR
-Display the node in a different format\.
-.
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
 .TP
-\fB\-m\fR, \fB\-\-medium\fR
-Display more, but not all, of the node\'s data when using the default \fIsummary\fR format
-.
-.P
-Displays the node identified by \fInode name\fR on stdout\.
-.
-.SH "RUN LIST ITEM FORMAT"
-Run list items may be either roles or recipes\. When adding a role to a run list, the correct syntax is "role[ROLE_NAME]"
-.
-.P
-When adding a recipe to a run list, there are several valid formats:
-.
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
 .TP
-Fully Qualified Format
-"recipe[COOKBOOK::RECIPE_NAME]", for example, "recipe[chef::client]"
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-Cookbook Recipe Format
-For brevity, the recipe part of the fully qualified format may be omitted, and recipes specified as "COOKBOOK::RECIPE_NAME", e\.g\., "chef::client"
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
 .TP
-Default Recipe Format
-When adding the default recipe of a cookbook to a run list, the recipe name may be omitted as well, e\.g\., "chef::default" may be written as just "chef"
-.
-.SH "SEE ALSO"
-\fBknife\-client\fR(1) \fBknife\-search\fR(1) \fBknife\-role\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH BULK DELETE
+.sp
+The \fBbulk delete\fP argument is used to delete one or more nodes that match a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (/).
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node bulk delete REGEX
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To bulk delete many nodes, use a regular expression to define the pattern:
+.sp
+.nf
+.ft C
+$ knife node bulk delete "^[0\-9]{3}$"
+.ft P
+.fi
+.sp
+Type \fBY\fP to confirm a deletion.
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to add a node to the server. Node data is stored as JSON on the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node create NODE_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To add a node, enter:
+.sp
+.nf
+.ft C
+$ knife node create node1
+.ft P
+.fi
+.sp
+In the $EDITOR enter the node data in JSON:
+.sp
+.nf
+.ft C
+## sample:
+{
+   "normal": {
+   },
+   "name": "foobar",
+   "override": {
+   },
+   "default": {
+   },
+   "json_class": "Chef::Node",
+   "automatic": {
+   },
+   "run_list": [
+      "recipe[zsh]",
+      "role[webserver]"
+   ],
+   "chef_type": "node"
+}
+.ft P
+.fi
+.sp
+When finished, save it.
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete a node from the server.
+.IP Note
+Deleting a node will not delete any corresponding API clients.
+.RE
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node delete NODE_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To delete a node called "dev", enter:
+.sp
+.nf
+.ft C
+$ knife node delete dev
+.ft P
+.fi
+.SH EDIT
+.sp
+The \fBedit\fP argument is used to edit the details of a node on a server. Node data is stored as JSON on the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node edit NODE_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-all\fP
+Displays a node in the $EDITOR. By default, attributes that are default, override, or automatic are not shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To edit the data for a node named "node1", enter:
+.sp
+.nf
+.ft C
+$ knife node edit node1 \-a
+.ft P
+.fi
+.sp
+Update the role data in JSON:
+.sp
+.nf
+.ft C
+## sample:
+{
+   "normal": {
+   },
+   "name": "node1",
+   "override": {
+   },
+   "default": {
+   },
+   "json_class": "Chef::Node",
+   "automatic": {
+   },
+   "run_list": [
+      "recipe[devops]",
+      "role[webserver]"
+   ],
+   "chef_type": "node"
+}
+.ft P
+.fi
+.sp
+When finished, save it.
+.SH FROM FILE
+.sp
+The \fBfrom file\fP argument is used to create a node using existing node data as a template.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node from file FILE
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To add a node using data contained in a JSON file:
+.sp
+.nf
+.ft C
+$ knife node from file "path to JSON file"
+.ft P
+.fi
+.SH LIST
+.sp
+The \fBlist\fP argument is used to view all of the nodes that exist on a server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node list (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To verify the list of nodes that are registered with the server, enter:
+.sp
+.nf
+.ft C
+$ knife node list
+.ft P
+.fi
+.sp
+to return something similar to:
+.sp
+.nf
+.ft C
+i\-12345678
+rs\-123456
+.ft P
+.fi
+.SH RUN_LIST ADD
+.sp
+The \fBrun_list add\fP argument is used to add run list items (roles or recipes) to a node. A recipe must be in one of the following formats: fully qualified, cookbook, or default. Both roles and recipes must be in quotes, for example: \fB\(aqrole[ROLE_NAME]\(aq\fP or \fB\(aqrecipe[COOKBOOK::RECIPE_NAME]\(aq\fP. Use a comma to separate roles and recipes when adding more than one, like this: \fB\(aqrecipe[COOKBOOK::RECIPE_NAME],COOKBOOK::RECIPE_NAME,role[ROLE_NAME]\(aq\fP.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node run_list add NODE_NAME RUN_LIST_ITEM (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a ITEM\fP, \fB\-\-after ITEM\fP
+Use this to add the run list item after the specified run list item.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To add a role to a run list, enter:
+.sp
+.nf
+.ft C
+$ knife node run_list add node \(aqrole[ROLE_NAME]\(aq
+.ft P
+.fi
+.sp
+To add roles and recipes to a run list, enter:
+.sp
+.nf
+.ft C
+$ knife node run_list add node \(aqrecipe[COOKBOOK::RECIPE_NAME],recipe[COOKBOOK::RECIPE_NAME],role[ROLE_NAME]\(aq
+.ft P
+.fi
+.sp
+To add a recipe to a run list using the fully qualified format, enter:
+.sp
+.nf
+.ft C
+$ knife node run_list add node \(aqrecipe[COOKBOOK::RECIPE_NAME]\(aq
+.ft P
+.fi
+.sp
+To add a recipe to a run list using the cookbook format, enter:
+.sp
+.nf
+.ft C
+$ knife node run_list add node \(aqCOOKBOOK::RECIPE_NAME\(aq
+.ft P
+.fi
+.sp
+To add the default recipe of a cookbook to a run list, enter:
+.sp
+.nf
+.ft C
+$ knife node run_list add node \(aqCOOKBOOK\(aq
+.ft P
+.fi
+.SH RUN_LIST REMOVE
+.sp
+The \fBrun_list remove\fP argument is used to remove run list items (roles or recipes) from a node. A recipe must be in one of the following formats: fully qualified, cookbook, or default. Both roles and recipes must be in quotes, for example: \fB\(aqrole[ROLE_NAME]\(aq\fP or \fB\(aqrecipe[COOKBOOK::RECIPE_NAME]\(aq\fP. Use a comma to separate roles and recipes when removing more than one, like this: \fB\(aqrecipe[COOKBOOK::RECIPE_NAME],COOKBOOK::RECIPE_NAME,role[ROLE_NAME]\(aq\fP.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node run_list remove NODE_NAME RUN_LIST_ITEM
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To remove a role from a run list, enter:
+.sp
+.nf
+.ft C
+$ knife node run_list remove node \(aqrole[ROLE_NAME]\(aq
+.ft P
+.fi
+.sp
+To remove a recipe from a run list using the fully qualified format, enter:
+.sp
+.nf
+.ft C
+$ knife node run_list remove node \(aqrecipe[COOKBOOK::RECIPE_NAME]\(aq
+.ft P
+.fi
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to display information about a node.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife node show NODE_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP
+The attribute (or attributes) to show.
+.TP
+.B \fB\-l\fP, \fB\-\-long\fP
+Display long output when searching nodes while using the default summary format.
+.TP
+.B \fB\-m\fP, \fB\-\-medium\fP
+Display more, but not all, of a node\(aqs data when searching using the default summary format.
+.TP
+.B \fB\-r\fP, \fB\-\-run\-list\fP
+Indicates that only the run\-list will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view all data for a node named "build", enter:
+.sp
+.nf
+.ft C
+$ knife node show build
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+Node Name:   build
+Environment: _default
+FQDN:
+IP:
+Run List:
+Roles:
+Recipes:
+Platform:
+.ft P
+.fi
+.sp
+To show basic information about a node, truncated and nicely formatted:
+.sp
+.nf
+.ft C
+knife node show <node_name>
+.ft P
+.fi
+.sp
+To show all information about a node, nicely formatted:
+.sp
+.nf
+.ft C
+knife node show \-l <node_name>
+.ft P
+.fi
+.sp
+To list a single node attribute:
+.sp
+.nf
+.ft C
+knife node show <node_name> \-a <attribute_name>
+.ft P
+.fi
+.sp
+where \fB<attribute_name>\fP is something like kernel or platform. (This doesn\(aqt work for nested attributes like \fBnode[kernel][machine]\fP because \fBknife node show\fP doesn\(aqt understand nested attributes.)
+.sp
+To view the FQDN for a node named "i\-12345678", enter:
+.sp
+.nf
+.ft C
+$ knife node show i\-12345678 \-a fqdn
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+fqdn: ip\-10\-251\-75\-20.ec2.internal
+.ft P
+.fi
+.sp
+To view the run list for a node named "dev", enter:
+.sp
+.nf
+.ft C
+$ knife node show dev \-r
+.ft P
+.fi
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife role show devops \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.sp
+To view node information in raw JSON, use the \fB\-l\fP or \fB\-\-long\fP option:
+.sp
+.nf
+.ft C
+knife node show \-l \-F json <node_name>
+.ft P
+.fi
+.sp
+and/or:
+.sp
+.nf
+.ft C
+knife node show \-l \-\-format=json <node_name>
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-raw.1 b/distro/common/man/man1/knife-raw.1
new file mode 100644
index 0000000..c35b010
--- /dev/null
+++ b/distro/common/man/man1/knife-raw.1
@@ -0,0 +1,170 @@
+.TH "KNIFE-RAW" "1" "Chef 11.8.0" "" "knife raw"
+.SH NAME
+knife-raw \- The man page for the knife raw subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife raw\fP subcommand is used to send a REST request to a specified path using the Chef Server API.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife raw REQUEST_PATH (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-i FILE\fP, \fB\-\-input FILE\fP
+The name of a file to be used with the \fBPUT\fP or a \fBPOST\fP request.
+.TP
+.B \fB\-\-[no\-]pretty\fP
+Use \fB\-\-no\-pretty\fP to disable pretty\-print output for JSON. Default: \fB\-\-pretty\fP.
+.TP
+.B \fB\-m METHOD\fP, \fB\-\-method METHOD\fP
+The request method: \fBDELETE\fP, \fBGET\fP, \fBPOST\fP, or \fBPUT\fP. Default value: \fBGET\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view information about a client:
+.sp
+.nf
+.ft C
+knife raw /clients/<client_name>
+.ft P
+.fi
+.sp
+To view information about a node:
+.sp
+.nf
+.ft C
+knife raw /nodes/<node_name>
+.ft P
+.fi
+.sp
+To delete a data bag, enter a command similar to:
+.sp
+.nf
+.ft C
+$ knife raw \-m DELETE /data/foo
+.ft P
+.fi
+.sp
+to return something similar to:
+.sp
+.nf
+.ft C
+{
+  "name":"foo",
+  "json_class":"Chef::DataBag",
+  "chef_type":"data_bag"
+}
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-recipe-list.1 b/distro/common/man/man1/knife-recipe-list.1
new file mode 100644
index 0000000..ab7bf6e
--- /dev/null
+++ b/distro/common/man/man1/knife-recipe-list.1
@@ -0,0 +1,133 @@
+.TH "KNIFE-RECIPE-LIST" "1" "Chef 11.8.0" "" "knife recipe list"
+.SH NAME
+knife-recipe-list \- The man page for the knife recipe list subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife recipe list\fP subcommand is used to view all of the recipes that are on a server. A regular expression can be used to limit the results to recipes that match a specific pattern. The regular expression must be within quotes and not be surrounded by forward slashes (/).
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife recipe list REGEX
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To view a list of recipes:
+.sp
+.nf
+.ft C
+$ knife recipe list \(aqcouchdb::*\(aq
+.ft P
+.fi
+.sp
+to return:
+.sp
+.nf
+.ft C
+couchdb::main_monitors
+couchdb::master
+couchdb::default
+couchdb::org_cleanu
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-role.1 b/distro/common/man/man1/knife-role.1
index 2ab781f..33c7b6b 100644
--- a/distro/common/man/man1/knife-role.1
+++ b/distro/common/man/man1/knife-role.1
@@ -1,88 +1,374 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.TH "KNIFE-ROLE" "1" "Chef 11.8.0" "" "knife role"
+.SH NAME
+knife-role \- The man page for the knife role subcommand.
 .
-.TH "KNIFE\-ROLE" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
+.nr rst2man-indent-level 0
 .
-.SH "NAME"
-\fBknife\-role\fR \- Group common configuration settings
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBrole\fR \fIsub\-command\fR \fI(options)\fR
-.
-.SH "ROLE SUB\-COMMANDS"
-The following \fBrole\fR subcommands are available:
-.
-.SH "LIST"
-\fBknife role list\fR \fI(options)\fR
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
 .
+.sp
+A role is a way to define certain patterns and processes that exist across nodes in an organization as belonging to a single job function. Each role consists of zero (or more) attributes and a run list. Each node can have zero (or more) roles assigned to it. When a role is run against a node, the configuration details of that node are compared against the attributes of the role, and then the contents of that role\(aqs run list are applied to the node\(aqs configuration details. When a ch [...]
+.sp
+The \fBknife role\fP subcommand is used to manage the roles that are associated with one or more nodes on a server.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role [ARGUMENT] (options)
+.ft P
+.fi
+.IP Note
+Use the \fBknife node\fP subcommand (and the \fBrun_list add node\fP argument) to add a role to a node on the server.
+.RE
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-w\fR, \fB\-\-with\-uri\fR
-Show corresponding URIs
-.
-.P
-List roles\.
-.
-.SH "SHOW"
-\fBknife role show ROLE\fR \fI(options)\fR
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-a\fR, \fB\-\-attribute ATTR\fR
-Show only one attribute
-.
-.P
-Show a specific role\.
-.
-.SH "CREATE"
-\fBknife role create ROLE\fR \fI(options)\fR
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-d\fR, \fB\-\-description\fR
-The role description
-.
-.P
-Create a new role\.
-.
-.SH "EDIT"
-\fBknife role edit ROLE\fR \fI(options)\fR
-.
-.P
-Edit a role\.
-.
-.SH "FROM FILE"
-\fBknife role from file FILE\fR \fI(options)\fR
-.
-.P
-Create or update a role from a role Ruby DSL (\fB\.rb\fR) or JSON file\.
-.
-.SH "DELETE"
-\fBknife role delete ROLE\fR \fI(options)\fR
-.
-.P
-Delete a role\.
-.
-.SH "BULK DELETE"
-\fBknife role bulk delete REGEX\fR \fI(options)\fR
-.
-.P
-Delete roles on the Chef Server based on a regular expression\. The regular expression (\fIREGEX\fR) should be in quotes, not in //\'s\.
-.
-.SH "DESCRIPTION"
-Roles provide a mechanism to group repeated configuration settings\. Roles are data structures that contain \fBdefault_attributes\fR, and \fBoverride_attributes\fR, which are nested hashes of configuration settings, and a \fBrun_list\fR, which is an ordered list of recipes and roles that should be applied to a host by chef\-client\.
-.
-.P
-\fBdefault_attributes\fR will be overridden if they conflict with a value on a node that includes the role\. Conversely, \fBoverride_attributes\fR will override any values set on nodes that apply them\.
-.
-.P
-When \fBchef\-client\fR(8) configures a host, it will "expand" the \fBrun_list\fR included in that host\'s node data\. The expansion process will recursively replace any roles in the run_list with that role\'s run_list\.
-.
-.SH "SEE ALSO"
-\fBknife\-node(1)\fR \fBknife\-environment(1)\fR \fIhttp://wiki\.opscode\.com/display/chef/Roles\fR \fIhttp://wiki\.opscode\.com/display/chef/Attributes\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH BULK DELETE
+.sp
+The \fBbulk delete\fP argument is used to delete one or more roles that match a pattern defined by a regular expression. The regular expression must be within quotes and not be surrounded by forward slashes (/).
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role bulk delete REGEX
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To bulk delete roles using a regular expression:
+.sp
+.nf
+.ft C
+$ knife role bulk delete "^[0\-9]{3}$"
+.ft P
+.fi
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to add a role to the server. To add a role to a node, you must use the \fBnode\fP sub\-command and the \fBrun\-list add\fP argument. Role data is saved as JSON on the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role create ROLE_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-d DESCRIPTION\fP, \fB\-\-description DESCRIPTION\fP
+The description of the role. This value will populate the description field for the role on the server.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To add a role named "role1", enter:
+.sp
+.nf
+.ft C
+$ knife role create role1
+.ft P
+.fi
+.sp
+In the $EDITOR enter the role data in JSON:
+.sp
+.nf
+.ft C
+## sample:
+{
+   "name": "role1",
+   "default_attributes": {
+   },
+   "json_class": "Chef::Role",
+   "run_list": [
+
+   ],
+   "description": "",
+   "chef_type": "role",
+   "override_attributes": {
+   }
+}
+.ft P
+.fi
+.sp
+When finished, save it.
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete a role from the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role delete ROLE_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To delete a role:
+.sp
+.nf
+.ft C
+$ knife role delete devops
+.ft P
+.fi
+.sp
+Type \fBY\fP to confirm a deletion.
+.SH EDIT
+.sp
+The \fBedit\fP argument is used to edit role details on the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role edit ROLE_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To edit the data for a role named "role1", enter:
+.sp
+.nf
+.ft C
+$ knife role edit role1
+.ft P
+.fi
+.sp
+Update the role data in JSON:
+.sp
+.nf
+.ft C
+## sample:
+{
+   "name": "role1",
+   "default_attributes": {
+   },
+   "json_class": "Chef::Role",
+   "run_list": [
+
+   ],
+   "description": "This is the description for the role1 role.",
+   "chef_type": "role",
+   "override_attributes": {
+   }
+}
+.ft P
+.fi
+.sp
+When finished, save it.
+.SH FROM FILE
+.sp
+The \fBfrom file\fP argument is used to create a role using existing JSON data as a template.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role from file FILE
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To view role details based on the values contained in a JSON file:
+.sp
+.nf
+.ft C
+$ knife role from file "path to JSON file"
+.ft P
+.fi
+.SH LIST
+.sp
+The \fBlist\fP argument is used to view a list of roles that are currently available on the server.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role list
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view a list of roles on the server and display the URI for each role returned, enter:
+.sp
+.nf
+.ft C
+$ knife role list \-w
+.ft P
+.fi
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to view the details of a role.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife role show ROLE_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP
+The attribute (or attributes) to show.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife role show devops \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife role show devops \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-search.1 b/distro/common/man/man1/knife-search.1
index 953a5f6..e59be88 100644
--- a/distro/common/man/man1/knife-search.1
+++ b/distro/common/man/man1/knife-search.1
@@ -1,280 +1,304 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-SEARCH" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-search\fR \- Find objects on a Chef Server by query
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBsearch INDEX QUERY\fR \fI(options)\fR
-.
+.TH "KNIFE-SEARCH" "1" "Chef 11.8.0" "" "knife search"
+.SH NAME
+knife-search \- The man page for the knife search subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+Search indexes allow queries to be made for any type of data that is indexed by the server, including data bags (and data bag items), environments, nodes, and roles. A defined query syntax is used to support search patterns like exact, wildcard, range, and fuzzy. A search is a full\-text query that can be done from several locations, including from within a recipe, by using the \fBsearch\fP subcommand in Knife, by using the search functionality in the Management Console, or by using the  [...]
+.sp
+The \fBknife search\fP subcommand is used run a search query for information that is indexed on a server.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-a\fR, \fB\-\-attribute ATTR\fR
-Show only one attribute
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-i\fR, \fB\-\-id\-only\fR
-Show only the ID of matching objects
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-q\fR, \fB\-\-query QUERY\fR
-The search query; useful to protect queries starting with \-
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-R\fR, \fB\-\-rows INT\fR
-The number of rows to return
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-r\fR, \fB\-\-run\-list\fR
-Show only the run list
-.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
 .TP
-\fB\-o\fR, \fB\-\-sort SORT\fR
-The order to sort the results in
-.
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
 .TP
-\fB\-b\fR, \fB\-\-start ROW\fR
-The row to start returning results at
-.
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
 .TP
-\fB\-m\fR, \fB\-\-medium\fR
-Display medium sized output when searching nodes using the default summary format
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fB\-l\fR, \fB\-\-long\fR
-Display long output when searching nodes using the default summary format
-.
-.SH "DESCRIPTION"
-Search is a feature of the Chef Server that allows you to use a full\-text search engine to query information about your infrastructure and applications\. You can utilize this service via search calls in a recipe or the knife search command\. The search syntax is based on Lucene\.
-.
-.SH "INDEXES"
-Search indexes are a feature of the Chef Server and the search sub\-command allows querying any of the available indexes using SOLR query syntax\. The following data types are indexed for search:
-.
-.IP "\(bu" 4
-\fInode\fR
-.
-.IP "\(bu" 4
-\fIrole\fR
-.
-.IP "\(bu" 4
-\fIenvironment\fR
-.
-.IP "\(bu" 4
-\fIclients\fR
-.
-.IP "\(bu" 4
-\fIdata bag\fR
-.
-.IP "" 0
-.
-.P
-Data bags are indexed by the data bag\'s name\. For example, to search a data bag named "admins":
-.
-.IP "" 4
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
 .nf
-
-knife search admins "field:search_pattern"
-.
+.ft C
+$ knife search INDEX SEARCH_QUERY
+.ft P
 .fi
-.
-.IP "" 0
-.
-.SH "QUERY SYNTAX"
-Queries have the form \fBfield:search_pattern\fR where \fBfield\fR is a key in the JSON description of the relevant objects (nodes, roles, environments, or data bags)\. Both \fBfield\fR and \fBsearch_pattern\fR are case\-sensitive\. \fBsearch_pattern\fR can be an exact, wildcard, range, or fuzzy match (see below)\. The \fBfield\fR supports exact matching and limited wildcard matching\.
-.
-.P
-Searches will return the relevant objects (nodes, roles, environments, or data bags) where the \fBsearch_pattern\fR matches the object\'s value of \fBfield\fR\.
-.
-.SS "FIELD NAMES"
-Field names are the keys within the JSON description of the object being searched\. Nested Keys can be searched by placing an underscore ("_") between key names\.
-.
-.SS "WILDCARD MATCHING FOR FIELD NAMES"
-The field name also has limited support for wildcard matching\. Both the "*" and "?" wildcards (see below) can be used within a field name; however, they cannot be the first character of the field name\.
-.
-.SS "EXACT MATCHES"
-Without any search modifiers, a search returns those fields for which the \fBsearch_pattern\fR exactly matches the value of \fBfield\fR in the JSON description of the object\.
-.
-.SS "WILDCARD MATCHES"
-Search support both single\- and multi\-character wildcard searches within a search pattern\.
-.
-.P
-\'?\' matches exactly one character\.
-.
-.P
-\'*\' matches zero or more characters\.
-.
-.SS "RANGE MATCHES"
-Range searches allows one to match values between two given values\. To match values between X and Y, inclusively, use square brackets:
-.
-.IP "" 4
-.
+.sp
+where \fBINDEX\fP is one of \fBclient\fP, \fBenvironment\fP, \fBnode\fP, \fBrole\fP, or the name of a data bag and \fBSEARCH_QUERY\fP is the search query syntax for the query that will be executed.
+.sp
+\fBINDEX\fP is implied if omitted, and will default to \fBnode\fP. For example:
+.sp
 .nf
-
-knife search INDEX \'field:[X TO Y]
-.
+.ft C
+$ knife search \(aq*:*\(aq \-i
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-To match values between X and Y, exclusively, use curly brackets:
-.
-.IP "" 4
-.
+.sp
+will return something similar to:
+.sp
 .nf
+.ft C
+8 items found
 
-knife search INDEX \'field:{X TO Y}\'
-.
+centos\-62\-dev
+opensuse\-1203
+ubuntu\-1304\-dev
+ubuntu\-1304\-orgtest
+ubuntu\-1204\-ohai\-test
+ubuntu\-1304\-ifcfg\-test
+ohai\-test
+win2k8\-dev
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-Values are sorted in lexicographic order\.
-.
-.SS "FUZZY MATCHES"
-Fuzzy searches allows one to match values based on the Levenshtein Distance algorithm\. To perform a fuzzy match, append a tilda (~) to the search term:
-.
-.IP "" 4
-.
+.sp
+and is the same search as:
+.sp
 .nf
-
-knife search INDEX \'field:term~\'
-.
+.ft C
+$ knife node search \(aq*:*" \-i
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-This search would return nodes whose \fBfield\fR was \'perm\' or \'germ\'\.
-.
-.SS "BOOLEAN OPERATORS"
-The boolean operators NOT, AND, and OR are supported\. To find values of \fBfield\fR that are not X:
-.
-.IP "" 4
-.
+.sp
+If the \fBSEARCH_QUERY\fP does not contain a colon character (\fB:\fP), then the default query pattern is \fBtags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*\fP, which means the following two search queries are effectively the same:
+.sp
 .nf
-
-knife search INDEX \'field:(NOT X)\'
-.
+.ft C
+$ knife search ubuntu
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-To find records where \fBfield1\fR is X and \fBfield2\fR is Y:
-.
-.IP "" 4
-.
+.sp
+or:
+.sp
 .nf
-
-knife search INDEX \'field1:X AND field2:Y\'
-.
+.ft C
+$ knife search node "tags:*ubuntu* OR roles:*ubuntu* OR fqdn:*ubuntu* (etc.)"
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-To find records where \fBfield\fR is X or Y:
-.
-.IP "" 4
-.
+.sp
+\fBOptions\fP
+.sp
+This sub\-command has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP
+The attribute (or attributes) to show.
+.TP
+.B \fB\-b ROW\fP, \fB\-\-start ROW\fP
+The row at which return results will begin.
+.TP
+.B \fB\-i\fP, \fB\-\-id\-only\fP
+Indicates that only matching object IDs will be shown.
+.TP
+.B \fBINDEX\fP
+The name of the index to be queried: \fBclient\fP, \fBenvironment\fP, \fBnode\fP, \fBrole\fP, or \fBDATA_BAG_NAME\fP. Default index: \fBnode\fP.
+.TP
+.B \fB\-l\fP, \fB\-\-long\fP
+Display long output when searching nodes while using the default summary format.
+.TP
+.B \fB\-m\fP, \fB\-\-medium\fP
+Display more, but not all, of a node\(aqs data when searching using the default summary format.
+.TP
+.B \fB\-o SORT\fP, \fB\-\-sort SORT\fP
+The order in which search results will be sorted.
+.TP
+.B \fB\-q SEARCH_QUERY\fP, \fB\-\-query SEARCH_QUERY\fP
+Use to protect search queries that start with a hyphen (\-). A \fB\-q\fP query may be specified as an argument or an option, but not both.
+.TP
+.B \fB\-r\fP, \fB\-\-run\-list\fP
+Indicates that only the run\-list will be shown.
+.TP
+.B \fB\-R INT\fP, \fB\-\-rows INT\fP
+The number of rows to be returned.
+.TP
+.B \fBSEARCH_QUERY\fP
+The search query used to identify a a list of items on a server. This option uses the same syntax as the \fBsearch\fP sub\-command.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To search for the IDs of all nodes running on the Amazon EC2 platform, enter:
+.sp
 .nf
-
-knife search INDEX \'field:X OR field:Y\'
-.
+.ft C
+$ knife search node \(aqec2:*\(aq \-i
+.ft P
 .fi
-.
-.IP "" 0
-.
-.SS "QUOTING AND SPECIAL CHARACTERS"
-In order to avoid having special characters and escape sequences within your search term interpreted by either Ruby or the shell, enclose them in single quotes\.
-.
-.P
-Search terms that include spaces should be enclosed in double\-quotes:
-.
-.IP "" 4
-.
+.sp
+to return something like:
+.sp
 .nf
+.ft C
+4 items found
 
-knife search INDEX \'field:"term with spaces"\'
-.
+ip\-0A7CA19F.ec2.internal
+
+ip\-0A58CF8E.ec2.internal
+
+ip\-0A58E134.ec2.internal
+
+ip\-0A7CFFD5.ec2.internal
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-The following characters must be escaped:
-.
-.IP "" 4
-.
+.sp
+To search for the instance type (flavor) of all nodes running on the Amazon EC2 platform, enter:
+.sp
 .nf
-
-+ \- && || ! ( ) { } [ ] ^ " ~ * ? : \e
-.
+.ft C
+$ knife search node \(aqec2:*\(aq \-a ec2.instance_type
+.ft P
 .fi
-.
-.IP "" 0
-.
-.SH "EXAMPLES"
-Find the nodes with the fully\-qualified domain name (FQDN) www\.example\.com:
-.
-.IP "" 4
-.
+.sp
+to return something like:
+.sp
 .nf
+.ft C
+4 items found
 
-knife search node \'fqdn:www\.example\.com\'
-.
+ec2.instance_type:  m1.large
+id:                 ip\-0A7CA19F.ec2.internal
+
+ec2.instance_type:  m1.large
+id:                 ip\-0A58CF8E.ec2.internal
+
+ec2.instance_type:  m1.large
+id:                 ip\-0A58E134.ec2.internal
+
+ec2.instance_type:  m1.large
+id:                 ip\-0A7CFFD5.ec2.internal
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-Find the nodes running a version of Ubuntu:
-.
-.IP "" 4
-.
+.sp
+To search for all nodes running Ubuntu, enter:
+.sp
 .nf
-
-knife search node \'platform:ubuntu*\'
-.
+.ft C
+$ knife search node \(aqplatform:ubuntu\(aq
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-Find all nodes running CentOS in the production environment:
-.
-.IP "" 4
-.
+.sp
+To search for all nodes running CentOS in the production environment, enter:
+.sp
 .nf
-
-knife search node \'chef_environment:production AND platform:centos\'
-.
+.ft C
+$ knife search node \(aqchef_environment:production AND platform:centos\(aq
+.ft P
 .fi
+.sp
+To find a nested attribute, use a pattern similar to the following:
+.sp
+.nf
+.ft C
+$ knife search node <query_to_run> \-a <main_attribute>.<nested_attribute>
+.ft P
+.fi
+.sp
+To build a search query to use more than one attribute, use an underscore ( _ ) to separate each attribute. For example, the following query will search for all nodes running a specific version of Ruby:
+.sp
+.nf
+.ft C
+$ knife search node "languages_ruby_version:1.9.3"
+.ft P
+.fi
+.sp
+To build a search query that can find a nested attribute:
+.sp
+.nf
+.ft C
+$ knife search node name:<node_name> \-a kernel.machine
+.ft P
+.fi
+.sp
+To test a search query that will be used in a \fBknife ssh\fP command:
+.sp
+.nf
+.ft C
+$ knife search node "role:web AND NOT name:web03"
+.ft P
+.fi
+.sp
+where the query in the previous example will search all servers that have the \fBweb\fP role, but not on the server named \fBweb03\fP.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.IP "" 0
-.
-.SH "KNOWN BUGS"
-.
-.IP "\(bu" 4
-Searches against the client index return no results in most cases\. (CHEF\-2477)
-.
-.IP "\(bu" 4
-Searches using the fuzzy match operator (~) produce an error\. (CHEF\-2478)
-.
-.IP "" 0
-.
-.SH "SEE ALSO"
-\fBknife\-ssh\fR(1) \fIhttp://wiki\.opscode\.com/display/chef/Attributes\fR Lucene Query Parser Syntax \fIhttp://lucene\.apache\.org/java/2_3_2/queryparsersyntax\.html\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-show.1 b/distro/common/man/man1/knife-show.1
new file mode 100644
index 0000000..0612440
--- /dev/null
+++ b/distro/common/man/man1/knife-show.1
@@ -0,0 +1,138 @@
+.TH "KNIFE-SHOW" "1" "Chef 11.8.0" "" "knife show"
+.SH NAME
+knife-show \- The man page for the knife show subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife show\fP subcommand is used to view the details of one (or more) objects on the server. This subcommand works similar to \fBknife cookbook show\fP, \fBknife data bag show\fP, \fBknife environment show\fP, \fBknife node show\fP, and \fBknife role show\fP, but with a single verb (and a single action).
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife show [PATTERN...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To show all cookbooks in the \fBcookbooks/\fP directory:
+.sp
+.nf
+.ft C
+$ knife show cookbooks/
+.ft P
+.fi
+.sp
+or, (if already in the \fBcookbooks/\fP directory in the local chef\-repo):
+.sp
+.nf
+.ft C
+$ knife show
+.ft P
+.fi
+.sp
+To show roles and environments:
+.sp
+.nf
+.ft C
+$ knife show roles/ environments/
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-ssh.1 b/distro/common/man/man1/knife-ssh.1
index 827745f..41f47ee 100644
--- a/distro/common/man/man1/knife-ssh.1
+++ b/distro/common/man/man1/knife-ssh.1
@@ -1,79 +1,254 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "KNIFE\-SSH" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\-ssh\fR \- Run a command or interactive session on multiple remote hosts
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBssh QUERY COMMAND\fR \fI(options)\fR
-.
+.TH "KNIFE-SSH" "1" "Chef 11.8.0" "" "knife ssh"
+.SH NAME
+knife-ssh \- The man page for the knife ssh subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife ssh\fP subcommand is used to invoke SSH commands (in parallel) on a subset of nodes within an organization, based on the results of a search query.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-a\fR, \fB\-\-attribute ATTR\fR
-The attribute to use for opening the connection \- default is fqdn
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-C\fR, \fB\-\-concurrency NUM\fR
-The number of concurrent connections
-.
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
 .TP
-\fB\-m\fR, \fB\-\-manual\-list\fR
-QUERY is a space separated list of servers
-.
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
 .TP
-\fB\-P\fR, \fB\-\-ssh\-password PASSWORD\fR
-The ssh password
-.
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
 .TP
-\fB\-x\fR, \fB\-\-ssh\-user USERNAME\fR
-The ssh username
-.
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
 .TP
-\fB\-i\fR, \fB\-\-identity\-file IDENTITY_FILE\fR
-The SSH identity file used for authentication
-.
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
 .TP
-\fB\-p\fR, \fB\-\-ssh\-port PORT\fR
-The ssh port
-.
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
 .TP
-\fB\-\-[no\-]host\-key\-verify\fR
-Verify host key, enabled by default\.
-.
-.SH "DESCRIPTION"
-The \fIssh\fR sub\-command opens an ssh session to each of the nodes in the search results of the \fIQUERY\fR\. This sub\-command requires that the net\-ssh\-multi and highline Ruby libraries are installed\. On Debian systems, these are the libnet\-ssh\-multi\-ruby and libhighline\-ruby packages\. They can also be installed as RubyGems (net\-ssh\-multi and highline, respectively)\.
-.
-.SH "TERMINAL MULTIPLEXING AND TERMINAL TAB SUPPORT"
-\fBknife ssh\fR integrates with several terminal multiplexer programs to provide a more convenient means of managing multiple ssh sessions\. When the \fICOMMAND\fR option matches one of these, \fBknife ssh\fR will create multiple interactive ssh sessions running locally in the terminal multiplexer instead of invoking the command on the remote host\.
-.
-.P
-The available multiplexers are:
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fBinteractive\fR
-A built\-in multiplexer\. \fBinteractive\fR supports running commands on a subset of the connected hosts in parallel
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
 .TP
-\fBscreen\fR(1)
-Runs ssh interactively inside \fBscreen\fR\. ~/\.screenrc will be sourced if it exists\.
-.
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
 .TP
-\fBtmux\fR(1)
-Runs ssh interactively inside tmux\.
-.
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
 .TP
-\fBmacterm\fR (Mac OS X only)
-Opens a Terminal\.app window and creates a tab for each ssh session\. You must install the rb\-appscript gem before you can use this option\.
-.
-.SH "SEE ALSO"
-\fBknife\-search\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife ssh SEARCH_QUERY SSH_COMMAND (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a SSH_ATTR\fP, \fB\-\-attribute SSH_ATTR\fP
+The attribute that is used when opening the SSH connection. The default attribute is the FQDN of the host. Other possible values include a public IP address, a private IP address, or a hostname.
+.TP
+.B \fB\-C NUM\fP, \fB\-\-concurrency NUM\fP
+The number of allowed concurrent connections.
+.TP
+.B \fB\-G GATEWAY\fP, \fB\-\-ssh\-gateway GATEWAY\fP
+The SSH tunnel or gateway that is used to run a bootstrap action on a machine that is not accessible from the workstation.
+.TP
+.B \fB\-i IDENTITY_FILE\fP, \fB\-\-identity\-file IDENTIFY_FILE\fP
+The SSH identity file used for authentication. Key\-based authentication is recommended.
+.TP
+.B \fB\-m\fP, \fB\-\-manual\-list\fP
+Indicates that a search query is a space\-separated list of servers. If there is more than one item in the list, put quotes around the entire list. For example: \fB\-\-manual\-list "server01 server 02 server 03"\fP
+.TP
+.B \fB\-\-[no\-]host\-key\-verify\fP
+Use \fB\-\-no\-host\-key\-verify\fP to disable host key verification. Default setting: \fB\-\-host\-key\-verify\fP.
+.TP
+.B \fBOTHER\fP
+The shell type. Possible values: \fBinteractive\fP, \fBscreen\fP, \fBtmux\fP, \fBmacterm\fP, or \fBcssh\fP. (\fBcsshx\fP is deprecated in favor of \fBcssh\fP.)
+.TP
+.B \fB\-p PORT\fP, \fB\-\-ssh\-port PORT\fP
+The SSH port.
+.TP
+.B \fB\-P PASSWORD\fP, \fB\-\-ssh\-password PASSWORD\fP
+The SSH password. This can be used to pass the password directly on the command line. If this option is not specified (and a password is required) Knife will prompt for the password.
+.TP
+.B \fBSEARCH_QUERY\fP
+The search query used to return a list of servers to be accessed using SSH and the specified \fBSSH_COMMAND\fP. This option uses the same syntax as the search sub\-command.
+.TP
+.B \fBSSH_COMMAND\fP
+The command that will be run against the results of a search query.
+.TP
+.B \fB\-x USER_NAME\fP, \fB\-\-ssh\-user USER_NAME\fP
+The SSH user name.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To find the uptime of all of web servers running Ubuntu on the Amazon EC2 platform, enter:
+.sp
+.nf
+.ft C
+$ knife ssh "role:web" "uptime" \-x ubuntu \-a ec2.public_hostname
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+ec2\-174\-129\-127\-206.compute\-1.amazonaws.com  13:50:47 up 1 day, 23:26,  1 user,  load average: 0.25, 0.18, 0.11
+ec2\-67\-202\-63\-102.compute\-1.amazonaws.com    13:50:47 up 1 day, 23:33,  1 user,  load average: 0.12, 0.13, 0.10
+ec2\-184\-73\-9\-250.compute\-1.amazonaws.com     13:50:48 up 16:45,  1 user,  load average: 0.30, 0.22, 0.13
+ec2\-75\-101\-240\-230.compute\-1.amazonaws.com   13:50:48 up 1 day, 22:59,  1 user,  load average: 0.24, 0.17, 0.11
+ec2\-184\-73\-60\-141.compute\-1.amazonaws.com    13:50:48 up 1 day, 23:30,  1 user,  load average: 0.32, 0.17, 0.15
+.ft P
+.fi
+.sp
+To run the chef\-client on all nodes, enter:
+.sp
+.nf
+.ft C
+$ knife ssh \(aqname:*\(aq \(aqsudo chef\-client\(aq
+.ft P
+.fi
+.sp
+To force a chef\-client run on all of the web servers running Ubuntu on the Amazon EC2 platform, enter:
+.sp
+.nf
+.ft C
+$ knife ssh "role:web" "sudo chef\-client" \-x ubuntu \-a ec2.public_hostname
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+ec2\-67\-202\-63\-102.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:37 +0000] INFO: Starting Chef Run (Version 0.9.10)
+ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:37 +0000] INFO: Starting Chef Run (Version 0.9.10)
+ec2\-184\-73\-9\-250.compute\-1.amazonaws.com    [Fri, 22 Oct 2010 14:18:38 +0000] INFO: Starting Chef Run (Version 0.9.10)
+ec2\-75\-101\-240\-230.compute\-1.amazonaws.com  [Fri, 22 Oct 2010 14:18:38 +0000] INFO: Starting Chef Run (Version 0.9.10)
+ec2\-184\-73\-60\-141.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:38 +0000] INFO: Starting Chef Run (Version 0.9.10)
+ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Chef Run complete in 1.419243 seconds
+ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: cleaning the checksum cache
+ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Running report handlers
+ec2\-174\-129\-127\-206.compute\-1.amazonaws.com [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Report handlers complete
+ec2\-67\-202\-63\-102.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Chef Run complete in 1.578265 seconds
+ec2\-67\-202\-63\-102.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:39 +0000] INFO: cleaning the checksum cache
+ec2\-67\-202\-63\-102.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Running report handlers
+ec2\-67\-202\-63\-102.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:39 +0000] INFO: Report handlers complete
+ec2\-184\-73\-9\-250.compute\-1.amazonaws.com    [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Chef Run complete in 1.638884 seconds
+ec2\-184\-73\-9\-250.compute\-1.amazonaws.com    [Fri, 22 Oct 2010 14:18:40 +0000] INFO: cleaning the checksum cache
+ec2\-184\-73\-9\-250.compute\-1.amazonaws.com    [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Running report handlers
+ec2\-184\-73\-9\-250.compute\-1.amazonaws.com    [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Report handlers complete
+ec2\-75\-101\-240\-230.compute\-1.amazonaws.com  [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Chef Run complete in 1.540257 seconds
+ec2\-75\-101\-240\-230.compute\-1.amazonaws.com  [Fri, 22 Oct 2010 14:18:40 +0000] INFO: cleaning the checksum cache
+ec2\-75\-101\-240\-230.compute\-1.amazonaws.com  [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Running report handlers
+ec2\-75\-101\-240\-230.compute\-1.amazonaws.com  [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Report handlers complete
+ec2\-184\-73\-60\-141.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Chef Run complete in 1.502489 seconds
+ec2\-184\-73\-60\-141.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:40 +0000] INFO: cleaning the checksum cache
+ec2\-184\-73\-60\-141.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Running report handlers
+ec2\-184\-73\-60\-141.compute\-1.amazonaws.com   [Fri, 22 Oct 2010 14:18:40 +0000] INFO: Report handlers complete
+.ft P
+.fi
+.sp
+To query for all nodes that have the "webserver" role and then use SSH to run the command "sudo chef\-client", enter:
+.sp
+.nf
+.ft C
+$ knife ssh "role:webserver" "sudo chef\-client"
+.ft P
+.fi
+.sp
+To upgrade all nodes, enter:
+.sp
+.nf
+.ft C
+$ knife ssh name:* "sudo aptitude upgrade \-y"
+.ft P
+.fi
+.sp
+To specify the shell type used on the nodes returned by a search query:
+.sp
+.nf
+.ft C
+$ knife ssh roles:opscode\-omnitruck macterm
+.ft P
+.fi
+.sp
+where \fBscreen\fP is one of the following values: \fBcssh\fP, \fBinteractive\fP, \fBmacterm\fP, \fBscreen\fP, or \fBtmux\fP. If the node does not have the shell type installed, Knife will return an error similar to the following:
+.sp
+.nf
+.ft C
+you need the rb\-appscript gem to use knife ssh macterm.
+\(ga(sudo) gem    install rb\-appscript\(ga to install
+ERROR: LoadError: cannot load such file \-\- appscript
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-status.1 b/distro/common/man/man1/knife-status.1
index fe5136f..0682fdc 100644
--- a/distro/common/man/man1/knife-status.1
+++ b/distro/common/man/man1/knife-status.1
@@ -1,29 +1,207 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.TH "KNIFE-STATUS" "1" "Chef 11.8.0" "" "knife status"
+.SH NAME
+knife-status \- The man page for the knife status subcommand.
 .
-.TH "KNIFE\-STATUS" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
+.nr rst2man-indent-level 0
 .
-.SH "NAME"
-\fBknife\-status\fR \- Display status information for the nodes in your infrastructure
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fBstatus\fR \fI(options)\fR
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
 .
+.sp
+The \fBknife status\fP subcommand is used to display a brief summary of the nodes on a server, including the time of the most recent successful chef\-client run.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
 .TP
-\fB\-r\fR, \fB\-\-run\-list RUN_LIST\fR
-Show the run list
-.
-.SH "DESCRIPTION"
-The \fIstatus\fR sub\-command searches the Chef Server for all nodes and displays information about the last time the node checked into the server and executed a \fBnode\.save\fR\. The fields displayed are the relative checkin time, the node name, it\'s operating system platform and version, the fully\-qualified domain name and the default IP address\. If the \fB\-r\fR option is given, the node\'s run list will also be displayed\. Note that depending on the configuration of the nodes, th [...]
-.
-.SH "SEE ALSO"
-\fBknife\-search\fR(1)
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife status (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fBQUERY\fP
+The search query used to identify a a list of items on a server. This option uses the same syntax as the \fBsearch\fP sub\-command.
+.TP
+.B \fB\-H\fP, \fB\-\-hide\-healthy\fP
+Indicates that nodes on which a chef\-client run has occurred within the previous hour will be hidden.
+.TP
+.B \fB\-r RUN_LIST\fP, \fB\-\-run\-list RUN_LIST\fP
+A comma\-separated list of roles and/or recipes to be applied.
+.TP
+.B \fB\-s\fP, \fB\-\-sort\-reverse\fP
+Indicates that the list will be sorted by last run time, descending.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To include run lists in the status, enter:
+.sp
+.nf
+.ft C
+$ knife status \-\-run\-list
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+20 hours ago, dev\-vm.chisamore.com, ubuntu 10.04, dev\-vm.chisamore.com, 10.66.44.126, role[lb].
+3 hours ago, i\-225f954f, ubuntu 10.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102, role[web].
+3 hours ago, i\-a45298c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 174.129.127.206, role[web].
+3 hours ago, i\-5272a43f, ubuntu 10.04, ec2\-184\-73\-9\-250.compute\-1.amazonaws.com, 184.73.9.250, role[web].
+3 hours ago, i\-226ca64f, ubuntu 10.04, ec2\-75\-101\-240\-230.compute\-1.amazonaws.com, 75.101.240.230, role[web].
+3 hours ago, i\-f65c969b, ubuntu 10.04, ec2\-184\-73\-60\-141.compute\-1.amazonaws.com, 184.73.60.141, role[web].
+.ft P
+.fi
+.sp
+To show the status for nodes on which the chef\-client did not run successfully within the past hour, enter:
+.sp
+.nf
+.ft C
+$ knife status \-\-hide\-healthy
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+1 hour ago, i\-256f884f, ubuntu 12.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102, role[web].
+1 hour ago, i\-a47823c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 184.129.143.111, role[lb].
+.ft P
+.fi
+.sp
+To show the status of a subset of nodes that are returned by a specific query, enter:
+.sp
+.nf
+.ft C
+$ knife status "role:web" \-\-run\-list
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+3 hours ago, i\-225f954f, ubuntu 10.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102, role[web].
+3 hours ago, i\-a45298c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 174.129.127.206, role[web].
+3 hours ago, i\-5272a43f, ubuntu 10.04, ec2\-184\-73\-9\-250.compute\-1.amazonaws.com, 184.73.9.250, role[web].
+3 hours ago, i\-226ca64f, ubuntu 10.04, ec2\-75\-101\-240\-230.compute\-1.amazonaws.com, 75.101.240.230, role[web].
+3 hours ago, i\-f65c969b, ubuntu 10.04, ec2\-184\-73\-60\-141.compute\-1.amazonaws.com, 184.73.60.141, role[web].
+.ft P
+.fi
+.sp
+To view the status of all nodes in the organization, enter:
+.sp
+.nf
+.ft C
+$ knife status
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+20 hours ago, dev\-vm.chisamore.com, ubuntu 10.04, dev\-vm.chisamore.com, 10.66.44.126
+3 hours ago, i\-225f954f, ubuntu 10.04, ec2\-67\-202\-63\-102.compute\-1.amazonaws.com, 67.202.63.102
+3 hours ago, i\-a45298c9, ubuntu 10.04, ec2\-174\-129\-127\-206.compute\-1.amazonaws.com, 174.129.127.206
+3 hours ago, i\-5272a43f, ubuntu 10.04, ec2\-184\-73\-9\-250.compute\-1.amazonaws.com, 184.73.9.250
+3 hours ago, i\-226ca64f, ubuntu 10.04, ec2\-75\-101\-240\-230.compute\-1.amazonaws.com, 75.101.240.230
+3 hours ago, i\-f65c969b, ubuntu 10.04, ec2\-184\-73\-60\-141.compute\-1.amazonaws.com, 184.73.60.141
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-tag.1 b/distro/common/man/man1/knife-tag.1
index 39e85fd..945912f 100644
--- a/distro/common/man/man1/knife-tag.1
+++ b/distro/common/man/man1/knife-tag.1
@@ -1,43 +1,180 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.TH "KNIFE-TAG" "1" "Chef 11.8.0" "" "knife tag"
+.SH NAME
+knife-tag \- The man page for the knife tag subcommand.
 .
-.TH "KNIFE\-TAG" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
+.nr rst2man-indent-level 0
 .
-.SH "NAME"
-\fBknife\-tag\fR \- Apply tags to nodes on a Chef Server
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
 .
-.SH "SYNOPSIS"
-\fBknife\fR \fBtag\fR \fIsubcommand\fR \fI(options)\fR
+.sp
+A tag is a custom description that is applied to a node. A tag, once applied, can be helpful when managing nodes using Knife or when building recipes by providing alternate methods of grouping similar types of information.
+.sp
+The \fBknife tag\fP subcommand is used to apply tags to nodes on a server.
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife tag [ARGUMENT]
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to add one or more tags to a node.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife tag create NODE_NAME [TAG...]
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To create tags named "seattle", "portland", and "vancouver", enter:
+.sp
+.nf
+.ft C
+$ knife tag create node seattle portland vancouver
+.ft P
+.fi
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete one or more tags from a node.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife tag delete NODE_NAME [TAG...]
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To delete tags named "denver" and "phoenix", enter:
+.sp
+.nf
+.ft C
+$ knife tag delete node denver phoenix
+.ft P
+.fi
+.sp
+Type \fBY\fP to confirm a deletion.
+.SH LIST
+.sp
+The \fBlist\fP argument is used to list all of the tags that have been applied to a node.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife tag list [NODE_NAME...]
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.SH "TAG SUBCOMMANDS"
-The following \fBtag\fR subcommands are available:
-.
-.SH "CREATE"
-\fBknife tag create\fR \fInode\fR \fItag\fR [\fI\.\.\.\fR]
-.
-.P
-Adds one or more tags to \fInode\fR
-.
-.SH "DELETE"
-\fBknife tag delete\fR \fInode\fR \fItag\fR [\fI\.\.\.\fR]
-.
-.P
-Removes one or more tags from \fInode\fR
-.
-.SH "LIST"
-\fBknife tag list\fR \fInode\fR
-.
-.P
-Lists the tags applied to \fInode\fR
-.
-.SH "SEE ALSO"
-\fBknife\-node(1)\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Daniel DeLeo \fIdan at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/knife-upload.1 b/distro/common/man/man1/knife-upload.1
new file mode 100644
index 0000000..e59f2f1
--- /dev/null
+++ b/distro/common/man/man1/knife-upload.1
@@ -0,0 +1,239 @@
+.TH "KNIFE-UPLOAD" "1" "Chef 11.8.0" "" "knife upload"
+.SH NAME
+knife-upload \- The man page for the knife upload subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife upload\fP subcommand is used to upload roles, cookbooks, environments, and data bags to the server from the current working directory in the chef\-repo. This subcommand is often used in conjunction with \fBknife diff\fP, which can be used to see exactly what changes will be uploaded, and then \fBknife download\fP, which does the opposite of \fBknife upload\fP.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife upload [PATTERN...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-\-[no\-]diff\fP
+Indicates that only new and modified files will be uploaded. Set to \fBfalse\fP to upload all files. Default: \fBtrue\fP.
+.TP
+.B \fB\-\-[no\-]force\fP
+Use \fB\-\-force\fP to upload roles, cookbooks, etc. even if the file in the directory is identical (by default, no \fBPOST\fP or \fBPUT\fP is performed unless an actual change would be made). Default: \fB\-\-no\-force\fP.
+.TP
+.B \fB\-\-[no\-]freeze\fP
+Indicates that a cookbook cannot be modified; any changes to this cookbook must be included as a new version. Only the \fB\-\-force\fP option can override this setting. Default: \fBfalse\fP.
+.TP
+.B \fB\-n\fP, \fB\-\-dry\-run\fP
+Indicates that no action is taken and that results are only printed out. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-[no\-]purge\fP
+Use \fB\-\-purge\fP to delete roles, cookbooks, etc. from the server if their corresponding files do not exist in the chef\-repo. By default, such objects are left alone and NOT purged. Default: \fB\-\-no\-purge\fP.
+.TP
+.B \fB\-\-[no\-]recurse\fP
+Use \fB\-\-no\-recurse\fP to disable uploading a directory recursively. Default: \fB\-\-recurse\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default: \fBeverything\fP / \fBhosted_everything\fP.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To upload the entire chef\-repo to the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife upload
+.ft P
+.fi
+.sp
+or from anywhere in the chef\-repo, enter:
+.sp
+.nf
+.ft C
+$ knife upload /
+.ft P
+.fi
+.sp
+To upload the \fBcookbooks/\fP directory to the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife upload cookbooks
+.ft P
+.fi
+.sp
+or from anywhere in the chef\-repo, enter:
+.sp
+.nf
+.ft C
+$ knife upload /cookbooks
+.ft P
+.fi
+.sp
+To upload the \fBenvironments/\fP directory to the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife upload environments
+.ft P
+.fi
+.sp
+or from anywhere in the chef\-repo, enter:
+.sp
+.nf
+.ft C
+$ knife upload /environments
+.ft P
+.fi
+.sp
+To upload an environment named "production" to the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife upload environments/production.json
+.ft P
+.fi
+.sp
+or from the \fBenvironments/\fP directory, enter:
+.sp
+.nf
+.ft C
+$ knife upload production.json
+.ft P
+.fi
+.sp
+To upload the \fBroles/\fP directory to the server, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife upload roles
+.ft P
+.fi
+.sp
+or from anywhere in the chef\-repo, enter:
+.sp
+.nf
+.ft C
+$ knife upload /roles
+.ft P
+.fi
+.sp
+To upload all cookbooks that start with "apache" and belong to the "webserver" role, browse to the top level of the chef\-repo and enter:
+.sp
+.nf
+.ft C
+$ knife upload cookbooks/apache\e* roles/webserver.json
+.ft P
+.fi
+.sp
+Use the output of \fBknife deps\fP to pass a command to \fBknife upload\fP. For example:
+.sp
+.nf
+.ft C
+$ knife upload \(gaknife deps nodes/*.json\(ga
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-user.1 b/distro/common/man/man1/knife-user.1
new file mode 100644
index 0000000..b8fe458
--- /dev/null
+++ b/distro/common/man/man1/knife-user.1
@@ -0,0 +1,317 @@
+.TH "KNIFE-USER" "1" "Chef 11.8.0" "" "knife user"
+.SH NAME
+knife-user \- The man page for the knife user subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife user\fP subcommand is used to manage the list of users and their associated RSA public key\-pairs.
+.IP Note
+This subcommand ONLY works when run against the open source server and will not run against Enterprise Chef (including hosted Enterprise Chef), or Private Chef.
+.RE
+.sp
+This subcommand has the following syntax:
+.sp
+.nf
+.ft C
+$ knife user [ARGUMENT] (options)
+.ft P
+.fi
+.SH COMMON OPTIONS
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.SH CREATE
+.sp
+The \fBcreate\fP argument is used to create a user. This process will generate an RSA key pair for the named user. The public key will be stored on the server and the private key will be displayed on \fBSTDOUT\fP or written to a named file.
+.INDENT 0.0
+.IP \(bu 2
+For the user, the private key should be copied to the system as /etc/chef/client.pem.
+.IP \(bu 2
+For Knife, the private key is typically copied to ~/.chef/client_name.pem and referenced in the knife.rb configuration file.
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife user create USER_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a\fP, \fB\-\-admin\fP
+Indicates that a client will be created as an admin client. This is required when users of the open source server need to access the Chef Server API as an administrator. This option only works when used with the open source server and will have no effect when used with Hosted Chef or Private Chef.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-user\-key FILE_NAME\fP
+All users are assigned a public key. Use to write the public key to a file.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To create a new user named "Radio Birdman" with a private key saved to "/keys/user_name", enter:
+.sp
+.nf
+.ft C
+$ knife user create "Radio Birdman" \-f /keys/user_name
+.ft P
+.fi
+.SH DELETE
+.sp
+The \fBdelete\fP argument is used to delete a registered user.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife user delete USER_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+To delete a user named "Steve Danno", enter:
+.sp
+.nf
+.ft C
+$ knife user delete "Steve Danno"
+.ft P
+.fi
+.SH EDIT
+.sp
+The \fBedit\fP argument is used to edit the details of a user. When this argument is run, Knife will open $EDITOR. When finished, Knife will update the server with those changes.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife user edit USER_NAME
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This command does not have any specific options.
+.sp
+\fBExamples\fP
+.sp
+None.
+.SH LIST
+.sp
+The \fBlist\fP argument is used to view a list of registered users.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife user list (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-w\fP, \fB\-\-with\-uri\fP
+Indicates that the corresponding URIs will be shown.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+None.
+.SH REREGISTER
+.sp
+The \fBreregister\fP argument is used to regenerate an RSA key pair for a user. The public key will be stored on the server and the private key will be displayed on \fBSTDOUT\fP or written to a named file.
+.IP Note
+Running this argument will invalidate the previous RSA key pair, making it unusable during authentication to the server.
+.RE
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife user reregister USER_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To regenerate the RSA key pair for a user named "Robert Younger", enter:
+.sp
+.nf
+.ft C
+$ knife user reregister "Robert Younger"
+.ft P
+.fi
+.SH SHOW
+.sp
+The \fBshow\fP argument is used to show the details of a user.
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife user show USER_NAME (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This argument has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-a ATTR\fP, \fB\-\-attribute ATTR\fP
+The attribute (or attributes) to show.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To view a user named "Dennis Teck", enter:
+.sp
+.nf
+.ft C
+$ knife user show "Dennis Teck"
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+chef_type:   user
+json_class:  Chef::User
+name:        Dennis Teck
+public_key:
+.ft P
+.fi
+.sp
+To view information in JSON format, use the \fB\-F\fP common option as part of the command like this:
+.sp
+.nf
+.ft C
+$ knife user show "Dennis Teck" \-F json
+.ft P
+.fi
+.sp
+Other formats available include \fBtext\fP, \fByaml\fP, and \fBpp\fP.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife-xargs.1 b/distro/common/man/man1/knife-xargs.1
new file mode 100644
index 0000000..23e74ea
--- /dev/null
+++ b/distro/common/man/man1/knife-xargs.1
@@ -0,0 +1,166 @@
+.TH "KNIFE-XARGS" "1" "Chef 11.8.0" "" "knife xargs"
+.SH NAME
+knife-xargs \- The man page for the knife xargs subcommand.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+The \fBknife xargs\fP subcommand is used to build and execute command lines against objects on a server using standard input.
+.sp
+\fBCommon Options\fP
+.sp
+The following options can be run with all Knife sub\-commands and plug\-ins:
+.INDENT 0.0
+.TP
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
+.TP
+.B \fB\-\-color\fP
+Indicates that colored output will be used.
+.TP
+.B \fB\-d\fP, \fB\-\-disable\-editing\fP
+Indicates that $EDITOR will not be opened; data will be accepted as\-is.
+.TP
+.B \fB\-\-defaults\fP
+Indicates that Knife will use the default value, instead of asking a user to provide one.
+.TP
+.B \fB\-e EDITOR\fP, \fB\-\-editor EDITOR\fP
+The $EDITOR that is used for all interactive commands.
+.TP
+.B \fB\-E ENVIRONMENT\fP, \fB\-\-environment ENVIRONMENT\fP
+The name of the environment. When this option is added to a command, the command will run only against the named environment.
+.TP
+.B \fB\-f FILE_NAME\fP, \fB\-\-file FILE_NAME\fP
+Indicates that the private key will be saved to a specified file name.
+.TP
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
+.TP
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
+.TP
+.B \fB\-k KEY\fP, \fB\-\-key KEY\fP
+The private key that Knife will use to sign requests made by the API client to the server.
+.TP
+.B \fB\-\-no\-color\fP
+Indicates that color will not be used in the output.
+.TP
+.B \fB\-p PASSWORD\fP, \fB\-\-password PASSWORD\fP
+The user password.
+.TP
+.B \fB\-\-print\-after\fP
+Indicates that data will be shown after a destructive operation.
+.TP
+.B \fB\-s URL\fP, \fB\-\-server\-url URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user name used by Knife to sign requests made by the API client to the server. Authentication will fail if the user name does not match the private key.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-V\fP, \fB\-\-verbose\fP
+Set for more verbose outputs. Use \fB\-VV\fP for maximum verbosity.
+.TP
+.B \fB\-y\fP, \fB\-\-yes\fP
+Indicates that the response to all confirmation prompts will be "Yes" (and that Knife will not ask for confirmation).
+.UNINDENT
+.sp
+\fBSyntax\fP
+.sp
+This argument has the following syntax:
+.sp
+.nf
+.ft C
+$ knife xargs [PATTERN...] (options)
+.ft P
+.fi
+.sp
+\fBOptions\fP
+.sp
+This subcommand has the following options:
+.INDENT 0.0
+.TP
+.B \fB\-0\fP
+Indicates that a \fBNULL\fP character (\fB\e0\fP) will be used as a separator, instead of white space. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-chef\-repo\-path PATH\fP
+The path to the chef\-repo. This setting will override the default path to the chef\-repo. Default: same as specified by \fBchef_repo_path\fP in config.rb.
+.TP
+.B \fB\-\-concurrency\fP
+The number of allowed concurrent connections. Default: \fB10\fP.
+.TP
+.B \fB\-\-[no\-]diff\fP
+Use to show a diff when a file changes. Default: \fB\-\-diff\fP.
+.TP
+.B \fB\-\-dry\-run\fP
+Use to prevent changes from being uploaded to the server. Default: \fBfalse\fP.
+.TP
+.B \fB\-\-[no\-]force\fP
+Use to force the upload of files even if they haven\(aqt been changed. Default: \fB\-\-no\-force\fP.
+.TP
+.B \fB\-\-local\fP
+Indicates that a command line will be built or executed against a local file. Set to \fBfalse\fP to build or execute against a remote file. Default: \fBfalse\fP.
+.TP
+.B \fB\-n MAX_ARGS\fP, \fB\-\-max\-args MAX_ARGS\fP
+The maximum number of arguments per command line. Default: \fBnil\fP.
+.TP
+.B \fB\-s LENGTH\fP, \fB\-\-max\-chars LENGTH\fP
+The maximum size (in characters) for a command line. Default: \fBnil\fP.
+.TP
+.B \fB\-p [PATTERN...]\fP, \fB\-\-pattern [PATTERN...]\fP
+One (or more) patterns for a command line. If this option is not specified, a list of patterns may be expected on standard input. Default: \fBnil\fP.
+.TP
+.B \fB\-I REPLACE_STRING\fP, \fB\-\-replace REPLACE_STRING\fP
+Use to define a string that will be used to replace all occurrences of a file name. Default: \fBnil\fP.
+.TP
+.B \fB\-J REPLACE_STRING\fP, \fB\-\-replace\-first REPLACE_STRING\fP
+Use to define a string that will be used to replace the first occurrence of a file name. Default: \fBnil\fP.
+.TP
+.B \fB\-\-repo\-mode MODE\fP
+The layout of the local chef\-repo. Possible values: \fBstatic\fP, \fBeverything\fP, or \fBhosted_everything\fP. Use \fBstatic\fP for just roles, environments, cookbooks, and data bags. By default, \fBeverything\fP and \fBhosted_everything\fP are dynamically selected depending on the server type. Default value: \fBdefault\fP.
+.TP
+.B \fB\-t\fP
+Indicates that the print command will be run on the command line. Default: \fBnil\fP.
+.UNINDENT
+.sp
+\fBExamples\fP
+.sp
+To use the output of \fBknife deps\fP to pass a command to \fBknife xargs\fP. For example:
+.sp
+.nf
+.ft C
+$ knife deps nodes/*.json | xargs knife upload
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
+.
diff --git a/distro/common/man/man1/knife.1 b/distro/common/man/man1/knife.1
index ef0944c..7e20d8a 100644
--- a/distro/common/man/man1/knife.1
+++ b/distro/common/man/man1/knife.1
@@ -1,288 +1,228 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.TH "KNIFE" "1" "Chef 11.8.0" "" "knife"
+.SH NAME
+knife \- The man page for the knife command line tool.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+Knife is a command\-line tool that provides an interface between a local chef\-repo and the server. Knife helps users to manage:
+.INDENT 0.0
+.IP \(bu 2
+Nodes
+.IP \(bu 2
+Cookbooks and recipes
+.IP \(bu 2
+Roles
+.IP \(bu 2
+Stores of JSON data (data bags), including encrypted data
+.IP \(bu 2
+Environments
+.IP \(bu 2
+Cloud resources, including provisioning
+.IP \(bu 2
+The installation of the chef\-client on management workstations
+.IP \(bu 2
+Searching of indexed data on the server
+.UNINDENT
+.sp
+Knife subcommands:
+.INDENT 0.0
+.IP \(bu 2
+knife bootstrap
+.IP \(bu 2
+knife client
+.IP \(bu 2
+knife configure
+.IP \(bu 2
+knife cookbook
+.IP \(bu 2
+knife cookbook site
+.IP \(bu 2
+knife data bag
+.IP \(bu 2
+knife delete
+.IP \(bu 2
+knife deps
+.IP \(bu 2
+knife diff
+.IP \(bu 2
+knife download
+.IP \(bu 2
+knife edit
+.IP \(bu 2
+knife environment
+.IP \(bu 2
+knife exec
+.IP \(bu 2
+knife list
+.IP \(bu 2
+knife node
+.IP \(bu 2
+knife raw
+.IP \(bu 2
+knife recipe list
+.IP \(bu 2
+knife role
+.IP \(bu 2
+knife search
+.IP \(bu 2
+knife show
+.IP \(bu 2
+knife ssh
+.IP \(bu 2
+knife status
+.IP \(bu 2
+knife tag
+.IP \(bu 2
+knife upload
+.IP \(bu 2
+knife user
+.IP \(bu 2
+knife xargs
+.UNINDENT
+.SH WORKING WITH KNIFE
+.sp
+Knife runs from a management workstation and sits in\-between a server and an organization\(aqs infrastructure. Knife interacts with a server by using the same REST API that is used by a chef\-client. Role\-based authentication controls (RBAC) can be used to authorize changes when Knife is run with Hosted Chef or Private Chef. Knife is configured during workstation setup, but subsequent modifications can be made using the knife.rb configuration file.
+.SS JSON Data Format
+.sp
+Most data is entered using a text editor in JSON format, unless the \fB\-\-disable\-editing\fP option is entered as part of a command. (Encrypted data bags use YAML, which is a superset of JSON.) JSON is a common, language\-independent data format that provides a simple text representation of arbitrary data structures. For more information about JSON, see \fI\%http://www.json.org/\fP or \fI\%http://en.wikipedia.org/wiki/JSON\fP.
+.SS Set the Text Editor
+.sp
+Some Knife commands, such as \fBknife data bag edit\fP, require that information be edited as JSON data using a text editor. For example, the following command:
+.sp
+.nf
+.ft C
+$ knife data bag edit admins admin_name
+.ft P
+.fi
+.sp
+will open up the text editor with data similar to:
+.sp
+.nf
+.ft C
+{
+  "id": "admin_name"
+}
+.ft P
+.fi
+.sp
+Changes to that file can then be made:
+.sp
+.nf
+.ft C
+{
+  "id": "Justin C."
+  "description": "I am passing the time by letting time pass over me ..."
+}
+.ft P
+.fi
+.sp
+The type of text editor that is used by Knife can be configured by adding an entry to the knife.rb file or by setting an \fBEDITOR\fP environment variable. For example, to configure the text editor to always open with vim, add the following to the knife.rb file:
+.sp
+.nf
+.ft C
+knife[:editor] = "/usr/bin/vim"
+.ft P
+.fi
+.sp
+When a Microsoft Windows file path is enclosed in a double\-quoted string (" "), the same backslash character (\fB\e\fP) that is used to define the file path separator is also used in Ruby to define an escape character. The knife.rb file is a Ruby file; therefore, file path separators must be escaped. In addition, spaces in the file path must be replaced with \fB~1\fP so that the length of each section within the file path is not more than 8 characters. For example, if EditPad Pro is the [...]
+.sp
+.nf
+.ft C
+C:\e\eProgram Files (x86)\eEditPad Pro\eEditPad.exe
+.ft P
+.fi
+.sp
+the setting in the knife.rb file would be similar to:
+.sp
+.nf
+.ft C
+knife[:editor] = "C:\e\eProgra~1\e\eEditPa~1\e\eEditPad.exe"
+.ft P
+.fi
+.sp
+One approach to working around the double\- vs. single\-quote issue is to put the single\-quotes outside of the double\-quotes. For example, for Notepad++:
+.sp
+.nf
+.ft C
+knife[:editor] = \(aq"C:\eProgram Files (x86)\eNotepad++\enotepad++.exe \-nosession \-multiInst"\(aq
+.ft P
+.fi
+.sp
+for Sublime Text:
+.sp
+.nf
+.ft C
+knife[:editor] = \(aq"C:\eProgram Files\eSublime Text 2\esublime_text.exe \-\-wait"\(aq
+.ft P
+.fi
+.sp
+for TextPad:
+.sp
+.nf
+.ft C
+knife[:editor] = \(aq"C:\eProgram Files (x86)\eTextPad 7\eTextPad.exe"\(aq
+.ft P
+.fi
+.sp
+and for vim:
+.sp
+.nf
+.ft C
+knife[:editor] = \(aq"C:\eProgram Files (x86)\evim\evim74\egvim.exe"\(aq
+.ft P
+.fi
+.SS Using Quotes
+.sp
+Values can be entered with double quotes (" ") or single quotes (\(aq \(aq), but this should be done consistently.
+.SS Sub\-commands
+.sp
+Knife comes with a collection of built in subcommands that work together to provide all of the functionality required to take specific actions against any object in an organization, including cookbooks, nodes, roles, data bags, environments, and users. A Knife plugin extends the functionality beyond built\-in subcommands.
+.sp
+Knife has the following subcommands: \fBbootstrap\fP, \fBclient\fP, \fBconfigure\fP, \fBcookbook\fP, \fBcookbook site\fP, \fBdata bag\fP, \fBdelete\fP, \fBdeps\fP, \fBdiff\fP, \fBdownload\fP, \fBedit\fP, \fBenvironment\fP, \fBexec\fP, \fBindex rebuild\fP, \fBlist\fP, \fBnode\fP, \fBrecipe list\fP, \fBrole\fP, \fBsearch\fP, \fBshow\fP, \fBssh\fP, \fBstatus\fP, \fBtag\fP, \fBupload\fP, \fBuser\fP, and \fBxargs\fP.
+.IP Note
+The following subcommands run only against the open source server: \fBindex rebuild\fP and \fBuser\fP.
+.RE
+.SS Syntax
+.sp
+All Knife subcommands have the following syntax:
+.INDENT 0.0
+.INDENT 3.5
+knife subcommand [ARGUMENT] (options)
+.UNINDENT
+.UNINDENT
+.sp
+Each subcommand has its own set of arguments and options.
+.IP Note
+All syntax examples in this document show variables in ALL_CAPS. For example \fB\-u PORT_LIST\fP (where PORT_LIST is a comma\-separated list of local and public UDP ports) or \fB\-F FORMAT\fP (where FORMAT determines the output format, either \fBsummary\fP, \fBtext\fP, \fBjson\fP, \fByaml\fP, or \fBpp\fP). These variables often require specific values that are unique to each organization.
+.RE
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.TH "KNIFE" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBknife\fR \- Chef Server API client utility
-.
-.SH "SYNOPSIS"
-\fBknife\fR \fIsub\-command\fR [\fIargument\fR\.\.\.] \fI(options)\fR
-.
-.SH "DESCRIPTION"
-Knife is a command\-line utility used to manage data on a Chef server through the HTTP(S) API\. Knife is organized into groups of subcommands centered around the various object types in Chef\. Each category of subcommand is documented in its own manual page\. Available topics are:
-.
-.IP "\(bu" 4
-bootstrap
-.
-.IP "\(bu" 4
-client
-.
-.IP "\(bu" 4
-configure
-.
-.IP "\(bu" 4
-cookbook\-site
-.
-.IP "\(bu" 4
-cookbook
-.
-.IP "\(bu" 4
-data\-bag
-.
-.IP "\(bu" 4
-environment
-.
-.IP "\(bu" 4
-exec
-.
-.IP "\(bu" 4
-index
-.
-.IP "\(bu" 4
-node
-.
-.IP "\(bu" 4
-recipe
-.
-.IP "\(bu" 4
-role
-.
-.IP "\(bu" 4
-search
-.
-.IP "\(bu" 4
-ssh
-.
-.IP "\(bu" 4
-status
-.
-.IP "\(bu" 4
-tag
-.
-.IP "" 0
-.
-.P
-If the knife manuals are in your \fBMANPATH\fR, you can access help for the above topics using \fBman knife\-TOPIC\fR; otherwise, you can view the documentation using \fBknife help TOPIC\fR\.
-.
-.SH "OPTIONS"
-.
-.TP
-\fB\-s\fR, \fB\-\-server\-url\fR URL
-Chef Server URL, corresponds to \fBChef::Config\fR \fBchef_server_url\fR\.
-.
-.TP
-\fB\-k\fR, \fB\-\-key\fR KEY
-API Client Key, corresponds to \fBChef::Config\fR \fBclient_key\fR\.
-.
-.TP
-\fB\-c\fR, \fB\-\-config\fR CONFIG
-The configuration file to use
-.
-.TP
-\fB\-E\fR, \fB\-\-environment ENVIRONMENT\fR
-Set the Chef environment
-.
-.TP
-\fB\-e\fR, \fB\-\-editor\fR EDITOR
-Set the editor to use for interactive commands
-.
-.TP
-\fB\-F\fR, \fB\-\-format\fR FORMAT
-Which format to use for output\. See FORMATS for details\.
-.
-.TP
-\fB\-V\fR, \fB\-\-verbose\fR
-More verbose output\. Use twice for max verbosity
-.
-.TP
-\fB\-d\fR, \fB\-\-disable\-editing\fR
-Do not open EDITOR, just accept the data as is
-.
-.TP
-\fB\-u\fR, \fB\-\-user\fR USER
-API Client Username, corresponds to \fBChef::Config\fR \fBnode_name\fR\.
-.
-.TP
-\fB\-p\fR, \fB\-\-print\-after\fR
-Show the data after a destructive operation
-.
-.TP
-\fB\-v\fR, \fB\-\-version\fR
-Show chef version
-.
-.TP
-\fB\-y\fR, \fB\-\-yes\fR
-Say yes to all prompts for confirmation
-.
-.TP
-\fB\-\-defaults\fR
-Accept default values for all questions
-.
-.TP
-\fB\-\-[no\-]color\fR
-Use colored output\. Color enabled by default\.
-.
-.TP
-\fB\-h\fR, \fB\-\-help\fR
-Show the available options for a command\.
-.
-.SH "SUB\-COMMANDS"
-Sub\-commands that operate on the basic Chef data types are structured as \fINOUN verb NOUN (options)\fR\. For all data types, the following commands are available:
-.
-.IP "\(bu" 4
-create (create)
-.
-.IP "\(bu" 4
-list and show (read)
-.
-.IP "\(bu" 4
-edit (update)
-.
-.IP "\(bu" 4
-delete (destroy)
-.
-.IP "" 0
-.
-.P
-Knife also includes commands that take actions other than displaying or modifying data on the Chef Server, such as \fBknife\-ssh(1)\fR\.
-.
-.SH "CONFIGURATION"
-The knife configuration file is a Ruby DSL to set configuration parameters for Knife\'s \fBGENERAL OPTIONS\fR\. The default location for the config file is \fB~/\.chef/knife\.rb\fR\. If managing multiple Chef repositories, per\-repository config files can be created\. The file must be \fB\.chef/knife\.rb\fR in the current directory of the repository\.
-.
-.P
-If the config file exists, knife uses these settings for \fBGENERAL OPTIONS\fR defaults\.
-.
-.IP "\(bu" 4
-\fBnode_name\fR: User or client identity (i\.e\., \fIname\fR) to use for authenticating requests to the Chef Server\.
-.
-.IP "\(bu" 4
-\fBclient_key\fR: Private key file to authenticate to the Chef server\. Corresponds to the \fB\-k\fR or \fB\-\-key\fR option\.
-.
-.IP "\(bu" 4
-\fBchef_server_url\fR: URL of the Chef server\. Corresponds to the \fB\-s\fR or \fB\-\-server\-url\fR option\. This is requested from the user when running this sub\-command\.
-.
-.IP "\(bu" 4
-\fBcache_type\fR: The type of cache to use\. Default is BasicFile\. This can be any type of Cache that moneta supports: BasicFile, Berkeley, Couch, DataMapper, File, LMC, Memcache, Memory, MongoDB, Redis, Rufus, S3, SDBM, Tyrant, Xattr, YAML\.
-.
-.IP "\(bu" 4
-\fBcache_options\fR: Specifies various options to use for caching\. These options are dependent on the \fBcache_type\fR\.
-.
-.IP "\(bu" 4
-\fBvalidation_client_name\fR: Specifies the name of the client used to validate new clients\.
-.
-.IP "\(bu" 4
-\fBvalidation_key\fR: Specifies the private key file to use when bootstrapping new hosts\. See knife\-client(1) for more information about the validation client\.
-.
-.IP "\(bu" 4
-\fBcookbook_copyright\fR, \fBcookbook_email\fR, \fBcookbook_license\fR, \fBreadme_format\fR Used by \fBknife cookbook create\fR sub\-command to specify the copyright holder, maintainer email, license and readme format (respectively) for new cookbooks\. The copyright holder is listed as the maintainer in the cookbook\'s metadata and as the Copyright in the comments of the default recipe\. The maintainer email is used in the cookbook metadata\. The license determines what preamble to put i [...]
-.
-.IP "" 0
-.
-.SH "FILES"
-\fI~/\.chef/knife\.rb\fR
-.
-.P
-Ruby DSL configuration file for knife\. See \fBCONFIGURATION\fR\.
-.
-.SH "FORMATS"
-The amount of content displayed and the output format are modified by the \fB\-\-format\fR option\. If no alternate format is selected, the default is summary\.
-.
-.P
-Valid formats are:
-.
-.TP
-\fBsummary\fR
-displays the node in a custom, summarized format (default)
-.
-.TP
-\fBtext\fR
-displays the node data in its entirety using the colorized tree display
-.
-.TP
-\fBjson\fR
-displays the node in JSON format
-.
-.TP
-\fByaml\fR
-displays the node in YAML format
-.
-.TP
-\fBpp\fR
-displays the node using Ruby\'s pretty printer\.
-.
-.P
-For brevity, only the first character of the format is required, for example, \-Fj will produce JSON format output\.
-.
-.SH "CHEF WORKFLOW"
-When working with Chef and Knife in the local repository, the recommended workflow outline looks like:
-.
-.IP "\(bu" 4
-Create repository\. A skeleton sample is provided at \fIhttp://github\.com/opscode/chef\-repo/\fR\.
-.
-.IP "\(bu" 4
-Configure knife, see \fBCONFIGURATION\fR\.
-.
-.IP "\(bu" 4
-Download cookbooks from the Opscode cookbooks site, see \fBCOOKBOOK SITE SUB\-COMMANDS\fR\.
-.
-.IP "\(bu" 4
-Or, create new cookbooks, see \fBcookbook create\fR sub\-command\.
-.
-.IP "\(bu" 4
-Commit changes to the version control system\. See your tool\'s documentation\.
-.
-.IP "\(bu" 4
-Upload cookbooks to the Chef Server, see \fBCOOKBOOK SUB\-COMMANDS\fR\.
-.
-.IP "\(bu" 4
-Launch instances in the Cloud, OR provision new hosts; see \fBCLOUD COMPUTING SUB\-COMMANDS\fR and \fBBOOTSTRAP SUB\-COMMANDS\fR\.
-.
-.IP "\(bu" 4
-Watch Chef configure systems!
-.
-.IP "" 0
-.
-.P
-A note about git: Opscode and many folks in the Chef community use git, but it is not required, except in the case of the \fBcookbook site vendor\fR sub\-command, as it uses git directly\. Version control is strongly recommended though, and git fits with a lot of the workflow paradigms\.
-.
-.SH "EXAMPLES"
-.
-.SH "ENVIRONMENT"
-.
-.TP
-\fBEDITOR\fR
-The text editor to use for editing data\. The \-\-editor option takes precedence over this value, and the \-\-disable\-editing option supresses data editing entirely\.
-.
-.SH "SEE ALSO"
-\fBchef\-client(8)\fR \fBchef\-server(8)\fR \fBshef(1)\fR
-.
-.P
-\fBknife\-bootstrap(1)\fR \fBknife\-client(1)\fR \fBknife\-configure(1)\fR \fBknife\-cookbook\-site(1)\fR \fBknife\-cookbook(1)\fR \fBknife\-data\-bag(1)\fR \fBknife\-environment(1)\fR \fBknife\-exec(1)\fR \fBknife\-index(1)\fR \fBknife\-node(1)\fR \fBknife\-recipe(1)\fR \fBknife\-role(1)\fR \fBknife\-search(1)\fR \fBknife\-ssh(1)\fR \fBknife\-tag(1)\fR
-.
-.P
-Complete Chef documentation is available online: \fIhttp://wiki\.opscode\.com/display/chef/Home/\fR
-.
-.P
-JSON is JavaScript Object Notation \fIhttp://json\.org/\fR
-.
-.P
-SOLR is an open source search engine\. \fIhttp://lucene\.apache\.org/solr/\fR
-.
-.P
-\fBgit(1)\fR is a version control system \fIhttp://git\-scm\.com/\fR
-.
-.P
-This manual page was generated from Markdown with \fBronn(1)\fR \fIhttp://rtomayko\.github\.com/ronn/ronn\.1\.html\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR of Opscode (\fIhttp://www\.opscode\.com\fR), with contributions from the community\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR\.
-.
-.SH "LICENSE"
-Both Chef and this documentation are released under the terms of the Apache 2\.0 License\. You may view the license online: \fIhttp://www\.apache\.org/licenses/LICENSE\-2\.0\.html\fR On some systems, the complete text of the Apache 2\.0 License may be found in \fB/usr/share/common\-licenses/Apache\-2\.0\fR\.
-.
-.SH "CHEF"
-Knife is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man1/shef.1 b/distro/common/man/man1/shef.1
deleted file mode 100644
index 4618835..0000000
--- a/distro/common/man/man1/shef.1
+++ /dev/null
@@ -1,256 +0,0 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "SHEF" "1" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBshef\fR \- Interactive Chef Console
-.
-.SH "SYNOPSIS"
-\fBshef\fR [\fInamed configuration\fR] \fI(options)\fR
-.
-.TP
-\fB\-S\fR, \fB\-\-server CHEF_SERVER_URL\fR
-The chef server URL
-.
-.TP
-\fB\-z\fR, \fB\-\-client\fR
-chef\-client mode
-.
-.TP
-\fB\-c\fR, \fB\-\-config CONFIG\fR
-The configuration file to use
-.
-.TP
-\fB\-j\fR, \fB\-\-json\-attributes JSON_ATTRIBS\fR
-Load attributes from a JSON file or URL
-.
-.TP
-\fB\-l\fR, \fB\-\-log\-level LOG_LEVEL\fR
-Set the logging level
-.
-.TP
-\fB\-s\fR, \fB\-\-solo\fR
-chef\-solo shef session
-.
-.TP
-\fB\-a\fR, \fB\-\-standalone\fR
-standalone shef session
-.
-.TP
-\fB\-v\fR, \fB\-\-version\fR
-Show chef version
-.
-.TP
-\fB\-h\fR, \fB\-\-help\fR
-Show command options
-.
-.P
-When no \-\-config option is specified, shef attempts to load a default configuration file:
-.
-.IP "\(bu" 4
-If a \fInamed configuration\fR is given, shef will load ~/\.chef/\fInamed configuration\fR/shef\.rb
-.
-.IP "\(bu" 4
-If no \fInamed configuration\fR is given shef will load ~/\.chef/shef\.rb if it exists
-.
-.IP "\(bu" 4
-Shef falls back to loading /etc/chef/client\.rb or /etc/chef/solo\.rb if \-z or \-s options are given and no shef\.rb can be found\.
-.
-.IP "\(bu" 4
-The \-\-config option takes precedence over implicit configuration paths\.
-.
-.IP "" 0
-.
-.SH "DESCRIPTION"
-\fBshef\fR is an irb(1) (interactive ruby) session customized for Chef\. \fBshef\fR serves two primary functions: it provides a means to interact with a Chef Server interactively using a convenient DSL; it allows you to define and run Chef recipes interactively\.
-.
-.SH "SYNTAX"
-Shef uses irb\'s subsession feature to provide multiple modes of interaction\. In addition to the primary mode which is entered on start, \fBrecipe\fR and \fBattributes\fR modes are available\.
-.
-.SH "PRIMARY MODE"
-The following commands are available in the primary session:
-.
-.TP
-\fBhelp\fR
-Prints a list of available commands
-.
-.TP
-\fBversion\fR
-Prints the Chef version
-.
-.TP
-\fBrecipe\fR
-Switches to \fBrecipe\fR mode
-.
-.TP
-\fBattributes\fR
-Switches to \fBattributes\fR mode
-.
-.TP
-\fBrun_chef\fR
-Initiates a chef run
-.
-.TP
-\fBreset\fR
-reinitializes shef
-.
-.TP
-\fBecho :on|:off\fR
-Turns irb\'s echo function on or off\. Echo is \fIon\fR by default\.
-.
-.TP
-\fBtracing :on|:off\fR
-Turns irb\'s function tracing feature on or off\. Tracing is extremely verbose and expected to be of interest primarily to developers\.
-.
-.TP
-\fBnode\fR
-Returns the \fInode\fR object for the current host\. See knife\-node(1) for more information about nodes\.
-.
-.TP
-\fBohai\fR
-Prints the attributes of \fInode\fR
-.
-.P
-In addition to these commands, shef provides a DSL for accessing data on the Chef Server\. When working with remote data in shef, you chain method calls in the form \fIobject type\fR\.\fIoperation\fR, where \fIobject type\fR is in plural form\. The following object types are available:
-.
-.IP "\(bu" 4
-\fBnodes\fR
-.
-.IP "\(bu" 4
-\fBroles\fR
-.
-.IP "\(bu" 4
-\fBdata_bags\fR
-.
-.IP "\(bu" 4
-\fBclients\fR
-.
-.IP "\(bu" 4
-\fBcookbooks\fR
-.
-.IP "" 0
-.
-.P
-For each \fIobject type\fR the following operations are available:
-.
-.TP
-\fIobject type\fR\.all(\fI&block\fR)
-Loads all items from the server\. If the optional code \fIblock\fR is given, each item will be passed to the block and the results returned, similar to ruby\'s \fBEnumerable#map\fR method\.
-.
-.TP
-\fIobject type\fR\.show(\fIobject name\fR)
-Aliased as \fIobject type\fR\.load
-.
-.IP
-Loads the singular item identified by \fIobject name\fR\.
-.
-.TP
-\fIobject type\fR\.search(\fIquery\fR, \fI&block\fR)
-Aliased as \fIobject type\fR\.find
-.
-.IP
-Runs a search against the server and returns the matching items\. If the optional code \fIblock\fR is given each item will be passed to the block and the results returned\.
-.
-.IP
-The \fIquery\fR may be a Solr/Lucene format query given as a String, or a Hash of conditions\. If a Hash is given, the options will be ANDed together\. To join conditions with OR, use negative queries, or any advanced search syntax, you must provide give the query in String form\.
-.
-.TP
-\fIobject type\fR\.transform(:all|\fIquery\fR, \fI&block\fR)
-Aliased as \fIobject type\fR\.bulk_edit
-.
-.IP
-Bulk edit objects by processing them with the (required) code \fIblock\fR\. You can edit all objects of the given type by passing the Symbol \fB:all\fR as the argument, or only a subset by passing a \fIquery\fR as the argument\. The \fIquery\fR is evaluated in the same way as with \fBsearch\fR\.
-.
-.IP
-The return value of the code \fIblock\fR is used to alter the behavior of \fBtransform\fR\. If the value returned from the block is \fBnil\fR or \fBfalse\fR, the object will not be saved\. Otherwise, the object is saved after being passed to the block\. This behavior can be exploited to create a dry run to test a data transformation\.
-.
-.SH "RECIPE MODE"
-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:
-.
-.IP "\(bu" 4
-\fIhttp://wiki\.opscode\.com/display/chef/Resources\fR
-.
-.IP "\(bu" 4
-\fIhttp://wiki\.opscode\.com/display/chef/Recipes\fR
-.
-.IP "" 0
-.
-.P
-Once you have defined resources in the recipe, you can trigger a convergence run via \fBrun_chef\fR
-.
-.SH "EXAMPLES"
-.
-.IP "\(bu" 4
-A "Hello World" interactive recipe
-.
-.IP
-chef > recipe
-.
-.br
-chef:recipe > echo :off
-.
-.br
-chef:recipe > file "/tmp/hello_world"
-.
-.br
-chef:recipe > run_chef
-.
-.br
-[Sat, 09 Apr 2011 08:56:56 \-0700] INFO: Processing file[/tmp/hello_world] action create ((irb#1) line 2)
-.
-.br
-[Sat, 09 Apr 2011 08:56:56 \-0700] INFO: file[/tmp/hello_world] created file /tmp/hello_world
-.
-.br
-chef:recipe > pp ls \'/tmp\'
-.
-.br
-["\.",
-.
-.br
-"\.\.",
-.
-.br
-"hello_world"]
-.
-.IP "\(bu" 4
-Search for \fInodes\fR by role, and print their IP addresses
-.
-.IP
-chef > nodes\.find(:roles => \'monitoring\-server\') {|n| n[:ipaddress] }
-.
-.br
-=> ["10\.254\.199\.5"]
-.
-.IP "\(bu" 4
-Remove the role \fIobsolete\fR from every node in the system
-.
-.IP
-chef > nodes\.transform(:all) {|n| n\.run_list\.delete(\'role[obsolete]\') }
-.
-.br
-=> [node[chef098b2\.opschef\.com], node[ree\-woot], node[graphite\-dev], node[fluke\.localdomain], node[ghost\.local], node[kallistec]]
-.
-.IP "" 0
-.
-.SH "BUGS"
-The name \fBshef\fR is clever in print but is confusing when spoken aloud\. Pronouncing \fBshef\fR as \fBchef console\fR is an imperfect workaround\.
-.
-.P
-\fBshef\fR often does not perfectly replicate the context in which chef\-client(8) configures a host, which may lead to discrepancies in observed behavior\.
-.
-.P
-\fBshef\fR has to duplicate much code from chef\-client\'s internal libraries and may become out of sync with the behavior of those libraries\.
-.
-.SH "SEE ALSO"
-chef\-client(8) knife(1) \fIhttp://wiki\.opscode\.com/display/chef/Shef\fR
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at opscode\.com\fR with many contributions from the community\. Shef was written by Daniel DeLeo\.
-.
-.SH "DOCUMENTATION"
-This manual page was written by Daniel DeLeo \fIdan at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.SH "CHEF"
-Shef is distributed with Chef\. \fIhttp://wiki\.opscode\.com/display/chef/Home\fR
diff --git a/distro/common/man/man8/chef-client.8 b/distro/common/man/man8/chef-client.8
index e6749ba..c4df48d 100644
--- a/distro/common/man/man8/chef-client.8
+++ b/distro/common/man/man8/chef-client.8
@@ -1,108 +1,273 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.TH "CHEF-CLIENT" "8" "Chef 11.8.0" "" "chef-client"
+.SH NAME
+chef-client \- The man page for the chef-client command line tool.
 .
-.TH "CHEF\-CLIENT" "8" "June 2012" "Chef 10.12.0" "Chef Manual"
+.nr rst2man-indent-level 0
 .
-.SH "NAME"
-\fBchef\-client\fR \- Runs a client node connecting to a chef\-server\.
-.
-.SH "SYNOPSIS"
-\fBchef\-client\fR \fI(options)\fR
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
 .
+.sp
+A chef\-client is an agent that runs locally on every node that is registered with the server. When a chef\-client is run, it will perform all of the steps that are required to bring the node into the expected state, including:
+.INDENT 0.0
+.IP \(bu 2
+Registering and authenticating the node with the server
+.IP \(bu 2
+Building the node object
+.IP \(bu 2
+Synchronizing cookbooks
+.IP \(bu 2
+Compiling the resource collection by loading each of the required cookbooks, including recipes, attributes, and all other dependencies
+.IP \(bu 2
+Taking the appropriate and required actions to configure the node
+.IP \(bu 2
+Looking for exceptions and notifications, handling each as required
+.UNINDENT
+.sp
+The chef\-client executable can be run as a command\-line tool.
+.IP Note
+A client.rb file is used to specify the configuration details for the chef\-client. This file is the default configuration file and is loaded every time the chef\-client executable is run. The chef\-client executable can be run as a daemon. On UNIX\- and Linux\-based machines, the configuration file is located at: /etc/chef/client.rb. On Microsoft Windows machines, the configuration file is located at C:chefclient.rb.
+.RE
+.SH OPTIONS
+.sp
+This command has the following syntax:
+.sp
+.nf
+.ft C
+chef\-client OPTION VALUE OPTION VALUE ...
+.ft P
+.fi
+.sp
+This command has the following options:
+.INDENT 0.0
 .TP
-\fB\-S\fR, \fB\-\-server CHEFSERVERURL\fR
-The chef server URL
-.
+.B \fB\-A\fP, \fB\-\-fatal\-windows\-admin\-check\fP
+Indicates that a chef\-client run should fail if the chef\-client does not have administrator privileges in Microsoft Windows.
 .TP
-\fB\-c\fR, \fB\-\-config CONFIG\fR
-The configuration file to use
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-d\fR, \fB\-\-daemonize\fR
-Daemonize the process
-.
+.B \fB\-d\fP, \fB\-\-daemonize\fP
+Indicates that the executable will be run as a daemon. This option is only available on machines that run in UNIX or Linux environments. For machines that are running Microsoft Windows that require similar functionality, use the \fBchef\-client::service\fP recipe in the \fBchef\-client\fP cookbook: \fI\%http://community.opscode.com/cookbooks/chef-client\fP. This will install a chef\-client service under Microsoft Windows using the Windows Service Wrapper.
 .TP
-\fB\-g\fR, \fB\-\-group GROUP\fR
-Group to set privilege to
-.
+.B \fB\-E ENVIRONMENT_NAME\fP, \fB\-\-environment ENVIRONMENT_NAME\fP
+The name of the environment.
 .TP
-\fB\-i\fR, \fB\-\-interval SECONDS\fR
-Run chef\-client periodically, in seconds
-.
+.B \fB\-f\fP, \fB\-\-fork\fP
+Indicates that a chef\-client run will be contained in a secondary process with dedicated RAM. When the chef\-client run is complete the RAM will be returned to the master process. This option helps ensure that a chef\-client will use a steady amount of RAM over time because the master process will not run recipes. This option will also help prevent memory leaks (such as those that can be introduced by the code contained within a poorly designed cookbook). Use \fB\-\-no\-fork\fP to disab [...]
 .TP
-\fB\-j\fR, \fB\-\-json\-attributes JSON_ATTRIBS\fR
-Load attributes from a JSON file or URL
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fB\-E\fR, \fB\-\-environment ENVIRONMENT\fR
-Set the Chef Environment on the node
-.
+.B \fB\-\-force\-formatter\fP
+Indicates that formatter output will be used instead of logger output.
 .TP
-\fB\-l\fR, \fB\-\-log_level LEVEL\fR
-Set the log level (debug, info, warn, error, fatal)
-.
+.B \fB\-\-force\-logger\fP
+Indicates that logger output will be used instead of formatter output.
 .TP
-\fB\-L\fR, \fB\-\-logfile LOGLOCATION\fR
-Set the log file location, defaults to STDOUT \- recommended for daemonizing
-.
+.B \fB\-g GROUP\fP, \fB\-\-group GROUP\fP
+The name of the group that owns a process. This is required when starting any executable as a daemon.
 .TP
-\fB\-V\fR, \fB\-\-verbose\fR
-Ensures logging goes to STDOUT as well as to other configured log location(s)\.
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
 .TP
-\fB\-N\fR, \fB\-\-node\-name NODE_NAME\fR
-The node name for this client
-.
+.B \fB\-i SECONDS\fP, \fB\-\-interval SECONDS\fP
+The frequency (in seconds) at which the chef\-client runs. This value is configured for the chef\-client application run time, rather than in \fBChef::Config\fP. Default value: \fB1800\fP.
 .TP
-\fB\-o\fR, \fB\-\-override\-runlist\fR
-Replace current run list with specified items
-.
+.B \fB\-j PATH\fP, \fB\-\-json\-attributes PATH\fP
+The path to a file that contains JSON data. Use this option to override attributes that are set from other locations, such as from within a cookbook or by a role.
 .TP
-\fB\-K\fR, \fB\-\-validation_key KEY_FILE\fR
-Set the validation key file location, used for registering new clients
-.
+.B \fB\-k KEY_FILE\fP, \fB\-\-client_key KEY_FILE\fP
+The location of the file which contains the client key. Default value: \fB/etc/chef/client.pem\fP.
 .TP
-\fB\-k\fR, \fB\-\-client_key KEY_FILE\fR
-Set the client key file location
-.
+.B \fB\-K KEY_FILE\fP, \fB\-\-validation_key KEY_FILE\fP
+The location of the file which contains the key used when a chef\-client is registered with a server. A validation key is signed using the \fBvalidation_client_name\fP for authentication. Default value: \fB/etc/chef/validation.pem\fP.
 .TP
-\fB\-s\fR, \fB\-\-splay SECONDS\fR
-The splay time for running at intervals, in seconds
-.
+.B \fB\-l LEVEL\fP, \fB\-\-log_level LEVEL\fP
+The level of logging that will be stored in a log file: \fBdebug\fP, \fBinfo\fP, \fBwarn\fP, \fBerror\fP, or \fBfatal\fP.
 .TP
-\fB\-u\fR, \fB\-\-user USER\fR
-User to set privilege to
-.
+.B \fB\-L LOGLOCATION\fP, \fB\-\-logfile c\fP
+The location in which log file output files will be saved. If this location is set to something other than \fBSTDOUT\fP, standard output logging will still be performed (otherwise there would be no output other than to a file). This is recommended when starting any executable as a daemon. Default value: \fBSTDOUT\fP.
 .TP
-\fB\-P\fR, \fB\-\-pid PIDFILE\fR
-Set the PID file location, defaults to /tmp/chef\-client\.pid
-.
+.B \fB\-\-[no\-]color\fP
+Indicates that color will not be used in the output. Default setting: \fB\-\-color\fP.
 .TP
-\fB\-\-once\fR
-Cancel any interval or splay options, run chef once and exit
-.
+.B \fB\-N NODE_NAME\fP, \fB\-\-node\-name NODE_NAME\fP
+The name of the node.
 .TP
-\fB\-v\fR, \fB\-\-version\fR
-Show chef version
-.
+.B \fB\-o RUN_LIST_ITEM\fP, \fB\-\-override\-runlist RUN_LIST_ITEM\fP
+Replace the current run list with the specified items.
 .TP
-\fB\-h\fR, \fB\-\-help\fR
-Show this message
-.
-.SH "DESCRIPTION"
-The Chef Client is where almost all of the work in Chef is done\. It communicates with the Chef Server via REST, authenticates via Signed Header Authentication, and compiles and executes Cookbooks\.
-.
-.P
-A Chef Client does work on behalf of a Node\. A single Chef Client can run recipes for multiple Nodes\.
-.
-.P
-Clients are where all the action happens \- the Chef Server and Chef Expander are largely services that exist only to provide the Client with information\.
-.
-.SH "SEE ALSO"
-Full documentation for Chef and chef\-client is located on the Chef wiki, http://wiki\.opscode\.com/display/chef/Home\.
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at ospcode\.com\fR of Opscode (http://www\.opscode\.com), with contributions from the community\. This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR with help2man\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
+.B \fB\-\-once\fP
+Indicates that the chef\-client is run once and that interval and splay options are cancelled.
+.TP
+.B \fB\-P PID_FILE\fP, \fB\-\-pid PID_FILE\fP
+The location in which a process identification number (pid) is saved. An executable, when started as a daemon, will write the pid to the specified file. Default value: \fB/tmp/name\-of\-executable.pid\fP.
+.TP
+.B \fB\-R\fP, \fB\-\-enable\-reporting\fP
+Indicates that data collection reporting is enabled during a chef\-client run.
+.TP
+.B \fB\-s SECONDS\fP, \fB\-\-splay SECONDS\fP
+A number (in seconds) to add to the \fBinterval\fP that is used to determine the frequency of chef\-client runs. This number can help prevent server load when there are many clients running at the same time.
+.TP
+.B \fB\-S CHEF_SERVER_URL\fP, \fB\-\-server CHEF_SERVER_URL\fP
+The URL for the server.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user that owns a process. This is required when starting any executable as a daemon.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-W\fP, \fB\-\-why\-run\fP
+Indicates that the executable will be run in why\-run mode, which is a type of chef\-client run that does everything except modify the system. Use why\-run mode to understand why the chef\-client makes the decisions that it makes and to learn more about the current and proposed state of the system.
+.TP
+.B \fB\-z\fP, \fB\-\-local\-mode\fP
+Indicates that the chef\-client will be run in local mode, which allows all commands that work against the server to also work against the local chef\-repo.
+.UNINDENT
+.SH RUN WITH ELEVATED PRIVILEGES
+.sp
+The chef\-client may need to be run with elevated privileges in order to get a recipe to converge correctly. On UNIX and UNIX\-like operating systems this can be done by running the command as root. On Microsoft Windows this can be done by running the command prompt as an administrator.
+.SS Linux
+.sp
+On Linux, the following error sometimes occurs when the permissions used to run the chef\-client are incorrect:
+.sp
+.nf
+.ft C
+$ chef\-client
+[Tue, 29 Nov 2011 19:46:17 \-0800] INFO: *** Chef 10.X.X ***
+[Tue, 29 Nov 2011 19:46:18 \-0800] WARN: Failed to read the private key /etc/chef/client.pem: #<Errno::EACCES: Permission denied \- /etc/chef/client.pem>
+.ft P
+.fi
+.sp
+This can be resolved by running the command as root. There are a few ways this can be done:
+.INDENT 0.0
+.IP \(bu 2
+Log in as root and then run the chef\-client
+.IP \(bu 2
+Use \fBsu\fP to become the root user, and then run the chef\-client. For example:
+.INDENT 2.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+$ su
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.UNINDENT
+.sp
+and then:
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+$ chef\-client
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.INDENT 0.0
+.IP \(bu 2
+Use the sudo utility
+.INDENT 2.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+$ sudo chef\-client
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.IP \(bu 2
+Give a user access to read \fB/etc/chef\fP and also the files accessed by the chef\-client. This requires super user privileges and, as such, is not a recommended approach
+.UNINDENT
+.SS Windows
+.sp
+On Microsoft Windows, running without elevated privileges (when they are necessary) is an issue that fails silently. It will appear that the chef\-client completed its run successfully, but the changes will not have been made. When this occurs, do one of the following to run the chef\-client as the administrator:
+.INDENT 0.0
+.IP \(bu 2
+Log in to the administrator account. (This is not the same as an account in the administrator\(aqs security group.)
+.IP \(bu 2
+Run the chef\-client process from the administrator account while being logged into another account. Run the following command:
+.INDENT 2.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+$ runas /user:Administrator "cmd /C chef\-client"
+.ft P
+.fi
+.sp
+This will prompt for the administrator account password.
+.UNINDENT
+.UNINDENT
+.IP \(bu 2
+Open a command prompt by right\-clicking on the command prompt application, and then selecting \fBRun as administrator\fP. After the command window opens, the chef\-client can be run as the administrator
+.UNINDENT
+.SH EXAMPLES
+.sp
+\fBStart a Chef run when the chef\-client is running as a daemon\fP
+.sp
+A chef\-client that is running as a daemon can be woken up and started by sending the process a \fBSIGUSR1\fP. For example, to trigger a chef\-client run on a machine running Linux:
+.sp
+.nf
+.ft C
+$ sudo killall \-USR1 chef\-client
+.ft P
+.fi
+.sp
+\fBStart a Chef run manually\fP
+.sp
+.nf
+.ft C
+$ ps auxw|grep chef\-client
+.ft P
+.fi
+.sp
+to return something like:
+.sp
+.nf
+.ft C
+root           66066   0.9  0.0  2488880    264 s001  S+   10:26AM   0:03.05
+/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby /usr/bin/chef\-client \-i 3600 \-s 20
+.ft P
+.fi
+.sp
+and then enter:
+.sp
+.nf
+.ft C
+$ sudo kill \-USR1 66066
+.ft P
+.fi
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.P
-On Debian systems, the complete text of the Apache 2\.0 License can be found in /usr/share/common\-licenses/Apache\-2\.0\.
diff --git a/distro/common/man/man8/chef-expander.8 b/distro/common/man/man8/chef-expander.8
deleted file mode 100644
index 8531f68..0000000
--- a/distro/common/man/man8/chef-expander.8
+++ /dev/null
@@ -1,97 +0,0 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "CHEF\-EXPANDER" "8" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBchef\-expander\fR \- fetches messages from RabbitMQ, processes, and loads into chef\-solr
-.
-.SH "SYNOPSIS"
-\fBchef\-expander\fR \fI(options)\fR
-.
-.TP
-\fB\-c\fR, \fB\-\-config CONFIG_FILE\fR
-a configuration file to use
-.
-.TP
-\fB\-i\fR, \fB\-\-index INDEX\fR
-the slot this node will occupy in the ring
-.
-.TP
-\fB\-n\fR, \fB\-\-node\-count NUMBER\fR
-the number of nodes in the ring
-.
-.TP
-\fB\-l\fR, \fB\-\-log\-level LOG_LEVEL\fR
-set the log level
-.
-.TP
-\fB\-L\fR, \fB\-\-logfile LOG_LOCATION\fR
-Logfile to use
-.
-.TP
-\fB\-d\fR, \fB\-\-daemonize\fR
-fork into the background
-.
-.TP
-\fB\-P\fR, \fB\-\-pid PIDFILE\fR
-PID file
-.
-.TP
-\fB\-h\fR, \fB\-\-help\fR
-show help message
-.
-.TP
-\fB\-v\fR, \fB\-\-version\fR
-show the version and exit
-.
-.SH "DESCRIPTION"
-Chef Expander fetches messages from RabbitMQ, processes them into the correct format to be loaded into Solr and loads them into Solr\.
-.
-.P
-\fBRunning Chef Expander\fR
-.
-.P
-Chef Expander is designed for clustered operation, though small installations will only need one worker process\. To run Chef Expander with one worker process, run chef\-expander \-n 1\. You will then have a master and worker process, which looks like this in ps:
-.
-.IP "" 4
-.
-.nf
-
-your\-shell> ps aux|grep expander
-you   52110   0\.1  0\.7  2515476  62748 s003  S+    3:49PM   0:00\.80 chef\-expander worker #1 (vnodes 0\-1023)
-you   52108   0\.1  0\.5  2492880  41696 s003  S+    3:49PM   0:00\.91 ruby bin/chef\-expander \-n 1
-.
-.fi
-.
-.IP "" 0
-.
-.P
-Workers are single threaded and therefore cannot use more than 100% of a single CPU\. If you find that your queues are getting backlogged, increase the number of workers
-.
-.P
-\fBDesign\fR
-.
-.P
-Chef Expander uses 1024 queues (called vnodes in some places) to allow you to scale the number of Chef Expander workers to meet the needs of your infrastructure\. When objects are saved in the API server, they are added to queues based on their database IDs\. These queues can be assigned to different Chef Expander workers to distribute the load of processing the index updates\.
-.
-.P
-\fBChef Expander Operation and Troubleshooting\fR
-.
-.P
-Chef Expander includes chef\-expanderctl, a management program that allows you to get status information or change the logging verbosity (without restarting)\.
-.
-.P
-See \fBchef\-expanderctl\fR(8) for details\.
-.
-.SH "SEE ALSO"
-\fBchef\-expanderctl\fR(8) \fBchef\-solr\fR(8)
-.
-.P
-Full documentation for Chef and chef\-server is located on the Chef wiki, http://wiki\.opscode\.com/display/chef/Home\.
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at ospcode\.com\fR of Opscode (http://www\.opscode\.com), with contributions from the community\. This manual page was created by Nuo Yan \fInuo at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.P
-On Debian systems, the complete text of the Apache 2\.0 License can be found in /usr/share/common\-licenses/Apache\-2\.0\.
diff --git a/distro/common/man/man8/chef-expanderctl.8 b/distro/common/man/man8/chef-expanderctl.8
deleted file mode 100644
index e6f4e26..0000000
--- a/distro/common/man/man8/chef-expanderctl.8
+++ /dev/null
@@ -1,62 +0,0 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "CHEF\-EXPANDERCTL" "8" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBchef\-expanderctl\fR \- management program for chef\-expander
-.
-.SH "SYNOPSIS"
-\fBchef\-expanderctl\fR \fICOMMAND\fR
-.
-.P
-\fBCommands:\fR
-.
-.TP
-\fBhelp\fR
-Show help message
-.
-.TP
-\fBqueue\-depth\fR
-display the aggregate queue backlog
-.
-.TP
-\fBqueue\-status\fR
-show the backlog and consumer count for each vnode queue
-.
-.TP
-\fBnode\-status\fR
-show the status of the nodes in the cluster
-.
-.TP
-\fBlog\-level\fR
-sets the log level of all nodes in the cluster
-.
-.SH "DESCRIPTION"
-Chef\-expanderctl is a management program that allows you to get status information or change the logging verbosity (without restarting)\. chef\-expanderctl has the following commands:
-.
-.IP "\(bu" 4
-\fBchef\-expanderctl help\fR prints usage\.
-.
-.IP "\(bu" 4
-\fBchef\-expanderctl queue\-depth\fR Shows the total number of messages in the queues\.
-.
-.IP "\(bu" 4
-\fBchef\-expanderctl queue\-status\fR Show the number of messages in each queue\. This is mainly of use when debugging a Chef Expander cluster\.
-.
-.IP "\(bu" 4
-\fBchef\-expanderctl log\-level LEVEL\fR Sets the log level on a running Chef Expander or cluster\. If you suspect that a worker process is stuck, as long as you are using clustered operation, you can simply kill the worker process and it will be restarted by the master process\.
-.
-.IP "" 0
-.
-.SH "SEE ALSO"
-\fBchef\-expander\-cluster\fR(8) \fBchef\-solr\fR(8)
-.
-.P
-Full documentation for Chef and chef\-server is located on the Chef wiki, http://wiki\.opscode\.com/display/chef/Home\.
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at ospcode\.com\fR of Opscode (http://www\.opscode\.com), with contributions from the community\. This manual page was created by Nuo Yan \fInuo at opscode\.com\fR\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.P
-On Debian systems, the complete text of the Apache 2\.0 License can be found in /usr/share/common\-licenses/Apache\-2\.0\.
diff --git a/distro/common/man/man8/chef-server-webui.8 b/distro/common/man/man8/chef-server-webui.8
deleted file mode 100644
index d41e85a..0000000
--- a/distro/common/man/man8/chef-server-webui.8
+++ /dev/null
@@ -1,155 +0,0 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "CHEF\-SERVER\-WEBUI" "8" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBchef\-server\-webui\fR \- Start the Chef Server merb application slice providing Web User Interface (Management Console)\.
-.
-.SH "SYNOPSIS"
-\fBchef\-server\-webui\fR \fI(options)\fR
-.
-.TP
-\fB\-u\fR, \fB\-\-user USER\fR
-This flag is for having chef\-server\-webui run as a user other than the one currently logged in\. Note: if you set this you must also provide a \-\-group option for it to take effect\.
-.
-.TP
-\fB\-G\fR, \fB\-\-group GROUP\fR
-This flag is for having chef\-server\-webui run as a group other than the one currently logged in\. Note: if you set this you must also provide a \-\-user option for it to take effect\.
-.
-.TP
-\fB\-d\fR, \fB\-\-daemonize\fR
-This will run a single chef\-server\-webui in the background\.
-.
-.TP
-\fB\-N\fR, \fB\-\-no\-daemonize\fR
-This will allow you to run a cluster in console mode\.
-.
-.TP
-\fB\-c\fR, \fB\-\-cluster\-nodes NUM_MERBS\fR
-Number of merb daemons to run for chef\-server\-webui\.
-.
-.TP
-\fB\-I\fR, \fB\-\-init\-file FILE\fR
-File to use for initialization on load, defaults to config/init\.rb\.
-.
-.TP
-\fB\-p\fR, \fB\-\-port PORTNUM\fR
-Port to run chef\-server\-webui on, defaults to 4040\. Additional nodes (\-c) listen on incrementing port numbers\.
-.
-.TP
-\fB\-o\fR, \fB\-\-socket\-file FILE\fR
-Socket file to run chef\-server\-webui on, defaults to [Merb\.root]/log/merb\.sock\. This is for web servers, like thin, that use sockets\. Specify this \fIonly\fR if you \fImust\fR\.
-.
-.TP
-\fB\-s\fR, \fB\-\-socket SOCKNUM\fR
-Socket number to run chef\-server\-webui on, defaults to 0\.
-.
-.TP
-\fB\-n\fR, \fB\-\-name NAME\fR
-Set the name of the application\. This is used in the process title and log file names\.
-.
-.TP
-\fB\-P\fR, \fB\-\-pid PIDFILE\fR
-PID file, defaults to [Merb\.root]/log/merb\.main\.pid for the master process and[Merb\.root]/log/merb\.[port number]\.pid for worker processes\. For clusters, use %s to specify where in the file chef\-server\-webui should place the port number\. For instance: \-P myapp\.%s\.pid\.
-.
-.TP
-\fB\-h\fR, \fB\-\-host HOSTNAME\fR
-Host to bind to (default is 0\.0\.0\.0)\.
-.
-.TP
-\fB\-m\fR, \fB\-\-merb\-root PATH_TO_APP_ROOT\fR
-The path to the Merb\.root for the app you want to run (default is current working directory)\.
-.
-.TP
-\fB\-a\fR, \fB\-\-adapter ADAPTER\fR
-The rack adapter to use to run chef\-server\-webui (default is mongrel) [mongrel, emongrel, thin, ebb, fastcgi, webrick]\.
-.
-.TP
-\fB\-R\fR, \fB\-\-rackup FILE\fR
-Load an alternate Rack config file (default is config/rack\.rb)\.
-.
-.TP
-\fB\-i\fR, \fB\-\-irb\-console\fR
-This flag will start chef\-server\-webui in irb console mode\. All your models and other classes will be available for you in an irb session\.
-.
-.TP
-\fB\-S\fR, \fB\-\-sandbox\fR
-This flag will enable a sandboxed irb console\. If your ORM supports transactions, all edits will be rolled back on exit\.
-.
-.TP
-\fB\-l\fR, \fB\-\-log\-level LEVEL\fR
-Log levels can be set to any of these options: debug < info < warn < error < fatal (default is info)\.
-.
-.TP
-\fB\-L\fR, \fB\-\-log LOGFILE\fR
-A string representing the logfile to use\. Defaults to [Merb\.root]/log/merb\.[main]\.log for the master process and [Merb\.root]/log/merb[port number]\.logfor worker processes\.
-.
-.TP
-\fB\-e\fR, \fB\-\-environment STRING\fR
-Environment to run Merb under [development, production, testing] (default is development)\.
-.
-.TP
-\fB\-r\fR, \fB\-\-script\-runner [\'RUBY CODE\'| FULL_SCRIPT_PATH]\fR
-Command\-line option to run scripts and/or code in the chef\-server\-webui app\.
-.
-.TP
-\fB\-K\fR, \fB\-graceful PORT or all\fR
-Gracefully kill chef\-server\-webui proceses by port number\. Use chef\-server \-K all to gracefully kill all merbs\.
-.
-.TP
-\fB\-k\fR, \fB\-\-kill PORT\fR
-Force kill one merb worker by port number\. This will cause the worker to be respawned\.
-.
-.TP
-\fB\-\-fast\-deploy\fR
-Reload the code, but not yourinit\.rb or gems\.
-.
-.TP
-\fB\-X\fR, \fB\-\-mutex on/off\fR
-This flag is for turning the mutex lock on and off\.
-.
-.TP
-\fB\-D\fR, \fB\-\-debugger\fR
-Run chef\-server\-webui using rDebug\.
-.
-.TP
-\fB\-V\fR, \fB\-\-verbose\fR
-Print extra information\.
-.
-.TP
-\fB\-C\fR, \fB\-\-console\-trap\fR
-Enter an irb console on ^C\.
-.
-.TP
-\fB\-?\fR, \fB\-H\fR, \fB\-\-help\fR
-Show this help message\.
-.
-.SH "DESCRIPTION"
-The Chef Server WebUI (Management Console) is a Merb application slice\. The default listen port is 4040\.
-.
-.P
-The Management Console is Chef Server\'s web interface\. Nodes, roles, cookbooks, data bags, and API clients can be managed through the Management Console\. Search can also be done on the console\.
-.
-.P
-In order to start using the Management Console, you need to first create a user or change the default password on the "admin" user\.
-.
-.P
-The default credentials are:
-.
-.IP "\(bu" 4
-\fBUsername\fR: admin
-.
-.IP "\(bu" 4
-\fBPassword\fR: p at ssw0rd1
-.
-.IP "" 0
-.
-.SH "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\.
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at ospcode\.com\fR of Opscode (http://www\.opscode\.com), with contributions from the community\. This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR with help2man for the Debian project (but may be used by others)\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.P
-On Debian systems, the complete text of the Apache 2\.0 License can be found in /usr/share/common\-licenses/Apache\-2\.0\.
diff --git a/distro/common/man/man8/chef-server.8 b/distro/common/man/man8/chef-server.8
deleted file mode 100644
index 4d26ec8..0000000
--- a/distro/common/man/man8/chef-server.8
+++ /dev/null
@@ -1,147 +0,0 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "CHEF\-SERVER" "8" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBchef\-server\fR \- Start the Chef Server merb application slice\.
-.
-.SH "SYNOPSIS"
-\fBchef\-server\fR \fI(options)\fR
-.
-.TP
-\fB\-u\fR, \fB\-\-user USER\fR
-This flag is for having chef\-server\-webui run as a user other than the one currently logged in\. Note: if you set this you must also provide a \-\-group option for it to take effect\.
-.
-.TP
-\fB\-G\fR, \fB\-\-group GROUP\fR
-This flag is for having chef\-server\-webui run as a group other than the one currently logged in\. Note: if you set this you must also provide a \-\-user option for it to take effect\.
-.
-.TP
-\fB\-d\fR, \fB\-\-daemonize\fR
-This will run a single chef\-server\-webui in the background\.
-.
-.TP
-\fB\-N\fR, \fB\-\-no\-daemonize\fR
-This will allow you to run a cluster in console mode\.
-.
-.TP
-\fB\-c\fR, \fB\-\-cluster\-nodes NUM_MERBS\fR
-Number of merb daemons to run for chef\-server\-webui\.
-.
-.TP
-\fB\-I\fR, \fB\-\-init\-file FILE\fR
-File to use for initialization on load, defaults to config/init\.rb\.
-.
-.TP
-\fB\-p\fR, \fB\-\-port PORTNUM\fR
-Port to run chef\-server\-webui on, defaults to 4040\. Additional nodes (\-c) listen on incrementing port numbers\.
-.
-.TP
-\fB\-o\fR, \fB\-\-socket\-file FILE\fR
-Socket file to run chef\-server\-webui on, defaults to [Merb\.root]/log/merb\.sock\. This is for web servers, like thin, that use sockets\. Specify this \fIonly\fR if you \fImust\fR\.
-.
-.TP
-\fB\-s\fR, \fB\-\-socket SOCKNUM\fR
-Socket number to run chef\-server\-webui on, defaults to 0\.
-.
-.TP
-\fB\-n\fR, \fB\-\-name NAME\fR
-Set the name of the application\. This is used in the process title and log file names\.
-.
-.TP
-\fB\-P\fR, \fB\-\-pid PIDFILE\fR
-PID file, defaults to [Merb\.root]/log/merb\.main\.pid for the master process and[Merb\.root]/log/merb\.[port number]\.pid for worker processes\. For clusters, use %s to specify where in the file chef\-server\-webui should place the port number\. For instance: \-P myapp\.%s\.pid\.
-.
-.TP
-\fB\-h\fR, \fB\-\-host HOSTNAME\fR
-Host to bind to (default is 0\.0\.0\.0)\.
-.
-.TP
-\fB\-m\fR, \fB\-\-merb\-root PATH_TO_APP_ROOT\fR
-The path to the Merb\.root for the app you want to run (default is current working directory)\.
-.
-.TP
-\fB\-a\fR, \fB\-\-adapter ADAPTER\fR
-The rack adapter to use to run chef\-server\-webui (default is mongrel) [mongrel, emongrel, thin, ebb, fastcgi, webrick]\.
-.
-.TP
-\fB\-R\fR, \fB\-\-rackup FILE\fR
-Load an alternate Rack config file (default is config/rack\.rb)\.
-.
-.TP
-\fB\-i\fR, \fB\-\-irb\-console\fR
-This flag will start chef\-server\-webui in irb console mode\. All your models and other classes will be available for you in an irb session\.
-.
-.TP
-\fB\-S\fR, \fB\-\-sandbox\fR
-This flag will enable a sandboxed irb console\. If your ORM supports transactions, all edits will be rolled back on exit\.
-.
-.TP
-\fB\-l\fR, \fB\-\-log\-level LEVEL\fR
-Log levels can be set to any of these options: debug < info < warn < error < fatal (default is info)\.
-.
-.TP
-\fB\-L\fR, \fB\-\-log LOGFILE\fR
-A string representing the logfile to use\. Defaults to [Merb\.root]/log/merb\.[main]\.log for the master process and [Merb\.root]/log/merb[port number]\.logfor worker processes\.
-.
-.TP
-\fB\-e\fR, \fB\-\-environment STRING\fR
-Environment to run Merb under [development, production, testing] (default is development)\.
-.
-.TP
-\fB\-r\fR, \fB\-\-script\-runner [\'RUBY CODE\'| FULL_SCRIPT_PATH]\fR
-Command\-line option to run scripts and/or code in the chef\-server\-webui app\.
-.
-.TP
-\fB\-K\fR, \fB\-graceful PORT or all\fR
-Gracefully kill chef\-server\-webui proceses by port number\. Use chef\-server \-K all to gracefully kill all merbs\.
-.
-.TP
-\fB\-k\fR, \fB\-\-kill PORT\fR
-Force kill one merb worker by port number\. This will cause the worker to be respawned\.
-.
-.TP
-\fB\-\-fast\-deploy\fR
-Reload the code, but not yourinit\.rb or gems\.
-.
-.TP
-\fB\-X\fR, \fB\-\-mutex on/off\fR
-This flag is for turning the mutex lock on and off\.
-.
-.TP
-\fB\-D\fR, \fB\-\-debugger\fR
-Run chef\-server\-webui using rDebug\.
-.
-.TP
-\fB\-V\fR, \fB\-\-verbose\fR
-Print extra information\.
-.
-.TP
-\fB\-C\fR, \fB\-\-console\-trap\fR
-Enter an irb console on ^C\.
-.
-.TP
-\fB\-?\fR, \fB\-H\fR, \fB\-\-help\fR
-Show this help message\.
-.
-.SH "DESCRIPTION"
-The Chef Server provides a central point for the distribution of Cookbooks, management and authentication of Nodes, and the use of Search\. It provides a REST API\.
-.
-.P
-The API service is what clients use to interact with the server to manage node configuration in Chef\. By default, the service is started on port 4000 as a Merb application slice running with the thin server adapter\.
-.
-.P
-The two methods of interaction with the API for humans are the command\-line tool Knife and the Management Console\. The Chef Client library is used for interacting with the API for client nodes\.
-.
-.SH "SEE ALSO"
-\fBchef\-client\fR(8) \fBchef\-server\-webui\fR(8) \fBknife\fR(1)
-.
-.P
-Full documentation for Chef and chef\-server is located on the Chef wiki, http://wiki\.opscode\.com/display/chef/Home\.
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at ospcode\.com\fR of Opscode (http://www\.opscode\.com), with contributions from the community\. This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR with help2man\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.P
-On Debian systems, the complete text of the Apache 2\.0 License can be found in /usr/share/common\-licenses/Apache\-2\.0\.
diff --git a/distro/common/man/man8/chef-solo.8 b/distro/common/man/man8/chef-solo.8
index 42983af..541e7b6 100644
--- a/distro/common/man/man8/chef-solo.8
+++ b/distro/common/man/man8/chef-solo.8
@@ -1,158 +1,158 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "CHEF\-SOLO" "8" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBchef\-solo\fR \- Runs chef in solo mode against a specified cookbook location\.
-.
-.SH "SYNOPSIS"
-\fBchef\-solo\fR \fI(options)\fR
-.
+.TH "CHEF-SOLO" "8" "Chef 11.8.0" "" "chef-solo"
+.SH NAME
+chef-solo \- The man page for the chef-solo command line tool.
+.
+.nr rst2man-indent-level 0
+.
+.de1 rstReportMargin
+\\$1 \\n[an-margin]
+level \\n[rst2man-indent-level]
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
+-
+\\n[rst2man-indent0]
+\\n[rst2man-indent1]
+\\n[rst2man-indent2]
+..
+.de1 INDENT
+.\" .rstReportMargin pre:
+. RS \\$1
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
+. nr rst2man-indent-level +1
+.\" .rstReportMargin post:
+..
+.de UNINDENT
+. RE
+.\" indent \\n[an-margin]
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.nr rst2man-indent-level -1
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
+..
+.\" Man page generated from reStructuredText.
+.
+.sp
+chef\-solo is an open source version of the chef\-client that allows using cookbooks with nodes without requiring access to a server. chef\-solo runs locally and requires that a cookbook (and any of its dependencies) be on the same physical disk as the node. chef\-solo is a limited\-functionality version of the chef\-client and \fBdoes not support\fP the following:
+.INDENT 0.0
+.IP \(bu 2
+Node data storage
+.IP \(bu 2
+Search indexes
+.IP \(bu 2
+Centralized distribution of cookbooks
+.IP \(bu 2
+A centralized API that interacts with and integrates infrastructure components
+.IP \(bu 2
+Authentication or authorization
+.IP \(bu 2
+Persistent attributes
+.UNINDENT
+.sp
+The chef\-solo executable can be run as a command\-line tool.
+.SH OPTIONS
+.sp
+This command has the following syntax:
+.sp
+.nf
+.ft C
+chef\-solo OPTION VALUE OPTION VALUE ...
+.ft P
+.fi
+.sp
+This command has the following options:
+.INDENT 0.0
 .TP
-\fB\-c\fR, \fB\-\-config CONFIG\fR
-The configuration file to use
-.
+.B \fB\-c CONFIG\fP, \fB\-\-config CONFIG\fP
+The configuration file to use.
 .TP
-\fB\-d\fR, \fB\-\-daemonize\fR
-Daemonize the process
-.
+.B \fB\-d\fP, \fB\-\-daemonize\fP
+Indicates that the executable will be run as a daemon. This option is only available on machines that run in UNIX or Linux environments. For machines that are running Microsoft Windows that require similar functionality, use the \fBchef\-client::service\fP recipe in the \fBchef\-client\fP cookbook: \fI\%http://community.opscode.com/cookbooks/chef-client\fP. This will install a chef\-client service under Microsoft Windows using the Windows Service Wrapper.
 .TP
-\fB\-g\fR, \fB\-\-group GROUP\fR
-Group to set privilege to
-.
+.B \fB\-f\fP, \fB\-\-[no\-]fork\fP
+Indicates that a chef\-client run will be contained in a secondary process with dedicated RAM. When the chef\-client run is complete the RAM will be returned to the master process. This option helps ensure that a chef\-client will use a steady amount of RAM over time because the master process will not run recipes. This option will also help prevent memory leaks (such as those that can be introduced by the code contained within a poorly designed cookbook). Use \fB\-\-no\-fork\fP to disab [...]
 .TP
-\fB\-i\fR, \fB\-\-interval SECONDS\fR
-Run chef\-client periodically, in seconds
-.
+.B \fB\-F FORMAT\fP, \fB\-\-format FORMAT\fP
+The output format: \fBsummary\fP (default), \fBtext\fP, \fBjson\fP, \fByaml\fP, and \fBpp\fP.
 .TP
-\fB\-j\fR, \fB\-\-json\-attributes JSON_ATTRIBS\fR
-Load attributes from a JSON file or URL
-.
+.B \fB\-\-force\-formatter\fP
+Indicates that formatter output will be used instead of logger output.
 .TP
-\fB\-l\fR, \fB\-\-log_level LEVEL\fR
-Set the log level (debug, info, warn, error, fatal)
-.
+.B \fB\-\-force\-logger\fP
+Indicates that logger output will be used instead of formatter output.
 .TP
-\fB\-L\fR, \fB\-\-logfile LOGLOCATION\fR
-Set the log file location, defaults to STDOUT \- recommended for daemonizing
-.
+.B \fB\-g GROUP\fP, \fB\-\-group GROUP\fP
+The name of the group that owns a process. This is required when starting any executable as a daemon.
 .TP
-\fB\-N\fR, \fB\-\-node\-name NODE_NAME\fR
-The node name for this client
-.
+.B \fB\-h\fP, \fB\-\-help\fP
+Shows help for the command.
 .TP
-\fB\-r\fR, \fB\-\-recipe\-url RECIPE_URL\fR
-Pull down a remote gzipped tarball of recipes and untar it to the cookbook cache\.
-.
+.B \fB\-i SECONDS\fP, \fB\-\-interval SECONDS\fP
+The frequency (in seconds) at which the chef\-client runs. This value is configured for the chef\-client application run time, rather than in \fBChef::Config\fP.
 .TP
-\fB\-s\fR, \fB\-\-splay SECONDS\fR
-The splay time for running at intervals, in seconds
-.
+.B \fB\-j PATH\fP, \fB\-\-json\-attributes PATH\fP
+The path to a file that contains JSON data. Use this option to override attributes that are set from other locations, such as from within a cookbook or by a role.
 .TP
-\fB\-u\fR, \fB\-\-user USER\fR
-User to set privilege to
-.
+.B \fB\-l LEVEL\fP, \fB\-\-log_level LEVEL\fP
+The level of logging that will be stored in a log file: \fBdebug\fP, \fBinfo\fP, \fBwarn\fP, \fBerror\fP, or \fBfatal\fP.
 .TP
-\fB\-v\fR, \fB\-\-version\fR
-Show chef version
-.
+.B \fB\-L LOGLOCATION\fP, \fB\-\-logfile c\fP
+The location in which log file output files will be saved. If this location is set to something other than \fBSTDOUT\fP, standard output logging will still be performed (otherwise there would be no output other than to a file). This is recommended when starting any executable as a daemon.
 .TP
-\fB\-h\fR, \fB\-\-help\fR
-Show this message
-.
-.SH "DESCRIPTION"
-Chef Solo allows you to run Chef Cookbooks in the absence of a Chef Server\. To do this, the complete cookbook needs to be present on disk\.
-.
-.P
-By default Chef Solo will look in /etc/chef/solo\.rb for its configuration\. This configuration file has two required variables: file_cache_path and cookbook_path\.
-.
-.P
-For example:
-.
-.IP "" 4
-.
-.nf
-
-file_cache_path "/var/chef\-solo"
-cookbook_path "/var/chef\-solo/cookbooks"
-.
-.fi
-.
-.IP "" 0
-.
-.P
-For your own systems, you can change this to reflect any directory you like, but you\'ll need to specify absolute paths and the cookbook_path directory should be a subdirectory of the file_cache_path\.
-.
-.P
-You can also specify cookbook_path as an array, passing multiple locations to search for cookbooks\.
-.
-.P
-For example:
-.
-.IP "" 4
-.
+.B \fB\-\-[no\-]color\fP
+Indicates that color will not be used in the output. Default setting: \fB\-\-color\fP.
+.TP
+.B \fB\-N NODE_NAME\fP, \fB\-\-node\-name NODE_NAME\fP
+The name of the node.
+.TP
+.B \fB\-o RUN_LIST_ITEM\fP, \fB\-\-override\-runlist RUN_LIST_ITEM\fP
+Replace the current run list with the specified items.
+.TP
+.B \fB\-r RECIPE_URL\fP, \fB\-\-recipe\-url RECIPE_URL\fP
+The URL location from which a remote cookbook tar.gz will be downloaded.
+.TP
+.B \fB\-s SECONDS\fP, \fB\-\-splay SECONDS\fP
+A number (in seconds) to add to the \fBinterval\fP that is used to determine the frequency of chef\-client runs. This number can help prevent server load when there are many clients running at the same time.
+.TP
+.B \fB\-u USER\fP, \fB\-\-user USER\fP
+The user that owns a process. This is required when starting any executable as a daemon.
+.TP
+.B \fB\-v\fP, \fB\-\-version\fP
+The version of the chef\-client.
+.TP
+.B \fB\-W\fP, \fB\-\-why\-run\fP
+Indicates that the executable will be run in why\-run mode, which is a type of chef\-client run that does everything except modify the system. Use why\-run mode to understand why the chef\-client makes the decisions that it makes and to learn more about the current and proposed state of the system.
+.UNINDENT
+.SH EXAMPLES
+.sp
+\fBUse a URL\fP
+.sp
 .nf
-
-file_cache_path "/var/chef\-solo"
-cookbook_path ["/var/chef\-solo/cookbooks", "/var/chef\-solo/site\-cookbooks"]
-.
+.ft C
+$ chef\-solo \-c ~/solo.rb \-j ~/node.json \-r http://www.example.com/chef\-solo.tar.gz
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-Note that earlier entries are now overridden by later ones\.
-.
-.P
-Since chef\-solo doesn\'t have any interaction with a Chef Server, you\'ll need to specify node\-specifc attributes in a JSON file\. This can be located on the target system itself, or it can be stored on a remote server such as S3, or a web server on your network\.
-.
-.P
-Within the JSON file, you\'ll also specify the recipes that Chef should run in the "run_list"\. An example JSON file, which sets a resolv\.conf:
-.
-.IP "" 4
-.
+.sp
+where \fB\-r\fP uses the \fBremote_file\fP resource to retrieve the tar.gz archive into the \fBfile_cache_path\fP, and then extract it to \fBcookbooks_path\fP.
+.sp
+\fBUse a directory\fP
+.sp
 .nf
-
-{
-  "resolver": {
-    "nameservers": [ "10\.0\.0\.1" ],
-    "search":"int\.example\.com"
-  },
-  "run_list": [ "recipe[resolver]" ]
-}
-.
+.ft C
+$ chef\-solo \-c ~/solo.rb \-j ~/node.json
+.ft P
 .fi
-.
-.IP "" 0
-.
-.P
-Then you can run chef\-solo with \-j to specify the JSON file\. It will look for cookbooks in the cookbook_path configured in the configuration file, and apply attributes and use the run_list from the JSON file specified\.
-.
-.P
-You can use \-c to specify the path to the configuration file (if you don\'t want chef\-solo to use the default)\. You can also specify \-r for a cookbook tarball\.
-.
-.P
-For example:
-.
-.IP "" 4
-.
+.sp
+where the \fB\-r URL\fP option is not used. chef\-solo will look in the solo.rb file to determine the directory in which cookbooks are located.
+.sp
+\fBUse a URL for cookbook and JSON data\fP
+.sp
 .nf
-
-chef\-solo \-c ~/solo\.rb \-j ~/node\.json  \-r http://www\.example\.com/chef\-solo\.tar\.gz
-.
+.ft C
+$ chef\-solo \-c ~/solo.rb \-j http://www.example.com/node.json \-r http://www.example.com/chef\-solo.tar.gz
+.ft P
 .fi
+.sp
+where \fB\-r\fP corresponds to \fBrecipe_url\fP and \fB\-j\fP corresponds to \fBjson_attribs\fP, both of which are configuration options in solo.rb.
+.SH AUTHOR
+Opscode
+.\" Generated by docutils manpage writer.
 .
-.IP "" 0
-.
-.P
-In the above case, chef\-solo would extract the tarball to your specified cookbook_path, use ~/solo\.rb as the configuration file, and apply attributes and use the run_list from ~/node\.json\.
-.
-.SH "SEE ALSO"
-Full documentation for Chef and chef\-solo is located on the Chef wiki, http://wiki\.opscode\.com/display/chef/Home\.
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at ospcode\.com\fR of Opscode (http://www\.opscode\.com), with contributions from the community\. This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR with help2man\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.P
-On Debian systems, the complete text of the Apache 2\.0 License can be found in /usr/share/common\-licenses/Apache\-2\.0\.
diff --git a/distro/common/man/man8/chef-solr.8 b/distro/common/man/man8/chef-solr.8
deleted file mode 100644
index 33a3059..0000000
--- a/distro/common/man/man8/chef-solr.8
+++ /dev/null
@@ -1,122 +0,0 @@
-.\" generated with Ronn/v0.7.3
-.\" http://github.com/rtomayko/ronn/tree/0.7.3
-.
-.TH "CHEF\-SOLR" "8" "June 2012" "Chef 10.12.0" "Chef Manual"
-.
-.SH "NAME"
-\fBchef\-solr\fR \- Runs as Chef\'s search server
-.
-.SH "SYNOPSIS"
-\fBchef\-solr\fR \fI(options)\fR
-.
-.TP
-\fB\-c\fR, \fB\-\-config CONFIG\fR
-The configuration file to use
-.
-.TP
-\fB\-d\fR, \fB\-\-daemonize\fR
-Daemonize the process
-.
-.TP
-\fB\-g\fR, \fB\-\-group GROUP\fR
-Group to set privilege to
-.
-.TP
-\fB\-l\fR, \fB\-\-log_level LEVEL\fR
-Set the log level (debug, info, warn, error, fatal)
-.
-.TP
-\fB\-L\fR, \fB\-\-logfile LOGLOCATION\fR
-Set the log file location, defaults to STDOUT \- recommended for daemonizing
-.
-.TP
-\fB\-P\fR, \fB\-\-pid PIDFILE\fR
-Set the PID file location, defaults to /tmp/chef\-solr\.pid
-.
-.TP
-\fB\-D\fR, \fB\-\-solr\-data\-dir PATH\fR
-Where the Solr data lives
-.
-.TP
-\fB\-x\fR, \fB\-\-solor\-heap\-size SIZE\fR
-Set the size of the Java Heap
-.
-.TP
-\fB\-H\fR, \fB\-\-solr\-home\-dir PATH\fR
-Solr home directory
-.
-.TP
-\fB\-j\fR, \fB\-\-java\-opts OPTS\fR
-Raw options passed to Java
-.
-.TP
-\fB\-x\fR, \fB\-\-solor\-heap\-size\fR
-Set the size of the Java Heap
-.
-.TP
-\fB\-W\fR, \fB\-\-solr\-jetty\-dir PATH\fR
-Where to place the Solr Jetty instance
-.
-.TP
-\fB\-u\fR, \fB\-\-user USER\fR
-User to set privilege to
-.
-.TP
-\fB\-v\fR, \fB\-\-version\fR
-Show chef\-solr version
-.
-.TP
-\fB\-h\fR, \fB\-\-help\fR
-Show this message
-.
-.SH "DESCRIPTION"
-Chef\-solr provides search service for Chef\. You need to have both chef\-solr and chef\-expander\-cluster running in order for search to work\.
-.
-.P
-\fBInstallation\fR
-.
-.P
-Make sure you backed up your data if you are upgrading from a previous version\. Run chef\-solr\-installer to upgrade your Chef Solr installation\. Answer "yes" when prompted for confirmation\. The process should look like this:
-.
-.IP "" 4
-.
-.nf
-
-yourshell> chef\-solr\-installer
-Configuration setting solr_heap_size is unknown and will be ignored
-
-Chef Solr is already installed in /var/chef/solr
-Do you want to overwrite the current install? All existing Solr data will be lost\. [y/n] y
-Removing the existing Chef Solr installation
-  rm \-rf /var/chef/solr
-  rm \-rf /var/chef/solr\-jetty
-  rm \-rf /var/chef/solr/data
-Creating Solr Home Directory
-  mkdir \-p /var/chef/solr
-  entering /var/chef/solr
-  tar zxvf /Users/ddeleo/opscode/chef/chef\-solr/solr/solr\-home\.tar\.gz
-Creating Solr Data Directory
-  mkdir \-p /var/chef/solr/data
-Unpacking Solr Jetty
-  mkdir \-p /var/chef/solr\-jetty
-  entering /var/chef/solr\-jetty
-  tar zxvf /Users/ddeleo/opscode/chef/chef\-solr/solr/solr\-jetty\.tar\.gz
-
-Successfully installed Chef Solr\.
-You can restore your search index using `knife index rebuild`
-.
-.fi
-.
-.IP "" 0
-.
-.SH "SEE ALSO"
-\fBchef\-expander\-cluster\fR(8)
-.
-.P
-Full documentation for Chef and chef\-server is located on the Chef wiki, http://wiki\.opscode\.com/display/chef/Home\.
-.
-.SH "AUTHOR"
-Chef was written by Adam Jacob \fIadam at ospcode\.com\fR of Opscode (http://www\.opscode\.com), with contributions from the community\. This manual page was written by Joshua Timberman \fIjoshua at opscode\.com\fR with help2man\. Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2\.0 License\.
-.
-.P
-On Debian systems, the complete text of the Apache 2\.0 License can be found in /usr/share/common\-licenses/Apache\-2\.0\.
diff --git a/distro/common/markdown/man1/chef-shell.mkd b/distro/common/markdown/man1/chef-shell.mkd
new file mode 100644
index 0000000..5525ef8
--- /dev/null
+++ b/distro/common/markdown/man1/chef-shell.mkd
@@ -0,0 +1,195 @@
+chef-shell(1) -- Interactive Chef Console
+=========================================
+
+## SYNOPSIS
+
+__chef-shell__ [_named configuration_] _(options)_
+
+  * `-S`, `--server CHEF_SERVER_URL`:
+    The chef server URL
+  * `-z`, `--client`:
+    chef-client mode
+  * `-c`, `--config CONFIG`:
+    The configuration file to use
+  * `-j`, `--json-attributes JSON_ATTRIBS`:
+    Load attributes from a JSON file or URL
+  * `-l`, `--log-level LOG_LEVEL`:
+    Set the logging level
+  * `-s`, `--solo`:
+    chef-solo session
+  * `-a`, `--standalone`:
+    standalone session
+  * `-v`, `--version`:
+    Show chef version
+  * `-h`, `--help`:
+    Show command options
+
+When no --config option is specified, chef-shell attempts to load a
+default configuration file:
+
+* If a _named configuration_ is given, chef-shell will load ~/.chef/_named
+  configuration_/chef_shell.rb
+* If no _named configuration_ is given chef-shell will load
+  ~/.chef/chef_shell.rb if it exists
+* chef-shell falls back to loading /etc/chef/client.rb or
+/etc/chef/solo.rb if -z or -s options are given and no chef_shell.rb
+can be found.
+* The --config option takes precedence over implicit configuration
+  paths.
+
+## DESCRIPTION
+
+`chef-shell` is an irb(1) (interactive ruby) session customized for Chef.
+`chef-shell` serves two primary functions: it provides a means to
+interact with a Chef Server interactively using a convenient DSL; it
+allows you to define and run Chef recipes interactively.
+
+## SYNTAX
+
+chef-shell uses irb's subsession feature to provide multiple modes of
+interaction. In addition to the primary mode which is entered on start,
+`recipe` and `attributes` modes are available.
+
+## PRIMARY MODE
+The following commands are available in the primary
+session:
+
+  * `help`:
+    Prints a list of available commands
+  * `version`:
+    Prints the Chef version
+  * `recipe`:
+    Switches to `recipe` mode
+  * `attributes`:
+    Switches to `attributes` mode
+  * `run_chef`:
+    Initiates a chef run
+  * `reset`:
+    reinitializes chef-shell session
+  * `echo :on|:off`:
+    Turns irb's echo function on or off. Echo is _on_ by default.
+  * `tracing :on|:off`:
+    Turns irb's function tracing feature on or off. Tracing is extremely
+    verbose and expected to be of interest primarily to developers.
+  * `node`:
+    Returns the _node_ object for the current host. See knife-node(1)
+    for more information about nodes.
+  * `ohai`:
+    Prints the attributes of _node_
+
+In addition to these commands, chef-shell provides a DSL for accessing
+data on the Chef Server. When working with remote data in chef-shell, you
+chain method calls in the form _object type_._operation_, where
+_object type_ is in plural form. The following object types are
+available:
+
+  * `nodes`
+  * `roles`
+  * `data_bags`
+  * `clients`
+  * `cookbooks`
+
+For each _object type_ the following operations are available:
+
+  * _object type_.all(_&block_):
+    Loads all items from the server. If the optional code _block_ is
+    given, each item will be passed to the block and the results
+    returned, similar to ruby's `Enumerable#map` method.
+  * _object type_.show(_object name_):
+    Aliased as _object type_.load
+
+    Loads the singular item identified by _object name_.
+  * _object type_.search(_query_, _&block_):
+    Aliased as _object type_.find
+
+    Runs a search against the server and returns the matching items. If
+    the optional code _block_ is given each item will be passed to the
+    block and the results returned.
+
+    The _query_ may be a Solr/Lucene format query given as a String, or
+    a Hash of conditions. If a Hash is given, the options will be ANDed
+    together. To join conditions with OR, use negative queries, or any
+    advanced search syntax, you must provide give the query in String
+    form.
+  * _object type_.transform(:all|_query_, _&block_):
+    Aliased as _object type_.bulk_edit
+
+    Bulk edit objects by processing them with the (required) code _block_.
+    You can edit all objects of the given type by passing the Symbol
+    `:all` as the argument, or only a subset by passing a _query_ as the
+    argument. The _query_ is evaluated in the same way as with
+    __search__.
+
+    The return value of the code _block_ is used to alter the behavior
+    of `transform`. If the value returned from the block is `nil` or
+    `false`, the object will not be saved. Otherwise, the object is
+    saved after being passed to the block. This behavior can be
+    exploited to create a dry run to test a data transformation.
+
+## RECIPE MODE
+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>
+
+Once you have defined resources in the recipe, you can trigger a
+convergence run via `run_chef`
+
+## EXAMPLES
+
+* A "Hello World" interactive recipe
+
+  chef > recipe
+  chef:recipe > echo :off
+  chef:recipe > file "/tmp/hello\_world"
+  chef:recipe > run\_chef
+  [Sat, 09 Apr 2011 08:56:56 -0700] INFO: Processing file[/tmp/hello\_world] action create ((irb#1) line 2)
+  [Sat, 09 Apr 2011 08:56:56 -0700] INFO: file[/tmp/hello\_world] created file /tmp/hello\_world
+  chef:recipe > pp ls '/tmp'
+  [".",
+  "..",
+  "hello\_world"]
+
+* Search for _nodes_ by role, and print their IP addresses
+
+  chef > nodes.find(:roles => 'monitoring-server') {|n| n[:ipaddress] }
+  => ["10.254.199.5"]
+
+* Remove the role _obsolete_ from every node in the system
+
+  chef > nodes.transform(:all) {|n| n.run\_list.delete('role[obsolete]') }
+   => [node[chef098b2.opschef.com], node[ree-woot], node[graphite-dev], node[fluke.localdomain], node[ghost.local], node[kallistec]]
+
+
+## BUGS
+
+`chef-shell` often does not perfectly replicate the context in which
+chef-client(8) configures a host, which may lead to discrepancies in
+observed behavior.
+
+`chef-shell` has to duplicate much code from chef-client's internal
+libraries and may become out of sync with the behavior of those
+libraries.
+
+## SEE ALSO
+
+  chef-client(8) knife(1)
+  <http://wiki.opscode.com/display/chef/Chef+Shell>
+
+## AUTHOR
+
+   Chef was written by Adam Jacob <adam at opscode.com> with many
+   contributions from the community. chef-shell was written by Daniel
+   DeLeo.
+
+## DOCUMENTATION
+
+   This manual page was written by Daniel DeLeo <dan at opscode.com>.
+   Permission is granted to copy, distribute and / or modify this
+   document under the terms of the Apache 2.0 License.
+
+## CHEF
+
+   chef-shell is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
diff --git a/distro/common/markdown/man1/knife-bootstrap.mkd b/distro/common/markdown/man1/knife-bootstrap.mkd
index 9e8aa53..5c5e456 100644
--- a/distro/common/markdown/man1/knife-bootstrap.mkd
+++ b/distro/common/markdown/man1/knife-bootstrap.mkd
@@ -31,6 +31,9 @@ __knife__ __bootstrap__ _(options)_
     Bootstrap a distro using a template
   * `--[no-]host-key-verify`:
     Enable host key verification, which is the default behavior.
+  * `--hint HINT_NAME[=HINT_FILE]`:
+    Provide the name of a hint (with option JSON file) to set for use by
+    Ohai plugins.
 
 ## DESCRIPTION
 
diff --git a/distro/common/markdown/man1/knife-configure.mkd b/distro/common/markdown/man1/knife-configure.mkd
index cc7dd1d..507d30d 100644
--- a/distro/common/markdown/man1/knife-configure.mkd
+++ b/distro/common/markdown/man1/knife-configure.mkd
@@ -35,17 +35,17 @@ the specified _directory_.
     field blank to accept the default value. On most systems, the
     default values are acceptable.
 
-    user at host$ knife configure -i  
-    Please enter the chef server URL: [http://localhost:4000]  
-    Please enter a clientname for the new client: [username]  
-    Please enter the existing admin clientname: [chef-webui]  
-    Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]  
-    Please enter the validation clientname: [chef-validator]  
-    Please enter the location of the validation key: [/etc/chef/validation.pem]  
-    Please enter the path to a chef repository (or leave blank):  
-    Creating initial API user...  
-    Created (or updated) client[username]  
-    Configuration file written to /home/username/.chef/knife.rb  
+    user at host$ knife configure -i
+    Please enter the chef server URL: [http://localhost:4000]
+    Please enter a clientname for the new client: [username]
+    Please enter the existing admin clientname: [chef-webui]
+    Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]
+    Please enter the validation clientname: [chef-validator]
+    Please enter the location of the validation key: [/etc/chef/validation.pem]
+    Please enter the path to a chef repository (or leave blank):
+    Creating initial API user...
+    Created (or updated) client[username]
+    Configuration file written to /home/username/.chef/knife.rb
 
     This creates a new administrator client named _username_, writes
     a configuration file to _/home/username/.chef/knife.rb_, and the
diff --git a/distro/common/markdown/man1/knife-cookbook.mkd b/distro/common/markdown/man1/knife-cookbook.mkd
index 4f714c5..deaf004 100644
--- a/distro/common/markdown/man1/knife-cookbook.mkd
+++ b/distro/common/markdown/man1/knife-cookbook.mkd
@@ -117,11 +117,11 @@ __knife cookbook create cookbook__ _(options)_
     the directory where the cookbook will be created
   * `-r`, `--readme-format format`:
     format of the readme file md, mkd, txt, rdoc
-  * `-c`, `--copyright copyright`:
+  * `-C`, `--copyright copyright`:
     name of copyright holder
   * `-i`, `--license license`:
     license for cookbook, apachev2 or none
-  * `-e`, `--email email`:
+  * `-m`, `--email email`:
     email address of cookbook maintainer
 
 this is a helper command that creates a new cookbook directory in the
@@ -143,7 +143,7 @@ supported readme formats are 'md' (default), 'mkd', 'txt', 'rdoc'. the
 readme file will be written with the specified extension and a set of
 helpful starting headers.
 
-specify `-c` or `--copyright` with the name of the copyright holder as
+specify `-C` or `--copyright` with the name of the copyright holder as
 your name or your company/organization name in a quoted string. if this
 value is not specified an all-caps string `your_company_name` is used
 which can be easily changed with find/replace.
@@ -156,7 +156,7 @@ the cookbook and follow any restrictions they describe. when using
 are pre-filled. the `none` license will be treated as
 non-redistributable.
 
-specify `-e` or `--email` with the email address of the cookbook's
+specify `-m` or `--email` with the email address of the cookbook's
 maintainer. if this value is not specified, an all-caps string
 `your_email` is used which can easily be changed with find/replace.
 
diff --git a/distro/common/markdown/man1/knife-exec.mkd b/distro/common/markdown/man1/knife-exec.mkd
index 34d9297..d4aa87c 100644
--- a/distro/common/markdown/man1/knife-exec.mkd
+++ b/distro/common/markdown/man1/knife-exec.mkd
@@ -9,13 +9,15 @@ __knife__ __exec__ _(options)_
     Provide a snippet of code to evaluate on the command line
 
 ## DESCRIPTION
-`knife exec` runs arbitrary ruby scripts in a context similar to that of
-the shef(1) DSL. See the shef documentation for a description of the
-commands available.
+
+`knife exec` runs arbitrary ruby scripts in a context similar to that
+of the chef-shell(1) DSL. See the chef-shell documentation for a
+description of the commands available.
 
 ## EXAMPLES
+
   * Make an API call against an arbitrary endpoint:
-    knife exec -E 'api.get("nodes/fluke.localdomain/cookbooks")'  
+    knife exec -E 'api.get("nodes/fluke.localdomain/cookbooks")'
     => list of cookbooks for the node _fluke.localdomain_
   * Remove the role _obsolete_ from all nodes:
     knife exec -E 'nodes.transform(:all){|n| n.run\_list.delete("role[obsolete]")}'
@@ -23,15 +25,18 @@ commands available.
     knife exec -E 'nodes.find(:roles => "webserver") {|n| n.expand!; n[:recipes]}'
 
 ## SEE ALSO
-   __shef(1)__
+
+   __chef-shell(1)__
 
 ## AUTHOR
+
    Chef was written by Adam Jacob <adam at opscode.com> with many contributions from the community.
 
 ## DOCUMENTATION
+
    This manual page was written by Joshua Timberman <joshua at opscode.com>.
    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://wiki.opscode.com/display/chef/Home>
diff --git a/distro/common/markdown/man1/knife.mkd b/distro/common/markdown/man1/knife.mkd
index 57438d4..8c97cc9 100644
--- a/distro/common/markdown/man1/knife.mkd
+++ b/distro/common/markdown/man1/knife.mkd
@@ -46,8 +46,6 @@ documentation using `knife help TOPIC`.
     Set the editor to use for interactive commands
   * `-F`, `--format` FORMAT:
     Which format to use for output. See FORMATS for details.
-  * `-V`, `--verbose`:
-    More verbose output.  Use twice for max verbosity
   * `-d`, `--disable-editing`:
     Do not open EDITOR, just accept the data as is
   * `-u`, `--user` USER:
@@ -56,6 +54,8 @@ documentation using `knife help TOPIC`.
     Show the data after a destructive operation
   * `-v`, `--version`:
     Show chef version
+  * `-V`, `--verbose`:
+    More verbose output. Use twice for max verbosity.
   * `-y`, `--yes`:
     Say yes to all prompts for confirmation
   * `--defaults`:
@@ -98,14 +98,9 @@ If the config file exists, knife uses these settings for __GENERAL OPTIONS__ def
   * `chef_server_url`:
     URL of the Chef server. Corresponds to the `-s` or `--server-url`
     option. This is requested from the user when running this sub-command.
-  * `cache_type`:
-    The type of cache to use. Default is BasicFile. This can be any type of
-    Cache that moneta supports: BasicFile, Berkeley, Couch, DataMapper,
-    File, LMC, Memcache, Memory, MongoDB, Redis, Rufus, S3, SDBM, Tyrant,
-    Xattr, YAML.
-  * `cache_options`:
-    Specifies various options to use for caching. These options are
-    dependent on the `cache_type`.
+  * `syntax_check_cache_path`:
+    Specifies the path to a directory where knife caches information
+    about files that it has syntax checked.
   * `validation_client_name`:
     Specifies the name of the client used to validate new clients.
   * `validation_key`:
@@ -183,7 +178,7 @@ recommended though, and git fits with a lot of the workflow paradigms.
     data editing entirely.
 
 ## SEE ALSO
-  __chef-client(8)__ __chef-server(8)__ __shef(1)__
+  __chef-client(8)__ __chef-server(8)__ __chef-shell(1)__
 
   __knife-bootstrap(1)__ __knife-client(1)__ __knife-configure(1)__
   __knife-cookbook-site(1)__ __knife-cookbook(1)__ __knife-data-bag(1)__
diff --git a/distro/common/markdown/man1/shef.mkd b/distro/common/markdown/man1/shef.mkd
deleted file mode 100644
index a2e0f04..0000000
--- a/distro/common/markdown/man1/shef.mkd
+++ /dev/null
@@ -1,189 +0,0 @@
-shef(1) -- Interactive Chef Console
-========================================
-
-## SYNOPSIS
-
-__shef__ [_named configuration_] _(options)_
-
-  * `-S`, `--server CHEF_SERVER_URL`:
-    The chef server URL
-  * `-z`, `--client`:
-    chef-client mode
-  * `-c`, `--config CONFIG`:
-    The configuration file to use
-  * `-j`, `--json-attributes JSON_ATTRIBS`:
-    Load attributes from a JSON file or URL
-  * `-l`, `--log-level LOG_LEVEL`:
-    Set the logging level
-  * `-s`, `--solo`:
-    chef-solo shef session
-  * `-a`, `--standalone`:
-    standalone shef session
-  * `-v`, `--version`:
-    Show chef version
-  * `-h`, `--help`:
-    Show command options
-
-When no --config option is specified, shef attempts to load a default configuration file:
-
-* If a _named configuration_ is given, shef will load ~/.chef/_named
-  configuration_/shef.rb
-* If no _named configuration_ is given shef will load ~/.chef/shef.rb if it exists
-* Shef falls back to loading /etc/chef/client.rb or /etc/chef/solo.rb if -z or
-  -s options are given and no shef.rb can be found.
-* The --config option takes precedence over implicit configuration
-  paths.
-
-## DESCRIPTION
-
-`shef` is an irb(1) (interactive ruby) session customized for Chef.
-`shef` serves two primary functions: it provides a means to
-interact with a Chef Server interactively using a convenient DSL; it
-allows you to define and run Chef recipes interactively.
-
-## SYNTAX
-Shef uses irb's subsession feature to provide multiple modes of
-interaction. In addition to the primary mode which is entered on start,
-`recipe` and `attributes` modes are available.
-
-## PRIMARY MODE
-The following commands are available in the primary
-session:
-
-  * `help`:
-    Prints a list of available commands
-  * `version`:
-    Prints the Chef version
-  * `recipe`:
-    Switches to `recipe` mode
-  * `attributes`:
-    Switches to `attributes` mode
-  * `run_chef`:
-    Initiates a chef run
-  * `reset`:
-    reinitializes shef
-  * `echo :on|:off`:
-    Turns irb's echo function on or off. Echo is _on_ by default.
-  * `tracing :on|:off`:
-    Turns irb's function tracing feature on or off. Tracing is extremely
-    verbose and expected to be of interest primarily to developers.
-  * `node`:
-    Returns the _node_ object for the current host. See knife-node(1)
-    for more information about nodes.
-  * `ohai`:
-    Prints the attributes of _node_
-
-In addition to these commands, shef provides a DSL for accessing data on
-the Chef Server. When working with remote data in shef, you chain method
-calls in the form _object type_._operation_, where _object type_ is in
-plural form. The following object types are available:
-
-  * `nodes`
-  * `roles`
-  * `data_bags`
-  * `clients`
-  * `cookbooks`
-
-For each _object type_ the following operations are available:
-
-  * _object type_.all(_&block_):
-    Loads all items from the server. If the optional code _block_ is
-    given, each item will be passed to the block and the results
-    returned, similar to ruby's `Enumerable#map` method.
-  * _object type_.show(_object name_):
-    Aliased as _object type_.load
-
-    Loads the singular item identified by _object name_.
-  * _object type_.search(_query_, _&block_):
-    Aliased as _object type_.find
-
-    Runs a search against the server and returns the matching items. If
-    the optional code _block_ is given each item will be passed to the
-    block and the results returned.
-
-    The _query_ may be a Solr/Lucene format query given as a String, or
-    a Hash of conditions. If a Hash is given, the options will be ANDed
-    together. To join conditions with OR, use negative queries, or any
-    advanced search syntax, you must provide give the query in String
-    form.
-  * _object type_.transform(:all|_query_, _&block_):
-    Aliased as _object type_.bulk_edit
-
-    Bulk edit objects by processing them with the (required) code _block_.
-    You can edit all objects of the given type by passing the Symbol
-    `:all` as the argument, or only a subset by passing a _query_ as the
-    argument. The _query_ is evaluated in the same way as with
-    __search__.
-
-    The return value of the code _block_ is used to alter the behavior
-    of `transform`. If the value returned from the block is `nil` or
-    `false`, the object will not be saved. Otherwise, the object is
-    saved after being passed to the block. This behavior can be
-    exploited to create a dry run to test a data transformation.
-
-## RECIPE MODE
-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>
-
-Once you have defined resources in the recipe, you can trigger a
-convergence run via `run_chef`
-
-## EXAMPLES
-
-* A "Hello World" interactive recipe
-
-  chef > recipe  
-  chef:recipe > echo :off  
-  chef:recipe > file "/tmp/hello\_world"  
-  chef:recipe > run\_chef  
-  [Sat, 09 Apr 2011 08:56:56 -0700] INFO: Processing file[/tmp/hello\_world] action create ((irb#1) line 2)  
-  [Sat, 09 Apr 2011 08:56:56 -0700] INFO: file[/tmp/hello\_world] created file /tmp/hello\_world  
-  chef:recipe > pp ls '/tmp'  
-  [".",  
-  "..",  
-  "hello\_world"]  
-
-* Search for _nodes_ by role, and print their IP addresses
-
-  chef > nodes.find(:roles => 'monitoring-server') {|n| n[:ipaddress] }  
-  => ["10.254.199.5"]  
-
-* Remove the role _obsolete_ from every node in the system
-
-  chef > nodes.transform(:all) {|n| n.run\_list.delete('role[obsolete]') }  
-   => [node[chef098b2.opschef.com], node[ree-woot], node[graphite-dev], node[fluke.localdomain], node[ghost.local], node[kallistec]]  
-
-
-## BUGS
-The name `shef` is clever in print but is confusing when spoken aloud.
-Pronouncing `shef` as `chef console` is an imperfect workaround.
-
-`shef` often does not perfectly replicate the context in which
-chef-client(8) configures a host, which may lead to discrepancies in
-observed behavior.
-
-`shef` has to duplicate much code from chef-client's internal libraries
-and may become out of sync with the behavior of those libraries.
-
-## SEE ALSO
-  chef-client(8) knife(1)
-  <http://wiki.opscode.com/display/chef/Shef>
-
-## AUTHOR
-   Chef was written by Adam Jacob <adam at opscode.com> with many
-   contributions from the community. Shef was written by Daniel DeLeo.
-
-## DOCUMENTATION
-   This manual page was written by Daniel DeLeo <dan at opscode.com>.
-   Permission is granted to copy, distribute and / or modify this
-   document under the terms of the Apache 2.0 License.
-
-## CHEF
-   Shef is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
-
-
-
diff --git a/distro/common/markdown/man8/chef-client.mkd b/distro/common/markdown/man8/chef-client.mkd
index d440d48..e37d283 100644
--- a/distro/common/markdown/man8/chef-client.mkd
+++ b/distro/common/markdown/man8/chef-client.mkd
@@ -24,9 +24,6 @@ __chef-client__ _(options)_
   * `-L`, `--logfile LOGLOCATION`:
     Set the log file location, defaults to STDOUT - recommended for
     daemonizing
-  * `-V`, `--verbose`:
-    Ensures logging goes to STDOUT as well as  to  other  configured
-    log location(s).
   * `-N`, `--node-name NODE_NAME`:
     The node name for this client
   * `-o`, `--override-runlist`:
diff --git a/distro/common/markdown/man8/chef-expander.mkd b/distro/common/markdown/man8/chef-expander.mkd
index d72ee1d..9190a9a 100644
--- a/distro/common/markdown/man8/chef-expander.mkd
+++ b/distro/common/markdown/man8/chef-expander.mkd
@@ -79,4 +79,4 @@ granted to copy, distribute and / or modify this document under the
 terms of the Apache 2.0 License.
 
 On Debian systems, the complete text of the Apache 2.0 License  can  be
-found in /usr/share/common-licenses/Apache-2.0.
\ No newline at end of file
+found in /usr/share/common-licenses/Apache-2.0.
diff --git a/distro/common/markdown/man8/chef-expanderctl.mkd b/distro/common/markdown/man8/chef-expanderctl.mkd
index 00b34e7..03ce6af 100644
--- a/distro/common/markdown/man8/chef-expanderctl.mkd
+++ b/distro/common/markdown/man8/chef-expanderctl.mkd
@@ -55,4 +55,4 @@ granted to copy, distribute and / or modify this document under the
 terms of the Apache 2.0 License.
 
 On Debian systems, the complete text of the Apache 2.0 License  can  be
-found in /usr/share/common-licenses/Apache-2.0.
\ No newline at end of file
+found in /usr/share/common-licenses/Apache-2.0.
diff --git a/distro/debian/etc/init.d/chef-client b/distro/debian/etc/init.d/chef-client
index efac1ed..b74f6d9 100755
--- a/distro/debian/etc/init.d/chef-client
+++ b/distro/debian/etc/init.d/chef-client
@@ -15,12 +15,12 @@
 # description: starts up chef-client in daemon mode.
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/bin/chef-client
+DAEMON=$(which chef-client)
 NAME=chef-client
 DESC=chef-client
 PIDFILE=/var/run/chef/client.pid
 
-test -x $DAEMON || exit 0
+test -x $DAEMON || exit 1
 
 . /lib/lsb/init-functions
 
@@ -165,8 +165,10 @@ case "$1" in
   restart|force-reload)
     log_daemon_msg "Restarting $DESC" "$NAME"
     errcode=0
-    stop_server || errcode=$?
-    [ -n "$DIETIME" ] && sleep $DIETIME
+    if running ; then
+      stop_server || errcode=$?
+      [ -n "$DIETIME" ] && sleep $DIETIME
+    fi
     start_server || errcode=$?
     [ -n "$STARTTIME" ] && sleep $STARTTIME
     running || errcode=$?
diff --git a/distro/debian/etc/init.d/chef-expander b/distro/debian/etc/init.d/chef-expander
index 19b6c3e..fdf6e40 100755
--- a/distro/debian/etc/init.d/chef-expander
+++ b/distro/debian/etc/init.d/chef-expander
@@ -15,12 +15,12 @@
 # description: starts up chef-expander in daemon mode.
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/bin/chef-expander
+DAEMON=$(which chef-expander)
 NAME=chef-expander
 DESC=chef-expander
 PIDFILE=/var/run/chef/expander.pid
 
-test -x $DAEMON || exit 0
+test -x $DAEMON || exit 1
 
 . /lib/lsb/init-functions
 
diff --git a/distro/debian/etc/init.d/chef-server b/distro/debian/etc/init.d/chef-server
index ca21ddf..0b94fc7 100755
--- a/distro/debian/etc/init.d/chef-server
+++ b/distro/debian/etc/init.d/chef-server
@@ -15,13 +15,13 @@
 # description: starts up chef-server webui.
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/bin/chef-server
+DAEMON=$(which chef-server)
 PIDFILE=/var/run/chef/server.%s.pid
 MAINPID=/var/run/chef/server.main.pid
 NAME=chef-server
 DESC=chef-server
 
-test -x $DAEMON || exit 0
+test -x $DAEMON || exit 1
 
 . /lib/lsb/init-functions
 
diff --git a/distro/debian/etc/init.d/chef-server-webui b/distro/debian/etc/init.d/chef-server-webui
index f3dd3db..e2db8a4 100755
--- a/distro/debian/etc/init.d/chef-server-webui
+++ b/distro/debian/etc/init.d/chef-server-webui
@@ -15,13 +15,13 @@
 # description: starts up chef-server webui.
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/bin/chef-server-webui
+DAEMON=$(which chef-server-webui)
 PIDFILE=/var/run/chef/server-webui.%s.pid
 MAINPID=/var/run/chef/server-webui.main.pid
 NAME=chef-server-webui
 DESC=chef-server-webui
 
-test -x $DAEMON || exit 0
+test -x $DAEMON || exit 1
 
 . /lib/lsb/init-functions
 
diff --git a/distro/debian/etc/init.d/chef-solr b/distro/debian/etc/init.d/chef-solr
index e53634f..2cc93b5 100755
--- a/distro/debian/etc/init.d/chef-solr
+++ b/distro/debian/etc/init.d/chef-solr
@@ -15,13 +15,13 @@
 # description: starts up chef-solr in daemon mode.
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/bin/chef-solr
+DAEMON=$(which chef-solr)
 DAEMON_NAME=java
 NAME=chef-solr
 DESC=chef-solr
 PIDFILE=/var/run/chef/solr.pid
 
-test -x $DAEMON || exit 0
+test -x $DAEMON || exit 1
 
 . /lib/lsb/init-functions
 
diff --git a/distro/debian/etc/init/chef-client.conf b/distro/debian/etc/init/chef-client.conf
index 1f8c6d0..5f246b1 100644
--- a/distro/debian/etc/init/chef-client.conf
+++ b/distro/debian/etc/init/chef-client.conf
@@ -11,7 +11,7 @@ respawn
 respawn limit 5 30
 
 pre-start script
-    test -x /usr/bin/chef-client || { stop; exit 0; }
+    test -x /usr/bin/chef-client || { stop; exit 1; }
 end script
 
 exec /usr/bin/chef-client -i 1800 -L /var/log/chef/client.log
diff --git a/distro/debian/etc/init/chef-expander.conf b/distro/debian/etc/init/chef-expander.conf
index 21ff246..2c08f6c 100644
--- a/distro/debian/etc/init/chef-expander.conf
+++ b/distro/debian/etc/init/chef-expander.conf
@@ -11,7 +11,7 @@ respawn
 respawn limit 5 30
 
 pre-start script
-    test -x /usr/bin/chef-expander || { stop; exit 0; }
+    test -x /usr/bin/chef-expander || { stop; exit 1; }
 end script
 
 exec /usr/bin/chef-expander -c /etc/chef/solr.rb -L /var/log/chef/expander.log -n 1 -i 1
diff --git a/distro/debian/etc/init/chef-server-webui.conf b/distro/debian/etc/init/chef-server-webui.conf
index 8b443c5..8ba2587 100644
--- a/distro/debian/etc/init/chef-server-webui.conf
+++ b/distro/debian/etc/init/chef-server-webui.conf
@@ -11,7 +11,7 @@ respawn
 respawn limit 5 30
 
 pre-start script
-    test -x /usr/bin/chef-server-webui || { stop; exit 0; }
+    test -x /usr/bin/chef-server-webui || { stop; exit 1; }
 end script
 
 exec /usr/bin/chef-server-webui -e production -p 4040 -L /var/log/chef/server-webui.log
diff --git a/distro/debian/etc/init/chef-server.conf b/distro/debian/etc/init/chef-server.conf
index 1eebd8f..a372e50 100644
--- a/distro/debian/etc/init/chef-server.conf
+++ b/distro/debian/etc/init/chef-server.conf
@@ -11,7 +11,7 @@ respawn
 respawn limit 5 30
 
 pre-start script
-    test -x /usr/bin/chef-server || { stop; exit 0; }
+    test -x /usr/bin/chef-server || { stop; exit 1; }
 end script
 
 exec /usr/bin/chef-server -e production -p 4000 -L /var/log/chef/server.log
diff --git a/distro/debian/etc/init/chef-solr.conf b/distro/debian/etc/init/chef-solr.conf
index 4ca885e..edca5e3 100644
--- a/distro/debian/etc/init/chef-solr.conf
+++ b/distro/debian/etc/init/chef-solr.conf
@@ -11,7 +11,7 @@ respawn
 respawn limit 5 30
 
 pre-start script
-    test -x /usr/bin/chef-solr || { stop; exit 0; }
+    test -x /usr/bin/chef-solr || { stop; exit 1; }
 end script
 
 exec /usr/bin/chef-solr -c /etc/chef/solr.rb -L /var/log/chef/solr.log
diff --git a/distro/redhat/etc/init.d/chef-client b/distro/redhat/etc/init.d/chef-client
index fbeaa39..b41150e 100644
--- a/distro/redhat/etc/init.d/chef-client
+++ b/distro/redhat/etc/init.d/chef-client
@@ -61,7 +61,7 @@ reload() {
     killproc -p $pidfile chef-client -HUP
     retval=$?
     echo
-    return $retval 
+    return $retval
 }
 
 run() {
diff --git a/distro/redhat/etc/init.d/chef-server b/distro/redhat/etc/init.d/chef-server
index 4424717..f956dd4 100644
--- a/distro/redhat/etc/init.d/chef-server
+++ b/distro/redhat/etc/init.d/chef-server
@@ -11,7 +11,7 @@
 # Required-Stop: $local_fs $network $remote_fs chef-solr chef-expander
 # Should-Start: $named $time
 # Should-Stop: $named $time
-# Short-Description: Startup script for chef-server 
+# Short-Description: Startup script for chef-server
 # Description: Server component of the Chef systems integration framework.
 ### END INIT INFO
 
@@ -50,7 +50,7 @@ start() {
 
 stop() {
     echo -n $"Stopping $prog: "
-    killproc -p $pidfile $prog 
+    killproc -p $pidfile $prog
     retval=$?
     echo
     [ $retval -eq 0 ] && rm -f $lockfile
diff --git a/distro/redhat/etc/init.d/chef-server-webui b/distro/redhat/etc/init.d/chef-server-webui
index f26f9c1..65498bf 100644
--- a/distro/redhat/etc/init.d/chef-server-webui
+++ b/distro/redhat/etc/init.d/chef-server-webui
@@ -11,7 +11,7 @@
 # Required-Stop: $local_fs $network $remote_fs chef-server
 # Should-Start: $named $time
 # Should-Stop: $named $time
-# Short-Description: Startup script for chef-server-webui 
+# Short-Description: Startup script for chef-server-webui
 # Description: Server WebUI component of the Chef systems integration framework.
 ### END INIT INFO
 
@@ -50,7 +50,7 @@ start() {
 
 stop() {
     echo -n $"Stopping $prog: "
-    killproc -p $pidfile $prog 
+    killproc -p $pidfile $prog
     retval=$?
     echo
     [ $retval -eq 0 ] && rm -f $lockfile
diff --git a/distro/redhat/etc/init.d/chef-solr b/distro/redhat/etc/init.d/chef-solr
index b75c40f..9223173 100644
--- a/distro/redhat/etc/init.d/chef-solr
+++ b/distro/redhat/etc/init.d/chef-solr
@@ -1,5 +1,5 @@
 #!/bin/bash
-# 
+#
 # chef-solr Startup script for the SOLR search engine
 #
 # chkconfig: - 94 06
@@ -42,7 +42,7 @@ start() {
 
 stop() {
     echo -n $"Stopping $prog: "
-    killproc -p $pidfile $prog 
+    killproc -p $pidfile $prog
     retval=$?
     echo
     [ $retval -eq 0 ] && rm -f $lockfile
diff --git a/distro/windows/service_manager.rb b/distro/windows/service_manager.rb
index 8d71448..3cd3335 100644
--- a/distro/windows/service_manager.rb
+++ b/distro/windows/service_manager.rb
@@ -16,149 +16,5 @@
 # limitations under the License.
 #
 
-require 'win32/service'
-require 'rbconfig'
-require 'mixlib/cli'
-
-class Chef
-  class Windows
-    class ServiceManager
-      include Config
-      include Mixlib::CLI
-
-      option :action,
-        :short => "-a ACTION",
-        :long  => "--action ACTION",
-        :default => "start",
-        :description => "Action to carry out on the resource; one of 'install', 'uninstall', 'start', 'stop', 'pause', or 'resume'"
-
-      option :name,
-        :short => "-n NAME",
-        :long  => "--name NAME",
-        :default => "chef-client",
-        :description => "The service name to use."
-
-      option :display_name,
-        :long  => "--display_name NAME",
-        :default => "chef-client",
-        :description => "The display name to use for the service."
-
-      option :description,
-        :short => "-d DESCRIPTION",
-        :long  => "--description DESCRIPTION",
-        :default => "chef-client",
-        :description => "The description for the service."
-
-      option :config_file,
-        :short => "-c CONFIG",
-        :long  => "--config CONFIG",
-        :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb",
-        :description => "The configuration file to use"
-
-      option :log_location,
-        :short        => "-L LOGLOCATION",
-        :long         => "--logfile LOGLOCATION",
-        :description  => "Set the log file location",
-        :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
-
-      option :splay,
-        :short        => "-s SECONDS",
-        :long         => "--splay SECONDS",
-        :description  => "The splay time for running at intervals, in seconds",
-        :proc         => lambda { |s| s.to_i }
-
-      option :interval,
-        :short        => "-i SECONDS",
-        :long         => "--interval SECONDS",
-        :description  => "Set the number of seconds to wait between chef-client runs",
-        :proc         => lambda { |s| s.to_i }
-
-      option :help,
-        :short        => "-h",
-        :long         => "--help",
-        :description  => "Show this message",
-        :on           => :tail,
-        :boolean      => true,
-        :show_options => true,
-        :exit         => 0
-
-      def run
-        parse_options
-
-        case config[:action]
-        when 'install'
-          ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
-          path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'chef', 'application', 'windows_service.rb'))
-
-          opts = ""
-          opts << " -c #{config[:config_file]}" if config[:config_file]
-          opts << " -L #{config[:log_location]}" if config[:log_location]
-          opts << " -i #{config[:interval]}" if config[:interval]
-          opts << " -s #{config[:splay]}" if config[:splay]
-
-          # Quote the full paths to deal with possible spaces in the path name.
-          # Also ensure all forward slashes are backslashes
-          cmd = "\"#{ruby}\" \"#{path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR)
-
-          Win32::Service.new(
-                      :service_name     => config[:name],
-                      :display_name     => config[:display_name],
-                      :description      => config[:description],
-                      :binary_path_name => cmd)
-          puts "Service '#{config[:name]}' has successfully been 'installed'."
-        when 'start'
-          # TODO: allow override of startup parameters here?
-          take_action('start', RUNNING) if Win32::Service.exists?(config[:name])
-        when 'stop'
-          take_action('stop', STOPPED) if Win32::Service.exists?(config[:name])
-        when 'uninstall', 'delete'
-          take_action('stop', STOPPED)
-          if Win32::Service.exists?(config[:name])
-            Win32::Service.delete(config[:name])
-            puts "Service #{config[:name]} deleted"
-          end
-        when 'pause'
-          take_action('pause', PAUSED)
-        when 'resume'
-          take_action('resume', RUNNING)
-        end
-      end
-
-      private
-
-      # Just some state constants
-      STOPPED = "stopped"
-      RUNNING = "running"
-      PAUSED = "paused"
-
-      def take_action(action=nil, desired_state=nil)
-        if Win32::Service.exists?(config[:name])
-          if current_state != desired_state
-            Win32::Service.send(action, config[:name])
-            wait_for_state(desired_state)
-            puts "Service '#{config[:name]}' is now '#{current_state}'."
-          else
-            puts "Service '#{config[:name]}' is already '#{desired_state}'."
-          end
-        else
-          puts "Cannot '#{action}' service '#{config[:name]}', service does not exist."
-        end
-      end
-
-      def current_state
-        Win32::Service.status(config[:name]).current_state
-      end
-
-      # Helper method that waits for a status to change its state since state
-      # changes aren't usually instantaneous.
-      def wait_for_state(desired_state)
-        while current_state != desired_state
-          puts "One moment... #{current_state}"
-          sleep 1
-        end
-      end
-    end
-  end
-end
-
-Chef::Windows::ServiceManager.new.run
+$stderr.puts "WARNING: service_manager.rb is changed to: chef-service-manager"
+$stderr.puts "Please use chef-service-manager instead of service_manager.rb to install and manage chef-client as a windows service."
diff --git a/lib/chef.rb b/lib/chef.rb
index e56e805..3e5394b 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -27,15 +27,13 @@ require 'chef/resources'
 require 'chef/shell_out'
 
 require 'chef/daemon'
-require 'chef/webui_user'
-require 'chef/openid_registration'
 
 require 'chef/run_status'
 require 'chef/handler'
 require 'chef/handler/json_file'
 
 require 'chef/monkey_patches/tempfile'
-require 'chef/monkey_patches/dir'
 require 'chef/monkey_patches/string'
 require 'chef/monkey_patches/numeric'
 require 'chef/monkey_patches/object'
+require 'chef/monkey_patches/file'
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index f95978a..66cbd3f 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -20,9 +20,6 @@
 require 'chef/config'
 require 'chef/mixin/params_validate'
 require 'chef/mixin/from_file'
-require 'chef/couchdb'
-require 'chef/certificate'
-require 'chef/index_queue'
 require 'chef/mash'
 require 'chef/json_compat'
 require 'chef/search/query'
@@ -32,51 +29,14 @@ class Chef
 
     include Chef::Mixin::FromFile
     include Chef::Mixin::ParamsValidate
-    include Chef::IndexQueue::Indexable
-
-
-    DESIGN_DOCUMENT = {
-      "version" => 1,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "client") {
-              emit(doc.name, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "client") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        }
-      }
-    }
-
-    INDEX_OBJECT_TYPE = 'client'.freeze
-
-    def self.index_object_type
-      INDEX_OBJECT_TYPE
-    end
-
-    attr_accessor :couchdb_rev, :couchdb_id, :couchdb
 
     # Create a new Chef::ApiClient object.
-    def initialize(couchdb=nil)
+    def initialize
       @name = ''
       @public_key = nil
       @private_key = nil
-      @couchdb_rev = nil
-      @couchdb_id = nil
       @admin = false
-      @couchdb = (couchdb || Chef::CouchDB.new)
+      @validator = false
     end
 
     # Gets or sets the client name.
@@ -115,6 +75,19 @@ class Chef
       )
     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
+
     # Gets or sets the private key.
     #
     # @params [Optional String] The string representation of the private key.
@@ -127,30 +100,20 @@ class Chef
       )
     end
 
-    # Creates a new public/private key pair, and populates the public_key and
-    # private_key attributes.
-    #
-    # @return [True]
-    def create_keys
-      results = Chef::Certificate.gen_keypair(self.name)
-      self.public_key(results[0].to_s)
-      self.private_key(results[1].to_s)
-      true
-    end
-
-    # The hash representation of the object.  Includes the name and public_key,
-    # but never the private key.
+    # 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,
         "public_key" => @public_key,
+        "validator" => @validator,
         "admin" => @admin,
         'json_class' => self.class.name,
         "chef_type" => "client"
       }
-      result["_rev"] = @couchdb_rev if @couchdb_rev
+      result["private_key"] = @private_key if @private_key
       result
     end
 
@@ -164,20 +127,20 @@ class Chef
     def self.json_create(o)
       client = Chef::ApiClient.new
       client.name(o["name"] || o["clientname"])
+      client.private_key(o["private_key"]) if o.key?("private_key")
       client.public_key(o["public_key"])
       client.admin(o["admin"])
-      client.couchdb_rev = o["_rev"]
-      client.couchdb_id = o["_id"]
-      client.index_id = client.couchdb_id
+      client.validator(o["validator"])
       client
     end
 
-    # List all the Chef::ApiClient objects in the CouchDB.  If inflate is set
-    # to true, you will get the full list of all ApiClients, fully inflated.
-    def self.cdb_list(inflate=false, couchdb=nil)
-      rs = (couchdb || Chef::CouchDB.new).list("clients", inflate)
-      lookup = (inflate ? "value" : "key")
-      rs["rows"].collect { |r| r[lookup] }
+    def self.http_api
+      Chef::REST.new(Chef::Config[:chef_server_url])
+    end
+
+    def self.reregister(name)
+      api_client = load(name)
+      api_client.reregister
     end
 
     def self.list(inflate=false)
@@ -189,21 +152,13 @@ class Chef
         end
         response
       else
-        Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients")
+        http_api.get("clients")
       end
     end
 
-    # Load a client by name from CouchDB
-    #
-    # @params [String] The name of the client to load
-    # @return [Chef::ApiClient] The resulting Chef::ApiClient object
-    def self.cdb_load(name, couchdb=nil)
-      (couchdb || Chef::CouchDB.new).load("client", name)
-    end
-
     # Load a client by name via the API
     def self.load(name)
-      response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{name}")
+      response = http_api.get("clients/#{name}")
       if response.kind_of?(Chef::ApiClient)
         response
       else
@@ -213,52 +168,38 @@ class Chef
       end
     end
 
-    # Remove this client from the CouchDB
-    #
-    # @params [String] The name of the client to delete
-    # @return [Chef::ApiClient] The last version of the object
-    def cdb_destroy
-      @couchdb.delete("client", @name, @couchdb_rev)
-    end
-
     # Remove this client via the REST API
     def destroy
-      Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("clients/#{@name}")
-    end
-
-    # Save this client to the CouchDB
-    def cdb_save
-      @couchdb_rev = @couchdb.store("client", @name, self)["rev"]
+      http_api.delete("clients/#{@name}")
     end
 
     # Save this client via the REST API, returns a hash including the private key
-    def save(new_key=false, validation=false)
-      if validation
-        r = Chef::REST.new(Chef::Config[:chef_server_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key])
-      else
-        r = Chef::REST.new(Chef::Config[:chef_server_url])
-      end
-      # First, try and create a new registration
+    def save
       begin
-        r.post_rest("clients", {:name => self.name, :admin => self.admin })
+        http_api.put("clients/#{name}", { :name => self.name, :admin => self.admin, :validator => self.validator})
       rescue Net::HTTPServerException => e
         # If that fails, go ahead and try and update it
-        if e.response.code == "409"
-          r.put_rest("clients/#{name}", { :name => self.name, :admin => self.admin, :private_key => new_key })
+        if e.response.code == "404"
+          http_api.post("clients", {:name => self.name, :admin => self.admin, :validator => self.validator })
         else
           raise e
         end
       end
     end
 
-    # Create the client via the REST API
-    def create
-      Chef::REST.new(Chef::Config[:chef_server_url]).post_rest("clients", self)
+    def reregister
+      reregistered_self = http_api.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
     end
 
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      (couchdb ||= Chef::CouchDB.new).create_design_document("clients", DESIGN_DOCUMENT)
+    # Create the client via the REST API
+    def create
+      http_api.post("clients", self)
     end
 
     # As a string
@@ -266,6 +207,14 @@ class Chef
       "client[#{@name}]"
     end
 
+    def inspect
+      "Chef::ApiClient name:'#{name}' admin:'#{admin.inspect}' validator:'#{validator}' " +
+      "public_key:'#{public_key}' private_key:'#{private_key}'"
+    end
+
+    def http_api
+      @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url])
+    end
+
   end
 end
-
diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb
new file mode 100644
index 0000000..f44c326
--- /dev/null
+++ b/lib/chef/api_client/registration.rb
@@ -0,0 +1,126 @@
+#
+# Author:: Daniel DeLeo (<dan 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 'chef/config'
+require 'chef/rest'
+require 'chef/exceptions'
+
+class Chef
+  class ApiClient
+
+    # ==Chef::ApiClient::Registration
+    # Manages the process of creating or updating a Chef::ApiClient on the
+    # server and writing the resulting private key to disk. Registration uses
+    # the validator credentials for its API calls. This allows it to bootstrap
+    # a new client/node identity by borrowing the validator client identity
+    # when creating a new client.
+    class Registration
+      attr_reader :private_key
+      attr_reader :destination
+      attr_reader :name
+
+      def initialize(name, destination)
+        @name = name
+        @destination = destination
+        @private_key = nil
+      end
+
+      # Runs the client registration process, including creating the client on
+      # the chef-server and writing its private key to disk.
+      #--
+      # If client creation fails with a 5xx, it is retried up to 5 times. These
+      # retries are on top of the retries with randomized exponential backoff
+      # built in to Chef::REST. The retries here are a workaround for failures
+      # caused by resource contention in Hosted Chef when creating a very large
+      # number of clients simultaneously, (e.g., spinning up 100s of ec2 nodes
+      # at once). Future improvements to the affected component should make
+      # these retries unnecessary.
+      def run
+        assert_destination_writable!
+        retries = Config[:client_registration_retries] || 5
+        begin
+          create_or_update
+        rescue Net::HTTPFatalError => e
+          # HTTPFatalError implies 5xx.
+          raise if retries <= 0
+          retries -= 1
+          Chef::Log.warn("Failed to register new client, #{retries} tries remaining")
+          Chef::Log.warn("Response: HTTP #{e.response.code} - #{e}")
+          retry
+        end
+        write_key
+      end
+
+      def assert_destination_writable!
+        if (File.exists?(destination) && !File.writable?(destination)) or !File.writable?(File.dirname(destination))
+          raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?"
+        end
+      end
+
+      def write_key
+        ::File.open(destination, file_flags, 0600) do |f|
+          f.print(private_key)
+        end
+      rescue IOError => e
+        raise Chef::Exceptions::CannotWritePrivateKey, "Error writing private key to #{destination}: #{e}"
+      end
+
+      def create_or_update
+        create
+      rescue Net::HTTPServerException => e
+        # If create fails because the client exists, attempt to update. This
+        # requires admin privileges.
+        raise unless e.response.code == "409"
+        update
+      end
+
+      def create
+        response = http_api.post("clients", :name => name, :admin => false)
+        @private_key = response["private_key"]
+        response
+      end
+
+      def update
+        response = http_api.put("clients/#{name}", :name => name,
+                                                     :admin => false,
+                                                     :private_key => true)
+        if response.respond_to?(:private_key) # Chef 11
+          @private_key = response.private_key
+        else # Chef 10
+          @private_key = response["private_key"]
+        end
+        response
+      end
+
+      def http_api
+        @http_api_as_validator ||= Chef::REST.new(Chef::Config[:chef_server_url],
+                                                  Chef::Config[:validation_client_name],
+                                                  Chef::Config[:validation_key])
+      end
+
+      def file_flags
+        base_flags = File::CREAT|File::TRUNC|File::RDWR
+        # Windows doesn't have symlinks, so it doesn't have NOFOLLOW
+        base_flags |= File::NOFOLLOW if defined?(File::NOFOLLOW)
+        base_flags
+      end
+    end
+  end
+end
+
+
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 305b8be..c1fc3a7 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -16,6 +16,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+require 'pp'
 require 'socket'
 require 'chef/config'
 require 'chef/exceptions'
@@ -28,16 +29,11 @@ require 'rbconfig'
 class Chef::Application
   include Mixlib::CLI
 
-  class Wakeup < Exception
-  end
-
   def initialize
     super
 
-    trap("TERM") do
-      Chef::Application.fatal!("SIGTERM received, stopping", 1)
-    end
-
+    @chef_client = nil
+    @chef_client_json = nil
     trap("INT") do
       Chef::Application.fatal!("SIGINT received, stopping", 2)
     end
@@ -70,47 +66,95 @@ class Chef::Application
     run_application
   end
 
-  # Parse the configuration file
+  # Parse configuration (options and config file)
   def configure_chef
     parse_options
+    load_config_file
+  end
 
-    begin
-      case config[:config_file]
-      when /^(http|https):\/\//
-        Chef::REST.new("", nil, nil).fetch(config[:config_file]) { |f| apply_config(f.path) }
-      else
-        ::File::open(config[:config_file]) { |f| apply_config(f.path) }
-      end
-    rescue Errno::ENOENT => error
+  # Parse the config file
+  def load_config_file
+    config_fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail)
+    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?
       Chef::Log.warn("*****************************************")
       Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
       Chef::Log.warn("*****************************************")
+    else
+      config_content = config_fetcher.read_config
+      apply_config(config_content, config[:config_file])
+    end
+    Chef::Config.merge!(config)
+  end
 
-      Chef::Config.merge!(config)
-    rescue SocketError => error
-      Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", 2)
-    rescue Chef::Exceptions::ConfigurationError => error
-      Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
-    rescue Exception => error
-      Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
+  # Initialize and configure the logger.
+  # === Loggers and Formatters
+  # In Chef 10.x and previous, the Logger was the primary/only way that Chef
+  # communicated information to the user. In Chef 10.14, a new system, "output
+  # formatters" was added, and in Chef 11.0+ it is the default when running
+  # chef in a console (detected by `STDOUT.tty?`). Because output formatters
+  # are more complex than the logger system and users have less experience with
+  # them, the config option `force_logger` is provided to restore the Chef 10.x
+  # behavior.
+  #
+  # Conversely, for users who want formatter output even when chef is running
+  # unattended, the `force_formatter` option is provided.
+  #
+  # === Auto Log Level
+  # When `log_level` is set to `:auto` (default), the log level will be `:warn`
+  # when the primary output mode is an output formatter (see
+  # +using_output_formatter?+) and `:info` otherwise.
+  #
+  # === Automatic STDOUT Logging
+  # When `force_logger` is configured (e.g., Chef 10 mode), a second logger
+  # with output on STDOUT is added when running in a console (STDOUT is a tty)
+  # and the configured log_location isn't STDOUT. This accounts for the case
+  # that a user has configured a log_location in client.rb, but is running
+  # chef-client by hand to troubleshoot a problem.
+  def configure_logging
+    Chef::Log.init(MonoLogger.new(Chef::Config[:log_location]))
+    if want_additional_logger?
+      configure_stdout_logger
     end
+    Chef::Log.level = resolve_log_level
+  end
 
+  def configure_stdout_logger
+    stdout_logger = MonoLogger.new(STDOUT)
+    STDOUT.sync = true
+    stdout_logger.formatter = Chef::Log.logger.formatter
+    Chef::Log.loggers <<  stdout_logger
   end
 
-  # Initialize and configure the logger. If the configured log location is not
-  # STDOUT, but stdout is a TTY and we're not daemonizing, we set up a secondary
-  # logger with output to stdout. This way, we magically do the right thing when
-  # the user has configured logging to a file but they're running chef in the
-  # shell to debug something.
-  def configure_logging
-    Chef::Log.init(Chef::Config[:log_location])
-    if ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize])
-      stdout_logger = Logger.new(STDOUT)
-      STDOUT.sync = true
-      stdout_logger.formatter = Chef::Log.logger.formatter
-      Chef::Log.loggers <<  stdout_logger
+  # Based on config and whether or not STDOUT is a tty, should we setup a
+  # secondary logger for stdout?
+  def want_additional_logger?
+    ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger])
+  end
+
+  # Use of output formatters is assumed if `force_formatter` is set or if
+  # `force_logger` is not set and STDOUT is to a console (tty)
+  def using_output_formatter?
+    Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?)
+  end
+
+  def auto_log_level?
+    Chef::Config[:log_level] == :auto
+  end
+
+  # if log_level is `:auto`, convert it to :warn (when using output formatter)
+  # or :info (no output formatter). See also +using_output_formatter?+
+  def resolve_log_level
+    if auto_log_level?
+      if using_output_formatter?
+        :warn
+      else
+        :info
+      end
+    else
+      Chef::Config[:log_level]
     end
-    Chef::Log.level = Chef::Config[:log_level]
   end
 
   # Called prior to starting the application, by the run method
@@ -123,13 +167,60 @@ class Chef::Application
     raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application"
   end
 
-  private
+  def self.setup_server_connectivity
+    if Chef::Config.chef_zero.enabled
+      destroy_server_connectivity
+
+      require 'chef_zero/server'
+      require 'chef/chef_fs/chef_fs_data_store'
+      require 'chef/chef_fs/config'
+
+      chef_fs = Chef::ChefFS::Config.new.local_fs
+      chef_fs.write_pretty_json = true
+      server_options = {}
+      server_options[:data_store] = Chef::ChefFS::ChefFSDataStore.new(chef_fs)
+      server_options[:log_level] = Chef::Log.level
+      server_options[:port] = Chef::Config.chef_zero.port
+      Chef::Log.info("Starting chef-zero on port #{Chef::Config.chef_zero.port} with repository at #{server_options[:data_store].chef_fs.fs_description}")
+      @chef_zero_server = ChefZero::Server.new(server_options)
+      @chef_zero_server.start_background
+      Chef::Config.chef_server_url = @chef_zero_server.url
+    end
+  end
 
-  def apply_config(config_file_path)
-    Chef::Config.from_file(config_file_path)
-    Chef::Config.merge!(config)
+  def self.destroy_server_connectivity
+    if @chef_zero_server
+      @chef_zero_server.stop
+      @chef_zero_server = nil
+    end
   end
 
+  # Initializes Chef::Client instance and runs it
+  def run_chef_client
+    Chef::Application.setup_server_connectivity
+
+    @chef_client = Chef::Client.new(
+      @chef_client_json,
+      :override_runlist => config[:override_runlist]
+    )
+    @chef_client_json = nil
+
+    @chef_client.run
+    @chef_client = nil
+
+    Chef::Application.destroy_server_connectivity
+  end
+
+  private
+
+  def apply_config(config_content, config_file_path)
+    Chef::Config.from_string(config_content, config_file_path)
+  rescue Exception => error
+    Chef::Log.fatal("Configuration error #{error.class}: #{error.message}")
+    filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
+    filtered_trace.each {|line| Chef::Log.fatal("  " + line )}
+    Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
+  end
 
   class << self
     def debug_stacktrace(e)
diff --git a/lib/chef/application/agent.rb b/lib/chef/application/agent.rb
index 353d452..66b0c25 100644
--- a/lib/chef/application/agent.rb
+++ b/lib/chef/application/agent.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
new file mode 100644
index 0000000..9bffa48
--- /dev/null
+++ b/lib/chef/application/apply.rb
@@ -0,0 +1,162 @@
+#
+# Author:: Bryan W. Berry (<bryan.berry at gmail.com>)
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2012 Bryan W. Berry
+# Copyright:: Copyright (c) 2012 Daniel DeLeo
+# 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'
+require 'chef/application'
+require 'chef/client'
+require 'chef/config'
+require 'chef/log'
+require 'fileutils'
+require 'tempfile'
+require 'chef/providers'
+require 'chef/resources'
+
+class Chef::Application::Apply < Chef::Application
+
+  banner "Usage: chef-apply [RECIPE_FILE] [-e RECIPE_TEXT] [-s]"
+
+
+  option :execute,
+    :short        => "-e RECIPE_TEXT",
+    :long         => "--execute RECIPE_TEXT",
+    :description  => "Execute resources supplied in a string",
+    :proc         => nil
+
+  option :stdin,
+    :short        => "-s",
+    :long         => "--stdin",
+    :description  => "Execute resources read from STDIN",
+    :boolean      => true
+
+  option :log_level,
+    :short        => "-l LEVEL",
+    :long         => "--log_level LEVEL",
+    :description  => "Set the log level (debug, info, warn, error, fatal)",
+    :proc         => lambda { |l| l.to_sym }
+
+  option :help,
+    :short        => "-h",
+    :long         => "--help",
+    :description  => "Show this message",
+    :on           => :tail,
+    :boolean      => true,
+    :show_options => true,
+    :exit         => 0
+
+
+  option :version,
+    :short        => "-v",
+    :long         => "--version",
+    :description  => "Show chef version",
+    :boolean      => true,
+    :proc         => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
+    :exit         => 0
+
+  option :why_run,
+    :short        => '-W',
+    :long         => '--why-run',
+    :description  => 'Enable whyrun mode',
+    :boolean      => true
+
+  def initialize
+    super
+  end
+
+  def reconfigure
+    parse_options
+    Chef::Config.merge!(config)
+    configure_logging
+  end
+
+  def read_recipe_file(file_name)
+    recipe_path = file_name
+    unless File.exist?(recipe_path)
+      Chef::Application.fatal!("No file exists at #{recipe_path}", 1)
+    end
+    recipe_path = File.expand_path(recipe_path)
+    recipe_fh = open(recipe_path)
+    recipe_text = recipe_fh.read
+    [recipe_text, recipe_fh]
+  end
+
+  def get_recipe_and_run_context
+    Chef::Config[:solo] = true
+    @chef_client = Chef::Client.new
+    @chef_client.run_ohai
+    @chef_client.load_node
+    @chef_client.build_node
+    run_context = if @chef_client.events.nil?
+                    Chef::RunContext.new(@chef_client.node, {})
+                  else
+                    Chef::RunContext.new(@chef_client.node, {}, @chef_client.events)
+                  end
+    recipe = Chef::Recipe.new("(chef-apply cookbook)", "(chef-apply recipe)", run_context)
+    [recipe, run_context]
+  end
+
+  # write recipe to temp file, so in case of error,
+  # user gets error w/ context
+  def temp_recipe_file
+    @recipe_fh = Tempfile.open('recipe-temporary-file')
+    @recipe_fh.write(@recipe_text)
+    @recipe_fh.rewind
+    @recipe_filename = @recipe_fh.path
+  end
+
+  def run_chef_recipe
+    if config[:execute]
+      @recipe_text = config[:execute]
+      temp_recipe_file
+    elsif config[:stdin]
+      @recipe_text = STDIN.read
+      temp_recipe_file
+    else
+      @recipe_filename = ARGV[0]
+      @recipe_text, at recipe_fh = read_recipe_file @recipe_filename
+    end
+    recipe,run_context = get_recipe_and_run_context
+    recipe.instance_eval(@recipe_text, @recipe_filename, 1)
+    runner = Chef::Runner.new(run_context)
+    begin
+      runner.converge
+    ensure
+      @recipe_fh.close
+    end
+  end
+
+  def run_application
+    begin
+      parse_options
+      run_chef_recipe
+      Chef::Application.exit! "Exiting", 0
+    rescue SystemExit => e
+      raise
+    rescue Exception => e
+      Chef::Application.debug_stacktrace(e)
+      Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+    end
+  end
+
+    # Get this party started
+  def run
+    reconfigure
+    run_application
+  end
+
+end
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index e6806c0..a04b4f1 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -22,7 +22,7 @@ require 'chef/client'
 require 'chef/config'
 require 'chef/daemon'
 require 'chef/log'
-require 'chef/rest'
+require 'chef/config_fetcher'
 require 'chef/handler/error_report'
 
 
@@ -34,9 +34,32 @@ class Chef::Application::Client < Chef::Application
   option :config_file,
     :short => "-c CONFIG",
     :long  => "--config CONFIG",
-    :default => Chef::Config.platform_specific_path("/etc/chef/client.rb"),
     :description => "The configuration file to use"
 
+  option :formatter,
+    :short        => "-F FORMATTER",
+    :long         => "--format FORMATTER",
+    :description  => "output format to use",
+    :proc         => lambda { |format| Chef::Config.add_formatter(format) }
+
+  option :force_logger,
+    :long         => "--force-logger",
+    :description  => "Use logger output instead of formatter output",
+    :boolean      => true,
+    :default      => false
+
+  option :force_formatter,
+    :long         => "--force-formatter",
+    :description  => "Use formatter output instead of logger output",
+    :boolean      => true,
+    :default      => false
+
+  option :color,
+    :long         => '--[no-]color',
+    :boolean      => true,
+    :default      => !Chef::Platform.windows?,
+    :description  => "Use colored output, defaults to false on Windows, true otherwise"
+
   option :log_level,
     :short        => "-l LEVEL",
     :long         => "--log_level LEVEL",
@@ -70,11 +93,13 @@ class Chef::Application::Client < Chef::Application
     :description => "Group to set privilege to",
     :proc => nil
 
-  option :daemonize,
-    :short => "-d",
-    :long => "--daemonize",
-    :description => "Daemonize the process",
-    :proc => lambda { |p| true }
+  unless Chef::Platform.windows?
+    option :daemonize,
+      :short => "-d",
+      :long => "--daemonize",
+      :description => "Daemonize the process",
+      :proc => lambda { |p| true }
+  end
 
   option :pid_file,
     :short        => "-P PID_FILE",
@@ -153,13 +178,51 @@ class Chef::Application::Client < Chef::Application
       }
     }
 
+  option :why_run,
+    :short        => '-W',
+    :long         => '--why-run',
+    :description  => 'Enable whyrun mode',
+    :boolean      => true
+
+  option :client_fork,
+    :short        => "-f",
+    :long         => "--[no-]fork",
+    :description  => "Fork client",
+    :boolean      => true
+
+  option :enable_reporting,
+    :short        => "-R",
+    :long         => "--enable-reporting",
+    :description  => "Enable reporting data collection for chef runs",
+    :boolean      => true
+
+  option :local_mode,
+    :short        => "-z",
+    :long         => "--local-mode",
+    :description  => "Point chef-client at local repository",
+    :boolean      => true
+
+  option :chef_zero_port,
+    :long         => "--chef-zero-port PORT",
+    :description  => "Port to start chef-zero on"
+
+  option :config_file_jail,
+    :long         => "--config-file-jail PATH",
+    :description  => "Directory under which config files are allowed to be loaded (no client.rb or knife.rb outside this path will be loaded)."
+
+  if Chef::Platform.windows?
+    option :fatal_windows_admin_check,
+      :short        => "-A",
+      :long         => "--fatal-windows-admin-check",
+      :description  => "Fail the run when chef-client doesn't have administrator privileges on Windows",
+      :boolean      => true
+  end
+
   attr_reader :chef_client_json
 
   def initialize
     super
-
-    @chef_client = nil
-    @chef_client_json = nil
+    @exit_gracefully = false
   end
 
   # Reconfigure the chef client
@@ -168,9 +231,12 @@ class Chef::Application::Client < Chef::Application
     super
 
     Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
-    unless Chef::Config[:exception_handlers].any? {|h| Chef::Handler::ErrorReport === h}
-      Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new
+
+    Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+    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
+    Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port]
 
     if Chef::Config[:daemonize]
       Chef::Config[:interval] ||= 1800
@@ -182,31 +248,22 @@ class Chef::Application::Client < Chef::Application
     end
 
     if Chef::Config[:json_attribs]
-      begin
-        json_io = case Chef::Config[:json_attribs]
-                  when /^(http|https):\/\//
-                    @rest = Chef::REST.new(Chef::Config[:json_attribs], nil, nil)
-                    @rest.get_rest(Chef::Config[:json_attribs], true).open
-                  else
-                    open(Chef::Config[:json_attribs])
-                  end
-      rescue SocketError => error
-        Chef::Application.fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2)
-      rescue Errno::ENOENT => error
-        Chef::Application.fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2)
-      rescue Errno::EACCES => error
-        Chef::Application.fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2)
-      rescue Exception => error
-        Chef::Application.fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2)
-      end
+      config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
+      @chef_client_json = config_fetcher.fetch_json
+    end
+  end
 
-      begin
-        @chef_client_json = Chef::JSONCompat.from_json(json_io.read)
-        json_io.close unless json_io.closed?
-      rescue JSON::ParserError => error
-        Chef::Application.fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2)
+  def load_config_file
+    Chef::Config.config_file_jail = config[:config_file_jail] if config[:config_file_jail]
+    if !config.has_key?(:config_file)
+      if config[:local_mode]
+        require 'chef/knife'
+        config[:config_file] = Chef::Knife.locate_config_file
+      else
+        config[:config_file] = Chef::Config.platform_specific_path("/etc/chef/client.rb")
       end
     end
+    super
   end
 
   def configure_logging
@@ -228,6 +285,12 @@ class Chef::Application::Client < Chef::Application
         Chef::Log.info("SIGUSR1 received, waking up")
         SELF_PIPE[1].putc('.') # wakeup master process from select
       end
+
+      trap("TERM") do
+        Chef::Log.info("SIGTERM received, exiting gracefully")
+        @exit_gracefully = true
+        SELF_PIPE[1].putc('.')
+      end
     end
 
     if Chef::Config[:version]
@@ -240,19 +303,13 @@ class Chef::Application::Client < Chef::Application
 
     loop do
       begin
+        Chef::Application.exit!("Exiting", 0) if @exit_gracefully
         if Chef::Config[:splay]
           splay = rand Chef::Config[:splay]
           Chef::Log.debug("Splay sleep #{splay} seconds")
           sleep splay
         end
-        @chef_client = Chef::Client.new(
-          @chef_client_json, 
-          :override_runlist => config[:override_runlist]
-        )
-        @chef_client_json = nil
-
-        @chef_client.run
-        @chef_client = nil
+        run_chef_client
         if Chef::Config[:interval]
           Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
           unless SELF_PIPE.empty?
@@ -264,15 +321,11 @@ class Chef::Application::Client < Chef::Application
         else
           Chef::Application.exit! "Exiting", 0
         end
-      rescue Chef::Application::Wakeup => e
-        Chef::Log.debug("Received Wakeup signal.  Starting run.")
-        next
       rescue SystemExit => e
         raise
       rescue Exception => e
         if Chef::Config[:interval]
           Chef::Log.error("#{e.class}: #{e}")
-          Chef::Application.debug_stacktrace(e)
           Chef::Log.error("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
           unless SELF_PIPE.empty?
             client_sleep Chef::Config[:interval]
@@ -282,11 +335,8 @@ class Chef::Application::Client < Chef::Application
           end
           retry
         else
-          Chef::Application.debug_stacktrace(e)
           Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
         end
-      ensure
-        GC.start
       end
     end
   end
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
index 629cd9f..3a9cd4e 100644
--- a/lib/chef/application/knife.rb
+++ b/lib/chef/application/knife.rb
@@ -43,8 +43,8 @@ class Chef::Application::Knife < Chef::Application
   option :color,
     :long         => '--[no-]color',
     :boolean      => true,
-    :default      => true,
-    :description  => "Use colored output, defaults to enabled"
+    :default      => !Chef::Platform.windows?,
+    :description  => "Use colored output, defaults to false on Windows, true otherwise"
 
   option :environment,
     :short        => "-E ENVIRONMENT",
@@ -106,6 +106,16 @@ class Chef::Application::Knife < Chef::Application
     :description => "Which format to use for output",
     :default => "summary"
 
+  option :local_mode,
+    :short        => "-z",
+    :long         => "--local-mode",
+    :description  => "Point knife commands at local repository instead of server",
+    :boolean      => true
+
+  option :chef_zero_port,
+    :long         => "--chef-zero-port PORT",
+    :description  => "Port to start chef-zero on"
+
   option :version,
     :short        => "-v",
     :long         => "--version",
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index 1844880..47825a9 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -23,7 +23,7 @@ require 'chef/config'
 require 'chef/daemon'
 require 'chef/log'
 require 'chef/rest'
-require 'open-uri'
+require 'chef/config_fetcher'
 require 'fileutils'
 
 class Chef::Application::Solo < Chef::Application
@@ -31,9 +31,33 @@ class Chef::Application::Solo < Chef::Application
   option :config_file,
     :short => "-c CONFIG",
     :long  => "--config CONFIG",
-    :default => Chef::Config.platform_specfic_path('/etc/chef/solo.rb'),
+    :default => Chef::Config.platform_specific_path('/etc/chef/solo.rb'),
     :description => "The configuration file to use"
 
+  option :formatter,
+    :short        => "-F FORMATTER",
+    :long         => "--format FORMATTER",
+    :description  => "output format to use",
+    :proc         => lambda { |format| Chef::Config.add_formatter(format) }
+
+  option :force_logger,
+    :long         => "--force-logger",
+    :description  => "Use logger output instead of formatter output",
+    :boolean      => true,
+    :default      => false
+
+  option :force_formatter,
+    :long         => "--force-formatter",
+    :description  => "Use formatter output instead of logger output",
+    :boolean      => true,
+    :default      => false
+
+  option :color,
+    :long         => '--[no-]color',
+    :boolean      => true,
+    :default      => true,
+    :description  => "Use colored output, defaults to enabled"
+
   option :log_level,
     :short        => "-l LEVEL",
     :long         => "--log_level LEVEL",
@@ -67,11 +91,13 @@ class Chef::Application::Solo < Chef::Application
     :description => "Group to set privilege to",
     :proc => nil
 
-  option :daemonize,
-    :short => "-d",
-    :long => "--daemonize",
-    :description => "Daemonize the process",
-    :proc => lambda { |p| true }
+  unless Chef::Platform.windows?
+    option :daemonize,
+      :short => "-d",
+      :long => "--daemonize",
+      :description => "Daemonize the process",
+      :proc => lambda { |p| true }
+  end
 
   option :interval,
     :short => "-i SECONDS",
@@ -122,12 +148,27 @@ class Chef::Application::Solo < Chef::Application
       }
     }
 
-  attr_reader :chef_solo_json
+  option :client_fork,
+    :short        => "-f",
+    :long         => "--[no-]fork",
+    :description  => "Fork client",
+    :boolean      => true
+
+  option :why_run,
+    :short        => '-W',
+    :long         => '--why-run',
+    :description  => 'Enable whyrun mode',
+    :boolean      => true
+
+  option :environment,
+    :short        => '-E ENVIRONMENT',
+    :long         => '--environment ENVIRONMENT',
+    :description  => 'Set the Chef Environment on the node'
+
+  attr_reader :chef_client_json
 
   def initialize
     super
-    @chef_solo = nil
-    @chef_solo_json = nil
   end
 
   def reconfigure
@@ -140,36 +181,13 @@ class Chef::Application::Solo < Chef::Application
     end
 
     if Chef::Config[:json_attribs]
-      begin
-        json_io = case Chef::Config[:json_attribs]
-                  when /^(http|https):\/\//
-                    @rest = Chef::REST.new(Chef::Config[:json_attribs], nil, nil)
-                    @rest.get_rest(Chef::Config[:json_attribs], true).open
-                  else
-                    open(Chef::Config[:json_attribs])
-                  end
-      rescue SocketError => error
-        Chef::Application.fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2)
-      rescue Errno::ENOENT => error
-        Chef::Application.fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2)
-      rescue Errno::EACCES => error
-        Chef::Application.fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2)
-      rescue Exception => error
-        Chef::Application.fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2)
-      end
-
-      begin
-        @chef_solo_json = Chef::JSONCompat.from_json(json_io.read)
-        json_io.close unless json_io.closed?
-      rescue JSON::ParserError => error
-        Chef::Application.fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2)
-      end
+      config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
+      @chef_client_json = config_fetcher.fetch_json
     end
 
     if Chef::Config[:recipe_url]
       cookbooks_path = Array(Chef::Config[:cookbook_path]).detect{|e| e =~ /\/cookbooks\/*$/ }
       recipes_path = File.expand_path(File.join(cookbooks_path, '..'))
-      target_file = File.join(recipes_path, 'recipes.tgz')
 
       Chef::Log.debug "Creating path #{recipes_path} to extract recipes into"
       FileUtils.mkdir_p recipes_path
@@ -179,7 +197,7 @@ class Chef::Application::Solo < Chef::Application
           f.write(r.read)
         end
       end
-      Chef::Mixin::Command.run_command(:command => "tar zxvfC #{path} #{recipes_path}")
+      Chef::Mixin::Command.run_command(:command => "tar zxvf #{path} -C #{recipes_path}")
     end
   end
 
@@ -200,12 +218,7 @@ class Chef::Application::Solo < Chef::Application
           sleep splay
         end
 
-        @chef_solo = Chef::Client.new(
-          @chef_solo_json, 
-          :override_runlist => config[:override_runlist]
-        )
-        @chef_solo.run
-        @chef_solo = nil
+        run_chef_client
         if Chef::Config[:interval]
           Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
           sleep Chef::Config[:interval]
@@ -222,12 +235,10 @@ class Chef::Application::Solo < Chef::Application
           sleep Chef::Config[:interval]
           retry
         else
-          Chef::Application.debug_stacktrace(e)
           Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
         end
-      ensure
-        GC.start
       end
     end
   end
+
 end
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
index dbef443..4c99e50 100644
--- a/lib/chef/application/windows_service.rb
+++ b/lib/chef/application/windows_service.rb
@@ -16,6 +16,8 @@
 # limitations under the License.
 #
 
+require 'chef'
+require 'chef/monologger'
 require 'chef/application'
 require 'chef/client'
 require 'chef/config'
@@ -25,11 +27,13 @@ require 'chef/rest'
 require 'mixlib/cli'
 require 'socket'
 require 'win32/daemon'
+require 'chef/mixin/shell_out'
 
 class Chef
   class Application
     class WindowsService < ::Win32::Daemon
       include Mixlib::CLI
+      include Chef::Mixin::ShellOut
 
       option :config_file,
         :short => "-c CONFIG",
@@ -55,67 +59,57 @@ class Chef
         :description  => "Set the number of seconds to wait between chef-client runs",
         :proc         => lambda { |s| s.to_i }
 
-      option :override_runlist,
-        :short        => "-o RunlistItem,RunlistItem...",
-        :long         => "--override-runlist RunlistItem,RunlistItem...",
-        :description  => "Replace current run list with specified items",
-        :proc         => lambda{|items|
-          items = items.split(',')
-          items.compact.map{|item|
-            Chef::RunList::RunListItem.new(item)
-          }
-        }
-
       def service_init
+        @service_action_mutex = Mutex.new
+        @service_signal = ConditionVariable.new
+
         reconfigure
         Chef::Log.info("Chef Client Service initialized")
       end
 
       def service_main(*startup_parameters)
+        # Chef::Config is initialized during service_init
+        # Set the initial timeout to splay sleep time
+        timeout = rand Chef::Config[:splay]
 
-        while running?
-          if state == RUNNING
+        while running? do
+          # Grab the service_action_mutex to make a chef-client run
+          @service_action_mutex.synchronize do
             begin
+              Chef::Log.info("Next chef-client run will happen in #{timeout} seconds")
+              @service_signal.wait(@service_action_mutex, timeout)
+
+              # Continue only if service is RUNNING
+              next if state != RUNNING
+
               # Reconfigure each time through to pick up any changes in the client file
               Chef::Log.info("Reconfiguring with startup parameters")
               reconfigure(startup_parameters)
+              timeout = Chef::Config[:interval]
 
-              splay = rand Chef::Config[:splay]
-              Chef::Log.debug("Splay sleep #{splay} seconds")
-              sleep splay
+              # Honor splay sleep config
+              timeout += rand Chef::Config[:splay]
 
-              # If we've stopped, then bail out now, instead of going on to run Chef
+              # run chef-client only if service is in RUNNING state
               next if state != RUNNING
 
-              @chef_client = Chef::Client.new(
-                @chef_client_json, 
-                :override_runlist => config[:override_runlist]
-              )
-              @chef_client_json = nil
-
-              @chef_client.run
-              @chef_client = nil
-
-              Chef::Log.debug("Sleeping for #{Chef::Config[:interval]} seconds")
-              client_sleep Chef::Config[:interval]
-            rescue Chef::Application::Wakeup => e
-              Chef::Log.debug("Received Wakeup signal.  Starting run.")
-              next
+              Chef::Log.info("Chef-Client service is starting a chef-client run...")
+              run_chef_client
             rescue SystemExit => e
-              raise
+              # Do not raise any of the errors here in order to
+              # prevent service crash
+              Chef::Log.error("#{e.class}: #{e}")
             rescue Exception => e
               Chef::Log.error("#{e.class}: #{e}")
-              Chef::Application.debug_stacktrace(e)
-              Chef::Log.error("Sleeping for #{Chef::Config[:interval]} seconds before trying again")
-              client_sleep Chef::Config[:interval]
-              retry
-            ensure
-              GC.start
             end
-          else # PAUSED or IDLE
-            sleep 5
           end
         end
+
+        # Daemon class needs to have all the signal callbacks return
+        # before service_main returns.
+        Chef::Log.debug("Giving signal callbacks some time to exit...")
+        sleep 1
+        Chef::Log.debug("Exiting service...")
       end
 
       ################################################################################
@@ -123,19 +117,57 @@ class Chef
       ################################################################################
 
       def service_stop
-        Chef::Log.info("SERVICE_CONTROL_STOP received, stopping")
+        run_warning_displayed = false
+        Chef::Log.info("STOP request from operating system.")
+        loop do
+          # See if a run is in flight
+          if @service_action_mutex.try_lock
+            # Run is not in flight. Wake up service_main to exit.
+            @service_signal.signal
+            @service_action_mutex.unlock
+            break
+          else
+            unless run_warning_displayed
+              Chef::Log.info("Currently a chef run is happening on this system.")
+              Chef::Log.info("Service  will stop when run is completed.")
+              run_warning_displayed = true
+            end
+
+            Chef::Log.debug("Waiting for chef-client run...")
+            sleep 1
+          end
+        end
+        Chef::Log.info("Service is stopping....")
       end
 
       def service_pause
-        Chef::Log.info("SERVICE_CONTROL_PAUSE received, pausing")
+        Chef::Log.info("PAUSE request from operating system.")
+
+        # We don't need to wake up the service_main if it's waiting
+        # since this is a PAUSE signal.
+
+        if @service_action_mutex.locked?
+          Chef::Log.info("Currently a chef-client run is happening.")
+          Chef::Log.info("Service will pause once it's completed.")
+        else
+          Chef::Log.info("Service is pausing....")
+        end
       end
 
       def service_resume
-        Chef::Log.info("SERVICE_CONTROL_CONTINUE received, resuming")
+        # We don't need to wake up the service_main if it's waiting
+        # since this is a RESUME signal.
+
+        Chef::Log.info("RESUME signal received from the OS.")
+        Chef::Log.info("Service is resuming....")
       end
 
       def service_shutdown
-        Chef::Log.info("SERVICE_CONTROL_SHUTDOWN received, shutting down")
+        Chef::Log.info("SHUTDOWN signal received from the OS.")
+
+        # Treat shutdown similar to stop.
+
+        service_stop
       end
 
       ################################################################################
@@ -144,6 +176,31 @@ class Chef
 
       private
 
+      # Initializes Chef::Client instance and runs it
+      def run_chef_client
+        # The chef client will be started in a new process. We have used shell_out to start the chef-client.
+        # The log_location and config_file of the parent process is passed to the new chef-client process.
+        # We need to add the --no-fork, as by default it is set to fork=true.
+        begin
+          Chef::Log.info "Starting chef-client in a new process"
+          # Pass config params to the new process
+          config_params = " --no-fork"
+          config_params += " -c #{Chef::Config[:config_file]}" unless  Chef::Config[:config_file].nil?
+          config_params += " -L #{Chef::Config[:log_location]}" unless Chef::Config[:log_location] == STDOUT
+          # Starts a new process and waits till the process exits
+          result = shell_out("chef-client #{config_params}")
+          Chef::Log.debug "#{result.stdout}"
+          Chef::Log.debug "#{result.stderr}"
+        rescue Mixlib::ShellOut::ShellCommandFailed => e
+          Chef::Log.warn "Not able to start chef-client in new process (#{e})"
+        rescue => e
+          Chef::Log.error e
+        ensure
+          # Once process exits, we log the current process' pid
+          Chef::Log.info "Child process exited (pid: #{Process.pid})"
+        end
+      end
+
       def apply_config(config_file_path)
         Chef::Config.from_file(config_file_path)
         Chef::Config.merge!(config)
@@ -163,22 +220,58 @@ class Chef
         Chef::Config[:interval] ||= 1800
       end
 
-      # Lifted from Chef::Application and Chef::Application::Client
-      # MUST BE RUN AFTER configuration has been parsed!
+      # Lifted from application.rb
+      # See application.rb for related comments.
+
       def configure_logging
-        # Implementation from Chef::Application
-        Chef::Log.init(Chef::Config[:log_location])
-        Chef::Log.level = Chef::Config[:log_level]
+        Chef::Log.init(MonoLogger.new(Chef::Config[:log_location]))
+        if want_additional_logger?
+          configure_stdout_logger
+        end
+        Chef::Log.level = resolve_log_level
+      end
+
+      def configure_stdout_logger
+        stdout_logger = MonoLogger.new(STDOUT)
+        STDOUT.sync = true
+        stdout_logger.formatter = Chef::Log.logger.formatter
+        Chef::Log.loggers <<  stdout_logger
+      end
+
+      # Based on config and whether or not STDOUT is a tty, should we setup a
+      # secondary logger for stdout?
+      def want_additional_logger?
+        ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger])
+      end
 
-        # Implementation from Chef::Application::Client
-        Mixlib::Authentication::Log.use_log_devices( Chef::Log )
-        Ohai::Log.use_log_devices( Chef::Log )
+      # Use of output formatters is assumed if `force_formatter` is set or if
+      # `force_logger` is not set and STDOUT is to a console (tty)
+      def using_output_formatter?
+        Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?)
+      end
+
+      def auto_log_level?
+        Chef::Config[:log_level] == :auto
+      end
+
+      # if log_level is `:auto`, convert it to :warn (when using output formatter)
+      # or :info (no output formatter). See also +using_output_formatter?+
+      def resolve_log_level
+        if auto_log_level?
+          if using_output_formatter?
+            :warn
+          else
+            :info
+          end
+        else
+          Chef::Config[:log_level]
+        end
       end
 
       def configure_chef(startup_parameters)
         # Bit of a hack ahead:
         # It is possible to specify a service's binary_path_name with arguments, like "foo.exe -x argX".
-        # It is also possible to specify startup parameters separately, either via the the Services manager
+        # It is also possible to specify startup parameters separately, either via the Services manager
         # or by using the registry (I think).
 
         # In order to accommodate all possible sources of parameterization, we first parse any command line
@@ -213,20 +306,6 @@ class Chef
         end
       end
 
-      # Since we need to be able to respond to signals between Chef runs, we need to periodically
-      # wake up to see if we're still in the running state.  The method returns when it has slept
-      # for +sec+ seconds (but at least +10+ seconds), or when the service
-      # is no client_sleep in the +RUNNING+ state, whichever comes first.
-      def client_sleep(sec)
-        chunk_length = 10
-        chunks = sec / chunk_length
-        chunks = 1 if chunks < 1
-        (1..chunks).each do
-          return unless state == RUNNING
-          sleep chunk_length
-        end
-      end
-
     end
   end
 end
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
new file mode 100644
index 0000000..493f0df
--- /dev/null
+++ b/lib/chef/application/windows_service_manager.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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 'win32/service'
+require 'chef/config'
+require 'mixlib/cli'
+
+class Chef
+  class Application
+    #
+    # This class is used to create and manage a windows service.
+    # Service should be created using Daemon class from
+    # win32/service gem.
+    # For an example see: Chef::Application::WindowsService
+    #
+    # Outside programs are expected to use this class to manage
+    # windows services.
+    #
+    class WindowsServiceManager
+      include Mixlib::CLI
+
+      option :action,
+        :short => "-a ACTION",
+        :long  => "--action ACTION",
+        :default => "status",
+        :description => "Action to carry out on chef-service (install, uninstall, status, start, stop, pause, or resume)"
+
+      option :config_file,
+        :short => "-c CONFIG",
+        :long  => "--config CONFIG",
+        :default => "#{ENV['SYSTEMDRIVE']}/chef/client.rb",
+        :description => "The configuration file to use for chef runs"
+
+      option :log_location,
+        :short        => "-L LOGLOCATION",
+        :long         => "--logfile LOGLOCATION",
+        :description  => "Set the log file location for chef-service",
+        :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+
+      option :help,
+        :short        => "-h",
+        :long         => "--help",
+        :description  => "Show this message",
+        :on           => :tail,
+        :boolean      => true,
+        :show_options => true,
+        :exit         => 0
+
+      option :version,
+        :short        => "-v",
+        :long         => "--version",
+        :description  => "Show chef version",
+        :boolean      => true,
+        :proc         => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
+        :exit         => 0
+
+      def initialize(service_options)
+        # having to call super in initialize is the most annoying
+        # anti-pattern :(
+        super()
+
+        raise ArgumentError, "Service definition is not provided" if service_options.nil?
+
+        required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path]
+
+        required_options.each do |req_option|
+          if !service_options.has_key?(req_option)
+            raise ArgumentError, "Service definition doesn't contain required option #{req_option}"
+          end
+        end
+
+        @service_name = service_options[:service_name]
+        @service_display_name = service_options[:service_display_name]
+        @service_description = service_options[:service_description]
+        @service_file_path = service_options[:service_file_path]
+      end
+
+      def run(params = ARGV)
+        parse_options(params)
+
+        case config[:action]
+        when 'install'
+          if service_exists?
+            puts "Service #{@service_name} already exists on the system."
+          else
+            ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
+
+            opts = ""
+            opts << " -c #{config[:config_file]}" if config[:config_file]
+            opts << " -L #{config[:log_location]}" if config[:log_location]
+
+            # Quote the full paths to deal with possible spaces in the path name.
+            # Also ensure all forward slashes are backslashes
+            cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR)
+
+            ::Win32::Service.new(
+                                 :service_name     => @service_name,
+                                 :display_name     => @service_display_name,
+                                 :description      => @service_description,
+                                 :start_type       => ::Win32::Service::SERVICE_AUTO_START,
+                                 :binary_path_name => cmd
+                                 )
+            puts "Service '#{@service_name}' has successfully been installed."
+          end
+        when 'status'
+          if !service_exists?
+            puts "Service #{@service_name} doesn't exist on the system."
+          else
+            puts "State of #{@service_name} service is: #{current_state}"
+          end
+        when 'start'
+          # TODO: allow override of startup parameters here?
+          take_action('start', RUNNING)
+        when 'stop'
+          take_action('stop', STOPPED)
+        when 'uninstall', 'delete'
+          take_action('stop', STOPPED)
+          unless service_exists?
+            puts "Service #{@service_name} doesn't exist on the system."
+          else
+            ::Win32::Service.delete(@service_name)
+            puts "Service #{@service_name} deleted"
+          end
+        when 'pause'
+          take_action('pause', PAUSED)
+        when 'resume'
+          take_action('resume', RUNNING)
+        end
+      end
+
+      private
+
+      # Just some state constants
+      STOPPED = "stopped"
+      RUNNING = "running"
+      PAUSED = "paused"
+
+      def service_exists?
+        return ::Win32::Service.exists?(@service_name)
+      end
+
+      def take_action(action=nil, desired_state=nil)
+        if service_exists?
+          if current_state != desired_state
+            ::Win32::Service.send(action, @service_name)
+            wait_for_state(desired_state)
+            puts "Service '#{@service_name}' is now '#{current_state}'."
+          else
+            puts "Service '#{@service_name}' is already '#{desired_state}'."
+          end
+        else
+          puts "Cannot '#{action}' service '#{@service_name}'"
+          puts "Service #{@service_name} doesn't exist on the system."
+        end
+      end
+
+      def current_state
+        ::Win32::Service.status(@service_name).current_state
+      end
+
+      # Helper method that waits for a status to change its state since state
+      # changes aren't usually instantaneous.
+      def wait_for_state(desired_state)
+        while current_state != desired_state
+          puts "One moment... #{current_state}"
+          sleep 1
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/applications.rb b/lib/chef/applications.rb
index 48fd56a..6160bb9 100644
--- a/lib/chef/applications.rb
+++ b/lib/chef/applications.rb
@@ -2,3 +2,4 @@ require 'chef/application/agent'
 require 'chef/application/client'
 require 'chef/application/knife'
 require 'chef/application/solo'
+require 'chef/application/apply'
diff --git a/lib/chef/certificate.rb b/lib/chef/certificate.rb
deleted file mode 100644
index 7943589..0000000
--- a/lib/chef/certificate.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Christopher Brown (<cb at opscode.com>)
-# Copyright:: Copyright (c) 2009 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/log'
-require 'chef/config'
-require 'chef/api_client'
-require 'openssl'
-require 'fileutils'
-
-class Chef
-  class Certificate
-    class << self
-
-      # Generates a new CA Certificate and Key, and writes them out to
-      # Chef::Config[:signing_ca_cert] and Chef::Config[:signing_ca_key].
-      def generate_signing_ca
-        ca_cert_file = Chef::Config[:signing_ca_cert]
-        ca_keypair_file = Chef::Config[:signing_ca_key]
-
-        unless File.exists?(ca_cert_file) && File.exists?(ca_keypair_file)
-          Chef::Log.info("Creating new signing certificate")
-
-          [ ca_cert_file, ca_keypair_file ].each do |f|
-            ca_basedir = File.dirname(f)
-            FileUtils.mkdir_p ca_basedir
-          end
-
-          keypair = OpenSSL::PKey::RSA.generate(1024)
-
-          ca_cert = OpenSSL::X509::Certificate.new
-          ca_cert.version = 3
-          ca_cert.serial = 1
-          info = [
-            ["C", Chef::Config[:signing_ca_country]],
-            ["ST", Chef::Config[:signing_ca_state]],
-            ["L", Chef::Config[:signing_ca_location]],
-            ["O", Chef::Config[:signing_ca_org]],
-            ["OU", "Certificate Service"],
-            ["CN", "#{Chef::Config[:signing_ca_domain]}/emailAddress=#{Chef::Config[:signing_ca_email]}"]
-          ]
-          ca_cert.subject = ca_cert.issuer = OpenSSL::X509::Name.new(info)
-          ca_cert.not_before = Time.now
-          ca_cert.not_after = Time.now + 10 * 365 * 24 * 60 * 60 # 10 years
-          ca_cert.public_key = keypair.public_key
-
-          ef = OpenSSL::X509::ExtensionFactory.new
-          ef.subject_certificate = ca_cert
-          ef.issuer_certificate = ca_cert
-          ca_cert.extensions = [
-                  ef.create_extension("basicConstraints", "CA:TRUE", true),
-                  ef.create_extension("subjectKeyIdentifier", "hash"),
-                  ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
-          ]
-          ca_cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
-          ca_cert.sign keypair, OpenSSL::Digest::SHA1.new
-
-          File.open(ca_cert_file, "w") { |f| f.write ca_cert.to_pem }
-          File.open(ca_keypair_file, File::WRONLY|File::EXCL|File::CREAT, 0600) { |f| f.write keypair.to_pem }
-          if (Chef::Config[:signing_ca_user] && Chef::Config[:signing_ca_group])
-            FileUtils.chown(Chef::Config[:signing_ca_user], Chef::Config[:signing_ca_group], ca_keypair_file)
-          end
-        end
-        self
-      end
-
-      # Creates a new key pair, and signs them with the signing certificate
-      # and key generated from generate_signing_ca above.
-      #
-      # All arguments are unused, though two arguments are accepted for compatibility.
-      #
-      # returns an array of [public_key, private_key]
-      def gen_keypair(common_name=nil, subject_alternative_name = nil)
-
-        Chef::Log.info("Creating new key pair for #{common_name}")
-
-        # generate client keypair
-        client_keypair = OpenSSL::PKey::RSA.generate(2048)
-
-        return client_keypair.public_key, client_keypair
-      end
-
-      def gen_validation_key(name=Chef::Config[:validation_client_name], key_file=Chef::Config[:validation_key], admin=false)
-        # Create the validation key
-        api_client = Chef::ApiClient.new
-        api_client.name(name)
-        api_client.admin(admin)
-
-        begin
-          # If both the couch record and file exist, don't do anything. Otherwise,
-          # re-generate the validation key.
-          Chef::ApiClient.cdb_load(name)
-
-          # The couch document was loaded successfully if we got to here; if we
-          # can't also load the file on the filesystem, we'll regenerate it all.
-          File.open(key_file, "r") do |file|
-          end
-        rescue Chef::Exceptions::CouchDBNotFound
-          create_validation_key(api_client, key_file)
-        rescue
-          if $!.class.name =~ /Errno::/
-            Chef::Log.error("Error opening validation key: #{$!} -- destroying and regenerating")
-            begin
-              api_client.cdb_destroy
-            rescue Bunny::ServerDownError => e
-              # create_validation_key is gonna fail anyway, so let's just bail out.
-              Chef::Log.fatal("Could not de-index (to rabbitmq) previous validation key - rabbitmq is down! Start rabbitmq then restart chef-server to re-generate it")
-              raise
-            end
-
-            create_validation_key(api_client, key_file)
-          else
-            raise
-          end
-        end
-      end
-
-      private
-      def create_validation_key(api_client, key_file)
-        Chef::Log.info("Creating validation key...")
-
-        api_client.create_keys
-        begin
-          api_client.cdb_save
-        rescue Bunny::ServerDownError => e
-          # If rabbitmq is down, the client will have been saved in CouchDB,
-          # but not in the index.
-          Chef::Log.fatal("Could not index (to rabbitmq) validation key - rabbitmq is down! Start rabbitmq then restart chef-server to re-generate it")
-
-          # re-raise so the error bubbles out and nukes chef-server
-          raise e
-        end
-
-        key_dir = File.dirname(key_file)
-        FileUtils.mkdir_p(key_dir) unless File.directory?(key_dir)
-        File.open(key_file, File::WRONLY|File::CREAT, 0600) do |f|
-          f.print(api_client.private_key)
-        end
-        if (Chef::Config[:signing_ca_user] && Chef::Config[:signing_ca_group])
-          FileUtils.chown(Chef::Config[:signing_ca_user], Chef::Config[:signing_ca_group], key_file)
-        end
-      end
-
-    end
-  end
-end
diff --git a/lib/chef/checksum.rb b/lib/chef/checksum.rb
deleted file mode 100644
index fc19311..0000000
--- a/lib/chef/checksum.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-#
-# Author:: Tim Hinderliter (<tim at opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# 
-#     http://www.apache.org/licenses/LICENSE-2.0
-# 
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-require 'chef/log'
-require 'chef/checksum/storage'
-require 'uuidtools'
-
-class Chef
-  # == Chef::Checksum
-  # Checksum for an individual file; e.g., used for sandbox/cookbook uploading
-  # to track which files the system already manages.
-  class Checksum
-    attr_accessor :checksum, :create_time
-    attr_accessor :couchdb_id, :couchdb_rev
-
-    attr_reader :storage
-
-    # When a Checksum commits a sandboxed file to its final home in the checksum
-    # repo, this attribute will have the original on-disk path where the file
-    # was stored; it will be used if the commit is reverted to restore the sandbox
-    # to the pre-commit state.
-    attr_reader :original_committed_file_location
-
-    DESIGN_DOCUMENT = {
-      "version" => 1,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) { 
-            if (doc.chef_type == "checksum") {
-              emit(doc.checksum, doc);
-            }
-          }
-          EOJS
-        },
-      }
-    }
-    
-    # Creates a new Chef::Checksum object.
-    # === Arguments
-    # checksum::: the MD5 content hash of the file
-    # couchdb::: An instance of Chef::CouchDB
-    #
-    # === Returns
-    # object<Chef::Checksum>:: Duh. :)
-    def initialize(checksum=nil, couchdb=nil)
-      @create_time = Time.now.iso8601
-      @checksum = checksum
-      @original_committed_file_location = nil
-      @storage = Storage::Filesystem.new(Chef::Config.checksum_path, checksum)
-    end
-    
-    def to_json(*a)
-      result = {
-        :checksum => checksum,
-        :create_time => create_time,
-        :json_class => self.class.name,
-        :chef_type => 'checksum',
-
-        # For Chef::CouchDB (id_to_name, name_to_id)
-        :name => checksum
-      }
-      result.to_json(*a)
-    end
-
-    def self.json_create(o)
-      checksum = new(o['checksum'])
-      checksum.create_time = o['create_time']
-
-      if o.has_key?('_rev')
-        checksum.couchdb_rev = o["_rev"]
-        o.delete("_rev")
-      end
-      if o.has_key?("_id")
-        checksum.couchdb_id = o["_id"]
-        o.delete("_id")
-      end
-      checksum
-    end
-
-    # Moves the given +sandbox_file+ into the checksum repo using the path
-    # given by +file_location+ and saves the Checksum to the database
-    def commit_sandbox_file(sandbox_file)
-      @original_committed_file_location = sandbox_file
-      Chef::Log.info("Commiting sandbox file: move #{sandbox_file} to #{@storage}")
-      @storage.commit(sandbox_file)
-      cdb_save
-    end
-
-    # Moves the checksum file back to its pre-commit location and deletes
-    # the checksum object from the database, effectively undoing +commit_sandbox_file+.
-    # Raises Chef::Exceptions::IllegalChecksumRevert if the original file location
-    # is unknown, which is will be the case if commit_sandbox_file was not
-    # previously called
-    def revert_sandbox_file_commit
-      unless original_committed_file_location
-        raise Chef::Exceptions::IllegalChecksumRevert, "Checksum #{self.inspect} cannot be reverted because the original sandbox file location is not known"
-      end
-
-      Chef::Log.warn("Reverting sandbox file commit: moving #{@storage} back to #{original_committed_file_location}")
-      @storage.revert(original_committed_file_location)
-      cdb_destroy
-    end
-
-    # Removes the on-disk file backing this checksum object, then removes it
-    # from the database
-    def purge
-      purge_file
-      cdb_destroy
-    end
-
-    ##
-    # Couchdb
-    ##
-
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("checksums", DESIGN_DOCUMENT)
-    end
-    
-    def self.cdb_list(inflate=false, couchdb=nil)
-      rs = (couchdb || Chef::CouchDB.new).list("checksums", inflate)
-      lookup = (inflate ? "value" : "key")
-      rs["rows"].collect { |r| r[lookup] }        
-    end
-    
-    def self.cdb_all_checksums(couchdb = nil)
-      rs = (couchdb || Chef::CouchDB.new).list("checksums", true)
-      rs["rows"].inject({}) { |hash_result, r| hash_result[r['key']] = 1; hash_result }
-    end
-
-    def self.cdb_load(checksum, couchdb=nil)
-      # Probably want to look for a view here at some point
-      (couchdb || Chef::CouchDB.new).load("checksum", checksum)
-    end
-
-    def cdb_destroy(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).delete("checksum", checksum, @couchdb_rev)
-    end
-
-    def cdb_save(couchdb=nil)
-      @couchdb_rev = (couchdb || Chef::CouchDB.new).store("checksum", checksum, self)["rev"]
-    end
-
-
-    private
-
-    def purge_file
-      @storage.purge
-    end
-
-  end
-end
diff --git a/lib/chef/checksum/storage.rb b/lib/chef/checksum/storage.rb
index 90aef57..4a1b3d3 100644
--- a/lib/chef/checksum/storage.rb
+++ b/lib/chef/checksum/storage.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/checksum/storage/filesystem.rb b/lib/chef/checksum/storage/filesystem.rb
index 7500a5a..94257f5 100644
--- a/lib/chef/checksum/storage/filesystem.rb
+++ b/lib/chef/checksum/storage/filesystem.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/checksum_cache.rb b/lib/chef/checksum_cache.rb
deleted file mode 100644
index 6db7115..0000000
--- a/lib/chef/checksum_cache.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Copyright:: Copyright (c) 2009 Opscode, Inc.
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
-# 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 'set'
-require 'fileutils'
-require 'chef/log'
-require 'chef/config'
-require 'chef/client'
-require 'chef/mixin/convert_to_class_name'
-require 'singleton'
-require 'moneta'
-
-class Chef
-  class ChecksumCache
-    include Chef::Mixin::ConvertToClassName
-    include ::Singleton
-
-    attr_reader :moneta
-
-    def initialize(*args)
-      self.reset!(*args)
-    end
-
-    def reset!(backend=nil, options=nil)
-      backend ||= Chef::Config[:cache_type]
-      options ||= Chef::Config[:cache_options]
-
-      begin
-        require "moneta/#{convert_to_snake_case(backend, 'Moneta')}"
-        require 'chef/monkey_patches/moneta'
-      rescue LoadError => e
-        Chef::Log.fatal("Could not load Moneta back end #{backend.inspect}")
-        raise e
-      end
-
-      @moneta = Moneta.const_get(backend).new(options)
-    end
-
-    def self.reset_cache_validity
-      @valid_cached_checksums = nil
-    end
-
-    Chef::Client.when_run_starts do |run_status|
-      reset_cache_validity
-    end
-
-    def self.valid_cached_checksums
-      @valid_cached_checksums ||= Set.new
-    end
-
-    def self.validate_checksum(checksum_key)
-      valid_cached_checksums << checksum_key
-    end
-
-    def self.all_cached_checksums
-      all_checksums_with_filenames = {}
-
-      Dir[File.join(Chef::Config[:cache_options][:path], '*')].each do |cksum_file|
-        all_checksums_with_filenames[File.basename(cksum_file)] = cksum_file
-      end
-      all_checksums_with_filenames
-    end
-
-    def self.cleanup_checksum_cache
-      Chef::Log.debug("Cleaning the checksum cache")
-      if (Chef::Config[:cache_type].to_s == "BasicFile")
-        all_cached_checksums.each do |cache_key, cksum_cache_file|
-          unless valid_cached_checksums.include?(cache_key)
-            remove_unused_checksum(cksum_cache_file)
-          end
-        end
-      end
-    end
-
-    Chef::Client.when_run_completes_successfully do |run_status|
-      cleanup_checksum_cache
-    end
-
-    def self.remove_unused_checksum(checksum_file)
-      Chef::Log.debug("Removing unused checksum cache file #{checksum_file}")
-      FileUtils.rm(checksum_file)
-    end
-
-    def self.checksum_for_file(*args)
-      instance.checksum_for_file(*args)
-    end
-
-    def validate_checksum(*args)
-      self.class.validate_checksum(*args)
-    end
-
-    def checksum_for_file(file, key=nil)
-      key ||= generate_key(file)
-      fstat = File.stat(file)
-      lookup_checksum(key, fstat) || generate_checksum(key, file, fstat)
-    end
-
-    def lookup_checksum(key, fstat)
-      cached = fetch(key)
-      if cached && file_unchanged?(cached, fstat)
-        validate_checksum(key)
-        cached["checksum"]
-      else
-        nil
-      end
-    end
-
-    def generate_checksum(key, file, fstat)
-      checksum = checksum_file(file, Digest::SHA256.new)
-      moneta.store(key, {"mtime" => fstat.mtime.to_f, "checksum" => checksum})
-      validate_checksum(key)
-      checksum
-    end
-
-    def generate_key(file, group="chef")
-      "#{group}-file-#{file.gsub(/(#{File::SEPARATOR}|\.)/, '-')}"
-    end
-
-    def self.generate_md5_checksum_for_file(*args)
-      instance.generate_md5_checksum_for_file(*args)
-    end
-
-    def generate_md5_checksum_for_file(file)
-      checksum_file(file, Digest::MD5.new)
-    end
-
-    def generate_md5_checksum(io)
-      checksum_io(io, Digest::MD5.new)
-    end
-
-    private
-
-    def fetch(key)
-      @moneta.fetch(key)
-    rescue ArgumentError => e
-      Log.warn "Error loading cached checksum for key #{key.inspect}"
-      Log.warn(e)
-      repair_checksum_cache
-      nil
-    end
-
-    def repair_checksum_cache
-      Chef::Log.info("Removing invalid checksum cache files")
-      Dir["#{Chef::Config[:cache_options][:path]}/*"].each do |file_path|
-        File.unlink(file_path) unless File.size?(file_path)
-      end
-    end
-
-    def file_unchanged?(cached, fstat)
-      cached["mtime"].to_f == fstat.mtime.to_f
-    end
-
-    def checksum_file(file, digest)
-      File.open(file, 'rb') { |f| checksum_io(f, digest) }
-    end
-
-    def checksum_io(io, digest)
-      while chunk = io.read(1024 * 8)
-        digest.update(chunk)
-      end
-      digest.hexdigest
-    end
-
-  end
-end
-
-module Moneta
-  module Defaults
-    def default
-      nil
-    end
-  end
-end
diff --git a/lib/chef/chef_fs.rb b/lib/chef/chef_fs.rb
new file mode 100644
index 0000000..bc445e5
--- /dev/null
+++ b/lib/chef/chef_fs.rb
@@ -0,0 +1,9 @@
+require 'chef/platform'
+
+class Chef
+  module ChefFS
+    def self.windows?
+      Chef::Platform.windows?
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb
new file mode 100644
index 0000000..1fedc98
--- /dev/null
+++ b/lib/chef/chef_fs/chef_fs_data_store.rb
@@ -0,0 +1,371 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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_zero/data_store/memory_store'
+require 'chef_zero/data_store/data_already_exists_error'
+require 'chef_zero/data_store/data_not_found_error'
+require 'chef/chef_fs/file_pattern'
+require 'chef/chef_fs/file_system'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/memory_root'
+
+class Chef
+  module ChefFS
+    class ChefFSDataStore
+      def initialize(chef_fs)
+        @chef_fs = chef_fs
+        @memory_store = ChefZero::DataStore::MemoryStore.new
+      end
+
+      def publish_description
+        "Reading and writing data to #{chef_fs.fs_description}"
+      end
+
+      attr_reader :chef_fs
+
+      def create_dir(path, name, *options)
+        if use_memory_store?(path)
+          @memory_store.create_dir(path, name, *options)
+        else
+          with_dir(path) do |parent|
+            parent.create_child(chef_fs_filename(path + [name]), nil)
+          end
+        end
+      end
+
+      def create(path, name, data, *options)
+        if use_memory_store?(path)
+          @memory_store.create(path, name, data, *options)
+
+        elsif path[0] == 'cookbooks' && path.length == 2
+          # Do nothing.  The entry gets created when the cookbook is created.
+
+        else
+          if !data.is_a?(String)
+            raise "set only works with strings"
+          end
+
+          with_dir(path) do |parent|
+            parent.create_child(chef_fs_filename(path + [name]), data)
+          end
+        end
+      end
+
+      def get(path, request=nil)
+        if use_memory_store?(path)
+          @memory_store.get(path)
+
+        elsif path[0] == 'file_store' && path[1] == 'repo'
+          entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[2..-1].join('/'))
+          begin
+            entry.read
+          rescue Chef::ChefFS::FileSystem::NotFoundError => e
+            raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+          end
+
+        else
+          with_entry(path) do |entry|
+            if path[0] == 'cookbooks' && path.length == 3
+              # get /cookbooks/NAME/version
+              result = entry.chef_object.to_hash
+              result.each_pair do |key, value|
+                if value.is_a?(Array)
+                  value.each do |file|
+                    if file.is_a?(Hash) && file.has_key?('checksum')
+                      relative = ['file_store', 'repo', 'cookbooks']
+                      if Chef::Config.versioned_cookbooks
+                        relative << "#{path[1]}-#{path[2]}"
+                      else
+                        relative << path[1]
+                      end
+                      relative = relative + file[:path].split('/')
+                      file['url'] = ChefZero::RestBase::build_uri(request.base_uri, relative)
+                    end
+                  end
+                end
+              end
+              JSON.pretty_generate(result)
+
+            else
+              entry.read
+            end
+          end
+        end
+      end
+
+      def set(path, data, *options)
+        if use_memory_store?(path)
+          @memory_store.set(path, data, *options)
+        else
+          if !data.is_a?(String)
+            raise "set only works with strings: #{path} = #{data.inspect}"
+          end
+
+          # Write out the files!
+          if path[0] == 'cookbooks' && path.length == 3
+            write_cookbook(path, data, *options)
+          else
+            with_dir(path[0..-2]) do |parent|
+              parent.create_child(chef_fs_filename(path), data)
+            end
+          end
+        end
+      end
+
+      def delete(path)
+        if use_memory_store?(path)
+          @memory_store.delete(path)
+        else
+          with_entry(path) do |entry|
+            if path[0] == 'cookbooks' && path.length >= 3
+              entry.delete(true)
+            else
+              entry.delete(false)
+            end
+          end
+        end
+      end
+
+      def delete_dir(path, *options)
+        if use_memory_store?(path)
+          @memory_store.delete_dir(path, *options)
+        else
+          with_entry(path) do |entry|
+            entry.delete(options.include?(:recursive))
+          end
+        end
+      end
+
+      def list(path)
+        if use_memory_store?(path)
+          @memory_store.list(path)
+
+        elsif path[0] == 'cookbooks' && path.length == 1
+          with_entry(path) do |entry|
+            begin
+              if Chef::Config.versioned_cookbooks
+                # /cookbooks/name-version -> /cookbooks/name
+                entry.children.map { |child| split_name_version(child.name)[0] }.uniq
+              else
+                entry.children.map { |child| child.name }
+              end
+            rescue Chef::ChefFS::FileSystem::NotFoundError
+              # If the cookbooks dir doesn't exist, we have no cookbooks (not 404)
+              []
+            end
+          end
+
+        elsif path[0] == 'cookbooks' && path.length == 2
+          if Chef::Config.versioned_cookbooks
+            # list /cookbooks/name = filter /cookbooks/name-version down to name
+            entry.children.map { |child| split_name_version(child.name) }.
+                           select { |name, version| name == path[1] }.
+                           map { |name, version| version }.to_a
+          else
+            # list /cookbooks/name = <single version>
+            version = get_single_cookbook_version(path)
+            [version]
+          end
+
+        else
+          with_entry(path) do |entry|
+            begin
+              entry.children.map { |c| zero_filename(c) }.sort
+            rescue Chef::ChefFS::FileSystem::NotFoundError
+              # /cookbooks, /data, etc. never return 404
+              if path_always_exists?(path)
+                []
+              else
+                raise
+              end
+            end
+          end
+        end
+      end
+
+      def exists?(path)
+        if use_memory_store?(path)
+          @memory_store.exists?(path)
+        else
+          path_always_exists?(path) || Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
+        end
+      end
+
+      def exists_dir?(path)
+        if use_memory_store?(path)
+          @memory_store.exists_dir?(path)
+        elsif path[0] == 'cookbooks' && path.length == 2
+          list([ path[0] ]).include?(path[1])
+        else
+          Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
+        end
+      end
+
+      private
+
+      def use_memory_store?(path)
+        return path[0] == 'sandboxes' || path[0] == 'file_store' && path[1] == 'checksums' || path == [ 'environments', '_default' ]
+      end
+
+      def write_cookbook(path, data, *options)
+        # Create a little Chef::ChefFS memory filesystem with the data
+        if Chef::Config.versioned_cookbooks
+          cookbook_path = "cookbooks/#{path[1]}-#{path[2]}"
+        else
+          cookbook_path = "cookbooks/#{path[1]}"
+        end
+        cookbook_fs = Chef::ChefFS::FileSystem::MemoryRoot.new('uploading')
+        cookbook = JSON.parse(data, :create_additions => false)
+        cookbook.each_pair do |key, value|
+          if value.is_a?(Array)
+            value.each do |file|
+              if file.is_a?(Hash) && file.has_key?('checksum')
+                file_data = @memory_store.get(['file_store', 'checksums', file['checksum']])
+                cookbook_fs.add_file("#{cookbook_path}/#{file['path']}", file_data)
+              end
+            end
+          end
+        end
+
+        # Use the copy/diff algorithm to copy it down so we don't destroy
+        # chefignored data.  This is terribly un-thread-safe.
+        Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), cookbook_fs, chef_fs, nil, {:purge => true})
+      end
+
+      def split_name_version(entry_name)
+        name_version = entry_name.split('-')
+        name = name_version[0..-2].join('-')
+        version = name_version[-1]
+        [name,version]
+      end
+
+      def to_chef_fs_path(path)
+        _to_chef_fs_path(path).join('/')
+      end
+
+      def chef_fs_filename(path)
+        _to_chef_fs_path(path)[-1]
+      end
+
+      def _to_chef_fs_path(path)
+        if path[0] == 'data'
+          path = path.dup
+          path[0] = 'data_bags'
+          if path.length >= 3
+            path[2] = "#{path[2]}.json"
+          end
+        elsif path[0] == 'cookbooks'
+          if path.length == 2
+            raise ChefZero::DataStore::DataNotFoundError.new(path)
+          elsif Chef::Config.versioned_cookbooks
+            if path.length >= 3
+              # cookbooks/name/version -> cookbooks/name-version
+              path = [ path[0], "#{path[1]}-#{path[2]}" ] + path[3..-1]
+            end
+          else
+            if path.length >= 3
+              # cookbooks/name/version/... -> /cookbooks/name/... iff metadata says so
+              version = get_single_cookbook_version(path)
+              if path[2] == version
+                path = path[0..1] + path[3..-1]
+              else
+                raise ChefZero::DataStore::DataNotFoundError.new(path)
+              end
+            end
+          end
+        elsif path.length == 2
+          path = path.dup
+          path[1] = "#{path[1]}.json"
+        end
+        path
+      end
+
+      def to_zero_path(entry)
+        path = entry.path.split('/')[1..-1]
+        if path[0] == 'data_bags'
+          path = path.dup
+          path[0] = 'data'
+          if path.length >= 3
+            path[2] = path[2][0..-6]
+          end
+
+        elsif path[0] == 'cookbooks'
+          if Chef::Config.versioned_cookbooks
+            # cookbooks/name-version/... -> cookbooks/name/version/...
+            if path.length >= 2
+              name, version = split_name_version(path[1])
+              path = [ path[0], name, version ] + path[2..-1]
+            end
+          else
+            if path.length >= 2
+              # cookbooks/name/... -> cookbooks/name/version/...
+              version = get_single_cookbook_version(path)
+              path = path[0..1] + [version] + path[2..-1]
+            end
+          end
+
+        elsif path.length == 2 && path[0] != 'cookbooks'
+          path = path.dup
+          path[1] = path[1][0..-6]
+        end
+        path
+      end
+
+      def zero_filename(entry)
+        to_zero_path(entry)[-1]
+      end
+
+      def path_always_exists?(path)
+        return path.length == 1 && %w(clients cookbooks data environments nodes roles users).include?(path[0])
+      end
+
+      def with_entry(path)
+        begin
+          yield Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path))
+        rescue Chef::ChefFS::FileSystem::NotFoundError => e
+          raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+        end
+      end
+
+      def with_dir(path)
+        begin
+          yield get_dir(_to_chef_fs_path(path), true)
+        rescue Chef::ChefFS::FileSystem::NotFoundError => e
+          raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+        end
+      end
+
+      def get_dir(path, create=false)
+        result = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path.join('/'))
+        if result.exists?
+          result
+        elsif create
+          get_dir(path[0..-2], create).create_child(result.name, nil)
+        else
+          raise ChefZero::DataStore::DataNotFoundError.new(path)
+        end
+      end
+
+      def get_single_cookbook_version(path)
+        dir = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[0..1].join('/'))
+        metadata = ChefZero::CookbookData.metadata_from(dir, path[1], nil, [])
+        metadata[:version] || '0.0.0'
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb
new file mode 100644
index 0000000..d0183a5
--- /dev/null
+++ b/lib/chef/chef_fs/command_line.rb
@@ -0,0 +1,285 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system'
+require 'chef/chef_fs/file_system/operation_failed_error'
+require 'chef/chef_fs/file_system/operation_not_allowed_error'
+require 'chef/util/diff'
+
+class Chef
+  module ChefFS
+    module CommandLine
+
+      def self.diff_print(pattern, a_root, b_root, recurse_depth, output_mode, format_path = nil, diff_filter = nil, ui = nil)
+        if format_path.nil?
+          format_path = proc { |entry| entry.path_for_printing }
+        end
+
+        get_content = (output_mode != :name_only && output_mode != :name_status)
+        found_match = false
+        diff(pattern, a_root, b_root, recurse_depth, get_content).each do |type, old_entry, new_entry, old_value, new_value, error|
+          found_match = true unless type == :both_nonexistent
+          old_path = format_path.call(old_entry)
+          new_path = format_path.call(new_entry)
+
+          case type
+          when :common_subdirectories
+            if output_mode != :name_only && output_mode != :name_status
+              yield "Common subdirectories: #{new_path}\n"
+            end
+
+          when :directory_to_file
+            next if diff_filter && diff_filter !~ /T/
+            if output_mode == :name_only
+              yield "#{new_path}\n"
+            elsif output_mode == :name_status
+              yield "T\t#{new_path}\n"
+            else
+              yield "File #{old_path} is a directory while file #{new_path} is a regular file\n"
+            end
+
+          when :file_to_directory
+            next if diff_filter && diff_filter !~ /T/
+            if output_mode == :name_only
+              yield "#{new_path}\n"
+            elsif output_mode == :name_status
+              yield "T\t#{new_path}\n"
+            else
+              yield "File #{old_path} is a regular file while file #{new_path} is a directory\n"
+            end
+
+          when :deleted
+            next if diff_filter && diff_filter !~ /D/
+            if output_mode == :name_only
+              yield "#{new_path}\n"
+            elsif output_mode == :name_status
+              yield "D\t#{new_path}\n"
+            elsif old_value
+              result = "diff --knife #{old_path} #{new_path}\n"
+              result << "deleted file\n"
+              result << diff_text(old_path, '/dev/null', old_value, '')
+              yield result
+            else
+              yield "Only in #{format_path.call(old_entry.parent)}: #{old_entry.name}\n"
+            end
+
+          when :added
+            next if diff_filter && diff_filter !~ /A/
+            if output_mode == :name_only
+              yield "#{new_path}\n"
+            elsif output_mode == :name_status
+              yield "A\t#{new_path}\n"
+            elsif new_value
+              result = "diff --knife #{old_path} #{new_path}\n"
+              result << "new file\n"
+              result << diff_text('/dev/null', new_path, '', new_value)
+              yield result
+            else
+              yield "Only in #{format_path.call(new_entry.parent)}: #{new_entry.name}\n"
+            end
+
+          when :modified
+            next if diff_filter && diff_filter !~ /M/
+            if output_mode == :name_only
+              yield "#{new_path}\n"
+            elsif output_mode == :name_status
+              yield "M\t#{new_path}\n"
+            else
+              result = "diff --knife #{old_path} #{new_path}\n"
+              result << diff_text(old_path, new_path, old_value, new_value)
+              yield result
+            end
+
+          when :both_nonexistent
+          when :added_cannot_upload
+          when :deleted_cannot_download
+          when :same
+            # Skip these silently
+          when :error
+            if error.is_a?(Chef::ChefFS::FileSystem::OperationFailedError)
+              ui.error "#{format_path.call(error.entry)} failed to #{error.operation}: #{error.message}" if ui
+              error = true
+            elsif error.is_a?(Chef::ChefFS::FileSystem::OperationNotAllowedError)
+              ui.error "#{format_path.call(error.entry)} #{error.reason}." if ui
+            else
+              raise error
+            end
+          end
+        end
+        if !found_match
+          ui.error "#{pattern}: No such file or directory on remote or local" if ui
+          error = true
+        end
+        error
+      end
+
+      def self.diff(pattern, old_root, new_root, recurse_depth, get_content)
+        Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.list_pairs(pattern, old_root, new_root), :flatten => true) do |old_entry, new_entry|
+          diff_entries(old_entry, new_entry, recurse_depth, get_content)
+        end
+      end
+
+      # Diff two known entries (could be files or dirs)
+      def self.diff_entries(old_entry, new_entry, recurse_depth, get_content)
+        # If both are directories
+        if old_entry.dir?
+          if new_entry.dir?
+            if recurse_depth == 0
+              return [ [ :common_subdirectories, old_entry, new_entry ] ]
+            else
+              return Chef::ChefFS::Parallelizer.parallelize(Chef::ChefFS::FileSystem.child_pairs(old_entry, new_entry), :flatten => true) do |old_child, new_child|
+                Chef::ChefFS::CommandLine.diff_entries(old_child, new_child, recurse_depth ? recurse_depth - 1 : nil, get_content)
+              end
+            end
+
+          # If old is a directory and new is a file
+          elsif new_entry.exists?
+            return [ [ :directory_to_file, old_entry, new_entry ] ]
+
+          # If old is a directory and new does not exist
+          elsif new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?)
+            return [ [ :deleted, old_entry, new_entry ] ]
+
+          # If the new entry does not and *cannot* exist, report that.
+          else
+            return [ [ :new_cannot_upload, old_entry, new_entry ] ]
+          end
+
+        # If new is a directory and old is a file
+        elsif new_entry.dir?
+          if old_entry.exists?
+            return [ [ :file_to_directory, old_entry, new_entry ] ]
+
+          # If new is a directory and old does not exist
+          elsif old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
+            return [ [ :added, old_entry, new_entry ] ]
+
+          # If the new entry does not and *cannot* exist, report that.
+          else
+            return [ [ :old_cannot_upload, old_entry, new_entry ] ]
+          end
+
+        # Neither is a directory, so they are diffable with file diff
+        else
+          are_same, old_value, new_value = Chef::ChefFS::FileSystem.compare(old_entry, new_entry)
+          if are_same
+            if old_value == :none
+              return [ [ :both_nonexistent, old_entry, new_entry ] ]
+            else
+              return [ [ :same, old_entry, new_entry ] ]
+            end
+          else
+            if old_value == :none
+              old_exists = false
+            elsif old_value.nil?
+              old_exists = old_entry.exists?
+            else
+              old_exists = true
+            end
+
+            if new_value == :none
+              new_exists = false
+            elsif new_value.nil?
+              new_exists = new_entry.exists?
+            else
+              new_exists = true
+            end
+
+            # If one of the files doesn't exist, we only want to print the diff if the
+            # other file *could be uploaded/downloaded*.
+            if !old_exists && !old_entry.parent.can_have_child?(new_entry.name, new_entry.dir?)
+              return [ [ :old_cannot_upload, old_entry, new_entry ] ]
+            end
+            if !new_exists && !new_entry.parent.can_have_child?(old_entry.name, old_entry.dir?)
+              return [ [ :new_cannot_upload, old_entry, new_entry ] ]
+            end
+
+            if get_content
+              # If we haven't read the values yet, get them now so that they can be diffed
+              begin
+                old_value = old_entry.read if old_value.nil?
+              rescue Chef::ChefFS::FileSystem::NotFoundError
+                old_value = :none
+              end
+              begin
+                new_value = new_entry.read if new_value.nil?
+              rescue Chef::ChefFS::FileSystem::NotFoundError
+                new_value = :none
+              end
+            end
+
+            if old_value == :none || (old_value == nil && !old_entry.exists?)
+              return [ [ :added, old_entry, new_entry, old_value, new_value ] ]
+            elsif new_value == :none
+              return [ [ :deleted, old_entry, new_entry, old_value, new_value ] ]
+            else
+              return [ [ :modified, old_entry, new_entry, old_value, new_value ] ]
+            end
+          end
+        end
+      rescue Chef::ChefFS::FileSystem::FileSystemError => e
+        return [ [ :error, old_entry, new_entry, nil, nil, e ] ]
+      end
+
+      private
+
+      def self.sort_keys(json_object)
+        if json_object.is_a?(Array)
+          json_object.map { |o| sort_keys(o) }
+        elsif json_object.is_a?(Hash)
+          new_hash = {}
+          json_object.keys.sort.each { |key| new_hash[key] = sort_keys(json_object[key]) }
+          new_hash
+        else
+          json_object
+        end
+      end
+
+      def self.canonicalize_json(json_text)
+        parsed_json = JSON.parse(json_text, :create_additions => false)
+        sorted_json = sort_keys(parsed_json)
+        JSON.pretty_generate(sorted_json)
+      end
+
+      def self.diff_text(old_path, new_path, old_value, new_value)
+        # Copy to tempfiles before diffing
+        # TODO don't copy things that are already in files!  Or find an in-memory diff algorithm
+        begin
+          new_tempfile = Tempfile.new("new")
+          new_tempfile.write(new_value)
+          new_tempfile.close
+
+          begin
+            old_tempfile = Tempfile.new("old")
+            old_tempfile.write(old_value)
+            old_tempfile.close
+
+            result = Chef::Util::Diff.new.udiff(old_tempfile.path, new_tempfile.path)
+            result = result.gsub(/^--- #{old_tempfile.path}/, "--- #{old_path}")
+            result = result.gsub(/^\+\+\+ #{new_tempfile.path}/, "+++ #{new_path}")
+            result
+          ensure
+            old_tempfile.close!
+          end
+        ensure
+          new_tempfile.close!
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
new file mode 100644
index 0000000..e08b976
--- /dev/null
+++ b/lib/chef/chef_fs/config.rb
@@ -0,0 +1,160 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/log'
+require 'chef/chef_fs/path_utils'
+
+class Chef
+  module ChefFS
+    #
+    # Helpers to take Chef::Config and create chef_fs and local_fs from it
+    #
+    class Config
+      def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {})
+        @chef_config = chef_config
+        @cwd = cwd
+        @cookbook_version = options[:cookbook_version]
+
+        # Default to getting *everything* from the server.
+        if !@chef_config[:repo_mode]
+          if @chef_config[:chef_server_url] =~ /\/+organizations\/.+/
+            @chef_config[:repo_mode] = 'hosted_everything'
+          else
+            @chef_config[:repo_mode] = 'everything'
+          end
+        end
+      end
+
+      attr_reader :chef_config
+      attr_reader :cwd
+      attr_reader :cookbook_version
+
+      def chef_fs
+        @chef_fs ||= create_chef_fs
+      end
+
+      def create_chef_fs
+        require 'chef/chef_fs/file_system/chef_server_root_dir'
+        Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", @chef_config, :cookbook_version => @cookbook_version)
+      end
+
+      def local_fs
+        @local_fs ||= create_local_fs
+      end
+
+      def create_local_fs
+        require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
+        Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
+      end
+
+      # Returns the given real path's location relative to the server root.
+      #
+      # If chef_repo is /home/jkeiser/chef_repo,
+      # and pwd is /home/jkeiser/chef_repo/cookbooks,
+      # server_path('blah') == '/cookbooks/blah'
+      # server_path('../roles/blah.json') == '/roles/blah'
+      # server_path('../../readme.txt') == nil
+      # server_path('*/*ab*') == '/cookbooks/*/*ab*'
+      # server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
+      # server_path('/home/*/chef_repo/cookbooks/blah') == nil
+      #
+      # If there are multiple paths (cookbooks, roles, data bags, etc. can all
+      # have separate paths), and cwd+the path reaches into one of them, we will
+      # return a path relative to that.  Otherwise we will return a path to
+      # chef_repo.
+      #
+      # Globs are allowed as well, but globs outside server paths are NOT
+      # (presently) supported.  See above examples.  TODO support that.
+      #
+      # If the path does not reach into ANY specified directory, nil is returned.
+      def server_path(file_path)
+        pwd = File.expand_path(Dir.pwd)
+        absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
+
+        # Check all object paths (cookbooks_dir, data_bags_dir, etc.)
+        object_paths.each_pair do |name, paths|
+          paths.each do |path|
+            realest_path = Chef::ChefFS::PathUtils.realest_path(path)
+            if PathUtils.descendant_of?(absolute_pwd, realest_path)
+              relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path)
+              return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
+            end
+          end
+        end
+
+        # Check chef_repo_path
+        Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
+          realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path)
+          if absolute_pwd == realest_chef_repo_path
+            return '/'
+          end
+        end
+
+        nil
+      end
+
+      # The current directory, relative to server root
+      def base_path
+        @base_path ||= begin
+          if @chef_config[:chef_repo_path]
+            server_path(File.expand_path(@cwd))
+          else
+            nil
+          end
+        end
+      end
+
+      # Print the given server path, relative to the current directory
+      def format_path(entry)
+        server_path = entry.path
+        if base_path && server_path[0,base_path.length] == base_path
+          if server_path == base_path
+            return "."
+          elsif server_path[base_path.length,1] == "/"
+            return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
+          elsif base_path == "/" && server_path[0,1] == "/"
+            return server_path[1, server_path.length - 1]
+          end
+        end
+        server_path
+      end
+
+      private
+
+      def object_paths
+        @object_paths ||= begin
+          result = {}
+          case @chef_config[:repo_mode]
+          when 'static'
+            object_names = %w(cookbooks data_bags environments roles)
+          when 'hosted_everything'
+            object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles)
+          else
+            object_names = %w(clients cookbooks data_bags environments nodes roles users)
+          end
+          object_names.each do |object_name|
+            variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
+            paths = Array(@chef_config[variable_name]).flatten
+            result[object_name] = paths.map { |path| File.expand_path(path) }
+          end
+          result
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/acl_data_handler.rb b/lib/chef/chef_fs/data_handler/acl_data_handler.rb
new file mode 100644
index 0000000..5ce4e33
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/acl_data_handler.rb
@@ -0,0 +1,26 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class AclDataHandler < DataHandlerBase
+        def normalize(node, entry)
+          # Normalize the order of the keys for easier reading
+          result = normalize_hash(node, {
+            'create' => {},
+            'read' => {},
+            'update' => {},
+            'delete' => {},
+            'grant' => {}
+            })
+          result.keys.each do |key|
+            result[key] = normalize_hash(result[key], { 'actors' => [], 'groups' => [] })
+            result[key]['actors'] = result[key]['actors'].sort
+            result[key]['groups'] = result[key]['groups'].sort
+          end
+          result
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/client_data_handler.rb b/lib/chef/chef_fs/data_handler/client_data_handler.rb
new file mode 100644
index 0000000..4b6b8f5
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/client_data_handler.rb
@@ -0,0 +1,37 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+require 'chef/api_client'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class ClientDataHandler < DataHandlerBase
+        def normalize(client, entry)
+          defaults = {
+            'name' => remove_dot_json(entry.name),
+            'clientname' => remove_dot_json(entry.name),
+            'admin' => false,
+            'validator' => false,
+            'chef_type' => 'client'
+          }
+          if entry.respond_to?(:org) && entry.org
+            defaults['orgname'] = entry.org
+          end
+          result = normalize_hash(client, defaults)
+          # You can NOT send json_class, or it will fail
+          result.delete('json_class')
+          result
+        end
+
+        def preserve_key(key)
+          return key == 'name'
+        end
+
+        def chef_class
+          Chef::ApiClient
+        end
+
+        # There is no Ruby API for Chef::ApiClient
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/container_data_handler.rb b/lib/chef/chef_fs/data_handler/container_data_handler.rb
new file mode 100644
index 0000000..8b108bc
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/container_data_handler.rb
@@ -0,0 +1,29 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class ContainerDataHandler < DataHandlerBase
+        def normalize(container, entry)
+          normalize_hash(container, {
+            'containername' => remove_dot_json(entry.name),
+            'containerpath' => remove_dot_json(entry.name)
+          })
+        end
+
+        def preserve_key(key)
+          return key == 'containername'
+        end
+
+        def verify_integrity(object, entry, &on_error)
+          base_name = remove_dot_json(entry.name)
+          if object['containername'] != base_name
+            on_error.call("Name in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['name']}')")
+          end
+        end
+
+        # There is no chef_class for users, nor does to_ruby work.
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb b/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
new file mode 100644
index 0000000..d2e2a3e
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
@@ -0,0 +1,38 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+require 'chef/cookbook/metadata'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class CookbookDataHandler < DataHandlerBase
+        def normalize(cookbook, entry)
+          version = entry.name
+          name = entry.parent.name
+          result = normalize_hash(cookbook, {
+            'name' => "#{name}-#{version}",
+            'version' => version,
+            'cookbook_name' => name,
+            'json_class' => 'Chef::CookbookVersion',
+            'chef_type' => 'cookbook_version',
+            'frozen?' => false,
+            'metadata' => {}
+          })
+          result['metadata'] = normalize_hash(result['metadata'], {
+            'version' => version,
+            'name' => name
+          })
+        end
+
+        def preserve_key(key)
+          return key == 'cookbook_name' || key == 'version'
+        end
+
+        def chef_class
+          Chef::Cookbook::Metadata
+        end
+
+        # Not using this yet, so not sure if to_ruby will be useful.
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb b/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
new file mode 100644
index 0000000..240a427
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
@@ -0,0 +1,56 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+require 'chef/data_bag_item'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class DataBagItemDataHandler < DataHandlerBase
+        def normalize(data_bag_item, entry)
+          # If it's wrapped with raw_data, unwrap it.
+          if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
+            data_bag_item = data_bag_item['raw_data']
+          end
+          # chef_type and data_bag come back in PUT and POST results, but we don't
+          # use those in knife-essentials.
+          normalize_hash(data_bag_item, {
+            'id' => remove_dot_json(entry.name)
+          })
+        end
+
+        def normalize_for_post(data_bag_item, entry)
+          if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
+            data_bag_item = data_bag_item['raw_data']
+          end
+          {
+            "name" => "data_bag_item_#{entry.parent.name}_#{remove_dot_json(entry.name)}",
+            "json_class" => "Chef::DataBagItem",
+            "chef_type" => "data_bag_item",
+            "data_bag" => entry.parent.name,
+            "raw_data" => normalize(data_bag_item, entry)
+          }
+        end
+
+        def normalize_for_put(data_bag_item, entry)
+          normalize_for_post(data_bag_item, entry)
+        end
+
+        def preserve_key(key)
+          return key == 'id'
+        end
+
+        def chef_class
+          Chef::DataBagItem
+        end
+
+        def verify_integrity(object, entry, &on_error)
+          base_name = remove_dot_json(entry.name)
+          if object['raw_data']['id'] != base_name
+            on_error.call("ID in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['raw_data']['id']}')")
+          end
+        end
+
+        # Data bags do not support .rb files (or if they do, it's undocumented)
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/data_handler_base.rb b/lib/chef/chef_fs/data_handler/data_handler_base.rb
new file mode 100644
index 0000000..a9bbc0b
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/data_handler_base.rb
@@ -0,0 +1,128 @@
+class Chef
+  module ChefFS
+    module DataHandler
+      class DataHandlerBase
+        def minimize(object, entry)
+          default_object = default(entry)
+          object.each_pair do |key, value|
+            if default_object[key] == value && !preserve_key(key)
+              object.delete(key)
+            end
+          end
+          object
+        end
+
+        def remove_dot_json(name)
+          if name.length < 5 || name[-5,5] != ".json"
+            raise "Invalid name #{path}: must end in .json"
+          end
+          name[0,name.length-5]
+        end
+
+        def preserve_key(key)
+          false
+        end
+
+        def default(entry)
+          normalize({}, entry)
+        end
+
+        def normalize_hash(object, defaults)
+          # Make a normalized result in the specified order for diffing
+          result = {}
+          defaults.each_pair do |key, default|
+            result[key] = object.has_key?(key) ? object[key] : default
+          end
+          object.each_pair do |key, value|
+            result[key] = value if !result.has_key?(key)
+          end
+          result
+        end
+
+        def normalize_for_post(object, entry)
+          normalize(object, entry)
+        end
+
+        def normalize_for_put(object, entry)
+          normalize(object, entry)
+        end
+
+        def normalize_run_list(run_list)
+          run_list.map{|item|
+            case item.to_s
+            when /^recipe\[.*\]$/
+              item # explicit recipe
+            when /^role\[.*\]$/
+              item # explicit role
+            else
+              "recipe[#{item}]"
+            end
+          }.uniq
+        end
+
+        def from_ruby(ruby)
+          chef_class.from_file(ruby).to_hash
+        end
+
+        def chef_object(object)
+          chef_class.json_create(object)
+        end
+
+        def to_ruby(object)
+          raise NotImplementedError
+        end
+
+        def chef_class
+          raise NotImplementedError
+        end
+
+        def to_ruby_keys(object, keys)
+          result = ''
+          keys.each do |key|
+            if object[key]
+              if object[key].is_a?(Hash)
+                if object[key].size > 0
+                  result << key
+                  first = true
+                  object[key].each_pair do |k,v|
+                    if first
+                      first = false
+                    else
+                      result << ' '*key.length
+                    end
+                    result << " #{k.inspect} => #{v.inspect}\n"
+                  end
+                end
+              elsif object[key].is_a?(Array)
+                if object[key].size > 0
+                  result << key
+                  first = true
+                  object[key].each do |value|
+                    if first
+                      first = false
+                    else
+                      result << ", "
+                    end
+                    result << value.inspect
+                  end
+                  result << "\n"
+                end
+              elsif !object[key].nil?
+                result << "#{key} #{object[key].inspect}\n"
+              end
+            end
+          end
+          result
+        end
+
+        def verify_integrity(object, entry, &on_error)
+          base_name = remove_dot_json(entry.name)
+          if object['name'] != base_name
+            on_error.call("Name must be '#{base_name}' (is '#{object['name']}')")
+          end
+        end
+
+      end # class DataHandlerBase
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/environment_data_handler.rb b/lib/chef/chef_fs/data_handler/environment_data_handler.rb
new file mode 100644
index 0000000..9da10eb
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/environment_data_handler.rb
@@ -0,0 +1,40 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+require 'chef/environment'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class EnvironmentDataHandler < DataHandlerBase
+        def normalize(environment, entry)
+          normalize_hash(environment, {
+            'name' => remove_dot_json(entry.name),
+            'description' => '',
+            'cookbook_versions' => {},
+            'default_attributes' => {},
+            'override_attributes' => {},
+            'json_class' => 'Chef::Environment',
+            'chef_type' => 'environment'
+          })
+        end
+
+        def preserve_key(key)
+          return key == 'name'
+        end
+
+        def chef_class
+          Chef::Environment
+        end
+
+        def to_ruby(object)
+          result = to_ruby_keys(object, %w(name description default_attributes override_attributes))
+          if object['cookbook_versions']
+            object['cookbook_versions'].each_pair do |name, version|
+              result << "cookbook #{name.inspect}, #{version.inspect}"
+            end
+          end
+          result
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/group_data_handler.rb b/lib/chef/chef_fs/data_handler/group_data_handler.rb
new file mode 100644
index 0000000..619822f
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/group_data_handler.rb
@@ -0,0 +1,51 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+require 'chef/api_client'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class GroupDataHandler < DataHandlerBase
+        def normalize(group, entry)
+          defaults = {
+            'name' => remove_dot_json(entry.name),
+            'groupname' => remove_dot_json(entry.name),
+            'users' => [],
+            'clients' => [],
+            'groups' => [],
+          }
+          if entry.org
+            defaults['orgname'] = entry.org
+          end
+          result = normalize_hash(group, defaults)
+          if result['actors'] && result['actors'].sort.uniq == (result['users'] + result['clients']).sort.uniq
+            result.delete('actors')
+          end
+          result
+        end
+
+        def normalize_for_put(group, entry)
+          result = super(group, entry)
+          result['actors'] = {
+            'users' => result['users'],
+            'clients' => result['clients'],
+            'groups' => result['groups']
+          }
+          result.delete('users')
+          result.delete('clients')
+          result.delete('groups')
+          result
+        end
+
+        def preserve_key(key)
+          return key == 'name'
+        end
+
+        def chef_class
+          Chef::ApiClient
+        end
+
+        # There is no Ruby API for Chef::ApiClient
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/node_data_handler.rb b/lib/chef/chef_fs/data_handler/node_data_handler.rb
new file mode 100644
index 0000000..f2c97c7
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/node_data_handler.rb
@@ -0,0 +1,36 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+require 'chef/node'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class NodeDataHandler < DataHandlerBase
+        def normalize(node, entry)
+          result = normalize_hash(node, {
+            'name' => remove_dot_json(entry.name),
+            'json_class' => 'Chef::Node',
+            'chef_type' => 'node',
+            'chef_environment' => '_default',
+            'override' => {},
+            'normal' => {},
+            'default' => {},
+            'automatic' => {},
+            'run_list' => []
+          })
+          result['run_list'] = normalize_run_list(result['run_list'])
+          result
+        end
+
+        def preserve_key(key)
+          return key == 'name'
+        end
+
+        def chef_class
+          Chef::Node
+        end
+
+        # Nodes do not support .rb files
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/role_data_handler.rb b/lib/chef/chef_fs/data_handler/role_data_handler.rb
new file mode 100644
index 0000000..bc1c076
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/role_data_handler.rb
@@ -0,0 +1,40 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+require 'chef/role'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class RoleDataHandler < DataHandlerBase
+        def normalize(role, entry)
+          result = normalize_hash(role, {
+            'name' => remove_dot_json(entry.name),
+            'description' => '',
+            'json_class' => 'Chef::Role',
+            'chef_type' => 'role',
+            'default_attributes' => {},
+            'override_attributes' => {},
+            'run_list' => [],
+            'env_run_lists' => {}
+          })
+          result['run_list'] = normalize_run_list(result['run_list'])
+          result['env_run_lists'].each_pair do |env, run_list|
+            result['env_run_lists'][env] = normalize_run_list(run_list)
+          end
+          result
+        end
+
+        def preserve_key(key)
+          return key == 'name'
+        end
+
+        def chef_class
+          Chef::Role
+        end
+
+        def to_ruby(object)
+          to_ruby_keys(object, %w(name description default_attributes override_attributes run_list env_run_lists))
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/data_handler/user_data_handler.rb b/lib/chef/chef_fs/data_handler/user_data_handler.rb
new file mode 100644
index 0000000..66a7806
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/user_data_handler.rb
@@ -0,0 +1,27 @@
+require 'chef/chef_fs/data_handler/data_handler_base'
+
+class Chef
+  module ChefFS
+    module DataHandler
+      class UserDataHandler < DataHandlerBase
+        def normalize(user, entry)
+          normalize_hash(user, {
+            'name' => remove_dot_json(entry.name),
+            'admin' => false,
+            'json_class' => 'Chef::WebUIUser',
+            'chef_type' => 'webui_user',
+            'salt' => nil,
+            'password' => nil,
+            'openid' => nil
+          })
+        end
+
+        def preserve_key(key)
+          return key == 'name'
+        end
+
+        # There is no chef_class for users, nor does to_ruby work.
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb
new file mode 100644
index 0000000..134d22c
--- /dev/null
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -0,0 +1,312 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs'
+require 'chef/chef_fs/path_utils'
+
+class Chef
+  module ChefFS
+    #
+    # Represents a glob pattern.  This class is designed so that it can
+    # match arbitrary strings, and tell you about partial matches.
+    #
+    # Examples:
+    # * <tt>a*z</tt>
+    #   - Matches <tt>abcz</tt>
+    #   - Does not match <tt>ab/cd/ez</tt>
+    #   - Does not match <tt>xabcz</tt>
+    # * <tt>a**z</tt>
+    #   - Matches <tt>abcz</tt>
+    #   - Matches <tt>ab/cd/ez</tt>
+    #
+    # Special characters supported:
+    # * <tt>/</tt> (and <tt>\\</tt> on Windows) - directory separators
+    # * <tt>\*</tt> - match zero or more characters (but not directory separators)
+    # * <tt>\*\*</tt> - match zero or more characters, including directory separators
+    # * <tt>?</tt> - match exactly one character (not a directory separator)
+    # Only on Unix:
+    # * <tt>[abc0-9]</tt> - match one of the included characters
+    # * <tt>\\<character></tt> - escape character: match the given character
+    #
+    class FilePattern
+      # Initialize a new FilePattern with the pattern string.
+      #
+      # Raises +ArgumentError+ if empty file pattern is specified
+      def initialize(pattern)
+        @pattern = pattern
+      end
+
+      # The pattern string.
+      attr_reader :pattern
+
+      # Reports whether this pattern could match children of <tt>path</tt>.
+      # If the pattern doesn't match the path up to this point or
+      # if it matches and doesn't allow further children, this will
+      # return <tt>false</tt>.
+      #
+      # ==== Attributes
+      #
+      # * +path+ - a path to check
+      #
+      # ==== Examples
+      #
+      #   abc/def.could_match_children?('abc') == true
+      #   abc.could_match_children?('abc') == false
+      #   abc/def.could_match_children?('x') == false
+      #   a**z.could_match_children?('ab/cd') == true
+      def could_match_children?(path)
+        return false if path == '' # Empty string is not a path
+
+        argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+        return false if is_absolute != argument_is_absolute
+        path = path[1,path.length-1] if argument_is_absolute
+
+        path_parts = Chef::ChefFS::PathUtils::split(path)
+        # If the pattern is shorter than the path (or same size), children will be larger than the pattern, and will not match.
+        return false if regexp_parts.length <= path_parts.length && !has_double_star
+        # If the path doesn't match up to this point, children won't match either.
+        return false if path_parts.zip(regexp_parts).any? { |part,regexp| !regexp.nil? && !regexp.match(part) }
+        # Otherwise, it's possible we could match: the path matches to this point, and the pattern is longer than the path.
+        # TODO There is one edge case where the double star comes after some characters like abc**def--we could check whether the next
+        # bit of path starts with abc in that case.
+        return true
+      end
+
+      # Returns the immediate child of a path that would be matched
+      # if this FilePattern was applied.  If more than one child
+      # could match, this method returns nil.
+      #
+      # ==== Attributes
+      #
+      # * +path+ - The path to look for an exact child name under.
+      #
+      # ==== Returns
+      #
+      # The next directory in the pattern under the given path.
+      # If the directory part could match more than one child, it
+      # returns +nil+.
+      #
+      # ==== Examples
+      #
+      #   abc/def.exact_child_name_under('abc') == 'def'
+      #   abc/def/ghi.exact_child_name_under('abc') == 'def'
+      #   abc/*/ghi.exact_child_name_under('abc') == nil
+      #   abc/*/ghi.exact_child_name_under('abc/def') == 'ghi'
+      #   abc/**/ghi.exact_child_name_under('abc/def') == nil
+      #
+      # This method assumes +could_match_children?(path)+ is +true+.
+      def exact_child_name_under(path)
+        path = path[1,path.length-1] if !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+        dirs_in_path = Chef::ChefFS::PathUtils::split(path).length
+        return nil if exact_parts.length <= dirs_in_path
+        return exact_parts[dirs_in_path]
+      end
+
+      # If this pattern represents an exact path, returns the exact path.
+      #
+      #   abc/def.exact_path == 'abc/def'
+      #   abc/*def.exact_path == 'abc/def'
+      #   abc/x\\yz.exact_path == 'abc/xyz'
+      def exact_path
+        return nil if has_double_star || exact_parts.any? { |part| part.nil? }
+        result = Chef::ChefFS::PathUtils::join(*exact_parts)
+        is_absolute ? Chef::ChefFS::PathUtils::join('', result) : result
+      end
+
+      # Returns the normalized version of the pattern, with / as the directory
+      # separator, and "." and ".." removed.
+      #
+      # This does not presently change things like \b to b, but in the future
+      # it might.
+      def normalized_pattern
+        calculate
+        @normalized_pattern
+      end
+
+      # Tell whether this pattern matches absolute, or relative paths
+      def is_absolute
+        calculate
+        @is_absolute
+      end
+
+      # Returns <tt>true+ if this pattern matches the path, <tt>false+ otherwise.
+      #
+      #   abc/*/def.match?('abc/foo/def') == true
+      #   abc/*/def.match?('abc/foo') == false
+      def match?(path)
+        argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+        return false if is_absolute != argument_is_absolute
+        path = path[1,path.length-1] if argument_is_absolute
+        !!regexp.match(path)
+      end
+
+      # Returns the string pattern
+      def to_s
+        pattern
+      end
+
+      # Given a relative file pattern and a directory, makes a new file pattern
+      # starting with the directory.
+      #
+      #   FilePattern.relative_to('/usr/local', 'bin/*grok') == FilePattern.new('/usr/local/bin/*grok')
+      #
+      # BUG: this does not support patterns starting with <tt>..</tt>
+      def self.relative_to(dir, pattern)
+        return FilePattern.new(pattern) if pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/
+        FilePattern.new(Chef::ChefFS::PathUtils::join(dir, pattern))
+      end
+
+    private
+
+      def regexp
+        calculate
+        @regexp
+      end
+
+      def regexp_parts
+        calculate
+        @regexp_parts
+      end
+
+      def exact_parts
+        calculate
+        @exact_parts
+      end
+
+      def has_double_star
+        calculate
+        @has_double_star
+      end
+
+      def calculate
+        if !@regexp
+          @is_absolute = !!(@pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+
+          full_regexp_parts = []
+          normalized_parts = []
+          @regexp_parts = []
+          @exact_parts = []
+          @has_double_star = false
+
+          Chef::ChefFS::PathUtils::split(pattern).each do |part|
+            regexp, exact, has_double_star = FilePattern::pattern_to_regexp(part)
+            if has_double_star
+              @has_double_star = true
+            end
+
+            # Skip // and /./ (pretend it's not there)
+            if exact == '' || exact == '.'
+              next
+            end
+
+            # Back up when you see .. (unless the prior part has ** in it, in which case .. must be preserved)
+            if exact == '..'
+              if @is_absolute && normalized_parts.length == 0
+                # If we are at the root, just pretend the .. isn't there
+                next
+              elsif normalized_parts.length > 0
+                regexp_prev, exact_prev, has_double_star_prev = FilePattern.pattern_to_regexp(normalized_parts[-1])
+                if has_double_star_prev
+                  raise ArgumentError, ".. overlapping a ** is unsupported"
+                end
+                full_regexp_parts.pop
+                normalized_parts.pop
+                if !@has_double_star
+                  @regexp_parts.pop
+                  @exact_parts.pop
+                end
+                next
+              end
+            end
+
+            # Build up the regexp
+            full_regexp_parts << regexp
+            normalized_parts << part
+            if !@has_double_star
+              @regexp_parts << Regexp.new("^#{regexp}$")
+              @exact_parts << exact
+            end
+          end
+
+          @regexp = Regexp.new("^#{full_regexp_parts.join(Chef::ChefFS::PathUtils::regexp_path_separator)}$")
+          @normalized_pattern = Chef::ChefFS::PathUtils.join(*normalized_parts)
+          @normalized_pattern = Chef::ChefFS::PathUtils.join('', @normalized_pattern) if @is_absolute
+        end
+      end
+
+      def self.pattern_special_characters
+        if Chef::ChefFS::windows?
+          @pattern_special_characters ||= /(\*\*|\*|\?|[\*\?\.\|\(\)\[\]\{\}\+\\\\\^\$])/
+        else
+          # Unix also supports character regexes and backslashes
+          @pattern_special_characters ||= /(\\.|\[[^\]]+\]|\*\*|\*|\?|[\*\?\.\|\(\)\[\]\{\}\+\\\\\^\$])/
+        end
+        @pattern_special_characters
+      end
+
+      def self.regexp_escape_characters
+        [ '[', '\\', '^', '$', '.', '|', '?', '*', '+', '(', ')', '{', '}' ]
+      end
+
+      def self.pattern_to_regexp(pattern)
+        regexp = ""
+        exact = ""
+        has_double_star = false
+        pattern.split(pattern_special_characters).each_with_index do |part, index|
+          # Odd indexes from the split are symbols.  Even are normal bits.
+          if index % 2 == 0
+            exact << part if !exact.nil?
+            regexp << part
+          else
+            case part
+            # **, * and ? happen on both platforms.
+            when '**'
+              exact = nil
+              has_double_star = true
+              regexp << '.*'
+            when '*'
+              exact = nil
+              regexp << '[^\/]*'
+            when '?'
+              exact = nil
+              regexp << '.'
+            else
+              if part[0,1] == '\\' && part.length == 2
+                # backslash escapes are only supported on Unix, and are handled here by leaving the escape on (it means the same thing in a regex)
+                exact << part[1,1] if !exact.nil?
+                if regexp_escape_characters.include?(part[1,1])
+                  regexp << part
+                else
+                  regexp << part[1,1]
+                end
+              elsif part[0,1] == '[' && part.length > 1
+                # [...] happens only on Unix, and is handled here by *not* backslashing (it means the same thing in and out of regex)
+                exact = nil
+                regexp << part
+              else
+                exact += part if !exact.nil?
+                regexp << "\\#{part}"
+              end
+            end
+          end
+        end
+        [regexp, exact, has_double_star]
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb
new file mode 100644
index 0000000..f2478c4
--- /dev/null
+++ b/lib/chef/chef_fs/file_system.rb
@@ -0,0 +1,426 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/path_utils'
+require 'chef/chef_fs/file_system/default_environment_cannot_be_modified_error'
+require 'chef/chef_fs/file_system/operation_failed_error'
+require 'chef/chef_fs/file_system/operation_not_allowed_error'
+require 'chef/chef_fs/parallelizer'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      # Returns a list of all things under (and including) this entry that match the
+      # given pattern.
+      #
+      # ==== Attributes
+      #
+      # * +root+ - Entry to start listing under
+      # * +pattern+ - Chef::ChefFS::FilePattern to match children under
+      #
+      def self.list(root, pattern)
+        Lister.new(root, pattern)
+      end
+
+      class Lister
+        include Enumerable
+
+        def initialize(root, pattern)
+          @root = root
+          @pattern = pattern
+        end
+
+        attr_reader :root
+        attr_reader :pattern
+
+        def each(&block)
+          list_from(root, &block)
+        end
+
+        def list_from(entry, &block)
+          # Include self in results if it matches
+          if pattern.match?(entry.path)
+            block.call(entry)
+          end
+
+          if pattern.could_match_children?(entry.path)
+            # If it's possible that our children could match, descend in and add matches.
+            exact_child_name = pattern.exact_child_name_under(entry.path)
+
+            # If we've got an exact name, don't bother listing children; just grab the
+            # child with the given name.
+            if exact_child_name
+              exact_child = entry.child(exact_child_name)
+              if exact_child
+                list_from(exact_child, &block)
+              end
+
+            # Otherwise, go through all children and find any matches
+            elsif entry.dir?
+              results = Parallelizer::parallelize(entry.children, :flatten => true) { |child| Chef::ChefFS::FileSystem.list(child, pattern) }
+              results.each(&block)
+            end
+          end
+        end
+      end
+
+      # Resolve the given path against the entry, returning
+      # the entry at the end of the path.
+      #
+      # ==== Attributes
+      #
+      # * +entry+ - the entry to start looking under.  Relative
+      #   paths will be resolved from here.
+      # * +path+ - the path to resolve.  If it starts with +/+,
+      #   the path will be resolved starting from +entry.root+.
+      #
+      # ==== Examples
+      #
+      #     Chef::ChefFS::FileSystem.resolve_path(root_path, 'cookbooks/java/recipes/default.rb')
+      #
+      def self.resolve_path(entry, path)
+        return entry if path.length == 0
+        return resolve_path(entry.root, path) if path[0,1] == "/" && entry.root != entry
+        if path[0,1] == "/"
+          path = path[1,path.length-1]
+        end
+
+        result = entry
+        Chef::ChefFS::PathUtils::split(path).each do |part|
+          result = result.child(part)
+        end
+        result
+      end
+
+      # Copy everything matching the given pattern from src to dest.
+      #
+      # After this method completes, everything in dest matching the
+      # given pattern will look identical to src.
+      #
+      # ==== Attributes
+      #
+      # * +pattern+ - Chef::ChefFS::FilePattern to match children under
+      # * +src_root+ - the root from which things will be copied
+      # * +dest_root+ - the root to which things will be copied
+      # * +recurse_depth+ - the maximum depth to copy things. +nil+
+      #   means infinite depth.  0 means no recursion.
+      # * +options+ - hash of options:
+      #   - +purge+ - if +true+, items in +dest+ that are not in +src+
+      #   will be deleted from +dest+.  If +false+, these items will
+      #   be left alone.
+      #   - +force+ - if +true+, matching files are always copied from
+      #     +src+ to +dest+.  If +false+, they will only be copied if
+      #     actually different (which will take time to determine).
+      #   - +dry_run+ - if +true+, action will not actually be taken;
+      #     things will be printed out instead.
+      #
+      # ==== Examples
+      #
+      #     Chef::ChefFS::FileSystem.copy_to(FilePattern.new('/cookbooks'),
+      #       chef_fs, local_fs, nil, true) do |message|
+      #       puts message
+      #     end
+      #
+      def self.copy_to(pattern, src_root, dest_root, recurse_depth, options, ui = nil, format_path = nil)
+        found_result = false
+        error = false
+        parallel_do(list_pairs(pattern, src_root, dest_root)) do |src, dest|
+          found_result = true
+          new_dest_parent = get_or_create_parent(dest, options, ui, format_path)
+          child_error = copy_entries(src, dest, new_dest_parent, recurse_depth, options, ui, format_path)
+          error ||= child_error
+        end
+        if !found_result && pattern.exact_path
+          ui.error "#{pattern}: No such file or directory on remote or local" if ui
+          error = true
+        end
+        error
+      end
+
+      # Yield entries for children that are in either +a_root+ or +b_root+, with
+      # matching pairs matched up.
+      #
+      # ==== Yields
+      #
+      # Yields matching entries in pairs:
+      #
+      #    [ a_entry, b_entry ]
+      #
+      # ==== Example
+      #
+      #     Chef::ChefFS::FileSystem.list_pairs(FilePattern.new('**x.txt', a_root, b_root)).each do |a, b|
+      #       ...
+      #     end
+      #
+      def self.list_pairs(pattern, a_root, b_root)
+        PairLister.new(pattern, a_root, b_root)
+      end
+
+      class PairLister
+        include Enumerable
+
+        def initialize(pattern, a_root, b_root)
+          @pattern = pattern
+          @a_root = a_root
+          @b_root = b_root
+        end
+
+        attr_reader :pattern
+        attr_reader :a_root
+        attr_reader :b_root
+
+        def each
+          # Make sure everything on the server is also on the filesystem, and diff
+          found_paths = Set.new
+          Chef::ChefFS::FileSystem.list(a_root, pattern).each do |a|
+            found_paths << a.path
+            b = Chef::ChefFS::FileSystem.resolve_path(b_root, a.path)
+            yield [ a, b ]
+          end
+
+          # Check the outer regex pattern to see if it matches anything on the
+          # filesystem that isn't on the server
+          Chef::ChefFS::FileSystem.list(b_root, pattern).each do |b|
+            if !found_paths.include?(b.path)
+              a = Chef::ChefFS::FileSystem.resolve_path(a_root, b.path)
+              yield [ a, b ]
+            end
+          end
+        end
+      end
+
+      # Get entries for children of either a or b, with matching pairs matched up.
+      #
+      # ==== Returns
+      #
+      # An array of child pairs.
+      #
+      #     [ [ a_child, b_child ], ... ]
+      #
+      # If a child is only in a or only in b, the other child entry will be
+      # retrieved by name (and will most likely be a "nonexistent child").
+      #
+      # ==== Example
+      #
+      #     Chef::ChefFS::FileSystem.child_pairs(a, b).length
+      #
+      def self.child_pairs(a, b)
+        # If both are directories, recurse into them and diff the children instead of returning ourselves.
+        result = []
+        a_children_names = Set.new
+        a.children.each do |a_child|
+          a_children_names << a_child.name
+          result << [ a_child, b.child(a_child.name) ]
+        end
+
+        # Check b for children that aren't in a
+        b.children.each do |b_child|
+          if !a_children_names.include?(b_child.name)
+            result << [ a.child(b_child.name), b_child ]
+          end
+        end
+        result
+      end
+
+      def self.compare(a, b)
+        are_same, a_value, b_value = a.compare_to(b)
+        if are_same.nil?
+          are_same, b_value, a_value = b.compare_to(a)
+        end
+        if are_same.nil?
+          # TODO these reads can be parallelized
+          begin
+            a_value = a.read if a_value.nil?
+          rescue Chef::ChefFS::FileSystem::NotFoundError
+            a_value = :none
+          end
+          begin
+            b_value = b.read if b_value.nil?
+          rescue Chef::ChefFS::FileSystem::NotFoundError
+            b_value = :none
+          end
+          are_same = (a_value == b_value)
+        end
+        [ are_same, a_value, b_value ]
+      end
+
+      private
+
+      # Copy two entries (could be files or dirs)
+      def self.copy_entries(src_entry, dest_entry, new_dest_parent, recurse_depth, options, ui, format_path)
+        # A NOTE about this algorithm:
+        # There are cases where this algorithm does too many network requests.
+        # knife upload with a specific filename will first check if the file
+        # exists (a "dir" in the parent) before deciding whether to POST or
+        # PUT it.  If we just tried PUT (or POST) and then tried the other if
+        # the conflict failed, we wouldn't need to check existence.
+        # On the other hand, we may already have DONE the request, in which
+        # case we shouldn't waste time trying PUT if we know the file doesn't
+        # exist.
+        # Will need to decide how that works with checksums, though.
+
+        error = false
+        begin
+          dest_path = format_path.call(dest_entry) if ui
+          src_path = format_path.call(src_entry) if ui
+          if !src_entry.exists?
+            if options[:purge]
+              # If we would not have uploaded it, we will not purge it.
+              if src_entry.parent.can_have_child?(dest_entry.name, dest_entry.dir?)
+                if options[:dry_run]
+                  ui.output "Would delete #{dest_path}" if ui
+                else
+                  dest_entry.delete(true)
+                  ui.output "Deleted extra entry #{dest_path} (purge is on)" if ui
+                end
+              else
+                ui.output ("Not deleting extra entry #{dest_path} (purge is off)") if ui
+              end
+            end
+
+          elsif !dest_entry.exists?
+            if new_dest_parent.can_have_child?(src_entry.name, src_entry.dir?)
+              # If the entry can do a copy directly from filesystem, do that.
+              if new_dest_parent.respond_to?(:create_child_from)
+                if options[:dry_run]
+                  ui.output "Would create #{dest_path}" if ui
+                else
+                  new_dest_parent.create_child_from(src_entry)
+                  ui.output "Created #{dest_path}" if ui
+                end
+                return
+              end
+
+              if src_entry.dir?
+                if options[:dry_run]
+                  ui.output "Would create #{dest_path}" if ui
+                  new_dest_dir = new_dest_parent.child(src_entry.name)
+                else
+                  new_dest_dir = new_dest_parent.create_child(src_entry.name, nil)
+                  ui.output "Created #{dest_path}" if ui
+                end
+                # Directory creation is recursive.
+                if recurse_depth != 0
+                  parallel_do(src_entry.children) do |src_child|
+                    new_dest_child = new_dest_dir.child(src_child.name)
+                    child_error = copy_entries(src_child, new_dest_child, new_dest_dir, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path)
+                    error ||= child_error
+                  end
+                end
+              else
+                if options[:dry_run]
+                  ui.output "Would create #{dest_path}" if ui
+                else
+                  new_dest_parent.create_child(src_entry.name, src_entry.read)
+                  ui.output "Created #{dest_path}" if ui
+                end
+              end
+            end
+
+          else
+            # Both exist.
+
+            # If the entry can do a copy directly, do that.
+            if dest_entry.respond_to?(:copy_from)
+              if options[:force] || compare(src_entry, dest_entry)[0] == false
+                if options[:dry_run]
+                  ui.output "Would update #{dest_path}" if ui
+                else
+                  dest_entry.copy_from(src_entry, options)
+                  ui.output "Updated #{dest_path}" if ui
+                end
+              end
+              return
+            end
+
+            # If they are different types, log an error.
+            if src_entry.dir?
+              if dest_entry.dir?
+                # If both are directories, recurse into their children
+                if recurse_depth != 0
+                  parallel_do(child_pairs(src_entry, dest_entry)) do |src_child, dest_child|
+                    child_error = copy_entries(src_child, dest_child, dest_entry, recurse_depth ? recurse_depth - 1 : recurse_depth, options, ui, format_path)
+                    error ||= child_error
+                  end
+                end
+              else
+                # If they are different types.
+                ui.error("File #{src_path} is a directory while file #{dest_path} is a regular file\n") if ui
+                return
+              end
+            else
+              if dest_entry.dir?
+                ui.error("File #{src_path} is a directory while file #{dest_path} is a regular file\n") if ui
+                return
+              else
+
+                # Both are files!  Copy them unless we're sure they are the same.'
+                if options[:diff] == false
+                  should_copy = false
+                elsif options[:force]
+                  should_copy = true
+                  src_value = nil
+                else
+                  are_same, src_value, dest_value = compare(src_entry, dest_entry)
+                  should_copy = !are_same
+                end
+                if should_copy
+                  if options[:dry_run]
+                    ui.output "Would update #{dest_path}" if ui
+                  else
+                    src_value = src_entry.read if src_value.nil?
+                    dest_entry.write(src_value)
+                    ui.output "Updated #{dest_path}" if ui
+                  end
+                end
+              end
+            end
+          end
+        rescue DefaultEnvironmentCannotBeModifiedError => e
+          ui.warn "#{format_path.call(e.entry)} #{e.reason}." if ui
+        rescue OperationFailedError => e
+          ui.error "#{format_path.call(e.entry)} failed to #{e.operation}: #{e.message}" if ui
+          error = true
+        rescue OperationNotAllowedError => e
+          ui.error "#{format_path.call(e.entry)} #{e.reason}." if ui
+          error = true
+        end
+        error
+      end
+
+      def self.get_or_create_parent(entry, options, ui, format_path)
+        parent = entry.parent
+        if parent && !parent.exists?
+          parent_path = format_path.call(parent) if ui
+          parent_parent = get_or_create_parent(parent, options, ui, format_path)
+          if options[:dry_run]
+            ui.output "Would create #{parent_path}" if ui
+          else
+            parent = parent_parent.create_child(parent.name, nil)
+            ui.output "Created #{parent_path}" if ui
+          end
+        end
+        return parent
+      end
+
+      def self.parallel_do(enum, options = {}, &block)
+        Chef::ChefFS::Parallelizer.parallelize(enum, options, &block).to_a
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/acl_dir.rb b/lib/chef/chef_fs/file_system/acl_dir.rb
new file mode 100644
index 0000000..c2354d4
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/acl_dir.rb
@@ -0,0 +1,64 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/acl_entry'
+require 'chef/chef_fs/file_system/operation_not_allowed_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class AclDir < BaseFSDir
+        def api_path
+          parent.parent.child(name).api_path
+        end
+
+        def child(name)
+          result = @children.select { |child| child.name == name }.first if @children
+          result ||= can_have_child?(name, false) ?
+                     AclEntry.new(name, self) : NonexistentFSObject.new(name, self)
+        end
+
+        def can_have_child?(name, is_dir)
+          name =~ /\.json$/ && !is_dir
+        end
+
+        def children
+          if @children.nil?
+            # Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
+            names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
+            @children = names.map { |name| AclEntry.new(name, self, true) }
+          end
+          @children
+        end
+
+        def create_child(name, file_contents)
+          raise OperationNotAllowedError.new(:create_child, self), "ACLs can only be updated, and can only be created when the corresponding object is created."
+        end
+
+        def data_handler
+          parent.data_handler
+        end
+
+        def rest
+          parent.rest
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/acl_entry.rb b/lib/chef/chef_fs/file_system/acl_entry.rb
new file mode 100644
index 0000000..8edc02d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/acl_entry.rb
@@ -0,0 +1,58 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/operation_not_allowed_error'
+require 'chef/chef_fs/file_system/operation_failed_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class AclEntry < RestListEntry
+        PERMISSIONS = %w(create read update delete grant)
+
+        def api_path
+          "#{super}/_acl"
+        end
+
+        def delete(recurse)
+          raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self, e), "ACLs cannot be deleted."
+        end
+
+        def write(file_contents)
+          # ACL writes are fun.
+          acls = data_handler.normalize(JSON.parse(file_contents, :create_additions => false), self)
+          PERMISSIONS.each do |permission|
+            begin
+              rest.put("#{api_path}/#{permission}", { permission => acls[permission] })
+            rescue Timeout::Error => e
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}"
+            rescue Net::HTTPServerException => e
+              if e.response.code == "404"
+                raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+              else
+                raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
+              end
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/acls_dir.rb b/lib/chef/chef_fs/file_system/acls_dir.rb
new file mode 100644
index 0000000..938bf73
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/acls_dir.rb
@@ -0,0 +1,68 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/acl_dir'
+require 'chef/chef_fs/file_system/cookbooks_acl_dir'
+require 'chef/chef_fs/file_system/acl_entry'
+require 'chef/chef_fs/data_handler/acl_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class AclsDir < BaseFSDir
+        ENTITY_TYPES = %w(clients containers cookbooks data_bags environments groups nodes roles) # we don't read sandboxes, so we don't read their acls
+
+        def initialize(parent)
+          super('acls', parent)
+        end
+
+        def data_handler
+          @data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new
+        end
+
+        def api_path
+          parent.api_path
+        end
+
+        def can_have_child?(name, is_dir)
+          is_dir ? ENTITY_TYPES.include(name) : name == 'organization.json'
+        end
+
+        def children
+          if @children.nil?
+            @children = ENTITY_TYPES.map do |entity_type|
+              case entity_type
+              when 'cookbooks'
+                CookbooksAclDir.new(entity_type, self)
+              else
+                AclDir.new(entity_type, self)
+              end
+            end
+            @children << AclEntry.new('organization.json', self, true) # the org acl is retrieved as GET /organizations/ORGNAME/ANYTHINGATALL/_acl
+          end
+          @children
+        end
+
+        def rest
+          parent.rest
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/already_exists_error.rb b/lib/chef/chef_fs/file_system/already_exists_error.rb
new file mode 100644
index 0000000..bf8994f
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/already_exists_error.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/operation_failed_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class AlreadyExistsError < OperationFailedError
+        def initialize(operation, entry, cause = nil)
+          super(operation, entry, cause)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/base_fs_dir.rb b/lib/chef/chef_fs/file_system/base_fs_dir.rb
new file mode 100644
index 0000000..74038f4
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb
@@ -0,0 +1,47 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/nonexistent_fs_object'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class BaseFSDir < BaseFSObject
+        def initialize(name, parent)
+          super
+        end
+
+        def dir?
+          true
+        end
+
+        # Override child(name) to provide a child object by name without the network read
+        def child(name)
+          children.select { |child| child.name == name }.first || NonexistentFSObject.new(name, self)
+        end
+
+        def can_have_child?(name, is_dir)
+          true
+        end
+
+        # Abstract: children
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/base_fs_object.rb b/lib/chef/chef_fs/file_system/base_fs_object.rb
new file mode 100644
index 0000000..43e6a51
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -0,0 +1,180 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/path_utils'
+require 'chef/chef_fs/file_system/operation_not_allowed_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class BaseFSObject
+        def initialize(name, parent)
+          @parent = parent
+          @name = name
+          if parent
+            @path = Chef::ChefFS::PathUtils::join(parent.path, name)
+          else
+            if name != ''
+              raise ArgumentError, "Name of root object must be empty string: was '#{name}' instead"
+            end
+            @path = '/'
+          end
+        end
+
+        attr_reader :name
+        attr_reader :parent
+        attr_reader :path
+
+        # Override this if you have a special comparison algorithm that can tell
+        # you whether this entry is the same as another--either a quicker or a
+        # more reliable one.  Callers will use this to decide whether to upload,
+        # download or diff an object.
+        #
+        # You should not override this if you're going to do the standard
+        # +self.read == other.read+.  If you return +nil+, the caller will call
+        # +other.compare_to(you)+ instead.  Give them a chance :)
+        #
+        # ==== Parameters
+        #
+        # * +other+ - the entry to compare to
+        #
+        # ==== Returns
+        #
+        # * +[ are_same, value, other_value ]+
+        #   +are_same+ may be +true+, +false+ or +nil+ (which means "don't know").
+        #   +value+ and +other_value+ must either be the text of +self+ or +other+,
+        #   +:none+ (if the entry does not exist or has no value) or +nil+ if the
+        #   value was not retrieved.
+        # * +nil+ if a definitive answer cannot be had and nothing was retrieved.
+        #
+        # ==== Example
+        #
+        #     are_same, value, other_value = entry.compare_to(other)
+        #     if are_same.nil?
+        #       are_same, other_value, value = other.compare_to(entry)
+        #     end
+        #     if are_same.nil?
+        #       value = entry.read if value.nil?
+        #       other_value = entry.read if other_value.nil?
+        #       are_same = (value == other_value)
+        #     end
+        def compare_to(other)
+          nil
+        end
+
+        # Override can_have_child? to report whether a given file *could* be added
+        # to this directory.  (Some directories can't have subdirs, some can only have .json
+        # files, etc.)
+        def can_have_child?(name, is_dir)
+          false
+        end
+
+        # Get a child of this entry with the given name.  This MUST always
+        # return a child, even if it is NonexistentFSObject.  Overriders should
+        # take caution not to do expensive network requests to get the list of
+        # children to fulfill this request, unless absolutely necessary here; it
+        # is intended as a quick way to traverse a hierarchy.
+        #
+        # For example, knife show /data_bags/x/y.json will call
+        # root.child('data_bags').child('x').child('y.json'), which can then
+        # directly perform a network request to retrieve the y.json data bag.  No
+        # network request was necessary to retrieve
+        def child(name)
+          NonexistentFSObject.new(name, self)
+        end
+
+        # Override children to report your *actual* list of children as an array.
+        def children
+          raise NotFoundError.new(self) if !exists?
+          []
+        end
+
+        # Expand this entry into a chef object (Chef::Role, ::Node, etc.)
+        def chef_object
+          raise NotFoundError.new(self) if !exists?
+          nil
+        end
+
+        # Create a child of this entry with the given name and contents.  If
+        # contents is nil, create a directory.
+        #
+        # NOTE: create_child_from is an optional method that can also be added to
+        # your entry class, and will be called without actually reading the
+        # file_contents.  This is used for knife upload /cookbooks/cookbookname.
+        def create_child(name, file_contents)
+          raise NotFoundError.new(self) if !exists?
+          raise OperationNotAllowedError.new(:create_child, self)
+        end
+
+        # Delete this item, possibly recursively.  Entries MUST NOT delete a
+        # directory unless recurse is true.
+        def delete(recurse)
+          raise NotFoundError.new(self) if !exists?
+          raise OperationNotAllowedError.new(:delete, self)
+        end
+
+        # Ask whether this entry is a directory.  If not, it is a file.
+        def dir?
+          false
+        end
+
+        # Ask whether this entry exists.
+        def exists?
+          true
+        end
+
+        # Printable path, generally used to distinguish paths in one root from
+        # paths in another.
+        def path_for_printing
+          if parent
+            parent_path = parent.path_for_printing
+            if parent_path == '.'
+              name
+            else
+              Chef::ChefFS::PathUtils::join(parent.path_for_printing, name)
+            end
+          else
+            name
+          end
+        end
+
+        def root
+          parent ? parent.root : self
+        end
+
+        # Read the contents of this file entry.
+        def read
+          raise NotFoundError.new(self) if !exists?
+          raise OperationNotAllowedError.new(:read, self)
+        end
+
+        # Write the contents of this file entry.
+        def write(file_contents)
+          raise NotFoundError.new(self) if !exists?
+          raise OperationNotAllowedError.new(:write, self)
+        end
+
+        # Important directory attributes: name, parent, path, root
+        # Overridable attributes: dir?, child(name), path_for_printing
+        # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to
+      end # class BaseFsObject
+    end
+  end
+end
+
+require 'chef/chef_fs/file_system/nonexistent_fs_object'
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb
new file mode 100644
index 0000000..7d2a930
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb
@@ -0,0 +1,37 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/acls_dir'
+require 'chef/chef_fs/data_handler/acl_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class ChefRepositoryFileSystemAclsDir < ChefRepositoryFileSystemEntry
+        def initialize(name, parent, path = nil)
+          super(name, parent, path, Chef::ChefFS::DataHandler::AclDataHandler.new)
+        end
+
+        def can_have_child?(name, is_dir)
+          is_dir ? Chef::ChefFS::FileSystem::AclsDir::ENTITY_TYPES.include?(name) : name == 'organization.json'
+        end
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
new file mode 100644
index 0000000..5203637
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
@@ -0,0 +1,93 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry'
+require 'chef/chef_fs/file_system/cookbook_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/cookbook/chefignore'
+require 'chef/cookbook/cookbook_version_loader'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class ChefRepositoryFileSystemCookbookDir < ChefRepositoryFileSystemCookbookEntry
+        def initialize(name, parent, file_path = nil)
+          super(name, parent, file_path)
+        end
+
+        def chef_object
+          begin
+            loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+            # We need the canonical cookbook name if we are using versioned cookbooks, but we don't
+            # want to spend a lot of time adding code to the main Chef libraries
+            if Chef::Config[:versioned_cookbooks]
+              _canonical_name = canonical_cookbook_name(File.basename(file_path))
+              fail "When versioned_cookbooks mode is on, cookbook #{file_path} must match format <cookbook_name>-x.y.z"  unless _canonical_name
+
+              # KLUDGE: We shouldn't have to use instance_variable_set
+              loader.instance_variable_set(:@cookbook_name, _canonical_name)
+            end
+
+            loader.load_cookbooks
+            return loader.cookbook_version
+          rescue
+            Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
+          end
+          nil
+        end
+
+        def children
+          begin
+            Dir.entries(file_path).sort.
+                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
+                map { |child_name| make_child(child_name) }.
+                select { |entry| !(entry.dir? && entry.children.size == 0) }
+          rescue Errno::ENOENT
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+          end
+        end
+
+        def can_have_child?(name, is_dir)
+          if is_dir
+            # Only the given directories will be uploaded.
+            return CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != 'root_files'
+          end
+          super(name, is_dir)
+        end
+
+        # Exposed as a class method so that it can be used elsewhere
+        def self.canonical_cookbook_name(entry_name)
+          name_match = Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME.match(entry_name)
+          return nil if name_match.nil?
+          return name_match[1]
+        end
+
+        def canonical_cookbook_name(entry_name)
+          self.class.canonical_cookbook_name(entry_name)
+        end
+
+        protected
+
+        def make_child(child_name)
+          segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
+          ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
new file mode 100644
index 0000000..6541b07
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
@@ -0,0 +1,86 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class ChefRepositoryFileSystemCookbookEntry < ChefRepositoryFileSystemEntry
+        def initialize(name, parent, file_path = nil, ruby_only = false, recursive = false)
+          super(name, parent, file_path)
+          @ruby_only = ruby_only
+          @recursive = recursive
+        end
+
+        attr_reader :ruby_only
+        attr_reader :recursive
+
+        def children
+          begin
+            Dir.entries(file_path).sort.
+                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
+                map { |child_name| make_child(child_name) }.
+                select { |entry| !(entry.dir? && entry.children.size == 0) }
+          rescue Errno::ENOENT
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+          end
+        end
+
+        def can_have_child?(name, is_dir)
+          if is_dir
+            return recursive && name != '.' && name != '..'
+          elsif ruby_only
+            return false if name[-3..-1] != '.rb'
+          end
+
+          # Check chefignore
+          ignorer = parent
+          begin
+            if ignorer.is_a?(ChefRepositoryFileSystemCookbooksDir)
+              # Grab the path from entry to child
+              path_to_child = name
+              child = self
+              while child.parent != ignorer
+                path_to_child = PathUtils.join(child.name, path_to_child)
+                child = child.parent
+              end
+              # Check whether that relative path is ignored
+              return !ignorer.chefignore || !ignorer.chefignore.ignored?(path_to_child)
+            end
+            ignorer = ignorer.parent
+          end while ignorer
+
+          true
+        end
+
+        def write_pretty_json
+          false
+        end
+
+        protected
+
+        def make_child(child_name)
+          ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
new file mode 100644
index 0000000..6e16f18
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
@@ -0,0 +1,70 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir'
+require 'chef/cookbook/chefignore'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class ChefRepositoryFileSystemCookbooksDir < ChefRepositoryFileSystemEntry
+        def initialize(name, parent, file_path)
+          super(name, parent, file_path)
+          begin
+            @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
+          rescue Errno::EISDIR
+          rescue Errno::EACCES
+            # Work around a bug in Chefignore when chefignore is a directory
+          end
+        end
+
+        attr_reader :chefignore
+
+        def children
+          begin
+            Dir.entries(file_path).sort.
+                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
+                map { |child_name| make_child(child_name) }.
+                select do |entry|
+                  # empty cookbooks and cookbook directories are ignored
+                  if entry.children.size == 0
+                    Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
+                    false
+                  else
+                    true
+                  end
+                end
+          rescue Errno::ENOENT
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+          end
+        end
+
+        def can_have_child?(name, is_dir)
+          is_dir && !name.start_with?('.')
+        end
+
+        protected
+
+        def make_child(child_name)
+          ChefRepositoryFileSystemCookbookDir.new(child_name, self)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb
new file mode 100644
index 0000000..73556b2
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb
@@ -0,0 +1,36 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
+require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class ChefRepositoryFileSystemDataBagsDir < ChefRepositoryFileSystemEntry
+        def initialize(name, parent, path = nil)
+          super(name, parent, path, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
+        end
+
+        def can_have_child?(name, is_dir)
+          is_dir && !name.start_with?('.')
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
new file mode 100644
index 0000000..3d3f582
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
@@ -0,0 +1,88 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Author:: Ho-Sheng Hsiao (<hosh 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 'chef/chef_fs/file_system/file_system_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
+      # except can inflate Chef objects
+      class ChefRepositoryFileSystemEntry < FileSystemEntry
+        def initialize(name, parent, file_path = nil, data_handler = nil)
+          super(name, parent, file_path)
+          @data_handler = data_handler
+        end
+
+        def write_pretty_json
+          root.write_pretty_json
+        end
+
+        def data_handler
+          @data_handler || parent.data_handler
+        end
+
+        def chef_object
+          begin
+            return data_handler.chef_object(JSON.parse(read, :create_additions => false))
+          rescue
+            Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
+          end
+          nil
+        end
+
+        def can_have_child?(name, is_dir)
+          !is_dir && name[-5..-1] == '.json'
+        end
+
+        def write(file_contents)
+          if file_contents && write_pretty_json && name[-5..-1] == '.json'
+            file_contents = minimize(file_contents, self)
+          end
+          super(file_contents)
+        end
+
+        def minimize(file_contents, entry)
+          object = JSONCompat.from_json(file_contents, :create_additions => false)
+          object = data_handler.normalize(object, entry)
+          object = data_handler.minimize(object, entry)
+          JSONCompat.to_json_pretty(object)
+        end
+
+        def children
+          # Except cookbooks and data bag dirs, all things must be json files
+          begin
+            Dir.entries(file_path).sort.
+                select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
+                map { |child_name| make_child(child_name) }
+          rescue Errno::ENOENT
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+          end
+        end
+
+        protected
+
+        def make_child(child_name)
+          ChefRepositoryFileSystemEntry.new(child_name, self)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
new file mode 100644
index 0000000..d615e0f
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
@@ -0,0 +1,128 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
+require 'chef/chef_fs/file_system/chef_repository_file_system_acls_dir'
+require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
+require 'chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir'
+require 'chef/chef_fs/file_system/multiplexed_dir'
+require 'chef/chef_fs/data_handler/client_data_handler'
+require 'chef/chef_fs/data_handler/environment_data_handler'
+require 'chef/chef_fs/data_handler/node_data_handler'
+require 'chef/chef_fs/data_handler/role_data_handler'
+require 'chef/chef_fs/data_handler/user_data_handler'
+require 'chef/chef_fs/data_handler/group_data_handler'
+require 'chef/chef_fs/data_handler/container_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class ChefRepositoryFileSystemRootDir < BaseFSDir
+        def initialize(child_paths)
+          super("", nil)
+          @child_paths = child_paths
+        end
+
+        attr_accessor :write_pretty_json
+
+        attr_reader :child_paths
+
+        def children
+          @children ||= child_paths.keys.sort.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
+        end
+
+        def can_have_child?(name, is_dir)
+          child_paths.has_key?(name) && is_dir
+        end
+
+        def create_child(name, file_contents = nil)
+          child_paths[name].each do |path|
+            begin
+              Dir.mkdir(path)
+            rescue Errno::EEXIST
+            end
+          end
+          child = make_child_entry(name)
+          @children = nil
+          child
+        end
+
+        def json_class
+          nil
+        end
+
+        # Used to print out the filesystem
+        def fs_description
+          repo_path = File.dirname(child_paths['cookbooks'][0])
+          result = "repository at #{repo_path}\n"
+          if Chef::Config[:versioned_cookbooks]
+            result << "  Multiple versions per cookbook\n"
+          else
+            result << "  One version per cookbook\n"
+          end
+          child_paths.each_pair do |name, paths|
+            if paths.any? { |path| File.dirname(path) != repo_path }
+              result << "  #{name} at #{paths.join(', ')}\n"
+            end
+          end
+          result
+        end
+
+        private
+
+        def make_child_entry(name)
+          paths = child_paths[name].select do |path|
+            File.exists?(path)
+          end
+          if paths.size == 0
+            return nil
+          end
+          if name == 'cookbooks'
+            dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
+          elsif name == 'data_bags'
+            dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
+          elsif name == 'acls'
+            dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
+          else
+            data_handler = case name
+              when 'clients'
+                Chef::ChefFS::DataHandler::ClientDataHandler.new
+              when 'environments'
+                Chef::ChefFS::DataHandler::EnvironmentDataHandler.new
+              when 'nodes'
+                Chef::ChefFS::DataHandler::NodeDataHandler.new
+              when 'roles'
+                Chef::ChefFS::DataHandler::RoleDataHandler.new
+              when 'users'
+                Chef::ChefFS::DataHandler::UserDataHandler.new
+              when 'groups'
+                Chef::ChefFS::DataHandler::GroupDataHandler.new
+              when 'containers'
+                Chef::ChefFS::DataHandler::ContainerDataHandler.new
+              else
+                raise "Unknown top level path #{name}"
+              end
+            dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, data_handler) }
+          end
+          MultiplexedDir.new(dirs)
+        end
+      end
+    end
+  end
+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
new file mode 100644
index 0000000..0083ee4
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
@@ -0,0 +1,120 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/server_api'
+require 'chef/chef_fs/file_system/acls_dir'
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/cookbooks_dir'
+require 'chef/chef_fs/file_system/data_bags_dir'
+require 'chef/chef_fs/file_system/nodes_dir'
+require 'chef/chef_fs/file_system/environments_dir'
+require 'chef/chef_fs/data_handler/client_data_handler'
+require 'chef/chef_fs/data_handler/role_data_handler'
+require 'chef/chef_fs/data_handler/user_data_handler'
+require 'chef/chef_fs/data_handler/group_data_handler'
+require 'chef/chef_fs/data_handler/container_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class ChefServerRootDir < BaseFSDir
+        def initialize(root_name, chef_config, options = {})
+          super("", nil)
+          @chef_server_url = chef_config[:chef_server_url]
+          @chef_username = chef_config[:node_name]
+          @chef_private_key = chef_config[:client_key]
+          @environment = chef_config[:environment]
+          @repo_mode = chef_config[:repo_mode]
+          @root_name = root_name
+          @cookbook_version = options[:cookbook_version] # Used in knife diff and download for server cookbook version
+        end
+
+        attr_reader :chef_server_url
+        attr_reader :chef_username
+        attr_reader :chef_private_key
+        attr_reader :environment
+        attr_reader :repo_mode
+        attr_reader :cookbook_version
+
+        def fs_description
+          "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}"
+        end
+
+        def rest
+          Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true)
+        end
+
+        def get_json(path)
+          Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key).get(path)
+        end
+
+        def chef_rest
+          Chef::REST.new(chef_server_url, chef_username, chef_private_key)
+        end
+
+        def api_path
+          ""
+        end
+
+        def path_for_printing
+          "#{@root_name}/"
+        end
+
+        def can_have_child?(name, is_dir)
+          is_dir && children.any? { |child| child.name == name }
+        end
+
+        def org
+          @org ||= if URI.parse(chef_server_url).path =~ /^\/+organizations\/+([^\/]+)$/
+            $1
+          else
+            nil
+          end
+        end
+
+        def children
+          @children ||= begin
+            result = [
+              CookbooksDir.new(self),
+              DataBagsDir.new(self),
+              EnvironmentsDir.new(self),
+              RestListDir.new("roles", self, nil, Chef::ChefFS::DataHandler::RoleDataHandler.new)
+            ]
+            if repo_mode == 'hosted_everything'
+              result += [
+                AclsDir.new(self),
+                RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
+                RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new),
+                RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new),
+                NodesDir.new(self)
+              ]
+            elsif repo_mode != 'static'
+              result += [
+                RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
+                NodesDir.new(self),
+                RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new)
+              ]
+            end
+            result.sort_by { |child| child.name }
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
new file mode 100644
index 0000000..d7411e1
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb
@@ -0,0 +1,224 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/cookbook_subdir'
+require 'chef/chef_fs/file_system/cookbook_file'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/cookbook_version'
+require 'chef/cookbook_uploader'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class CookbookDir < BaseFSDir
+        def initialize(name, parent, options = {})
+          super(name, parent)
+          @exists = options[:exists]
+          # If the name is apache2-1.0.0 and versioned_cookbooks is on, we know
+          # the actual cookbook_name and version.
+          if Chef::Config[:versioned_cookbooks]
+            if name =~ VALID_VERSIONED_COOKBOOK_NAME
+              @cookbook_name = $1
+              @version = $2
+            else
+              @exists = false
+            end
+          else
+            @cookbook_name = name
+            @version = root.cookbook_version # nil unless --cookbook-version specified in download/diff
+          end
+        end
+
+        attr_reader :cookbook_name, :version
+
+        COOKBOOK_SEGMENT_INFO = {
+          :attributes => { :ruby_only => true },
+          :definitions => { :ruby_only => true },
+          :recipes => { :ruby_only => true },
+          :libraries => { :ruby_only => true },
+          :templates => { :recursive => true },
+          :files => { :recursive => true },
+          :resources => { :ruby_only => true, :recursive => true },
+          :providers => { :ruby_only => true, :recursive => true },
+          :root_files => { }
+        }
+
+        # See Erchef code
+        # https://github.com/opscode/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94
+        VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/
+
+        def add_child(child)
+          @children << child
+        end
+
+        def api_path
+          "#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
+        end
+
+        def child(name)
+          # Since we're ignoring the rules and doing a network request here,
+          # we need to make sure we don't rethrow the exception.  (child(name)
+          # is not supposed to fail.)
+          begin
+            result = children.select { |child| child.name == name }.first
+            return result if result
+          rescue Chef::ChefFS::FileSystem::NotFoundError
+          end
+          return NonexistentFSObject.new(name, self)
+        end
+
+        def can_have_child?(name, is_dir)
+          # A cookbook's root may not have directories unless they are segment directories
+          return name != 'root_files' && COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) if is_dir
+          return true
+        end
+
+        def children
+          if @children.nil?
+            @children = []
+            manifest = chef_object.manifest
+            COOKBOOK_SEGMENT_INFO.each do |segment, segment_info|
+              next unless manifest.has_key?(segment)
+
+              # Go through each file in the manifest for the segment, and
+              # add cookbook subdirs and files for it.
+              manifest[segment].each do |segment_file|
+                parts = segment_file[:path].split('/')
+                # Get or create the path to the file
+                container = self
+                parts[0,parts.length-1].each do |part|
+                  old_container = container
+                  container = old_container.children.select { |child| part == child.name }.first
+                  if !container
+                    container = CookbookSubdir.new(part, old_container, segment_info[:ruby_only], segment_info[:recursive])
+                    old_container.add_child(container)
+                  end
+                end
+                # Create the file itself
+                container.add_child(CookbookFile.new(parts[parts.length-1], container, segment_file))
+              end
+            end
+            @children = @children.sort_by { |c| c.name }
+          end
+          @children
+        end
+
+        def dir?
+          exists?
+        end
+
+        def delete(recurse)
+          if recurse
+            begin
+              rest.delete(api_path)
+            rescue Timeout::Error => e
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
+            rescue Net::HTTPServerException
+              if $!.response.code == "404"
+                raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+              else
+                raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "HTTP error deleting: #{e}"
+              end
+            end
+          else
+            raise NotFoundError.new(self) if !exists?
+            raise MustDeleteRecursivelyError.new(self), "#{path_for_printing} must be deleted recursively"
+          end
+        end
+
+        # In versioned cookbook mode, actually check if the version exists
+        # Probably want to cache this.
+        def exists?
+          if @exists.nil?
+            @exists = parent.children.any? { |child| child.name == name }
+          end
+          @exists
+        end
+
+        def compare_to(other)
+          if !other.dir?
+            return [ !exists?, nil, nil ]
+          end
+          are_same = true
+          Chef::ChefFS::CommandLine::diff_entries(self, other, nil, :name_only).each do |type, old_entry, new_entry|
+            if [ :directory_to_file, :file_to_directory, :deleted, :added, :modified ].include?(type)
+              are_same = false
+            end
+          end
+          [ are_same, nil, nil ]
+        end
+
+        def copy_from(other, options = {})
+          parent.upload_cookbook_from(other, options)
+        end
+
+        def rest
+          parent.rest
+        end
+
+        def chef_object
+          # We cheat and cache here, because it seems like a good idea to keep
+          # the cookbook view consistent with the directory structure.
+          return @chef_object if @chef_object
+
+          # The negative (not found) response is cached
+          if @could_not_get_chef_object
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+          end
+
+          begin
+            # We want to fail fast, for now, because of the 500 issue :/
+            # This will make things worse for parallelism, a little, because
+            # Chef::Config is global and this could affect other requests while
+            # this request is going on.  (We're not parallel yet, but we will be.)
+            # Chef bug http://tickets.opscode.com/browse/CHEF-3066
+            old_retry_count = Chef::Config[:http_retry_count]
+            begin
+              Chef::Config[:http_retry_count] = 0
+              @chef_object ||= Chef::CookbookVersion.json_create(root.get_json(api_path))
+            ensure
+              Chef::Config[:http_retry_count] = old_retry_count
+            end
+
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}"
+
+          rescue Net::HTTPServerException => e
+            if e.response.code == "404"
+              @could_not_get_chef_object = e
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}"
+            end
+
+          # Chef bug http://tickets.opscode.com/browse/CHEF-3066 ... instead of 404 we get 500 right now.
+          # Remove this when that bug is fixed.
+          rescue Net::HTTPFatalError => e
+            if e.response.code == "500"
+              @could_not_get_chef_object = e
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}"
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_file.rb b/lib/chef/chef_fs/file_system/cookbook_file.rb
new file mode 100644
index 0000000..7868322
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbook_file.rb
@@ -0,0 +1,82 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_object'
+require 'chef/http/simple'
+require 'digest/md5'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class CookbookFile < BaseFSObject
+        def initialize(name, parent, file)
+          super(name, parent)
+          @file = file
+        end
+
+        attr_reader :file
+
+        def checksum
+          file[:checksum]
+        end
+
+        def read
+          begin
+            tmpfile = rest.streaming_request(file[:url])
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading #{file[:url]}: #{e}"
+          rescue Net::HTTPServerException => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "#{e.message} retrieving #{file[:url]}"
+          end
+
+          begin
+            tmpfile.open
+            tmpfile.read
+          ensure
+            tmpfile.close!
+          end
+        end
+
+        def rest
+          parent.rest
+        end
+
+        def compare_to(other)
+          other_value = nil
+          if other.respond_to?(:checksum)
+            other_checksum = other.checksum
+          else
+            begin
+              other_value = other.read
+            rescue Chef::ChefFS::FileSystem::NotFoundError
+              return [ false, nil, :none ]
+            end
+            other_checksum = calc_checksum(other_value)
+          end
+          [ checksum == other_checksum, nil, other_value ]
+        end
+
+        private
+
+        def calc_checksum(value)
+          Digest::MD5.hexdigest(value)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb b/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb
new file mode 100644
index 0000000..7056733
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/already_exists_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class CookbookFrozenError < AlreadyExistsError
+        def initialize(operation, entry, cause = nil)
+          super(operation, entry, cause)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_subdir.rb b/lib/chef/chef_fs/file_system/cookbook_subdir.rb
new file mode 100644
index 0000000..73c709e
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbook_subdir.rb
@@ -0,0 +1,54 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_dir'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class CookbookSubdir < BaseFSDir
+        def initialize(name, parent, ruby_only, recursive)
+          super(name, parent)
+          @children = []
+          @ruby_only = ruby_only
+          @recursive = recursive
+        end
+
+        attr_reader :versions
+        attr_reader :children
+
+        def add_child(child)
+          @children << child
+        end
+
+        def can_have_child?(name, is_dir)
+          if is_dir
+            return false if !@recursive
+          else
+            return false if @ruby_only && name !~ /\.rb$/
+          end
+          true
+        end
+
+        def rest
+          parent.rest
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
new file mode 100644
index 0000000..d6246f1
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
@@ -0,0 +1,41 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/chef_fs/file_system/acl_dir'
+require 'chef/chef_fs/file_system/acl_entry'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class CookbooksAclDir < AclDir
+        # If versioned_cookbooks is on, the list of cookbooks will have versions
+        # in them.  But all versions of a cookbook have the same acl, so even if
+        # we have cookbooks/apache2-1.0.0 and cookbooks/apache2-1.1.2, we will
+        # only have one acl: acls/cookbooks/apache2.json.  Thus, the list of
+        # children of acls/cookbooks is a unique list of cookbook *names*.
+        def children
+          if @children.nil?
+            names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
+            @children = names.uniq.map { |name| AclEntry.new(name, self, true) }
+          end
+          @children
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
new file mode 100644
index 0000000..a58bfdd
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
@@ -0,0 +1,164 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/cookbook_dir'
+require 'chef/chef_fs/file_system/operation_failed_error'
+require 'chef/chef_fs/file_system/cookbook_frozen_error'
+require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir'
+require 'chef/mixin/file_class'
+
+require 'tmpdir'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class CookbooksDir < RestListDir
+
+        include Chef::Mixin::FileClass
+
+        def initialize(parent)
+          super("cookbooks", parent)
+        end
+
+        def child(name)
+          if @children
+            result = self.children.select { |child| child.name == name }.first
+            if result
+              result
+            else
+              NonexistentFSObject.new(name, self)
+            end
+          else
+            CookbookDir.new(name, self)
+          end
+        end
+
+        def children
+          @children ||= begin
+            if Chef::Config[:versioned_cookbooks]
+              result = []
+              root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks|
+                cookbooks['versions'].each do |cookbook_version|
+                  result << CookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self, :exists => true)
+                end
+              end
+            else
+              result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, :exists => true) }
+            end
+            result.sort_by(&:name)
+          end
+        end
+
+        def create_child_from(other, options = {})
+          @children = nil
+          upload_cookbook_from(other, options)
+        end
+
+        def upload_cookbook_from(other, options = {})
+          Chef::Config[:versioned_cookbooks] ? upload_versioned_cookbook(other, options) : upload_unversioned_cookbook(other, options)
+        rescue Timeout::Error => e
+          raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}"
+        rescue Net::HTTPServerException => e
+          case e.response.code
+          when "409"
+            raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen"
+          else
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
+          end
+        rescue Chef::Exceptions::CookbookFrozen => e
+          raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen"
+        end
+
+        # Knife currently does not understand versioned cookbooks
+        # Cookbook Version uploader also requires a lot of refactoring
+        # to make this work. So instead, we make a temporary cookbook
+        # symlinking back to real cookbook, and upload the proxy.
+        def upload_versioned_cookbook(other, options)
+          cookbook_name = Chef::ChefFS::FileSystem::ChefRepositoryFileSystemCookbookDir.canonical_cookbook_name(other.name)
+
+          Dir.mktmpdir do |temp_cookbooks_path|
+            proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
+
+            # Make a symlink
+            file_class.symlink other.file_path, proxy_cookbook_path
+
+            # Instantiate a proxy loader using the temporary symlink
+            proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore)
+            proxy_loader.load_cookbooks
+
+            cookbook_to_upload = proxy_loader.cookbook_version
+            cookbook_to_upload.freeze_version if options[:freeze]
+
+            # Instantiate a new uploader based on the proxy loader
+            uploader = Chef::CookbookUploader.new(cookbook_to_upload, proxy_cookbook_path, :force => options[:force], :rest => root.chef_rest)
+
+            with_actual_cookbooks_dir(temp_cookbooks_path) do
+              upload_cookbook!(uploader)
+            end
+
+            #
+            # When the temporary directory is being deleted on
+            # windows, the contents of the symlink under that
+            # directory is also deleted. So explicitly remove
+            # the symlink without removing the original contents if we
+            # are running on windows
+            #
+            if Chef::Platform.windows?
+              Dir.rmdir proxy_cookbook_path
+            end
+          end
+        end
+
+        def upload_unversioned_cookbook(other, options)
+          cookbook_to_upload = other.chef_object
+          cookbook_to_upload.freeze_version if options[:freeze]
+          uploader = Chef::CookbookUploader.new(cookbook_to_upload, other.parent.file_path, :force => options[:force], :rest => root.chef_rest)
+
+          with_actual_cookbooks_dir(other.parent.file_path) do
+            upload_cookbook!(uploader)
+          end
+        end
+
+        # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet)
+        def with_actual_cookbooks_dir(actual_cookbook_path)
+          old_cookbook_path = Chef::Config.cookbook_path
+          Chef::Config.cookbook_path = actual_cookbook_path if !Chef::Config.cookbook_path
+
+          yield
+        ensure
+          Chef::Config.cookbook_path = old_cookbook_path
+        end
+
+        def upload_cookbook!(uploader, options = {})
+          if uploader.respond_to?(:upload_cookbook)
+            uploader.upload_cookbook
+          else
+            uploader.upload_cookbooks
+          end
+        end
+
+        def can_have_child?(name, is_dir)
+          return false if !is_dir
+          return false if Chef::Config[:versioned_cookbooks] && name !~ Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
+          return true
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/data_bag_dir.rb b/lib/chef/chef_fs/file_system/data_bag_dir.rb
new file mode 100644
index 0000000..212f76f
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/data_bag_dir.rb
@@ -0,0 +1,69 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/must_delete_recursively_error'
+require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class DataBagDir < RestListDir
+        def initialize(name, parent, exists = nil)
+          super(name, parent, nil, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
+          @exists = nil
+        end
+
+        def dir?
+          exists?
+        end
+
+        def read
+          # This will only be called if dir? is false, which means exists? is false.
+          raise Chef::ChefFS::FileSystem::NotFoundError.new(self)
+        end
+
+        def exists?
+          if @exists.nil?
+            @exists = parent.children.any? { |child| child.name == name }
+          end
+          @exists
+        end
+
+        def delete(recurse)
+          if !recurse
+            raise NotFoundError.new(self) if !exists?
+            raise MustDeleteRecursivelyError.new(self), "#{path_for_printing} must be deleted recursively"
+          end
+          begin
+            rest.delete(api_path)
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
+          rescue Net::HTTPServerException => e
+            if e.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "HTTP error deleting: #{e}"
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb
new file mode 100644
index 0000000..6d0685d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/data_bags_dir.rb
@@ -0,0 +1,73 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/data_bag_dir'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class DataBagsDir < RestListDir
+        def initialize(parent)
+          super("data_bags", parent, "data")
+        end
+
+        def child(name)
+          result = @children.select { |child| child.name == name }.first if @children
+          result || DataBagDir.new(name, self)
+        end
+
+        def children
+          begin
+            @children ||= root.get_json(api_path).keys.sort.map do |entry|
+              DataBagDir.new(entry, self, true)
+            end
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout getting children: #{e}"
+          rescue Net::HTTPServerException => e
+            if e.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error getting children: #{e}"
+            end
+          end
+        end
+
+        def can_have_child?(name, is_dir)
+          is_dir
+        end
+
+        def create_child(name, file_contents)
+          begin
+            rest.post(api_path, { 'name' => name })
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating child '#{name}': #{e}"
+          rescue Net::HTTPServerException => e
+            if e.response.code == "409"
+              raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e), "Cannot create #{name} under #{path}: already exists"
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "HTTP error creating child '#{name}': #{e}"
+            end
+          end
+          @children = nil
+          DataBagDir.new(name, self, true)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb b/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb
new file mode 100644
index 0000000..8ca3b91
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb
@@ -0,0 +1,36 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/operation_not_allowed_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class DefaultEnvironmentCannotBeModifiedError < OperationNotAllowedError
+        def initialize(operation, entry, cause = nil)
+          super(operation, entry, cause)
+        end
+
+        def reason
+          result = super
+          result + " (default environment cannot be modified)"
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/environments_dir.rb b/lib/chef/chef_fs/file_system/environments_dir.rb
new file mode 100644
index 0000000..559dd6a
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/environments_dir.rb
@@ -0,0 +1,60 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/default_environment_cannot_be_modified_error'
+require 'chef/chef_fs/data_handler/environment_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class EnvironmentsDir < RestListDir
+        def initialize(parent)
+          super("environments", parent, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new)
+        end
+
+        def _make_child_entry(name, exists = nil)
+          if name == '_default.json'
+            DefaultEnvironmentEntry.new(name, self, exists)
+          else
+            super
+          end
+        end
+
+        class DefaultEnvironmentEntry < RestListEntry
+          def initialize(name, parent, exists = nil)
+            super(name, parent)
+            @exists = exists
+          end
+
+          def delete(recurse)
+            raise NotFoundError.new(self) if !exists?
+            raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self), "#{path_for_printing} cannot be deleted."
+          end
+
+          def write(file_contents)
+            raise NotFoundError.new(self) if !exists?
+            raise DefaultEnvironmentCannotBeModifiedError.new(:write, self), "#{path_for_printing} cannot be updated."
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb
new file mode 100644
index 0000000..46d4eb5
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/file_system_entry.rb
@@ -0,0 +1,100 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_dir'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/must_delete_recursively_error'
+require 'chef/chef_fs/path_utils'
+require 'fileutils'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class FileSystemEntry < BaseFSDir
+        def initialize(name, parent, file_path = nil)
+          super(name, parent)
+          @file_path = file_path || "#{parent.file_path}/#{name}"
+        end
+
+        attr_reader :file_path
+
+        def path_for_printing
+          file_path
+        end
+
+        def children
+          begin
+            Dir.entries(file_path).sort.select { |entry| entry != '.' && entry != '..' }.map { |entry| make_child(entry) }
+          rescue Errno::ENOENT
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+          end
+        end
+
+        def create_child(child_name, file_contents=nil)
+          child = make_child(child_name)
+          if file_contents
+            child.write(file_contents)
+          else
+            begin
+              Dir.mkdir(child.file_path)
+            rescue Errno::EEXIST
+            end
+          end
+          @children = nil
+          child
+        end
+
+        def dir?
+          File.directory?(file_path)
+        end
+
+        def delete(recurse)
+          if dir?
+            if !recurse
+              raise MustDeleteRecursivelyError.new(self, $!)
+            end
+            FileUtils.rm_rf(file_path)
+          else
+            File.delete(file_path)
+          end
+        end
+
+        def read
+          begin
+            File.open(file_path, "rb") {|f| f.read}
+          rescue Errno::ENOENT
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+          end
+        end
+
+        def write(content)
+          File.open(file_path, 'wb') do |file|
+            file.write(content)
+          end
+        end
+
+        protected
+
+        def make_child(child_name)
+          FileSystemEntry.new(child_name, self)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/file_system_error.rb b/lib/chef/chef_fs/file_system/file_system_error.rb
new file mode 100644
index 0000000..80aff35
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/file_system_error.rb
@@ -0,0 +1,33 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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 ChefFS
+    module FileSystem
+      class FileSystemError < StandardError
+        def initialize(entry, cause = nil)
+          @entry = entry
+          @cause = cause
+        end
+
+        attr_reader :entry
+        attr_reader :cause
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/file_system_root_dir.rb b/lib/chef/chef_fs/file_system/file_system_root_dir.rb
new file mode 100644
index 0000000..afbf7b1
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/file_system_root_dir.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/file_system_entry'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class FileSystemRootDir < FileSystemEntry
+        def initialize(file_path)
+          super("", nil, file_path)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/memory_dir.rb b/lib/chef/chef_fs/file_system/memory_dir.rb
new file mode 100644
index 0000000..a7eda3c
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory_dir.rb
@@ -0,0 +1,52 @@
+require 'chef/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/nonexistent_fs_object'
+require 'chef/chef_fs/file_system/memory_file'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir
+        def initialize(name, parent)
+          super(name, parent)
+          @children = []
+        end
+
+        attr_reader :children
+
+        def child(name)
+          @children.select { |child| child.name == name }.first || Chef::ChefFS::FileSystem::NonexistentFSObject.new(name, self)
+        end
+
+        def add_child(child)
+          @children.push(child)
+        end
+
+        def can_have_child?(name, is_dir)
+          root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true
+        end
+
+        def add_file(path, value)
+          path_parts = path.split('/')
+          dir = add_dir(path_parts[0..-2].join('/'))
+          file = MemoryFile.new(path_parts[-1], dir, value)
+          dir.add_child(file)
+          file
+        end
+
+        def add_dir(path)
+          path_parts = path.split('/')
+          dir = self
+          path_parts.each do |path_part|
+            subdir = dir.child(path_part)
+            if !subdir.exists?
+              subdir = MemoryDir.new(path_part, dir)
+              dir.add_child(subdir)
+            end
+            dir = subdir
+          end
+          dir
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/memory_file.rb b/lib/chef/chef_fs/file_system/memory_file.rb
new file mode 100644
index 0000000..0c44e70
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory_file.rb
@@ -0,0 +1,17 @@
+require 'chef/chef_fs/file_system/base_fs_object'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject
+        def initialize(name, parent, value)
+          super(name, parent)
+          @value = value
+        end
+        def read
+          return @value
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/memory_root.rb b/lib/chef/chef_fs/file_system/memory_root.rb
new file mode 100644
index 0000000..4a83830
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory_root.rb
@@ -0,0 +1,21 @@
+require 'chef/chef_fs/file_system/memory_dir'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class MemoryRoot < MemoryDir
+        def initialize(pretty_name, cannot_be_in_regex = nil)
+          super('', nil)
+          @pretty_name = pretty_name
+          @cannot_be_in_regex = cannot_be_in_regex
+        end
+
+        attr_reader :cannot_be_in_regex
+
+        def path_for_printing
+          @pretty_name
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
new file mode 100644
index 0000000..06d4af7
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
@@ -0,0 +1,49 @@
+require 'chef/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/nonexistent_fs_object'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class MultiplexedDir < BaseFSDir
+        def initialize(*multiplexed_dirs)
+          @multiplexed_dirs = multiplexed_dirs.flatten
+          super(@multiplexed_dirs[0].name, @multiplexed_dirs[0].parent)
+        end
+
+        attr_reader :multiplexed_dirs
+
+        def write_dir
+          multiplexed_dirs[0]
+        end
+
+        def children
+          begin
+            result = []
+            seen = {}
+            # If multiple things have the same name, the first one wins.
+            multiplexed_dirs.each do |dir|
+              dir.children.each do |child|
+                if seen[child.name]
+                  Chef::Log.warn("Child with name '#{child.name}' found in multiple directories: #{seen[child.name].path_for_printing} and #{child.path_for_printing}")
+                else
+                  result << child
+                  seen[child.name] = child
+                end
+              end
+            end
+            result
+          end
+        end
+
+        def can_have_child?(name, is_dir)
+          write_dir.can_have_child?(name, is_dir)
+        end
+
+        def create_child(name, file_contents = nil)
+          @children = nil
+          write_dir.create_child(name, file_contents)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
new file mode 100644
index 0000000..bfa8ba2
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/file_system_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class MustDeleteRecursivelyError < FileSystemError
+        def initialize(entry, cause = nil)
+          super(entry, cause)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb
new file mode 100644
index 0000000..c3c4837
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/nodes_dir.rb
@@ -0,0 +1,55 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/data_handler/node_data_handler'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class NodesDir < RestListDir
+        def initialize(parent)
+          super("nodes", parent, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new)
+        end
+
+        # Identical to RestListDir.children, except supports environments
+        def children
+          begin
+            @children ||= root.get_json(env_api_path).keys.sort.map do |key|
+              _make_child_entry("#{key}.json", true)
+            end
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
+          rescue Net::HTTPServerException => e
+            if $!.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error retrieving children: #{e}"
+            end
+          end
+        end
+
+        def env_api_path
+          environment ? "environments/#{environment}/#{api_path}" : api_path
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb b/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
new file mode 100644
index 0000000..a587ab4
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
@@ -0,0 +1,36 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class NonexistentFSObject < BaseFSObject
+        def initialize(name, parent)
+          super
+        end
+
+        def exists?
+          false
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/not_found_error.rb b/lib/chef/chef_fs/file_system/not_found_error.rb
new file mode 100644
index 0000000..9eab3d6
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/not_found_error.rb
@@ -0,0 +1,31 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/file_system_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class NotFoundError < FileSystemError
+        def initialize(entry, cause = nil)
+          super(entry, cause)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/operation_failed_error.rb b/lib/chef/chef_fs/file_system/operation_failed_error.rb
new file mode 100644
index 0000000..28d170d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/operation_failed_error.rb
@@ -0,0 +1,42 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/file_system_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class OperationFailedError < FileSystemError
+        def initialize(operation, entry, cause = nil)
+          super(entry, cause)
+          @operation = operation
+        end
+
+        def message
+          if cause && cause.is_a?(Net::HTTPExceptions) && cause.response.code == "400"
+            "#{super} cause: #{cause.response.body}"
+          else
+            super
+          end
+        end
+
+        attr_reader :operation
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb b/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb
new file mode 100644
index 0000000..4b4f974
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb
@@ -0,0 +1,48 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/file_system_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class OperationNotAllowedError < FileSystemError
+        def initialize(operation, entry, cause = nil)
+          super(entry, cause)
+          @operation = operation
+        end
+
+        attr_reader :operation
+        attr_reader :entry
+
+        def reason
+          case operation
+          when :delete
+            "cannot be deleted"
+          when :write
+            "cannot be updated"
+          when :create_child
+            "cannot have a child created under it"
+          when :read
+            "cannot be read"
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb
new file mode 100644
index 0000000..b7ee51d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb
@@ -0,0 +1,115 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_dir'
+require 'chef/chef_fs/file_system/rest_list_entry'
+require 'chef/chef_fs/file_system/not_found_error'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class RestListDir < BaseFSDir
+        def initialize(name, parent, api_path = nil, data_handler = nil)
+          super(name, parent)
+          @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
+          @data_handler = data_handler
+        end
+
+        attr_reader :api_path
+        attr_reader :data_handler
+
+        def child(name)
+          result = @children.select { |child| child.name == name }.first if @children
+          result ||= can_have_child?(name, false) ?
+                     _make_child_entry(name) : NonexistentFSObject.new(name, self)
+        end
+
+        def can_have_child?(name, is_dir)
+          name =~ /\.json$/ && !is_dir
+        end
+
+        def children
+          begin
+            @children ||= root.get_json(api_path).keys.sort.map do |key|
+              _make_child_entry("#{key}.json", true)
+            end
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
+          rescue Net::HTTPServerException => e
+            if $!.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error retrieving children: #{e}"
+            end
+          end
+        end
+
+        def create_child(name, file_contents)
+          begin
+            object = JSON.parse(file_contents, :create_additions => false)
+          rescue JSON::ParserError => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Parse error reading JSON creating child '#{name}': #{e}"
+          end
+
+          result = _make_child_entry(name, true)
+
+          if data_handler
+            object = data_handler.normalize_for_post(object, result)
+            data_handler.verify_integrity(object, result) do |error|
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self), "Error creating '#{name}': #{error}"
+            end
+          end
+
+          begin
+            rest.post(api_path, object)
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating '#{name}': #{e}"
+          rescue Net::HTTPServerException => e
+            if e.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+            elsif $!.response.code == "409"
+              raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e), "Failure creating '#{name}': #{path}/#{name} already exists"
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Failure creating '#{name}': #{e.message}"
+            end
+          end
+
+          @children = nil
+
+          result
+        end
+
+        def org
+          parent.org
+        end
+
+        def environment
+          parent.environment
+        end
+
+        def rest
+          parent.rest
+        end
+
+        def _make_child_entry(name, exists = nil)
+          RestListEntry.new(name, self, exists)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/file_system/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb
new file mode 100644
index 0000000..0d5557d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/rest_list_entry.rb
@@ -0,0 +1,175 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system/base_fs_object'
+require 'chef/chef_fs/file_system/not_found_error'
+require 'chef/chef_fs/file_system/operation_failed_error'
+require 'chef/role'
+require 'chef/node'
+
+class Chef
+  module ChefFS
+    module FileSystem
+      class RestListEntry < BaseFSObject
+        def initialize(name, parent, exists = nil)
+          super(name, parent)
+          @exists = exists
+        end
+
+        def data_handler
+          parent.data_handler
+        end
+
+        def api_child_name
+          if name.length < 5 || name[-5,5] != ".json"
+            raise "Invalid name #{path}: must end in .json"
+          end
+          name[0,name.length-5]
+        end
+
+        def api_path
+          "#{parent.api_path}/#{api_child_name}"
+        end
+
+        def org
+          parent.org
+        end
+
+        def environment
+          parent.environment
+        end
+
+        def exists?
+          if @exists.nil?
+            begin
+              @exists = parent.children.any? { |child| child.name == name }
+            rescue Chef::ChefFS::FileSystem::NotFoundError
+              @exists = false
+            end
+          end
+          @exists
+        end
+
+        def delete(recurse)
+          begin
+            rest.delete(api_path)
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
+          rescue Net::HTTPServerException => e
+            if e.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
+            end
+          end
+        end
+
+        def read
+          Chef::JSONCompat.to_json_pretty(_read_hash)
+        end
+
+        def _read_hash
+          begin
+            # Minimize the value (get rid of defaults) so the results don't look terrible
+            minimize_value(root.get_json(api_path))
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}"
+          rescue Net::HTTPServerException => e
+            if $!.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}"
+            end
+          end
+        end
+
+        def chef_object
+          # REST will inflate the Chef object using json_class
+          data_handler.json_class.json_create(read)
+        end
+
+        def minimize_value(value)
+          data_handler.minimize(data_handler.normalize(value, self), self)
+        end
+
+        def compare_to(other)
+          # TODO this pair of reads can be parallelized
+
+          # Grab the other value
+          begin
+            other_value_json = other.read
+          rescue Chef::ChefFS::FileSystem::NotFoundError
+            return [ nil, nil, :none ]
+          end
+
+          # Grab this value
+          begin
+            value = _read_hash
+          rescue Chef::ChefFS::FileSystem::NotFoundError
+            return [ false, :none, other_value_json ]
+          end
+
+          # Minimize (and normalize) both values for easy and beautiful diffs
+          value = minimize_value(value)
+          value_json = Chef::JSONCompat.to_json_pretty(value)
+          begin
+            other_value = JSON.parse(other_value_json, :create_additions => false)
+          rescue JSON::ParserError => e
+            Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}")
+            return [ nil, value_json, other_value_json ]
+          end
+          other_value = minimize_value(other_value)
+          other_value_json = Chef::JSONCompat.to_json_pretty(other_value)
+
+          [ value == other_value, value_json, other_value_json ]
+        end
+
+        def rest
+          parent.rest
+        end
+
+        def write(file_contents)
+          begin
+            object = JSON.parse(file_contents, :create_additions => false)
+          rescue JSON::ParserError => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Parse error reading JSON: #{e}"
+          end
+
+          if data_handler
+            object = data_handler.normalize_for_put(object, self)
+            data_handler.verify_integrity(object, self) do |error|
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self), "#{error}"
+            end
+          end
+
+          begin
+            rest.put(api_path, object)
+          rescue Timeout::Error => e
+            raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}"
+          rescue Net::HTTPServerException => e
+            if e.response.code == "404"
+              raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+            else
+              raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
new file mode 100644
index 0000000..652c728
--- /dev/null
+++ b/lib/chef/chef_fs/knife.rb
@@ -0,0 +1,133 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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
+  module ChefFS
+    class Knife < Chef::Knife
+      # Workaround for CHEF-3932
+      def self.deps
+        super do
+          require 'chef/config'
+          require 'chef/chef_fs/parallelizer'
+          require 'chef/chef_fs/config'
+          require 'chef/chef_fs/file_pattern'
+          require 'chef/chef_fs/path_utils'
+          yield
+        end
+      end
+
+      def self.inherited(c)
+        super
+
+        # Ensure we always get to do our includes, whether subclass calls deps or not
+        c.deps do
+        end
+
+        c.options.merge!(options)
+      end
+
+      option :repo_mode,
+        :long => '--repo-mode MODE',
+        :description => "Specifies the local repository layout.  Values: static, everything, hosted_everything.  Default: everything/hosted_everything"
+
+      option :chef_repo_path,
+        :long => '--chef-repo-path PATH',
+        :description => 'Overrides the location of chef repo. Default is specified by chef_repo_path in the config'
+
+      option :concurrency,
+        :long => '--concurrency THREADS',
+        :description => 'Maximum number of simultaneous requests to send (default: 10)'
+
+      def configure_chef
+        super
+        Chef::Config[:repo_mode] = config[:repo_mode] if config[:repo_mode]
+        Chef::Config[:concurrency] = config[:concurrency].to_i if config[:concurrency]
+
+        # --chef-repo-path forcibly overrides all other paths
+        if config[:chef_repo_path]
+          Chef::Config[:chef_repo_path] = config[:chef_repo_path]
+          %w(acl client cookbook container data_bag environment group node role user).each do |variable_name|
+            Chef::Config.delete("#{variable_name}_path".to_sym)
+          end
+        end
+
+        @chef_fs_config = Chef::ChefFS::Config.new(Chef::Config, Dir.pwd, config)
+
+        Chef::ChefFS::Parallelizer.threads = (Chef::Config[:concurrency] || 10) - 1
+      end
+
+      def chef_fs
+        @chef_fs_config.chef_fs
+      end
+
+      def create_chef_fs
+        @chef_fs_config.create_chef_fs
+      end
+
+      def local_fs
+        @chef_fs_config.local_fs
+      end
+
+      def create_local_fs
+        @chef_fs_config.create_local_fs
+      end
+
+      def pattern_args
+        @pattern_args ||= pattern_args_from(name_args)
+      end
+
+      def pattern_args_from(args)
+        args.map { |arg| pattern_arg_from(arg) }
+      end
+
+      def pattern_arg_from(arg)
+        # TODO support absolute file paths and not just patterns?  Too much?
+        # Could be super useful in a world with multiple repo paths
+        if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg)
+          # Check if chef repo path is specified to give a better error message
+          ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
+          exit(1)
+        end
+        Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg)
+      end
+
+      def format_path(entry)
+        @chef_fs_config.format_path(entry)
+      end
+
+      def parallelize(inputs, options = {}, &block)
+        Chef::ChefFS::Parallelizer.parallelize(inputs, options, &block)
+      end
+
+      def discover_repo_dir(dir)
+        %w(.chef cookbooks data_bags environments roles).each do |subdir|
+          return dir if File.directory?(File.join(dir, subdir))
+        end
+        # If this isn't it, check the parent
+        parent = File.dirname(dir)
+        if parent && parent != dir
+          discover_repo_dir(parent)
+        else
+          nil
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/parallelizer.rb b/lib/chef/chef_fs/parallelizer.rb
new file mode 100644
index 0000000..84f3d4d
--- /dev/null
+++ b/lib/chef/chef_fs/parallelizer.rb
@@ -0,0 +1,129 @@
+class Chef
+  module ChefFS
+    class Parallelizer
+      @@parallelizer = nil
+      @@threads = 0
+
+      def self.threads=(value)
+        if @@threads != value
+          @@threads = value
+          @@parallelizer = nil
+        end
+      end
+
+      def self.parallelize(enumerator, options = {}, &block)
+        @@parallelizer ||= Parallelizer.new(@@threads)
+        @@parallelizer.parallelize(enumerator, options, &block)
+      end
+
+      def initialize(threads)
+        @tasks_mutex = Mutex.new
+        @tasks = []
+        @threads = []
+        1.upto(threads) do
+          @threads << Thread.new { worker_loop }
+        end
+      end
+
+      def parallelize(enumerator, options = {}, &block)
+        task = ParallelizedResults.new(enumerator, options, &block)
+        @tasks_mutex.synchronize do
+          @tasks << task
+        end
+        task
+      end
+
+      class ParallelizedResults
+        include Enumerable
+
+        def initialize(enumerator, options, &block)
+          @inputs = enumerator.to_a
+          @options = options
+          @block = block
+
+          @mutex = Mutex.new
+          @outputs = []
+          @status = []
+        end
+
+        def each
+          next_index = 0
+          while true
+            # Report any results that already exist
+            while @status.length > next_index && ([:finished, :exception].include?(@status[next_index]))
+              if @status[next_index] == :finished
+                if @options[:flatten]
+                  @outputs[next_index].each do |entry|
+                    yield entry
+                  end
+                else
+                  yield @outputs[next_index]
+                end
+              else
+                raise @outputs[next_index]
+              end
+              next_index = next_index + 1
+            end
+
+            # Pick up a result and process it, if there is one.  This ensures we
+            # move forward even if there are *zero* worker threads available.
+            if !process_input
+              # Exit if we're done.
+              if next_index >= @status.length
+                break
+              else
+                # Ruby 1.8 threading sucks.  Wait till we process more things.
+                sleep(0.05)
+              end
+            end
+          end
+        end
+
+        def process_input
+          # Grab the next one to process
+          index, input = @mutex.synchronize do
+            index = @status.length
+            if index >= @inputs.length
+              return nil
+            end
+            input = @inputs[index]
+            @status[index] = :started
+            [ index, input ]
+          end
+
+          begin
+            @outputs[index] = @block.call(input)
+            @status[index] = :finished
+          rescue Exception
+            @outputs[index] = $!
+            @status[index] = :exception
+          end
+          index
+        end
+      end
+
+      private
+
+      def worker_loop
+        while true
+          begin
+            task = @tasks[0]
+            if task
+              if !task.process_input
+                @tasks_mutex.synchronize do
+                  @tasks.delete(task)
+                end
+              end
+            else
+              # Ruby 1.8 threading sucks.  Wait a bit to see if another task comes in.
+              sleep(0.05)
+            end
+          rescue
+            puts "ERROR #{$!}"
+            puts $!.backtrace
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
new file mode 100644
index 0000000..9ef75ce
--- /dev/null
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -0,0 +1,95 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs'
+require 'pathname'
+
+class Chef
+  module ChefFS
+    class PathUtils
+
+      # If you are in 'source', this is what you would have to type to reach 'dest'
+      # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
+      # relative_to('/a/b', '/a/b') == '.'
+      def self.relative_to(dest, source)
+        # Skip past the common parts
+        source_parts = Chef::ChefFS::PathUtils.split(source)
+        dest_parts = Chef::ChefFS::PathUtils.split(dest)
+        i = 0
+        until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
+          i+=1
+        end
+        # dot-dot up from 'source' to the common ancestor, then
+        # descend to 'dest' from the common ancestor
+        result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
+        result == '' ? '.' : result
+      end
+
+      def self.join(*parts)
+        return "" if parts.length == 0
+        # Determine if it started with a slash
+        absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/
+        # Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away)
+        parts = parts.map { |part| part.gsub(/^\/|\/$/, "") }
+        # Don't join empty bits
+        result = parts.select { |part| part != "" }.join("/")
+        # Put the / back on
+        absolute ? "/#{result}" : result
+      end
+
+      def self.split(path)
+        path.split(Regexp.new(regexp_path_separator))
+      end
+
+      def self.regexp_path_separator
+        Chef::ChefFS::windows? ? '[\/\\\\]' : '/'
+      end
+
+      # Given a path which may only be partly real (i.e. /x/y/z when only /x exists,
+      # or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest
+      # part that actually exists.
+      #
+      # If /x is a symlink to /blarghle, and has no subdirectories, then:
+      # PathUtils.realest_path('/x/y/z') == '/blarghle/y/z'
+      # PathUtils.realest_path('/x/*/z') == '/blarghle/*/z'
+      # PathUtils.realest_path('/*/y/z') == '/*/y/z'
+      def self.realest_path(path)
+        path = Pathname.new(path)
+        begin
+          path.realpath.to_s
+        rescue Errno::ENOENT
+          dirname = path.dirname
+          if dirname
+            PathUtils.join(realest_path(dirname), path.basename.to_s)
+          else
+            path.to_s
+          end
+        end
+      end
+
+      def self.descendant_of?(path, ancestor)
+        path[0,ancestor.length] == ancestor &&
+          (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/)
+      end
+
+      def self.is_absolute?(path)
+        path =~ /^#{regexp_path_separator}/
+      end
+    end
+  end
+end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 55fc674..30b714c 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -24,7 +24,8 @@ require 'chef/mixin/path_sanity'
 require 'chef/log'
 require 'chef/rest'
 require 'chef/api_client'
-require 'chef/platform'
+require 'chef/api_client/registration'
+require 'chef/platform/query_helpers'
 require 'chef/node'
 require 'chef/role'
 require 'chef/file_cache'
@@ -35,7 +36,13 @@ require 'chef/cookbook/cookbook_collection'
 require 'chef/cookbook/file_vendor'
 require 'chef/cookbook/file_system_file_vendor'
 require 'chef/cookbook/remote_file_vendor'
+require 'chef/event_dispatch/dispatcher'
+require 'chef/formatters/base'
+require 'chef/formatters/doc'
+require 'chef/formatters/minimal'
 require 'chef/version'
+require 'chef/resource_reporter'
+require 'chef/run_lock'
 require 'ohai'
 require 'rbconfig'
 
@@ -96,18 +103,21 @@ class Chef
       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
     def run_completed_successfully
-      self.class.run_completed_successfully_notifications.each do |notification|
+      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
     def run_failed
-      self.class.run_failed_notifications.each do |notification|
+      failure_handlers = self.class.run_failed_notifications
+      failure_handlers.each do |notification|
         notification.call(run_status)
       end
     end
@@ -120,8 +130,8 @@ class Chef
     #--
     # TODO: timh/cw: 5-19-2010: json_attribs should be moved to RunContext?
     attr_reader :json_attribs
-
     attr_reader :run_status
+    attr_reader :events
 
     # Creates a new Chef::Client.
     def initialize(json_attribs=nil, args={})
@@ -130,55 +140,92 @@ class Chef
       @run_status = nil
       @runner = nil
       @ohai = Ohai::System.new
+
+      event_handlers = configure_formatters
+      event_handlers += Array(Chef::Config[:event_handlers])
+
+      @events = EventDispatch::Dispatcher.new(*event_handlers)
       @override_runlist = args.delete(:override_runlist)
       runlist_override_sanity_check!
     end
 
+    def configure_formatters
+      formatters_for_run.map do |formatter_name, output_path|
+        if output_path.nil?
+          Chef::Formatters.new(formatter_name, STDOUT, STDERR)
+        else
+          io = File.open(output_path, "a+")
+          io.sync = true
+          Chef::Formatters.new(formatter_name, io, io)
+        end
+      end
+    end
+
+    def formatters_for_run
+      if Chef::Config.formatters.empty?
+        [default_formatter]
+      else
+        Chef::Config.formatters
+      end
+    end
+
+    def default_formatter
+      if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
+        [:doc]
+      else
+        [:null]
+      end
+    end
+
     # Do a full run for this Chef::Client.  Calls:
+    # * do_run
     #
-    #  * 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
-    #
+    # This provides a wrapper around #do_run allowing the
+    # run to be optionally forked.
     # === Returns
-    # true:: Always returns true.
+    # boolean:: Return value from #do_run. Should always returns true.
     def run
-      run_context = nil
-
-      Chef::Log.info("*** Chef #{Chef::VERSION} ***")
-      enforce_path_sanity
-      run_ohai
-      register unless Chef::Config[:solo]
-      build_node
-
-      begin
-
-        run_status.start_clock
-        Chef::Log.info("Starting Chef Run for #{node.name}")
-        run_started
-
-        run_context = setup_run_context
-        converge(run_context)
-        save_updated_node
-
-        run_status.stop_clock
-        Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
-        run_completed_successfully
+      # win32-process gem exposes some form of :fork for Process
+      # class. So we are seperately ensuring that the platform we're
+      # running on is not windows before forking.
+      if(Chef::Config[:client_fork] && Process.respond_to?(:fork) && !Chef::Platform.windows?)
+        Chef::Log.info "Forking chef instance to converge..."
+        pid = fork do
+          [:INT, :TERM].each {|s| trap(s, "EXIT") }
+          client_solo = Chef::Config[:solo] ? "chef-solo" : "chef-client"
+          $0 = "#{client_solo} worker: ppid=#{Process.ppid};start=#{Time.new.strftime("%R:%S")};"
+          begin
+            Chef::Log.debug "Forked instance now converging"
+            do_run
+          rescue Exception => e
+            Chef::Log.error(e.to_s)
+            exit 1
+          else
+            exit 0
+          end
+        end
+        Chef::Log.debug "Fork successful. Waiting for new chef pid: #{pid}"
+        result = Process.waitpid2(pid)
+        handle_child_exit(result)
+        Chef::Log.debug "Forked instance successfully reaped (pid: #{pid})"
         true
-      rescue Exception => e
-        run_status.stop_clock
-        run_status.exception = e
-        run_failed
-        Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n  ")}")
-        raise
-      ensure
-        run_status = nil
+      else
+        do_run
       end
-      true
     end
 
+    def handle_child_exit(pid_and_status)
+      status = pid_and_status[1]
+      return true if status.success?
+      message = if status.signaled?
+        "Chef run process terminated by signal #{status.termsig} (#{Signal.list.invert[status.termsig]})"
+      else
+        "Chef run process exited unsuccessfully (exit code #{status.exitstatus})"
+      end
+      raise Exceptions::ChildConvergeError, message
+    end
+
+
 
     # Configures the Chef::Cookbook::FileVendor class to fetch file from the
     # server or disk as appropriate, creates the run context for this run, and
@@ -188,11 +235,15 @@ class Chef
     def setup_run_context
       if Chef::Config[:solo]
         Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, Chef::Config[:cookbook_path]) }
-        run_context = Chef::RunContext.new(node, Chef::CookbookCollection.new(Chef::CookbookLoader.new(Chef::Config[:cookbook_path])))
+        cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
+        cl.load_cookbooks
+        cookbook_collection = Chef::CookbookCollection.new(cl)
+        run_context = Chef::RunContext.new(node, cookbook_collection, @events)
       else
         Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, rest) }
         cookbook_hash = sync_cookbooks
-        run_context = Chef::RunContext.new(node, Chef::CookbookCollection.new(cookbook_hash))
+        cookbook_collection = Chef::CookbookCollection.new(cookbook_hash)
+        run_context = Chef::RunContext.new(node, cookbook_collection, @events)
       end
       run_status.run_context = run_context
 
@@ -206,7 +257,7 @@ class Chef
         Chef::Log.debug("Saving the current state of node #{node_name}")
         if(@original_runlist)
           @node.run_list(*@original_runlist)
-          @node[:runlist_override_history] = {Time.now.to_i => @override_runlist.inspect}
+          @node.automatic_attrs[:runlist_override_history] = {Time.now.to_i => @override_runlist.inspect}
         end
         @node.save
       end
@@ -225,23 +276,21 @@ class Chef
         raise Chef::Exceptions::CannotDetermineNodeName, msg
       end
 
+      # node names > 90 bytes only work with authentication protocol >= 1.1
+      # see discussion in config.rb.
+      if name.bytesize > 90
+        Chef::Config[:authentication_protocol_version] = "1.1"
+      end
+
       name
     end
 
-    # Builds a new node object for this client.  Starts with querying for the FQDN of the current
-    # host (unless it is supplied), then merges in the facts from Ohai.
+    # Applies environment, external JSON attributes, and override run list to
+    # the node, Then expands the run_list.
     #
     # === Returns
-    # node<Chef::Node>:: Returns the created node object, also stored in @node
+    # node<Chef::Node>:: The modified @node object. @node is modified in place.
     def build_node
-      Chef::Log.debug("Building node object for #{node_name}")
-
-      if Chef::Config[:solo]
-        @node = Chef::Node.build(node_name)
-      else
-        @node = Chef::Node.find_or_create(node_name)
-      end
-
       # Allow user to override the environment of a node by specifying
       # a config parameter.
       if Chef::Config[:environment] && !Chef::Config[:environment].chop.empty?
@@ -263,11 +312,7 @@ class Chef
         Chef::Log.warn "Overridden Run List: [#{@node.run_list}]"
       end
 
-      if Chef::Config[:solo]
-        @run_list_expansion = @node.expand!('disk')
-      else
-        @run_list_expansion = @node.expand!('server')
-      end
+      @run_list_expansion = expand_run_list
 
       # @run_list_expansion is a RunListExpansion.
       #
@@ -282,23 +327,68 @@ class Chef
       Chef::Log.info("Run List is [#{@node.run_list}]")
       Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
 
-      @run_status = Chef::RunStatus.new(@node)
+      @run_status = Chef::RunStatus.new(@node, @events)
+
+      @events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config)
 
       @node
     end
 
+    # In client-server operation, loads the node state from the server. In
+    # chef-solo operation, builds a new node object.
+    def load_node
+      @events.node_load_start(node_name, Chef::Config)
+      Chef::Log.debug("Building node object for #{node_name}")
+
+      if Chef::Config[:solo]
+        @node = Chef::Node.build(node_name)
+      else
+        @node = Chef::Node.find_or_create(node_name)
+      end
+    rescue Exception => e
+      # TODO: wrap this exception so useful error info can be given to the
+      # user.
+      @events.node_load_failed(node_name, e, Chef::Config)
+      raise
+    end
+
+    def expand_run_list
+      if Chef::Config[:solo]
+        @node.expand!('disk')
+      else
+        @node.expand!('server')
+      end
+    rescue Exception => e
+      # TODO: wrap/munge exception with useful error output.
+      @events.run_list_expand_failed(node, e)
+      raise
+    end
+
     #
     # === Returns
     # rest<Chef::REST>:: returns Chef::REST connection object
     def register(client_name=node_name, config=Chef::Config)
-      if File.exists?(config[:client_key])
+      if !config[:client_key]
+        @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)
         Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration")
       else
+        @events.registration_start(node_name, config)
         Chef::Log.info("Client key #{config[:client_key]} is not present - registering")
-        Chef::REST.new(config[:client_url], config[:validation_client_name], config[:validation_key]).register(client_name, config[:client_key])
+        Chef::ApiClient::Registration.new(node_name, config[:client_key]).run
+        @events.registration_completed
       end
       # We now have the client key, and should use it from now on.
-      self.rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key])
+      @rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key])
+      @resource_reporter = Chef::ResourceReporter.new(@rest)
+      @events.register(@resource_reporter)
+    rescue Exception => e
+      # TODO: munge exception so a semantic failure message can be given to the
+      # user
+      @events.registration_failed(node_name, e, config)
+      raise
     end
 
     # Sync_cookbooks eagerly loads all files except files and
@@ -310,9 +400,21 @@ class Chef
     # Hash:: The hash of cookbooks with download URLs as given by the server
     def sync_cookbooks
       Chef::Log.debug("Synchronizing cookbooks")
-      cookbook_hash = rest.post_rest("environments/#{@node.chef_environment}/cookbook_versions",
-                                     {:run_list => @expanded_run_list_with_versions})
-      Chef::CookbookVersion.sync_cookbooks(cookbook_hash)
+
+      begin
+        @events.cookbook_resolution_start(@expanded_run_list_with_versions)
+        cookbook_hash = rest.post_rest("environments/#{@node.chef_environment}/cookbook_versions",
+                                       {:run_list => @expanded_run_list_with_versions})
+      rescue Exception => e
+        # TODO: wrap/munge exception to provide helpful error output
+        @events.cookbook_resolution_failed(@expanded_run_list_with_versions, e)
+        raise
+      else
+        @events.cookbook_resolution_complete(cookbook_hash)
+      end
+
+      synchronizer = Chef::CookbookSynchronizer.new(cookbook_hash, @events)
+      synchronizer.sync_cookbooks
 
       # register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks
       Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks")
@@ -325,17 +427,112 @@ class Chef
     # === Returns
     # true:: Always returns true
     def converge(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
       true
+    rescue Exception
+      # TODO: should this be a separate #converge_failed(exception) method?
+      @events.converge_complete
+      raise
+    end
+
+    def do_windows_admin_check
+      if Chef::Platform.windows?
+        Chef::Log.debug("Checking for administrator privileges....")
+
+        if !has_admin_privileges?
+          message = "chef-client doesn't have administrator privileges on node #{node_name}."
+          if Chef::Config[:fatal_windows_admin_check]
+            Chef::Log.fatal(message)
+            Chef::Log.fatal("fatal_windows_admin_check is set to TRUE.")
+            raise Chef::Exceptions::WindowsNotAdmin, message
+          else
+            Chef::Log.warn("#{message} This might cause unexpected resource failures.")
+          end
+        else
+          Chef::Log.debug("chef-client has administrator privileges on node #{node_name}.")
+        end
+      end
     end
 
     private
 
+    # 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 do_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
+        run_context = nil
+        @events.run_start(Chef::VERSION)
+        Chef::Log.info("*** Chef #{Chef::VERSION} ***")
+        Chef::Log.info "Chef-client pid: #{Process.pid}"
+        enforce_path_sanity
+        run_ohai
+        @events.ohai_completed(node)
+        register unless Chef::Config[:solo]
+
+        load_node
+
+        build_node
+
+        run_status.start_clock
+        Chef::Log.info("Starting Chef Run for #{node.name}")
+        run_started
+
+        do_windows_admin_check
+
+        run_context = setup_run_context
+
+        converge(run_context)
+
+        save_updated_node
+
+        run_status.stop_clock
+        Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
+        run_completed_successfully
+        @events.run_completed(node)
+        true
+      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
+        @run_status = nil
+        run_context = nil
+        runlock.release
+        GC.start
+      end
+      true
+    end
+
     # Ensures runlist override contains RunListItem instances
     def runlist_override_sanity_check!
-      @override_runlist = @override_runlist.split(',') if @override_runlist.is_a?(String)
+      # Convert to array and remove whitespace
+      if @override_runlist.is_a?(String)
+        @override_runlist = @override_runlist.split(',').map { |e| e.strip }
+      end
       @override_runlist = [@override_runlist].flatten.compact
       @override_runlist.map! do |item|
         if(item.is_a?(Chef::RunList::RunListItem))
@@ -374,10 +571,18 @@ class Chef
       end
 
     end
+
+    def has_admin_privileges?
+      require 'chef/win32/security'
+
+      Chef::ReservedNames::Win32::Security.has_admin_privileges?
+    end
+
   end
 end
 
 # HACK cannot load this first, but it must be loaded.
 require 'chef/cookbook_loader'
 require 'chef/cookbook_version'
+require 'chef/cookbook/synchronizer'
 
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index dce9631..082c00c 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -2,7 +2,8 @@
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Christopher Brown (<cb at opscode.com>)
 # Author:: AJ Christensen (<aj at opscode.com>)
-# Author:: Mark Mzyk (mmzyk at opscode.com)
+# Author:: Mark Mzyk (<mmzyk at opscode.com>)
+# Author:: Kyle Goodwin (<kgoodwin at primerevenue.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -19,13 +20,23 @@
 # limitations under the License.
 
 require 'chef/log'
+require 'chef/exceptions'
 require 'mixlib/config'
+require 'chef/util/selinux'
+require 'pathname'
 
 class Chef
   class Config
 
     extend Mixlib::Config
 
+    # 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
@@ -47,44 +58,74 @@ class Chef
       configuration.inspect
     end
 
+    def self.on_windows?
+      RUBY_PLATFORM =~ /mswin|mingw|windows/
+    end
+
+    BACKSLASH = '\\'.freeze
+
+    def self.platform_path_separator
+      if on_windows?
+        File::ALT_SEPARATOR || BACKSLASH
+      else
+        File::SEPARATOR
+      end
+    end
+
+    def self.path_join(*args)
+      args = args.flatten
+      args.inject do |joined_path, component|
+        unless joined_path[-1,1] == platform_path_separator
+          joined_path += platform_path_separator
+        end
+        joined_path += component
+      end
+    end
+
     def self.platform_specific_path(path)
-      if RUBY_PLATFORM =~ /mswin|mingw|windows/
+      if on_windows?
         # turns /etc/chef/client.rb into C:/chef/client.rb
-        path = File.join(ENV['SYSTEMDRIVE'], path.split('/')[2..-1])
+        system_drive = ENV['SYSTEMDRIVE'] ? ENV['SYSTEMDRIVE'] : ""
+        path = File.join(system_drive, path.split('/')[2..-1])
         # ensure all forward slashes are backslashes
         path.gsub!(File::SEPARATOR, (File::ALT_SEPARATOR || '\\'))
       end
       path
     end
 
+    def self.add_formatter(name, file_path=nil)
+      formatters << [name, file_path]
+    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 local_mode
+        path_join(user_home, ".chef#{platform_path_separator}")
+      else
+        config_file && ::File.dirname(config_file)
+      end
+    end
+
+    # No config file (client.rb / knife.rb / etc.) will be loaded outside this path.
+    # Major use case is tests, where we don't want to load the user's config files.
+    configurable(:config_file_jail)
+
+    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
     #
-    config_attr_writer :chef_server_url do |url|
-      configure do |c|
-        [ :registration_url,
-          :template_url,
-          :remotefile_url,
-          :search_url,
-          :chef_server_url,
-          :role_url ].each do |u|
-            c[u] = url
-        end
-      end
-      url
-    end
+    configurable(:chef_server_url).writes_value { |url| url.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.
-    config_attr_writer :daemonize do |v|
-      configure do |c|
-        c[:daemonize] = v
-      end
-    end
+    configurable(:daemonize).writes_value { |v| v }
 
     # Override the config dispatch to set the value of log_location configuration option
     #
@@ -99,164 +140,348 @@ class Chef
         begin
           f = File.new(location.to_str, "a")
           f.sync = true
-        rescue Errno::ENOENT => error
-          raise Chef::Exceptions::ConfigurationError("Failed to open or create log file at #{location.to_str}")
+        rescue Errno::ENOENT
+          raise Chef::Exceptions::ConfigurationError, "Failed to open or create log file at #{location.to_str}"
         end
-          f
+        f
       end
     end
 
-    # Override the config dispatch to set the value of authorized_openid_providers when openid_providers (deprecated) is used
-    #
-    # === Parameters
-    # providers<Array>:: An array of openid providers that are authorized to login to the chef server
-    #
-    config_attr_writer :openid_providers do |providers|
-      configure { |c| c[:authorized_openid_providers] = providers }
-      providers
+    # 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
+        platform_specific_path("/var/chef")
+      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?(path_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)
+        path_join(chef_repo_path, child_path)
+      else
+        chef_repo_path.map { |path| path_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') }
+
     # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity
-    enforce_path_sanity(true)
+    default :enforce_path_sanity, true
 
-    # Used when OpenID authentication is enabled in the Web UI
-    authorized_openid_identifiers nil
-    authorized_openid_providers nil
-    openid_cstore_couchdb false
-    openid_cstore_path "/var/chef/openid/cstore"
+    # 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
-    client_registration_retries 5
+    default :client_registration_retries, 5
 
-    # Where the cookbooks are located. Meaning is somewhat context dependent between
-    # knife, chef-client, and chef-solo.
-    cookbook_path [ platform_specific_path("/var/chef/cookbooks"),
-                    platform_specific_path("/var/chef/site-cookbooks") ]
+    # An array of paths to search for knife exec scripts if they aren't in the current directory
+    default :script_path, []
 
-    # Where files are stored temporarily during uploads
-    sandbox_path "/var/chef/sandboxes"
+    # 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
+        "#{config_dir}local-mode-cache"
+      else
+        platform_specific_path("/var/chef")
+      end
+    end
 
     # Where cookbook files are stored on the server (by content checksum)
-    checksum_path "/var/chef/checksums"
-
-    # CouchDB database name to use
-    couchdb_database "chef"
-
-    couchdb_url "http://localhost:5984"
+    default(:checksum_path) { path_join(cache_path, "checksums") }
 
     # Where chef's cache files should be stored
-    file_cache_path platform_specific_path("/var/chef/cache")
+    default(:file_cache_path) { path_join(cache_path, "cache") }
 
     # Where backups of chef-managed files should go
-    file_backup_path platform_specific_path("/var/chef/backup")
+    default(:file_backup_path) { path_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) { path_join(file_cache_path, "chef-client-running.pid") }
 
     ## Daemonization Settings ##
     # What user should Chef run as?
-    user nil
-    # What group should the chef-server, -solr, -solr-indexer run as
-    group nil
-    umask 0022
-
-    http_retry_count 5
-    http_retry_delay 5
-    interval nil
-    json_attribs nil
-    log_level :info
-    log_location STDOUT
+    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 becase 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
+
+    # 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
+    default :log_location, STDOUT
     # toggle info level log items that can create a lot of output
-    verbose_logging true
-    node_name nil
-    node_path "/var/chef/node"
-
-    pid_file nil
-
-    chef_server_url   "http://localhost:4000"
-    registration_url  "http://localhost:4000"
-    template_url      "http://localhost:4000"
-    role_url          "http://localhost:4000"
-    remotefile_url    "http://localhost:4000"
-    search_url        "http://localhost:4000"
-
-    client_url "http://localhost:4042"
-
-    rest_timeout 300
-    run_command_stderr_timeout 120
-    run_command_stdout_timeout 120
-    solo  false
-    splay nil
+    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
+
+    config_context :chef_zero do
+      config_strict_mode true
+      default(:enabled) { Chef::Config.local_mode }
+      default :port, 8889
+    end
+    default :chef_server_url,   "https://localhost:443"
+
+    default :rest_timeout, 300
+    default :yum_timeout, 900
+    default :solo,  false
+    default :splay, nil
+    default :why_run, false
+    default :color, false
+    default :client_fork, true
+    default :enable_reporting, true
+    default :enable_reporting_url_fatals, false
 
     # Set these to enable SSL authentication / mutual-authentication
     # with the server
-    ssl_client_cert nil
-    ssl_client_key nil
-    ssl_verify_mode :verify_none
-    ssl_ca_path nil
-    ssl_ca_file nil
-
 
-    # Where should chef-solo look for role files?
-    role_path platform_specific_path("/var/chef/roles")
+    # 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. If set to
+    # :verify_peer, all HTTPS requests will be validated regardless of other
+    # SSL verification settings.
+    default :ssl_verify_mode, :verify_none
+
+    # 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.
+    default :verify_api_cert, false
+
+    # Path to the default CA bundle files.
+    default :ssl_ca_path, nil
+    default(:ssl_ca_file) do
+      if on_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
 
-    data_bag_path platform_specific_path("/var/chef/data_bags")
+    # 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) { config_dir && path_join(config_dir, "trusted_certs") }
 
     # Where should chef-solo download recipes from?
-    recipe_url nil
-
-    solr_url "http://localhost:8983/solr"
-    solr_jetty_path "/var/chef/solr-jetty"
-    solr_data_path "/var/chef/solr/data"
-    solr_home_path "/var/chef/solr"
-    solr_heap_size "256M"
-    solr_java_opts nil
-
-    # Parameters for connecting to RabbitMQ
-    amqp_host '0.0.0.0'
-    amqp_port '5672'
-    amqp_user 'chef'
-    amqp_pass 'testing'
-    amqp_vhost '/chef'
-    # Setting this to a UUID string also makes the queue durable
-    # (persist across rabbitmq restarts)
-    amqp_consumer_id "default"
-
-    client_key platform_specific_path("/etc/chef/client.pem")
-    validation_key platform_specific_path("/etc/chef/validation.pem")
-    validation_client_name "chef-validator"
-    web_ui_client_name "chef-webui"
-    web_ui_key "/etc/chef/webui.pem"
-    web_ui_admin_user_name  "admin"
-    web_ui_admin_default_password "p at ssw0rd1"
-
-    # Server Signing CA
+    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") }
+
+    # This secret is used to decrypt encrypted data bag items.
+    default(:encrypted_data_bag_secret) do
+      # We have to check for the existence of the default file before setting it
+      # since +Chef::Config[:encrypted_data_bag_secret]+ is read by older
+      # bootstrap templates to determine if the local secret should be uploaded to
+      # node being bootstrapped. This should be removed in Chef 12.
+      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:
+    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.
     #
-    # In truth, these don't even have to change
-    signing_ca_cert "/var/chef/ca/cert.pem"
-    signing_ca_key "/var/chef/ca/key.pem"
-    signing_ca_user nil
-    signing_ca_group nil
-    signing_ca_country "US"
-    signing_ca_state "Washington"
-    signing_ca_location "Seattle"
-    signing_ca_org "Chef User"
-    signing_ca_domain "opensource.opscode.com"
-    signing_ca_email "opensource-cert at opscode.com"
+    # 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"
+
+    # 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
-    report_handlers []
+    default :report_handlers, []
 
     # Exception Handlers
-    exception_handlers []
+    default :exception_handlers, []
 
     # Start handlers
-    start_handlers []
-
-    # Checksum Cache
-    # Uses Moneta on the back-end
-    cache_type "BasicFile"
-    cache_options({ :path => platform_specific_path("/var/chef/cache/checksums"), :skip_expires => true })
-
-    # Arbitrary knife configuration data
-    knife Hash.new
+    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:
+    default(:cache_options) { { :path => path_join(file_cache_path, "checksums") } }
+
+    # Set to false to silence Chef 11 deprecation warnings:
+    default :chef11_deprecation_warnings, true
+
+    # 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 :identity_file, nil
+      default :host_key_verify, nil
+      default :forward_agent, nil
+      default :sort_status_reverse, nil
+      default :hints, {}
+    end
 
     # Those lists of regular expressions define what chef considers a
     # valid user and group name
@@ -264,15 +489,49 @@ class Chef
       # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx
 
       principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+'
-      user_valid_regex [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
-      group_valid_regex [ /^(#{principal_valid_regex_part}\\)?#{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
     else
-      user_valid_regex [ /^([-a-zA-Z0-9_.]+)$/, /^\d+$/ ]
-      group_valid_regex [ /^([-a-zA-Z0-9_.\\ ]+)$/, /^\d+$/ ]
+      default :user_valid_regex, [ /^([-a-zA-Z0-9_.]+[\\@]?[-a-zA-Z0-9_.]+)$/, /^\d+$/ ]
+      default :group_valid_regex, [ /^([-a-zA-Z0-9_.\\@^ ]+)$/, /^\d+$/ ]
     end
 
     # returns a platform specific path to the user home dir
     windows_home_path = ENV['SYSTEMDRIVE'] + ENV['HOMEPATH'] if ENV['SYSTEMDRIVE'] && ENV['HOMEPATH']
-    user_home (ENV['HOME'] || windows_home_path || ENV['USERPROFILE'])
+    default :user_home, (ENV['HOME'] || windows_home_path || ENV['USERPROFILE'])
+
+    # 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
+
+    # If false file staging is will be done via tempfiles that are
+    # created under ENV['TMP'] otherwise tempfiles will be created in
+    # the directory that files are going to reside.
+    default :file_staging_uses_destdir, false
+
+    # 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/config_fetcher.rb b/lib/chef/config_fetcher.rb
new file mode 100644
index 0000000..26440c9
--- /dev/null
+++ b/lib/chef/config_fetcher.rb
@@ -0,0 +1,79 @@
+require 'chef/application'
+require 'chef/chef_fs/path_utils'
+require 'chef/http/simple'
+require 'chef/json_compat'
+
+class Chef
+  class ConfigFetcher
+
+    attr_reader :config_location
+    attr_reader :config_file_jail
+
+    def initialize(config_location, config_file_jail=nil)
+      @config_location = config_location
+      @config_file_jail = config_file_jail
+    end
+
+    def fetch_json
+      config_data = read_config
+      begin
+        Chef::JSONCompat.from_json(config_data)
+      rescue JSON::ParserError => error
+        Chef::Application.fatal!("Could not parse the provided JSON file (#{config_location}): " + error.message, 2)
+      end
+    end
+
+    def read_config
+      if remote_config?
+        fetch_remote_config
+      else
+        read_local_config
+      end
+    end
+
+    def fetch_remote_config
+      http.get("")
+    rescue SocketError, SystemCallError, Net::HTTPServerException => error
+      Chef::Application.fatal!("Cannot fetch config '#{config_location}': '#{error.class}: #{error.message}", 2)
+    end
+
+    def read_local_config
+      ::File.read(config_location)
+    rescue Errno::ENOENT => error
+      Chef::Application.fatal!("Cannot load configuration from #{config_location}", 2)
+    rescue Errno::EACCES => error
+      Chef::Application.fatal!("Permissions are incorrect on #{config_location}. Please chmod a+r #{config_location}", 2)
+    end
+
+    def config_missing?
+      return false if remote_config?
+
+      # Check if the config file exists, and check if it is underneath the config file jail
+      begin
+        real_config_file = Pathname.new(config_location).realpath.to_s
+      rescue Errno::ENOENT
+        return true
+      end
+
+      # If realpath succeeded, the file exists
+      return false if !config_file_jail
+
+      begin
+        real_jail = Pathname.new(config_file_jail).realpath.to_s
+      rescue Errno::ENOENT
+        Chef::Log.warn("Config file jail #{config_file_jail} does not exist: will not load any config file.")
+        return true
+      end
+
+      !Chef::ChefFS::PathUtils.descendant_of?(real_config_file, real_jail)
+    end
+
+    def http
+      Chef::HTTP::Simple.new(config_location)
+    end
+
+    def remote_config?
+      !!(config_location =~ %r{^(http|https)://})
+    end
+  end
+end
diff --git a/lib/chef/cookbook/chefignore.rb b/lib/chef/cookbook/chefignore.rb
index e9d5463..17c0003 100644
--- a/lib/chef/cookbook/chefignore.rb
+++ b/lib/chef/cookbook/chefignore.rb
@@ -20,7 +20,7 @@ class Chef
   class Cookbook
     class Chefignore
 
-      COMMENTS_AND_WHITESPACE = /^\w*(?:#.*)?$/
+      COMMENTS_AND_WHITESPACE = /^\s*(?:#.*)?$/
 
       attr_reader :ignores
 
@@ -43,7 +43,8 @@ class Chef
 
       def parse_ignore_file
         ignore_globs = []
-        if File.exist?(@ignore_file) && File.readable?(@ignore_file)
+        if File.exist?(@ignore_file) && File.readable?(@ignore_file) &&
+          (File.file?(@ignore_file) || File.symlink?(@ignore_file))
           File.foreach(@ignore_file) do |line|
             ignore_globs << line.strip unless line =~ COMMENTS_AND_WHITESPACE
           end
diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb
index 48de17c..e98da77 100644
--- a/lib/chef/cookbook/cookbook_version_loader.rb
+++ b/lib/chef/cookbook/cookbook_version_loader.rb
@@ -104,9 +104,7 @@ class Chef
       end
 
       def empty?
-        cookbook_settings.inject(true) do |all_empty, files|
-          all_empty && files.last.empty?
-        end
+        @cookbook_settings.values.all? { |files_hash| files_hash.empty? }
       end
 
       def merge!(other_cookbook_loader)
diff --git a/lib/chef/cookbook/file_vendor.rb b/lib/chef/cookbook/file_vendor.rb
index 38eab18..406f23c 100644
--- a/lib/chef/cookbook/file_vendor.rb
+++ b/lib/chef/cookbook/file_vendor.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -27,14 +27,14 @@ class Chef
       def self.on_create(&block)
         @instance_creator = block
       end
-      
+
       # Factory method that creates the appropriate kind of
       # Cookbook::FileVendor to serve the contents of the manifest
       def self.create_from_manifest(manifest)
         raise "Must call Chef::Cookbook::FileVendor.on_create before calling create_from_manifest factory" unless defined?(@instance_creator)
         @instance_creator.call(manifest)
       end
-      
+
       # Gets the on-disk location for the given cookbook file.
       #
       # Subclasses are responsible for determining exactly how the
@@ -42,7 +42,7 @@ class Chef
       def get_filename(filename)
         raise NotImplemented, "Subclasses must implement this method"
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 8398de4..b9b32c8 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -21,7 +21,6 @@
 require 'chef/mash'
 require 'chef/mixin/from_file'
 require 'chef/mixin/params_validate'
-require 'chef/mixin/check_helper'
 require 'chef/log'
 require 'chef/version_class'
 require 'chef/version_constraint'
@@ -64,7 +63,6 @@ class Chef
                              :provides    => PROVIDING,
                              :replaces    => REPLACING }
 
-      include Chef::Mixin::CheckHelper
       include Chef::Mixin::ParamsValidate
       include Chef::Mixin::FromFile
 
@@ -112,8 +110,8 @@ class Chef
         @version = Version.new "0.0.0"
         if cookbook
           @recipes = cookbook.fully_qualified_recipe_names.inject({}) do |r, e|
-            e = self.name if e =~ /::default$/
-            r[e] = ""
+            e = self.name.to_s if e =~ /::default$/
+            r[e] ||= ""
             self.provides e
             r
           end
diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb
index 0ff0ad3..49de62c 100644
--- a/lib/chef/cookbook/remote_file_vendor.rb
+++ b/lib/chef/cookbook/remote_file_vendor.rb
@@ -76,11 +76,7 @@ class Chef
       end
 
       def validate_cached_copy(cache_filename)
-        valid_cache_entries[cache_filename] = true
-      end
-
-      def valid_cache_entries
-        Chef::CookbookVersion.valid_cache_entries
+        CookbookCacheCleaner.instance.mark_file_as_valid(cache_filename)
       end
 
     end
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
new file mode 100644
index 0000000..4522323
--- /dev/null
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -0,0 +1,218 @@
+require 'chef/client'
+require 'singleton'
+
+class Chef
+
+  # Keep track of the filenames that we use in both eager cookbook
+  # downloading (during sync_cookbooks) and lazy (during the run
+  # itself, through FileVendor). After the run is over, clean up the
+  # cache.
+  class CookbookCacheCleaner
+
+    # Setup a notification to clear the valid_cache_entries when a Chef client
+    # run starts
+    Chef::Client.when_run_starts do |run_status|
+      instance.reset!
+    end
+
+    # Register a notification to cleanup unused files from cookbooks
+    Chef::Client.when_run_completes_successfully do |run_status|
+      instance.cleanup_file_cache
+    end
+
+    include Singleton
+
+    def initialize
+      reset!
+    end
+
+    def reset!
+      @valid_cache_entries = {}
+    end
+
+    def mark_file_as_valid(cache_path)
+      @valid_cache_entries[cache_path] = true
+    end
+
+    def cache
+      Chef::FileCache
+    end
+
+    def cleanup_file_cache
+      unless Chef::Config[:solo]
+        # Delete each file in the cache that we didn't encounter in the
+        # manifest.
+        cache.find(File.join(%w{cookbooks ** *})).each do |cache_filename|
+          unless @valid_cache_entries[cache_filename]
+            Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer needed by chef-client.")
+            cache.delete(cache_filename)
+          end
+        end
+      end
+    end
+
+  end
+
+  # Synchronizes the locally cached copies of cookbooks with the files on the
+  # server.
+  class CookbookSynchronizer
+    def initialize(cookbooks_by_name, events)
+      @eager_segments = Chef::CookbookVersion::COOKBOOK_SEGMENTS.dup
+      unless Chef::Config[:no_lazy_load]
+        @eager_segments.delete(:files)
+        @eager_segments.delete(:templates)
+      end
+      @eager_segments.freeze
+
+      @cookbooks_by_name, @events = cookbooks_by_name, events
+    end
+
+    def cache
+      Chef::FileCache
+    end
+
+    def cookbook_names
+      @cookbooks_by_name.keys
+    end
+
+    def cookbooks
+      @cookbooks_by_name.values
+    end
+
+    def cookbook_count
+      @cookbooks_by_name.size
+    end
+
+    def have_cookbook?(cookbook_name)
+      @cookbooks_by_name.key?(cookbook_name)
+    end
+
+    # Synchronizes all the cookbooks from the chef-server.
+    #)
+    # === Returns
+    # true:: Always returns true
+    def sync_cookbooks
+      Chef::Log.info("Loading cookbooks [#{cookbook_names.sort.join(', ')}]")
+      Chef::Log.debug("Cookbooks detail: #{cookbooks.inspect}")
+
+      clear_obsoleted_cookbooks
+
+      @events.cookbook_sync_start(cookbook_count)
+
+      # Synchronize each of the node's cookbooks, and add to the
+      # valid_cache_entries hash.
+      cookbooks.each do |cookbook|
+        sync_cookbook(cookbook)
+      end
+
+    rescue Exception => e
+      @events.cookbook_sync_failed(cookbooks, e)
+      raise
+    else
+      @events.cookbook_sync_complete
+      true
+    end
+
+    # Iterates over cached cookbooks' files, removing files belonging to
+    # cookbooks that don't appear in +cookbook_hash+
+    def clear_obsoleted_cookbooks
+      @events.cookbook_clean_start
+      # Remove all cookbooks no longer relevant to this node
+      cache.find(File.join(%w{cookbooks ** *})).each do |cache_file|
+        cache_file =~ /^cookbooks\/([^\/]+)\//
+        unless have_cookbook?($1)
+          Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
+          cache.delete(cache_file)
+          @events.removed_cookbook_file(cache_file)
+        end
+      end
+      @events.cookbook_clean_complete
+    end
+
+    # Sync the eagerly loaded files contained by +cookbook+
+    #
+    # === Arguments
+    # cookbook<Chef::Cookbook>:: The cookbook to update
+    # valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
+    # were referred to by this cookbook
+    def sync_cookbook(cookbook)
+      Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")
+
+      # files and templates are lazily loaded, and will be done later.
+
+      @eager_segments.each do |segment|
+        segment_filenames = Array.new
+        cookbook.manifest[segment].each do |manifest_record|
+
+          cache_filename = sync_file_in_cookbook(cookbook, manifest_record)
+          # make the segment filenames a full path.
+          full_path_cache_filename = cache.load(cache_filename, false)
+          segment_filenames << full_path_cache_filename
+        end
+
+        # replace segment filenames with a full-path one.
+        if segment.to_sym == :recipes
+          cookbook.recipe_filenames = segment_filenames
+        elsif segment.to_sym == :attributes
+          cookbook.attribute_filenames = segment_filenames
+        else
+          cookbook.segment_filenames(segment).replace(segment_filenames)
+        end
+      end
+      @events.synchronized_cookbook(cookbook.name)
+    end
+
+    # Sync an individual file if needed. If there is an up to date copy
+    # locally, nothing is done.
+    #
+    # === Arguments
+    # file_manifest::: A Hash of the form {"path" => 'relative/path', "url" => "location to fetch the file"}
+    # === Returns
+    # Path to the cached file as a String
+    def sync_file_in_cookbook(cookbook, file_manifest)
+      cache_filename = File.join("cookbooks", cookbook.name, file_manifest['path'])
+      mark_cached_file_valid(cache_filename)
+
+      # If the checksums are different between on-disk (current) and on-server
+      # (remote, per manifest), do the update. This will also execute if there
+      # is no current checksum.
+      if !cached_copy_up_to_date?(cache_filename, file_manifest['checksum'])
+        download_file(file_manifest['url'], cache_filename)
+        @events.updated_cookbook_file(cookbook.name, cache_filename)
+      else
+        Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.")
+      end
+
+      cache_filename
+    end
+
+    def cached_copy_up_to_date?(local_path, expected_checksum)
+      if cache.has_key?(local_path)
+        current_checksum = CookbookVersion.checksum_cookbook_file(cache.load(local_path, false))
+        expected_checksum == current_checksum
+      else
+        false
+      end
+    end
+
+    # Unconditionally download the file from the given URL. File will be
+    # downloaded to the path +destination+ which is relative to the Chef file
+    # cache root.
+    def download_file(url, destination)
+      raw_file = server_api.get_rest(url, true)
+
+      Chef::Log.info("Storing updated #{destination} in the cache.")
+      cache.move_to(raw_file.path, destination)
+    end
+
+    # Marks the given file as valid (non-stale).
+    def mark_cached_file_valid(cache_filename)
+      CookbookCacheCleaner.instance.mark_file_as_valid(cache_filename)
+    end
+
+    def server_api
+      Chef::REST.new(Chef::Config[:chef_server_url])
+    end
+
+  end
+end
diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb
index bf7c45e..59888e2 100644
--- a/lib/chef/cookbook/syntax_check.rb
+++ b/lib/chef/cookbook/syntax_check.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -16,8 +16,9 @@
 # limitations under the License.
 #
 
-require 'chef/checksum_cache'
+require 'pathname'
 require 'chef/mixin/shell_out'
+require 'chef/mixin/checksum'
 
 class Chef
   class Cookbook
@@ -25,10 +26,55 @@ class Chef
     # Encapsulates the process of validating the ruby syntax of files in Chef
     # cookbooks.
     class SyntaxCheck
+
+      # == Chef::Cookbook::SyntaxCheck::PersistentSet
+      # Implements set behavior with disk-based persistence. Objects in the set
+      # are expected to be strings containing only characters that are valid in
+      # filenames.
+      #
+      # This class is used to track which files have been syntax checked so
+      # that known good files are not rechecked.
+      class PersistentSet
+
+        attr_reader :cache_path
+
+        # Create a new PersistentSet. Values in the set are persisted by
+        # creating a file in the +cache_path+ directory.
+        def initialize(cache_path=Chef::Config[:syntax_check_cache_path])
+          @cache_path = cache_path
+          @cache_path_created = false
+        end
+
+        # Adds +value+ to the set's collection.
+        def add(value)
+          ensure_cache_path_created
+          FileUtils.touch(File.join(cache_path, value))
+        end
+
+        # Returns true if the set includes +value+
+        def include?(value)
+          File.exist?(File.join(cache_path, value))
+        end
+
+        private
+
+        def ensure_cache_path_created
+          return true if @cache_path_created
+          FileUtils.mkdir_p(cache_path)
+          @cache_path_created = true
+        end
+
+      end
+
       include Chef::Mixin::ShellOut
+      include Chef::Mixin::Checksum
 
       attr_reader :cookbook_path
 
+      # A PersistentSet object that tracks which files have already been
+      # validated.
+      attr_reader :validated_files
+
       # Creates a new SyntaxCheck given the +cookbook_name+ and a +cookbook_path+.
       # If no +cookbook_path+ is given, +Chef::Config.cookbook_path+ is used.
       def self.for_cookbook(cookbook_name, cookbook_path=nil)
@@ -44,14 +90,25 @@ class Chef
       # cookbook_path::: the (on disk) path to the cookbook
       def initialize(cookbook_path)
         @cookbook_path = cookbook_path
+        @validated_files = PersistentSet.new
       end
 
-      def cache
-        Chef::ChecksumCache.instance
+      def chefignore
+        @chefignore ||= Chefignore.new(File.dirname(cookbook_path))
+      end
+
+      def remove_ignored_files(file_list)
+        return file_list unless chefignore.ignores.length > 0
+        file_list.reject do |full_path|
+          cookbook_pn = Pathname.new cookbook_path
+          full_pn = Pathname.new full_path
+          relative_pn = full_pn.relative_path_from cookbook_pn
+          chefignore.ignored? relative_pn.to_s
+        end
       end
 
       def ruby_files
-        Dir[File.join(cookbook_path, '**', '*.rb')]
+        remove_ignored_files Dir[File.join(cookbook_path, '**', '*.rb')]
       end
 
       def untested_ruby_files
@@ -66,11 +123,11 @@ class Chef
       end
 
       def template_files
-        Dir[File.join(cookbook_path, '**', '*.erb')]
+        remove_ignored_files Dir[File.join(cookbook_path, '**', '*.erb')]
       end
 
       def untested_template_files
-        template_files.reject do |file| 
+        template_files.reject do |file|
           if validated?(file)
             Chef::Log.debug("Template #{file} is unchanged, skipping syntax check")
             true
@@ -81,16 +138,11 @@ class Chef
       end
 
       def validated?(file)
-        !!cache.lookup_checksum(cache_key(file), File.stat(file))
+        validated_files.include?(checksum(file))
       end
 
       def validated(file)
-        cache.generate_checksum(cache_key(file), file, File.stat(file))
-      end
-
-      def cache_key(file)
-        @cache_keys ||= {}
-        @cache_keys[file] ||= cache.generate_key(file, "chef-test")
+        validated_files.add(checksum(file))
       end
 
       def validate_ruby_files
@@ -118,7 +170,7 @@ class Chef
         result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
         false
       end
-      
+
       def validate_ruby_file(ruby_file)
         Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
         result = shell_out("ruby -c #{ruby_file}")
@@ -130,7 +182,7 @@ class Chef
         result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
         false
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb
index 046561b..27cf978 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -25,31 +25,36 @@ require 'chef/cookbook_version'
 require 'chef/cookbook/chefignore'
 require 'chef/cookbook/metadata'
 
+#
+# CookbookLoader class loads the cookbooks lazily as read
+#
 class Chef
   class CookbookLoader
 
-    attr_accessor :metadata
     attr_reader :cookbooks_by_name
     attr_reader :merged_cookbooks
     attr_reader :cookbook_paths
+    attr_reader :metadata
 
     include Enumerable
 
     def initialize(*repo_paths)
-      @repo_paths = repo_paths.flatten
-      raise ArgumentError, "You must specify at least one cookbook repo path" if @repo_paths.empty?
+      repo_paths = repo_paths.flatten
+      raise ArgumentError, "You must specify at least one cookbook repo path" if repo_paths.empty?
       @cookbooks_by_name = Mash.new
       @loaded_cookbooks = {}
       @metadata = Mash.new
       @cookbooks_paths = Hash.new {|h,k| h[k] = []} # for deprecation warnings
+      @chefignores = {}
+      @repo_paths = repo_paths.map do |repo_path|
+        repo_path = File.expand_path(repo_path)
+      end
 
       # Used to track which cookbooks appear in multiple places in the cookbook repos
       # and are merged in to a single cookbook by file shadowing. This behavior is
       # deprecated, so users of this class may issue warnings to the user by checking
       # this variable
       @merged_cookbooks = []
-
-      load_cookbooks
     end
 
     def merged_cookbook_paths # for deprecation warnings
@@ -59,35 +64,43 @@ class Chef
     end
 
     def load_cookbooks
-      cookbook_settings = Hash.new
       @repo_paths.each do |repo_path|
-        repo_path = File.expand_path(repo_path)
-        chefignore = Cookbook::Chefignore.new(repo_path)
         Dir[File.join(repo_path, "*")].each do |cookbook_path|
-          next unless File.directory?(cookbook_path)
-          loader = Cookbook::CookbookVersionLoader.new(cookbook_path, chefignore)
-          loader.load_cookbooks
-          next if loader.empty?
-          @cookbooks_paths[loader.cookbook_name] << cookbook_path # for deprecation warnings
-          if @loaded_cookbooks.key?(loader.cookbook_name)
-            @merged_cookbooks << loader.cookbook_name # for deprecation warnings
-            @loaded_cookbooks[loader.cookbook_name].merge!(loader)
-          else
-            @loaded_cookbooks[loader.cookbook_name] = loader
-          end
+          load_cookbook(File.basename(cookbook_path), [repo_path])
+        end
+      end
+      @cookbooks_by_name
+    end
+
+    def load_cookbook(cookbook_name, repo_paths=nil)
+      repo_paths ||= @repo_paths
+      repo_paths.each do |repo_path|
+        @chefignores[repo_path] ||= Cookbook::Chefignore.new(repo_path)
+        cookbook_path = File.join(repo_path, cookbook_name.to_s)
+        next unless File.directory?(cookbook_path) and Dir[File.join(repo_path, "*")].include?(cookbook_path)
+        loader = Cookbook::CookbookVersionLoader.new(cookbook_path, @chefignores[repo_path])
+        loader.load_cookbooks
+        next if loader.empty?
+        cookbook_name = loader.cookbook_name
+        @cookbooks_paths[cookbook_name] << cookbook_path # for deprecation warnings
+        if @loaded_cookbooks.key?(cookbook_name)
+          @merged_cookbooks << cookbook_name # for deprecation warnings
+          @loaded_cookbooks[cookbook_name].merge!(loader)
+        else
+          @loaded_cookbooks[cookbook_name] = loader
         end
       end
 
-      @loaded_cookbooks.each do |cookbook, loader|
-        cookbook_version = loader.cookbook_version
-        @cookbooks_by_name[cookbook] = cookbook_version
-        @metadata[cookbook] = cookbook_version.metadata
+      if @loaded_cookbooks.has_key?(cookbook_name)
+        cookbook_version = @loaded_cookbooks[cookbook_name].cookbook_version
+        @cookbooks_by_name[cookbook_name] = cookbook_version
+        @metadata[cookbook_name] = cookbook_version.metadata
       end
-      @cookbooks_by_name
+      @cookbooks_by_name[cookbook_name]
     end
 
     def [](cookbook)
-      if @cookbooks_by_name.has_key?(cookbook.to_sym)
+      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)"
@@ -97,7 +110,7 @@ class Chef
     alias :fetch :[]
 
     def has_key?(cookbook_name)
-      @cookbooks_by_name.has_key?(cookbook_name)
+      not self[cookbook_name.to_sym].nil?
     end
     alias :cookbook_exists? :has_key?
     alias :key? :has_key?
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
index abb5499..92193fe 100644
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ b/lib/chef/cookbook_site_streaming_uploader.rb
@@ -208,8 +208,11 @@ class Chef
         @parts.inject(0) {|size, part| size + part.size}
       end
 
-      def read(how_much)
-        return nil if @part_no >= @parts.size
+      def read(how_much, dst_buf = nil)
+        if @part_no >= @parts.size
+          dst_buf.replace('') if dst_buf
+          return dst_buf
+        end
 
         how_much_current_part = @parts[@part_no].size - @part_offset
 
@@ -228,15 +231,16 @@ class Chef
           @part_no += 1
           @part_offset = 0
           next_part = read(how_much_next_part)
-          current_part + if next_part
+          result = current_part + if next_part
                            next_part
                          else
                            ''
                          end
         else
           @part_offset += how_much_current_part
-          current_part
+          result = current_part
         end
+        dst_buf ? dst_buf.replace(result || '') : result
       end
     end
 
diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb
index 7f9fc39..3ead26e 100644
--- a/lib/chef/cookbook_uploader.rb
+++ b/lib/chef/cookbook_uploader.rb
@@ -3,11 +3,11 @@ require 'set'
 require 'rest_client'
 require 'chef/exceptions'
 require 'chef/knife/cookbook_metadata'
-require 'chef/checksum_cache'
-require 'chef/sandbox'
+require 'chef/digester'
 require 'chef/cookbook_version'
 require 'chef/cookbook/syntax_check'
 require 'chef/cookbook/file_system_file_vendor'
+require 'chef/sandbox'
 
 class Chef
   class CookbookUploader
@@ -29,14 +29,15 @@ class Chef
       end
     end
 
-    attr_reader :cookbook
+    attr_reader :cookbooks
     attr_reader :path
     attr_reader :opts
     attr_reader :rest
 
     # Creates a new CookbookUploader.
     # ===Arguments:
-    # * cookbook::: A Chef::CookbookVersion describing the cookbook to be uploaded
+    # * cookbooks::: A Chef::CookbookVersion or array of them describing the
+    #                cookbook(s) to be uploaded
     # * path::: A String or Array of Strings representing the base paths to the
     #           cookbook repositories.
     # * opts::: (optional) An options Hash
@@ -48,19 +49,24 @@ class Chef
     # * :rest   A Chef::REST object that you have configured the way you like it.
     #           If you don't provide this, one will be created using the values
     #           in Chef::Config.
-    def initialize(cookbook, path, opts={})
-      @cookbook, @path, @opts = cookbook, path, opts
+    def initialize(cookbooks, path, opts={})
+      @path, @opts = path, opts
+      @cookbooks = Array(cookbooks)
       @rest = opts[:rest] || Chef::REST.new(Chef::Config[:chef_server_url])
     end
 
-    def upload_cookbook
+    def upload_cookbooks
       Thread.abort_on_exception = true
-      Chef::Log.info("Saving #{cookbook.name}")
 
       # Syntax Check
-      validate_cookbook
+      validate_cookbooks
       # generate checksums of cookbook files and create a sandbox
-      checksum_files = cookbook.checksums
+      checksum_files = {}
+      cookbooks.each do |cb|
+        Chef::Log.info("Saving #{cb.name}")
+        checksum_files.merge!(cb.checksums)
+      end
+
       checksums = checksum_files.inject({}){|memo,elt| memo[elt.first]=nil ; memo}
       new_sandbox = rest.post_rest("sandboxes", { :checksums => checksums })
 
@@ -102,8 +108,19 @@ class Chef
       end
 
       # files are uploaded, so save the manifest
-      save_url = opts[:force] ? cookbook.force_save_url : cookbook.save_url
-      rest.put_rest(save_url, cookbook)
+      cookbooks.each do |cb|
+        save_url = opts[:force] ? cb.force_save_url : cb.save_url
+        begin
+          rest.put_rest(save_url, cb)
+        rescue Net::HTTPServerException => e
+          case e.response.code
+          when "409"
+            raise Chef::Exceptions::CookbookFrozen, "Version #{cb.version} of cookbook #{cb.name} is frozen. Use --force to override."
+          else
+            raise
+          end
+        end
+      end
 
       Chef::Log.info("Upload complete!")
     end
@@ -120,15 +137,17 @@ class Chef
         timestamp = Time.now.utc.iso8601
         file_contents = File.open(file, "rb") {|f| f.read}
         # TODO - 5/28/2010, cw: make signing and sending the request streaming
-        sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(
-                                                                           :http_method => :put,
-                                                                           :path        => URI.parse(url).path,
-                                                                           :body        => file_contents,
-                                                                           :timestamp   => timestamp,
-                                                                           :user_id     => rest.client_name
-                                                                           )
         headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, :accept => 'application/json' }
-        headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key)))
+        if rest.signing_key
+          sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(
+                                                                             :http_method => :put,
+                                                                             :path        => URI.parse(url).path,
+                                                                             :body        => file_contents,
+                                                                             :timestamp   => timestamp,
+                                                                             :user_id     => rest.client_name
+                                                                             )
+          headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key)))
+        end
 
         begin
           RestClient::Resource.new(url, :headers=>headers, :timeout=>1800, :open_timeout=>1800).put(file_contents)
@@ -140,14 +159,16 @@ class Chef
       end
     end
 
-    def validate_cookbook
-      syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cookbook.name, @user_cookbook_path)
-      Chef::Log.info("Validating ruby files")
-      exit(1) unless syntax_checker.validate_ruby_files
-      Chef::Log.info("Validating templates")
-      exit(1) unless syntax_checker.validate_templates
-      Chef::Log.info("Syntax OK")
-      true
+    def validate_cookbooks
+      cookbooks.each do |cb|
+        syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cb.name, @user_cookbook_path)
+        Chef::Log.info("Validating ruby files")
+        exit(1) unless syntax_checker.validate_ruby_files
+        Chef::Log.info("Validating templates")
+        exit(1) unless syntax_checker.validate_templates
+        Chef::Log.info("Syntax OK")
+        true
+      end
     end
 
   end
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 1c2deef..5bd0ca0 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -20,90 +20,15 @@
 # limitations under the License.
 
 require 'chef/log'
-require 'chef/client'
 require 'chef/node'
 require 'chef/resource_definition_list'
 require 'chef/recipe'
 require 'chef/cookbook/file_vendor'
-require 'chef/checksum'
 require 'chef/cookbook/metadata'
 require 'chef/version_class'
 
 class Chef
 
-  #== Chef::MinimalCookbookVersion
-  # MinimalCookbookVersion is a duck type of CookbookVersion, used
-  # internally by Chef Server as an optimization when determining the
-  # optimal cookbook set for a chef-client.
-  #
-  # MinimalCookbookVersion objects contain only enough information to
-  # solve the cookbook collection for a given run list. They *do not*
-  # contain enough information to generate the response.
-  #
-  # See also: Chef::CookbookVersionSelector
-  class MinimalCookbookVersion
-
-    include Comparable
-
-    ID   = "id".freeze
-    NAME = 'name'.freeze
-    KEY  = 'key'.freeze
-    VERSION = 'version'.freeze
-    VALUE   = 'value'.freeze
-    DEPS    = 'deps'.freeze
-
-    DEPENDENCIES      = 'dependencies'.freeze
-
-    # Loads the full list of cookbooks, using a couchdb view to fetch
-    # only the id, name, version, and dependency constraints. This is
-    # enough information to solve for the cookbook collection for a
-    # given run list. After solving for the cookbook collection, you
-    # need to call +load_full_versions_of+ to convert
-    # MinimalCookbookVersion objects to their non-minimal counterparts
-    def self.load_all(couchdb)
-      # Example:
-      # {"id"=>"1a806f1c-b409-4d8e-abab-fa414ff5b96d", "key"=>"activemq", "value"=>{"version"=>"0.3.3", "deps"=>{"java"=>">= 0.0.0", "runit"=>">= 0.0.0"}}}
-      couchdb ||= Chef::CouchDB.new
-      couchdb.get_view("cookbooks", "all_with_version_and_deps")["rows"].map {|params| self.new(params) }
-    end
-
-    # Loads the non-minimal CookbookVersion objects corresponding to
-    # +minimal_cookbook_versions+ from couchdb using a bulk GET.
-    def self.load_full_versions_of(minimal_cookbook_versions, couchdb)
-      database_ids = Array(minimal_cookbook_versions).map {|mcv| mcv.couchdb_id }
-      couchdb ||= Chef::CouchDB.new
-      couchdb.bulk_get(*database_ids)
-    end
-
-    attr_reader :couchdb_id
-    attr_reader :name
-    attr_reader :version
-    attr_reader :deps
-
-    def initialize(params)
-      @couchdb_id = params[ID]
-      @name = params[KEY]
-      @version = params[VALUE][VERSION]
-      @deps    = params[VALUE][DEPS]
-    end
-
-    # Returns the Cookbook::MinimalMetadata object for this cookbook
-    # version.
-    def metadata
-      @metadata ||= Cookbook::MinimalMetadata.new(@name, DEPENDENCIES => @deps)
-    end
-
-    def legit_version
-      @legit_version ||= Chef::Version.new(@version)
-    end
-
-    def <=>(o)
-      raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name
-      raise "Unexpected comparison to #{o}" unless o.respond_to?(:legit_version)
-      legit_version <=> o.legit_version
-    end
-  end
-
   # == Chef::CookbookVersion
   # CookbookVersion is a model object encapsulating the data about a Chef
   # cookbook. Chef supports maintaining multiple versions of a cookbook on a
@@ -113,140 +38,10 @@ class Chef
   # TODO: timh/cw: 5-24-2010: mutators for files (e.g., recipe_filenames=,
   # recipe_filenames.insert) should dirty the manifest so it gets regenerated.
   class CookbookVersion
-    include Chef::IndexQueue::Indexable
     include Comparable
 
     COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
 
-    DESIGN_DOCUMENT = {
-      "version" => 8,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "cookbook_version") {
-              emit(doc.name, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "cookbook_version") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        },
-        "all_with_version" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "cookbook_version") {
-              emit(doc.cookbook_name, doc.version);
-            }
-          }
-          EOJS
-        },
-        "all_with_version_and_deps" => {
-          "map" => <<-JS
-          function(doc) {
-            if (doc.chef_type == "cookbook_version") {
-              emit(doc.cookbook_name, {version: doc.version, deps: doc.metadata.dependencies});
-            }
-          }
-          JS
-        },
-        "all_latest_version" => {
-          "map" => %q@
-          function(doc) {
-            if (doc.chef_type == "cookbook_version") {
-              emit(doc.cookbook_name, doc.version);
-            }
-          }
-          @,
-          "reduce" => %q@
-          function(keys, values, rereduce) {
-            var result = null;
-
-            for (var idx in values) {
-              var value = values[idx];
-
-              if (idx == 0) {
-                result = value;
-                continue;
-              }
-
-              var valueParts = value.split('.').map(function(v) { return parseInt(v); });
-              var resultParts = result.split('.').map(function(v) { return parseInt(v); });
-
-              if (valueParts[0] != resultParts[0]) {
-                if (valueParts[0] > resultParts[0]) {
-                  result = value;
-                }
-              }
-              else if (valueParts[1] != resultParts[1]) {
-                if (valueParts[1] > resultParts[1]) {
-                  result = value;
-                }
-              }
-              else if (valueParts[2] != resultParts[2]) {
-                if (valueParts[2] > resultParts[2]) {
-                  result = value;
-                }
-              }
-            }
-            return result;
-          }
-          @
-        },
-        "all_latest_version_by_id" => {
-          "map" => %q@
-          function(doc) {
-            if (doc.chef_type == "cookbook_version") {
-              emit(doc.cookbook_name, {version: doc.version, id:doc._id});
-            }
-          }
-          @,
-          "reduce" => %q@
-          function(keys, values, rereduce) {
-            var result = null;
-
-            for (var idx in values) {
-              var value = values[idx];
-
-              if (idx == 0) {
-                result = value;
-                continue;
-              }
-
-              var valueParts = value.version.split('.').map(function(v) { return parseInt(v); });
-              var resultParts = result.version.split('.').map(function(v) { return parseInt(v); });
-
-              if (valueParts[0] != resultParts[0]) {
-                if (valueParts[0] > resultParts[0]) {
-                  result = value;
-                }
-              }
-              else if (valueParts[1] != resultParts[1]) {
-                if (valueParts[1] > resultParts[1]) {
-                  result = value;
-                }
-              }
-              else if (valueParts[2] != resultParts[2]) {
-                if (valueParts[2] > resultParts[2]) {
-                  result = value;
-                }
-              }
-            }
-            return result;
-          }
-          @
-        },
-      }
-    }
-
     attr_accessor :root_dir
     attr_accessor :definition_filenames
     attr_accessor :template_filenames
@@ -259,10 +54,6 @@ class Chef
     attr_accessor :metadata
     attr_accessor :metadata_filenames
     attr_accessor :status
-    attr_accessor :couchdb_rev
-    attr_accessor :couchdb
-
-    attr_reader :couchdb_id
 
     # attribute_filenames also has a setter that has non-default
     # functionality.
@@ -278,150 +69,21 @@ class Chef
     # This is the one and only method that knows how cookbook files'
     # checksums are generated.
     def self.checksum_cookbook_file(filepath)
-      Chef::ChecksumCache.generate_md5_checksum_for_file(filepath)
+      Chef::Digester.generate_md5_checksum_for_file(filepath)
     rescue Errno::ENOENT
       Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
       nil
     end
 
-    # Keep track of the filenames that we use in both eager cookbook
-    # downloading (during sync_cookbooks) and lazy (during the run
-    # itself, through FileVendor). After the run is over, clean up the
-    # cache.
-    def self.valid_cache_entries
-      @valid_cache_entries ||= {}
-    end
-
-    def self.reset_cache_validity
-      @valid_cache_entries = nil
-    end
-
     def self.cache
       Chef::FileCache
     end
 
-    # Setup a notification to clear the valid_cache_entries when a Chef client
-    # run starts
-    Chef::Client.when_run_starts do |run_status|
-      reset_cache_validity
-    end
-
-    # Synchronizes all the cookbooks from the chef-server.
-    #
-    # === Returns
-    # true:: Always returns true
-    def self.sync_cookbooks(cookbook_hash)
-      Chef::Log.info("Loading cookbooks [#{cookbook_hash.keys.sort.join(', ')}]")
-      Chef::Log.debug("Cookbooks detail: #{cookbook_hash.inspect}")
-
-      clear_obsoleted_cookbooks(cookbook_hash)
-
-      # Synchronize each of the node's cookbooks, and add to the
-      # valid_cache_entries hash.
-      cookbook_hash.values.each do |cookbook|
-        sync_cookbook_file_cache(cookbook)
-      end
-
-      true
-    end
-
-    # Iterates over cached cookbooks' files, removing files belonging to
-    # cookbooks that don't appear in +cookbook_hash+
-    def self.clear_obsoleted_cookbooks(cookbook_hash)
-      # Remove all cookbooks no longer relevant to this node
-      cache.find(File.join(%w{cookbooks ** *})).each do |cache_file|
-        cache_file =~ /^cookbooks\/([^\/]+)\//
-        unless cookbook_hash.has_key?($1)
-          Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
-          cache.delete(cache_file)
-        end
-      end
-    end
-
-    # Update the file caches for a given cache segment.  Takes a segment name
-    # and a hash that matches one of the cookbooks/_attribute_files style
-    # remote file listings.
-    #
-    # === Parameters
-    # cookbook<Chef::Cookbook>:: The cookbook to update
-    # valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
-    # were referred to by this cookbook
-    def self.sync_cookbook_file_cache(cookbook)
-      Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")
-
-      # files and templates are lazily loaded, and will be done later.
-      eager_segments = COOKBOOK_SEGMENTS.dup
-      eager_segments.delete(:files)
-      eager_segments.delete(:templates)
-
-      eager_segments.each do |segment|
-        segment_filenames = Array.new
-        cookbook.manifest[segment].each do |manifest_record|
-          # segment = cookbook segment
-          # remote_list = list of file hashes
-          #
-          # We need the list of known good attribute files, so we can delete any that are
-          # just laying about.
-
-          cache_filename = File.join("cookbooks", cookbook.name, manifest_record['path'])
-          valid_cache_entries[cache_filename] = true
-
-          current_checksum = nil
-          if cache.has_key?(cache_filename)
-            current_checksum = checksum_cookbook_file(cache.load(cache_filename, false))
-          end
-
-          # If the checksums are different between on-disk (current) and on-server
-          # (remote, per manifest), do the update. This will also execute if there
-          # is no current checksum.
-          if current_checksum != manifest_record['checksum']
-            raw_file = chef_server_rest.get_rest(manifest_record[:url], true)
-
-            Chef::Log.info("Storing updated #{cache_filename} in the cache.")
-            cache.move_to(raw_file.path, cache_filename)
-          else
-            Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.")
-          end
-
-          # make the segment filenames a full path.
-          full_path_cache_filename = cache.load(cache_filename, false)
-          segment_filenames << full_path_cache_filename
-        end
-
-        # replace segment filenames with a full-path one.
-        if segment.to_sym == :recipes
-          cookbook.recipe_filenames = segment_filenames
-        elsif segment.to_sym == :attributes
-          cookbook.attribute_filenames = segment_filenames
-        else
-          cookbook.segment_filenames(segment).replace(segment_filenames)
-        end
-      end
-    end
-
-    def self.cleanup_file_cache
-      unless Chef::Config[:solo]
-        # Delete each file in the cache that we didn't encounter in the
-        # manifest.
-        cache.find(File.join(%w{cookbooks ** *})).each do |cache_filename|
-          unless valid_cache_entries[cache_filename]
-            Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer needed by chef-client.")
-            cache.delete(cache_filename)
-          end
-        end
-      end
-    end
-
-    # Register a notification to cleanup unused files from cookbooks
-    Chef::Client.when_run_completes_successfully do |run_status|
-      cleanup_file_cache
-    end
-
     # Creates a new Chef::CookbookVersion object.
     #
     # === Returns
     # object<Chef::CookbookVersion>:: Duh. :)
-    def initialize(name, couchdb=nil)
+    def initialize(name)
       @name = name
       @frozen = false
       @attribute_filenames = Array.new
@@ -436,9 +98,6 @@ class Chef
       @metadata_filenames = Array.new
       @root_dir = nil
       @root_filenames = Array.new
-      @couchdb_id = nil
-      @couchdb = couchdb || Chef::CouchDB.new
-      @couchdb_rev = nil
       @status = :ready
       @manifest = nil
       @file_vendor = nil
@@ -564,7 +223,7 @@ class Chef
     # called from DSL
     def load_recipe(recipe_name, run_context)
       unless recipe_filenames_by_name.has_key?(recipe_name)
-        raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{name}"
+        raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
       end
 
       Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}")
@@ -572,7 +231,7 @@ class Chef
       recipe_filename = recipe_filenames_by_name[recipe_name]
 
       unless recipe_filename
-        raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
+        raise Chef::Exceptions::RecipeNotFound, "could not find #{recipe_name} files for cookbook #{name}"
       end
 
       recipe.from_file(recipe_filename)
@@ -606,6 +265,18 @@ class Chef
       end
     end
 
+    # Query whether a template file +template_filename+ is available. File
+    # specificity for the given +node+ is obeyed in the lookup.
+    def has_template_for_node?(node, template_filename)
+      !!find_preferred_manifest_record(node, :templates, template_filename)
+    end
+
+    # Query whether a cookbook_file file +cookbook_filename+ is available. File
+    # specificity for the given +node+ is obeyed in the lookup.
+    def has_cookbook_file_for_node?(node, cookbook_filename)
+      !!find_preferred_manifest_record(node, :files, cookbook_filename)
+    end
+
     # Determine the most specific manifest record for the given
     # segment/filename, given information in the node. Throws
     # FileNotFound if there is no such segment and filename in the
@@ -619,14 +290,7 @@ class Chef
     #   :checksum => "1234"
     # }
     def preferred_manifest_record(node, segment, filename)
-      preferences = preferences_for_path(node, segment, filename)
-
-      # ensure that we generate the manifest, which will also generate
-      # @manifest_records_by_path
-      manifest
-
-      # in order of prefernce, look for the filename in the manifest
-      found_pref = preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] }
+      found_pref = find_preferred_manifest_record(node, segment, filename)
       if found_pref
         @manifest_records_by_path[found_pref]
       else
@@ -781,7 +445,6 @@ class Chef
       result = manifest.dup
       result['frozen?'] = frozen_version?
       result['chef_type'] = 'cookbook_version'
-      result["_rev"] = couchdb_rev if couchdb_rev
       result.to_hash
     end
 
@@ -793,21 +456,12 @@ class Chef
 
     def self.json_create(o)
       cookbook_version = new(o["cookbook_name"])
-      if o.has_key?('_rev')
-        cookbook_version.couchdb_rev = o["_rev"] if o.has_key?("_rev")
-        o.delete("_rev")
-      end
-      if o.has_key?("_id")
-        cookbook_version.couchdb_id = o["_id"] if o.has_key?("_id")
-        cookbook_version.index_id = cookbook_version.couchdb_id
-        o.delete("_id")
-      end
       # We want the Chef::Cookbook::Metadata object to always be inflated
       cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
       cookbook_version.manifest = o
 
       # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>)
-      cookbook_version.manifest["metadata"] = JSON.parse(cookbook_version.metadata.to_json)
+      cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(cookbook_version.metadata.to_json)
 
       cookbook_version.freeze_version if o["frozen?"]
       cookbook_version
@@ -908,83 +562,6 @@ class Chef
       chef_server_rest.get_rest('cookbooks/_latest')
     end
 
-    ##
-    # Couchdb
-    ##
-
-    def self.cdb_by_name(cookbook_name, couchdb=nil)
-      cdb = (couchdb || Chef::CouchDB.new)
-      options = { :startkey => cookbook_name, :endkey => cookbook_name }
-      rs = cdb.get_view("cookbooks", "all_with_version", options)
-      rs["rows"].inject({}) { |memo, row| memo.has_key?(row["key"]) ? memo[row["key"]] << row["value"] : memo[row["key"]] = [ row["value"] ]; memo }
-    end
-
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("cookbooks", DESIGN_DOCUMENT)
-    end
-
-    def self.cdb_list_latest(inflate=false, couchdb=nil)
-      couchdb ||= Chef::CouchDB.new
-      if inflate
-        doc_ids = cdb_list_latest_ids.map {|i|i["id"]}
-        couchdb.bulk_get(doc_ids)
-      else
-        results = couchdb.get_view("cookbooks", "all_latest_version", :group=>true)["rows"]
-        results.inject({}) { |mapped, row| mapped[row["key"]] = row["value"]; mapped}
-      end
-    end
-
-    def self.cdb_list_latest_ids(inflate=false, couchdb=nil)
-      couchdb ||= Chef::CouchDB.new
-      results = couchdb.get_view("cookbooks", "all_latest_version_by_id", :group=>true)["rows"]
-      results.map { |name_and_id| name_and_id["value"]}
-    end
-
-    def self.cdb_list(inflate=false, couchdb=nil)
-      couchdb ||= Chef::CouchDB.new
-      if inflate
-        couchdb.list("cookbooks", true)["rows"].collect{|r| r["value"]}
-      else
-        # If you modify this, please make sure the desc sorted order on the versions doesn't get broken.
-        couchdb.get_view("cookbooks", "all_with_version")["rows"].inject({}) { |mapped, row| mapped[row["key"]]||=Array.new; mapped[row["key"]].push(Chef::Version.new(row["value"])); mapped[row["key"]].sort!.reverse!; mapped}
-      end
-    end
-
-    def self.cdb_load(name, version='latest', couchdb=nil)
-      cdb = couchdb || Chef::CouchDB.new
-      if version == "latest" || version == "_latest"
-        rs = cdb.get_view("cookbooks", "all_latest_version", :key => name, :descending => true, :group => true, :reduce => true)["rows"].first
-        cdb.load("cookbook_version", "#{rs["key"]}-#{rs["value"]}")
-      else
-        cdb.load("cookbook_version", "#{name}-#{version}")
-      end
-    end
-
-    def cdb_destroy
-      (couchdb || Chef::CouchDB.new).delete("cookbook_version", full_name, couchdb_rev)
-    end
-
-    # Runs on Chef Server (API); deletes the cookbook from couchdb and also destroys associated
-    # checksum documents
-    def purge
-      checksums.keys.each do |checksum|
-        begin
-          Chef::Checksum.cdb_load(checksum, couchdb).purge
-        rescue Chef::Exceptions::CouchDBNotFound
-        end
-      end
-      cdb_destroy
-    end
-
-    def cdb_save
-      @couchdb_rev = couchdb.store("cookbook_version", full_name, self)["rev"]
-    end
-
-    def couchdb_id=(value)
-      @couchdb_id = value
-      @index_id = value
-    end
-
     def <=>(o)
       raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name
       # FIXME: can we change the interface to the Metadata class such
@@ -995,6 +572,17 @@ class Chef
 
     private
 
+    def find_preferred_manifest_record(node, segment, filename)
+      preferences = preferences_for_path(node, segment, filename)
+
+      # ensure that we generate the manifest, which will also generate
+      # @manifest_records_by_path
+      manifest
+
+      # in order of prefernce, look for the filename in the manifest
+      preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] }
+    end
+
     # For each filename, produce a mapping of base filename (i.e. recipe name
     # or attribute file) to on disk location
     def filenames_by_name(filenames)
diff --git a/lib/chef/cookbook_version_selector.rb b/lib/chef/cookbook_version_selector.rb
deleted file mode 100644
index 9e60f85..0000000
--- a/lib/chef/cookbook_version_selector.rb
+++ /dev/null
@@ -1,168 +0,0 @@
-#
-# Author:: Tim Hinderliter (<tim at opscode.com>)
-# Copyright:: Copyright (c) 2011 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 'dep_selector'
-
-class Chef
-  module CookbookVersionSelector
-    # This method replaces verbiage from DepSelector messages with
-    # Chef-domain-specific verbiage, such as replacing package with
-    # cookbook.
-    #
-    # TODO [cw, 2011/2/25]: this is a near-term hack. In the long run,
-    # we'll do this better.
-    def self.filter_dep_selector_message(message)
-      m = message
-      m.gsub!("Package", "Cookbook")
-      m.gsub!("package", "cookbook")
-      m.gsub!("Solution constraint", "Run list item")
-      m.gsub!("solution constraint", "run list item")
-      m
-    end
-
-    # all_cookbooks - a hash mapping cookbook names to an array of
-    # available CookbookVersions.
-    #
-    # Creates a DependencyGraph from CookbookVersion objects
-    def self.create_dependency_graph_from_cookbooks(all_cookbooks)
-      dep_graph = DepSelector::DependencyGraph.new
-
-      all_cookbooks.each do |cb_name, cb_versions|
-        cb_versions.each do |cb_version|
-          cb_version_deps = cb_version.metadata.dependencies
-          # TODO [cw. 2011/2/10]: CookbookVersion#version returns a
-          # String even though we're storing as a DepSelector::Version
-          # object underneath. This should be changed so that we
-          # return the object and handle proper serialization and
-          # de-serialization. For now, I'm just going to create a
-          # Version object from the String representation.
-          pv = dep_graph.package(cb_name).add_version(Chef::Version.new(cb_version.version))
-          cb_version_deps.each_pair do |dep_name, constraint_str|
-            # if the dependency is specified as cookbook::recipe,
-            # extract the cookbook component
-            dep_cb_name = dep_name.split("::").first
-            constraint = Chef::VersionConstraint.new(constraint_str)
-            pv.dependencies << DepSelector::Dependency.new(dep_graph.package(dep_cb_name), constraint)
-          end
-        end
-      end
-
-      dep_graph
-    end
-
-    # Return a hash mapping cookbook names to a CookbookVersion
-    # object. If there is no solution that satisfies the constraints,
-    # the first run list item that caused unsatisfiability is
-    # returned.
-    #
-    # This is the final version-resolved list of cookbooks for the
-    # RunList.
-    #
-    # all_cookbooks - a hash mapping cookbook names to an array of
-    # available CookbookVersions.
-    #
-    # recipe_constraints - an array of hashes describing the expanded
-    # run list.  Each element is a hash containing keys :name and
-    # :version_constraint. The :name component is either the
-    # fully-qualified recipe name (e.g. "cookbook1::non_default_recipe")
-    # or just a cookbook name, indicating the default recipe is to be
-    # run (e.g. "cookbook1").
-    def self.constrain(all_cookbooks, recipe_constraints)
-      dep_graph = create_dependency_graph_from_cookbooks(all_cookbooks)
-
-      # extract cookbook names from (possibly) fully-qualified recipe names
-      cookbook_constraints = recipe_constraints.map do |recipe_spec|
-        cookbook_name = (recipe_spec[:name][/^(.+)::/, 1] || recipe_spec[:name])
-        DepSelector::SolutionConstraint.new(dep_graph.package(cookbook_name),
-                                            recipe_spec[:version_constraint])
-      end
-
-      # Pass in the list of all available cookbooks (packages) so that
-      # DepSelector can distinguish between "no version available for
-      # cookbook X" and "no such cookbook X" when an environment
-      # filters out all versions for a given cookbook.
-      all_packages = all_cookbooks.inject([]) do |acc, (cookbook_name, cookbook_versions)|
-        acc << dep_graph.package(cookbook_name)
-        acc
-      end
-
-      # find a valid assignment of CoookbookVersions. If no valid
-      # assignment exists, indicate which run_list_item causes the
-      # unsatisfiability and try to hint at what might be wrong.
-      soln =
-        begin
-          DepSelector::Selector.new(dep_graph).find_solution(cookbook_constraints, all_packages)
-        rescue DepSelector::Exceptions::InvalidSolutionConstraints => e
-          non_existent_cookbooks = e.non_existent_packages.map {|constraint| constraint.package.name}
-          cookbooks_with_no_matching_versions = e.constrained_to_no_versions.map {|constraint| constraint.package.name}
-
-          # Spend a whole lot of effort for pluralizing and
-          # prettifying the message.
-          message = ""
-          if non_existent_cookbooks.length > 0
-            message += "no such " + (non_existent_cookbooks.length > 1 ? "cookbooks" : "cookbook")
-            message += " #{non_existent_cookbooks.join(", ")}"
-          end
-
-          if cookbooks_with_no_matching_versions.length > 0
-            if message.length > 0
-              message += "; "
-            end
-
-            message += "no versions match the constraints on " + (cookbooks_with_no_matching_versions.length > 1 ? "cookbooks" : "cookbook")
-            message += " #{cookbooks_with_no_matching_versions.join(", ")}"
-          end
-
-          message = "Run list contains invalid items: #{message}."
-
-          raise Chef::Exceptions::CookbookVersionSelection::InvalidRunListItems.new(message, non_existent_cookbooks, cookbooks_with_no_matching_versions)
-        rescue DepSelector::Exceptions::NoSolutionExists => e
-          raise Chef::Exceptions::CookbookVersionSelection::UnsatisfiableRunListItem.new(filter_dep_selector_message(e.message), e.unsatisfiable_solution_constraint, e.disabled_non_existent_packages, e.disabled_most_constrained_packages)
-        end
-
-
-      # map assignment back to CookbookVersion objects
-      selected_cookbooks = {}
-      soln.each_pair do |cb_name, cb_version|
-        # TODO [cw, 2011/2/10]: related to the TODO in
-        # create_dependency_graph_from_cookbooks, cbv.version
-        # currently returns a String, so we must compare to
-        # cb_version.to_s, since it's a for-real Version object.
-        selected_cookbooks[cb_name] = all_cookbooks[cb_name].find{|cbv| cbv.version == cb_version.to_s}
-      end
-      selected_cookbooks
-    end
-
-    # Expands the run_list, constrained to the environment's CookbookVersion
-    # constraints.
-    #
-    # Returns:
-    #   Hash of: name to CookbookVersion
-    def self.expand_to_cookbook_versions(run_list, environment, couchdb=nil)
-      # expand any roles in this run_list.
-      expanded_run_list = run_list.expand(environment, 'couchdb', :couchdb => couchdb).recipes.with_version_constraints
-
-      cookbooks_for_environment = Chef::Environment.cdb_minimal_filtered_versions(environment, couchdb)
-      cookbook_collection = constrain(cookbooks_for_environment, expanded_run_list)
-      full_cookbooks = Chef::MinimalCookbookVersion.load_full_versions_of(cookbook_collection.values, couchdb)
-      full_cookbooks.inject({}) do |cb_map, cookbook_version|
-        cb_map[cookbook_version.name] = cookbook_version
-        cb_map
-      end
-    end
-  end
-end
diff --git a/lib/chef/couchdb.rb b/lib/chef/couchdb.rb
deleted file mode 100644
index 71ee196..0000000
--- a/lib/chef/couchdb.rb
+++ /dev/null
@@ -1,246 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Christopher Brown (<cb at opscode.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 'chef/mixin/params_validate'
-require 'chef/config'
-require 'chef/rest'
-require 'chef/log'
-require 'digest/sha2'
-require 'chef/json_compat'
-
-# We want to fail on create if uuidtools isn't installed
-begin
-  require 'uuidtools'
-rescue LoadError
-end
-
-class Chef
-  class CouchDB
-    include Chef::Mixin::ParamsValidate
-
-    def initialize(url=nil, db=Chef::Config[:couchdb_database])
-      url ||= Chef::Config[:couchdb_url]
-      @db = db
-      @rest = Chef::REST.new(url, nil, nil)
-    end
-
-    def couchdb_database(args=nil)
-      @db = args || @db
-    end
-
-    def create_id_map
-      create_design_document(
-        "id_map",
-        {
-          "version" => 1,
-          "language" => "javascript",
-          "views" => {
-            "name_to_id" => {
-              "map" => <<-EOJS
-                function(doc) {
-                  emit([ doc.chef_type, doc.name], doc._id);
-                }
-              EOJS
-            },
-            "id_to_name" => {
-              "map" => <<-EOJS
-                function(doc) {
-                  emit(doc._id, [ doc.chef_type, doc.name ]);
-                }
-              EOJS
-            }
-          }
-        }
-      )
-    end
-
-    def create_db(check_for_existing=true)
-      @database_list = @rest.get_rest("_all_dbs")
-      if !check_for_existing || !@database_list.any? { |db| db == couchdb_database }
-        response = @rest.put_rest(couchdb_database, Hash.new)
-      end
-      couchdb_database
-    end
-
-    def create_design_document(name, data)
-      to_update = true
-      begin
-        old_doc = @rest.get_rest("#{couchdb_database}/_design/#{name}")
-        if data["version"] != old_doc["version"]
-          data["_rev"] = old_doc["_rev"]
-          Chef::Log.debug("Updating #{name} views")
-        else
-          to_update = false
-        end
-      rescue
-        Chef::Log.debug("Creating #{name} views for the first time because: #{$!}")
-      end
-      if to_update
-        @rest.put_rest("#{couchdb_database}/_design%2F#{name}", data)
-      end
-      true
-    end
-
-    # Save the object to Couch. Add to index if the object supports it.
-    def store(obj_type, name, object)
-      validate(
-        {
-          :obj_type => obj_type,
-          :name => name,
-          :object => object,
-        },
-        {
-          :object => { :respond_to => :to_json },
-        }
-      )
-      rows = get_view("id_map", "name_to_id", :key => [ obj_type, name ])["rows"]
-      uuid = rows.empty? ? UUIDTools::UUID.random_create.to_s : rows.first.fetch("id")
-
-      db_put_response = @rest.put_rest("#{couchdb_database}/#{uuid}", object)
-
-      if object.respond_to?(:add_to_index)
-        Chef::Log.info("Sending #{obj_type}(#{uuid}) to the index queue for addition.")
-        object.add_to_index(:database => couchdb_database, :id => uuid, :type => obj_type)
-      end
-
-      db_put_response
-    end
-
-    def load(obj_type, name)
-      validate(
-        {
-          :obj_type => obj_type,
-          :name => name,
-        },
-        {
-          :obj_type => { :kind_of => String },
-          :name => { :kind_of => String },
-        }
-               )
-      doc = find_by_name(obj_type, name)
-      doc.couchdb = self if doc.respond_to?(:couchdb)
-      doc
-    end
-
-    def delete(obj_type, name, rev=nil)
-      validate(
-        {
-          :obj_type => obj_type,
-          :name => name,
-        },
-        {
-          :obj_type => { :kind_of => String },
-          :name => { :kind_of => String },
-        }
-      )
-      del_id = nil
-      object, uuid = find_by_name(obj_type, name, true)
-      unless rev
-        if object.respond_to?(:couchdb_rev)
-          rev = object.couchdb_rev
-        else
-          rev = object['_rev']
-        end
-      end
-      response = @rest.delete_rest("#{couchdb_database}/#{uuid}?rev=#{rev}")
-      response.couchdb = self if response.respond_to?(:couchdb=)
-
-      if object.respond_to?(:delete_from_index)
-        Chef::Log.info("Sending #{obj_type}(#{uuid}) to the index queue for deletion..")
-        object.delete_from_index(:database => couchdb_database, :id => uuid, :type => obj_type)
-      end
-
-      response
-    end
-
-    def list(view, inflate=false)
-      validate(
-        {
-          :view => view,
-        },
-        {
-          :view => { :kind_of => String }
-        }
-      )
-      if inflate
-        r = @rest.get_rest(view_uri(view, "all"))
-        r["rows"].each { |i| i["value"].couchdb = self if i["value"].respond_to?(:couchdb=) }
-        r
-      else
-        r = @rest.get_rest(view_uri(view, "all_id"))
-      end
-      r
-    end
-
-    def has_key?(obj_type, name)
-      validate(
-        {
-          :obj_type => obj_type,
-          :name => name,
-        },
-        {
-          :obj_type => { :kind_of => String },
-          :name => { :kind_of => String },
-        }
-      )
-      begin
-        find_by_name(obj_type, name)
-        true
-      rescue
-        false
-      end
-    end
-
-    def find_by_name(obj_type, name, with_id=false)
-      r = get_view("id_map", "name_to_id", :key => [ obj_type, name ], :include_docs => true)
-      if r["rows"].length == 0
-        raise Chef::Exceptions::CouchDBNotFound, "Cannot find #{obj_type} #{name} in CouchDB!"
-      end
-      if with_id
-        [ r["rows"][0]["doc"], r["rows"][0]["id"] ]
-      else
-        r["rows"][0]["doc"]
-      end
-    end
-
-    def get_view(design, view, options={})
-      view_string = view_uri(design, view)
-      view_string << "?" if options.length != 0
-      view_string << options.map { |k,v| "#{k}=#{URI.escape(v.to_json)}"}.join('&')
-      @rest.get_rest(view_string)
-    end
-
-    def bulk_get(*to_fetch)
-      response = @rest.post_rest("#{couchdb_database}/_all_docs?include_docs=true", { "keys" => to_fetch.flatten })
-      response["rows"].collect { |r| r["doc"] }
-    end
-
-    def view_uri(design, view)
-      "#{couchdb_database}/_design/#{design}/_view/#{view}"
-    end
-
-    def server_stats
-      @rest.get_rest('/')
-    end
-
-    def db_stats
-      @rest.get_rest("/#{@db}")
-    end
-
-  end
-end
diff --git a/lib/chef/daemon.rb b/lib/chef/daemon.rb
index bb5ccf7..e5abca2 100644
--- a/lib/chef/daemon.rb
+++ b/lib/chef/daemon.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -18,12 +18,14 @@
 # I love you Merb (lib/merb-core/server.rb)
 
 require 'chef/config'
+require 'chef/run_lock'
 require 'etc'
 
 class Chef
   class Daemon
     class << self
       attr_accessor :name
+      attr_accessor :runlock
 
       # Daemonize the current process, managing pidfiles and process uid/gid
       #
@@ -32,9 +34,9 @@ class Chef
       #
       def daemonize(name)
         @name = name
-        pid = pid_from_file
-        unless running?
-          remove_pid_file()
+        @runlock = RunLock.new(pid_file)
+        if runlock.test
+          # We've acquired the daemon lock. Now daemonize.
           Chef::Log.info("Daemonizing..")
           begin
             exit if fork
@@ -45,35 +47,15 @@ class Chef
             $stdin.reopen("/dev/null")
             $stdout.reopen("/dev/null", "a")
             $stderr.reopen($stdout)
-            save_pid_file
-            at_exit { remove_pid_file }
+            runlock.save_pid
           rescue NotImplementedError => e
             Chef::Application.fatal!("There is no fork: #{e.message}")
           end
         else
-          Chef::Application.fatal!("Chef is already running pid #{pid}")
-        end
-      end
-  
-      # Check if Chef is running based on the pid_file
-      # ==== Returns
-      # Boolean::
-      # True if Chef is running
-      # False if Chef is not running
-      #
-      def running?
-        if pid_from_file.nil?
-          false
-        else
-          Process.kill(0, pid_from_file)
-          true
+          Chef::Application.fatal!("Chef is already running pid #{pid_from_file}")
         end
-      rescue Errno::ESRCH, Errno::ENOENT
-        false
-      rescue Errno::EACCES => e
-        Chef::Application.fatal!("You don't have access to the PID file at #{pid_file}: #{e.message}")
       end
-      
+
       # Gets the pid file for @name
       # ==== Returns
       # String::
@@ -81,7 +63,7 @@ class Chef
       def pid_file
          Chef::Config[:pid_file] or "/tmp/#{@name}.pid"
       end
-      
+
       # Suck the pid out of pid_file
       # ==== Returns
       # Integer::
@@ -94,30 +76,7 @@ class Chef
       rescue Errno::ENOENT, Errno::EACCES
         nil
       end
-    
-      # Store the PID on the filesystem
-      # This uses the Chef::Config[:pid_file] option, or "/tmp/name.pid" otherwise
-      #
-      def save_pid_file
-        file = pid_file
-        begin
-          FileUtils.mkdir_p(File.dirname(file))
-        rescue Errno::EACCES => e
-          Chef::Application.fatal!("Failed store pid in #{File.dirname(file)}, permission denied: #{e.message}")
-        end
-      
-        begin
-          File.open(file, "w") { |f| f.write(Process.pid.to_s) }
-        rescue Errno::EACCES => e
-          Chef::Application.fatal!("Couldn't write to pidfile #{file}, permission denied: #{e.message}")
-        end
-      end
-    
-      # Delete the PID from the filesystem
-      def remove_pid_file
-        FileUtils.rm(pid_file) if File.exists?(pid_file)
-      end
-           
+
       # Change process user/group to those specified in Chef::Config
       #
       def change_privilege
@@ -131,7 +90,7 @@ class Chef
           _change_privilege(Chef::Config[:user])
         end
       end
-    
+
       # Change privileges of the process to be the specified user and group
       #
       # ==== Parameters
@@ -150,14 +109,14 @@ class Chef
           Chef::Application.fatal!("Failed to get UID for user #{user}, does it exist? #{e.message}")
           return false
         end
-   
+
         begin
           target_gid = Etc.getgrnam(group).gid
         rescue ArgumentError => e
           Chef::Application.fatal!("Failed to get GID for group #{group}, does it exist? #{e.message}")
           return false
         end
-      
+
         if (uid != target_uid) or (gid != target_gid)
           Process.initgroups(user, target_gid)
           Process::GID.change_privilege(target_gid)
diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb
index a1eb46c..5994e6f 100644
--- a/lib/chef/data_bag.rb
+++ b/lib/chef/data_bag.rb
@@ -21,9 +21,7 @@
 require 'chef/config'
 require 'chef/mixin/params_validate'
 require 'chef/mixin/from_file'
-require 'chef/couchdb'
 require 'chef/data_bag_item'
-require 'chef/index_queue'
 require 'chef/mash'
 require 'chef/json_compat'
 
@@ -32,58 +30,18 @@ class Chef
 
     include Chef::Mixin::FromFile
     include Chef::Mixin::ParamsValidate
-    include Chef::IndexQueue::Indexable
 
     VALID_NAME = /^[\-[:alnum:]_]+$/
 
-    DESIGN_DOCUMENT = {
-      "version" => 2,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "data_bag") {
-              emit(doc.name, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "data_bag") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        },
-        "entries" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "data_bag_item") {
-              emit(doc.data_bag, doc.raw_data.id);
-            }
-          }
-          EOJS
-        }
-      }
-    }
-
     def self.validate_name!(name)
       unless name =~ VALID_NAME
         raise Exceptions::InvalidDataBagName, "DataBags must have a name matching #{VALID_NAME.inspect}, you gave #{name.inspect}"
       end
     end
 
-    attr_accessor :couchdb_rev, :couchdb_id, :couchdb
-
     # Create a new Chef::DataBag
-    def initialize(couchdb=nil)
+    def initialize
       @name = ''
-      @couchdb_rev = nil
-      @couchdb_id = nil
-      @couchdb = (couchdb || Chef::CouchDB.new)
     end
 
     def name(arg=nil)
@@ -100,7 +58,6 @@ class Chef
         'json_class' => self.class.name,
         "chef_type" => "data_bag",
       }
-      result["_rev"] = @couchdb_rev if @couchdb_rev
       result
     end
 
@@ -121,37 +78,30 @@ class Chef
     def self.json_create(o)
       bag = new
       bag.name(o["name"])
-      bag.couchdb_rev = o["_rev"] if o.has_key?("_rev")
-      bag.couchdb_id = o["_id"] if o.has_key?("_id")
-      bag.index_id = bag.couchdb_id
       bag
     end
 
-    # List all the Chef::DataBag objects in the CouchDB.  If inflate is set to true, you will get
-    # the full list of all Roles, fully inflated.
-    def self.cdb_list(inflate=false, couchdb=nil)
-      rs = (couchdb || Chef::CouchDB.new).list("data_bags", inflate)
-      lookup = (inflate ? "value" : "key")
-      rs["rows"].collect { |r| r[lookup] }
-    end
-
     def self.list(inflate=false)
-      if inflate
-        # Can't search for all data bags like other objects, fall back to N+1 :(
-        list(false).inject({}) do |response, bag_and_uri|
-          response[bag_and_uri.first] = load(bag_and_uri.first)
-          response
+      if Chef::Config[:solo]
+        unless File.directory?(Chef::Config[:data_bag_path])
+          raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{Chef::Config[:data_bag_path]}' is invalid"
         end
+
+        names = Dir.glob(File.join(Chef::Config[:data_bag_path], "*")).map{|f|File.basename(f)}.sort
+        names.inject({}) {|h, n| h[n] = n; h}
       else
-        Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data")
+        if inflate
+          # Can't search for all data bags like other objects, fall back to N+1 :(
+          list(false).inject({}) do |response, bag_and_uri|
+            response[bag_and_uri.first] = load(bag_and_uri.first)
+            response
+          end
+        else
+          Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data")
+        end
       end
     end
 
-    # Load a Data Bag by name from CouchDB
-    def self.cdb_load(name, couchdb=nil)
-      (couchdb || Chef::CouchDB.new).load("data_bag", name)
-    end
-
     # Load a Data Bag by name via either the RESTful API or local data_bag_path if run in solo mode
     def self.load(name)
       if Chef::Config[:solo]
@@ -159,8 +109,8 @@ class Chef
           raise Chef::Exceptions::InvalidDataBagPath, "Data bag path '#{Chef::Config[:data_bag_path]}' is invalid"
         end
 
-        Dir.glob(File.join(Chef::Config[:data_bag_path], name, "*.json")).inject({}) do |bag, f|
-          item = JSON.parse(IO.read(f))
+        Dir.glob(File.join(Chef::Config[:data_bag_path], "#{name}", "*.json")).inject({}) do |bag, f|
+          item = Chef::JSONCompat.from_json(IO.read(f))
           bag[item['id']] = item
           bag
         end
@@ -169,34 +119,20 @@ class Chef
       end
     end
 
-    # Remove this Data Bag from CouchDB
-    def cdb_destroy
-      removed = @couchdb.delete("data_bag", @name, @couchdb_rev)
-      rs = @couchdb.get_view("data_bags", "entries", :include_docs => true, :startkey => @name, :endkey => @name)
-      rs["rows"].each do |row|
-        row["doc"].couchdb = couchdb
-        row["doc"].cdb_destroy
-      end
-      removed
-    end
-
     def destroy
       chef_server_rest.delete_rest("data/#{@name}")
     end
 
-    # Save this Data Bag to the CouchDB
-    def cdb_save
-      results = @couchdb.store("data_bag", @name, self)
-      @couchdb_rev = results["rev"]
-    end
-
     # Save the Data Bag via RESTful API
     def save
       begin
-        chef_server_rest.put_rest("data/#{@name}", self)
+        if Chef::Config[:why_run]
+          Chef::Log.warn("In whyrun mode, so NOT performing data bag save.")
+        else
+          create
+        end
       rescue Net::HTTPServerException => e
-        raise e unless e.response.code == "404"
-        chef_server_rest.post_rest("data", self)
+        raise e unless e.response.code == "409"
       end
       self
     end
@@ -207,24 +143,6 @@ class Chef
       self
     end
 
-    # List all the items in this Bag from CouchDB
-    # The self.load method does this through the REST API
-    def list(inflate=false)
-      rs = nil
-      if inflate
-        rs = @couchdb.get_view("data_bags", "entries", :include_docs => true, :startkey => @name, :endkey => @name)
-        rs["rows"].collect { |r| r["doc"] }
-      else
-        rs = @couchdb.get_view("data_bags", "entries", :startkey => @name, :endkey => @name)
-        rs["rows"].collect { |r| r["value"] }
-      end
-    end
-
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("data_bags", DESIGN_DOCUMENT)
-    end
-
     # As a string
     def to_s
       "data_bag[#{@name}]"
diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb
index 9cd3704..3528ba7 100644
--- a/lib/chef/data_bag_item.rb
+++ b/lib/chef/data_bag_item.rb
@@ -23,8 +23,6 @@ require 'forwardable'
 require 'chef/config'
 require 'chef/mixin/params_validate'
 require 'chef/mixin/from_file'
-require 'chef/couchdb'
-require 'chef/index_queue'
 require 'chef/data_bag'
 require 'chef/mash'
 require 'chef/json_compat'
@@ -36,35 +34,9 @@ class Chef
 
     include Chef::Mixin::FromFile
     include Chef::Mixin::ParamsValidate
-    include Chef::IndexQueue::Indexable
 
     VALID_ID = /^[\-[:alnum:]_]+$/
 
-    DESIGN_DOCUMENT = {
-      "version" => 1,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "data_bag_item") {
-              emit(doc.name, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "data_bag_item") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        }
-      }
-    }
-
     def self.validate_id!(id_str)
       if id_str.nil? || ( id_str !~ VALID_ID )
         raise Exceptions::InvalidDataBagItemID, "Data Bag items must have an id matching #{VALID_ID.inspect}, you gave: #{id_str.inspect}"
@@ -74,16 +46,12 @@ class Chef
     # Define all Hash's instance methods as delegating to @raw_data
     def_delegators(:@raw_data, *(Hash.instance_methods - Object.instance_methods))
 
-    attr_accessor :couchdb_rev, :couchdb_id, :couchdb
     attr_reader :raw_data
 
     # Create a new Chef::DataBagItem
-    def initialize(couchdb=nil)
-      @couchdb_rev = nil
-      @couchdb_id = nil
+    def initialize
       @data_bag = nil
       @raw_data = Mash.new
-      @couchdb = couchdb || Chef::CouchDB.new
     end
 
     def chef_server_rest
@@ -138,7 +106,6 @@ class Chef
       result = self.raw_data
       result["chef_type"] = "data_bag_item"
       result["data_bag"] = self.data_bag
-      result["_rev"] = @couchdb_rev if @couchdb_rev
       result
     end
 
@@ -151,7 +118,6 @@ class Chef
         "data_bag" => self.data_bag,
         "raw_data" => self.raw_data
       }
-      result["_rev"] = @couchdb_rev if @couchdb_rev
       result.to_json(*a)
     end
 
@@ -169,24 +135,11 @@ class Chef
       o.delete("chef_type")
       o.delete("json_class")
       o.delete("name")
-      if o.has_key?("_rev")
-        bag_item.couchdb_rev = o["_rev"]
-        o.delete("_rev")
-      end
-      if o.has_key?("_id")
-        bag_item.couchdb_id = o["_id"]
-        bag_item.index_id = bag_item.couchdb_id
-        o.delete("_id")
-      end
+
       bag_item.raw_data = Mash.new(o["raw_data"])
       bag_item
     end
 
-    # Load a Data Bag Item by name from CouchDB
-    def self.cdb_load(data_bag, name, couchdb=nil)
-      (couchdb || Chef::CouchDB.new).load("data_bag_item", object_name(data_bag, name))
-    end
-
     # Load a Data Bag Item by name via either the RESTful API or local data_bag_path if run in solo mode
     def self.load(data_bag, name)
       if Chef::Config[:solo]
@@ -205,26 +158,19 @@ class Chef
       end
     end
 
-    # Remove this Data Bag Item from CouchDB
-    def cdb_destroy
-      Chef::Log.debug "Destroying data bag item: #{self.inspect}"
-      @couchdb.delete("data_bag_item", object_name, @couchdb_rev)
-    end
-
     def destroy(data_bag=data_bag, databag_item=name)
       chef_server_rest.delete_rest("data/#{data_bag}/#{databag_item}")
     end
 
-    # Save this Data Bag Item to CouchDB
-    def cdb_save
-      @couchdb_rev = @couchdb.store("data_bag_item", object_name, self)["rev"]
-    end
-
     # Save this Data Bag Item via RESTful API
     def save(item_id=@raw_data['id'])
       r = chef_server_rest
       begin
-        r.put_rest("data/#{data_bag}/#{item_id}", self)
+        if Chef::Config[:why_run]
+          Chef::Log.warn("In whyrun mode, so NOT performing data bag item save.")
+        else
+          r.put_rest("data/#{data_bag}/#{item_id}", self)
+        end
       rescue Net::HTTPServerException => e
         raise e unless e.response.code == "404"
         r.post_rest("data/#{data_bag}", self)
@@ -238,11 +184,6 @@ class Chef
       self
     end
 
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("data_bag_items", DESIGN_DOCUMENT)
-    end
-
     def ==(other)
       other.respond_to?(:to_hash) &&
       other.respond_to?(:data_bag) &&
diff --git a/lib/chef/deprecation/mixin/template.rb b/lib/chef/deprecation/mixin/template.rb
new file mode 100644
index 0000000..36d18ad
--- /dev/null
+++ b/lib/chef/deprecation/mixin/template.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'tempfile'
+require 'erubis'
+
+class Chef
+  module Deprecation
+    module Mixin
+      # == Deprecation::Provider::Mixin::Template
+      # This module contains the deprecated functions of
+      # Chef::Mixin::Template. These functions are refactored to different
+      # components. They are frozen and will be removed in Chef 12.
+      #
+
+      module Template
+        def render_template(template, context)
+          begin
+            eruby = Erubis::Eruby.new(template)
+            output = eruby.evaluate(context)
+          rescue Object => e
+            raise TemplateError.new(e, template, context)
+          end
+          Tempfile.open("chef-rendered-template") do |tempfile|
+            tempfile.print(output)
+            tempfile.close
+            yield tempfile
+          end
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/deprecation/provider/cookbook_file.rb b/lib/chef/deprecation/provider/cookbook_file.rb
new file mode 100644
index 0000000..dfbf4a3
--- /dev/null
+++ b/lib/chef/deprecation/provider/cookbook_file.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+class Chef
+  module Deprecation
+    module Provider
+
+      # == Deprecation::Provider::CookbookFile
+      # This module contains the deprecated functions of
+      # Chef::Provider::CookbookFile. These functions are refactored to
+      # different components. They are frozen and will be removed in Chef 12.
+      #
+      module CookbookFile
+
+        def file_cache_location
+          @file_cache_location ||= begin
+            cookbook = run_context.cookbook_collection[resource_cookbook]
+            cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path)
+          end
+        end
+
+        def resource_cookbook
+          @new_resource.cookbook || @new_resource.cookbook_name
+        end
+
+        def content_stale?
+          ( ! ::File.exist?(@new_resource.path)) || ( ! compare_content)
+        end
+
+        def backup_new_resource
+          if ::File.exists?(@new_resource.path)
+            backup @new_resource.path
+          end
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/deprecation/provider/file.rb b/lib/chef/deprecation/provider/file.rb
new file mode 100644
index 0000000..0e91052
--- /dev/null
+++ b/lib/chef/deprecation/provider/file.rb
@@ -0,0 +1,197 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+class Chef
+  module Deprecation
+    module Provider
+
+      # == Deprecation::Provider::File
+      # This module contains the deprecated functions of
+      # Chef::Provider::File. These functions are refactored to different
+      # components. They are frozen and will be removed in Chef 12.
+      #
+      module File
+
+        def diff_current_from_content(new_content)
+          result = nil
+          Tempfile.open("chef-diff") do |file|
+            file.write new_content
+            file.close
+            result = diff_current file.path
+          end
+          result
+        end
+
+        def is_binary?(path)
+          ::File.open(path) do |file|
+
+            buff = file.read(Chef::Config[:diff_filesize_threshold])
+            buff = "" if buff.nil?
+            return buff !~ /^[\r[:print:]]*$/
+          end
+        end
+
+        def diff_current(temp_path)
+          suppress_resource_reporting = false
+
+          return [ "(diff output suppressed by config)" ] if Chef::Config[:diff_disabled]
+          return [ "(no temp file with new content, diff output suppressed)" ] unless ::File.exists?(temp_path)  # should never happen?
+
+          # solaris does not support diff -N, so create tempfile to diff against if we are creating a new file
+          target_path = if ::File.exists?(@current_resource.path)
+                          @current_resource.path
+                        else
+                          suppress_resource_reporting = true  # suppress big diffs going to resource reporting service
+                          tempfile = Tempfile.new('chef-tempfile')
+                          tempfile.path
+                        end
+
+          diff_filesize_threshold = Chef::Config[:diff_filesize_threshold]
+          diff_output_threshold = Chef::Config[:diff_output_threshold]
+
+          if ::File.size(target_path) > diff_filesize_threshold || ::File.size(temp_path) > diff_filesize_threshold
+            return [ "(file sizes exceed #{diff_filesize_threshold} bytes, diff output suppressed)" ]
+          end
+
+          # MacOSX(BSD?) diff will *sometimes* happily spit out nasty binary diffs
+          return [ "(current file is binary, diff output suppressed)"] if is_binary?(target_path)
+          return [ "(new content is binary, diff output suppressed)"] if is_binary?(temp_path)
+
+          begin
+            # -u: Unified diff format
+            result = shell_out("diff -u #{target_path} #{temp_path}" )
+          rescue Exception => e
+            # Should *not* receive this, but in some circumstances it seems that
+            # an exception can be thrown even using shell_out instead of shell_out!
+            return [ "Could not determine diff. Error: #{e.message}" ]
+          end
+
+          # diff will set a non-zero return code even when there's
+          # valid stdout results, if it encounters something unexpected
+          # So as long as we have output, we'll show it.
+          if not result.stdout.empty?
+            if result.stdout.length > diff_output_threshold
+              [ "(long diff of over #{diff_output_threshold} characters, diff output suppressed)" ]
+            else
+              val = result.stdout.split("\n")
+              val.delete("\\ No newline at end of file")
+              @new_resource.diff(val.join("\\n")) unless suppress_resource_reporting
+              val
+            end
+          elsif not result.stderr.empty?
+            [ "Could not determine diff. Error: #{result.stderr}" ]
+          else
+            [ "(no diff)" ]
+          end
+        end
+
+        def setup_acl
+          return if Chef::Platform.windows?
+          acl_scanner = ScanAccessControl.new(@new_resource, @current_resource)
+          acl_scanner.set_all!
+        end
+
+        def compare_content
+          checksum(@current_resource.path) == new_resource_content_checksum
+        end
+
+        def set_content
+          unless compare_content
+            description = []
+            description << "update content in file #{@new_resource.path} from #{short_cksum(@current_resource.checksum)} to #{short_cksum(new_resource_content_checksum)}"
+            description << diff_current_from_content(@new_resource.content)
+            converge_by(description) do
+              backup @new_resource.path if ::File.exists?(@new_resource.path)
+              ::File.open(@new_resource.path, "w") {|f| f.write @new_resource.content }
+              Chef::Log.info("#{@new_resource} contents updated")
+            end
+          end
+        end
+
+        def update_new_file_state(path=@new_resource.path)
+          if !::File.directory?(path)
+            @new_resource.checksum(checksum(path))
+          end
+
+          if Chef::Platform.windows?
+            # TODO: To work around CHEF-3554, add support for Windows
+            # equivalent, or implicit resource reporting won't work for
+            # Windows.
+            return
+          end
+
+          acl_scanner = ScanAccessControl.new(@new_resource, @new_resource)
+          acl_scanner.set_all!
+        end
+
+        def set_all_access_controls
+          if access_controls.requires_changes?
+            converge_by(access_controls.describe_changes) do
+              access_controls.set_all
+              #Update file state with new access values
+              update_new_file_state
+            end
+          end
+        end
+
+        def deploy_tempfile
+          Tempfile.open(::File.basename(@new_resource.name)) do |tempfile|
+            yield tempfile
+
+            temp_res = Chef::Resource::CookbookFile.new(@new_resource.name)
+            temp_res.path(tempfile.path)
+            ac = Chef::FileAccessControl.new(temp_res, @new_resource, self)
+            ac.set_all!
+            FileUtils.mv(tempfile.path, @new_resource.path)
+          end
+        end
+
+        def backup(file=nil)
+          file ||= @new_resource.path
+          if @new_resource.backup != false && @new_resource.backup > 0 && ::File.exist?(file)
+            time = Time.now
+            savetime = time.strftime("%Y%m%d%H%M%S")
+            backup_filename = "#{@new_resource.path}.chef-#{savetime}"
+            backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows
+            # if :file_backup_path is nil, we fallback to the old behavior of
+            # keeping the backup in the same directory. We also need to to_s it
+            # so we don't get a type error around implicit to_str conversions.
+            prefix = Chef::Config[:file_backup_path].to_s
+            backup_path = ::File.join(prefix, backup_filename)
+            FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path]
+            FileUtils.cp(file, backup_path, :preserve => true)
+            Chef::Log.info("#{@new_resource} backed up to #{backup_path}")
+
+            # Clean up after the number of backups
+            slice_number = @new_resource.backup
+            backup_files = Dir[::File.join(prefix, ".#{@new_resource.path}.chef-*")].sort { |a,b| b <=> a }
+            if backup_files.length >= @new_resource.backup
+              remainder = backup_files.slice(slice_number..-1)
+              remainder.each do |backup_to_delete|
+                FileUtils.rm(backup_to_delete)
+                Chef::Log.info("#{@new_resource} removed backup at #{backup_to_delete}")
+              end
+            end
+          end
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/deprecation/provider/remote_file.rb b/lib/chef/deprecation/provider/remote_file.rb
new file mode 100644
index 0000000..4452de6
--- /dev/null
+++ b/lib/chef/deprecation/provider/remote_file.rb
@@ -0,0 +1,86 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+  module Deprecation
+    module Provider
+
+      # == Deprecation::Provider::RemoteFile
+      # This module contains the deprecated functions of
+      # Chef::Provider::RemoteFile. These functions are refactored to different
+      # components. They are frozen and will be removed in Chef 12.
+      #
+      module RemoteFile
+
+        def current_resource_matches_target_checksum?
+          @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{Regexp.escape(@new_resource.checksum)}/
+        end
+
+        def matches_current_checksum?(candidate_file)
+          Chef::Log.debug "#{@new_resource} checking for file existence of #{@new_resource.path}"
+          if ::File.exists?(@new_resource.path)
+            Chef::Log.debug "#{@new_resource} file exists at #{@new_resource.path}"
+            @new_resource.checksum(checksum(candidate_file.path))
+            Chef::Log.debug "#{@new_resource} target checksum: #{@current_resource.checksum}"
+            Chef::Log.debug "#{@new_resource} source checksum: #{@new_resource.checksum}"
+
+            @new_resource.checksum == @current_resource.checksum
+          else
+            Chef::Log.debug "#{@new_resource} creating #{@new_resource.path}"
+            false
+          end
+        end
+
+        def backup_new_resource
+          if ::File.exists?(@new_resource.path)
+            Chef::Log.debug "#{@new_resource} checksum changed from #{@current_resource.checksum} to #{@new_resource.checksum}"
+            backup @new_resource.path
+          end
+        end
+
+        def source_file(source, current_checksum, &block)
+          if absolute_uri?(source)
+            fetch_from_uri(source, &block)
+          elsif !Chef::Config[:solo]
+            fetch_from_chef_server(source, current_checksum, &block)
+          else
+            fetch_from_local_cookbook(source, &block)
+          end
+        end
+
+        def http_client_opts(source)
+          opts={}
+          # CHEF-3140
+          # 1. If it's already compressed, trying to compress it more will
+          # probably be counter-productive.
+          # 2. Some servers are misconfigured so that you GET $URL/file.tgz but
+          # they respond with content type of tar and content encoding of gzip,
+          # which tricks Chef::REST into decompressing the response body. In this
+          # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
+          # which is not what you wanted.
+          if @new_resource.path =~ /gz$/ or source =~ /gz$/
+            opts[:disable_gzip] = true
+          end
+          opts
+        end
+
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/deprecation/provider/template.rb b/lib/chef/deprecation/provider/template.rb
new file mode 100644
index 0000000..d7a228e
--- /dev/null
+++ b/lib/chef/deprecation/provider/template.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/deprecation/mixin/template'
+
+class Chef
+  module Deprecation
+    module Provider
+
+      # == Deprecation::Provider::Template
+      # This module contains the deprecated functions of
+      # Chef::Provider::Template. These functions are refactored to different
+      # components. They are frozen and will be removed in Chef 12.
+      #
+      module Template
+
+        include Chef::Deprecation::Mixin::Template
+
+        def template_finder
+          @template_finder ||= begin
+            Chef::Provider::TemplateFinder.new(run_context, cookbook_name, node)
+          end
+        end
+
+        def template_location
+          @template_file_cache_location ||= begin
+            template_finder.find(@new_resource.source, :local => @new_resource.local, :cookbook => @new_resource.cookbook)
+          end
+        end
+
+        def resource_cookbook
+          @new_resource.cookbook || @new_resource.cookbook_name
+        end
+
+        def rendered(rendered_template)
+          @new_resource.checksum(checksum(rendered_template.path))
+          Chef::Log.debug("Current content's checksum:  #{@current_resource.checksum}")
+          Chef::Log.debug("Rendered content's checksum: #{@new_resource.checksum}")
+        end
+
+        def content_matches?
+          @current_resource.checksum == @new_resource.checksum
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb
new file mode 100644
index 0000000..22b28f9
--- /dev/null
+++ b/lib/chef/deprecation/warnings.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+  module Deprecation
+    module Warnings
+
+      def add_deprecation_warnings_for(method_names)
+        method_names.each do |name|
+          m = instance_method(name)
+          define_method(name) do |*args|
+            Chef::Log.warn "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12."
+            Chef::Log.warn "Please update your cookbooks accordingly. Accessed from:"
+            caller[0..3].each {|l| Chef::Log.warn l}
+            super(*args)
+          end
+        end
+      end
+
+    end
+  end
+end
+
diff --git a/lib/chef/digester.rb b/lib/chef/digester.rb
new file mode 100644
index 0000000..669ff8b
--- /dev/null
+++ b/lib/chef/digester.rb
@@ -0,0 +1,73 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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 'digest'
+
+class Chef
+  class Digester
+
+    def self.instance
+      @instance ||= new
+    end
+
+    def self.checksum_for_file(*args)
+      instance.checksum_for_file(*args)
+    end
+
+    def validate_checksum(*args)
+      self.class.validate_checksum(*args)
+    end
+
+    def checksum_for_file(file)
+      generate_checksum(file)
+    end
+
+    def generate_checksum(file)
+      checksum_file(file, Digest::SHA256.new)
+    end
+
+    def self.generate_md5_checksum_for_file(*args)
+      instance.generate_md5_checksum_for_file(*args)
+    end
+
+    def generate_md5_checksum_for_file(file)
+      checksum_file(file, Digest::MD5.new)
+    end
+
+    def generate_md5_checksum(io)
+      checksum_io(io, Digest::MD5.new)
+    end
+
+    private
+
+    def checksum_file(file, digest)
+      File.open(file, 'rb') { |f| checksum_io(f, digest) }
+    end
+
+    def checksum_io(io, digest)
+      while chunk = io.read(1024 * 8)
+        digest.update(chunk)
+      end
+      digest.hexdigest
+    end
+
+  end
+end
+
diff --git a/lib/chef/dsl.rb b/lib/chef/dsl.rb
new file mode 100644
index 0000000..7717d99
--- /dev/null
+++ b/lib/chef/dsl.rb
@@ -0,0 +1,6 @@
+require 'chef/dsl/recipe'
+require 'chef/dsl/platform_introspection'
+require 'chef/dsl/data_query'
+require 'chef/dsl/include_recipe'
+require 'chef/dsl/include_attribute'
+require 'chef/dsl/registry_helper'
diff --git a/lib/chef/dsl/data_query.rb b/lib/chef/dsl/data_query.rb
new file mode 100644
index 0000000..65e7b18
--- /dev/null
+++ b/lib/chef/dsl/data_query.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 'chef/search/query'
+require 'chef/data_bag'
+require 'chef/data_bag_item'
+require 'chef/encrypted_data_bag_item'
+
+class Chef
+  module DSL
+
+    # ==Chef::DSL::DataQuery
+    # Provides DSL for querying data from the chef-server via search or data
+    # bag.
+    module DataQuery
+
+      def search(*args, &block)
+        # If you pass a block, or have at least the start argument, do raw result parsing
+        #
+        # Otherwise, do the iteration for the end user
+        if Kernel.block_given? || args.length >= 4
+          Chef::Search::Query.new.search(*args, &block)
+        else
+          results = Array.new
+          Chef::Search::Query.new.search(*args) do |o|
+            results << o
+          end
+          results
+        end
+      end
+
+      def data_bag(bag)
+        DataBag.validate_name!(bag.to_s)
+        rbag = DataBag.load(bag)
+        rbag.keys
+      rescue Exception
+        Log.error("Failed to list data bag items in data bag: #{bag.inspect}")
+        raise
+      end
+
+      def data_bag_item(bag, item)
+        DataBag.validate_name!(bag.to_s)
+        DataBagItem.validate_id!(item)
+        DataBagItem.load(bag, item)
+      rescue Exception
+        Log.error("Failed to load data bag item: #{bag.inspect} #{item.inspect}")
+        raise
+      end
+    end
+  end
+end
+
+# **DEPRECATED**
+# This used to be part of chef/mixin/language. Load the file to activate the deprecation code.
+require 'chef/mixin/language'
+
diff --git a/lib/chef/dsl/include_attribute.rb b/lib/chef/dsl/include_attribute.rb
new file mode 100644
index 0000000..3a95ce7
--- /dev/null
+++ b/lib/chef/dsl/include_attribute.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 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/log'
+
+class Chef
+  module DSL
+    module IncludeAttribute
+
+      # Loads the attribute file specified by the short name of the
+      # file, e.g., loads specified cookbook's
+      #   "attributes/mailservers.rb"
+      # if passed
+      #   "mailservers"
+      def include_attribute(*attr_file_specs)
+        attr_file_specs.flatten.each do |attr_file_spec|
+          cookbook_name, attr_file = parse_attribute_file_spec(attr_file_spec)
+          if run_context.loaded_fully_qualified_attribute?(cookbook_name, attr_file)
+            Chef::Log.debug("I am not loading attribute file #{cookbook_name}::#{attr_file}, because I have already seen it.")
+          else
+            Chef::Log.debug("Loading Attribute #{cookbook_name}::#{attr_file}")
+            run_context.loaded_attribute(cookbook_name, attr_file)
+            attr_file_path = run_context.resolve_attribute(cookbook_name, attr_file)
+            node.from_file(attr_file_path)
+          end
+        end
+        true
+      end
+
+      # Takes a attribute file specification, like "apache2" or "mysql::server"
+      # and converts it to a 2 element array of [cookbook_name, attribute_file_name]
+      def parse_attribute_file_spec(file_spec)
+        if match = file_spec.match(/(.+?)::(.+)/)
+          [match[1], match[2]]
+        else
+          [file_spec, "default"]
+        end
+      end
+
+    end
+  end
+end
+
+# **DEPRECATED**
+# This used to be part of chef/mixin/language_include_attribute. Load the file to activate the deprecation code.
+require 'chef/mixin/language_include_attribute'
+
+
diff --git a/lib/chef/dsl/include_recipe.rb b/lib/chef/dsl/include_recipe.rb
new file mode 100644
index 0000000..fc95e38
--- /dev/null
+++ b/lib/chef/dsl/include_recipe.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 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/log'
+
+class Chef
+  module DSL
+    module IncludeRecipe
+
+      def include_recipe(*recipe_names)
+        run_context.include_recipe(*recipe_names)
+      end
+
+      def load_recipe(recipe_name)
+        run_context.load_recipe(recipe_name)
+      end
+
+      def require_recipe(*args)
+        Chef::Log.warn("require_recipe is deprecated and will be removed in a future release, please use include_recipe")
+        include_recipe(*args)
+      end
+
+    end
+  end
+end
+
+# **DEPRECATED**
+# This used to be part of chef/mixin/language_include_recipe. Load the file to activate the deprecation code.
+require 'chef/mixin/language_include_recipe'
+
diff --git a/lib/chef/dsl/platform_introspection.rb b/lib/chef/dsl/platform_introspection.rb
new file mode 100644
index 0000000..33aa451
--- /dev/null
+++ b/lib/chef/dsl/platform_introspection.rb
@@ -0,0 +1,218 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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.
+#
+
+class Chef
+  module DSL
+
+    # == Chef::DSL::PlatformIntrospection
+    # Provides the DSL for platform-dependent switch logic, such as
+    # #value_for_platform.
+    module PlatformIntrospection
+
+      # Implementation class for determining platform dependent values
+      class PlatformDependentValue
+
+        # Create a platform dependent value object.
+        # === Arguments
+        # platform_hash (Hash) a hash of the same structure as Chef::Platform,
+        # like this:
+        #   {
+        #     :debian => {:default => 'the value for all debian'}
+        #     [:centos, :redhat, :fedora] => {:default => "value for all EL variants"}
+        #     :ubuntu => { :default => "default for ubuntu", '10.04' => "value for 10.04 only"},
+        #     :default => "the default when nothing else matches"
+        #   }
+        # * platforms can be specified as Symbols or Strings
+        # * multiple platforms can be grouped by using an Array as the key
+        # * values for platforms need to be Hashes of the form:
+        #   {platform_version => value_for_that_version}
+        # * the exception to the above is the default value, which is given as
+        #   :default => default_value
+        def initialize(platform_hash)
+          @values = {}
+          platform_hash.each { |platforms, value| set(platforms, value)}
+        end
+
+        def value_for_node(node)
+          platform, version = node[:platform].to_s, node[:platform_version].to_s
+          if @values.key?(platform) && @values[platform].key?(version)
+            @values[platform][version]
+          elsif @values.key?(platform) && @values[platform].key?("default")
+            @values[platform]["default"]
+          elsif @values.key?("default")
+            @values["default"]
+          else
+            nil
+          end
+        end
+
+        private
+
+        def set(platforms, value)
+          if platforms.to_s == 'default'
+            @values["default"] = value
+          else
+            assert_valid_platform_values!(platforms, value)
+            Array(platforms).each { |platform| @values[platform.to_s] = normalize_keys(value)}
+            value
+          end
+        end
+
+        def normalize_keys(hash)
+          hash.inject({}) do |h, key_value|
+            keys, value = *key_value
+            Array(keys).each do |key|
+              h[key.to_s] = value
+            end
+            h
+          end
+        end
+
+        def assert_valid_platform_values!(platforms, value)
+          unless value.kind_of?(Hash)
+            msg = "platform dependent values must be specified in the format :platform => {:version => value} "
+            msg << "you gave a value #{value.inspect} for platform(s) #{platforms}"
+            raise ArgumentError, msg
+          end
+        end
+      end
+
+
+
+      # Given a hash similar to the one we use for Platforms, select a value from the hash.  Supports
+      # per platform defaults, along with a single base default. Arrays may be passed as hash keys and
+      # will be expanded.
+      #
+      # === Parameters
+      # platform_hash:: A platform-style hash.
+      #
+      # === Returns
+      # value:: Whatever the most specific value of the hash is.
+      def value_for_platform(platform_hash)
+        PlatformDependentValue.new(platform_hash).value_for_node(node)
+      end
+
+      # Given a list of platforms, returns true if the current recipe is being run on a node with
+      # that platform, false otherwise.
+      #
+      # === Parameters
+      # args:: A list of platforms. Each platform can be in string or symbol format.
+      #
+      # === Returns
+      # true:: If the current platform is in the list
+      # false:: If the current platform is not in the list
+      def platform?(*args)
+        has_platform = false
+
+        args.flatten.each do |platform|
+          has_platform = true if platform.to_s == node[:platform]
+        end
+
+        has_platform
+      end
+
+
+
+     # Implementation class for determining platform family dependent values
+      class PlatformFamilyDependentValue
+
+        # Create a platform family dependent value object.
+        # === Arguments
+        # platform_family_hash (Hash) a map of platform families to values.
+        # like this:
+        #   {
+        #     :rhel => "value for all EL variants"
+        #     :fedora =>  "value for fedora variants fedora and amazon" ,
+        #     [:fedora, :rhel] => "value for all known redhat variants"
+        #     :debian =>  "value for debian variants including debian, ubuntu, mint" ,
+        #     :default => "the default when nothing else matches"
+        #   }
+        # * platform families can be specified as Symbols or Strings
+        # * multiple platform families can be grouped by using an Array as the key
+        # * values for platform families can be any object, with no restrictions. Some examples:
+        #   - [:stop, :start]
+        #   - "mysql-devel"
+        #   - { :key => "value" }
+        def initialize(platform_family_hash)
+          @values = {}
+          @values["default"] = nil
+          platform_family_hash.each { |platform_families, value| set(platform_families, value)}
+        end
+
+        def value_for_node(node)
+          if node.key?(:platform_family)
+            platform_family = node[:platform_family].to_s
+            if @values.key?(platform_family)
+              @values[platform_family]
+            else
+              @values["default"]
+            end
+          else
+            @values["default"]
+          end
+        end
+
+        private
+
+        def set(platform_family, value)
+          if platform_family.to_s == 'default'
+            @values["default"] = value
+          else
+            Array(platform_family).each { |family| @values[family.to_s] = value }
+            value
+          end
+        end
+      end
+
+
+      # Given a hash mapping platform families to values, select a value from the hash. Supports a single
+      # base default if platform family is not in the map. Arrays may be passed as hash keys and will be
+      # expanded
+      #
+      # === Parameters
+      # platform_family_hash:: A hash in the form { platform_family_name => value }
+      #
+      # === Returns
+      # value:: Whatever the most specific value of the hash is.
+      def value_for_platform_family(platform_family_hash)
+        PlatformFamilyDependentValue.new(platform_family_hash).value_for_node(node)
+      end
+
+      # Given a list of platform families, returns true if the current recipe is being run on a
+      # node within that platform family, false otherwise.
+      #
+      # === Parameters
+      # args:: A list of platform families. Each platform family can be in string or symbol format.
+      #
+      # === Returns
+      # true:: if the current node platform family is in the list.
+      # false:: if the current node platform family is not in the list.
+      def platform_family?(*args)
+        args.flatten.any? do |platform_family|
+          platform_family.to_s == node[:platform_family]
+        end
+      end
+
+    end
+  end
+end
+
+# **DEPRECATED**
+# This used to be part of chef/mixin/language. Load the file to activate the deprecation code.
+require 'chef/mixin/language'
+
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
new file mode 100644
index 0000000..f3507a8
--- /dev/null
+++ b/lib/chef/dsl/recipe.rb
@@ -0,0 +1,87 @@
+#--
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 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/resource'
+require 'chef/resource_platform_map'
+require 'chef/mixin/convert_to_class_name'
+
+class Chef
+  module DSL
+
+    # == Chef::DSL::Recipe
+    # Provides the primary recipe DSL functionality for defining Chef resource
+    # objects via method calls.
+    module Recipe
+
+      include Chef::Mixin::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 run_context.definitions.has_key?(method_symbol)
+          # This dupes the high level object, but we still need to dup the params
+          new_def = run_context.definitions[method_symbol].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)
+        else
+          # Otherwise, we're rocking the regular resource call route.
+
+          # Checks the new platform => short_name => resource mapping initially
+          # then fall back to the older approach (Chef::Resource.const_get) for
+          # backward compatibility
+          resource_class = Chef::Resource.resource_for_node(method_symbol, run_context.node)
+
+          super unless resource_class
+          raise ArgumentError, "You must supply a name when declaring a #{method_symbol} resource" unless args.size > 0
+
+          # If we have a resource like this one, we want to steal its state
+          args << run_context
+          resource = resource_class.new(*args)
+          resource.source_line = caller[0]
+          resource.load_prior_resource
+          resource.cookbook_name = cookbook_name
+          resource.recipe_name = @recipe_name
+          resource.params = @params
+          # Determine whether this resource is being created in the context of an enclosing Provider
+          resource.enclosing_provider = self.is_a?(Chef::Provider) ? self : nil
+          # Evaluate resource attribute DSL
+          resource.instance_eval(&block) if block
+
+          # Run optional resource hook
+          resource.after_created
+
+          run_context.resource_collection.insert(resource)
+          resource
+        end
+      end
+
+    end
+  end
+end
+
+# **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/registry_helper.rb b/lib/chef/dsl/registry_helper.rb
new file mode 100644
index 0000000..4dcd2f1
--- /dev/null
+++ b/lib/chef/dsl/registry_helper.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Lamont Granquist (<lamont 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.
+#
+
+#
+# Helper functions to access the windows registry from within recipes and
+# the not_if/only_if blocks in resources.  This only exposes the methods
+# in the chef/win32/registry class which are reasonably side-effect-free.
+# The actual modification of the registry should be done via the registry_key
+# resource in a more idempotent way.
+#
+#
+class Chef
+  module DSL
+    module RegistryHelper
+      # the registry instance is cheap to build and throwing it away ensures we
+      # don't carry any state (e.g. magic 32-bit/64-bit settings) between calls
+      def registry_key_exists?(key_path, architecture = :machine)
+        registry = Chef::Win32::Registry.new(run_context, architecture)
+        registry.key_exists?(key_path)
+      end
+      def registry_get_values(key_path, architecture = :machine)
+        registry = Chef::Win32::Registry.new(run_context, architecture)
+        registry.get_values(key_path)
+      end
+      def registry_has_subkeys?(key_path, architecture = :machine)
+        registry = Chef::Win32::Registry.new(run_context, architecture)
+        registry.has_subkeys?(key_path)
+      end
+      def registry_get_subkeys(key_path, architecture = :machine)
+        registry = Chef::Win32::Registry.new(run_context, architecture)
+        registry.get_subkeys(key_path)
+      end
+      def registry_value_exists?(key_path, value, architecture = :machine)
+        registry = Chef::Win32::Registry.new(run_context, architecture)
+        registry.value_exists?(key_path, value)
+      end
+      def registry_data_exists?(key_path, value, architecture = :machine)
+        registry = Chef::Win32::Registry.new(run_context, architecture)
+        registry.data_exists?(key_path, value)
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/encrypted_data_bag_item.rb b/lib/chef/encrypted_data_bag_item.rb
index 048ab8d..452a822 100644
--- a/lib/chef/encrypted_data_bag_item.rb
+++ b/lib/chef/encrypted_data_bag_item.rb
@@ -20,6 +20,7 @@ require 'base64'
 require 'openssl'
 require 'chef/data_bag_item'
 require 'yaml'
+require 'yajl'
 require 'open-uri'
 
 # An EncryptedDataBagItem represents a read-only data bag item where
@@ -47,9 +48,307 @@ require 'open-uri'
 # such nodes in the infrastructure.
 #
 class Chef::EncryptedDataBagItem
-  DEFAULT_SECRET_FILE = "/etc/chef/encrypted_data_bag_secret"
   ALGORITHM = 'aes-256-cbc'
 
+  class UnacceptableEncryptedDataBagItemFormat < StandardError
+  end
+
+  class UnsupportedEncryptedDataBagItemFormat < StandardError
+  end
+
+  class DecryptionFailure < StandardError
+  end
+
+  class UnsupportedCipher < StandardError
+  end
+
+  # Implementation class for converting plaintext data bag item values to an
+  # encrypted value, including any necessary wrappers and metadata.
+  module Encryptor
+
+    # "factory" method that creates an encryptor object with the proper class
+    # for the desired encrypted data bag format version.
+    #
+    # +Chef::Config[:data_bag_encrypt_version]+ determines which version is used.
+    def self.new(value, secret, iv=nil)
+      format_version = Chef::Config[:data_bag_encrypt_version]
+      case format_version
+      when 1
+        Version1Encryptor.new(value, secret, iv)
+      when 2
+        Version2Encryptor.new(value, secret, iv)
+      else
+        raise UnsupportedEncryptedDataBagItemFormat,
+          "Invalid encrypted data bag format version `#{format_version}'. Supported versions are '1', '2'"
+      end
+    end
+
+    class Version1Encryptor
+      attr_reader :key
+      attr_reader :plaintext_data
+
+      # Create a new Encryptor for +data+, which will be encrypted with the given
+      # +key+.
+      #
+      # === Arguments:
+      # * data: An object of any type that can be serialized to json
+      # * key: A String representing the desired passphrase
+      # * iv: The optional +iv+ parameter is intended for testing use only. When
+      # *not* supplied, Encryptor will use OpenSSL to generate a secure random
+      # IV, which is what you want.
+      def initialize(plaintext_data, key, iv=nil)
+        @plaintext_data = plaintext_data
+        @key = key
+        @iv = iv && Base64.decode64(iv)
+      end
+
+      # Returns a wrapped and encrypted version of +plaintext_data+ suitable for
+      # using as the value in an encrypted data bag item.
+      def for_encrypted_item
+        {
+          "encrypted_data" => encrypted_data,
+          "iv" => Base64.encode64(iv),
+          "version" => 1,
+          "cipher" => ALGORITHM
+        }
+      end
+
+      # Generates or returns the IV.
+      def iv
+        # Generated IV comes from OpenSSL::Cipher::Cipher#random_iv
+        # This gets generated when +openssl_encryptor+ gets created.
+        openssl_encryptor if @iv.nil?
+        @iv
+      end
+
+      # Generates (and memoizes) an OpenSSL::Cipher::Cipher object and configures
+      # it for the specified iv and encryption key.
+      def openssl_encryptor
+        @openssl_encryptor ||= begin
+          encryptor = OpenSSL::Cipher::Cipher.new(ALGORITHM)
+          encryptor.encrypt
+          @iv ||= encryptor.random_iv
+          encryptor.iv = @iv
+          encryptor.key = Digest::SHA256.digest(key)
+          encryptor
+        end
+      end
+
+      # Encrypts and Base64 encodes +serialized_data+
+      def encrypted_data
+        @encrypted_data ||= begin
+          enc_data = openssl_encryptor.update(serialized_data)
+          enc_data << openssl_encryptor.final
+          Base64.encode64(enc_data)
+        end
+      end
+
+      # Wraps the data in a single key Hash (JSON Object) and converts to JSON.
+      # The wrapper is required because we accept values (such as Integers or
+      # Strings) that do not produce valid JSON when serialized without the
+      # wrapper.
+      def serialized_data
+        Yajl::Encoder.encode(:json_wrapper => plaintext_data)
+      end
+    end
+
+    class Version2Encryptor < Version1Encryptor
+
+      # Returns a wrapped and encrypted version of +plaintext_data+ suitable for
+      # using as the value in an encrypted data bag item.
+      def for_encrypted_item
+        {
+          "encrypted_data" => encrypted_data,
+          "hmac" => hmac,
+          "iv" => Base64.encode64(iv),
+          "version" => 2,
+          "cipher" => ALGORITHM
+        }
+      end
+
+      # Generates an HMAC-SHA2-256 of the encrypted data (encrypt-then-mac)
+      def hmac
+        @hmac ||= begin
+          digest = OpenSSL::Digest::Digest.new("sha256")
+          raw_hmac = OpenSSL::HMAC.digest(digest, key, encrypted_data)
+          Base64.encode64(raw_hmac)
+        end
+      end
+
+    end
+  end
+
+  #=== Decryptor
+  # For backwards compatibility, Chef implements decryption/deserialization for
+  # older encrypted data bag item formats in addition to the current version.
+  # Each decryption/deserialization strategy is implemented as a class in this
+  # namespace. For convenience the factory method +Decryptor.for()+ can be used
+  # to create an instance of the appropriate strategy for the given encrypted
+  # data bag value.
+  module Decryptor
+
+    # Detects the encrypted data bag item format version and instantiates a
+    # decryptor object for that version. Call #for_decrypted_item on the
+    # resulting object to decrypt and deserialize it.
+    def self.for(encrypted_value, key)
+      format_version = format_version_of(encrypted_value)
+      assert_format_version_acceptable!(format_version)
+      case format_version
+      when 2
+        Version2Decryptor.new(encrypted_value, key)
+      when 1
+        Version1Decryptor.new(encrypted_value, key)
+      when 0
+        Version0Decryptor.new(encrypted_value, key)
+      else
+        raise UnsupportedEncryptedDataBagItemFormat,
+          "This version of chef does not support encrypted data bag item format version '#{format_version}'"
+      end
+    end
+
+    def self.format_version_of(encrypted_value)
+      if encrypted_value.respond_to?(:key?)
+        encrypted_value["version"]
+      else
+        0
+      end
+    end
+
+    def self.assert_format_version_acceptable!(format_version)
+      unless format_version.kind_of?(Integer) and format_version >= Chef::Config[:data_bag_decrypt_minimum_version]
+        raise UnacceptableEncryptedDataBagItemFormat,
+          "The encrypted data bag item has format version `#{format_version}', " +
+          "but the config setting 'data_bag_decrypt_minimum_version' requires version `#{Chef::Config[:data_bag_decrypt_minimum_version]}'"
+      end
+    end
+
+    class Version1Decryptor
+
+      attr_reader :encrypted_data
+      attr_reader :key
+
+      def initialize(encrypted_data, key)
+        @encrypted_data = encrypted_data
+        @key = key
+      end
+
+      def for_decrypted_item
+        Yajl::Parser.parse(decrypted_data)["json_wrapper"]
+      rescue Yajl::ParseError
+        # convert to a DecryptionFailure error because the most likely scenario
+        # here is that the decryption step was unsuccessful but returned bad
+        # data rather than raising an error.
+        raise DecryptionFailure, "Error decrypting data bag value. Most likely the provided key is incorrect"
+      end
+
+      def encrypted_bytes
+        Base64.decode64(@encrypted_data["encrypted_data"])
+      end
+
+      def iv
+        Base64.decode64(@encrypted_data["iv"])
+      end
+
+      def decrypted_data
+        @decrypted_data ||= begin
+          plaintext = openssl_decryptor.update(encrypted_bytes)
+          plaintext << openssl_decryptor.final
+        rescue OpenSSL::Cipher::CipherError => e
+          raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect"
+        end
+      end
+
+      def openssl_decryptor
+        @openssl_decryptor ||= begin
+          assert_valid_cipher!
+          d = OpenSSL::Cipher::Cipher.new(ALGORITHM)
+          d.decrypt
+          d.key = Digest::SHA256.digest(key)
+          d.iv = iv
+          d
+        end
+      end
+
+      def assert_valid_cipher!
+        # In the future, chef may support configurable ciphers. For now, only
+        # aes-256-cbc is supported.
+        requested_cipher = @encrypted_data["cipher"]
+        unless requested_cipher == ALGORITHM
+          raise UnsupportedCipher,
+            "Cipher '#{requested_cipher}' is not supported by this version of Chef. Available ciphers: ['#{ALGORITHM}']"
+        end
+      end
+
+    end
+
+    class Version2Decryptor < Version1Decryptor
+
+      def decrypted_data
+        validate_hmac! unless @decrypted_data
+        super
+      end
+
+      def validate_hmac!
+        digest = OpenSSL::Digest::Digest.new("sha256")
+        raw_hmac = OpenSSL::HMAC.digest(digest, key, @encrypted_data["encrypted_data"])
+
+        if candidate_hmac_matches?(raw_hmac)
+          true
+        else
+          raise DecryptionFailure, "Error decrypting data bag value: invalid hmac. Most likely the provided key is incorrect"
+        end
+      end
+
+      private
+
+      def candidate_hmac_matches?(expected_hmac)
+        return false unless @encrypted_data["hmac"]
+        expected_bytes = expected_hmac.bytes.to_a
+        candidate_hmac_bytes = Base64.decode64(@encrypted_data["hmac"]).bytes.to_a
+        valid = expected_bytes.size ^ candidate_hmac_bytes.size
+        expected_bytes.zip(candidate_hmac_bytes) { |x, y| valid |= x ^ y.to_i }
+        valid == 0
+      end
+    end
+
+    class Version0Decryptor
+
+      attr_reader :encrypted_data
+      attr_reader :key
+
+      def initialize(encrypted_data, key)
+        @encrypted_data = encrypted_data
+        @key = key
+      end
+
+      def for_decrypted_item
+        YAML.load(decrypted_data)
+      end
+
+      def decrypted_data
+        @decrypted_data ||= begin
+          plaintext = openssl_decryptor.update(encrypted_bytes)
+          plaintext << openssl_decryptor.final
+        rescue OpenSSL::Cipher::CipherError => e
+          raise DecryptionFailure, "Error decrypting data bag value: '#{e.message}'. Most likely the provided key is incorrect"
+        end
+      end
+
+      def encrypted_bytes
+        Base64.decode64(@encrypted_data)
+      end
+
+      def openssl_decryptor
+        @openssl_decryptor ||= begin
+          d = OpenSSL::Cipher::Cipher.new(ALGORITHM)
+          d.decrypt
+          d.pkcs5_keyivgen(key)
+          d
+        end
+      end
+    end
+  end
+
   def initialize(enc_hash, secret)
     @enc_hash = enc_hash
     @secret = secret
@@ -60,7 +359,7 @@ class Chef::EncryptedDataBagItem
     if key == "id" || value.nil?
       value
     else
-      self.class.decrypt_value(value, @secret)
+      Decryptor.for(value, @secret).for_decrypted_item
     end
   end
 
@@ -72,14 +371,10 @@ class Chef::EncryptedDataBagItem
     @enc_hash.keys.inject({}) { |hash, key| hash[key] = self[key]; hash }
   end
 
-  def self.from_plain_hash(plain_hash, secret)
-    self.new(self.encrypt_data_bag_item(plain_hash, secret), secret)
-  end
-
   def self.encrypt_data_bag_item(plain_hash, secret)
     plain_hash.inject({}) do |h, (key, val)|
       h[key] = if key != "id"
-                 self.encrypt_value(val, secret)
+                 Encryptor.new(val, secret).for_encrypted_item
                else
                  val
                end
@@ -88,22 +383,13 @@ class Chef::EncryptedDataBagItem
   end
 
   def self.load(data_bag, name, secret = nil)
-    path = "data/#{data_bag}/#{name}"
     raw_hash = Chef::DataBagItem.load(data_bag, name)
     secret = secret || self.load_secret
     self.new(raw_hash, secret)
   end
 
-  def self.encrypt_value(value, key)
-    Base64.encode64(self.cipher(:encrypt, value.to_yaml, key))
-  end
-
-  def self.decrypt_value(value, key)
-    YAML.load(self.cipher(:decrypt, Base64.decode64(value), key))
-  end
-
   def self.load_secret(path=nil)
-    path = path || Chef::Config[:encrypted_data_bag_secret] || DEFAULT_SECRET_FILE
+    path ||= Chef::Config[:encrypted_data_bag_secret]
     secret = case path
              when /^\w+:\/\//
                # We have a remote key
@@ -115,7 +401,7 @@ class Chef::EncryptedDataBagItem
                  raise ArgumentError, "Remote key not found at '#{path}'"
                end
              else
-               if !File.exists?(path)
+               if !File.exist?(path)
                  raise Errno::ENOENT, "file not found '#{path}'"
                end
                IO.read(path).strip
@@ -126,14 +412,4 @@ class Chef::EncryptedDataBagItem
     secret
   end
 
-  protected
-
-  def self.cipher(direction, data, key)
-    cipher = OpenSSL::Cipher::Cipher.new(ALGORITHM)
-    cipher.send(direction)
-    cipher.pkcs5_keyivgen(key)
-    ans = cipher.update(data)
-    ans << cipher.final
-    ans
-  end
 end
diff --git a/lib/chef/environment.rb b/lib/chef/environment.rb
index 8cf80f1..5c719ca 100644
--- a/lib/chef/environment.rb
+++ b/lib/chef/environment.rb
@@ -2,6 +2,7 @@
 # Author:: Stephen Delano (<stephen at opscode.com>)
 # Author:: Seth Falcon (<seth at opscode.com>)
 # Author:: John Keiser (<jkeiser at ospcode.com>)
+# Author:: Kyle Goodwin (<kgoodwin at primerevenue.com>)
 # Copyright:: Copyright 2010-2011 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -22,8 +23,6 @@ require 'chef/config'
 require 'chef/mash'
 require 'chef/mixin/params_validate'
 require 'chef/mixin/from_file'
-require 'chef/couchdb'
-require 'chef/index_queue'
 require 'chef/version_constraint'
 
 class Chef
@@ -33,52 +32,15 @@ class Chef
 
     include Chef::Mixin::ParamsValidate
     include Chef::Mixin::FromFile
-    include Chef::IndexQueue::Indexable
 
     COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:[\s]+)((?:#{Chef::VersionConstraint::OPS.join('|')})(?:[\s]+).+)$/.freeze
 
-    attr_accessor :couchdb, :couchdb_rev
-    attr_reader :couchdb_id
-
-    DESIGN_DOCUMENT = {
-      "version" => 1,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "environment") {
-              emit(doc.name, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "environment") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        }
-      }
-    }
-
-    def initialize(couchdb=nil)
+    def initialize
       @name = ''
       @description = ''
       @default_attributes = Mash.new
       @override_attributes = Mash.new
       @cookbook_versions = Hash.new
-      @couchdb_rev = nil
-      @couchdb_id = nil
-      @couchdb = couchdb || Chef::CouchDB.new
-    end
-
-    def couchdb_id=(value)
-      @couchdb_id = value
-      self.index_id = value
     end
 
     def chef_server_rest
@@ -113,6 +75,10 @@ class Chef
       )
     end
 
+    def default_attributes=(attrs)
+      default_attributes(attrs)
+    end
+
     def override_attributes(arg=nil)
       set_or_return(
         :override_attributes,
@@ -121,6 +87,10 @@ class Chef
       )
     end
 
+    def override_attributes=(attrs)
+      override_attributes(attrs)
+    end
+
     def cookbook_versions(arg=nil)
       set_or_return(
         :cookbook_versions,
@@ -155,7 +125,6 @@ class Chef
         "default_attributes" => @default_attributes,
         "override_attributes" => @override_attributes
       }
-      result["_rev"] = couchdb_rev if couchdb_rev
       result
     end
 
@@ -252,54 +221,54 @@ class Chef
       environment.cookbook_versions(o["cookbook_versions"])
       environment.default_attributes(o["default_attributes"])
       environment.override_attributes(o["override_attributes"])
-      environment.couchdb_rev = o["_rev"] if o.has_key?("_rev")
-      environment.couchdb_id = o["_id"] if o.has_key?("_id")
       environment
     end
 
-    def self.cdb_list(inflate=false, couchdb=nil)
-      es = (couchdb || Chef::CouchDB.new).list("environments", inflate)
-      lookup = (inflate ? "value" : "key")
-      es["rows"].collect { |e| e[lookup] }
-    end
-
     def self.list(inflate=false)
       if inflate
-        # TODO: index the environments and use search to inflate - don't inflate for now :(
-        chef_server_rest.get_rest("environments")
+        response = Hash.new
+        Chef::Search::Query.new.search(:environment) do |e|
+          response[e.name] = e unless e.nil?
+        end
+        response
       else
         chef_server_rest.get_rest("environments")
       end
     end
 
-    def self.cdb_load(name, couchdb=nil)
-      (couchdb || Chef::CouchDB.new).load("environment", name)
-    end
-
     def self.load(name)
-      chef_server_rest.get_rest("environments/#{name}")
+      if Chef::Config[:solo]
+        load_from_file(name)
+      else
+        chef_server_rest.get_rest("environments/#{name}")
+      end
     end
 
-    def self.exists?(name, couchdb)
-      begin
-        self.cdb_load(name, couchdb)
-      rescue Chef::Exceptions::CouchDBNotFound
-        nil
+    def self.load_from_file(name)
+      unless File.directory?(Chef::Config[:environment_path])
+        raise Chef::Exceptions::InvalidEnvironmentPath, "Environment path '#{Chef::Config[:environment_path]}' is invalid"
       end
-    end
 
-    def cdb_destroy
-      couchdb.delete("environment", @name, couchdb_rev)
+      js_file = File.join(Chef::Config[:environment_path], "#{name}.json")
+      rb_file = File.join(Chef::Config[:environment_path], "#{name}.rb")
+
+      if File.exists?(js_file)
+        # from_json returns object.class => json_class in the JSON.
+        Chef::JSONCompat.from_json(IO.read(js_file))
+      elsif File.exists?(rb_file)
+        environment = Chef::Environment.new
+        environment.name(name)
+        environment.from_file(rb_file)
+        environment
+      else
+        raise Chef::Exceptions::EnvironmentNotFound, "Environment '#{name}' could not be loaded from disk"
+      end
     end
 
     def destroy
       chef_server_rest.delete_rest("environments/#{@name}")
     end
 
-    def cdb_save
-      self.couchdb_rev = couchdb.store("environment", @name, self)["rev"]
-    end
-
     def save
       begin
         chef_server_rest.put_rest("environments/#{@name}", self)
@@ -315,106 +284,6 @@ class Chef
       self
     end
 
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("environments", DESIGN_DOCUMENT)
-    end
-
-    # Loads the set of Chef::CookbookVersion objects available to a given environment
-    # === Returns
-    # Hash
-    # i.e.
-    # {
-    #   "cookbook_name" => [ Chef::CookbookVersion ... ] ## the array of CookbookVersions is sorted highest to lowest
-    # }
-    #
-    # There will be a key for every cookbook.  If no CookbookVersions
-    # are available for the specified environment the value will be an
-    # empty list.
-    #
-    def self.cdb_load_filtered_cookbook_versions(name, couchdb=nil)
-      version_constraints = cdb_load(name, couchdb).cookbook_versions.inject({}) {|res, (k,v)| res[k] = Chef::VersionConstraint.new(v); res}
-
-      # inject all cookbooks into the hash while filtering out restricted versions, then sort the individual arrays
-      cookbook_list = Chef::CookbookVersion.cdb_list(true, couchdb)
-
-      filtered_list = cookbook_list.inject({}) do |res, cookbook|
-        # FIXME: should cookbook.version return a Chef::Version?
-        version               = Chef::Version.new(cookbook.version)
-        requirement_satisfied = version_constraints.has_key?(cookbook.name) ? version_constraints[cookbook.name].include?(version) : true
-        # we want a key for every cookbook, even if no versions are available
-        res[cookbook.name] ||= []
-        res[cookbook.name] << cookbook if requirement_satisfied
-        res
-      end
-
-      sorted_list = filtered_list.inject({}) do |res, (cookbook_name, versions)|
-        res[cookbook_name] = versions.sort.reverse
-        res
-      end
-
-      sorted_list
-    end
-
-    # Like +cdb_load_filtered_cookbook_versions+, loads the set of
-    # cookbooks available in a given environment. The difference is that
-    # this method will load Chef::MinimalCookbookVersion objects that
-    # contain only the information necessary for solving a cookbook
-    # collection for a given run list. The user of this method must call
-    # Chef::MinimalCookbookVersion.load_full_versions_of() after solving
-    # the cookbook collection to get the full objects.
-    # === Returns
-    # Hash
-    # i.e.
-    # {
-    #   "cookbook_name" => [ Chef::CookbookVersion ... ] ## the array of CookbookVersions is sorted highest to lowest
-    # }
-    #
-    # There will be a key for every cookbook.  If no CookbookVersions
-    # are available for the specified environment the value will be an
-    # empty list.
-    def self.cdb_minimal_filtered_versions(name, couchdb=nil)
-      version_constraints = cdb_load(name, couchdb).cookbook_versions.inject({}) {|res, (k,v)| res[k] = Chef::VersionConstraint.new(v); res}
-
-      # inject all cookbooks into the hash while filtering out restricted versions, then sort the individual arrays
-      cookbook_list = Chef::MinimalCookbookVersion.load_all(couchdb)
-
-      filtered_list = cookbook_list.inject({}) do |res, cookbook|
-        # FIXME: should cookbook.version return a Chef::Version?
-        version               = Chef::Version.new(cookbook.version)
-        requirement_satisfied = version_constraints.has_key?(cookbook.name) ? version_constraints[cookbook.name].include?(version) : true
-        # we want a key for every cookbook, even if no versions are available
-        res[cookbook.name] ||= []
-        res[cookbook.name] << cookbook if requirement_satisfied
-        res
-      end
-
-      sorted_list = filtered_list.inject({}) do |res, (cookbook_name, versions)|
-        res[cookbook_name] = versions.sort.reverse
-        res
-      end
-
-      sorted_list
-    end
-
-    def self.cdb_load_filtered_recipe_list(name, couchdb=nil)
-      cdb_load_filtered_cookbook_versions(name, couchdb).map do |cb_name, cb|
-        if cb.empty?            # no available versions
-          []                    # empty list elided with flatten
-        else
-          latest_version = cb.first
-          latest_version.recipe_filenames_by_name.keys.map do |recipe|
-            case recipe
-            when DEFAULT
-              cb_name
-            else
-              "#{cb_name}::#{recipe}"
-            end
-          end
-        end
-      end.flatten
-    end
-
     def self.load_filtered_recipe_list(environment)
       chef_server_rest.get_rest("environments/#{environment}/recipes")
     end
@@ -433,23 +302,17 @@ class Chef
 
     def self.validate_cookbook_version(version)
       begin
-        Chef::VersionConstraint.new version
-        true
+        if Chef::Config[:solo]
+          raise Chef::Exceptions::IllegalVersionConstraint,
+                "Environment cookbook version constraints not allowed in chef-solo"
+        else
+          Chef::VersionConstraint.new version
+          true
+        end
       rescue ArgumentError
         false
       end
     end
 
-    def self.create_default_environment(couchdb=nil)
-      couchdb = couchdb || Chef::CouchDB.new
-      begin
-        Chef::Environment.cdb_load('_default', couchdb)
-      rescue Chef::Exceptions::CouchDBNotFound
-        env = Chef::Environment.new(couchdb)
-        env.name '_default'
-        env.description 'The default Chef environment'
-        env.cdb_save
-      end
-    end
   end
 end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
new file mode 100644
index 0000000..82beefe
--- /dev/null
+++ b/lib/chef/event_dispatch/base.rb
@@ -0,0 +1,314 @@
+class Chef
+
+  # ==EventDispatch
+  # Classes in EventDispatch deal with collecting, distributing, and handling
+  # information in response to events that occur during a chef-client run.
+  #
+  # EventDispatch uses a simple publishing system where data from all events
+  # are forwarded to all subscribers unconditionally.
+  #
+  # EventDispatch is used to implement custom console output formatters so that
+  # users may have more control over the formatting and verbosity of Chef
+  # client output and client-side data collection for server-side client
+  # history storage and reporting.
+  #
+  # === API Stability Status
+  # The EventDispatch API is intended to become a stable, public API upon which
+  # end-users can implement their own custom output formatters, reporting
+  # integration libraries, and more. This is a new feature, however, so
+  # breaking changes may be required as it "bakes" in order to provide a clean,
+  # coherent and supportable API in the long term. Therefore, developers should
+  # consider the feature "beta" for now and be prepared for possible breaking
+  # changes in point releases.
+  module EventDispatch
+
+    # == EventDispatch::Base
+    # EventDispatch::Base is a completely abstract base class that defines the
+    # API used by both the classes that collect event information and those
+    # that process them.
+    class Base
+
+      # Called at the very start of a Chef Run
+      def run_start(version)
+      end
+
+      def run_started(run_status)
+      end
+
+      # Called at the end a successful Chef run.
+      def run_completed(node)
+      end
+
+      # Called at the end of a failed Chef run.
+      def run_failed(exception)
+      end
+
+      # Called right after ohai runs.
+      def ohai_completed(node)
+      end
+
+      # Already have a client key, assuming this node has registered.
+      def skipping_registration(node_name, config)
+      end
+
+      # About to attempt to register as +node_name+
+      def registration_start(node_name, config)
+      end
+
+      def registration_completed
+      end
+
+      # Failed to register this client with the server.
+      def registration_failed(node_name, exception, config)
+      end
+
+      # Called before Chef client loads the node data from the server
+      def node_load_start(node_name, config)
+      end
+
+      # TODO: def node_run_list_overridden(*args)
+
+      # Failed to load node data from the server
+      def node_load_failed(node_name, exception, config)
+      end
+
+      # Error expanding the run list
+      def run_list_expand_failed(node, exception)
+      end
+
+      # Called after Chef client has loaded the node data.
+      # Default and override attrs from roles have been computed, but not yet applied.
+      # Normal attrs from JSON have been added to the node.
+      def node_load_completed(node, expanded_run_list, config)
+      end
+
+      # Called before the cookbook collection is fetched from the server.
+      def cookbook_resolution_start(expanded_run_list)
+      end
+
+      # Called when there is an error getting the cookbook collection from the
+      # server.
+      def cookbook_resolution_failed(expanded_run_list, exception)
+      end
+
+      # Called when the cookbook collection is returned from the server.
+      def cookbook_resolution_complete(cookbook_collection)
+      end
+
+      # Called before unneeded cookbooks are removed
+      def cookbook_clean_start
+      end
+
+      # Called after the file at +path+ is removed. It may be removed if the
+      # cookbook containing it was removed from the run list, or if the file was
+      # removed from the cookbook.
+      def removed_cookbook_file(path)
+      end
+
+      # Called when cookbook cleaning is finished.
+      def cookbook_clean_complete
+      end
+
+      # Called before cookbook sync starts
+      def cookbook_sync_start(cookbook_count)
+      end
+
+      # Called when cookbook +cookbook_name+ has been sync'd
+      def synchronized_cookbook(cookbook_name)
+      end
+
+      # Called when an individual file in a cookbook has been updated
+      def updated_cookbook_file(cookbook_name, path)
+      end
+
+      # Called when an error occurs during cookbook sync
+      def cookbook_sync_failed(cookbooks, exception)
+      end
+
+      # Called after all cookbooks have been sync'd.
+      def cookbook_sync_complete
+      end
+
+      ## TODO: add cookbook name to the API for file load callbacks
+
+      ## TODO: add callbacks for overall cookbook eval start and complete.
+
+      # Called when library file loading starts
+      def library_load_start(file_count)
+      end
+
+      # Called when library file has been loaded
+      def library_file_loaded(path)
+      end
+
+      # Called when a library file has an error on load.
+      def library_file_load_failed(path, exception)
+      end
+
+      # Called when library file loading has finished
+      def library_load_complete
+      end
+
+      # Called when LWRP loading starts
+      def lwrp_load_start(lwrp_file_count)
+      end
+
+      # Called after a LWR or LWP has been loaded
+      def lwrp_file_loaded(path)
+      end
+
+      # Called after a LWR or LWP file errors on load
+      def lwrp_file_load_failed(path, exception)
+      end
+
+      # Called when LWRPs are finished loading
+      def lwrp_load_complete
+      end
+
+      # Called before attribute files are loaded
+      def attribute_load_start(attribute_file_count)
+      end
+
+      # Called after the attribute file is loaded
+      def attribute_file_loaded(path)
+      end
+
+      # Called when an attribute file fails to load.
+      def attribute_file_load_failed(path, exception)
+      end
+
+      # Called when attribute file loading is finished
+      def attribute_load_complete
+      end
+
+      # Called before resource definitions are loaded
+      def definition_load_start(definition_file_count)
+      end
+
+      # Called when a resource definition has been loaded
+      def definition_file_loaded(path)
+      end
+
+      # Called when a resource definition file fails to load
+      def definition_file_load_failed(path, exception)
+      end
+
+      # Called when resource defintions are done loading
+      def definition_load_complete
+      end
+
+      # Called before recipes are loaded
+      def recipe_load_start(recipe_count)
+      end
+
+      # Called after the recipe has been loaded
+      def recipe_file_loaded(path)
+      end
+
+      # Called after a recipe file fails to load
+      def recipe_file_load_failed(path, exception)
+      end
+
+      # Called when a recipe cannot be resolved
+      def recipe_not_found(exception)
+      end
+
+      # Called when recipes have been loaded.
+      def recipe_load_complete
+      end
+
+      # Called before convergence starts
+      def converge_start(run_context)
+      end
+
+      # Called when the converge phase is finished.
+      def converge_complete
+      end
+
+      # TODO: need events for notification resolve?
+      # def notifications_resolved
+      # end
+
+      # Called before action is executed on a resource.
+      def resource_action_start(resource, action, notification_type=nil, notifier=nil)
+      end
+
+      # Called when a resource fails, but will retry.
+      def resource_failed_retriable(resource, action, retry_count, exception)
+      end
+
+      # Called when a resource fails and will not be retried.
+      def resource_failed(resource, action, exception)
+      end
+
+      # Called when a resource action has been skipped b/c of a conditional
+      def resource_skipped(resource, action, conditional)
+      end
+
+      # Called when a resource action has been completed
+      def resource_completed(resource)
+      end
+
+      # Called after #load_current_resource has run.
+      def resource_current_state_loaded(resource, action, current_resource)
+      end
+
+      # Called when resource current state load is skipped due to the provider
+      # not supporting whyrun mode.
+      def resource_current_state_load_bypassed(resource, action, current_resource)
+      end
+
+      # Called when evaluating a resource that does not support whyrun in whyrun mode
+      def resource_bypassed(resource, action, current_resource)
+      end
+
+      # Called when a resource has no converge actions, e.g., it was already correct.
+      def resource_up_to_date(resource, action)
+      end
+
+      # Called when a change has been made to a resource. May be called multiple
+      # times per resource, e.g., a file may have its content updated, and then
+      # its permissions updated.
+      def resource_update_applied(resource, action, update)
+      end
+
+      # Called after a resource has been completely converged, but only if
+      # modifications were made.
+      def resource_updated(resource, action)
+      end
+
+      # Called before handlers run
+      def handlers_start(handler_count)
+      end
+
+      # Called after an individual handler has run
+      def handler_executed(handler)
+      end
+
+      # Called after all handlers have executed
+      def handlers_completed
+      end
+
+      # Called when an assertion declared by a provider fails
+      def provider_requirement_failed(action, resource, exception, message)
+      end
+
+      # Called when a provider makes an assumption after a failed assertion
+      # in whyrun mode, in order to allow execution to continue
+      def whyrun_assumption(action, resource, message)
+      end
+
+      ## TODO: deprecation warning. this way we can queue them up and present
+      #  them all at once.
+
+      # An uncategorized message. This supports the case that a user needs to
+      # pass output that doesn't fit into one of the callbacks above. Note that
+      # there's no semantic information about the content or importance of the
+      # message. That means that if you're using this too often, you should add a
+      # callback for it.
+      def msg(message)
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
new file mode 100644
index 0000000..c172a40
--- /dev/null
+++ b/lib/chef/event_dispatch/dispatcher.rb
@@ -0,0 +1,42 @@
+require 'chef/event_dispatch/base'
+
+class Chef
+  module EventDispatch
+
+    # == EventDispatch::Dispatcher
+    # The Dispatcher handles receiving event data from the sources
+    # (Chef::Client, Resources and Providers, etc.) and publishing the data to
+    # the registered subscribers.
+    class Dispatcher < Base
+
+      def initialize(*subscribers)
+        @subscribers = subscribers
+      end
+
+      # Add a new subscriber to the list of registered subscribers
+      def register(subscriber)
+        @subscribers << subscriber
+      end
+
+      ####
+      # All messages are unconditionally forwarded to all subscribers, so just
+      # define the forwarding in one go:
+      #
+
+      # Define a method that will be forwarded to all
+      def self.def_forwarding_method(method_name)
+        class_eval(<<-END_OF_METHOD, __FILE__, __LINE__)
+          def #{method_name}(*args)
+            @subscribers.each {|s| s.#{method_name}(*args)}
+          end
+        END_OF_METHOD
+      end
+
+      (Base.instance_methods - Object.instance_methods).each do |method_name|
+        def_forwarding_method(method_name)
+      end
+
+    end
+  end
+end
+
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index 72c8b8f..99054eb 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -1,6 +1,7 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Seth Falcon (<seth at opscode.com>)
+# Author:: Kyle Goodwin (<kgoodwin at primerevenue.com>)
 # Copyright:: Copyright 2008-2010 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -21,6 +22,21 @@ class Chef
   # Chef's custom exceptions are all contained within the Chef::Exceptions
   # namespace.
   class Exceptions
+
+    # Backcompat with Chef::ShellOut code:
+    require 'mixlib/shellout/exceptions'
+
+    def self.const_missing(const_name)
+      if const_name == :ShellCommandFailed
+        Chef::Log.warn("Chef::Exceptions::ShellCommandFailed is deprecated, use Mixlib::ShellOut::ShellCommandFailed")
+        called_from = caller[0..3].inject("Called from:\n") {|msg, trace_line| msg << "  #{trace_line}\n" }
+        Chef::Log.warn(called_from)
+        Mixlib::ShellOut::ShellCommandFailed
+      else
+        super
+      end
+    end
+
     class Application < RuntimeError; end
     class Cron < RuntimeError; end
     class Env < RuntimeError; end
@@ -34,13 +50,11 @@ class Chef
     class Override < RuntimeError; end
     class UnsupportedAction < RuntimeError; end
     class MissingLibrary < RuntimeError; end
-    class MissingRole < RuntimeError; end
     class CannotDetermineNodeName < RuntimeError; end
     class User < RuntimeError; end
     class Group < RuntimeError; end
     class Link < RuntimeError; end
     class Mount < RuntimeError; end
-    class CouchDBNotFound < RuntimeError; end
     class PrivateKeyMissing < RuntimeError; end
     class CannotWritePrivateKey < RuntimeError; end
     class RoleNotFound < RuntimeError; end
@@ -54,6 +68,7 @@ class Chef
     # 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
+    class RecipeNotFound < ArgumentError; end
     class AttributeNotFound < RuntimeError; end
     class InvalidCommandOption < RuntimeError; end
     class CommandTimeout < RuntimeError; end
@@ -70,6 +85,7 @@ class Chef
     class CookbookVersionNameMismatch < ArgumentError; end
     class MissingParentDirectory < RuntimeError; end
     class UnresolvableGitReference < RuntimeError; end
+    class InvalidRemoteGitReference < RuntimeError; end
     class InvalidEnvironmentRunListSpecification < ArgumentError; end
     class InvalidDataBagItemID < ArgumentError; end
     class InvalidDataBagName < ArgumentError; end
@@ -79,7 +95,11 @@ class Chef
     # Thrown when Win32 API layer binds to non-existent Win32 function.  Occurs
     # when older versions of Windows don't support newer Win32 API functions.
     class Win32APIFunctionNotImplemented < NotImplementedError; end
-
+    # Attempting to run windows code on a not-windows node
+    class Win32NotWindows < RuntimeError; end
+    class WindowsNotAdmin < RuntimeError; end
+    # Attempting to access a 64-bit only resource on a 32-bit Windows system
+    class Win32ArchitectureIncorrect < RuntimeError; end
     class ObsoleteDependencySyntax < ArgumentError; end
     class InvalidDataBagPath < ArgumentError; end
 
@@ -88,15 +108,124 @@ class Chef
     class CookbookVersionConflict < ArgumentError ; end
 
     # does not follow X.Y.Z format. ArgumentError?
+    class InvalidPlatformVersion < ArgumentError; end
     class InvalidCookbookVersion < ArgumentError; end
 
     # version constraint should be a string or array, or it doesn't
     # match OP VERSION. ArgumentError?
     class InvalidVersionConstraint < ArgumentError; end
 
-    # Backcompat with Chef::ShellOut code:
-    require 'mixlib/shellout/exceptions'
-    class ShellCommandFailed < Mixlib::ShellOut::ShellCommandFailed; end
+    # Version constraints are not allowed in chef-solo
+    class IllegalVersionConstraint < NotImplementedError; end
+
+    # File operation attempted but no permissions to perform it
+    class InsufficientPermissions < RuntimeError; end
+
+    # Ifconfig failed
+    class Ifconfig < RuntimeError; end
+
+    # Invalid "source" parameter to a remote_file resource
+    class InvalidRemoteFileURI < ArgumentError; end
+
+    # Node::Attribute computes the merged version of of attributes
+    # and makes it read-only. Attempting to modify a read-only
+    # attribute will cause this error.
+    class ImmutableAttributeModification < NoMethodError; end
+
+    # Merged node attributes are invalidated when the component
+    # attributes are updated. Attempting to read from a stale copy
+    # of merged attributes will trigger this error.
+    class StaleAttributeRead < StandardError; end
+
+    # Registry Helper throws the following errors
+    class Win32RegArchitectureIncorrect < Win32ArchitectureIncorrect; end
+    class Win32RegHiveMissing < ArgumentError; end
+    class Win32RegKeyMissing < RuntimeError; end
+    class Win32RegValueMissing < RuntimeError; end
+    class Win32RegDataMissing < RuntimeError; end
+    class Win32RegValueExists < RuntimeError; end
+    class Win32RegNoRecursive < ArgumentError; end
+    class Win32RegTypeDoesNotExist < ArgumentError; end
+    class Win32RegBadType < ArgumentError; end
+    class Win32RegBadValueSize < ArgumentError; end
+    class Win32RegTypesMismatch < ArgumentError; end
+
+    class InvalidEnvironmentPath < ArgumentError; end
+    class EnvironmentNotFound < RuntimeError; end
+
+    # File-like resource found a non-file (socket, pipe, directory, etc) at its destination
+    class FileTypeMismatch < RuntimeError; end
+
+    # File (or descendent) resource configured to manage symlink source, but
+    # the symlink that is there either loops or points to a nonexistent file
+    class InvalidSymlink < RuntimeError; end
+
+    class ChildConvergeError < RuntimeError; end
+
+    class MissingRole < RuntimeError
+      NULL = Object.new
+
+      attr_reader :expansion
+
+      def initialize(message_or_expansion=NULL)
+        @expansion = nil
+        case message_or_expansion
+        when NULL
+          super()
+        when String
+          super
+        when RunList::RunListExpansion
+          @expansion = message_or_expansion
+          missing_roles = @expansion.errors.join(', ')
+          super("The expanded run list includes nonexistent roles: #{missing_roles}")
+        end
+      end
+
+
+    end
+    # Exception class for collecting multiple failures. Used when running
+    # delayed notifications so that chef can process each delayed
+    # notification even if chef client or other notifications fail.
+    class MultipleFailures < StandardError
+      def initialize(*args)
+        super
+        @all_failures = []
+      end
+
+      def message
+        base = "Multiple failures occurred:\n"
+        @all_failures.inject(base) do |message, (location, error)|
+          message << "* #{error.class} occurred in #{location}: #{error.message}\n"
+        end
+      end
+
+      def client_run_failure(exception)
+        set_backtrace(exception.backtrace)
+        @all_failures << [ "chef run", exception ]
+      end
+
+      def notification_failure(exception)
+        @all_failures << [ "delayed notification", exception ]
+      end
+
+      def raise!
+        unless empty?
+          raise self.for_raise
+        end
+      end
+
+      def empty?
+        @all_failures.empty?
+      end
+
+      def for_raise
+        if @all_failures.size == 1
+          @all_failures[0][1]
+        else
+          self
+        end
+      end
+    end
 
     class CookbookVersionSelection
 
@@ -158,6 +287,14 @@ class Chef
           result.to_json(*a)
         end
       end
-    end
+
+    end # CookbookVersionSelection
+
+    # When the server sends a redirect, RFC 2616 states a user-agent should
+    # not follow it with a method other than GET or HEAD, unless a specific
+    # action is taken by the user. A redirect received as response to a
+    # non-GET and non-HEAD request will thus raise an InvalidRedirect.
+    class InvalidRedirect < StandardError; end
+
   end
 end
diff --git a/lib/chef/file_access_control.rb b/lib/chef/file_access_control.rb
index 5f6bda6..cc7fa8f 100644
--- a/lib/chef/file_access_control.rb
+++ b/lib/chef/file_access_control.rb
@@ -34,7 +34,9 @@ class Chef
       include FileAccessControl::Unix
     end
 
+    attr_reader :current_resource
     attr_reader :resource
+    attr_reader :provider
     attr_reader :file
 
     # FileAccessControl objects set the owner, group and mode of +file+ to
@@ -47,8 +49,11 @@ class Chef
     #             and +mode+
     # file:       The file whose access control settings you wish to modify,
     #             given as a String.
-    def initialize(resource, file)
-      @resource, @file = resource, file
+    #
+    # TODO requiring current_resource will break cookbook_file template_file
+    def initialize(current_resource, new_resource, provider)
+      @current_resource, @resource, @provider = current_resource, new_resource, provider
+      @file = @current_resource.path
       @modified = false
     end
 
diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb
index baedadc..a5d177d 100644
--- a/lib/chef/file_access_control/unix.rb
+++ b/lib/chef/file_access_control/unix.rb
@@ -26,47 +26,86 @@ class Chef
       UINT = (1 << 32)
       UID_MAX = (1 << 32) - 10
 
+      def set_all!
+        set_owner!
+        set_group!
+        set_mode!
+      end
+
       def set_all
         set_owner
         set_group
         set_mode
       end
 
-      # Workaround the fact that Ruby's Etc module doesn't believe in negative
-      # uids, so negative uids show up as the diminished radix complement of
-      # a uint. For example, a uid of -2 is reported as 4294967294
-      def diminished_radix_complement(int)
-        if int > UID_MAX
-          int - UINT
-        else
-          int
-        end
+      # TODO factor this up
+      def requires_changes?
+        should_update_mode? || should_update_owner? || should_update_group?
+      end
+
+      def define_resource_requirements
+        uid_from_resource(resource)
+        gid_from_resource(resource)
+      end
+
+      def describe_changes
+        changes = []
+        changes << "change mode from '#{mode_to_s(current_mode)}' to '#{mode_to_s(target_mode)}'" if should_update_mode?
+        changes << "change owner from '#{current_resource.owner}' to '#{resource.owner}'" if should_update_owner?
+        changes << "change group from '#{current_resource.group}' to '#{resource.group}'" if should_update_group?
+        changes
       end
 
       def target_uid
-        return nil if resource.owner.nil?
-        if resource.owner.kind_of?(String)
-          diminished_radix_complement( Etc.getpwnam(resource.owner).uid )
-        elsif resource.owner.kind_of?(Integer)
-          resource.owner
+        uid_from_resource(resource)
+      end
+
+      def current_uid
+        uid_from_resource(current_resource)
+      end
+
+      def should_update_owner?
+        if target_uid.nil?
+          # the user has not specified a permission on the new resource, so we never manage it with FAC
+          Chef::Log.debug("found target_uid == nil, so no owner was specified on resource, not managing owner")
+          return false
+        elsif current_uid.nil?
+          # the user has specified a permission, and we are creating a file, so always enforce permissions
+          Chef::Log.debug("found current_uid == nil, so we are creating a new file, updating owner")
+          return true
+        elsif target_uid != current_uid
+          # the user has specified a permission, and it does not match the file, so fix the permission
+          Chef::Log.debug("found target_uid != current_uid, updating owner")
+          return true
         else
-          Chef::Log.error("The `owner` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})")
-          raise ArgumentError, "cannot resolve #{resource.owner.inspect} to uid, owner must be a string or integer"
+          Chef::Log.debug("found target_uid == current_uid, not updating owner")
+          # the user has specified a permission, but it matches the file, so behave idempotently
+          return false
         end
-      rescue ArgumentError
-        raise Chef::Exceptions::UserIDNotFound, "cannot determine user id for '#{resource.owner}', does the user exist on this system?"
       end
 
-      def set_owner
-        if (uid = target_uid) && (uid != stat.uid)
-          chown(uid, nil, file)
-          Chef::Log.info("#{log_string} owner changed to #{uid}")
+      def set_owner!
+        unless target_uid.nil?
+          chown(target_uid, nil, file)
+          Chef::Log.info("#{log_string} owner changed to #{target_uid}")
           modified
         end
       end
 
+      def set_owner
+        set_owner! if should_update_owner?
+      end
+
       def target_gid
-        return nil if resource.group.nil?
+        gid_from_resource(resource)
+      end
+
+      def current_gid
+        gid_from_resource(current_resource)
+      end
+
+      def gid_from_resource(resource)
+        return nil if resource == nil or resource.group.nil?
         if resource.group.kind_of?(String)
           diminished_radix_complement( Etc.getgrnam(resource.group).gid )
         elsif resource.group.kind_of?(Integer)
@@ -76,42 +115,111 @@ class Chef
           raise ArgumentError, "cannot resolve #{resource.group.inspect} to gid, group must be a string or integer"
         end
       rescue ArgumentError
-        raise Chef::Exceptions::GroupIDNotFound, "cannot determine group id for '#{resource.group}', does the group exist on this system?"
+        provider.requirements.assert(:create, :create_if_missing, :touch) do |a|
+          a.assertion { false }
+          a.failure_message(Chef::Exceptions::GroupIDNotFound, "cannot determine group id for '#{resource.group}', does the group exist on this system?")
+          a.whyrun("Assuming group #{resource.group} would have been created")
+        end
+        return nil
       end
 
-      def set_group
-        if (gid = target_gid) && (gid != stat.gid)
-          chown(nil, gid, file)
-          Chef::Log.info("#{log_string} group changed to #{gid}")
+      def should_update_group?
+        if target_gid.nil?
+          # the user has not specified a permission on the new resource, so we never manage it with FAC
+          Chef::Log.debug("found target_gid == nil, so no group was specified on resource, not managing group")
+          return false
+        elsif current_gid.nil?
+          # the user has specified a permission, and we are creating a file, so always enforce permissions
+          Chef::Log.debug("found current_gid == nil, so we are creating a new file, updating group")
+          return true
+        elsif target_gid != current_gid
+          # the user has specified a permission, and it does not match the file, so fix the permission
+          Chef::Log.debug("found target_gid != current_gid, updating group")
+          return true
+        else
+          Chef::Log.debug("found target_gid == current_gid, not updating group")
+          # the user has specified a permission, but it matches the file, so behave idempotently
+          return false
+        end
+      end
+
+      def set_group!
+        unless target_gid.nil?
+          chown(nil, target_gid, file)
+          Chef::Log.info("#{log_string} group changed to #{target_gid}")
           modified
         end
       end
 
+      def set_group
+        set_group! if should_update_group?
+      end
+
+      def mode_from_resource(res)
+        return nil if res == nil or res.mode.nil?
+        (res.mode.respond_to?(:oct) ? res.mode.oct : res.mode.to_i) & 007777
+      end
+
       def target_mode
-        return nil if resource.mode.nil?
-        (resource.mode.respond_to?(:oct) ? resource.mode.oct : resource.mode.to_i) & 007777
+        mode_from_resource(resource)
       end
 
-      def set_mode
-        if (mode = target_mode) && (mode != (stat.mode & 007777))
+      def mode_to_s(mode)
+        mode.nil? ? "" : "0#{mode.to_s(8)}"
+      end
+
+      def current_mode
+        mode_from_resource(current_resource)
+      end
+
+      def should_update_mode?
+        if target_mode.nil?
+          # the user has not specified a permission on the new resource, so we never manage it with FAC
+          Chef::Log.debug("found target_mode == nil, so no mode was specified on resource, not managing mode")
+          return false
+        elsif current_mode.nil?
+          # the user has specified a permission, and we are creating a file, so always enforce permissions
+          Chef::Log.debug("found current_mode == nil, so we are creating a new file, updating mode")
+          return true
+        elsif target_mode != current_mode
+          # the user has specified a permission, and it does not match the file, so fix the permission
+          Chef::Log.debug("found target_mode != current_mode, updating mode")
+          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
+          return false
+        end
+      end
+
+      def set_mode!
+        unless target_mode.nil?
           chmod(target_mode, file)
-          Chef::Log.info("#{log_string} mode changed to #{mode.to_s(8)}")
+          Chef::Log.info("#{log_string} mode changed to #{target_mode.to_s(8)}")
           modified
         end
       end
 
+      def set_mode
+        set_mode! if should_update_mode?
+      end
+
       def stat
-        if File.symlink?(file)
+        if manage_symlink_attrs?
           @stat ||= File.lstat(file)
         else
           @stat ||= File.stat(file)
         end
       end
 
+      def manage_symlink_attrs?
+        @provider.manage_symlink_access?
+      end
+
       private
 
       def chmod(mode, file)
-        if File.symlink?(file)
+        if manage_symlink_attrs?
           begin
             File.lchmod(mode, file)
           rescue NotImplementedError
@@ -123,12 +231,43 @@ class Chef
       end
 
       def chown(uid, gid, file)
-        if ::File.symlink?(file)
+        if manage_symlink_attrs?
           File.lchown(uid, gid, file)
         else
           File.chown(uid, gid, file)
         end
       end
+
+      # Workaround the fact that Ruby's Etc module doesn't believe in negative
+      # uids, so negative uids show up as the diminished radix complement of
+      # a uint. For example, a uid of -2 is reported as 4294967294
+      def diminished_radix_complement(int)
+        if int > UID_MAX
+          int - UINT
+        else
+          int
+        end
+      end
+
+      def uid_from_resource(resource)
+        return nil if resource == nil or resource.owner.nil?
+        if resource.owner.kind_of?(String)
+          diminished_radix_complement( Etc.getpwnam(resource.owner).uid )
+        elsif resource.owner.kind_of?(Integer)
+          resource.owner
+        else
+          Chef::Log.error("The `owner` parameter of the #@resource resource is set to an invalid value (#{resource.owner.inspect})")
+          raise ArgumentError, "cannot resolve #{resource.owner.inspect} to uid, owner must be a string or integer"
+        end
+      rescue ArgumentError
+        provider.requirements.assert(:create, :create_if_missing, :touch) do |a|
+          a.assertion { false }
+          a.failure_message(Chef::Exceptions::UserIDNotFound, "cannot determine user id for '#{resource.owner}', does the user exist on this system?")
+          a.whyrun("Assuming user #{resource.owner} would have been created")
+        end
+        return nil
+      end
+
     end
   end
 end
diff --git a/lib/chef/file_access_control/windows.rb b/lib/chef/file_access_control/windows.rb
index 513f18d..32ac299 100644
--- a/lib/chef/file_access_control/windows.rb
+++ b/lib/chef/file_access_control/windows.rb
@@ -29,12 +29,35 @@ class Chef
       ACE = Security::ACE
       SID = Security::SID
 
+      def set_all!
+        set_owner!
+        set_group!
+        set_dacl
+      end
+
       def set_all
         set_owner
         set_group
         set_dacl
       end
 
+      def define_resource_requirements
+        # windows FAC has no assertions
+      end
+
+      def requires_changes?
+        should_update_dacl? || should_update_owner? || should_update_group?
+      end
+
+      def describe_changes
+        # FIXME: describe what these are changing from and to
+        changes = []
+        changes << "change dacl" if should_update_dacl?
+        changes << "change owner" if should_update_owner?
+        changes << "change group" if should_update_group?
+        changes
+      end
+
       private
 
       # Compare the actual ACL on a resource with the ACL we want.  This
@@ -90,6 +113,18 @@ class Chef
         end
       end
 
+      def should_update_dacl?
+        return true unless ::File.exists?(file)
+        dacl = target_dacl
+        existing_dacl = existing_descriptor.dacl
+        inherits = target_inherits
+        ( ! inherits.nil? && inherits != existing_descriptor.dacl_inherits? ) || ( dacl && !acls_equal(dacl, existing_dacl) )
+      end
+
+      def set_dacl!
+        set_dacl
+      end
+
       def set_dacl
         dacl = target_dacl
         existing_dacl = existing_descriptor.dacl
@@ -111,22 +146,44 @@ class Chef
         end
       end
 
-      def set_group
-        if (group = target_group) && (group != existing_descriptor.group)
+      def should_update_group?
+        return true unless ::File.exists?(file)
+        (group = target_group) && (group != existing_descriptor.group)
+      end
+
+      def set_group!
+        if (group = target_group)
           Chef::Log.info("#{log_string} group changed to #{group}")
           securable_object.group = group
           modified
         end
       end
 
-      def set_owner
-        if (owner = target_owner) && (owner != existing_descriptor.owner)
+      def set_group
+        if (group = target_group) && (group != existing_descriptor.group)
+          set_group!
+        end
+      end
+
+      def should_update_owner?
+        return true unless ::File.exists?(file)
+        (owner = target_owner) && (owner != existing_descriptor.owner)
+      end
+
+      def set_owner!
+        if owner = target_owner
           Chef::Log.info("#{log_string} owner changed to #{owner}")
           securable_object.owner = owner
           modified
         end
       end
 
+      def set_owner
+        if (owner = target_owner) && (owner != existing_descriptor.owner)
+          set_owner!
+        end
+      end
+
       def mode_ace(sid, mode)
         mask = 0
         mask |= GENERIC_READ if mode & 4 != 0
@@ -161,17 +218,24 @@ class Chef
       def calculate_flags(rights)
         # Handle inheritance flags
         flags = 0
-        case rights[:applies_to_children]
-        when :containers_only
-          flags |= CONTAINER_INHERIT_ACE
-        when :objects_only
-          flags |= OBJECT_INHERIT_ACE
-        when true
-          flags |= CONTAINER_INHERIT_ACE
-          flags |= OBJECT_INHERIT_ACE
-        when nil
-          flags |= CONTAINER_INHERIT_ACE
-          flags |= OBJECT_INHERIT_ACE
+
+        #
+        # Configure child inheritence only if the resource is some
+        # type of a directory.
+        #
+        if resource.is_a? Chef::Resource::Directory
+          case rights[:applies_to_children]
+          when :containers_only
+            flags |= CONTAINER_INHERIT_ACE
+          when :objects_only
+            flags |= OBJECT_INHERIT_ACE
+          when true
+            flags |= CONTAINER_INHERIT_ACE
+            flags |= OBJECT_INHERIT_ACE
+          when nil
+            flags |= CONTAINER_INHERIT_ACE
+            flags |= OBJECT_INHERIT_ACE
+          end
         end
 
         if rights[:applies_to_self] == false
diff --git a/lib/chef/file_cache.rb b/lib/chef/file_cache.rb
index a43fe60..89e934e 100644
--- a/lib/chef/file_cache.rb
+++ b/lib/chef/file_cache.rb
@@ -33,10 +33,12 @@ class Chef
       # path<String>:: The path to the file you want to put in the cache - should
       #   be relative to file_cache_path
       # contents<String>:: A string with the contents you want written to the file
+      # perm<String>:: Sets file permission bits. Permission bits are platform
+      #   dependent; on Unix systems, see open(2) for details.
       #
       # === Returns
       # true
-      def store(path, contents)
+      def store(path, contents, perm=0640)
         validate(
           {
             :path => path,
@@ -51,7 +53,7 @@ class Chef
         file_path_array = File.split(path)
         file_name = file_path_array.pop
         cache_path = create_cache_path(File.join(file_path_array))
-        File.open(File.join(cache_path, file_name), "w") do |io|
+        File.open(File.join(cache_path, file_name), "w", perm) do |io|
           io.print(contents)
         end
         true
diff --git a/lib/chef/file_content_management/content_base.rb b/lib/chef/file_content_management/content_base.rb
new file mode 100644
index 0000000..985e222
--- /dev/null
+++ b/lib/chef/file_content_management/content_base.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+  class FileContentManagement
+    class ContentBase
+
+      attr_reader :run_context
+      attr_reader :new_resource
+      attr_reader :current_resource
+
+      def initialize(new_resource, current_resource, run_context)
+        @new_resource = new_resource
+        @current_resource = current_resource
+        @run_context = run_context
+        @tempfile_loaded = false
+      end
+
+      def tempfile
+        # tempfile may be nil, so we cannot use ||= here
+        if @tempfile_loaded
+          @tempfile
+        else
+          @tempfile_loaded = true
+          @tempfile = file_for_provider
+        end
+      end
+
+      private
+
+      #
+      # Return something that looks like a File or Tempfile and
+      # you must assume the provider will unlink this file.  Copy
+      # the contents to a Tempfile if you need to.
+      #
+      def file_for_provider
+        raise "class must implement file_for_provider!"
+      end
+    end
+  end
+end
diff --git a/lib/chef/file_content_management/deploy.rb b/lib/chef/file_content_management/deploy.rb
new file mode 100644
index 0000000..35ea3c6
--- /dev/null
+++ b/lib/chef/file_content_management/deploy.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/file_content_management/deploy/cp'
+require 'chef/file_content_management/deploy/mv_unix'
+if Chef::Platform.windows?
+  require 'chef/file_content_management/deploy/mv_windows'
+end
+
+class Chef
+  class FileContentManagement
+    class Deploy
+      def self.strategy(atomic_update)
+        if atomic_update
+          Chef::Platform.windows? ? MvWindows.new() : MvUnix.new()
+        else
+          Cp.new()
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/file_content_management/deploy/cp.rb b/lib/chef/file_content_management/deploy/cp.rb
new file mode 100644
index 0000000..c6b1d6c
--- /dev/null
+++ b/lib/chef/file_content_management/deploy/cp.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# PURPOSE: This strategy preserves the inode, and will preserve modes + ownership
+#          even if the user running chef cannot create that ownership (but has
+#          rights to the file).  It is vulnerable to crashes in the middle of
+#          writing the file which could result in corruption or zero-length files.
+#
+
+class Chef
+  class FileContentManagement
+    class Deploy
+      #
+      # PURPOSE: This strategy preserves the inode, and will preserve modes + ownership
+      #          even if the user running chef cannot create that ownership (but has
+      #          rights to the file).  It is vulnerable to crashes in the middle of
+      #          writing the file which could result in corruption or zero-length files.
+      #
+      class Cp
+        def create(file)
+          Chef::Log.debug("touching #{file} to create it")
+          FileUtils.touch(file)
+        end
+
+        def deploy(src, dst)
+          Chef::Log.debug("copying temporary file #{src} into place at #{dst}")
+          FileUtils.cp(src, dst)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/file_content_management/deploy/mv_unix.rb b/lib/chef/file_content_management/deploy/mv_unix.rb
new file mode 100644
index 0000000..758c594
--- /dev/null
+++ b/lib/chef/file_content_management/deploy/mv_unix.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+  class FileContentManagement
+    class Deploy
+      #
+      # PURPOSE: this strategy is atomic, and attempts to preserve file modes
+      #
+      # NOTE: there is no preserve flag to FileUtils.mv, and we want to preserve the dst file
+      #       modes rather than the src file modes (preserve = true is what mv does already, we
+      #       would like preserve = false which is tricky).
+      #
+      class MvUnix
+        def create(file)
+          # this is very simple, but it ensures that ownership and file modes take
+          # good defaults, in particular mode needs to obey umask on create
+          Chef::Log.debug("touching #{file} to create it")
+          FileUtils.touch(file)
+        end
+
+        def deploy(src, dst)
+          # we are only responsible for content so restore the dst files perms
+          Chef::Log.debug("reading modes from #{dst} file")
+          stat = ::File.stat(dst)
+          mode = stat.mode & 07777
+          uid  = stat.uid
+          gid  = stat.gid
+
+          Chef::Log.debug("applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}")
+
+          # i own the inode, so should be able to at least chmod it
+          ::File.chmod(mode, src)
+
+          # we may be running as non-root in which case because we are doing an mv we cannot preserve
+          # the file modes.  after the mv we have a different inode and if we don't have rights to
+          # chown/chgrp on the inode then we can't fix the ownership.
+          #
+          # in the case where i'm running chef-solo on my homedir as myself and some root-shell
+          # work has caused dotfiles of mine to change to root-owned, i'm fine with this not being
+          # exceptional, and i think most use cases will consider this to not be exceptional, and
+          # the right thing is to fix the ownership of the file to the user running the commmand
+          # (which requires write perms to the directory, or mv will throw an exception)
+          begin
+            ::File.chown(uid, nil, src)
+          rescue Errno::EPERM
+            Chef::Log.warn("Could not set uid = #{uid} on #{src}, file modes not preserved")
+          end
+          begin
+            ::File.chown(nil, gid, src)
+          rescue Errno::EPERM
+            Chef::Log.warn("Could not set gid = #{gid} on #{src}, file modes not preserved")
+          end
+
+          Chef::Log.debug("moving temporary file #{src} into place at #{dst}")
+          FileUtils.mv(src, dst)
+        end
+      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
new file mode 100644
index 0000000..f4ed87f
--- /dev/null
+++ b/lib/chef/file_content_management/deploy/mv_windows.rb
@@ -0,0 +1,95 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# We update the contents of the file, using mv for atomicity, while maintaining all the
+# ACL information on the dst file.
+#
+
+require 'chef/platform/query_helpers'
+if Chef::Platform.windows?
+  require 'chef/win32/security'
+end
+
+class Chef
+  class FileContentManagement
+    class Deploy
+      class MvWindows
+
+        Security = Chef::ReservedNames::Win32::Security
+        ACL = Security::ACL
+
+        def create(file)
+          Chef::Log.debug("touching #{file} to create it")
+          FileUtils.touch(file)
+        end
+
+        def deploy(src, dst)
+          #
+          # At the time of deploy ACLs are correctly configured on the
+          # dst. This would be a simple atomic move operations in
+          # windows was not converting inherited ACLs of src to
+          # non-inherited ACLs in certain cases.See:
+          # http://blogs.msdn.com/b/oldnewthing/archive/2006/08/24/717181.aspx
+          #
+
+          #
+          # First cache the ACLs of dst file
+          #
+
+          dst_so = Security::SecurableObject.new(dst)
+          begin
+            # get the sd with the SACL
+            dst_sd = dst_so.security_descriptor(true)
+          rescue Chef::Exceptions::Win32APIError
+            # Catch and raise if the user is not elevated enough.
+            # At this point we can't configure the file as expected so
+            # we're failing action on the resource.
+            raise Chef::Exceptions::WindowsNotAdmin, "can not get the security information for '#{dst}' due to missing Administrator privilages."
+          end
+
+          if dst_sd.dacl_present?
+            apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+          end
+
+          if dst_sd.sacl_present?
+            apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+          end
+
+          #
+          # Then deploy the file
+          #
+
+          FileUtils.mv(src, dst)
+
+          #
+          # Then apply the cached acls to the new dst file
+          #
+
+          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?
+
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/file_content_management/tempfile.rb b/lib/chef/file_content_management/tempfile.rb
new file mode 100644
index 0000000..0bb7f3a
--- /dev/null
+++ b/lib/chef/file_content_management/tempfile.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "tempfile"
+
+class Chef
+  class FileContentManagement
+    class Tempfile
+
+      attr_reader :new_resource
+
+      def initialize(new_resource)
+        @new_resource = new_resource
+      end
+
+      def tempfile
+        @tempfile ||= tempfile_open
+      end
+
+      private
+
+      def tempfile_open
+        tf = ::Tempfile.open(tempfile_basename, tempfile_dirname)
+        # We always process the tempfile in binmode so that we
+        # preserve the line endings of the content.
+        tf.binmode
+        tf
+      end
+
+      #
+      # These are important for windows to get permissions right, and may
+      # be useful for SELinux and other ACL approaches.  Please use them
+      # as the arguments to Tempfile.new() consistently.
+      #
+      def tempfile_basename
+        basename = ::File.basename(@new_resource.name)
+        basename.insert 0, "." unless Chef::Platform.windows?  # dotfile if we're not on windows
+        basename
+      end
+
+      def tempfile_dirname
+        Chef::Config[:file_staging_uses_destdir] ? ::File.dirname(@new_resource.path) : Dir::tmpdir
+      end
+    end
+  end
+end
diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb
new file mode 100644
index 0000000..886fad0
--- /dev/null
+++ b/lib/chef/formatters/base.rb
@@ -0,0 +1,250 @@
+#
+# Author:: Tyler Cloke (<tyler 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 'chef/event_dispatch/base'
+require 'chef/formatters/error_inspectors'
+require 'chef/formatters/error_descriptor'
+require 'chef/formatters/error_mapper'
+
+class Chef
+
+  # == Chef::Formatters
+  # Formatters handle printing output about the progress/status of a chef
+  # client run to the user's screen.
+  module Formatters
+
+    class UnknownFormatter < StandardError; end
+
+    def self.formatters_by_name
+      @formatters_by_name ||= {}
+    end
+
+    def self.register(name, formatter)
+      formatters_by_name[name.to_s] = formatter
+    end
+
+    def self.by_name(name)
+      formatters_by_name[name]
+    end
+
+    def self.available_formatters
+      formatters_by_name.keys
+    end
+
+    #--
+    # TODO: is it too clever to be defining new() on a module like this?
+    def self.new(name, out, err)
+      formatter_class = by_name(name.to_s) or
+        raise UnknownFormatter, "No output formatter found for #{name} (available: #{available_formatters.join(', ')})"
+
+      formatter_class.new(out, err)
+    end
+
+    # == Outputter
+    # Handles basic printing tasks like colorizing.
+    # --
+    # TODO: Duplicates functionality from knife, upfactor.
+    class Outputter
+
+      attr_reader :out
+      attr_reader :err
+
+      def initialize(out, err)
+        @out, @err = out, err
+      end
+
+      def highline
+        @highline ||= begin
+          require 'highline'
+          HighLine.new
+        end
+      end
+
+      def color(string, *colors)
+        if Chef::Config[:color]
+          @out.print highline.color(string, *colors)
+        else
+          @out.print string
+        end
+      end
+
+      alias :print :color
+
+      def puts(string, *colors)
+        if Chef::Config[:color]
+          @out.puts highline.color(string, *colors)
+        else
+          @out.puts string
+        end
+      end
+
+    end
+
+
+    # == Formatters::Base
+    # Base class that all formatters should inherit from.
+    class Base < EventDispatch::Base
+
+      include ErrorMapper
+
+      def self.cli_name(name)
+        Chef::Formatters.register(name, self)
+      end
+
+      attr_reader :out
+      attr_reader :err
+      attr_reader :output
+
+      def initialize(out, err)
+        @output = Outputter.new(out, err)
+      end
+
+      def puts(*args)
+        @output.puts(*args)
+      end
+
+      def print(*args)
+        @output.print(*args)
+      end
+
+      # Input: a Formatters::ErrorDescription object.
+      # Outputs error to SDOUT.
+      def display_error(description)
+        puts("")
+        description.display(output)
+      end
+
+      def registration_failed(node_name, exception, config)
+        #A Formatters::ErrorDescription object
+        description = ErrorMapper.registration_failed(node_name, exception, config)
+        display_error(description)
+      end
+
+      def node_load_failed(node_name, exception, config)
+        description = ErrorMapper.node_load_failed(node_name, exception, config)
+        display_error(description)
+      end
+
+      def run_list_expand_failed(node, exception)
+        description = ErrorMapper.run_list_expand_failed(node, exception)
+        display_error(description)
+      end
+
+      def cookbook_resolution_failed(expanded_run_list, exception)
+        description = ErrorMapper.cookbook_resolution_failed(expanded_run_list, exception)
+        display_error(description)
+      end
+
+      def cookbook_sync_failed(cookbooks, exception)
+        description = ErrorMapper.cookbook_sync_failed(cookbooks, exception)
+        display_error(description)
+      end
+
+      def resource_failed(resource, action, exception)
+        description = ErrorMapper.resource_failed(resource, action, exception)
+        display_error(description)
+      end
+
+      # Generic callback for any attribute/library/lwrp/recipe file in a
+      # cookbook getting loaded. The per-filetype callbacks for file load are
+      # overriden so that they call this instead. This means that a subclass of
+      # Formatters::Base can implement #file_loaded to do the same thing for
+      # every kind of file that Chef loads from a recipe instead of
+      # implementing all the per-filetype callbacks.
+      def file_loaded(path)
+      end
+
+      # Generic callback for any attribute/library/lwrp/recipe file throwing an
+      # exception when loaded. Default behavior is to use CompileErrorInspector
+      # to print contextual info about the failure.
+      def file_load_failed(path, exception)
+        description = ErrorMapper.file_load_failed(path, exception)
+        display_error(description)
+      end
+
+      def recipe_not_found(exception)
+        description = ErrorMapper.file_load_failed(nil, exception)
+        display_error(description)
+      end
+
+      # Delegates to #file_loaded
+      def library_file_loaded(path)
+        file_loaded(path)
+      end
+
+      # Delegates to #file_load_failed
+      def library_file_load_failed(path, exception)
+        file_load_failed(path, exception)
+      end
+
+      # Delegates to #file_loaded
+      def lwrp_file_loaded(path)
+        file_loaded(path)
+      end
+
+      # Delegates to #file_load_failed
+      def lwrp_file_load_failed(path, exception)
+        file_load_failed(path, exception)
+      end
+
+      # Delegates to #file_loaded
+      def attribute_file_loaded(path)
+        file_loaded(path)
+      end
+
+      # Delegates to #file_load_failed
+      def attribute_file_load_failed(path, exception)
+        file_load_failed(path, exception)
+      end
+
+      # Delegates to #file_loaded
+      def definition_file_loaded(path)
+        file_loaded(path)
+      end
+
+      # Delegates to #file_load_failed
+      def definition_file_load_failed(path, exception)
+        file_load_failed(path, exception)
+      end
+
+      # Delegates to #file_loaded
+      def recipe_file_loaded(path)
+        file_loaded(path)
+      end
+
+      # Delegates to #file_load_failed
+      def recipe_file_load_failed(path, exception)
+        file_load_failed(path, exception)
+      end
+
+    end
+
+
+    # == NullFormatter
+    # Formatter that doesn't actually produce any ouput. You can use this to
+    # disable the use of output formatters.
+    class NullFormatter < Base
+
+      cli_name(:null)
+
+    end
+
+  end
+end
+
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
new file mode 100644
index 0000000..e403ed9
--- /dev/null
+++ b/lib/chef/formatters/doc.rb
@@ -0,0 +1,236 @@
+require 'chef/formatters/base'
+require 'chef/config'
+
+class Chef
+  module Formatters
+    #--
+    # TODO: not sold on the name, but the output is similar to what rspec calls
+    # "specdoc"
+    class Doc < Formatters::Base
+
+      cli_name(:doc)
+
+      def initialize(out, err)
+        super
+
+        @updated_resources = 0
+      end
+
+      def run_start(version)
+        puts "Starting Chef Client, version #{version}"
+      end
+
+      def run_completed(node)
+        if Chef::Config[:why_run]
+          puts "Chef Client finished, #{@updated_resources} resources would have been updated"
+        else
+          puts "Chef Client finished, #{@updated_resources} resources updated"
+        end
+      end
+
+      def run_failed(exception)
+        if Chef::Config[:why_run]
+          puts "Chef Client failed. #{@updated_resources} resources would have been updated"
+        else
+          puts "Chef Client failed. #{@updated_resources} resources updated"
+        end
+      end
+
+      # Called right after ohai runs.
+      def ohai_completed(node)
+      end
+
+      # Already have a client key, assuming this node has registered.
+      def skipping_registration(node_name, config)
+      end
+
+      # About to attempt to register as +node_name+
+      def registration_start(node_name, config)
+        puts "Creating a new client identity for #{node_name} using the validator key."
+      end
+
+      def registration_completed
+      end
+
+      def node_load_start(node_name, config)
+      end
+
+      # Failed to load node data from the server
+      def node_load_failed(node_name, exception, config)
+        super
+      end
+
+      # Default and override attrs from roles have been computed, but not yet applied.
+      # Normal attrs from JSON have been added to the node.
+      def node_load_completed(node, expanded_run_list, config)
+      end
+
+      # Called before the cookbook collection is fetched from the server.
+      def cookbook_resolution_start(expanded_run_list)
+        puts "resolving cookbooks for run list: #{expanded_run_list.inspect}"
+      end
+
+      # Called when there is an error getting the cookbook collection from the
+      # server.
+      def cookbook_resolution_failed(expanded_run_list, exception)
+        super
+      end
+
+      # Called when the cookbook collection is returned from the server.
+      def cookbook_resolution_complete(cookbook_collection)
+      end
+
+      # Called before unneeded cookbooks are removed
+      def cookbook_clean_start
+      end
+
+      # Called after the file at +path+ is removed. It may be removed if the
+      # cookbook containing it was removed from the run list, or if the file was
+      # removed from the cookbook.
+      def removed_cookbook_file(path)
+      end
+
+      # Called when cookbook cleaning is finished.
+      def cookbook_clean_complete
+      end
+
+      # Called before cookbook sync starts
+      def cookbook_sync_start(cookbook_count)
+        puts "Synchronizing Cookbooks:"
+      end
+
+      # Called when cookbook +cookbook_name+ has been sync'd
+      def synchronized_cookbook(cookbook_name)
+        puts "  - #{cookbook_name}"
+      end
+
+      # Called when an individual file in a cookbook has been updated
+      def updated_cookbook_file(cookbook_name, path)
+      end
+
+      # Called after all cookbooks have been sync'd.
+      def cookbook_sync_complete
+      end
+
+      # Called when cookbook loading starts.
+      def library_load_start(file_count)
+        puts "Compiling Cookbooks..."
+      end
+
+      # Called after a file in a cookbook is loaded.
+      def file_loaded(path)
+      end
+
+      # Called when recipes have been loaded.
+      def recipe_load_complete
+      end
+
+      # Called before convergence starts
+      def converge_start(run_context)
+        puts "Converging #{run_context.resource_collection.all_resources.size} resources"
+      end
+
+      # Called when the converge phase is finished.
+      def converge_complete
+      end
+
+      # Called before action is executed on a resource.
+      def resource_action_start(resource, action, notification_type=nil, notifier=nil)
+        if resource.cookbook_name && resource.recipe_name
+          resource_recipe = "#{resource.cookbook_name}::#{resource.recipe_name}"
+        else
+          resource_recipe = "<Dynamically Defined Resource>"
+        end
+
+        if resource_recipe != @current_recipe
+          puts "Recipe: #{resource_recipe}"
+          @current_recipe = resource_recipe
+        end
+        # TODO: info about notifies
+        print "  * #{resource} action #{action}"
+      end
+
+      # Called when a resource fails, but will retry.
+      def resource_failed_retriable(resource, action, retry_count, exception)
+      end
+
+      # Called when a resource fails and will not be retried.
+      def resource_failed(resource, action, exception)
+        super
+      end
+
+      # Called when a resource action has been skipped b/c of a conditional
+      def resource_skipped(resource, action, conditional)
+        # TODO: more info about conditional
+        puts " (skipped due to #{conditional.short_description})"
+      end
+
+      # Called after #load_current_resource has run.
+      def resource_current_state_loaded(resource, action, current_resource)
+      end
+
+      # Called when a resource has no converge actions, e.g., it was already correct.
+      def resource_up_to_date(resource, action)
+        puts " (up to date)"
+      end
+
+      def resource_bypassed(resource, action, provider)
+        puts " (Skipped: whyrun not supported by provider #{provider.class.name})"
+      end
+
+      def output_record(line)
+
+      end
+
+      # Called when a change has been made to a resource. May be called multiple
+      # times per resource, e.g., a file may have its content updated, and then
+      # its permissions updated.
+      def resource_update_applied(resource, action, update)
+        prefix = Chef::Config[:why_run] ? "Would " : ""
+        Array(update).each do |line|
+          next if line.nil?
+          output_record line
+          if line.kind_of? String
+            @output.color "\n    - #{prefix}#{line}", :green
+          elsif line.kind_of? Array
+            # Expanded output - delta
+            # @todo should we have a resource_update_delta callback?
+            line.each do |detail|
+              @output.color "\n        #{detail}", :white
+            end
+          end
+        end
+      end
+
+      # Called after a resource has been completely converged.
+      def resource_updated(resource, action)
+        @updated_resources += 1
+        puts "\n"
+      end
+
+      # Called when resource current state load is skipped due to the provider
+      # not supporting whyrun mode.
+      def resource_current_state_load_bypassed(resource, action, current_resource)
+        @output.color("\n    * Whyrun not supported for #{resource}, bypassing load.", :yellow)
+      end
+
+      # Called when a provider makes an assumption after a failed assertion
+      # in whyrun mode, in order to allow execution to continue
+      def whyrun_assumption(action, resource, message)
+        return unless message
+        [ message ].flatten.each do |line|
+          @output.color("\n    * #{line}", :yellow)
+        end
+      end
+
+      # Called when an assertion declared by a provider fails
+      def provider_requirement_failed(action, resource, exception, message)
+        return unless message
+        color = Chef::Config[:why_run] ? :yellow : :red
+        [ message ].flatten.each do |line|
+          @output.color("\n    * #{line}", color)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/formatters/error_descriptor.rb b/lib/chef/formatters/error_descriptor.rb
new file mode 100644
index 0000000..3f0756d
--- /dev/null
+++ b/lib/chef/formatters/error_descriptor.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Tyler Cloke (<tyler 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.
+#
+
+class Chef
+  module Formatters
+    # == Formatters::ErrorDescription
+    # Class for displaying errors on STDOUT.
+    class ErrorDescription
+
+      attr_reader :sections
+
+      def initialize(title)
+        @title = title
+        @sections = []
+      end
+
+      def section(heading, text)
+        @sections << {heading => text}
+      end
+
+      def display(out)
+        out.puts "=" * 80
+        out.puts @title, :red
+        out.puts "=" * 80
+        out.puts "\n"
+        sections.each do |section|
+          section.each do |heading, text|
+            display_section(heading, text, out)
+          end
+        end
+      end
+
+      def for_json()
+        {
+          'title' => @title,
+          'sections' => @sections
+        }
+      end
+
+      private
+
+      def display_section(heading, text, out)
+        out.puts heading
+        out.puts "-" * heading.size
+        out.puts text
+        out.puts "\n"
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/formatters/error_inspectors.rb b/lib/chef/formatters/error_inspectors.rb
new file mode 100644
index 0000000..4184573
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors.rb
@@ -0,0 +1,19 @@
+require 'chef/formatters/error_inspectors/node_load_error_inspector'
+require "chef/formatters/error_inspectors/registration_error_inspector"
+require 'chef/formatters/error_inspectors/compile_error_inspector'
+require 'chef/formatters/error_inspectors/resource_failure_inspector'
+require 'chef/formatters/error_inspectors/run_list_expansion_error_inspector'
+require 'chef/formatters/error_inspectors/cookbook_resolve_error_inspector'
+require "chef/formatters/error_inspectors/cookbook_sync_error_inspector"
+
+class Chef
+  module Formatters
+
+    # == ErrorInspectors
+    # Error inspectors wrap exceptions and contextual information. They
+    # generate diagnostic messages about possible causes of the error for user
+    # consumption.
+    module ErrorInspectors
+    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
new file mode 100644
index 0000000..cb64955
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
@@ -0,0 +1,111 @@
+#--
+# Author:: Daniel DeLeo (<dan 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.
+#
+
+class Chef
+  module Formatters
+
+    module APIErrorFormatting
+
+      NETWORK_ERROR_CLASSES = [Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError]
+
+      def describe_network_errors(error_description)
+        error_description.section("Networking Error:",<<-E)
+#{exception.message}
+
+Your chef_server_url may be misconfigured, or the network could be down.
+E
+        error_description.section("Relevant Config Settings:",<<-E)
+chef_server_url  "#{server_url}"
+E
+      end
+
+      def describe_401_error(error_description)
+        if clock_skew?
+          error_description.section("Authentication Error:",<<-E)
+Failed to authenticate to the chef server (http 401).
+The request failed because your clock has drifted by more than 15 minutes.
+Syncing your clock to an NTP Time source should resolve the issue.
+E
+        else
+          error_description.section("Authentication Error:",<<-E)
+Failed to authenticate to the chef server (http 401).
+E
+
+          error_description.section("Server Response:", format_rest_error)
+          error_description.section("Relevant Config Settings:",<<-E)
+chef_server_url   "#{server_url}"
+node_name         "#{username}"
+client_key        "#{api_key}"
+
+If these settings are correct, your client_key may be invalid.
+E
+        end
+      end
+
+      def describe_400_error(error_description)
+        error_description.section("Invalid Request Data:",<<-E)
+The data in your request was invalid (HTTP 400).
+E
+        error_description.section("Server Response:",format_rest_error)
+      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.
+E
+        error_description.section("Server Response:", format_rest_error)
+      end
+
+      def describe_503_error(error_description)
+        error_description.section("Server Unavailable","The Chef Server is temporarily unavailable")
+        error_description.section("Server Response:", format_rest_error)
+      end
+
+
+      # Fallback for unexpected/uncommon http errors
+      def describe_http_error(error_description)
+        error_description.section("Unexpected API Request Failure:", format_rest_error)
+      end
+
+      # Parses JSON from the error response sent by Chef Server and returns the
+      # error message
+      def format_rest_error
+        Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join('; ')
+      rescue Exception
+        exception.response.body
+      end
+
+      def username
+        config[:node_name]
+      end
+
+      def api_key
+        config[:client_key]
+      end
+
+      def server_url
+        config[:chef_server_url]
+      end
+
+      def clock_skew?
+        exception.response.body =~ /synchronize the clock/i
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
new file mode 100644
index 0000000..93328ad
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -0,0 +1,106 @@
+#--
+# Author:: Daniel DeLeo (<dan 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.
+#
+
+class Chef
+  module Formatters
+    module ErrorInspectors
+
+      # == CompileErrorInspector
+      # Wraps exceptions that occur during the compile phase of a Chef run and
+      # tries to find the code responsible for the error.
+      class CompileErrorInspector
+
+        attr_reader :path
+        attr_reader :exception
+
+        def initialize(path, exception)
+          @path, @exception = path, exception
+        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)
+
+            traceback = filtered_bt.map {|line| "  #{line}"}.join("\n")
+            error_description.section("Cookbook Trace:", traceback)
+            error_description.section("Relevant File Content:", context)
+          end
+        end
+
+        def context
+          context_lines = []
+          context_lines << "#{culprit_file}:\n\n"
+          Range.new(display_lower_bound, display_upper_bound).each do |i|
+            line_nr = (i + 1).to_s.rjust(3)
+            indicator = (i + 1) == culprit_line ? ">> " : ":  "
+            context_lines << "#{line_nr}#{indicator}#{file_lines[i]}"
+          end
+          context_lines.join("")
+        end
+
+        def display_lower_bound
+          lower = (culprit_line - 8)
+          lower = 0 if lower < 0
+          lower
+        end
+
+        def display_upper_bound
+          upper = (culprit_line + 8)
+          upper = file_lines.size if upper > file_lines.size
+          upper
+        end
+
+        def file_lines
+          @file_lines ||= IO.readlines(culprit_file)
+        end
+
+        def culprit_backtrace_entry
+          @culprit_backtrace_entry ||= begin
+             bt_entry = filtered_bt.first
+             Chef::Log.debug("backtrace entry for compile error: '#{bt_entry}'")
+             bt_entry
+          end
+        end
+
+        def culprit_line
+          @culprit_line ||= begin
+            line_number = culprit_backtrace_entry[/^(?:.\:)?[^:]+:([\d]+)/,1].to_i
+            Chef::Log.debug("Line number of compile error: '#{line_number}'")
+            line_number
+          end
+        end
+
+        def culprit_file
+          @culprit_file ||= culprit_backtrace_entry[/^((?:.\:)?[^:]+):([\d]+)/,1]
+        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
+        end
+
+      end
+
+    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
new file mode 100644
index 0000000..cfee8a2
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
@@ -0,0 +1,166 @@
+#--
+# Author:: Daniel DeLeo (<dan 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 'chef/formatters/error_inspectors/api_error_formatting'
+
+class Chef
+  module Formatters
+    module ErrorInspectors
+      class CookbookResolveErrorInspector
+
+        attr_reader :exception
+        attr_reader :expanded_run_list
+
+        include APIErrorFormatting
+
+        def initialize(expanded_run_list, exception)
+          @expanded_run_list = expanded_run_list
+          @exception = exception
+        end
+
+        def add_explanation(error_description)
+          case exception
+          when Net::HTTPServerException, Net::HTTPFatalError
+            humanize_http_exception(error_description)
+          when *NETWORK_ERROR_CLASSES
+            describe_network_errors(error_description)
+          else
+            error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}")
+          end
+        end
+
+        def humanize_http_exception(error_description)
+          response = exception.response
+          case response
+          when Net::HTTPUnauthorized
+            # TODO: this is where you'd see conflicts b/c of username/clientname stuff
+            describe_401_error(error_description)
+          when Net::HTTPForbidden
+            # TODO: we're rescuing errors from Node.find_or_create
+            # * could be no write on nodes container
+            # * could be no read on the node
+            error_description.section("Authorization Error",<<-E)
+This client is not authorized to read some of the information required to
+access its coobooks (HTTP 403).
+
+To access its cookbooks, a client needs to be able to read its environment and
+all of the cookbooks in its expanded run list.
+E
+            error_description.section("Expanded Run List:", expanded_run_list_ul)
+            error_description.section("Server Response:", format_rest_error)
+          when Net::HTTPPreconditionFailed
+            describe_412_error(error_description)
+          when Net::HTTPBadRequest
+            describe_400_error(error_description)
+          when Net::HTTPNotFound
+          when Net::HTTPInternalServerError
+            describe_500_error(error_description)
+          when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
+            describe_503_error(error_description)
+          else
+            describe_http_error(error_description)
+          end
+        end
+
+        def describe_412_error(error_description)
+          explanation = ""
+          error_reasons = extract_412_error_message
+
+          # Prepare the error message if there is detailed information
+          # about individual cookbooks.
+          if !error_reasons.respond_to?(:key?)
+            explanation << error_reasons.to_s
+          else
+            if error_reasons.key?("non_existent_cookbooks") && !Array(error_reasons["non_existent_cookbooks"]).empty?
+              explanation << "The following cookbooks are required by the client but don't exist on the server:\n"
+              Array(error_reasons["non_existent_cookbooks"]).each do |cookbook|
+                explanation << "* #{cookbook}\n"
+              end
+              explanation << "\n"
+            end
+            if error_reasons.key?("cookbooks_with_no_versions") && !Array(error_reasons["cookbooks_with_no_versions"]).empty?
+              explanation << "The following cookbooks exist on the server, but there is no version that meets\nthe version constraints in this environment:\n"
+              Array(error_reasons["cookbooks_with_no_versions"]).each do |cookbook|
+                explanation << "* #{cookbook}\n"
+              end
+              explanation << "\n"
+            end
+          end
+
+          if !explanation.empty?
+            error_description.section("Missing Cookbooks:", explanation)
+          else
+            # If we don't have any cookbook details print a more
+            # generic error message.
+            if error_reasons.respond_to?(:key?) && error_reasons["message"]
+              explanation << "Error message: #{error_reasons["message"]}\n"
+            end
+
+            explanation << <<EOM
+You might be able to resolve this issue with:
+  1-) Removing cookbook versions that depend on deleted cookbooks.
+  2-) Removing unused cookbook versions.
+  3-) Pinning exact cookbook versions using environments.
+EOM
+            error_description.section("Cookbook dependency resolution error:", explanation)
+          end
+
+          error_description.section("Expanded Run List:", expanded_run_list_ul)
+        end
+
+        def expanded_run_list_ul
+          @expanded_run_list.map {|i| "* #{i}"}.join("\n")
+        end
+
+        # In my tests, the error from the server is double JSON encoded, but we
+        # should not rely on this not getting fixed.
+        #
+        # Return *should* be a Hash like this:
+        #   { "non_existent_cookbooks"     => ["nope"],
+        #     "cookbooks_with_no_versions" => [],
+        #     "message" => "Run list contains invalid items: no such cookbook nope."}
+        def extract_412_error_message
+          # Example:
+          # "{\"error\":[\"{\\\"non_existent_cookbooks\\\":[\\\"nope\\\"],\\\"cookbooks_with_no_versions\\\":[],\\\"message\\\":\\\"Run list contains invalid items: no such cookbook nope.\\\"}\"]}"
+
+          wrapped_error_message = attempt_json_parse(exception.response.body)
+          unless wrapped_error_message.kind_of?(Hash) && wrapped_error_message.key?("error")
+            return wrapped_error_message.to_s
+          end
+
+          error_description = Array(wrapped_error_message["error"]).first
+          if error_description.kind_of?(Hash)
+            return error_description
+          end
+          attempt_json_parse(error_description)
+        end
+
+        private
+
+        def attempt_json_parse(maybe_json_string)
+          Chef::JSONCompat.from_json(maybe_json_string)
+        rescue Exception
+          maybe_json_string
+        end
+
+
+      end
+    end
+  end
+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
new file mode 100644
index 0000000..56a55a2
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
@@ -0,0 +1,80 @@
+#--
+# Author:: Daniel DeLeo (<dan 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 'chef/formatters/error_inspectors/api_error_formatting'
+
+class Chef
+  module Formatters
+    module ErrorInspectors
+
+      # == CookbookSyncErrorInspector
+      # Generates human-friendly explanations for errors encountered during
+      # cookbook sync.
+      #--
+      # TODO: Not sure what errors are commonly seen during cookbook sync, so
+      # the messaging is kinda generic.
+      class CookbookSyncErrorInspector
+
+        include APIErrorFormatting
+
+        attr_reader :exception
+        attr_reader :cookbooks
+
+        def initialize(cookbooks, exception)
+          @cookbooks, @exception = cookbooks, exception
+        end
+
+        def add_explanation(error_description)
+          case exception
+          when *NETWORK_ERROR_CLASSES
+            describe_network_errors(error_description)
+          when Net::HTTPServerException, Net::HTTPFatalError
+            humanize_http_exception(error_description)
+          else
+            error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}")
+          end
+        end
+
+        def config
+          Chef::Config
+        end
+
+        def humanize_http_exception(error_description)
+          response = exception.response
+          case response
+          when Net::HTTPUnauthorized
+            # TODO: this is where you'd see conflicts b/c of username/clientname stuff
+            describe_401_error(error_description)
+          when Net::HTTPBadRequest
+            describe_400_error(error_description)
+          when Net::HTTPNotFound
+          when Net::HTTPInternalServerError
+            describe_500_error(error_description)
+          when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
+            describe_503_error(error_description)
+          else
+            describe_http_error(error_description)
+          end
+        end
+
+      end
+    end
+  end
+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
new file mode 100644
index 0000000..e257ee3
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
@@ -0,0 +1,125 @@
+#--
+# Author:: Daniel DeLeo (<dan 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 'chef/formatters/error_inspectors/api_error_formatting'
+
+class Chef
+  module Formatters
+    module ErrorInspectors
+
+
+      # == APIErrorInspector
+      # Wraps exceptions caused by API calls to the server.
+      class NodeLoadErrorInspector
+
+        include APIErrorFormatting
+
+        attr_reader :exception
+        attr_reader :node_name
+        attr_reader :config
+
+        def initialize(node_name, exception, config)
+          @node_name = node_name
+          @exception = exception
+          @config = config
+        end
+
+        def add_explanation(error_description)
+          case exception
+          when Net::HTTPServerException, Net::HTTPFatalError
+            humanize_http_exception(error_description)
+          when *NETWORK_ERROR_CLASSES
+            describe_network_errors(error_description)
+          when Chef::Exceptions::PrivateKeyMissing
+            error_description.section("Private Key Not Found:",<<-E)
+Your private key could not be loaded. If the key file exists, ensure that it is
+readable by chef-client.
+E
+            error_description.section("Relevant Config Settings:",<<-E)
+client_key        "#{api_key}"
+E
+          else
+            error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}")
+          end
+        end
+
+        def humanize_http_exception(error_description)
+          response = exception.response
+          case response
+          when Net::HTTPUnauthorized
+            # TODO: this is where you'd see conflicts b/c of username/clientname stuff
+            describe_401_error(error_description)
+          when Net::HTTPForbidden
+            # TODO: we're rescuing errors from Node.find_or_create
+            # * could be no write on nodes container
+            # * could be no read on the node
+            error_description.section("Authorization Error",<<-E)
+Your client is not authorized to load the node data (HTTP 403).
+E
+            error_description.section("Server Response:", format_rest_error)
+
+            error_description.section("Possible Causes:",<<-E)
+* Your client (#{username}) may have misconfigured authorization permissions.
+E
+          when Net::HTTPBadRequest
+            describe_400_error(error_description)
+          when Net::HTTPNotFound
+            describe_404_error(error_description)
+          when Net::HTTPInternalServerError
+            describe_500_error(error_description)
+          when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
+            describe_503_error(error_description)
+          else
+            describe_http_error(error_description)
+          end
+        end
+
+        # Custom 404 error messaging. Users sometimes see 404s when they have
+        # misconfigured server URLs, and the wrong one redirects to the new
+        # one, e.g., PUT http://wrong.url/nodes/node-name becomes a GET after a
+        # redirect.
+        def describe_404_error(error_description)
+          error_description.section("Resource Not Found:",<<-E)
+The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect.
+E
+          error_description.section("Relevant Config Settings:",<<-E)
+chef_server_url "#{server_url}"
+E
+        end
+
+        def username
+          config[:node_name]
+        end
+
+        def api_key
+          config[:client_key]
+        end
+
+        def server_url
+          config[:chef_server_url]
+        end
+
+        def clock_skew?
+          exception.response.body =~ /synchronize the clock/i
+        end
+
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
new file mode 100644
index 0000000..f31b348
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
@@ -0,0 +1,141 @@
+class Chef
+  module Formatters
+    module ErrorInspectors
+
+      # == RegistrationErrorInspector
+      # Wraps exceptions that occur during the client registration process and
+      # suggests possible causes.
+      #--
+      # TODO: Lots of duplication with the node_load_error_inspector, just
+      # slightly tweaked to talk about validation keys instead of other keys.
+      class RegistrationErrorInspector
+        attr_reader :exception
+        attr_reader :node_name
+        attr_reader :config
+
+        def initialize(node_name, exception, config)
+          @node_name = node_name
+          @exception = exception
+          @config = config
+        end
+
+        def add_explanation(error_description)
+          case exception
+          when Net::HTTPServerException, Net::HTTPFatalError
+            humanize_http_exception(error_description)
+          when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
+            error_description.section("Network Error:",<<-E)
+There was a network error connecting to the Chef Server:
+#{exception.message}
+E
+            error_description.section("Relevant Config Settings:",<<-E)
+chef_server_url  "#{server_url}"
+
+If your chef_server_url is correct, your network could be down.
+E
+          when Chef::Exceptions::PrivateKeyMissing
+            error_description.section("Private Key Not Found:",<<-E)
+Your private key could not be loaded. If the key file exists, ensure that it is
+readable by chef-client.
+E
+            error_description.section("Relevant Config Settings:",<<-E)
+validation_key "#{api_key}"
+E
+          when Chef::Exceptions::InvalidRedirect
+            error_description.section("Invalid Redirect:",<<-E)
+Change your server location in client.rb to the server's FQDN to avoid unwanted redirections.
+E
+          else
+            "#{exception.class.name}: #{exception.message}"
+          end
+        end
+
+        def humanize_http_exception(error_description)
+          response = exception.response
+          case response
+          when Net::HTTPUnauthorized
+            if clock_skew?
+              error_description.section("Authentication Error:",<<-E)
+Failed to authenticate to the chef server (http 401).
+The request failed because your clock has drifted by more than 15 minutes.
+Syncing your clock to an NTP Time source should resolve the issue.
+E
+            else
+              error_description.section("Authentication Error:",<<-E)
+Failed to authenticate to the chef server (http 401).
+E
+
+              error_description.section("Server Response:", format_rest_error)
+              error_description.section("Relevant Config Settings:",<<-E)
+chef_server_url         "#{server_url}"
+validation_client_name  "#{username}"
+validation_key          "#{api_key}"
+
+If these settings are correct, your validation_key may be invalid.
+E
+            end
+          when Net::HTTPForbidden
+            error_description.section("Authorization Error:",<<-E)
+Your validation client is not authorized to create the client for this node (HTTP 403).
+E
+            error_description.section("Possible Causes:",<<-E)
+* There may already be a client named "#{config[:node_name]}"
+* Your validation client (#{username}) may have misconfigured authorization permissions.
+E
+          when Net::HTTPBadRequest
+            error_description.section("Invalid Request Data:",<<-E)
+The data in your request was invalid (HTTP 400).
+E
+            error_description.section("Server Response:",format_rest_error)
+          when Net::HTTPNotFound
+            error_description.section("Resource Not Found:",<<-E)
+The server returned a HTTP 404. This usually indicates that your chef_server_url is incorrect.
+E
+            error_description.section("Relevant Config Settings:",<<-E)
+chef_server_url "#{server_url}"
+E
+          when Net::HTTPInternalServerError
+            error_description.section("Unknown Server Error:",<<-E)
+The server had a fatal error attempting to load the node data.
+E
+            error_description.section("Server Response:", format_rest_error)
+          when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
+            error_description.section("Server Unavailable","The Chef Server is temporarily unavailable")
+            error_description.section("Server Response:", format_rest_error)
+          else
+            error_description.section("Unexpected API Request Failure:", format_rest_error)
+          end
+        end
+
+        def username
+          #config[:node_name]
+          config[:validation_client_name]
+        end
+
+        def api_key
+          config[:validation_key]
+          #config[:client_key]
+        end
+
+        def server_url
+          config[:chef_server_url]
+        end
+
+        def clock_skew?
+          exception.response.body =~ /synchronize the clock/i
+        end
+
+        # Parses JSON from the error response sent by Chef Server and returns the
+        # error message
+        #--
+        # TODO: this code belongs in Chef::REST
+        def format_rest_error
+          Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join('; ')
+        rescue Exception
+          exception.response.body
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
new file mode 100644
index 0000000..6f1f71b
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
@@ -0,0 +1,117 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Tyler Cloke (<tyler 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.
+#
+
+class Chef
+  module Formatters
+    module ErrorInspectors
+      class ResourceFailureInspector
+
+        attr_reader :resource
+        attr_reader :action
+        attr_reader :exception
+
+        def initialize(resource, action, exception)
+          @resource = resource
+          @action = action
+          @exception = exception
+        end
+
+        def add_explanation(error_description)
+          error_description.section(exception.class.name, exception.message)
+
+          unless filtered_bt.empty?
+            error_description.section("Cookbook Trace:", filtered_bt.join("\n"))
+          end
+
+          unless dynamic_resource?
+            error_description.section("Resource Declaration:", recipe_snippet)
+          end
+
+          error_description.section("Compiled Resource:", "#{resource.to_text}")
+
+          # Template errors get wrapped in an exception class that can show the relevant template code,
+          # so add them to the error output.
+          if exception.respond_to?(:source_listing)
+            error_description.section("Template Context:", "#{exception.source_location}\n#{exception.source_listing}")
+          end
+
+          if Chef::Platform.windows?
+            require 'chef/win32/security'
+
+            if !Chef::ReservedNames::Win32::Security.has_admin_privileges?
+              error_description.section("Missing Windows Admin Privileges", "chef-client doesn't have administrator privileges. This can be a possible reason for the resource failure.")
+            end
+          end
+        end
+
+        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
+              return nil unless ::File.exists?(file)
+              lines = IO.readlines(file)
+
+              relevant_lines = ["# In #{file}\n\n"]
+
+
+              current_line = line - 1
+              current_line = 0 if current_line < 0
+              nesting = 0
+
+              loop do
+
+                # low rent parser. try to gracefully handle nested blocks in resources
+                nesting += 1 if lines[current_line] =~ /[\s]+do[\s]*/
+                nesting -= 1 if lines[current_line] =~ /end[\s]*$/
+
+                relevant_lines << format_line(current_line, lines[current_line])
+
+                break if lines[current_line + 1].nil?
+                break if current_line >= (line + 50)
+                break if nesting <= 0
+
+                current_line += 1
+              end
+              relevant_lines << format_line(current_line + 1, lines[current_line + 1]) if lines[current_line + 1]
+              relevant_lines.join("")
+            end
+          end
+        end
+
+        def dynamic_resource?
+          !resource.source_line
+        end
+
+        def filtered_bt
+          filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/ }
+          exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
+        end
+
+        private
+
+        def format_line(line_nr, line)
+          # Print line number as 1-indexed not zero
+          line_nr_string = (line_nr + 1).to_s.rjust(3) + ": "
+          line_nr_string + line
+        end
+
+      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
new file mode 100644
index 0000000..ac19a98
--- /dev/null
+++ b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
@@ -0,0 +1,118 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Tyler Cloke (<tyler 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 'chef/formatters/error_inspectors/api_error_formatting'
+
+class Chef
+  module Formatters
+    module ErrorInspectors
+      class RunListExpansionErrorInspector
+
+        include APIErrorFormatting
+
+        attr_reader :exception
+        attr_reader :node
+
+        def initialize(node, exception)
+          @node, @exception = node, exception
+        end
+
+        def add_explanation(error_description)
+          case exception
+          when Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, SocketError
+            error_description.section("Networking Error:",<<-E)
+#{exception.message}
+
+Your chef_server_url may be misconfigured, or the network could be down.
+E
+          when Net::HTTPServerException, Net::HTTPFatalError
+            humanize_http_exception(error_description)
+          when Chef::Exceptions::MissingRole
+            describe_missing_role(error_description)
+          else
+            error_description.section("Unexpected Error:","#{exception.class.name}: #{exception.message}")
+          end
+        end
+
+        def describe_missing_role(error_description)
+          error_description.section("Missing Role(s) in Run List:", missing_roles_explained)
+          original_run_list = node.run_list.map {|item| "* #{item}"}.join("\n")
+          error_description.section("Original Run List", original_run_list)
+        end
+
+        def missing_roles_explained
+          run_list_expansion.missing_roles_with_including_role.map do |role, includer|
+            "* #{role} included by '#{includer}'"
+          end.join("\n")
+        end
+
+        def run_list_expansion
+          exception.expansion
+        end
+
+        def config
+          Chef::Config
+        end
+
+        def humanize_http_exception(error_description)
+          response = exception.response
+          case response
+          when Net::HTTPUnauthorized
+            error_description.section("Authentication Error:",<<-E)
+Failed to authenticate to the chef server (http 401).
+E
+
+            error_description.section("Server Response:", format_rest_error)
+            error_description.section("Relevant Config Settings:",<<-E)
+chef_server_url   "#{server_url}"
+node_name         "#{username}"
+client_key        "#{api_key}"
+
+If these settings are correct, your client_key may be invalid.
+E
+          when Net::HTTPForbidden
+            # TODO: we're rescuing errors from Node.find_or_create
+            # * could be no write on nodes container
+            # * could be no read on the node
+            error_description.section("Authorization Error",<<-E)
+Your client is not authorized to load one or more of your roles (HTTP 403).
+E
+            error_description.section("Server Response:", format_rest_error)
+
+            error_description.section("Possible Causes:",<<-E)
+* Your client (#{username}) may have misconfigured authorization permissions.
+E
+          when Net::HTTPInternalServerError
+            error_description.section("Unknown Server Error:",<<-E)
+The server had a fatal error attempting to load a role.
+E
+            error_description.section("Server Response:", format_rest_error)
+          when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
+            error_description.section("Server Unavailable","The Chef Server is temporarily unavailable")
+            error_description.section("Server Response:", format_rest_error)
+          else
+            error_description.section("Unexpected API Request Failure:", format_rest_error)
+          end
+        end
+
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/formatters/error_mapper.rb b/lib/chef/formatters/error_mapper.rb
new file mode 100644
index 0000000..2140c63
--- /dev/null
+++ b/lib/chef/formatters/error_mapper.rb
@@ -0,0 +1,85 @@
+#--
+# Author:: Tyler Cloke (<tyler 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.
+#
+
+class Chef
+  module Formatters
+    # == Formatters::ErrorMapper
+    # Collection of methods for creating and returning
+    # Formatters::ErrorDescription objects based on node,
+    # exception, and configuration information.
+    module ErrorMapper
+
+      # Failed to register this client with the server.
+      def self.registration_failed(node_name, exception, config)
+        error_inspector = ErrorInspectors::RegistrationErrorInspector.new(node_name, exception, config)
+        headline = "Chef encountered an error attempting to create the client \"#{node_name}\""
+        description = ErrorDescription.new(headline)
+        error_inspector.add_explanation(description)
+        return description
+      end
+
+      def self.node_load_failed(node_name, exception, config)
+        error_inspector = ErrorInspectors::NodeLoadErrorInspector.new(node_name, exception, config)
+        headline = "Chef encountered an error attempting to load the node data for \"#{node_name}\""
+        description = ErrorDescription.new(headline)
+        error_inspector.add_explanation(description)
+        return description
+      end
+
+      def self.run_list_expand_failed(node, exception)
+        error_inspector = ErrorInspectors::RunListExpansionErrorInspector.new(node, exception)
+        headline = "Error expanding the run_list:"
+        description = ErrorDescription.new(headline)
+        error_inspector.add_explanation(description)
+        return description
+      end
+
+      def self.cookbook_resolution_failed(expanded_run_list, exception)
+        error_inspector = ErrorInspectors::CookbookResolveErrorInspector.new(expanded_run_list, exception)
+        headline = "Error Resolving Cookbooks for Run List:"
+        description = ErrorDescription.new(headline)
+        error_inspector.add_explanation(description)
+        return description
+      end
+
+      def self.cookbook_sync_failed(cookbooks, exception)
+        error_inspector = ErrorInspectors::CookbookSyncErrorInspector.new(cookbooks, exception)
+        headline = "Error Syncing Cookbooks:"
+        description = ErrorDescription.new(headline)
+        error_inspector.add_explanation(description)
+        return description
+      end
+
+      def self.resource_failed(resource, action, exception)
+        error_inspector = ErrorInspectors::ResourceFailureInspector.new(resource, action, exception)
+        headline = "Error executing action `#{action}` on resource '#{resource}'"
+        description = ErrorDescription.new(headline)
+        error_inspector.add_explanation(description)
+        return description
+      end
+
+      def self.file_load_failed(path, exception)
+        error_inspector = ErrorInspectors::CompileErrorInspector.new(path, exception)
+        headline = "Recipe Compile Error" + ( path ? " in #{path}" : "" )
+        description = ErrorDescription.new(headline)
+        error_inspector.add_explanation(description)
+        description
+      end
+    end
+  end
+end
diff --git a/lib/chef/formatters/minimal.rb b/lib/chef/formatters/minimal.rb
new file mode 100644
index 0000000..a189cc6
--- /dev/null
+++ b/lib/chef/formatters/minimal.rb
@@ -0,0 +1,235 @@
+require 'chef/formatters/base'
+
+class Chef
+
+  module Formatters
+
+
+    # == Formatters::Minimal
+    # Shows the progress of the chef run by printing single characters, and
+    # displays a summary of updates at the conclusion of the run. For events
+    # that don't have meaningful status information (loading a file, syncing a
+    # cookbook) a dot is printed. For resources, a dot, 'S' or 'U' is printed
+    # if the resource is up to date, skipped by not_if/only_if, or updated,
+    # respectively.
+    class Minimal < Formatters::Base
+
+      cli_name(:minimal)
+      cli_name(:min)
+
+      attr_reader :updated_resources
+      attr_reader :updates_by_resource
+
+
+      def initialize(out, err)
+        super
+        @updated_resources = []
+        @updates_by_resource = Hash.new {|h, k| h[k] = []}
+      end
+
+      # Called at the very start of a Chef Run
+      def run_start(version)
+        puts "Starting Chef Client, version #{version}"
+      end
+
+      # Called at the end of the Chef run.
+      def run_completed(node)
+        puts "chef client finished, #{@updated_resources.size} resources updated"
+      end
+
+      # called at the end of a failed run
+      def run_failed(exception)
+        puts "chef client failed. #{@updated_resources.size} resources updated"
+      end
+
+      # Called right after ohai runs.
+      def ohai_completed(node)
+      end
+
+      # Already have a client key, assuming this node has registered.
+      def skipping_registration(node_name, config)
+      end
+
+      # About to attempt to register as +node_name+
+      def registration_start(node_name, config)
+      end
+
+      def registration_completed
+      end
+
+      # Failed to register this client with the server.
+      def registration_failed(node_name, exception, config)
+        super
+      end
+
+      def node_load_start(node_name, config)
+      end
+
+      # Failed to load node data from the server
+      def node_load_failed(node_name, exception, config)
+      end
+
+      # Default and override attrs from roles have been computed, but not yet applied.
+      # Normal attrs from JSON have been added to the node.
+      def node_load_completed(node, expanded_run_list, config)
+      end
+
+      # Called before the cookbook collection is fetched from the server.
+      def cookbook_resolution_start(expanded_run_list)
+        puts "resolving cookbooks for run list: #{expanded_run_list.inspect}"
+      end
+
+      # Called when there is an error getting the cookbook collection from the
+      # server.
+      def cookbook_resolution_failed(expanded_run_list, exception)
+      end
+
+      # Called when the cookbook collection is returned from the server.
+      def cookbook_resolution_complete(cookbook_collection)
+      end
+
+      # Called before unneeded cookbooks are removed
+      #--
+      # TODO: Should be called in CookbookVersion.sync_cookbooks
+      def cookbook_clean_start
+      end
+
+      # Called after the file at +path+ is removed. It may be removed if the
+      # cookbook containing it was removed from the run list, or if the file was
+      # removed from the cookbook.
+      def removed_cookbook_file(path)
+      end
+
+      # Called when cookbook cleaning is finished.
+      def cookbook_clean_complete
+      end
+
+      # Called before cookbook sync starts
+      def cookbook_sync_start(cookbook_count)
+        puts "Synchronizing cookbooks"
+      end
+
+      # Called when cookbook +cookbook_name+ has been sync'd
+      def synchronized_cookbook(cookbook_name)
+        print "."
+      end
+
+      # Called when an individual file in a cookbook has been updated
+      def updated_cookbook_file(cookbook_name, path)
+      end
+
+      # Called after all cookbooks have been sync'd.
+      def cookbook_sync_complete
+        puts "done."
+      end
+
+      # Called when cookbook loading starts.
+      def library_load_start(file_count)
+        puts "Compiling cookbooks"
+      end
+
+      # Called after a file in a cookbook is loaded.
+      def file_loaded(path)
+        print '.'
+      end
+
+      def file_load_failed(path, exception)
+        super
+      end
+
+      # Called when recipes have been loaded.
+      def recipe_load_complete
+        puts "done."
+      end
+
+      # Called before convergence starts
+      def converge_start(run_context)
+        puts "Converging #{run_context.resource_collection.all_resources.size} resources"
+      end
+
+      # Called when the converge phase is finished.
+      def converge_complete
+        puts "\n"
+        puts "System converged."
+        if updated_resources.empty?
+          puts "no resources updated"
+        else
+          puts "\n"
+          puts "resources updated this run:"
+          updated_resources.each do |resource|
+            puts "* #{resource.to_s}"
+            updates_by_resource[resource.name].flatten.each do |update|
+              puts "  - #{update}"
+            end
+            puts "\n"
+          end
+        end
+      end
+
+      # Called before action is executed on a resource.
+      def resource_action_start(resource, action, notification_type=nil, notifier=nil)
+      end
+
+      # Called when a resource fails, but will retry.
+      def resource_failed_retriable(resource, action, retry_count, exception)
+      end
+
+      # Called when a resource fails and will not be retried.
+      def resource_failed(resource, action, exception)
+      end
+
+      # Called when a resource action has been skipped b/c of a conditional
+      def resource_skipped(resource, action, conditional)
+        print "S"
+      end
+
+      # Called after #load_current_resource has run.
+      def resource_current_state_loaded(resource, action, current_resource)
+      end
+
+      # Called when a resource has no converge actions, e.g., it was already correct.
+      def resource_up_to_date(resource, action)
+        print "."
+      end
+
+      ## TODO: callback for assertion failures
+
+      ## TODO: callback for assertion fallback in why run
+
+      # Called when a change has been made to a resource. May be called multiple
+      # times per resource, e.g., a file may have its content updated, and then
+      # its permissions updated.
+      def resource_update_applied(resource, action, update)
+        @updates_by_resource[resource.name] << Array(update)[0]
+      end
+
+      # Called after a resource has been completely converged.
+      def resource_updated(resource, action)
+        updated_resources << resource
+        print "U"
+      end
+
+      # Called before handlers run
+      def handlers_start(handler_count)
+      end
+
+      # Called after an individual handler has run
+      def handler_executed(handler)
+      end
+
+      # Called after all handlers have executed
+      def handlers_completed
+      end
+
+      # An uncategorized message. This supports the case that a user needs to
+      # pass output that doesn't fit into one of the callbacks above. Note that
+      # there's no semantic information about the content or importance of the
+      # message. That means that if you're using this too often, you should add a
+      # callback for it.
+      def msg(message)
+      end
+
+    end
+  end
+end
+
diff --git a/lib/chef/handler.rb b/lib/chef/handler.rb
index 5d58f90..c4b729e 100644
--- a/lib/chef/handler.rb
+++ b/lib/chef/handler.rb
@@ -86,10 +86,14 @@ class Chef
     # Run the report handlers. This will usually be called by a notification
     # from Chef::Client
     def self.run_report_handlers(run_status)
+      events = run_status.events
+      events.handlers_start(report_handlers.size)
       Chef::Log.info("Running report handlers")
       report_handlers.each do |handler|
         handler.run_report_safely(run_status)
+        events.handler_executed(handler)
       end
+      events.handlers_completed
       Chef::Log.info("Report handlers complete")
     end
 
@@ -107,10 +111,14 @@ class Chef
     # Run the exception handlers. Usually will be called by a notification
     # from Chef::Client when the run fails.
     def self.run_exception_handlers(run_status)
+      events = run_status.events
+      events.handlers_start(exception_handlers.size)
       Chef::Log.error("Running exception handlers")
       exception_handlers.each do |handler|
         handler.run_report_safely(run_status)
+        events.handler_executed(handler)
       end
+      events.handlers_completed
       Chef::Log.error("Exception handlers complete")
     end
 
diff --git a/lib/chef/handler/error_report.rb b/lib/chef/handler/error_report.rb
index dc47ed5..8bf6764 100644
--- a/lib/chef/handler/error_report.rb
+++ b/lib/chef/handler/error_report.rb
@@ -24,7 +24,7 @@ class Chef
     class ErrorReport < ::Chef::Handler
 
       def report
-        Chef::FileCache.store("failed-run-data.json", Chef::JSONCompat.to_json_pretty(data))
+        Chef::FileCache.store("failed-run-data.json", Chef::JSONCompat.to_json_pretty(data), 0640)
         Chef::Log.fatal("Saving node information to #{Chef::FileCache.load("failed-run-data.json", false)}")
       end
 
diff --git a/lib/chef/handler/json_file.rb b/lib/chef/handler/json_file.rb
index 977c5a2..3473db1 100644
--- a/lib/chef/handler/json_file.rb
+++ b/lib/chef/handler/json_file.rb
@@ -41,11 +41,11 @@ class Chef
         build_report_dir
         savetime = Time.now.strftime("%Y%m%d%H%M%S")
         File.open(File.join(config[:path], "chef-run-report-#{savetime}.json"), "w") do |file|
-          
+
           #ensure start time and end time are output in the json properly in the event activesupport happens to be on the system
           run_data = data
           run_data[:start_time] = run_data[:start_time].to_s
-          run_data[:end_time] = run_data[:end_time].to_s          
+          run_data[:end_time] = run_data[:end_time].to_s
 
           file.puts Chef::JSONCompat.to_json_pretty(run_data)
         end
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
new file mode 100644
index 0000000..2b24668
--- /dev/null
+++ b/lib/chef/http.rb
@@ -0,0 +1,386 @@
+#--
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Thom May (<thom at clearairturbulence.org>)
+# Author:: Nuo Yan (<nuo at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010, 2013 Opscode, Inc.
+# 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 'net/https'
+require 'uri'
+require 'chef/http/basic_client'
+require 'chef/monkey_patches/string'
+require 'chef/monkey_patches/net_http'
+require 'chef/config'
+require 'chef/exceptions'
+
+class Chef
+
+  # == Chef::HTTP
+  # Basic HTTP client, with support for adding features via middleware
+  class HTTP
+
+    # Class for applying middleware behaviors to streaming
+    # responses. Collects stream handlers (if any) from each
+    # middleware. When #handle_chunk is called, the chunk gets
+    # passed to all handlers in turn for processing.
+    class StreamHandler
+      def initialize(middlewares, response)
+        middlewares = middlewares.flatten
+        @stream_handlers = []
+        middlewares.each do |middleware|
+          stream_handler = middleware.stream_response_handler(response)
+          @stream_handlers << stream_handler unless stream_handler.nil?
+        end
+      end
+
+      def handle_chunk(next_chunk)
+        @stream_handlers.inject(next_chunk) do |chunk, handler|
+          handler.handle_chunk(chunk)
+        end
+      end
+
+    end
+
+
+    def self.middlewares
+      @middlewares ||= []
+    end
+
+    def self.use(middleware_class)
+      middlewares << middleware_class
+    end
+
+    attr_reader :url
+    attr_reader :sign_on_redirect
+    attr_reader :redirect_limit
+
+    attr_reader :middlewares
+
+    # Create a HTTP client object. The supplied +url+ is used as the base for
+    # all subsequent requests. For example, when initialized with a base url
+    # http://localhost:4000, a call to +get+ with 'nodes' will make an
+    # HTTP GET request to http://localhost:4000/nodes
+    def initialize(url, options={})
+      @url = url
+      @default_headers = options[:headers] || {}
+      @sign_on_redirect = true
+      @redirects_followed = 0
+      @redirect_limit = 10
+
+      @middlewares = []
+      self.class.middlewares.each do |middleware_class|
+        @middlewares << middleware_class.new(options)
+      end
+    end
+
+    # Send an HTTP HEAD request to the path
+    #
+    # === Parameters
+    # path:: path part of the request URL
+    def head(path, headers={})
+      request(:HEAD, path, headers)
+    end
+
+    # Send an HTTP GET request to the path
+    #
+    # === Parameters
+    # path:: The path to GET
+    def get(path, headers={})
+      request(:GET, path, headers)
+    end
+
+    # Send an HTTP PUT request to the path
+    #
+    # === Parameters
+    # path:: path part of the request URL
+    def put(path, json, headers={})
+      request(:PUT, path, headers, json)
+    end
+
+    # Send an HTTP POST request to the path
+    #
+    # === Parameters
+    # path:: path part of the request URL
+    def post(path, json, headers={})
+      request(:POST, path, headers, json)
+    end
+
+    # Send an HTTP DELETE request to the path
+    #
+    # === Parameters
+    # path:: path part of the request URL
+    def delete(path, headers={})
+      request(:DELETE, path, headers)
+    end
+
+    # Makes an HTTP request to +path+ with the given +method+, +headers+, and
+    # +data+ (if applicable).
+    def request(method, path, headers={}, data=false)
+      url = create_url(path)
+      method, url, headers, data = apply_request_middleware(method, url, headers, data)
+
+      response, rest_request, return_value = send_http_request(method, url, headers, data)
+      response, rest_request, return_value = apply_response_middleware(response, rest_request, return_value)
+      response.error! unless success_response?(response)
+      return_value
+    rescue Exception => exception
+      log_failed_request(response, return_value) unless response.nil?
+
+      if exception.respond_to?(:chef_rest_request=)
+        exception.chef_rest_request = rest_request
+      end
+      raise
+    end
+
+    # Makes a streaming download request, streaming the response body to a
+    # tempfile. If a block is given, the tempfile is passed to the block and
+    # the tempfile will automatically be unlinked after the block is executed.
+    #
+    # If no block is given, the tempfile is returned, which means it's up to
+    # you to unlink the tempfile when you're done with it.
+    def streaming_request(path, headers={}, &block)
+      url = create_url(path)
+      response, rest_request, return_value = nil, nil, nil
+      tempfile = nil
+
+      method = :GET
+      method, url, headers, data = apply_request_middleware(method, url, headers, data)
+
+      response, rest_request, return_value = send_http_request(method, url, headers, data) do |http_response|
+        if http_response.kind_of?(Net::HTTPSuccess)
+          tempfile = stream_to_tempfile(url, http_response)
+          if block_given?
+            begin
+              yield tempfile
+            ensure
+              tempfile && tempfile.close!
+            end
+          end
+        end
+      end
+      unless response.kind_of?(Net::HTTPSuccess) or response.kind_of?(Net::HTTPRedirection)
+        response.error!
+      end
+      tempfile
+    rescue Exception => e
+      log_failed_request(response, return_value) unless response.nil?
+      if e.respond_to?(:chef_rest_request=)
+        e.chef_rest_request = rest_request
+      end
+      raise
+    end
+
+    def http_client(base_url=nil)
+      base_url ||= url
+      BasicClient.new(base_url)
+    end
+
+    protected
+
+    def create_url(path)
+      return path if path.is_a?(URI)
+      if path =~ /^(http|https):\/\//
+        URI.parse(path)
+      elsif path.nil? or path.empty?
+        URI.parse(@url)
+      else
+        URI.parse("#{@url}/#{path}")
+      end
+    end
+
+    def apply_request_middleware(method, url, headers, data)
+      middlewares.inject([method, url, headers, data]) do |req_data, middleware|
+        middleware.handle_request(*req_data)
+      end
+    end
+
+    def apply_response_middleware(response, rest_request, return_value)
+      middlewares.reverse.inject([response, rest_request, return_value]) do |res_data, middleware|
+        middleware.handle_response(*res_data)
+      end
+    end
+
+    def log_failed_request(response, return_value)
+      return_value ||= {}
+      error_message = "HTTP Request Returned #{response.code} #{response.message}: "
+      error_message << (return_value["error"].respond_to?(:join) ? return_value["error"].join(", ") : return_value["error"].to_s)
+      Chef::Log.info(error_message)
+    end
+
+    def success_response?(response)
+      response.kind_of?(Net::HTTPSuccess) || response.kind_of?(Net::HTTPRedirection)
+    end
+
+    # Runs a synchronous HTTP request, with no middleware applied (use #request
+    # to have the middleware applied). The entire response will be loaded into memory.
+    def send_http_request(method, url, headers, body, &response_handler)
+      headers = build_headers(method, url, headers, body)
+
+      retrying_http_errors(url) do
+        client = http_client(url)
+        return_value = nil
+        if block_given?
+          request, response = client.request(method, url, body, headers, &response_handler)
+        else
+          request, response = client.request(method, url, body, headers) {|r| r.read_body }
+          return_value = response.read_body
+        end
+        @last_response = response
+
+        Chef::Log.debug("---- HTTP Status and Header Data: ----")
+        Chef::Log.debug("HTTP #{response.http_version} #{response.code} #{response.msg}")
+
+        response.each do |header, value|
+          Chef::Log.debug("#{header}: #{value}")
+        end
+        Chef::Log.debug("---- End HTTP Status/Header Data ----")
+
+        if response.kind_of?(Net::HTTPSuccess)
+          [response, request, return_value]
+        elsif response.kind_of?(Net::HTTPNotModified) # Must be tested before Net::HTTPRedirection because it's subclass.
+          [response, request, false]
+        elsif redirect_location = redirected_to(response)
+          if [:GET, :HEAD].include?(method)
+            follow_redirect do
+              send_http_request(method, create_url(redirect_location), headers, body, &response_handler)
+            end
+          else
+            raise Exceptions::InvalidRedirect, "#{method} request was redirected from #{url} to #{redirect_location}. Only GET and HEAD support redirects."
+          end
+        else
+          [response, request, nil]
+        end
+      end
+    end
+
+
+    # Wraps an HTTP request with retry logic.
+    # === Arguments
+    # url:: URL of the request, used for error messages
+    def retrying_http_errors(url)
+      http_attempts = 0
+      begin
+        http_attempts += 1
+
+        yield
+
+      rescue SocketError, Errno::ETIMEDOUT => e
+        e.message.replace "Error connecting to #{url} - #{e.message}"
+        raise e
+      rescue Errno::ECONNREFUSED
+        if http_retry_count - http_attempts + 1 > 0
+          Chef::Log.error("Connection refused connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
+          sleep(http_retry_delay)
+          retry
+        end
+        raise Errno::ECONNREFUSED, "Connection refused connecting to #{url}, giving up"
+      rescue Timeout::Error
+        if http_retry_count - http_attempts + 1 > 0
+          Chef::Log.error("Timeout connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
+          sleep(http_retry_delay)
+          retry
+        end
+        raise Timeout::Error, "Timeout connecting to #{url}, giving up"
+      rescue Net::HTTPFatalError => e
+        if http_retry_count - http_attempts + 1 > 0
+          sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts)
+          Chef::Log.error("Server returned error for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s")
+          sleep(sleep_time)
+          retry
+        end
+        raise
+      end
+    end
+
+    def http_retry_delay
+      config[:http_retry_delay]
+    end
+
+    def http_retry_count
+      config[:http_retry_count]
+    end
+
+    def config
+      Chef::Config
+    end
+
+    def follow_redirect
+      raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit
+      @redirects_followed += 1
+      Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}")
+
+      yield
+    ensure
+      @redirects_followed = 0
+    end
+
+    private
+
+    def redirected_to(response)
+      return nil  unless response.kind_of?(Net::HTTPRedirection)
+      # Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this
+      return nil  if response.kind_of?(Net::HTTPNotModified)
+      response['location']
+    end
+
+    def build_headers(method, url, headers={}, json_body=false)
+      headers                 = @default_headers.merge(headers)
+      headers['Content-Length'] = json_body.bytesize.to_s if json_body
+      headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers]
+      headers
+    end
+
+    def stream_to_tempfile(url, response)
+      tf = Tempfile.open("chef-rest")
+      if Chef::Platform.windows?
+        tf.binmode # required for binary files on Windows platforms
+      end
+      Chef::Log.debug("Streaming download from #{url.to_s} to tempfile #{tf.path}")
+      # Stolen from http://www.ruby-forum.com/topic/166423
+      # Kudos to _why!
+
+      stream_handler = StreamHandler.new(middlewares, response)
+
+      response.read_body do |chunk|
+        tf.write(stream_handler.handle_chunk(chunk))
+      end
+      tf.close
+      tf
+    rescue Exception
+      tf.close!
+      raise
+    end
+
+
+    public
+
+    ############################################################################
+    # DEPRECATED
+    ############################################################################
+
+    # This is only kept around to provide access to cache control data in
+    # lib/chef/provider/remote_file/http.rb
+    # Find a better API.
+    def last_response
+      @last_response
+    end
+
+  end
+end
+
diff --git a/lib/chef/http/auth_credentials.rb b/lib/chef/http/auth_credentials.rb
new file mode 100644
index 0000000..bd73524
--- /dev/null
+++ b/lib/chef/http/auth_credentials.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Thom May (<thom at clearairturbulence.org>)
+# Author:: Nuo Yan (<nuo at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010 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/log'
+require 'mixlib/authentication/signedheaderauth'
+
+class Chef
+  class HTTP
+    class AuthCredentials
+      attr_reader :client_name, :key
+
+      def initialize(client_name=nil, key=nil)
+        @client_name, @key = client_name, key
+      end
+
+      def sign_requests?
+        !!key
+      end
+
+      def signature_headers(request_params={})
+        raise ArgumentError, "Cannot sign the request without a client name, check that :node_name is assigned" if client_name.nil?
+        Chef::Log.debug("Signing the request as #{client_name}")
+
+        # params_in = {:http_method => :GET, :path => "/clients", :body => "", :host => "localhost"}
+        request_params                 = request_params.dup
+        request_params[:timestamp]     = Time.now.utc.iso8601
+        request_params[:user_id]       = client_name
+        request_params[:proto_version] = Chef::Config[:authentication_protocol_version]
+        host = request_params.delete(:host) || "localhost"
+
+        sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(request_params)
+        signed =  sign_obj.sign(key).merge({:host => host})
+        signed.inject({}){|memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo}
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
new file mode 100644
index 0000000..489675a
--- /dev/null
+++ b/lib/chef/http/authenticator.rb
@@ -0,0 +1,89 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/http/auth_credentials'
+require 'chef/exceptions'
+require 'openssl'
+
+class Chef
+  class HTTP
+    class Authenticator
+
+      attr_reader :signing_key_filename
+      attr_reader :raw_key
+      attr_reader :attr_names
+      attr_reader :auth_credentials
+
+      attr_accessor :sign_request
+
+      def initialize(opts={})
+        @raw_key = nil
+        @sign_request = true
+        @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)
+      end
+
+      def handle_request(method, url, headers={}, data=false)
+        headers.merge!(authentication_headers(method, url, data)) if sign_requests?
+        [method, url, headers, data]
+      end
+
+      def handle_response(http_response, rest_request, return_value)
+        [http_response, rest_request, return_value]
+      end
+
+      def stream_response_handler(response)
+        nil
+      end
+
+      def sign_requests?
+        auth_credentials.sign_requests? && @sign_request
+      end
+
+      def client_name
+        @auth_credentials.client_name
+      end
+
+      def load_signing_key(key_file, raw_key = nil)
+        if (!!key_file)
+          @raw_key = IO.read(key_file).strip
+        elsif (!!raw_key)
+          @raw_key = raw_key.strip
+        else
+          return nil
+        end
+        @key = OpenSSL::PKey::RSA.new(@raw_key)
+      rescue SystemCallError, IOError => e
+        Chef::Log.warn "Failed to read the private key #{key_file}: #{e.inspect}"
+        raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!"
+      rescue OpenSSL::PKey::RSAError
+        msg = "The file #{key_file} or :raw_key option does not contain a correctly formatted private key.\n"
+        msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'"
+        raise Chef::Exceptions::InvalidPrivateKey, msg
+      end
+
+      def authentication_headers(method, url, json_body=nil)
+        request_params = {:http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}"}
+        request_params[:body] ||= ""
+        auth_credentials.signature_headers(request_params)
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/http/basic_client.rb b/lib/chef/http/basic_client.rb
new file mode 100644
index 0000000..c342170
--- /dev/null
+++ b/lib/chef/http/basic_client.rb
@@ -0,0 +1,118 @@
+#--
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Thom May (<thom at clearairturbulence.org>)
+# Author:: Nuo Yan (<nuo at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010 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 'uri'
+require 'net/http'
+require 'chef/http/ssl_policies'
+require 'chef/http/http_request'
+
+class Chef
+  class HTTP
+    class BasicClient
+
+      HTTPS = "https".freeze
+
+      attr_reader :url
+      attr_reader :http_client
+      attr_reader :ssl_policy
+
+      # Instantiate a BasicClient.
+      # === Arguments:
+      # url:: An URI for the remote server.
+      # === Options:
+      # ssl_policy:: The SSL Policy to use, defaults to DefaultSSLPolicy
+      def initialize(url, opts={})
+        @url = url
+        @ssl_policy = opts[:ssl_policy] || DefaultSSLPolicy
+        @http_client = build_http_client
+      end
+
+      def host
+        @url.host
+      end
+
+      def port
+        @url.port
+      end
+
+      def request(method, url, req_body, base_headers={})
+        http_request = HTTPRequest.new(method, url, req_body, base_headers).http_request
+        Chef::Log.debug("Initiating #{method} to #{url}")
+        Chef::Log.debug("---- HTTP Request Header Data: ----")
+        base_headers.each do |name, value|
+          Chef::Log.debug("#{name}: #{value}")
+        end
+        http_client.request(http_request) do |response|
+          yield response if block_given?
+          # http_client.request may not have the return signature we want, so
+          # force the issue:
+          return [http_request, response]
+        end
+      rescue OpenSSL::SSL::SSLError => e
+        Chef::Log.error("SSL Validation failure connecting to host: #{host} - #{e.message}")
+        raise
+      end
+
+      #adapted from buildr/lib/buildr/core/transports.rb
+      def proxy_uri
+        proxy = Chef::Config["#{url.scheme}_proxy"]
+        proxy = URI.parse(proxy) if String === proxy
+        excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact
+        excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
+        return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
+      end
+
+      def build_http_client
+        http_client = http_client_builder.new(host, port)
+
+        if url.scheme == HTTPS
+          configure_ssl(http_client)
+        end
+
+        http_client.read_timeout = config[:rest_timeout]
+        http_client
+      end
+
+      def config
+        Chef::Config
+      end
+
+      def http_client_builder
+        http_proxy = proxy_uri
+        if http_proxy.nil?
+          Net::HTTP
+        else
+          Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
+          user = Chef::Config["#{url.scheme}_proxy_user"]
+          pass = Chef::Config["#{url.scheme}_proxy_pass"]
+          Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass)
+        end
+      end
+
+      def configure_ssl(http_client)
+        http_client.use_ssl = true
+        ssl_policy.apply_to(http_client)
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/http/cookie_jar.rb b/lib/chef/http/cookie_jar.rb
new file mode 100644
index 0000000..418fb1d
--- /dev/null
+++ b/lib/chef/http/cookie_jar.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Thom May (<thom at clearairturbulence.org>)
+# Author:: Nuo Yan (<nuo at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010 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 'singleton'
+
+class Chef
+  class HTTP
+    class CookieJar < Hash
+      include Singleton
+    end
+  end
+end
diff --git a/lib/chef/http/cookie_manager.rb b/lib/chef/http/cookie_manager.rb
new file mode 100644
index 0000000..f6dcf9a
--- /dev/null
+++ b/lib/chef/http/cookie_manager.rb
@@ -0,0 +1,56 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/http/cookie_jar'
+
+class Chef
+  class HTTP
+
+    # An HTTP middleware to manage storing/sending cookies in HTTP requests.
+    # Most HTTP communication in Chef does not need cookies, it was originally
+    # implemented to support OpenID, but it's not known who might be relying on
+    # it, so it's included with Chef::REST
+    class CookieManager
+
+      def initialize(options={})
+        @cookies = CookieJar.instance
+      end
+
+      def handle_request(method, url, headers={}, data=false)
+        @host, @port = url.host, url.port
+        if @cookies.has_key?("#{@host}:#{@port}")
+          headers['Cookie'] = @cookies["#{@host}:#{@port}"]
+        end
+        [method, url, headers, data]
+      end
+
+      def handle_response(http_response, rest_request, return_value)
+        if http_response['set-cookie']
+          @cookies["#{@host}:#{@port}"] = http_response['set-cookie']
+        end
+        [http_response, rest_request, return_value]
+      end
+
+      def stream_response_handler(response)
+        nil
+      end
+
+
+    end
+  end
+end
diff --git a/lib/chef/http/decompressor.rb b/lib/chef/http/decompressor.rb
new file mode 100644
index 0000000..6010ffa
--- /dev/null
+++ b/lib/chef/http/decompressor.rb
@@ -0,0 +1,137 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'zlib'
+require 'chef/http/http_request'
+
+class Chef
+  class HTTP
+
+    # Middleware-esque class for handling compression in HTTP responses.
+    class Decompressor
+      class NoopInflater
+        def inflate(chunk)
+          chunk
+        end
+        alias :handle_chunk :inflate
+      end
+
+      class GzipInflater < Zlib::Inflate
+        def initialize
+          super(Zlib::MAX_WBITS + 16)
+        end
+        alias :handle_chunk :inflate
+      end
+
+      class DeflateInflater < Zlib::Inflate
+        def initialize
+          super
+        end
+        alias :handle_chunk :inflate
+      end
+
+      CONTENT_ENCODING  = "content-encoding".freeze
+      GZIP              = "gzip".freeze
+      DEFLATE           = "deflate".freeze
+      IDENTITY          = "identity".freeze
+
+      def initialize(opts={})
+        @disable_gzip = false
+        handle_options(opts)
+      end
+
+      def handle_request(method, url, headers={}, data=false)
+        headers[HTTPRequest::ACCEPT_ENCODING] = HTTPRequest::ENCODING_GZIP_DEFLATE unless gzip_disabled?
+        [method, url, headers, data]
+      end
+
+      def handle_response(http_response, rest_request, return_value)
+        # temporary hack, skip processing if return_value is false
+        # needed to keep conditional get stuff working correctly.
+        return [http_response, rest_request, return_value] if return_value == false
+        response_body = decompress_body(http_response)
+        http_response.body.replace(response_body) if http_response.body.respond_to?(:replace)
+        [http_response, rest_request, return_value]
+      end
+
+      def decompress_body(response)
+        if gzip_disabled? || response.body.nil?
+          response.body
+        else
+          case response[CONTENT_ENCODING]
+          when GZIP
+            Chef::Log.debug "decompressing gzip response"
+            Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body)
+          when DEFLATE
+            Chef::Log.debug "decompressing deflate response"
+            Zlib::Inflate.inflate(response.body)
+          else
+            response.body
+          end
+        end
+      end
+
+      # This isn't used when this class is used as middleware; it returns an
+      # object you can use to unzip/inflate a streaming response.
+      def stream_response_handler(response)
+        if gzip_disabled?
+          NoopInflater.new
+        else
+          case response[CONTENT_ENCODING]
+          when GZIP
+            Chef::Log.debug "decompressing gzip stream"
+            GzipInflater.new
+          when DEFLATE
+            Chef::Log.debug "decompressing inflate stream"
+            DeflateInflater.new
+          else
+            NoopInflater.new
+          end
+        end
+      end
+
+
+      # gzip is disabled using the disable_gzip => true option in the
+      # constructor. When gzip is disabled, no 'Accept-Encoding' header will be
+      # set, and the response will not be decompressed, no matter what the
+      # Content-Encoding header of the response is. The intended use case for
+      # this is to work around situations where you request +file.tar.gz+, but
+      # the server responds with a content type of tar and a content encoding of
+      # gzip, tricking the client into decompressing the response so you end up
+      # with a tar archive (no gzip) named file.tar.gz
+      def gzip_disabled?
+        @disable_gzip
+      end
+
+      private
+
+      def handle_options(opts)
+        opts.each do |name, value|
+          case name.to_s
+          when 'disable_gzip'
+            @disable_gzip = value
+          end
+        end
+      end
+
+
+    end
+  end
+end
+
+
diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb
new file mode 100644
index 0000000..ec837f1
--- /dev/null
+++ b/lib/chef/http/http_request.rb
@@ -0,0 +1,172 @@
+#--
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Thom May (<thom at clearairturbulence.org>)
+# Author:: Nuo Yan (<nuo at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010 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 'uri'
+require 'net/http'
+
+# To load faster, we only want ohai's version string.
+# However, in ohai before 0.6.0, the version is defined
+# in ohai, not ohai/version
+begin
+  require 'ohai/version' #used in user agent string.
+rescue LoadError
+  require 'ohai'
+end
+
+require 'chef/version'
+
+class Chef
+  class HTTP
+    class HTTPRequest
+
+      engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
+
+      UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)"
+      DEFAULT_UA = "Chef Client" << UA_COMMON
+
+      USER_AGENT = "User-Agent".freeze
+
+      ACCEPT_ENCODING = "Accept-Encoding".freeze
+      ENCODING_GZIP_DEFLATE = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3".freeze
+
+      GET     = "get".freeze
+      PUT     = "put".freeze
+      POST    = "post".freeze
+      DELETE  = "delete".freeze
+      HEAD    = "head".freeze
+
+      HTTPS = "https".freeze
+
+      SLASH = "/".freeze
+
+      def self.user_agent=(ua)
+        @user_agent = ua
+      end
+
+      def self.user_agent
+        @user_agent ||= DEFAULT_UA
+      end
+
+      attr_reader :method, :url, :headers, :http_client, :http_request
+
+      def initialize(method, url, req_body, base_headers={})
+        @method, @url = method, url
+        @request_body = nil
+        build_headers(base_headers)
+        configure_http_request(req_body)
+      end
+
+      def host
+        @url.host
+      end
+
+      def port
+        @url.port
+      end
+
+      def query
+        @url.query
+      end
+
+      def path
+        @url.path.empty? ? SLASH : @url.path
+      end
+
+      # DEPRECATED. Call request on an HTTP client object instead.
+      def call
+        hide_net_http_bug do
+          http_client.request(http_request) do |response|
+            yield response if block_given?
+            response
+          end
+        end
+      end
+
+      def config
+        Chef::Config
+      end
+
+      # DEPRECATED. Call request on an HTTP client object instead.
+      def http_client
+        @http_client ||= BasicClient.new(url).http_client
+      end
+
+      private
+
+      def hide_net_http_bug
+        yield
+      rescue NoMethodError => e
+        # http://redmine.ruby-lang.org/issues/show/2708
+        # http://redmine.ruby-lang.org/issues/show/2758
+        if e.to_s =~ /#{Regexp.escape(%q|undefined method `closed?' for nil:NilClass|)}/
+          Chef::Log.debug("Rescued error in http connect, re-raising as Errno::ECONNREFUSED to hide bug in net/http")
+          Chef::Log.debug("#{e.class.name}: #{e.to_s}")
+          Chef::Log.debug(e.backtrace.join("\n"))
+          raise Errno::ECONNREFUSED, "Connection refused attempting to contact #{url.scheme}://#{host}:#{port}"
+        else
+          raise
+        end
+      end
+
+      def build_headers(headers)
+        @headers = headers.dup
+        # No response compression unless we asked for it explicitly:
+        @headers[HTTPRequest::ACCEPT_ENCODING] ||= "identity"
+        @headers['X-Chef-Version'] = ::Chef::VERSION
+        @headers
+      end
+
+
+      def configure_http_request(request_body=nil)
+        req_path = "#{path}"
+        req_path << "?#{query}" if query
+
+        @http_request = case method.to_s.downcase
+        when GET
+          Net::HTTP::Get.new(req_path, headers)
+        when POST
+          Net::HTTP::Post.new(req_path, headers)
+        when PUT
+          Net::HTTP::Put.new(req_path, headers)
+        when DELETE
+          Net::HTTP::Delete.new(req_path, headers)
+        when HEAD
+          Net::HTTP::Head.new(req_path, headers)
+        else
+          raise ArgumentError, "You must provide :GET, :PUT, :POST, :DELETE or :HEAD as the method"
+        end
+
+        @http_request.body = request_body if (request_body && @http_request.request_body_permitted?)
+        # Optionally handle HTTP Basic Authentication
+        if url.user
+          user = URI.unescape(url.user)
+          password = URI.unescape(url.password) if url.password
+          @http_request.basic_auth(user, password)
+        end
+
+        # Overwrite default UA
+        @http_request[USER_AGENT] = self.class.user_agent
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/http/json_input.rb b/lib/chef/http/json_input.rb
new file mode 100644
index 0000000..741c48f
--- /dev/null
+++ b/lib/chef/http/json_input.rb
@@ -0,0 +1,53 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/json_compat'
+
+class Chef
+  class HTTP
+
+    # Middleware that takes json input and turns it into raw text
+    class JSONInput
+
+      def initialize(opts={})
+      end
+
+      def handle_request(method, url, headers={}, data=false)
+        if data
+          headers["Content-Type"] = 'application/json'
+          data = Chef::JSONCompat.to_json(data)
+          # Force encoding to binary to fix SSL related EOFErrors
+          # cf. http://tickets.opscode.com/browse/CHEF-2363
+          # http://redmine.ruby-lang.org/issues/5233
+          data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding)
+        end
+        [method, url, headers, data]
+      end
+
+      def handle_response(http_response, rest_request, return_value)
+        [http_response, rest_request, return_value]
+      end
+
+      def stream_response_handler(response)
+        nil
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/http/json_output.rb b/lib/chef/http/json_output.rb
new file mode 100644
index 0000000..7c1502f
--- /dev/null
+++ b/lib/chef/http/json_output.rb
@@ -0,0 +1,69 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/json_compat'
+require 'chef/log'
+
+class Chef
+  class HTTP
+
+    # Middleware that takes an HTTP response, parses it as JSON if possible.
+    class JSONOutput
+
+      def initialize(opts={})
+        @raw_output = opts[:raw_output]
+        @inflate_json_class = opts[:inflate_json_class]
+      end
+
+      def handle_request(method, url, headers={}, data=false)
+        # Ideally this should always set Accept to application/json, but
+        # Chef::REST is sometimes used to make non-JSON requests, so it sets
+        # Accept to the desired value before middlewares get called.
+        headers['Accept'] ||= 'application/json'
+        [method, url, headers, data]
+      end
+
+      def handle_response(http_response, rest_request, return_value)
+        # temporary hack, skip processing if return_value is false
+        # needed to keep conditional get stuff working correctly.
+        return [http_response, rest_request, return_value] if return_value == false
+        if http_response['content-type'] =~ /json/
+          if @raw_output
+            return_value = http_response.body.to_s
+          else
+            if @inflate_json_class
+              return_value = Chef::JSONCompat.from_json(http_response.body.chomp)
+            else
+              return_value = Chef::JSONCompat.from_json(http_response.body.chomp, :create_additions => false)
+            end
+          end
+          [http_response, rest_request, return_value]
+        else
+          Chef::Log.debug("Expected JSON response, but got content-type '#{http_response['content-type']}'")
+          return [http_response, rest_request, http_response.body.to_s]
+        end
+      end
+
+      def stream_response_handler(response)
+        nil
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/http/json_to_model_output.rb b/lib/chef/http/json_to_model_output.rb
new file mode 100644
index 0000000..9bc90a5
--- /dev/null
+++ b/lib/chef/http/json_to_model_output.rb
@@ -0,0 +1,34 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/http/json_output'
+
+class Chef
+  class HTTP
+
+    # A Middleware-ish thing that takes an HTTP response, parses it as JSON if
+    # possible, and converts it into an appropriate model object if it contains
+    # a `json_class` key.
+    class JSONToModelOutput < JSONOutput
+      def initialize(opts={})
+        opts[:inflate_json_class] = true if !opts.has_key?(:inflate_json_class)
+        super
+      end
+    end
+  end
+end
diff --git a/lib/chef/http/simple.rb b/lib/chef/http/simple.rb
new file mode 100644
index 0000000..0ecb288
--- /dev/null
+++ b/lib/chef/http/simple.rb
@@ -0,0 +1,16 @@
+require 'chef/http'
+require 'chef/http/authenticator'
+require 'chef/http/decompressor'
+
+
+class Chef
+  class HTTP
+
+    class Simple < HTTP
+
+      use Decompressor
+      use CookieManager
+
+    end
+  end
+end
diff --git a/lib/chef/http/ssl_policies.rb b/lib/chef/http/ssl_policies.rb
new file mode 100644
index 0000000..f2a9c5b
--- /dev/null
+++ b/lib/chef/http/ssl_policies.rb
@@ -0,0 +1,129 @@
+#--
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Thom May (<thom at clearairturbulence.org>)
+# Author:: Nuo Yan (<nuo at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010, 2013 Opscode, Inc.
+# 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 'openssl'
+
+class Chef
+  class HTTP
+
+    # == Chef::HTTP::DefaultSSLPolicy
+    # Configures SSL behavior on an HTTP object via visitor pattern.
+    class DefaultSSLPolicy
+
+      def self.apply_to(http_client)
+        new(http_client).apply
+        http_client
+      end
+
+      attr_reader :http_client
+
+      def initialize(http_client)
+        @http_client = http_client
+      end
+
+      def apply
+        set_verify_mode
+        set_ca_store
+        set_custom_certs
+        set_client_credentials
+      end
+
+      def set_verify_mode
+        if config[:ssl_verify_mode] == :verify_none
+          http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
+        elsif config[:ssl_verify_mode] == :verify_peer
+          http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
+        end
+      end
+
+      def set_ca_store
+        if config[:ssl_ca_path]
+          unless ::File.exist?(config[:ssl_ca_path])
+            raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist"
+          end
+          http_client.ca_path = config[:ssl_ca_path]
+        elsif config[:ssl_ca_file]
+          unless ::File.exist?(config[:ssl_ca_file])
+            raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist"
+          end
+          http_client.ca_file = config[:ssl_ca_file]
+        end
+      end
+
+      def set_custom_certs
+        unless http_client.cert_store
+          http_client.cert_store = OpenSSL::X509::Store.new
+          http_client.cert_store.set_default_paths
+        end
+        if config.trusted_certs_dir
+          certs = Dir.glob(File.join(config.trusted_certs_dir, "*.{crt,pem}"))
+          certs.each do |cert_file|
+            cert = OpenSSL::X509::Certificate.new(File.read(cert_file))
+            add_trusted_cert(cert)
+          end
+        end
+      end
+
+      def set_client_credentials
+        if (config[:ssl_client_cert] || config[:ssl_client_key])
+          unless (config[:ssl_client_cert] && config[:ssl_client_key])
+            raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
+          end
+          unless ::File.exists?(config[:ssl_client_cert])
+            raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
+          end
+          unless ::File.exists?(config[:ssl_client_key])
+            raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
+          end
+          http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert]))
+          http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key]))
+        end
+      end
+
+      def config
+        Chef::Config
+      end
+
+      private
+
+      def add_trusted_cert(cert)
+        http_client.cert_store.add_cert(cert)
+      rescue OpenSSL::X509::StoreError => e
+        raise e unless e.message == 'cert already in hash table'
+      end
+
+    end
+
+    class APISSLPolicy < DefaultSSLPolicy
+
+      def set_verify_mode
+        if config[:ssl_verify_mode] == :verify_peer or config[:verify_api_cert]
+          http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
+        elsif config[:ssl_verify_mode] == :verify_none
+          http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
+        end
+      end
+    end
+
+  end
+end
diff --git a/lib/chef/index_queue.rb b/lib/chef/index_queue.rb
deleted file mode 100644
index b350949..0000000
--- a/lib/chef/index_queue.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
-# 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 "singleton"
-require "bunny"
-
-require "chef/index_queue/amqp_client"
-require "chef/index_queue/indexable"
-require "chef/index_queue/consumer"
-
-class Chef
-  module IndexQueue
-  end
-end
diff --git a/lib/chef/index_queue/amqp_client.rb b/lib/chef/index_queue/amqp_client.rb
deleted file mode 100644
index a7d155f..0000000
--- a/lib/chef/index_queue/amqp_client.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
-# 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 IndexQueue
-    class AmqpClient
-      VNODES = 1024
-
-      include Singleton
-
-      def initialize
-        reset!
-      end
-
-      def reset!
-        @amqp_client && amqp_client.connected? && amqp_client.stop
-        @amqp_client = nil
-        @exchange = nil
-      end
-      
-      def stop
-        @amqp_client && @amqp_client.stop
-      end
-      
-      def amqp_client
-        unless @amqp_client
-          begin
-            @amqp_client = Bunny.new(amqp_opts)
-            Chef::Log.debug "Starting AMQP connection with client settings: #{@amqp_client.inspect}"
-            @amqp_client.start
-            @amqp_client.qos(:prefetch_count => 1)
-          rescue Bunny::ServerDownError => e
-            Chef::Log.fatal "Could not connect to rabbitmq. Is it running, reachable, and configured correctly?"
-            raise e
-          rescue Bunny::ProtocolError => e
-            Chef::Log.fatal "Connection to rabbitmq refused. Check your rabbitmq configuration and chef's amqp* settings"
-            raise e
-          end
-        end
-        @amqp_client
-      end
-
-      def exchange
-        @exchange ||= amqp_client.exchange("chef-indexer", :durable => true, :type => :fanout)
-      end
-      
-      def disconnected!
-        Chef::Log.error("Disconnected from the AMQP Broker (RabbitMQ)")
-        @amqp_client = nil
-        reset!
-      end
-
-      def queue_for_object(obj_id)
-        retries = 0
-        vnode_tag = obj_id_to_int(obj_id) % VNODES
-        begin
-          yield amqp_client.queue("vnode-#{vnode_tag}", :passive => false, :durable => true, :exclusive => false, :auto_delete => false)
-        rescue Bunny::ServerDownError, Bunny::ConnectionError, Errno::ECONNRESET
-          disconnected!
-          if (retries += 1) < 2
-            Chef::Log.info("Attempting to reconnect to the AMQP broker")
-            retry
-          else
-            Chef::Log.fatal("Could not re-connect to the AMQP broker, giving up")
-            raise
-          end
-        end
-      end
-
-      private
-
-      # Sometimes object ids are "proper" UUIDs, like "64bc00eb-120b-b6a2-ec0e-34fc90d151be"
-      # and sometimes they omit the dashes, like "64bc00eb120bb6a2ec0e34fc90d151be"
-      # UUIDTools uses different methods to parse the different styles.
-      def obj_id_to_int(obj_id)
-        UUIDTools::UUID.parse(obj_id).to_i
-      rescue ArgumentError
-        UUIDTools::UUID.parse_hexdigest(obj_id).to_i
-      end
-      
-      def durable_queue?
-        !!Chef::Config[:amqp_consumer_id]
-      end
-      
-      def consumer_id
-        Chef::Config[:amqp_consumer_id] || UUIDTools::UUID.random_create.to_s
-      end
-
-      def amqp_opts
-        { :spec   => '08',
-          :host   => Chef::Config[:amqp_host],
-          :port   => Chef::Config[:amqp_port],
-          :user   => Chef::Config[:amqp_user],
-          :pass   => Chef::Config[:amqp_pass],
-          :vhost  => Chef::Config[:amqp_vhost]}
-      end
-
-    end
-  end
-end
-
diff --git a/lib/chef/index_queue/consumer.rb b/lib/chef/index_queue/consumer.rb
deleted file mode 100644
index 8701cff..0000000
--- a/lib/chef/index_queue/consumer.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
-# 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 IndexQueue
-    module Consumer
-      module ClassMethods
-        def expose(*methods)
-          @exposed_methods = Array(@exposed_methods)
-          @exposed_methods += methods
-        end
-        
-        def exposed_methods
-          @exposed_methods || []
-        end
-        
-        def whitelisted?(method_name)
-          exposed_methods.include?(method_name)
-        end
-      end
-      
-      def self.included(including_class)
-        including_class.send(:extend, ClassMethods)
-      end
-      
-      def run
-        Chef::Log.debug("Starting Index Queue Consumer")
-        AmqpClient.instance.queue # triggers connection setup
-        
-        begin
-          AmqpClient.instance.queue.subscribe(:ack => true, :timeout => false) do |message|
-            call_action_for_message(message)
-          end
-        rescue Bunny::ConnectionError, Errno::ECONNRESET, Bunny::ServerDownError
-          AmqpClient.instance.disconnected!
-          Chef::Log.warn "Connection to rabbitmq lost. attempting to reconnect"
-          sleep 1
-          retry
-        end
-      end
-      alias :start :run
-      
-      def call_action_for_message(message)
-        amqp_payload  = Chef::JSONCompat.from_json(message[:payload], :create_additions => false, :max_nesting => false)
-        action        = amqp_payload["action"].to_sym
-        app_payload   = amqp_payload["payload"]
-        assert_method_whitelisted(action)
-        send(action, app_payload)
-      end
-      
-      private
-      
-      def assert_method_whitelisted(method_name)
-        unless self.class.whitelisted?(method_name)
-          raise ArgumentError, "non-exposed method #{method_name} called via index queue"
-        end
-      end
-      
-    end
-  end
-end
diff --git a/lib/chef/index_queue/indexable.rb b/lib/chef/index_queue/indexable.rb
deleted file mode 100644
index 73fd08b..0000000
--- a/lib/chef/index_queue/indexable.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Author:: Seth Falcon (<seth at opscode.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# 
-#     http://www.apache.org/licenses/LICENSE-2.0
-# 
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-require 'chef/json_compat'
-
-class Chef
-  module IndexQueue
-    module Indexable
-      
-      module ClassMethods
-        
-        def index_object_type(explicit_type_name=nil)
-          @index_object_type = explicit_type_name.to_s if explicit_type_name
-          @index_object_type
-        end
-        
-        # Resets all metadata used for indexing to nil. Used for testing
-        def reset_index_metadata!
-          @index_object_type = nil
-        end
-        
-      end
-
-      def self.included(including_class)
-        including_class.send(:extend, ClassMethods)
-      end
-      
-      attr_accessor :index_id
-      
-      def index_object_type
-        self.class.index_object_type || Mixin::ConvertToClassName.snake_case_basename(self.class.name)
-      end
-      
-      def with_indexer_metadata(indexer_metadata={})
-        # changing input param symbol keys to strings, as the keys in hash that goes to solr are expected to be strings [cb]
-        # Ruby 1.9 hates you, cb [dan]
-        with_metadata = {}
-        indexer_metadata.each_key do |key|
-          with_metadata[key.to_s] = indexer_metadata[key]
-        end
-
-        with_metadata["type"]     ||= self.index_object_type
-        with_metadata["id"]       ||= self.index_id
-        with_metadata["database"] ||= Chef::Config[:couchdb_database]
-        with_metadata["item"]     ||= self.to_hash
-        with_metadata["enqueued_at"] ||= Time.now.utc.to_i
-
-        raise ArgumentError, "Type, Id, or Database missing in index operation: #{with_metadata.inspect}" if (with_metadata["id"].nil? or with_metadata["type"].nil?)
-        with_metadata        
-      end
-
-      def add_to_index(metadata={})
-       Chef::Log.debug("Pushing item to index queue for addition: #{self.with_indexer_metadata(metadata)}")
-       object_with_metadata = with_indexer_metadata(metadata)
-       obj_id = object_with_metadata["id"]
-       obj = {:action => :add, :payload => self.with_indexer_metadata(metadata)}
-
-       publish_object(obj_id, obj)
-      end
-
-      def delete_from_index(metadata={})
-        Chef::Log.debug("Pushing item to index queue for deletion: #{self.with_indexer_metadata(metadata)}")
-        object_with_metadata = with_indexer_metadata(metadata)
-        obj_id = object_with_metadata["id"]
-        obj = {:action => :delete, :payload => self.with_indexer_metadata(metadata)}
-
-        publish_object(obj_id, obj)
-      end
-
-      private
-
-      # Uses the publisher to update the object's queue. If
-      # Chef::Config[:persistent_queue] is true, the update is wrapped
-      # in a transaction.
-      def publish_object(object_id, object)
-        publisher = AmqpClient.instance
-        begin
-          publisher.amqp_client.tx_select if Chef::Config[:persistent_queue]
-          publisher.queue_for_object(object_id) do |queue|
-            queue.publish(Chef::JSONCompat.to_json(object), :persistent => Chef::Config[:persistent_queue])
-          end
-          publisher.amqp_client.tx_commit if Chef::Config[:persistent_queue]
-        rescue
-          publisher.amqp_client.tx_rollback if Chef::Config[:persistent_queue]
-          raise
-        end
-
-        true
-      end
-
-    end
-  end
-end
diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb
index 9f59a41..e4795cf 100644
--- a/lib/chef/json_compat.rb
+++ b/lib/chef/json_compat.rb
@@ -24,7 +24,22 @@ class Chef
   class JSONCompat
     JSON_MAX_NESTING = 1000
 
+    JSON_CLASS = "json_class".freeze
+
+    CHEF_APICLIENT          = "Chef::ApiClient".freeze
+    CHEF_CHECKSUM           = "Chef::Checksum".freeze
+    CHEF_COOKBOOKVERSION    = "Chef::CookbookVersion".freeze
+    CHEF_DATABAG            = "Chef::DataBag".freeze
+    CHEF_DATABAGITEM        = "Chef::DataBagItem".freeze
+    CHEF_ENVIRONMENT        = "Chef::Environment".freeze
+    CHEF_NODE               = "Chef::Node".freeze
+    CHEF_ROLE               = "Chef::Role".freeze
+    CHEF_SANDBOX            = "Chef::Sandbox".freeze
+    CHEF_RESOURCE           = "Chef::Resource".freeze
+    CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze
+
     class <<self
+
       # See CHEF-1292/PL-538. Increase the max nesting for JSON, which defaults
       # to 19, and isn't enough for some (for example, a Node within a Node)
       # structures.
@@ -38,7 +53,49 @@ class Chef
 
       # Just call the JSON gem's parse method with a modified :max_nesting field
       def from_json(source, opts = {})
-        ::JSON.parse(source, opts_add_max_nesting(opts))
+        obj = ::Yajl::Parser.parse(source)
+
+        # JSON gem requires top level object to be a Hash or Array (otherwise
+        # you get the "must contain two octets" error). Yajl doesn't impose the
+        # same limitation. For compatibility, we re-impose this condition.
+        unless obj.kind_of?(Hash) or obj.kind_of?(Array)
+          raise JSON::ParserError, "Top level JSON object must be a Hash or Array. (actual: #{obj.class})"
+        end
+
+        # The old default in the json gem (which we are mimicing because we
+        # sadly rely on this misfeature) is to "create additions" i.e., convert
+        # JSON objects into ruby objects. Explicit :create_additions => false
+        # is required to turn it off.
+        if opts[:create_additions].nil? || opts[:create_additions]
+          map_to_rb_obj(obj)
+        else
+          obj
+        end
+      end
+
+      # Look at an object that's a basic type (from json parse) and convert it
+      # to an instance of Chef classes if desired.
+      def map_to_rb_obj(json_obj)
+        case json_obj
+        when Hash
+          mapped_hash = map_hash_to_rb_obj(json_obj)
+          if json_obj.has_key?(JSON_CLASS) && (class_to_inflate = class_for_json_class(json_obj[JSON_CLASS]))
+            class_to_inflate.json_create(mapped_hash)
+          else
+            mapped_hash
+          end
+        when Array
+          json_obj.map {|e| map_to_rb_obj(e) }
+        else
+          json_obj
+        end
+      end
+
+      def map_hash_to_rb_obj(json_hash)
+        json_hash.each do |key, value|
+          json_hash[key] = map_to_rb_obj(value)
+        end
+        json_hash
       end
 
       def to_json(obj, opts = nil)
@@ -48,6 +105,46 @@ class Chef
       def to_json_pretty(obj, opts = nil)
         ::JSON.pretty_generate(obj, opts_add_max_nesting(opts))
       end
+
+
+      # Map +json_class+ to a Class object. We use a +case+ instead of a Hash
+      # assigned to a constant because otherwise this file could not be loaded
+      # until all the constants were defined, which means you'd have to load
+      # the world to get json, which would make knife very slow.
+      def class_for_json_class(json_class)
+        case json_class
+        when CHEF_APICLIENT
+          Chef::ApiClient
+        when CHEF_CHECKSUM
+          Chef::Checksum
+        when CHEF_COOKBOOKVERSION
+          Chef::CookbookVersion
+        when CHEF_DATABAG
+          Chef::DataBag
+        when CHEF_DATABAGITEM
+          Chef::DataBagItem
+        when CHEF_ENVIRONMENT
+          Chef::Environment
+        when CHEF_NODE
+          Chef::Node
+        when CHEF_ROLE
+          Chef::Role
+        when CHEF_SANDBOX
+          # a falsey return here will disable object inflation/"create
+          # additions" in the caller. In Chef 11 this is correct, we just have
+          # a dummy Chef::Sandbox class for compat with Chef 10 servers.
+          false
+        when CHEF_RESOURCE
+          Chef::Resource
+        when CHEF_RESOURCECOLLECTION
+          Chef::ResourceCollection
+        when /^Chef::Resource/
+          Chef::Resource.find_subclass_by_name(json_class)
+        else
+          raise JSON::ParserError, "Unsupported `json_class` type '#{json_class}'"
+        end
+      end
+
     end
   end
 end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index d5c3183..3414022 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -20,6 +20,7 @@
 require 'forwardable'
 require 'chef/version'
 require 'mixlib/cli'
+require 'chef/config_fetcher'
 require 'chef/mixin/convert_to_class_name'
 require 'chef/mixin/path_sanity'
 require 'chef/knife/core/subcommand_loader'
@@ -57,6 +58,11 @@ class Chef
     attr_accessor :name_args
     attr_accessor :ui
 
+    # Configure mixlib-cli to always separate defaults from user-supplied CLI options
+    def self.use_separate_defaults?
+      true
+    end
+
     def self.ui
       @ui ||= Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
     end
@@ -145,7 +151,7 @@ class Chef
       commands_to_show.sort.each do |category, commands|
         next if category =~ /deprecated/i
         msg "** #{category.upcase} COMMANDS **"
-        commands.each do |command|
+        commands.sort.each do |command|
           msg subcommands[command].banner if subcommands[command]
         end
         msg
@@ -196,12 +202,18 @@ class Chef
       subcommand_class || subcommand_not_found!(args)
     end
 
+    def self.dependency_loaders
+      @dependency_loaders ||= []
+    end
+
     def self.deps(&block)
-      @dependency_loader = block
+      dependency_loaders << block
     end
 
     def self.load_deps
-      @dependency_loader && @dependency_loader.call
+      dependency_loaders.each do |dep_loader|
+        dep_loader.call
+      end
     end
 
     private
@@ -226,13 +238,22 @@ class Chef
       exit 10
     end
 
-    @@chef_config_dir = nil
+    def self.working_directory
+      ENV['PWD'] || Dir.pwd
+    end
+
+    def self.reset_config_path!
+      @@chef_config_dir = nil
+    end
+
+    reset_config_path!
+
 
     # search upward from current_dir until .chef directory is found
     def self.chef_config_dir
       if @@chef_config_dir.nil? # share this with subclasses
         @@chef_config_dir = false
-        full_path = Dir.pwd.split(File::SEPARATOR)
+        full_path = working_directory.split(File::SEPARATOR)
         (full_path.length - 1).downto(0) do |i|
           candidate_directory = File.join(full_path[0..i] + [".chef" ])
           if File.exist?(candidate_directory) && File.directory?(candidate_directory)
@@ -269,6 +290,10 @@ class Chef
         msg opt_parser
         exit 1
       end
+
+      # copy Mixlib::CLI over so that it cab be configured in knife.rb
+      # config file
+      Chef::Config[:verbosity] = config[:verbosity]
     end
 
     def parse_options(args)
@@ -279,49 +304,74 @@ class Chef
       exit(1)
     end
 
-    def configure_chef
-      unless config[:config_file]
-        candidate_configs = []
+    # Returns a subset of the Chef::Config[:knife] Hash that is relevant to the
+    # currently executing knife command. This is used by #configure_chef to
+    # apply settings from knife.rb to the +config+ hash.
+    def config_file_settings
+      config_file_settings = {}
+      self.class.options.keys.each do |key|
+        config_file_settings[key] = Chef::Config[:knife][key] if Chef::Config[:knife].has_key?(key)
+      end
+      config_file_settings
+    end
 
-        # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
-        if ENV['KNIFE_HOME']
-          candidate_configs << File.join(ENV['KNIFE_HOME'], 'knife.rb')
-        end
-        # Look for $PWD/knife.rb
-        if Dir.pwd
-          candidate_configs << File.join(Dir.pwd, 'knife.rb')
-        end
-        # Look for $UPWARD/.chef/knife.rb
-        if self.class.chef_config_dir
-          candidate_configs << File.join(self.class.chef_config_dir, 'knife.rb')
-        end
-        # Look for $HOME/.chef/knife.rb
-        if ENV['HOME']
-          candidate_configs << File.join(ENV['HOME'], '.chef', 'knife.rb')
-        end
+    def self.config_fetcher(candidate_config)
+      Chef::ConfigFetcher.new(candidate_config, Chef::Config.config_file_jail)
+    end
 
-        candidate_configs.each do | candidate_config |
-          candidate_config = File.expand_path(candidate_config)
-          if File.exist?(candidate_config)
-            config[:config_file] = candidate_config
-            break
-          end
-        end
-      end
+    def self.locate_config_file
+      candidate_configs = []
 
-      # Don't try to load a knife.rb if it doesn't exist.
-      if config[:config_file]
-        read_config_file(config[:config_file])
-      else
-        # ...but do log a message if no config was found.
-        Chef::Config[:color] = config[:color]
-        ui.warn("No knife configuration file found")
+      # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
+      if ENV['KNIFE_HOME']
+        candidate_configs << File.join(ENV['KNIFE_HOME'], 'knife.rb')
+      end
+      # Look for $PWD/knife.rb
+      if Dir.pwd
+        candidate_configs << File.join(Dir.pwd, 'knife.rb')
+      end
+      # Look for $UPWARD/.chef/knife.rb
+      if chef_config_dir
+        candidate_configs << File.join(chef_config_dir, 'knife.rb')
+      end
+      # Look for $HOME/.chef/knife.rb
+      if ENV['HOME']
+        candidate_configs << File.join(ENV['HOME'], '.chef', 'knife.rb')
       end
 
+      candidate_configs.each do | candidate_config |
+        fetcher = config_fetcher(candidate_config)
+        if !fetcher.config_missing?
+          return candidate_config
+        end
+      end
+      return nil
+    end
+
+    # Apply Config in this order:
+    # defaults from mixlib-cli
+    # settings from config file, via Chef::Config[:knife]
+    # config from command line
+    def merge_configs
+      # Apply config file settings on top of mixlib-cli defaults
+      combined_config = default_config.merge(config_file_settings)
+      # Apply user-supplied options on top of the above combination
+      combined_config = combined_config.merge(config)
+      # replace the config hash from mixlib-cli with our own.
+      # Need to use the mutate-in-place #replace method instead of assigning to
+      # the instance variable because other code may have a reference to the
+      # original config hash object.
+      config.replace(combined_config)
+    end
+
+    # Catch-all method that does any massaging needed for various config
+    # components, such as expanding file paths and converting verbosity level
+    # into log level.
+    def apply_computed_config
       Chef::Config[:color] = config[:color]
 
-      case config[:verbosity]
-      when 0
+      case Chef::Config[:verbosity]
+      when 0, nil
         Chef::Config[:log_level] = :error
       when 1
         Chef::Config[:log_level] = :info
@@ -334,6 +384,12 @@ class Chef
       Chef::Config[:chef_server_url]   = config[:chef_server_url] if config[:chef_server_url]
       Chef::Config[:environment]       = config[:environment]     if config[:environment]
 
+      Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+      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
+      Chef::Config.chef_zero.port = config[:chef_zero_port] if config[:chef_zero_port]
+
       # Expand a relative path from the config directory. Config from command
       # line should already be expanded, and absolute paths will be unchanged.
       if Chef::Config[:client_key] && config[:config_file]
@@ -344,31 +400,56 @@ class Chef
       Chef::Log.init(Chef::Config[:log_location])
       Chef::Log.level(Chef::Config[:log_level] || :error)
 
-      Chef::Log.debug("Using configuration from #{config[:config_file]}")
+      if Chef::Config[:node_name] && Chef::Config[:node_name].bytesize > 90
+        # node names > 90 bytes only work with authentication protocol >= 1.1
+        # see discussion in config.rb.
+        Chef::Config[:authentication_protocol_version] = "1.1"
+      end
+    end
+
+    def configure_chef
+      if !config[:config_file]
+        located_config_file = self.class.locate_config_file
+        config[:config_file] = located_config_file if located_config_file
+      end
 
-      if Chef::Config[:node_name].nil?
-        #raise ArgumentError, "No user specified, pass via -u or specifiy 'node_name' in #{config[:config_file] ? config[:config_file] : "~/.chef/knife.rb"}"
+      # Don't try to load a knife.rb if it wasn't specified.
+      if config[:config_file]
+        fetcher = Chef::ConfigFetcher.new(config[:config_file], Chef::Config.config_file_jail)
+        if fetcher.config_missing?
+          ui.error("Specified config file #{config[:config_file]} does not exist#{Chef::Config.config_file_jail ? " or is not under config file jail #{Chef::Config.config_file_jail}" : ""}!")
+          exit 1
+        end
+        Chef::Log.debug("Using configuration from #{config[:config_file]}")
+        read_config(fetcher.read_config, config[:config_file])
+      else
+        # ...but do log a message if no config was found.
+        Chef::Config[:color] = config[:color]
+        ui.warn("No knife configuration file found")
       end
+
+      merge_configs
+      apply_computed_config
     end
 
-    def read_config_file(file)
-      Chef::Config.from_file(file)
+    def read_config(config_content, config_file_path)
+      Chef::Config.from_string(config_content, config_file_path)
     rescue SyntaxError => e
-      ui.error "You have invalid ruby syntax in your config file #{file}"
+      ui.error "You have invalid ruby syntax in your config file #{config_file_path}"
       ui.info(ui.color(e.message, :red))
-      if file_line = e.message[/#{Regexp.escape(file)}:[\d]+/]
+      if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
         line = file_line[/:([\d]+)$/, 1].to_i
-        highlight_config_error(file, line)
+        highlight_config_error(config_file_path, line)
       end
       exit 1
     rescue Exception => e
-      ui.error "You have an error in your config file #{file}"
+      ui.error "You have an error in your config file #{config_file_path}"
       ui.info "#{e.class.name}: #{e.message}"
-      filtered_trace = e.backtrace.grep(/#{Regexp.escape(file)}/)
+      filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
       filtered_trace.each {|line| ui.msg("  " + ui.color(line, :red))}
       if !filtered_trace.empty?
-        line_nr = filtered_trace.first[/#{Regexp.escape(file)}:([\d]+)/, 1]
-        highlight_config_error(file, line_nr.to_i)
+        line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
+        highlight_config_error(config_file_path, line_nr.to_i)
       end
 
       exit 1
@@ -394,14 +475,19 @@ class Chef
       stdout.puts("USAGE: " + self.opt_parser.to_s)
     end
 
-    def run_with_pretty_exceptions
+    def run_with_pretty_exceptions(raise_exception = false)
       unless self.respond_to?(:run)
         ui.error "You need to add a #run method to your knife command before you can use it"
       end
       enforce_path_sanity
-      run
+      Chef::Application.setup_server_connectivity
+      begin
+        run
+      ensure
+        Chef::Application.destroy_server_connectivity
+      end
     rescue Exception => e
-      raise if config[:verbosity] == 2
+      raise if raise_exception || Chef::Config[:verbosity] == 2
       humanize_exception(e)
       exit 100
     end
@@ -423,6 +509,9 @@ class Chef
       when Chef::Exceptions::PrivateKeyMissing
         ui.error "Your private key could not be loaded from #{api_key}"
         ui.info  "Check your configuration file and ensure that your private key is readable"
+      when Chef::Exceptions::InvalidRedirect
+        ui.error "Invalid Redirect: #{e.message}"
+        ui.info  "Change your server location in knife.rb to the server's FQDN to avoid unwanted redirections."
       else
         ui.error "#{e.class.name}: #{e.message}"
       end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index a61fc86..e88bbc1 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -30,6 +30,7 @@ class Chef
         require 'highline'
         require 'net/ssh'
         require 'net/ssh/multi'
+        require 'chef/knife/ssh'
         Chef::Knife::Ssh.load_deps
       end
 
@@ -50,7 +51,6 @@ class Chef
         :short => "-p PORT",
         :long => "--ssh-port PORT",
         :description => "The ssh port",
-        :default => "22",
         :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
 
       option :ssh_gateway,
@@ -59,6 +59,12 @@ class Chef
         :description => "The ssh gateway",
         :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
 
+      option :forward_agent,
+        :short => "-A",
+        :long => "--forward-agent",
+        :description => "Enable SSH agent forwarding",
+        :boolean => true
+
       option :identity_file,
         :short => "-i IDENTITY_FILE",
         :long => "--identity-file IDENTITY_FILE",
@@ -83,6 +89,11 @@ class Chef
         :description => "The proxy server for the node being bootstrapped",
         :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
 
+      option :bootstrap_no_proxy,
+        :long => "--bootstrap-no-proxy [NO_PROXY_URL|NO_PROXY_IP]",
+        :description => "Do not proxy locations for the node being bootstrapped",
+        :proc => Proc.new { |np| Chef::Config[:knife][:bootstrap_no_proxy] = np }
+
       option :distro,
         :short => "-d DISTRO",
         :long => "--distro DISTRO",
@@ -94,6 +105,11 @@ class Chef
         :description => "Execute the bootstrap via sudo",
         :boolean => true
 
+      option :use_sudo_password,
+        :long => "--use-sudo-password",
+        :description => "Execute the bootstrap via sudo with password",
+        :boolean => false
+
       option :template_file,
         :long => "--template-file TEMPLATE",
         :description => "Full path to location of template to use",
@@ -105,7 +121,7 @@ class Chef
         :description => "Comma separated list of roles/recipes to apply",
         :proc => lambda { |o| o.split(/[\s,]+/) },
         :default => []
-      
+
       option :first_boot_attributes,
         :short => "-j JSON_ATTRIBS",
         :long => "--json-attributes",
@@ -119,7 +135,26 @@ class Chef
         :boolean => true,
         :default => true
 
-      def load_template(template=nil)
+      option :hint,
+        :long => "--hint HINT_NAME[=HINT_FILE]",
+        :description => "Specify Ohai Hint to be set on the bootstrap target.  Use multiple --hint options to specify multiple hints.",
+        :proc => Proc.new { |h|
+          Chef::Config[:knife][:hints] ||= Hash.new
+          name, path = h.split("=")
+          Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new  }
+
+      option :secret,
+        :short => "-s SECRET",
+        :long  => "--secret ",
+        :description => "The secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
+
+      option :secret_file,
+        :long => "--secret-file SECRET_FILE",
+        :description => "A file containing the secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
+
+      def find_template(template=nil)
         # Are we bootstrapping using an already shipped template?
         if config[:template_file]
           bootstrap_files = config[:template_file]
@@ -144,7 +179,7 @@ class Chef
 
         Chef::Log.debug("Found bootstrap template in #{File.dirname(template)}")
 
-        IO.read(template).chomp
+        template
       end
 
       def render_template(template=nil)
@@ -152,9 +187,14 @@ class Chef
         Erubis::Eruby.new(template).evaluate(context)
       end
 
-      def run
+      def read_template
+        IO.read(@template_file).chomp
+      end
 
+      def run
         validate_name_args!
+        warn_chef_config_secret_key
+        @template_file = find_template(config[:bootstrap_template])
         @node_name = Array(@name_args).first
         # back compat--templates may use this setting:
         config[:server_name] = @node_name
@@ -167,7 +207,7 @@ class Chef
           knife_ssh.run
         rescue Net::SSH::AuthenticationFailed
           unless config[:ssh_password]
-            puts "Failed to authenticate #{config[:ssh_user]} - trying password auth"
+            ui.info("Failed to authenticate #{config[:ssh_user]} - trying password auth")
             knife_ssh_with_password_auth.run
           end
         end
@@ -188,13 +228,14 @@ class Chef
         ssh = Chef::Knife::Ssh.new
         ssh.ui = ui
         ssh.name_args = [ server_name, ssh_command ]
-        ssh.config[:ssh_user] = config[:ssh_user]
+        ssh.config[:ssh_user] = Chef::Config[:knife][:ssh_user] || config[:ssh_user]
         ssh.config[:ssh_password] = config[:ssh_password]
         ssh.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
         ssh.config[:ssh_gateway] = Chef::Config[:knife][:ssh_gateway] || config[:ssh_gateway]
-        ssh.config[:identity_file] = config[:identity_file]
+        ssh.config[:forward_agent] = Chef::Config[:knife][:forward_agent] || config[:forward_agent]
+        ssh.config[:identity_file] = Chef::Config[:knife][:identity_file] || config[:identity_file]
         ssh.config[:manual] = true
-        ssh.config[:host_key_verify] = config[:host_key_verify]
+        ssh.config[:host_key_verify] = Chef::Config[:knife][:host_key_verify] || config[:host_key_verify]
         ssh.config[:on_error] = :raise
         ssh
       end
@@ -207,15 +248,37 @@ class Chef
       end
 
       def ssh_command
-        command = render_template(load_template(config[:bootstrap_template]))
+        command = render_template(read_template)
 
         if config[:use_sudo]
-          command = "sudo #{command}"
+          command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S #{command}" : "sudo #{command}"
         end
 
         command
       end
 
+      def warn_chef_config_secret_key
+        unless Chef::Config[:encrypted_data_bag_secret].nil?
+          ui.warn "* " * 40
+          ui.warn(<<-WARNING)
+Specifying the encrypted data bag secret key using an 'encrypted_data_bag_secret'
+entry in 'knife.rb' is deprecated. Please see CHEF-4011 for more details. You
+can supress this warning and still distribute the secret key to all bootstrapped
+machines by adding the following to your 'knife.rb' file:
+
+  knife[:secret_file] = "/path/to/your/secret"
+
+If you would like to selectively distribute a secret key during bootstrap
+please use the '--secret' or '--secret-file' options of this command instead.
+
+#{ui.color('IMPORTANT:', :red, :bold)} In a future version of Chef, this
+behavior will be removed and any 'encrypted_data_bag_secret' entries in
+'knife.rb' will be ignored completely.
+WARNING
+          ui.warn "* " * 40
+        end
+      end
+
     end
   end
 end
diff --git a/lib/chef/knife/bootstrap/archlinux-gems.erb b/lib/chef/knife/bootstrap/archlinux-gems.erb
index 3a60eb4..c4f4eca 100644
--- a/lib/chef/knife/bootstrap/archlinux-gems.erb
+++ b/lib/chef/knife/bootstrap/archlinux-gems.erb
@@ -10,26 +10,31 @@ if [ ! -f /usr/bin/chef-client ]; then
 fi
 
 mkdir -p /etc/chef
-(
-cat <<'EOP'
+
+cat > /etc/chef/validation.pem <<'EOP'
 <%= validation_key %>
 EOP
-) > /tmp/validation.pem
-awk NF /tmp/validation.pem > /etc/chef/validation.pem
-rm /tmp/validation.pem
+chmod 0600 /etc/chef/validation.pem
 
-<% if @chef_config[:encrypted_data_bag_secret] -%>
-(
-cat <<'EOP'
+<% if encrypted_data_bag_secret -%>
+cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
 <%= encrypted_data_bag_secret %>
 EOP
-) > /tmp/encrypted_data_bag_secret
-awk NF /tmp/encrypted_data_bag_secret > /etc/chef/encrypted_data_bag_secret
-rm /tmp/encrypted_data_bag_secret
+chmod 0600 /etc/chef/encrypted_data_bag_secret
 <% end -%>
 
-(
-cat <<'EOP'
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+<%= hash.to_json %>
+EOP
+<% end -%>
+<% end -%>
+
+cat > /etc/chef/client.rb <<'EOP'
 log_level        :info
 log_location     STDOUT
 chef_server_url  "<%= @chef_config[:chef_server_url] %>"
@@ -38,7 +43,7 @@ validation_client_name "<%= @chef_config[:validation_client_name] %>"
 node_name "<%= @config[:chef_node_name] %>"
 <% else -%>
 # Using default node name (fqdn)
-<% end -%> 
+<% end -%>
 # ArchLinux follows the Filesystem Hierarchy Standard
 file_cache_path    "/var/cache/chef"
 file_backup_path   "/var/lib/chef/backup"
@@ -49,12 +54,9 @@ http_proxy         "<%= knife_config[:bootstrap_proxy] %>"
 https_proxy        "<%= knife_config[:bootstrap_proxy] %>"
 <% end -%>
 EOP
-) > /etc/chef/client.rb
 
-(
-cat <<'EOP'
+cat > /etc/chef/first-boot.json <<'EOP'
 <%= first_boot.to_json %>
 EOP
-) > /etc/chef/first-boot.json
 
 <%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/centos5-gems.erb b/lib/chef/knife/bootstrap/centos5-gems.erb
index 9481178..6aacc47 100644
--- a/lib/chef/knife/bootstrap/centos5-gems.erb
+++ b/lib/chef/knife/bootstrap/centos5-gems.erb
@@ -2,6 +2,11 @@ bash -c '
 <%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
 
 if [ ! -f /usr/bin/chef-client ]; then
+  tmp_dir=$(mktemp -d) || exit 1
+  pushd "$tmp_dir"
+
+  yum install -y wget
+
   wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm
   rpm -Uvh epel-release-5-4.noarch.rpm
   wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://rpm.aegisco.com/aegisco/rhel/aegisco-rhel.rpm
@@ -9,11 +14,11 @@ if [ ! -f /usr/bin/chef-client ]; then
 
   yum install -y ruby ruby-devel gcc gcc-c++ automake autoconf make
 
-  cd /tmp
-  wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://production.cf.rubygems.org/rubygems/rubygems-1.6.2.tgz
-  tar zxf rubygems-1.6.2.tgz
-  cd rubygems-1.6.2
-  ruby setup.rb --no-format-executable
+  wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://production.cf.rubygems.org/rubygems/rubygems-1.6.2.tgz -O - | tar zxf -
+  (cd rubygems-1.6.2 && ruby setup.rb --no-format-executable)
+
+  popd
+  rm -r "$tmp_dir"
 fi
 
 gem update --system
@@ -23,34 +28,35 @@ gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
 
 mkdir -p /etc/chef
 
-(
-cat <<'EOP'
+cat > /etc/chef/validation.pem <<'EOP'
 <%= validation_key %>
 EOP
-) > /tmp/validation.pem
-awk NF /tmp/validation.pem > /etc/chef/validation.pem
-rm /tmp/validation.pem
+chmod 0600 /etc/chef/validation.pem
 
-<% if @chef_config[:encrypted_data_bag_secret] -%>
-(
-cat <<'EOP'
+<% if encrypted_data_bag_secret -%>
+cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
 <%= encrypted_data_bag_secret %>
 EOP
-) > /tmp/encrypted_data_bag_secret
-awk NF /tmp/encrypted_data_bag_secret > /etc/chef/encrypted_data_bag_secret
-rm /tmp/encrypted_data_bag_secret
+chmod 0600 /etc/chef/encrypted_data_bag_secret
+<% end -%>
+
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+<%= hash.to_json %>
+EOP
+<% end -%>
 <% end -%>
 
-(
-cat <<'EOP'
+cat > /etc/chef/client.rb <<'EOP'
 <%= config_content %>
 EOP
-) > /etc/chef/client.rb
 
-(
-cat <<'EOP'
+cat > /etc/chef/first-boot.json <<'EOP'
 <%= first_boot.to_json %>
 EOP
-) > /etc/chef/first-boot.json
 
 <%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/chef-full.erb b/lib/chef/knife/bootstrap/chef-full.erb
index 82d342b..24ffca2 100644
--- a/lib/chef/knife/bootstrap/chef-full.erb
+++ b/lib/chef/knife/bootstrap/chef-full.erb
@@ -1,5 +1,14 @@
 bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
+<%= "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
+fi
 
 exists() {
   if command -v $1 &>/dev/null
@@ -10,50 +19,51 @@ exists() {
   fi
 }
 
-install_sh="http://opscode.com/chef/install.sh"
+install_sh="https://www.opscode.com/chef/install.sh"
 version_string="-v <%= chef_version %>"
 
 if ! exists /usr/bin/chef-client; then
   if exists wget; then
     bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> ${install_sh} -O -) ${version_string}
+  elif exists curl; then
+    bash <(curl -L <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> ${install_sh}) ${version_string}
   else
-    if exists curl; then
-      bash <(curl -L <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> ${install_sh}) ${version_string}
-    fi
+    echo "Neither wget nor curl found. Please install one and try again." >&2
+    exit 1
   fi
 fi
 
 mkdir -p /etc/chef
 
-(
-cat <<'EOP'
+cat > /etc/chef/validation.pem <<'EOP'
 <%= validation_key %>
 EOP
-) > /tmp/validation.pem
-awk NF /tmp/validation.pem > /etc/chef/validation.pem
-rm /tmp/validation.pem
-
+chmod 0600 /etc/chef/validation.pem
 
-<% if @chef_config[:encrypted_data_bag_secret] -%>
-(
-cat <<'EOP'
+<% if encrypted_data_bag_secret -%>
+cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
 <%= encrypted_data_bag_secret %>
 EOP
-) > /tmp/encrypted_data_bag_secret
-awk NF /tmp/encrypted_data_bag_secret > /etc/chef/encrypted_data_bag_secret
-rm /tmp/encrypted_data_bag_secret
+chmod 0600 /etc/chef/encrypted_data_bag_secret
+<% end -%>
+
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+<%= hash.to_json %>
+EOP
+<% end -%>
 <% end -%>
 
-(
-cat <<'EOP'
+cat > /etc/chef/client.rb <<'EOP'
 <%= config_content %>
 EOP
-) > /etc/chef/client.rb
 
-(
-cat <<'EOP'
+cat > /etc/chef/first-boot.json <<'EOP'
 <%= first_boot.to_json %>
 EOP
-) > /etc/chef/first-boot.json
 
 <%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/fedora13-gems.erb b/lib/chef/knife/bootstrap/fedora13-gems.erb
index 3c7c5cf..0aabc31 100644
--- a/lib/chef/knife/bootstrap/fedora13-gems.erb
+++ b/lib/chef/knife/bootstrap/fedora13-gems.erb
@@ -10,34 +10,35 @@ gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
 
 mkdir -p /etc/chef
 
-(
-cat <<'EOP'
+cat > /etc/chef/validation.pem <<'EOP'
 <%= validation_key %>
 EOP
-) > /tmp/validation.pem
-awk NF /tmp/validation.pem > /etc/chef/validation.pem
-rm /tmp/validation.pem
+chmod 0600 /etc/chef/validation.pem
 
-<% if @chef_config[:encrypted_data_bag_secret] -%>
-(
-cat <<'EOP'
+<% if encrypted_data_bag_secret -%>
+cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
 <%= encrypted_data_bag_secret %>
 EOP
-) > /tmp/encrypted_data_bag_secret
-awk NF /tmp/encrypted_data_bag_secret > /etc/chef/encrypted_data_bag_secret
-rm /tmp/encrypted_data_bag_secret
+chmod 0600 /etc/chef/encrypted_data_bag_secret
 <% end -%>
 
-(
-cat <<'EOP'
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+<%= hash.to_json %>
+EOP
+<% end -%>
+<% end -%>
+
+cat > /etc/chef/client.rb <<'EOP'
 <%= config_content %>
 EOP
-) > /etc/chef/client.rb
 
-(
-cat <<'EOP'
+cat > /etc/chef/first-boot.json <<'EOP'
 <%= first_boot.to_json %>
 EOP
-) > /etc/chef/first-boot.json
 
 <%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb b/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb
index 9297087..4549b94 100644
--- a/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb
+++ b/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb
@@ -10,22 +10,27 @@ fi
 apt-get update
 apt-get install -y chef
 
-(
-cat <<'EOP'
+cat > /etc/chef/validation.pem <<'EOP'
 <%= validation_key %>
 EOP
-) > /tmp/validation.pem
-awk NF /tmp/validation.pem > /etc/chef/validation.pem
-rm /tmp/validation.pem
+chmod 0600 /etc/chef/validation.pem
 
-<% if @chef_config[:encrypted_data_bag_secret] -%>
-(
-cat <<'EOP'
+<% if encrypted_data_bag_secret -%>
+cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
 <%= encrypted_data_bag_secret %>
 EOP
-) > /tmp/encrypted_data_bag_secret
-awk NF /tmp/encrypted_data_bag_secret > /etc/chef/encrypted_data_bag_secret
-rm /tmp/encrypted_data_bag_secret
+chmod 0600 /etc/chef/encrypted_data_bag_secret
+<% end -%>
+
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+<%= hash.to_json %>
+EOP
+<% end -%>
 <% end -%>
 
 <% unless @chef_config[:validation_client_name] == "chef-validator" -%>
@@ -41,10 +46,8 @@ echo 'http_proxy  "knife_config[:bootstrap_proxy]"' >> /etc/chef/client.rb
 echo 'https_proxy "knife_config[:bootstrap_proxy]"' >> /etc/chef/client.rb
 <% end -%>
 
-(
-cat <<'EOP'
+cat > /etc/chef/first-boot.json <<'EOP'
 <%= first_boot.to_json %>
 EOP
-) > /etc/chef/first-boot.json
 
 <%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb
index 6adc621..62ff7c8 100644
--- a/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb
+++ b/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb
@@ -4,11 +4,8 @@ bash -c '
 if [ ! -f /usr/bin/chef-client ]; then
   apt-get update
   apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras
-  cd /tmp
-  wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://production.cf.rubygems.org/rubygems/rubygems-1.6.2.tgz
-  tar zxf rubygems-1.6.2.tgz
-  cd rubygems-1.6.2
-  ruby setup.rb --no-format-executable
+  wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %>http://production.cf.rubygems.org/rubygems/rubygems-1.6.2.tgz -O - | tar zxf -
+  (cd rubygems-1.6.2 && ruby setup.rb --no-format-executable)
 fi
 
 gem update --no-rdoc --no-ri
@@ -17,34 +14,35 @@ gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
 
 mkdir -p /etc/chef
 
-(
-cat <<'EOP'
+cat > /etc/chef/validation.pem <<'EOP'
 <%= validation_key %>
 EOP
-) > /tmp/validation.pem
-awk NF /tmp/validation.pem > /etc/chef/validation.pem
-rm /tmp/validation.pem
+chmod 0600 /etc/chef/validation.pem
 
-<% if @chef_config[:encrypted_data_bag_secret] -%>
-(
-cat <<'EOP'
+<% if encrypted_data_bag_secret -%>
+cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
 <%= encrypted_data_bag_secret %>
 EOP
-) > /tmp/encrypted_data_bag_secret
-awk NF /tmp/encrypted_data_bag_secret > /etc/chef/encrypted_data_bag_secret
-rm /tmp/encrypted_data_bag_secret
+chmod 0600 /etc/chef/encrypted_data_bag_secret
 <% end -%>
 
-(
-cat <<'EOP'
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+<%= hash.to_json %>
+EOP
+<% end -%>
+<% end -%>
+
+cat > /etc/chef/client.rb <<'EOP'
 <%= config_content %>
 EOP
-) > /etc/chef/client.rb
 
-(
-cat <<'EOP'
+cat > /etc/chef/first-boot.json <<'EOP'
 <%= first_boot.to_json %>
 EOP
-) > /etc/chef/first-boot.json
 
 <%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb b/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb
index 700c47a..8e9c658 100644
--- a/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb
+++ b/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb
@@ -12,34 +12,35 @@ gem install chef --no-rdoc --no-ri --verbose <%= bootstrap_version_string %>
 
 mkdir -p /etc/chef
 
-(
-cat <<'EOP'
+cat > /etc/chef/validation.pem <<'EOP'
 <%= validation_key %>
 EOP
-) > /tmp/validation.pem
-awk NF /tmp/validation.pem > /etc/chef/validation.pem
-rm /tmp/validation.pem
+chmod 0600 /etc/chef/validation.pem
 
-<% if @chef_config[:encrypted_data_bag_secret] -%>
-(
-cat <<'EOP'
+<% if encrypted_data_bag_secret -%>
+cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
 <%= encrypted_data_bag_secret %>
 EOP
-) > /tmp/encrypted_data_bag_secret
-awk NF /tmp/encrypted_data_bag_secret > /etc/chef/encrypted_data_bag_secret
-rm /tmp/encrypted_data_bag_secret
+chmod 0600 /etc/chef/encrypted_data_bag_secret
 <% end -%>
 
-(
-cat <<'EOP'
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+<%= hash.to_json %>
+EOP
+<% end -%>
+<% end -%>
+
+cat > /etc/chef/client.rb <<'EOP'
 <%= config_content %>
 EOP
-) > /etc/chef/client.rb
 
-(
-cat <<'EOP'
-<%= { "run_list" => @run_list }.to_json %>
+cat > /etc/chef/first-boot.json <<'EOP'
+<%= first_boot.to_json %>
 EOP
-) > /etc/chef/first-boot.json
 
-<%= start_chef %>'
\ No newline at end of file
+<%= start_chef %>'
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index 7e8a0cf..285254a 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -61,7 +61,7 @@ class Chef
         # We only get a private_key on client creation, not on client update.
         if client['private_key']
           ui.info("Created #{output}")
-  
+
           if config[:file]
             File.open(config[:file], "w") do |f|
               f.print(client['private_key'])
@@ -71,6 +71,7 @@ class Chef
           end
         else
           ui.error "Client '#{client['name']}' already exists"
+          exit 1
         end
       end
     end
diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb
index 73a93ec..666fd09 100644
--- a/lib/chef/knife/client_reregister.rb
+++ b/lib/chef/knife/client_reregister.rb
@@ -43,14 +43,15 @@ class Chef
           exit 1
         end
 
-        client = Chef::ApiClient.load(@client_name)
-        key = client.save(new_key=true)
+        client = Chef::ApiClient.reregister(@client_name)
+        Chef::Log.debug("Updated client data: #{client.inspect}")
+        key = client.private_key
         if config[:file]
           File.open(config[:file], "w") do |f|
-            f.print(key['private_key'])
+            f.print(key)
           end
         else
-          ui.msg key['private_key']
+          ui.msg key
         end
       end
     end
diff --git a/lib/chef/knife/client_show.rb b/lib/chef/knife/client_show.rb
index 5c2ffb4..822848f 100644
--- a/lib/chef/knife/client_show.rb
+++ b/lib/chef/knife/client_show.rb
@@ -22,6 +22,8 @@ class Chef
   class Knife
     class ClientShow < Knife
 
+      include Knife::Core::MultiAttributeReturnOption
+
       deps do
         require 'chef/api_client'
         require 'chef/json_compat'
@@ -29,11 +31,6 @@ class Chef
 
       banner "knife client show CLIENT (options)"
 
-      option :attribute,
-        :short => "-a ATTR",
-        :long => "--attribute ATTR",
-        :description => "Show only one attribute"
-
       def run
         @client_name = @name_args[0]
 
diff --git a/lib/chef/knife/configure.rb b/lib/chef/knife/configure.rb
index 0526244..3ad4fac 100644
--- a/lib/chef/knife/configure.rb
+++ b/lib/chef/knife/configure.rb
@@ -27,6 +27,7 @@ class Chef
       deps do
         require 'ohai'
         Chef::Knife::ClientCreate.load_deps
+        Chef::Knife::UserCreate.load_deps
       end
 
       banner "knife configure (options)"
@@ -40,7 +41,23 @@ class Chef
         :short => "-i",
         :long => "--initial",
         :boolean => true,
-        :description => "Create an initial API Client"
+        :description => "Create an initial API User"
+
+      option :admin_client_name,
+        :long => "--admin-client-name NAME",
+        :description => "The existing admin clientname (usually admin)"
+
+      option :admin_client_key,
+        :long => "--admin-client-key PATH",
+        :description => "The path to the admin client's private key (usually a file named admin.pem)"
+
+      option :validation_client_name,
+        :long => "--validation-client-name NAME",
+        :description => "The validation clientname (usually chef-validator)"
+
+      option :validation_key,
+        :long => "--validation-key PATH",
+        :description => "The location of the validation key (usually a file named validation.pem)"
 
       def configure_chef
         # We are just faking out the system so that you can do this without a key specified
@@ -65,8 +82,7 @@ client_key               '#{new_client_key}'
 validation_client_name   '#{validation_client_name}'
 validation_key           '#{validation_key}'
 chef_server_url          '#{chef_server}'
-cache_type               'BasicFile'
-cache_options( :path => '#{File.join(chef_config_path, "checksums")}' )
+syntax_check_cache_path  '#{File.join(chef_config_path, "syntax_check_cache")}'
 EOH
           unless chef_repo.empty?
             f.puts "cookbook_path [ '#{chef_repo}/cookbooks' ]"
@@ -78,13 +94,15 @@ EOH
           Chef::Config[:chef_server_url] = chef_server
           Chef::Config[:node_name] = admin_client_name
           Chef::Config[:client_key] = admin_client_key
-          client_create = Chef::Knife::ClientCreate.new
-          client_create.name_args = [ new_client_name ]
-          client_create.config[:admin] = true
-          client_create.config[:file] = new_client_key
-          client_create.config[:yes] = true
-          client_create.config[:disable_editing] = true
-          client_create.run
+          user_create = Chef::Knife::UserCreate.new
+          user_create.name_args = [ new_client_name ]
+          user_create.config[:user_password] = config[:user_password] ||
+            ui.ask("Please enter a password for the new user: ") {|q| q.echo = false}
+          user_create.config[:admin] = true
+          user_create.config[:file] = new_client_key
+          user_create.config[:yes] = true
+          user_create.config[:disable_editing] = true
+          user_create.run
         else
           ui.msg("*****")
           ui.msg("")
@@ -115,19 +133,22 @@ EOH
 
       def ask_user_for_config
         server_name = guess_servername
-        @chef_server            = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "http://#{server_name}:4000")
+        @chef_server            = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "https://#{server_name}:443")
         if config[:initial]
-          @new_client_name        = config[:node_name] || ask_question("Please enter a clientname for the new client: ", :default => Etc.getlogin)
-          @admin_client_name      = config[:admin_client_name] || ask_question("Please enter the existing admin clientname: ", :default => 'chef-webui')
-          @admin_client_key       = config[:admin_client_key] || ask_question("Please enter the location of the existing admin client's private key: ", :default => '/etc/chef/webui.pem')
+          @new_client_name        = config[:node_name] || ask_question("Please enter a name for the new user: ", :default => Etc.getlogin)
+          @admin_client_name      = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", :default => 'admin')
+          @admin_client_key       = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", :default => '/etc/chef-server/admin.pem')
+          @admin_client_key       = File.expand_path(@admin_client_key)
         else
           @new_client_name        = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", :default => Etc.getlogin)
         end
         @validation_client_name = config[:validation_client_name] || ask_question("Please enter the validation clientname: ", :default => 'chef-validator')
-        @validation_key         = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => '/etc/chef/validation.pem')
+        @validation_key         = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => '/etc/chef-server/chef-validator.pem')
+        @validation_key         = File.expand_path(@validation_key)
         @chef_repo              = config[:repository] || ask_question("Please enter the path to a chef repository (or leave blank): ")
 
         @new_client_key = config[:client_key] || File.join(chef_config_path, "#{@new_client_name}.pem")
+        @new_client_key = File.expand_path(@new_client_key)
       end
 
       def guess_servername
diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb
index c9abb26..01bd829 100644
--- a/lib/chef/knife/cookbook_create.rb
+++ b/lib/chef/knife/cookbook_create.rb
@@ -73,9 +73,10 @@ class Chef
         email = config[:cookbook_email] || "YOUR_EMAIL"
         license = ((config[:cookbook_license] != "false") && config[:cookbook_license]) || "none"
         readme_format = ((config[:readme_format] != "false") && config[:readme_format]) || "md"
-        create_cookbook(cookbook_path,cookbook_name, copyright, license)
-        create_readme(cookbook_path,cookbook_name,readme_format)
-        create_metadata(cookbook_path,cookbook_name, copyright, email, license,readme_format)
+        create_cookbook(cookbook_path, cookbook_name, copyright, license)
+        create_readme(cookbook_path, cookbook_name, readme_format)
+        create_changelog(cookbook_path, cookbook_name)
+        create_metadata(cookbook_path, cookbook_name, copyright, email, license, readme_format)
       end
 
       def create_cookbook(dir, cookbook_name, copyright, license)
@@ -179,54 +180,226 @@ EOH
         end
       end
 
-      def create_readme(dir, cookbook_name,readme_format)
+      def create_changelog(dir, cookbook_name)
+        msg("** Creating CHANGELOG for cookbook: #{cookbook_name}")
+        unless File.exists?(File.join(dir,cookbook_name,'CHANGELOG.md'))
+          open(File.join(dir, cookbook_name, 'CHANGELOG.md'),'w') do |file|
+            file.puts <<-EOH
+#{cookbook_name} CHANGELOG
+#{'='*"#{cookbook_name} CHANGELOG".length}
+
+This file is used to list changes made in each version of the #{cookbook_name} cookbook.
+
+0.1.0
+-----
+- [your_name] - Initial release of #{cookbook_name}
+
+- - -
+Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown.
+
+The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown.
+EOH
+          end
+        end
+      end
+
+      def create_readme(dir, cookbook_name, readme_format)
         msg("** Creating README for cookbook: #{cookbook_name}")
         unless File.exists?(File.join(dir, cookbook_name, "README.#{readme_format}"))
           open(File.join(dir, cookbook_name, "README.#{readme_format}"), "w") do |file|
             case readme_format
             when "rdoc"
               file.puts <<-EOH
-= DESCRIPTION:
-
-= REQUIREMENTS:
-
-= ATTRIBUTES:
-
-= USAGE:
-
+= #{cookbook_name} Cookbook
+TODO: Enter the cookbook description here.
+
+e.g.
+This cookbook makes your favorite breakfast sandwich.
+
+== Requirements
+TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
+
+e.g.
+==== packages
+- +toaster+ - #{cookbook_name} needs toaster to brown your bagel.
+
+== Attributes
+TODO: List your cookbook attributes here.
+
+e.g.
+==== #{cookbook_name}::default
+<table>
+  <tr>
+    <th>Key</th>
+    <th>Type</th>
+    <th>Description</th>
+    <th>Default</th>
+  </tr>
+  <tr>
+    <td><tt>['#{cookbook_name}']['bacon']</tt></td>
+    <td>Boolean</td>
+    <td>whether to include bacon</td>
+    <td><tt>true</tt></td>
+  </tr>
+</table>
+
+== Usage
+==== #{cookbook_name}::default
+TODO: Write usage instructions for each cookbook.
+
+e.g.
+Just include +#{cookbook_name}+ in your node's +run_list+:
+
+    {
+      "name":"my_node",
+      "run_list": [
+        "recipe[#{cookbook_name}]"
+      ]
+    }
+
+== Contributing
+TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
+
+e.g.
+1. Fork the repository on Github
+2. Create a named feature branch (like `add_component_x`)
+3. Write your change
+4. Write tests for your change (if applicable)
+5. Run the tests, ensuring they all pass
+6. Submit a Pull Request using Github
+
+== License and Authors
+Authors: TODO: List authors
 EOH
             when "md","mkd","txt"
               file.puts <<-EOH
-Description
-===========
+#{cookbook_name} Cookbook
+#{'='*"#{cookbook_name} Cookbook".length}
+TODO: Enter the cookbook description here.
+
+e.g.
+This cookbook makes your favorite breakfast sandwich.
 
 Requirements
-============
+------------
+TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
+
+e.g.
+#### packages
+- `toaster` - #{cookbook_name} needs toaster to brown your bagel.
 
 Attributes
-==========
+----------
+TODO: List you cookbook attributes here.
+
+e.g.
+#### #{cookbook_name}::default
+<table>
+  <tr>
+    <th>Key</th>
+    <th>Type</th>
+    <th>Description</th>
+    <th>Default</th>
+  </tr>
+  <tr>
+    <td><tt>['#{cookbook_name}']['bacon']</tt></td>
+    <td>Boolean</td>
+    <td>whether to include bacon</td>
+    <td><tt>true</tt></td>
+  </tr>
+</table>
 
 Usage
-=====
-
+-----
+#### #{cookbook_name}::default
+TODO: Write usage instructions for each cookbook.
+
+e.g.
+Just include `#{cookbook_name}` in your node's `run_list`:
+
+```json
+{
+  "name":"my_node",
+  "run_list": [
+    "recipe[#{cookbook_name}]"
+  ]
+}
+```
+
+Contributing
+------------
+TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
+
+e.g.
+1. Fork the repository on Github
+2. Create a named feature branch (like `add_component_x`)
+3. Write your change
+4. Write tests for your change (if applicable)
+5. Run the tests, ensuring they all pass
+6. Submit a Pull Request using Github
+
+License and Authors
+-------------------
+Authors: TODO: List authors
 EOH
             else
               file.puts <<-EOH
-Description
+#{cookbook_name} Cookbook
+#{'='*"#{cookbook_name} Cookbook".length}
+  TODO: Enter the cookbook description here.
+
+  e.g.
+  This cookbook makes your favorite breakfast sandwich.
 
 Requirements
+  TODO: List your cookbook requirements. Be sure to include any requirements this cookbook has on platforms, libraries, other cookbooks, packages, operating systems, etc.
+
+  e.g.
+  toaster         #{cookbook_name} needs toaster to brown your bagel.
 
 Attributes
+  TODO: List you cookbook attributes here.
 
-Usage
+  #{cookbook_name}
+  Key                                   Type        Description                           Default
+  ['#{cookbook_name}']['bacon']         Boolean     whether to include bacon              true
 
+Usage
+  #{cookbook_name}
+  TODO: Write usage instructions for each cookbook.
+
+  e.g.
+  Just include `#{cookbook_name}` in your node's `run_list`:
+
+  [code]
+  {
+    "name":"my_node",
+    "run_list": [
+      "recipe[#{cookbook_name}]"
+    ]
+  }
+  [/code]
+
+Contributing
+  TODO: (optional) If this is a public cookbook, detail the process for contributing. If this is a private cookbook, remove this section.
+
+  e.g.
+  1. Fork the repository on Github
+  2. Create a named feature branch (like `add_component_x`)
+  3. Write your change
+  4. Write tests for your change (if applicable)
+  5. Run the tests, ensuring they all pass
+  6. Submit a Pull Request using Github
+
+License and Authors
+  Authors: TODO: List authors
 EOH
             end
           end
         end
       end
 
-      def create_metadata(dir, cookbook_name, copyright, email, license,readme_format)
+      def create_metadata(dir, cookbook_name, copyright, email, license, readme_format)
         msg("** Creating metadata for cookbook: #{cookbook_name}")
 
         license_name = case license
@@ -248,12 +421,13 @@ EOH
               long_description = "long_description IO.read(File.join(File.dirname(__FILE__), 'README.#{readme_format}'))"
             end
             file.puts <<-EOH
-maintainer       "#{copyright}"
-maintainer_email "#{email}"
-license          "#{license_name}"
-description      "Installs/Configures #{cookbook_name}"
+name             '#{cookbook_name}'
+maintainer       '#{copyright}'
+maintainer_email '#{email}'
+license          '#{license_name}'
+description      'Installs/Configures #{cookbook_name}'
 #{long_description}
-version          "0.0.1"
+version          '0.1.0'
 EOH
           end
         end
diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb
index 1da1121..cb8eeb8 100644
--- a/lib/chef/knife/cookbook_download.rb
+++ b/lib/chef/knife/cookbook_download.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -61,6 +61,10 @@ class Chef
           exit 1
         elsif @version.nil?
           @version = determine_version
+          if @version.nil?
+            ui.fatal("No such cookbook found")
+            exit 1
+          end
         end
 
         ui.info("Downloading #{@cookbook_name} cookbook version #{@version}")
@@ -95,10 +99,13 @@ class Chef
       end
 
       def determine_version
-        if available_versions.size == 1
+
+        if available_versions.nil?
+          nil
+        elsif available_versions.size == 1
           @version = available_versions.first
         elsif config[:latest]
-          @version = available_versions.map { |v| Chef::Version.new(v) }.sort.last
+          @version = available_versions.last
         else
           ask_which_version
         end
@@ -106,10 +113,11 @@ class Chef
 
       def available_versions
         @available_versions ||= begin
-          versions = Chef::CookbookVersion.available_versions(@cookbook_name).map do |version|
-            Chef::Version.new(version)
+          versions = Chef::CookbookVersion.available_versions(@cookbook_name)
+          unless versions.nil?
+            versions.map! { |version| Chef::Version.new(version) }
+            versions.sort!
           end
-          versions.sort!
           versions
         end
         @available_versions
diff --git a/lib/chef/knife/cookbook_metadata.rb b/lib/chef/knife/cookbook_metadata.rb
index ac0b6a7..dfa69ae 100644
--- a/lib/chef/knife/cookbook_metadata.rb
+++ b/lib/chef/knife/cookbook_metadata.rb
@@ -46,6 +46,7 @@ class Chef
 
         if config[:all]
           cl = Chef::CookbookLoader.new(config[:cookbook_path])
+          cl.load_cookbooks
           cl.each do |cname, cookbook|
             generate_metadata(cname.to_s)
           end
diff --git a/lib/chef/knife/cookbook_metadata_from_file.rb b/lib/chef/knife/cookbook_metadata_from_file.rb
index eb1afa8..8e26251 100644
--- a/lib/chef/knife/cookbook_metadata_from_file.rb
+++ b/lib/chef/knife/cookbook_metadata_from_file.rb
@@ -9,9 +9,9 @@
 # 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.
diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb
index 3545d20..7c9cbeb 100644
--- a/lib/chef/knife/cookbook_show.rb
+++ b/lib/chef/knife/cookbook_show.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -50,7 +50,7 @@ class Chef
         :long => "--with-uri",
         :description => "Show corresponding URIs"
 
-      def run 
+      def run
         case @name_args.length
         when 4 # We are showing a specific file
           node = Hash.new
diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb
index a349313..913d171 100644
--- a/lib/chef/knife/cookbook_site_install.rb
+++ b/lib/chef/knife/cookbook_site_install.rb
@@ -52,6 +52,13 @@ class Chef
         :description => "Default branch to work with",
         :default => "master"
 
+      option :use_current_branch,
+        :short =>  "-b",
+        :long => "--use-current-branch",
+        :description => "Use the current branch",
+        :boolean => true,
+        :default => false
+
       attr_reader :cookbook_name
       attr_reader :vendor_path
 
@@ -67,7 +74,7 @@ class Chef
         @cookbook_name = parse_name_args!
         # Check to ensure we have a valid source of cookbooks before continuing
         #
-        @install_path = File.expand_path(config[:cookbook_path].first)
+        @install_path = File.expand_path(Array(config[:cookbook_path]).first)
         ui.info "Installing #@cookbook_name to #{@install_path}"
 
         @repo = CookbookSCMRepo.new(@install_path, ui, config)
@@ -75,8 +82,10 @@ class Chef
         upstream_file = File.join(@install_path, "#{@cookbook_name}.tar.gz")
 
         @repo.sanity_check
-        @repo.reset_to_default_state
-        @repo.prepare_to_import(@cookbook_name)
+        unless config[:use_current_branch]
+          @repo.reset_to_default_state
+          @repo.prepare_to_import(@cookbook_name)
+        end
 
         downloader = download_cookbook_to(upstream_file)
         clear_existing_files(File.join(@install_path, @cookbook_name))
@@ -88,10 +97,14 @@ class Chef
         File.unlink(upstream_file)
 
         if @repo.finalize_updates_to(@cookbook_name, downloader.version)
-          @repo.reset_to_default_state
+          unless config[:use_current_branch]
+            @repo.reset_to_default_state
+          end
           @repo.merge_updates_from(@cookbook_name, downloader.version)
         else
-          @repo.reset_to_default_state
+          unless config[:use_current_branch]
+            @repo.reset_to_default_state
+          end
         end
 
 
@@ -131,13 +144,21 @@ class Chef
 
       def extract_cookbook(upstream_file, version)
         ui.info("Uncompressing #{@cookbook_name} version #{version}.")
-        shell_out!("tar zxvf #{Shellwords.escape upstream_file}", :cwd => @install_path)
+        shell_out!("tar zxvf #{convert_path upstream_file}", :cwd => @install_path)
       end
 
       def clear_existing_files(cookbook_path)
         ui.info("Removing pre-existing version.")
         FileUtils.rmtree(cookbook_path) if File.directory?(cookbook_path)
       end
+
+      def convert_path(upstream_file)
+      	if ENV['MSYSTEM'] == 'MINGW32'
+      	  return upstream_file.sub(/^([[:alpha:]]):/, '/\1')
+	      else
+      	  return Shellwords.escape upstream_file
+      	end
+      end
     end
   end
 end
diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb
index 96c4ef0..fe83b71 100644
--- a/lib/chef/knife/cookbook_site_list.rb
+++ b/lib/chef/knife/cookbook_site_list.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb
index 5df7d67..b636276 100644
--- a/lib/chef/knife/cookbook_site_search.rb
+++ b/lib/chef/knife/cookbook_site_search.rb
@@ -5,9 +5,9 @@
 # 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.
@@ -36,7 +36,7 @@ class Chef
         end
         new_start = start + cr["items"].length
         if new_start < cr["total"]
-          search_cookbook(query, items, new_start, cookbook_collection) 
+          search_cookbook(query, items, new_start, cookbook_collection)
         else
           cookbook_collection
         end
diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb
index 61177fa..21645d4 100644
--- a/lib/chef/knife/cookbook_site_share.rb
+++ b/lib/chef/knife/cookbook_site_share.rb
@@ -23,6 +23,7 @@ class Chef
     class CookbookSiteShare < Knife
 
       deps do
+        require 'chef/cookbook_loader'
         require 'chef/cookbook_uploader'
         require 'chef/cookbook_site_streaming_uploader'
       end
@@ -50,7 +51,7 @@ class Chef
         cl = Chef::CookbookLoader.new(config[:cookbook_path])
         if cl.cookbook_exists?(cookbook_name)
           cookbook = cl[cookbook_name]
-          Chef::CookbookUploader.new(cookbook,config[:cookbook_path]).validate_cookbook
+          Chef::CookbookUploader.new(cookbook,config[:cookbook_path]).validate_cookbooks
           tmp_cookbook_dir = Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook)
           begin
             Chef::Log.debug("Temp cookbook directory is #{tmp_cookbook_dir.inspect}")
diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb
index cbaa815..d15098e 100644
--- a/lib/chef/knife/cookbook_site_show.rb
+++ b/lib/chef/knife/cookbook_site_show.rb
@@ -5,9 +5,9 @@
 # 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.
@@ -25,13 +25,16 @@ class Chef
       category "cookbook site"
 
       def run
+        output(format_for_display(get_cookbook_data))
+      end
+
+      def get_cookbook_data
         case @name_args.length
-        when 1 
-          cookbook_data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}")
+        when 1
+          noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}")
         when 2
-          cookbook_data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}")
+          noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}")
         end
-        output(format_for_display(cookbook_data))
       end
 
       def get_cookbook_list(items=10, start=0, cookbook_collection={})
@@ -42,7 +45,7 @@ class Chef
         end
         new_start = start + cr["items"].length
         if new_start < cr["total"]
-          get_cookbook_list(items, new_start, cookbook_collection) 
+          get_cookbook_list(items, new_start, cookbook_collection)
         else
           cookbook_collection
         end
diff --git a/lib/chef/knife/cookbook_test.rb b/lib/chef/knife/cookbook_test.rb
index 1cf71ae..0ff992b 100644
--- a/lib/chef/knife/cookbook_test.rb
+++ b/lib/chef/knife/cookbook_test.rb
@@ -25,7 +25,7 @@ class Chef
     class CookbookTest < Knife
 
       deps do
-        require 'chef/checksum_cache'
+        require 'chef/cookbook_loader'
         require 'chef/cookbook/syntax_check'
       end
 
@@ -47,7 +47,9 @@ class Chef
 
         checked_a_cookbook = false
         if config[:all]
-          cookbook_loader.each do |key, cookbook|
+          cl = cookbook_loader
+          cl.load_cookbooks
+          cl.each do |key, cookbook|
             checked_a_cookbook = true
             test_cookbook(key)
           end
diff --git a/lib/chef/knife/cookbook_upload.rb b/lib/chef/knife/cookbook_upload.rb
index acab378..d4a66e6 100644
--- a/lib/chef/knife/cookbook_upload.rb
+++ b/lib/chef/knife/cookbook_upload.rb
@@ -19,6 +19,7 @@
 #
 
 require 'chef/knife'
+require 'chef/cookbook_uploader'
 
 class Chef
   class Knife
@@ -86,7 +87,6 @@ class Chef
         end
 
         assert_environment_valid!
-        warn_about_cookbook_shadowing
         version_constraints_to_update = {}
         upload_failures = 0
         upload_ok = 0
@@ -94,31 +94,56 @@ class Chef
         # Get a list of cookbooks and their versions from the server
         # to check for the existence of a cookbook's dependencies.
         @server_side_cookbooks = Chef::CookbookVersion.list_all_versions
-
-        justify_width = cookbooks_to_upload.map {|name, cookbook| name.size}.max.to_i + 2
-
-        cookbooks_to_upload.each do |cookbook_name, cookbook|
-          cookbook.freeze_version if config[:freeze]
-          begin
-            upload(cookbook, justify_width)
-            upload_ok += 1
+        justify_width = @server_side_cookbooks.map {|name| name.size}.max.to_i + 2
+        if config[:all]
+          cookbook_repo.load_cookbooks
+          cbs = []
+          cookbook_repo.each do |cookbook_name, cookbook|
+            cbs << cookbook
+            cookbook.freeze_version if config[:freeze]
             version_constraints_to_update[cookbook_name] = cookbook.version
+          end
+          begin
+            upload(cbs, justify_width)
           rescue Exceptions::CookbookFrozen
-            upload_failures += 1
-            ui.warn("Not updating version constraints for #{cookbook_name} in the environment as the cookbook is frozen.") if config[:environment]
+            ui.warn("Not updating version constraints for some cookbooks in the environment as the cookbook is frozen.")
+          end
+          ui.info("Uploaded all cookbooks.")
+        else
+          if @name_args.empty?
+            show_usage
+            ui.error("You must specify the --all flag or at least one cookbook name")
+            exit 1
+          end
+
+          cookbooks_to_upload.each do |cookbook_name, cookbook|
+            cookbook.freeze_version if config[:freeze]
+            begin
+              upload([cookbook], justify_width)
+              upload_ok += 1
+              version_constraints_to_update[cookbook_name] = cookbook.version
+            rescue Exceptions::CookbookNotFoundInRepo => e
+              upload_failures += 1
+              ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
+              Log.debug(e)
+              upload_failures += 1
+            rescue Exceptions::CookbookFrozen
+              ui.warn("Not updating version constraints for #{cookbook_name} in the environment as the cookbook is frozen.")
+              upload_failures += 1
+            end
           end
-        end
 
-        upload_failures += config[:all] ? 0 : @name_args.length - @cookbooks_to_upload.length
+          upload_failures += @name_args.length - @cookbooks_to_upload.length
 
-        if upload_failures == 0
-          ui.info "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""}."
-        elsif upload_failures > 0 && upload_ok > 0
-          ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""} ok but #{upload_failures} " +
-            "cookbook#{upload_failures > 1 ? "s" : ""} upload failed."
-        elsif upload_failures > 0 && upload_ok == 0
-          ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures > 1 ? "s" : ""}."
-          exit 1
+          if upload_failures == 0
+            ui.info "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""}."
+          elsif upload_failures > 0 && upload_ok > 0
+            ui.warn "Uploaded #{upload_ok} cookbook#{upload_ok > 1 ? "s" : ""} ok but #{upload_failures} " +
+              "cookbook#{upload_failures > 1 ? "s" : ""} upload failed."
+          elsif upload_failures > 0 && upload_ok == 0
+            ui.error "Failed to upload #{upload_failures} cookbook#{upload_failures > 1 ? "s" : ""}."
+            exit 1
+          end
         end
 
         unless version_constraints_to_update.empty?
@@ -129,22 +154,22 @@ class Chef
       def cookbooks_to_upload
         @cookbooks_to_upload ||=
           if config[:all]
-            cookbook_repo
+            cookbook_repo.load_cookbooks
           else
             upload_set = {}
             @name_args.each do |cookbook_name|
-            begin
-              if ! upload_set.has_key?(cookbook_name)
-                upload_set[cookbook_name] = cookbook_repo[cookbook_name]
-                if config[:depends]
-                  upload_set[cookbook_name].metadata.dependencies.each { |dep, ver| @name_args << dep }
+              begin
+                if ! upload_set.has_key?(cookbook_name)
+                  upload_set[cookbook_name] = cookbook_repo[cookbook_name]
+                  if config[:depends]
+                    upload_set[cookbook_name].metadata.dependencies.each { |dep, ver| @name_args << dep }
+                  end
                 end
+              rescue Exceptions::CookbookNotFoundInRepo => e
+                ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
+                Log.debug(e)
               end
-            rescue Exceptions::CookbookNotFoundInRepo => e
-              ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
-              Log.debug(e)
             end
-          end
             upload_set
           end
       end
@@ -197,20 +222,16 @@ WARNING
         end
       end
 
-      def upload(cookbook, justify_width)
-        ui.info("Uploading #{cookbook.name.to_s.ljust(justify_width + 10)} [#{cookbook.version}]")
-        check_for_broken_links!(cookbook)
-        check_for_dependencies!(cookbook)
-        Chef::CookbookUploader.new(cookbook, config[:cookbook_path], :force => config[:force]).upload_cookbook
-      rescue Net::HTTPServerException => e
-        case e.response.code
-        when "409"
-          ui.error "Version #{cookbook.version} of cookbook #{cookbook.name} is frozen. Use --force to override."
-          Log.debug(e)
-          raise Exceptions::CookbookFrozen
-        else
-          raise
+      def upload(cookbooks, justify_width)
+        cookbooks.each do |cb|
+          ui.info("Uploading #{cb.name.to_s.ljust(justify_width + 10)} [#{cb.version}]")
+          check_for_broken_links!(cb)
+          check_for_dependencies!(cb)
         end
+        Chef::CookbookUploader.new(cookbooks, config[:cookbook_path], :force => config[:force]).upload_cookbooks
+      rescue Chef::Exceptions::CookbookFrozen => e
+        ui.error e
+        raise
       end
 
       def check_for_broken_links!(cookbook)
@@ -234,7 +255,7 @@ WARNING
         # the version is in the cookbooks being uploaded. If not, exit and warn the user.
         cookbook.metadata.dependencies.each do |cookbook_name, version|
           unless check_server_side_cookbooks(cookbook_name, version) || check_uploading_cookbooks(cookbook_name, version)
-            ui.error "Cookbook #{cookbook.name} depends on cookbook #{cookbook_name} version #{version},"
+            ui.error "Cookbook #{cookbook.name} depends on cookbook '#{cookbook_name}' version '#{version}',"
             ui.error "which is not currently being uploaded and cannot be found on the server."
             exit 1
           end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 71dc008..e1ad606 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -25,7 +25,7 @@ class Chef
       # following instance variables:
       # * @config   - a hash of knife's config values
       # * @run_list - the run list for the node to boostrap
-      # 
+      #
       class BootstrapContext
 
         def initialize(config, run_list, chef_config)
@@ -47,16 +47,22 @@ class Chef
         end
 
         def validation_key
-          IO.read(@chef_config[:validation_key])
+          IO.read(File.expand_path(@chef_config[:validation_key]))
         end
 
         def encrypted_data_bag_secret
-          IO.read(@chef_config[:encrypted_data_bag_secret])
+          knife_config[:secret] || begin
+            if knife_config[:secret_file] && File.exist?(knife_config[:secret_file])
+              IO.read(File.expand_path(knife_config[:secret_file]))
+            elsif @chef_config[:encrypted_data_bag_secret] && File.exist?(@chef_config[:encrypted_data_bag_secret])
+              IO.read(File.expand_path(@chef_config[:encrypted_data_bag_secret]))
+            end
+          end
         end
 
         def config_content
           client_rb = <<-CONFIG
-log_level        :info
+log_level        :auto
 log_location     STDOUT
 chef_server_url  "#{@chef_config[:chef_server_url]}"
 validation_client_name "#{@chef_config[:validation_client_name]}"
@@ -72,7 +78,11 @@ CONFIG
             client_rb << %Q{https_proxy       "#{knife_config[:bootstrap_proxy]}"\n}
           end
 
-          if @chef_config[:encrypted_data_bag_secret]
+          if knife_config[:bootstrap_no_proxy]
+            client_rb << %Q{no_proxy       "#{knife_config[:bootstrap_no_proxy]}"\n}
+          end
+
+          if encrypted_data_bag_secret
             client_rb << %Q{encrypted_data_bag_secret "/etc/chef/encrypted_data_bag_secret"\n}
           end
 
@@ -94,7 +104,7 @@ CONFIG
         def chef_version
           knife_config[:bootstrap_version] || Chef::VERSION
         end
-        
+
         def first_boot
           (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list)
         end
diff --git a/lib/chef/knife/core/cookbook_scm_repo.rb b/lib/chef/knife/core/cookbook_scm_repo.rb
index 5e675ee..727cff3 100644
--- a/lib/chef/knife/core/cookbook_scm_repo.rb
+++ b/lib/chef/knife/core/cookbook_scm_repo.rb
@@ -28,12 +28,14 @@ class Chef
 
       attr_reader :repo_path
       attr_reader :default_branch
+      attr_reader :use_current_branch
       attr_reader :ui
 
       def initialize(repo_path, ui, opts={})
         @repo_path = repo_path
         @ui = ui
         @default_branch = 'master'
+        @use_current_branch = false
         apply_opts(opts)
       end
 
@@ -47,6 +49,9 @@ class Chef
           ui.info("Use `git init` to initialize a git repo")
           exit 1
         end
+        if use_current_branch
+          @default_branch = get_current_branch()
+        end
         unless branch_exists?(default_branch)
           ui.error "The default branch '#{default_branch}' does not exist"
           ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks"
@@ -117,6 +122,11 @@ class Chef
         git("branch --no-color").stdout.lines.any? {|l| l =~ /\s#{Regexp.escape(branch_name)}(?:\s|$)/ }
       end
 
+      def get_current_branch()
+        ref = git("symbolic-ref HEAD").stdout
+        ref.chomp.split('/')[2]
+      end
+
       private
 
       def git_repo?(directory)
@@ -134,6 +144,8 @@ class Chef
           case option.to_s
           when 'default_branch'
             @default_branch = value
+          when 'use_current_branch'
+            @use_current_branch = value
           end
         end
       end
diff --git a/lib/chef/knife/core/generic_presenter.rb b/lib/chef/knife/core/generic_presenter.rb
index c7ce9bb..a1aeadb 100644
--- a/lib/chef/knife/core/generic_presenter.rb
+++ b/lib/chef/knife/core/generic_presenter.rb
@@ -22,6 +22,22 @@ class Chef
   class Knife
     module Core
 
+      # Allows includer knife commands to  return multiple attributes
+      # @brief knife node show NAME -a ATTR1 -a ATTR2
+      module MultiAttributeReturnOption
+        # :nodoc:
+        def self.included(includer)
+          includer.class_eval do
+            @attrs_to_show = []
+            option :attribute,
+              :short => "-a ATTR1 [-a ATTR2]",
+              :long => "--attribute ATTR1 [--attribute ATTR2] ",
+              :proc => lambda {|val| @attrs_to_show << val},
+              :description => "Show one or more attributes"
+          end
+        end
+      end
+
       #==Chef::Knife::Core::GenericPresenter
       # The base presenter class for displaying structured data in knife commands.
       # This is not an abstract base class, and it is suitable for displaying
@@ -121,7 +137,19 @@ class Chef
         end
 
         def format_for_display(data)
-          if config[:attribute]
+          if formatting_subset_of_data?
+            format_data_subset_for_display(data)
+          elsif config[:id_only]
+            name_or_id_for(data)
+          elsif config[:environment] && data.respond_to?(:chef_environment)
+            {"chef_environment" => data.chef_environment}
+          else
+            data
+          end
+        end
+
+        def format_data_subset_for_display(data)
+          subset = if config[:attribute]
             result = {}
             Array(config[:attribute]).each do |nested_value_spec|
               nested_value = extract_nested_value(data, nested_value_spec)
@@ -129,22 +157,23 @@ class Chef
             end
             result
           elsif config[:run_list]
-            data = data.run_list.run_list
-            { "run_list" => data }
-          elsif config[:environment]
-            if data.respond_to?(:chef_environment)
-              {"chef_environment" => data.chef_environment}
-            else
-              # this is a place holder for now. Feel free to modify (i.e. add other cases). [nuo]
-              data
-            end
-          elsif config[:id_only]
-            data.respond_to?(:name) ? data.name : data["id"]
+            run_list = data.run_list.run_list
+            { "run_list" => run_list }
           else
-            data
+            raise ArgumentError, "format_data_subset_for_display requires attribute, run_list, or id_only config option to be set"
           end
+          {name_or_id_for(data) => subset }
         end
 
+        def name_or_id_for(data)
+          data.respond_to?(:name) ? data.name : data["id"]
+        end
+
+        def formatting_subset_of_data?
+          config[:attribute] || config[:run_list]
+        end
+
+
         def extract_nested_value(data, nested_value_spec)
           nested_value_spec.split(".").each do |attr|
             if data.nil?
diff --git a/lib/chef/knife/core/node_editor.rb b/lib/chef/knife/core/node_editor.rb
index 22ba3ea..0734921 100644
--- a/lib/chef/knife/core/node_editor.rb
+++ b/lib/chef/knife/core/node_editor.rb
@@ -18,6 +18,7 @@
 
 require 'chef/json_compat'
 require 'chef/node'
+require 'tempfile'
 
 class Chef
   class Knife
@@ -35,11 +36,24 @@ class Chef
         abort "You specified the --disable_editing option, nothing to edit" if config[:disable_editing]
         assert_editor_set!
 
-        updated_node_data = edit_data(view)
+        updated_node_data = @ui.edit_data(view)
         apply_updates(updated_node_data)
         @updated_node
       end
 
+      def updated?
+        pristine_copy = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(node), :create_additions => false)
+        updated_copy  = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@updated_node), :create_additions => false)
+        unless pristine_copy == updated_copy
+          updated_properties = %w{name normal chef_environment run_list default override automatic}.reject do |key|
+             pristine_copy[key] == updated_copy[key]
+          end
+        end
+        ( pristine_copy != updated_copy ) && updated_properties
+      end
+
+      private
+
       def view
         result = {}
         result["name"] = node.name
@@ -52,12 +66,7 @@ class Chef
           result["override"]  = node.override_attrs
           result["automatic"] = node.automatic_attrs
         end
-        Chef::JSONCompat.to_json_pretty(result)
-      end
-
-      def edit_data(text)
-        edited_data = tempfile_for(text) {|filename| system("#{config[:editor]} #{filename}")}
-        Chef::JSONCompat.from_json(edited_data)
+        result
       end
 
       def apply_updates(updated_data)
@@ -84,19 +93,6 @@ class Chef
         end
       end
 
-      def updated?
-        pristine_copy = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(node), :create_additions => false)
-        updated_copy  = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@updated_node), :create_additions => false)
-        unless pristine_copy == updated_copy
-          updated_properties = %w{name normal chef_environment run_list default override automatic}.reject do |key|
-             pristine_copy[key] == updated_copy[key]
-          end
-        end
-        ( pristine_copy != updated_copy ) && updated_properties
-      end
-
-      private
-
       def abort(message)
         ui.error(message)
         exit 1
@@ -108,22 +104,6 @@ class Chef
         end
       end
 
-      def tempfile_for(data)
-        # TODO: include useful info like the node name in the temp file
-        # name
-        basename = "knife-edit-" << rand(1_000_000_000_000_000).to_s.rjust(15, '0') << '.js'
-        filename = File.join(Dir.tmpdir, basename)
-        File.open(filename, "w+") do |f|
-          f.sync = true
-          f.puts data
-        end
-
-        yield filename
-
-        IO.read(filename)
-      ensure
-        File.unlink(filename)
-      end
     end
   end
 end
diff --git a/lib/chef/knife/core/node_presenter.rb b/lib/chef/knife/core/node_presenter.rb
index ce8c66c..d1aab59 100644
--- a/lib/chef/knife/core/node_presenter.rb
+++ b/lib/chef/knife/core/node_presenter.rb
@@ -86,7 +86,7 @@ class Chef
         # Converts a Chef::Node object to a string suitable for output to a
         # terminal. If config[:medium_output] or config[:long_output] are set
         # the volume of output is adjusted accordingly. Uses colors if enabled
-        # in the the ui object.
+        # in the ui object.
         def summarize(data)
           if data.kind_of?(Chef::Node)
             node = data
@@ -102,6 +102,7 @@ class Chef
 #{key('Roles:')}       #{Array(node[:roles]).join(', ')}
 #{key('Recipes:')}     #{Array(node[:recipes]).join(', ')}
 #{key('Platform:')}    #{node[:platform]} #{node[:platform_version]}
+#{key('Tags:')}        #{Array(node[:tags]).join(', ')}
 SUMMARY
             if config[:medium_output] || config[:long_output]
               summarized +=<<-MORE
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index 314f54b..91c0590 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,9 +21,6 @@ class Chef
   class Knife
     class SubcommandLoader
 
-      CHEF_FILE_IN_GEM = /chef-[\d]+\.[\d]+\.[\d]+/
-      CURRENT_CHEF_GEM = /chef-#{Regexp.escape(Chef::VERSION)}/
-
       attr_reader :chef_config_dir
       attr_reader :env
 
@@ -86,8 +83,7 @@ class Chef
       end
 
       def find_subcommands_via_rubygems
-        files = Gem.find_files 'chef/knife/*.rb'
-        files.reject! {|f| from_old_gem?(f) }
+        files = find_files_latest_gems 'chef/knife/*.rb'
         subcommand_files = {}
         files.each do |file|
           rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
@@ -99,13 +95,48 @@ class Chef
 
       private
 
-      # wow, this is a sad hack :(
-      # Gem.find_files finds files in all versions of a gem, which
-      # means that if chef 0.10 and 0.9.x are installed, we'll try to
-      # require, e.g., chef/knife/ec2_server_create, which will cause
-      # a gem activation error. So remove files from older chef gems.
-      def from_old_gem?(path)
-        path =~ CHEF_FILE_IN_GEM && path !~ CURRENT_CHEF_GEM
+      def find_files_latest_gems(glob, check_load_path=true)
+        files = []
+
+        if check_load_path
+          files = $LOAD_PATH.map { |load_path|
+            Dir["#{File.expand_path glob, load_path}#{Gem.suffix_pattern}"]
+          }.flatten.select { |file| File.file? file.untaint }
+        end
+
+        gem_files = latest_gem_specs.map do |spec|
+          # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
+          if spec.respond_to? :matches_for_glob
+            spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+          else
+            check_spec_for_glob(spec, glob)
+          end
+        end.flatten
+
+        files.concat gem_files
+        files.uniq! if check_load_path
+
+        return files
+      end
+
+      def latest_gem_specs
+        @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
+          Gem::Specification.latest_specs
+        else
+          Gem.source_index.latest_specs
+        end
+      end
+
+      def check_spec_for_glob(spec, glob)
+        dirs = if spec.require_paths.size > 1 then
+          "{#{spec.require_paths.join(',')}}"
+        else
+          spec.require_paths.first
+        end
+
+        glob = File.join("#{spec.full_gem_path}/#{dirs}", glob)
+
+        Dir[glob].map { |f| f.untaint }
       end
     end
   end
diff --git a/lib/chef/knife/core/text_formatter.rb b/lib/chef/knife/core/text_formatter.rb
index 21142ce..6032848 100644
--- a/lib/chef/knife/core/text_formatter.rb
+++ b/lib/chef/knife/core/text_formatter.rb
@@ -41,58 +41,44 @@ class Chef
           @formatted_data ||= text_format(data)
         end
 
-        def text_format(data, indent=0)
+        def text_format(data)
           buffer = ''
 
           if data.respond_to?(:keys)
-            justify_width = data.keys.map {|k| k.to_s.size }.max.to_i + 2
+            justify_width = data.keys.map {|k| k.to_s.size }.max.to_i + 1
             data.sort.each do |key, value|
-              justified_key = ui.color("#{key}:".ljust(justify_width), :cyan)
-              if should_enumerate?(value)
-                buffer << indent_line(justified_key, indent)
-                buffer << text_format(value, indent + 1)
+              # key: ['value'] should be printed as key: value
+              if value.kind_of?(Array) && value.size == 1 && is_singleton(value[0])
+                value = value[0]
+              end
+              if is_singleton(value)
+                # Strings are printed as key: value.
+                justified_key = ui.color("#{key}:".ljust(justify_width), :cyan)
+                buffer << "#{justified_key} #{value}\n"
               else
-                buffer << indent_key_value(justified_key, value, justify_width, indent)
+                # Arrays and hashes get indented on their own lines.
+                buffer << ui.color("#{key}:\n", :cyan)
+                lines = text_format(value).split("\n")
+                lines.each { |line| buffer << "  #{line}\n" }
               end
             end
           elsif data.kind_of?(Array)
-            data.each do |item|
-              if should_enumerate?(data)
-                buffer << text_format(item, indent + 1)
-              else
-                buffer << indent_line(item, indent)
-              end
+            data.each_index do |index|
+              item = data[index]
+              buffer << text_format(data[index])
+              # Separate items with newlines if it's an array of hashes or an
+              # array of arrays
+              buffer << "\n" if !is_singleton(data[index]) && index != data.size-1
             end
           else
-            buffer << indent_line(stringify_value(data), indent)
+            buffer << "#{data}\n"
           end
           buffer
         end
 
-        # Ruby 1.8 Strings include enumberable, which is not what we want. So
-        # we have this heuristic to detect hashes and arrays instead.
-        def should_enumerate?(value)
-          ((value.respond_to?(:keys) && !value.empty? ) || ( value.kind_of?(Array) && (value.size > 1 || should_enumerate?(value.first) )))
-        end
-
-        def indent_line(string, indent)
-          ("  " * indent) << "#{string}\n"
-        end
-
-        def indent_key_value(key, value, justify_width, indent)
-          lines = value.to_s.split("\n")
-          if lines.size > 1
-            total_indent = (2 * indent) + justify_width + 1
-            indent_line("#{key} #{lines.shift}", indent) << lines.map {|l| (" " * total_indent) + l << "\n" }.join("")
-          else
-            indent_line("#{key} #{stringify_value(value)}", indent)
-          end
-        end
-
-        def stringify_value(data)
-          data.kind_of?(String) ? data : data.to_s
+        def is_singleton(value)
+          !(value.kind_of?(Array) || value.respond_to?(:keys))
         end
-
       end
     end
   end
diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb
index 4c80e9e..dfa8c11 100644
--- a/lib/chef/knife/core/ui.rb
+++ b/lib/chef/knife/core/ui.rb
@@ -19,8 +19,9 @@
 #
 
 require 'forwardable'
-require 'chef/platform'
+require 'chef/platform/query_helpers'
 require 'chef/knife/core/generic_presenter'
+require 'tempfile'
 
 class Chef
   class Knife
@@ -36,6 +37,8 @@ class Chef
       attr_reader :stdin
       attr_reader :config
 
+      attr_reader :presenter
+
       def_delegator :@presenter, :format_list_for_display
       def_delegator :@presenter, :format_for_display
       def_delegator :@presenter, :format_cookbook_list_for_display
@@ -62,14 +65,24 @@ class Chef
       # Prints a message to stdout. Aliased as +info+ for compatibility with
       # the logger API.
       def msg(message)
-        stdout.puts message
+        begin
+          stdout.puts message
+        rescue Errno::EPIPE => e
+          raise e if @config[:verbosity] >= 2
+          exit 0
+        end
       end
 
       alias :info :msg
 
       # Prints a msg to stderr. Used for warn, error, and fatal.
       def err(message)
-        stderr.puts message
+        begin
+          stderr.puts message
+        rescue Errno::EPIPE => e
+          raise e if @config[:verbosity] >= 2
+          exit 0
+        end
       end
 
       # Print a warning message
@@ -141,26 +154,26 @@ class Chef
       end
 
       def pretty_print(data)
-        stdout.puts data
+        begin
+          stdout.puts data
+        rescue Errno::EPIPE => e
+          raise e if @config[:verbosity] >= 2
+          exit 0
+        end
       end
 
       def edit_data(data, parse_output=true)
         output = Chef::JSONCompat.to_json_pretty(data)
 
         if (!config[:disable_editing])
-          filename = "knife-edit-"
-          0.upto(20) { filename += rand(9).to_s }
-          filename << ".js"
-          filename = File.join(Dir.tmpdir, filename)
-          tf = File.open(filename, "w")
-          tf.sync = true
-          tf.puts output
-          tf.close
-          raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}")
-          tf = File.open(filename, "r")
-          output = tf.gets(nil)
-          tf.close
-          File.unlink(filename)
+          Tempfile.open([ 'knife-edit-', '.json' ]) do |tf|
+            tf.sync = true
+            tf.puts output
+            tf.close
+            raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}")
+
+            output = IO.read(tf.path)
+          end
         end
 
         parse_output ? Chef::JSONCompat.from_json(output) : output
diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb
index e644ab7..55c1c71 100644
--- a/lib/chef/knife/data_bag_create.rb
+++ b/lib/chef/knife/data_bag_create.rb
@@ -32,13 +32,15 @@ class Chef
       category "data bag"
 
       option :secret,
-      :short => "-s SECRET",
-      :long  => "--secret ",
-      :description => "The secret key to use to encrypt data bag item values"
+        :short => "-s SECRET",
+        :long  => "--secret ",
+        :description => "The secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
 
       option :secret_file,
-      :long => "--secret-file SECRET_FILE",
-      :description => "A file containing the secret key to use to encrypt data bag item values"
+        :long => "--secret-file SECRET_FILE",
+        :description => "A file containing the secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
 
       def read_secret
         if config[:secret]
diff --git a/lib/chef/knife/data_bag_delete.rb b/lib/chef/knife/data_bag_delete.rb
index f8e5201..575e9d6 100644
--- a/lib/chef/knife/data_bag_delete.rb
+++ b/lib/chef/knife/data_bag_delete.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -29,7 +29,7 @@ class Chef
       banner "knife data bag delete BAG [ITEM] (options)"
       category "data bag"
 
-      def run 
+      def run
         if @name_args.length == 2
           delete_object(Chef::DataBagItem, @name_args[1], "data_bag_item") do
             rest.delete_rest("data/#{@name_args[0]}/#{@name_args[1]}")
diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb
index 9ea9e5d..b3f53af 100644
--- a/lib/chef/knife/data_bag_edit.rb
+++ b/lib/chef/knife/data_bag_edit.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -32,13 +32,15 @@ class Chef
       category "data bag"
 
       option :secret,
-      :short => "-s SECRET",
-      :long  => "--secret ",
-      :description => "The secret key to use to encrypt data bag item values"
+        :short => "-s SECRET",
+        :long  => "--secret ",
+        :description => "The secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
 
       option :secret_file,
-      :long => "--secret-file SECRET_FILE",
-      :description => "A file containing the secret key to use to encrypt data bag item values"
+        :long => "--secret-file SECRET_FILE",
+        :description => "A file containing the secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
 
       def read_secret
         if config[:secret]
@@ -84,7 +86,7 @@ class Chef
         output = edit_item(item)
         rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", output)
         stdout.puts("Saved data_bag_item[#{@name_args[1]}]")
-        output(format_for_display(object.raw_data)) if config[:print_after]
+        ui.output(output) if config[:print_after]
       end
     end
   end
diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb
index 4d26548..4c90fe6 100644
--- a/lib/chef/knife/data_bag_from_file.rb
+++ b/lib/chef/knife/data_bag_from_file.rb
@@ -35,18 +35,20 @@ class Chef
       category "data bag"
 
       option :secret,
-      :short => "-s SECRET",
-      :long  => "--secret ",
-      :description => "The secret key to use to encrypt data bag item values"
+        :short => "-s SECRET",
+        :long  => "--secret ",
+        :description => "The secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
 
       option :secret_file,
-      :long => "--secret-file SECRET_FILE",
-      :description => "A file containing the secret key to use to encrypt data bag item values"
+        :long => "--secret-file SECRET_FILE",
+        :description => "A file containing the secret key to use to encrypt data bag item values",
+        :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
 
       option :all,
-      :short => "-a",
-      :long  => "--all",
-      :description => "Upload all data bags"
+        :short => "-a",
+        :long  => "--all",
+        :description => "Upload all data bags or all items for specified data bags"
 
       def read_secret
         if config[:secret]
diff --git a/lib/chef/knife/data_bag_list.rb b/lib/chef/knife/data_bag_list.rb
index 31dcf98..5e556b6 100644
--- a/lib/chef/knife/data_bag_list.rb
+++ b/lib/chef/knife/data_bag_list.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb
index 81b1425..519859c 100644
--- a/lib/chef/knife/data_bag_show.rb
+++ b/lib/chef/knife/data_bag_show.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -32,13 +32,15 @@ class Chef
       category "data bag"
 
       option :secret,
-      :short => "-s SECRET",
-      :long  => "--secret ",
-      :description => "The secret key to use to decrypt data bag item values"
+        :short => "-s SECRET",
+        :long  => "--secret ",
+        :description => "The secret key to use to decrypt data bag item values",
+        :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
 
       option :secret_file,
-      :long => "--secret-file SECRET_FILE",
-      :description => "A file containing the secret key to use to decrypt data bag item values"
+        :long => "--secret-file SECRET_FILE",
+        :description => "A file containing the secret key to use to decrypt data bag item values",
+        :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
 
       def read_secret
         if config[:secret]
diff --git a/lib/chef/knife/delete.rb b/lib/chef/knife/delete.rb
new file mode 100644
index 0000000..0030c45
--- /dev/null
+++ b/lib/chef/knife/delete.rb
@@ -0,0 +1,108 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Delete < Chef::ChefFS::Knife
+      banner "knife delete [PATTERN1 ... PATTERNn]"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/file_system'
+      end
+
+      option :recurse,
+        :short => '-r',
+        :long => '--[no-]recurse',
+        :boolean => true,
+        :default => false,
+        :description => "Delete directories recursively."
+      option :both,
+        :long => '--both',
+        :boolean => true,
+        :default => false,
+        :description => "Delete both the local and remote copies."
+      option :local,
+        :long => '--local',
+        :boolean => true,
+        :default => false,
+        :description => "Delete the local copy (leave the remote copy)."
+
+      def run
+        if name_args.length == 0
+          show_usage
+          ui.fatal("Must specify at least one argument.  If you want to delete everything in this directory, type \"knife delete --recurse .\"")
+          exit 1
+        end
+
+        # Get the matches (recursively)
+        error = false
+        if config[:local]
+          pattern_args.each do |pattern|
+            Chef::ChefFS::FileSystem.list(local_fs, pattern).each do |result|
+              if delete_result(result)
+                error = true
+              end
+            end
+          end
+        elsif config[:both]
+          pattern_args.each do |pattern|
+            Chef::ChefFS::FileSystem.list_pairs(pattern, chef_fs, local_fs).each do |chef_result, local_result|
+              if delete_result(chef_result, local_result)
+                error = true
+              end
+            end
+          end
+        else # Remote only
+          pattern_args.each do |pattern|
+            Chef::ChefFS::FileSystem.list(chef_fs, pattern).each do |result|
+              if delete_result(result)
+                error = true
+              end
+            end
+          end
+        end
+
+        if error
+          exit 1
+        end
+      end
+
+      def format_path_with_root(entry)
+        root = entry.root == chef_fs ? " (remote)" : " (local)"
+        "#{format_path(entry)}#{root}"
+      end
+
+      def delete_result(*results)
+        deleted_any = false
+        found_any = false
+        error = false
+        results.each do |result|
+          begin
+            result.delete(config[:recurse])
+            deleted_any = true
+            found_any = true
+          rescue Chef::ChefFS::FileSystem::NotFoundError
+            # This is not an error unless *all* of them were not found
+          rescue Chef::ChefFS::FileSystem::MustDeleteRecursivelyError => e
+            ui.error "#{format_path_with_root(e.entry)} must be deleted recursively!  Pass -r to knife delete."
+            found_any = true
+            error = true
+          rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+            ui.error "#{format_path_with_root(e.entry)} #{e.reason}."
+            found_any = true
+            error = true
+          end
+        end
+        if deleted_any
+          output("Deleted #{format_path(results[0])}")
+        elsif !found_any
+          ui.error "#{format_path(results[0])}: No such file or directory"
+          error = true
+        end
+        error
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/deps.rb b/lib/chef/knife/deps.rb
new file mode 100644
index 0000000..b2a39a0
--- /dev/null
+++ b/lib/chef/knife/deps.rb
@@ -0,0 +1,141 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Deps < Chef::ChefFS::Knife
+      banner "knife deps PATTERN1 [PATTERNn]"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/file_system'
+        require 'chef/run_list'
+      end
+
+      option :recurse,
+        :long => '--[no-]recurse',
+        :boolean => true,
+        :description => "List dependencies recursively (default: true).  Only works with --tree."
+      option :tree,
+        :long => '--tree',
+        :boolean => true,
+        :description => "Show dependencies in a visual tree.  May show duplicates."
+      option :remote,
+        :long => '--remote',
+        :boolean => true,
+        :description => "List dependencies on the server instead of the local filesystem"
+
+      attr_accessor :exit_code
+
+      def run
+        if config[:recurse] == false && !config[:tree]
+          ui.error "--no-recurse requires --tree"
+          exit(1)
+        end
+        config[:recurse] = true if config[:recurse].nil?
+
+        @root = config[:remote] ? chef_fs : local_fs
+        dependencies = {}
+        pattern_args.each do |pattern|
+          Chef::ChefFS::FileSystem.list(@root, pattern).each do |entry|
+            if config[:tree]
+              print_dependencies_tree(entry, dependencies)
+            else
+              print_flattened_dependencies(entry, dependencies)
+            end
+          end
+        end
+        exit exit_code if exit_code
+      end
+
+      def print_flattened_dependencies(entry, dependencies)
+        if !dependencies[entry.path]
+          dependencies[entry.path] = get_dependencies(entry)
+          dependencies[entry.path].each do |child|
+            child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
+            print_flattened_dependencies(child_entry, dependencies)
+          end
+          output format_path(entry)
+        end
+      end
+
+      def print_dependencies_tree(entry, dependencies, printed = {}, depth = 0)
+        dependencies[entry.path] = get_dependencies(entry) if !dependencies[entry.path]
+        output "#{'  '*depth}#{format_path(entry)}"
+        if !printed[entry.path] && (config[:recurse] || depth == 0)
+          printed[entry.path] = true
+          dependencies[entry.path].each do |child|
+            child_entry = Chef::ChefFS::FileSystem.resolve_path(@root, child)
+            print_dependencies_tree(child_entry, dependencies, printed, depth+1)
+          end
+        end
+      end
+
+      def get_dependencies(entry)
+        begin
+          if entry.parent && entry.parent.path == '/cookbooks'
+            return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" }
+
+          elsif entry.parent && entry.parent.path == '/nodes'
+            node = JSON.parse(entry.read, :create_additions => false)
+            result = []
+            if node['chef_environment'] && node['chef_environment'] != '_default'
+              result << "/environments/#{node['chef_environment']}.json"
+            end
+            if node['run_list']
+              result += dependencies_from_runlist(node['run_list'])
+            end
+            result
+
+          elsif entry.parent && entry.parent.path == '/roles'
+            role = JSON.parse(entry.read, :create_additions => false)
+            result = []
+            if role['run_list']
+              dependencies_from_runlist(role['run_list']).each do |dependency|
+                result << dependency if !result.include?(dependency)
+              end
+            end
+            if role['env_run_lists']
+              role['env_run_lists'].each_pair do |env,run_list|
+                dependencies_from_runlist(run_list).each do |dependency|
+                  result << dependency if !result.include?(dependency)
+                end
+              end
+            end
+            result
+
+          elsif !entry.exists?
+            raise Chef::ChefFS::FileSystem::NotFoundError.new(entry)
+
+          else
+            []
+          end
+        rescue Chef::ChefFS::FileSystem::NotFoundError => e
+          ui.error "#{format_path(e.entry)}: No such file or directory"
+          self.exit_code = 2
+          []
+        end
+      end
+
+      def dependencies_from_runlist(run_list)
+        chef_run_list = Chef::RunList.new
+        chef_run_list.reset!(run_list)
+        chef_run_list.map do |run_list_item|
+          case run_list_item.type
+          when :role
+            "/roles/#{run_list_item.name}.json"
+          when :recipe
+            if run_list_item.name =~ /(.+)::[^:]*/
+              "/cookbooks/#{$1}"
+            else
+              "/cookbooks/#{run_list_item.name}"
+            end
+          else
+            raise "Unknown run list item type #{run_list_item.type}"
+          end
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/diff.rb b/lib/chef/knife/diff.rb
new file mode 100644
index 0000000..e5eda52
--- /dev/null
+++ b/lib/chef/knife/diff.rb
@@ -0,0 +1,69 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Diff < Chef::ChefFS::Knife
+      banner "knife diff PATTERNS"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/command_line'
+      end
+
+      option :recurse,
+        :long => '--[no-]recurse',
+        :boolean => true,
+        :default => true,
+        :description => "List directories recursively."
+
+      option :name_only,
+        :long => '--name-only',
+        :boolean => true,
+        :description => "Only show names of modified files."
+
+      option :name_status,
+        :long => '--name-status',
+        :boolean => true,
+        :description => "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed."
+
+      option :diff_filter,
+        :long => '--diff-filter=[(A|D|M|T)...[*]]',
+        :description => "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if
+           there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected."
+
+      option :cookbook_version,
+        :long => '--cookbook-version VERSION',
+        :description => 'Version of cookbook to download (if there are multiple versions and cookbook_versions is false)'
+
+      def run
+        if config[:name_only]
+          output_mode = :name_only
+        end
+        if config[:name_status]
+          output_mode = :name_status
+        end
+        patterns = pattern_args_from(name_args.length > 0 ? name_args : [ "" ])
+
+        # Get the matches (recursively)
+        error = false
+        begin
+          patterns.each do |pattern|
+            found_error = Chef::ChefFS::CommandLine.diff_print(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, output_mode, proc { |entry| format_path(entry) }, config[:diff_filter], ui ) do |diff|
+              stdout.print diff
+            end
+            error = true if found_error
+          end
+        rescue Chef::ChefFS::FileSystem::OperationFailedError => e
+          ui.error "Failed on #{format_path(e.entry)} in #{e.operation}: #{e.message}"
+          error = true
+        end
+
+        if error
+          exit 1
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/download.rb b/lib/chef/knife/download.rb
new file mode 100644
index 0000000..5a432af
--- /dev/null
+++ b/lib/chef/knife/download.rb
@@ -0,0 +1,69 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Download < Chef::ChefFS::Knife
+      banner "knife download PATTERNS"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/command_line'
+      end
+
+      option :recurse,
+        :long => '--[no-]recurse',
+        :boolean => true,
+        :default => true,
+        :description => "List directories recursively."
+
+      option :purge,
+        :long => '--[no-]purge',
+        :boolean => true,
+        :default => false,
+        :description => "Delete matching local files and directories that do not exist remotely."
+
+      option :force,
+        :long => '--[no-]force',
+        :boolean => true,
+        :default => false,
+        :description => "Force upload of files even if they match (quicker and harmless, but doesn't print out what it changed)"
+
+      option :dry_run,
+        :long => '--dry-run',
+        :short => '-n',
+        :boolean => true,
+        :default => false,
+        :description => "Don't take action, only print what would happen"
+
+      option :diff,
+        :long => '--[no-]diff',
+        :boolean => true,
+        :default => true,
+        :description => 'Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff'
+
+      option :cookbook_version,
+        :long => '--cookbook-version VERSION',
+        :description => 'Version of cookbook to download (if there are multiple versions and cookbook_versions is false)'
+
+      def run
+        if name_args.length == 0
+          show_usage
+          ui.fatal("Must specify at least one argument.  If you want to download everything in this directory, type \"knife download .\"")
+          exit 1
+        end
+
+        error = false
+        pattern_args.each do |pattern|
+          if Chef::ChefFS::FileSystem.copy_to(pattern, chef_fs, local_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) })
+            error = true
+          end
+        end
+        if error
+          exit 1
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/edit.rb b/lib/chef/knife/edit.rb
new file mode 100644
index 0000000..7408179
--- /dev/null
+++ b/lib/chef/knife/edit.rb
@@ -0,0 +1,73 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Edit < Chef::ChefFS::Knife
+      banner "knife edit [PATTERN1 ... PATTERNn]"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/file_system'
+        require 'chef/chef_fs/file_system/not_found_error'
+      end
+
+      option :local,
+        :long => '--local',
+        :boolean => true,
+        :description => "Show local files instead of remote"
+
+      def run
+        # Get the matches (recursively)
+        error = false
+        pattern_args.each do |pattern|
+          Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
+            if result.dir?
+              ui.error "#{format_path(result)}: is a directory" if pattern.exact_path
+              error = true
+            else
+              begin
+                new_value = edit_text(result.read, File.extname(result.name))
+                if new_value
+                  result.write(new_value)
+                  output "Updated #{format_path(result)}"
+                else
+                  output "#{format_path(result)} unchanged!"
+                end
+              rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+                ui.error "#{format_path(e.entry)}: #{e.reason}."
+                error = true
+              rescue Chef::ChefFS::FileSystem::NotFoundError => e
+                ui.error "#{format_path(e.entry)}: No such file or directory"
+                error = true
+              end
+            end
+          end
+        end
+        if error
+          exit 1
+        end
+      end
+
+      def edit_text(text, extension)
+        if (!config[:disable_editing])
+          Tempfile.open([ 'knife-edit-', extension ]) do |file|
+            # Write the text to a temporary file
+            file.write(text)
+            file.close
+
+            # Let the user edit the temporary file
+            if !system("#{config[:editor]} #{file.path}")
+              raise "Please set EDITOR environment variable"
+            end
+
+            result_text = IO.read(file.path)
+
+            return result_text if result_text != text
+          end
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/environment_from_file.rb b/lib/chef/knife/environment_from_file.rb
index 5a0ce8b..3812024 100644
--- a/lib/chef/knife/environment_from_file.rb
+++ b/lib/chef/knife/environment_from_file.rb
@@ -25,26 +25,59 @@ class Chef
         require 'chef/knife/core/object_loader'
       end
 
-      banner "knife environment from file FILE (options)"
+      banner "knife environment from file FILE [FILE..] (options)"
+
+      option :all,
+      :short => "-a",
+      :long  => "--all",
+      :description => "Upload all environments"
 
       def loader
         @loader ||= Knife::Core::ObjectLoader.new(Chef::Environment, ui)
       end
 
+      def environments_path
+        @environments_path ||= "environments"
+      end
 
-      def run
-        if @name_args[0].nil?
-          show_usage
-          ui.fatal("You must specify a file to load")
-          exit 1
-        end
+      def find_all_environments
+        loader.find_all_objects("./#{environments_path}/")
+      end
 
+      def load_all_environments
+        environments = find_all_environments
+        if environments.empty?
+          ui.fatal("Unable to find any environment files in '#{environments_path}'")
+          exit(1)
+        end
+        environments.each do |env|
+          load_environment(env)
+        end
+      end
 
-        updated = loader.load_from("environments", @name_args[0])
+      def load_environment(env)
+        updated = loader.load_from("environments", env)
         updated.save
         output(format_for_display(updated)) if config[:print_after]
         ui.info("Updated Environment #{updated.name}")
       end
+
+
+      def run
+        if config[:all] == true
+          load_all_environments
+        else
+          if @name_args[0].nil?
+            show_usage
+            ui.fatal("You must specify a file to load")
+            exit 1
+          end
+
+          @name_args.each do |arg|
+            load_environment(arg)
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/chef/knife/environment_show.rb b/lib/chef/knife/environment_show.rb
index 5863732..2dd07fe 100644
--- a/lib/chef/knife/environment_show.rb
+++ b/lib/chef/knife/environment_show.rb
@@ -22,6 +22,8 @@ class Chef
   class Knife
     class EnvironmentShow < Knife
 
+      include Knife::Core::MultiAttributeReturnOption
+
       deps do
         require 'chef/environment'
         require 'chef/json_compat'
diff --git a/lib/chef/knife/exec.rb b/lib/chef/knife/exec.rb
index 4437583..3e8196c 100644
--- a/lib/chef/knife/exec.rb
+++ b/lib/chef/knife/exec.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -27,19 +27,31 @@ class Chef::Knife::Exec < Chef::Knife
     :long => "--exec CODE",
     :description => "a string of Chef code to execute"
 
+  option :script_path,
+    :short => "-p PATH:PATH",
+    :long => "--script-path PATH:PATH",
+    :description => "A colon-separated path to look for scripts in",
+    :proc => lambda { |o| o.split(":") }
+
   deps do
-    require 'chef/shef/ext'
+    require 'chef/shell/ext'
   end
 
   def run
+    config[:script_path] ||= Array(Chef::Config[:script_path])
+
+    # Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts
+    config[:script_path] << File.join(Chef::Knife.chef_config_dir, 'scripts') if Chef::Knife.chef_config_dir
+    config[:script_path] << File.join(ENV['HOME'], '.chef', 'scripts') if ENV['HOME']
+
     scripts = Array(name_args)
     context = Object.new
-    Shef::Extensions.extend_context_object(context)
+    Shell::Extensions.extend_context_object(context)
     if config[:exec]
       context.instance_eval(config[:exec], "-E Argument", 0)
     elsif !scripts.empty?
       scripts.each do |script|
-        file = File.expand_path(script)
+        file = find_script(script)
         context.instance_eval(IO.read(file), file, 0)
       end
     else
@@ -47,5 +59,28 @@ class Chef::Knife::Exec < Chef::Knife
       context.instance_eval(script, "STDIN", 0)
     end
   end
-  
+
+  def find_script(x)
+    # Try to find a script. First try expanding the path given.
+    script = File.expand_path(x)
+    return script if File.exists?(script)
+
+    # Failing that, try searching the script path. If we can't find
+    # anything, fail gracefully.
+    Chef::Log.debug("Searching script_path: #{config[:script_path].inspect}")
+
+    config[:script_path].each do |path|
+      path = File.expand_path(path)
+      test = File.join(path, x)
+      Chef::Log.debug("Testing: #{test}")
+      if File.exists?(test)
+        script = test
+        Chef::Log.debug("Found: #{test}")
+        return script
+      end
+    end
+    ui.error("\"#{x}\" not found in current directory or script_path, giving up.")
+    exit(1)
+  end
+
 end
diff --git a/lib/chef/knife/help_topics.rb b/lib/chef/knife/help_topics.rb
index 1f848b8..a2aad65 100644
--- a/lib/chef/knife/help_topics.rb
+++ b/lib/chef/knife/help_topics.rb
@@ -1,4 +1,4 @@
 # Do not edit this file by hand
 # This file is autogenerated by the docs:list rake task from the available manpages
 
-HELP_TOPICS = ["knife-bootstrap", "knife-client", "knife-configure", "knife-cookbook-site", "knife-cookbook", "knife-data-bag", "knife-environment", "knife-exec", "knife-index", "knife-node", "knife-role", "knife-search", "knife-ssh", "knife-status", "knife-tag", "knife", "shef"]
+HELP_TOPICS = ["chef-shell", "knife-bootstrap", "knife-client", "knife-configure", "knife-cookbook-site", "knife-cookbook", "knife-data-bag", "knife-delete", "knife-deps", "knife-diff", "knife-download", "knife-edit", "knife-environment", "knife-exec", "knife-index-rebuild", "knife-list", "knife-node", "knife-raw", "knife-recipe-list", "knife-role", "knife-search", "knife-show", "knife-ssh", "knife-status", "knife-tag", "knife-upload", "knife-user", "knife-xargs", "knife"]
diff --git a/lib/chef/knife/index_rebuild.rb b/lib/chef/knife/index_rebuild.rb
index b35da77..4b9fcdd 100644
--- a/lib/chef/knife/index_rebuild.rb
+++ b/lib/chef/knife/index_rebuild.rb
@@ -30,20 +30,104 @@ class Chef
         :description  => "don't bother to ask if I'm sure"
 
       def run
-        nag
-        output rest.post_rest("/search/reindex", {})
+        api_info = grab_api_info
+
+        if unsupported_version?(api_info)
+          unsupported_server_message(api_info)
+          exit 1
+        else
+          deprecated_server_message
+          nag
+          output rest.post_rest("/search/reindex", {})
+        end
+
+      end
+
+      def grab_api_info
+        # Since we don't yet have any endpoints that implement an
+        # OPTIONS handler, we need to get our version header
+        # information in a more roundabout way.  We'll try to query
+        # for a node we know won't exist; the 404 response that comes
+        # back will give us what we want
+        dummy_node = "knife_index_rebuild_test_#{rand(1000000)}"
+        rest.get_rest("/nodes/#{dummy_node}")
+      rescue Net::HTTPServerException => exception
+        r = exception.response
+        parse_api_info(r)
+      end
+
+      # Only Chef 11+ servers will have version information in their
+      # headers, and only those servers will lack an API endpoint for
+      # index rebuilding.
+      def unsupported_version?(api_info)
+        !!api_info["version"]
+      end
+
+      def unsupported_server_message(api_info)
+        ui.error("Rebuilding the index is not available via knife for #{server_type(api_info)}s version 11.0.0 and above.")
+        ui.info("Instead, run the '#{ctl_command(api_info)} reindex' command on the server itself.")
+      end
+
+      def deprecated_server_message
+        ui.warn("'knife index rebuild' has been removed for Chef 11+ servers.  It will continue to work for prior versions, however.")
       end
 
       def nag
-        unless config[:yes]
-          yea_or_nay = ask_question("This operation is destructive. Rebuilding the index may take some time. You sure? (yes/no): ")
-          unless yea_or_nay =~ /^y/i
-            puts "aborting"
-            exit 7
+        ui.info("This operation is destructive.  Rebuilding the index may take some time.")
+        ui.confirm("Continue")
+      end
+
+      # Chef 11 (and above) servers return various pieces of
+      # information about the server in an +x-ops-api-info+ header.
+      # This is a +;+ delimited string of key / value pairs, separated
+      # by +=+.
+      #
+      # Given a Net::HTTPResponse object, this method extracts this
+      # information (if present), and returns it as a hash.  If no
+      # such header is found, an empty hash is returned.
+      def parse_api_info(response)
+        value = response["x-ops-api-info"]
+        if value
+          kv = value.split(";")
+          kv.inject({}) do |acc, pair|
+            k, v = pair.split("=")
+            acc[k] = v
+            acc
           end
+        else
+          {}
         end
       end
 
+      # Given an API info hash (see +#parse_api_info(response)+),
+      # return a string describing the kind of server we're
+      # interacting with (based on the +flavor+ field)
+      def server_type(api_info)
+        case api_info["flavor"]
+        when "osc"
+          "Open Source Chef Server"
+        when "opc"
+          "Private Chef Server"
+        else
+          # Generic fallback
+          "Chef Server"
+        end
+      end
+
+      # Given an API info hash (see +#parse_api_info(response)+),
+      # return the name of the "server-ctl" command for the kind of
+      # server we're interacting with (based on the +flavor+ field)
+      def ctl_command(api_info)
+        case api_info["flavor"]
+        when "osc"
+          "chef-server-ctl"
+        when "opc"
+          "private-chef-ctl"
+        else
+          # Generic fallback
+          "chef-server-ctl"
+        end
+      end
 
     end
   end
diff --git a/lib/chef/knife/list.rb b/lib/chef/knife/list.rb
new file mode 100644
index 0000000..4338e19
--- /dev/null
+++ b/lib/chef/knife/list.rb
@@ -0,0 +1,155 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class List < Chef::ChefFS::Knife
+      banner "knife list [-dfR1p] [PATTERN1 ... PATTERNn]"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/file_system'
+        require 'highline'
+      end
+
+      option :recursive,
+        :short => '-R',
+        :boolean => true,
+        :description => "List directories recursively"
+      option :bare_directories,
+        :short => '-d',
+        :boolean => true,
+        :description => "When directories match the pattern, do not show the directories' children"
+      option :local,
+        :long => '--local',
+        :boolean => true,
+        :description => "List local directory instead of remote"
+      option :flat,
+        :short => '-f',
+        :long => '--flat',
+        :boolean => true,
+        :description => "Show a list of filenames rather than the prettified ls-like output normally produced"
+      option :one_column,
+        :short => '-1',
+        :boolean => true,
+        :description => "Show only one column of results"
+      option :trailing_slashes,
+        :short => '-p',
+        :boolean => true,
+        :description => "Show trailing slashes after directories"
+
+      attr_accessor :exit_code
+
+      def run
+        patterns = name_args.length == 0 ? [""] : name_args
+
+        # Get the matches (recursively)
+        all_results = parallelize(pattern_args_from(patterns), :flatten => true) do |pattern|
+          pattern_results = Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern)
+          if pattern_results.first && !pattern_results.first.exists? && pattern.exact_path
+            ui.error "#{format_path(pattern_results.first)}: No such file or directory"
+            self.exit_code = 1
+          end
+          pattern_results
+        end
+
+        # Process directories
+        if !config[:bare_directories]
+          dir_results = parallelize(all_results.select { |result| result.dir? }, :flatten => true) do |result|
+            add_dir_result(result)
+          end.to_a
+        else
+          dir_results = []
+        end
+
+        # Process all other results
+        results = all_results.select { |result| result.exists? && (!result.dir? || config[:bare_directories]) }.to_a
+
+        # Flatten out directory results if necessary
+        if config[:flat]
+          dir_results.each do |result, children|
+            results += children
+          end
+          dir_results = []
+        end
+
+        # Sort by path for happy output
+        results = results.sort_by { |result| result.path }
+        dir_results = dir_results.sort_by { |result| result[0].path }
+
+        # Print!
+        if results.length == 0 && dir_results.length == 1
+          results = dir_results[0][1]
+          dir_results = []
+        end
+
+        print_result_paths results
+        printed_something = results.length > 0
+        dir_results.each do |result, children|
+          if printed_something
+            output ""
+          else
+            printed_something = true
+          end
+          output "#{format_path(result)}:"
+          print_results(children.map { |result| maybe_add_slash(result.name, result.dir?) }.sort, "")
+        end
+
+        exit self.exit_code if self.exit_code
+      end
+
+      def add_dir_result(result)
+        begin
+          children = result.children.sort_by { |child| child.name }
+        rescue Chef::ChefFS::FileSystem::NotFoundError => e
+          ui.error "#{format_path(e.entry)}: No such file or directory"
+          return []
+        end
+
+        result = [ [ result, children ] ]
+        if config[:recursive]
+          child_dirs = children.select { |child| child.dir? }
+          result += parallelize(child_dirs, :flatten => true) { |child| add_dir_result(child) }.to_a
+        end
+        result
+      end
+
+      def print_result_paths(results, indent = "")
+        print_results(results.map { |result| maybe_add_slash(format_path(result), result.dir?) }, indent)
+      end
+
+      def print_results(results, indent)
+        return if results.length == 0
+
+        print_space = results.map { |result| result.length }.max + 2
+        if config[:one_column] || !stdout.isatty
+          columns = 0
+        else
+          columns = HighLine::SystemExtensions.terminal_size[0]
+        end
+        current_line = ''
+        results.each do |result|
+          if current_line.length > 0 && current_line.length + print_space > columns
+            output current_line.rstrip
+            current_line = ''
+          end
+          if current_line.length == 0
+            current_line << indent
+          end
+          current_line << result
+          current_line << (' ' * (print_space - result.length))
+        end
+        output current_line.rstrip if current_line.length > 0
+      end
+
+      def maybe_add_slash(path, is_dir)
+        if config[:trailing_slashes] && is_dir
+          "#{path}/"
+        else
+          path
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/node_run_list_set.rb b/lib/chef/knife/node_run_list_set.rb
new file mode 100644
index 0000000..e327d2a
--- /dev/null
+++ b/lib/chef/knife/node_run_list_set.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Mike Fiedler (<miketheman at gmail.com>)
+# Copyright:: Copyright (c) 2013 Mike Fiedler
+# 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
+    class NodeRunListSet < Knife
+
+      deps do
+        require 'chef/node'
+        require 'chef/json_compat'
+      end
+
+      banner "knife node run_list set NODE ENTRIES (options)"
+
+      def run
+        if @name_args.size < 2
+          ui.fatal "You must supply both a node name and a run list."
+          show_usage
+          exit 1
+        elsif @name_args.size > 2
+          # Check for nested lists and create a single plain one
+          entries = @name_args[1..-1].map do |entry|
+            entry.split(',').map { |e| e.strip }
+          end.flatten
+        else
+          # Convert to array and remove the extra spaces
+          entries = @name_args[1].split(',').map { |e| e.strip }
+        end
+        node = Chef::Node.load(@name_args[0])
+
+        set_run_list(node, entries)
+
+        node.save
+
+        config[:run_list] = true
+
+        output(format_for_display(node))
+      end
+
+      # Clears out any existing run_list_items and sets them to the
+      # specified entries
+      def set_run_list(node, entries)
+        node.run_list.run_list_items.clear
+        entries.each { |e| node.run_list << e }
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/knife/node_show.rb b/lib/chef/knife/node_show.rb
index 4b0be18..dc47da1 100644
--- a/lib/chef/knife/node_show.rb
+++ b/lib/chef/knife/node_show.rb
@@ -24,6 +24,7 @@ class Chef
     class NodeShow < Knife
 
       include Knife::Core::NodeFormattingOptions
+      include Knife::Core::MultiAttributeReturnOption
 
       deps do
         require 'chef/node'
@@ -32,13 +33,6 @@ class Chef
 
       banner "knife node show NODE (options)"
 
-      @attrs_to_show = []
-      option :attribute,
-        :short => "-a [ATTR]",
-        :long => "--attribute [ATTR]",
-        :proc => lambda {|val| @attrs_to_show << val},
-        :description => "Show one or more attributes"
-
       option :run_list,
         :short => "-r",
         :long => "--run-list",
diff --git a/lib/chef/knife/raw.rb b/lib/chef/knife/raw.rb
new file mode 100644
index 0000000..2756de1
--- /dev/null
+++ b/lib/chef/knife/raw.rb
@@ -0,0 +1,90 @@
+require 'chef/knife'
+
+class Chef
+  class Knife
+    class Raw < Chef::Knife
+      banner "knife raw REQUEST_PATH"
+
+      deps do
+        require 'chef/json_compat'
+        require 'chef/config'
+        require 'chef/http'
+        require 'chef/http/authenticator'
+        require 'chef/http/cookie_manager'
+        require 'chef/http/decompressor'
+        require 'chef/http/json_output'
+      end
+
+      option :method,
+        :long => '--method METHOD',
+        :short => '-m METHOD',
+        :default => "GET",
+        :description => "Request method (GET, POST, PUT or DELETE).  Default: GET"
+
+      option :pretty,
+        :long => '--[no-]pretty',
+        :boolean => true,
+        :default => true,
+        :description => "Pretty-print JSON output.  Default: true"
+
+      option :input,
+        :long => '--input FILE',
+        :short => '-i FILE',
+        :description => "Name of file to use for PUT or POST"
+
+      class RawInputServerAPI < Chef::HTTP
+        def initialize(options = {})
+          options[:client_name] ||= Chef::Config[:node_name]
+          options[:signing_key_filename] ||= Chef::Config[:client_key]
+          super(Chef::Config[:chef_server_url], options)
+        end
+        use Chef::HTTP::JSONOutput
+        use Chef::HTTP::CookieManager
+        use Chef::HTTP::Decompressor
+        use Chef::HTTP::Authenticator
+      end
+
+      def run
+        if name_args.length == 0
+          show_usage
+          ui.fatal("You must provide the path you want to hit on the server")
+          exit(1)
+        elsif name_args.length > 1
+          show_usage
+          ui.fatal("Only one path accepted for knife raw")
+          exit(1)
+        end
+
+        path = name_args[0]
+        data = false
+        if config[:input]
+          data = IO.read(config[:input])
+        end
+        begin
+          method = config[:method].to_sym
+
+          if config[:pretty]
+            chef_rest = RawInputServerAPI.new
+            result = chef_rest.request(method, name_args[0], {'Content-Type' => 'application/json'}, data)
+            unless result.is_a?(String)
+              result = Chef::JSONCompat.to_json_pretty(result)
+            end
+          else
+            chef_rest = RawInputServerAPI.new(:raw_output => true)
+            result = chef_rest.request(method, name_args[0], {'Content-Type' => 'application/json'}, data)
+          end
+          output result
+        rescue Timeout::Error => e
+          ui.error "Server timeout"
+          exit 1
+        rescue Net::HTTPServerException => e
+          ui.error "Server responded with error #{e.response.code} \"#{e.response.message}\""
+          ui.error "Error Body: #{e.response.body}" if e.response.body && e.response.body != ''
+          exit 1
+        end
+      end
+
+    end # class Raw
+  end
+end
+
diff --git a/lib/chef/knife/role_show.rb b/lib/chef/knife/role_show.rb
index 2f09794..159571f 100644
--- a/lib/chef/knife/role_show.rb
+++ b/lib/chef/knife/role_show.rb
@@ -22,6 +22,8 @@ class Chef
   class Knife
     class RoleShow < Knife
 
+      include Knife::Core::MultiAttributeReturnOption
+
       deps do
         require 'chef/node'
         require 'chef/json_compat'
@@ -29,10 +31,6 @@ class Chef
 
       banner "knife role show ROLE (options)"
 
-      option :attribute,
-        :short => "-a ATTR",
-        :long => "--attribute ATTR",
-        :description => "Show only one attribute"
 
       def run
         @role_name = @name_args[0]
diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb
index eecb89a..bc020c0 100644
--- a/lib/chef/knife/search.rb
+++ b/lib/chef/knife/search.rb
@@ -23,6 +23,8 @@ class Chef
   class Knife
     class Search < Knife
 
+      include Knife::Core::MultiAttributeReturnOption
+
       deps do
         require 'chef/node'
         require 'chef/environment'
@@ -54,11 +56,6 @@ class Chef
         :default => 1000,
         :proc => lambda { |i| i.to_i }
 
-      option :attribute,
-        :short => "-a ATTR",
-        :long => "--attribute ATTR",
-        :description => "Show only one attribute"
-
       option :run_list,
         :short => "-r",
         :long => "--run-list",
@@ -75,30 +72,16 @@ class Chef
         :description => "The search query; useful to protect queries starting with -"
 
       def run
-        if config[:query] && @name_args[1]
-          ui.error "please specify query as an argument or an option via -q, not both"
-          ui.msg opt_parser
-          exit 1
-        end
-        raw_query = config[:query] || @name_args[1]
-        if !raw_query || raw_query.empty?
-          ui.error "no query specified"
-          ui.msg opt_parser
-          exit 1
-        end
+        read_cli_args
+        fuzzify_query
 
-        if name_args[0].nil?
-          ui.error "you must specify an item type to search for"
-          exit 1
-        end
-
-        if name_args[0] == 'node'
+        if @type == 'node'
           ui.use_presenter Knife::Core::NodePresenter
         end
 
 
         q = Chef::Search::Query.new
-        query = URI.escape(raw_query,
+        escaped_query = URI.escape(@query,
                            Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
 
         result_items = []
@@ -107,11 +90,11 @@ class Chef
         rows = config[:rows]
         start = config[:start]
         begin
-          q.search(@name_args[0], query, config[:sort], start, rows) do |item|
+          q.search(@type, escaped_query, config[:sort], start, rows) do |item|
             formatted_item = format_for_display(item)
-            if formatted_item.respond_to?(:has_key?) && !formatted_item.has_key?('id')
-              formatted_item['id'] = item.has_key?('id') ? item['id'] : item.name
-            end
+            # if formatted_item.respond_to?(:has_key?) && !formatted_item.has_key?('id')
+            #   formatted_item['id'] = item.has_key?('id') ? item['id'] : item.name
+            # end
             result_items << formatted_item
             result_count += 1
           end
@@ -128,10 +111,44 @@ class Chef
           ui.msg("\n")
           result_items.each do |item|
             output(item)
-            ui.msg("\n")
+            unless config[:id_only]
+              ui.msg("\n")
+            end
           end
         end
       end
+
+      def read_cli_args
+        if config[:query]
+          if @name_args[1]
+            ui.error "please specify query as an argument or an option via -q, not both"
+            ui.msg opt_parser
+            exit 1
+          end
+          @type = name_args[0]
+          @query = config[:query]
+        else
+          case name_args.size
+          when 0
+            ui.error "no query specified"
+            ui.msg opt_parser
+            exit 1
+          when 1
+            @type = "node"
+            @query = name_args[0]
+          when 2
+            @type = name_args[0]
+            @query = name_args[1]
+          end
+        end
+      end
+
+      def fuzzify_query
+        if @query !~ /:/
+          @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*"
+        end
+      end
+
     end
   end
 end
diff --git a/lib/chef/knife/show.rb b/lib/chef/knife/show.rb
new file mode 100644
index 0000000..acf1996
--- /dev/null
+++ b/lib/chef/knife/show.rb
@@ -0,0 +1,57 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Show < Chef::ChefFS::Knife
+      banner "knife show [PATTERN1 ... PATTERNn]"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/file_system'
+        require 'chef/chef_fs/file_system/not_found_error'
+      end
+
+      option :local,
+        :long => '--local',
+        :boolean => true,
+        :description => "Show local files instead of remote"
+
+      def run
+        # Get the matches (recursively)
+        error = false
+        entry_values = parallelize(pattern_args, :flatten => true) do |pattern|
+          parallelize(Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern)) do |entry|
+            if entry.dir?
+              ui.error "#{format_path(entry)}: is a directory" if pattern.exact_path
+              error = true
+              nil
+            else
+              begin
+                [entry, entry.read]
+              rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+                ui.error "#{format_path(e.entry)}: #{e.reason}."
+                error = true
+                nil
+              rescue Chef::ChefFS::FileSystem::NotFoundError => e
+                ui.error "#{format_path(e.entry)}: No such file or directory"
+                error = true
+                nil
+              end
+            end
+          end
+        end
+        entry_values.each do |entry, value|
+          if entry
+            output "#{format_path(entry)}:"
+            output(format_for_display(value))
+          end
+        end
+        if error
+          exit 1
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
index d440f9d..e1090fc 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -25,9 +25,12 @@ class Chef
       deps do
         require 'net/ssh'
         require 'net/ssh/multi'
+        require 'chef/monkey_patches/net-ssh-multi'
         require 'readline'
+        require 'chef/exceptions'
         require 'chef/search/query'
         require 'chef/mixin/shell_out'
+        require 'mixlib/shellout'
       end
 
       include Chef::Mixin::ShellOut
@@ -46,8 +49,8 @@ class Chef
       option :attribute,
         :short => "-a ATTR",
         :long => "--attribute ATTR",
-        :description => "The attribute to use for opening the connection - default is fqdn",
-        :proc => Proc.new { |key| Chef::Config[:knife][:ssh_attribute] = key }
+        :description => "The attribute to use for opening the connection - default depends on the context",
+        :proc => Proc.new { |key| Chef::Config[:knife][:ssh_attribute] = key.strip }
 
       option :manual,
         :short => "-m",
@@ -70,14 +73,19 @@ class Chef
         :short => "-p PORT",
         :long => "--ssh-port PORT",
         :description => "The ssh port",
-        :default => "22",
-        :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
+        :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key.strip }
 
       option :ssh_gateway,
         :short => "-G GATEWAY",
         :long => "--ssh-gateway GATEWAY",
         :description => "The ssh gateway",
-        :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gatewa] = key }
+        :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key.strip }
+
+      option :forward_agent,
+        :short => "-A",
+        :long => "--forward-agent",
+        :description => "Enable SSH agent forwarding",
+        :boolean => true
 
       option :identity_file,
         :short => "-i IDENTITY_FILE",
@@ -113,6 +121,22 @@ class Chef
         @session ||= Net::SSH::Multi.start(:concurrent_connections => config[:concurrency], :on_error => ssh_error_handler)
       end
 
+      def configure_gateway
+        config[:ssh_gateway] ||= Chef::Config[:knife][:ssh_gateway]
+        if config[:ssh_gateway]
+          gw_host, gw_user = config[:ssh_gateway].split('@').reverse
+          gw_host, gw_port = gw_host.split(':')
+          gw_opts = gw_port ? { :port => gw_port } : {}
+
+          session.via(gw_host, gw_user || config[:ssh_user], gw_opts)
+        end
+      rescue Net::SSH::AuthenticationFailed
+        user = gw_user || config[:ssh_user]
+        prompt = "Enter the password for #{user}@#{gw_host}: "
+        gw_opts.merge!(:password => prompt_for_password(prompt))
+        session.via(gw_host, user, gw_opts)
+      end
+
       def configure_session
         list = case config[:manual]
                when true
@@ -122,34 +146,51 @@ class Chef
                  q = Chef::Search::Query.new
                  @action_nodes = q.search(:node, @name_args[0])[0]
                  @action_nodes.each do |item|
-                   i = format_for_display(item)[config[:attribute]]
-                   r.push(i) unless i.nil?
+                   # 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[:override_attribute]
+                   if !config[:override_attribute] && item[:cloud] and item[:cloud][:public_hostname]
+                     i = item[:cloud][:public_hostname]
+                   elsif config[:override_attribute]
+                     i = extract_nested_value(item, config[:override_attribute])
+                   else
+                     i = extract_nested_value(item, config[:attribute])
+                   end
+                   # next if we couldn't find the specified attribute in the returned node object
+                   next if i.nil?
+                   r.push(i)
                  end
                  r
                end
-        (ui.fatal("No nodes returned from search!"); exit 10) if list.length == 0
+        if list.length == 0
+          if @action_nodes.length == 0
+            ui.fatal("No nodes returned from search!")
+          else
+            ui.fatal("#{@action_nodes.length} #{@action_nodes.length > 1 ? "nodes":"node"} found, " +
+                     "but does not have the required attribute to establish the connection. " +
+                     "Try setting another attribute to open the connection using --attribute.")
+          end
+          exit 10
+        end
         session_from_list(list)
       end
 
       def session_from_list(list)
-        config[:ssh_gateway] ||= Chef::Config[:knife][:ssh_gateway]
-        if config[:ssh_gateway]
-          gw_host, gw_user = config[:ssh_gateway].split('@').reverse
-          gw_host, gw_port = gw_host.split(':')
-          gw_opts = gw_port ? { :port => gw_port } : {}
-
-          session.via(gw_host, gw_user || config[:ssh_user], gw_opts)
-        end
-
         list.each do |item|
           Chef::Log.debug("Adding #{item}")
-
-          hostspec = config[:ssh_user] ? "#{config[:ssh_user]}@#{item}" : item
           session_opts = {}
+
+          ssh_config = Net::SSH.configuration_for(item)
+
+          # Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user]
+          user = config[:ssh_user] || ssh_config[:user]
+          hostspec = user ? "#{user}@#{item}" : item
           session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
           session_opts[:keys_only] = true if config[:identity_file]
           session_opts[:password] = config[:ssh_password] if config[:ssh_password]
-          session_opts[:port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
+          session_opts[:forward_agent] = config[:forward_agent]
+          session_opts[:port] = config[:ssh_port] || Chef::Config[:knife][:ssh_port] || ssh_config[:port]
           session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
 
           if !config[:host_key_verify]
@@ -170,18 +211,33 @@ class Chef
       end
 
       def print_data(host, data)
-        if data =~ /\n/
-          data.split(/\n/).each { |d| print_data(host, d) }
+        @buffers ||= {}
+        if leftover = @buffers[host]
+          @buffers[host] = nil
+          print_data(host, leftover + data)
         else
-          padding = @longest - host.length
-          str = ui.color(host, :cyan) + (" " * (padding + 1)) + data
-          ui.msg(str)
+          if newline_index = data.index("\n")
+            line = data.slice!(0...newline_index)
+            data.slice!(0)
+            print_line(host, line)
+            print_data(host, data)
+          else
+            @buffers[host] = data
+          end
         end
       end
 
+      def print_line(host, data)
+        padding = @longest - host.length
+        str = ui.color(host, :cyan) + (" " * (padding + 1)) + data
+        ui.msg(str)
+      end
+
       def ssh_command(command, subsession=nil)
+        exit_status = 0
         subsession ||= session
         command = fixup_sudo(command)
+        command.force_encoding('binary') if command.respond_to?(:force_encoding)
         subsession.open_channel do |ch|
           ch.request_pty
           ch.exec command do |ch, success|
@@ -189,16 +245,25 @@ class Chef
             ch.on_data do |ichannel, data|
               print_data(ichannel[:host], data)
               if data =~ /^knife sudo password: /
+                print_data(ichannel[:host], "\n")
                 ichannel.send_data("#{get_password}\n")
               end
             end
+            ch.on_request "exit-status" do |ichannel, data|
+              exit_status = [exit_status, data.read_long].max
+            end
           end
         end
         session.loop
+        exit_status
       end
 
       def get_password
-        @password ||= ui.ask("Enter your password: ") { |q| q.echo = false }
+        @password ||= prompt_for_password
+      end
+
+      def prompt_for_password(prompt = "Enter your password: ")
+        ui.ask(prompt) { |q| q.echo = false }
       end
 
       # Present the prompt and read a single line from the console. It also
@@ -327,17 +392,34 @@ class Chef
       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[:override_attribute] = config[:attribute] || Chef::Config[:knife][:ssh_attribute]
         config[:attribute] = (Chef::Config[:knife][:ssh_attribute] ||
                               config[:attribute] ||
                               "fqdn").strip
       end
 
-      def csshx
-        csshx_cmd = "csshX"
+      def cssh
+        cssh_cmd = nil
+        %w[csshX cssh].each do |cmd|
+          begin
+            # Unix and Mac only
+            cssh_cmd = shell_out!("which #{cmd}").stdout.strip
+            break
+          rescue Mixlib::ShellOut::ShellCommandFailed
+          end
+        end
+        raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd
+
         session.servers_for.each do |server|
-          csshx_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}"
+          cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}"
         end
-        exec(csshx_cmd)
+        Chef::Log.debug("starting cssh session with command: #{cssh_cmd}")
+        exec(cssh_cmd)
       end
 
       def get_stripped_unfrozen_value(value)
@@ -351,10 +433,14 @@ class Chef
       end
 
       def configure_identity_file
-        config[:identity_file] = get_stripped_unfrozen_value(config[:identity_file] || 
+        config[:identity_file] = get_stripped_unfrozen_value(config[:identity_file] ||
                              Chef::Config[:knife][:ssh_identity_file])
       end
 
+      def extract_nested_value(data_structure, path_spec)
+        ui.presenter.extract_nested_value(data_structure, path_spec)
+      end
+
       def run
         extend Chef::Mixin::Command
 
@@ -363,8 +449,10 @@ class Chef
         configure_attribute
         configure_user
         configure_identity_file
+        configure_gateway
         configure_session
 
+        exit_status =
         case @name_args[1]
         when "interactive"
           interactive
@@ -374,16 +462,24 @@ class Chef
           tmux
         when "macterm"
           macterm
+        when "cssh"
+          cssh
         when "csshx"
-          csshx
+          Chef::Log.warn("knife ssh csshx will be deprecated in a future release")
+          Chef::Log.warn("please use knife ssh cssh instead")
+          cssh
         else
           ssh_command(@name_args[1..-1].join(" "))
         end
 
         session.close
+        if exit_status != 0
+          exit exit_status
+        else
+          exit_status
+        end
       end
 
     end
   end
 end
-
diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb
index 428adf4..5906a4a 100644
--- a/lib/chef/knife/status.rb
+++ b/lib/chef/knife/status.rb
@@ -34,6 +34,16 @@ class Chef
         :long => "--run-list",
         :description => "Show the run list"
 
+      option :sort_reverse,
+        :short => "-s",
+        :long => "--sort-reverse",
+        :description => "Sort the status list by last run time descending"
+
+      option :hide_healthy,
+        :short => "-H",
+        :long => "--hide-healthy",
+        :description => "Hide nodes that have run chef in the last hour"
+
       def highline
         @h ||= HighLine.new
       end
@@ -45,7 +55,13 @@ class Chef
         q.search(:node, query) do |node|
           all_nodes << node
         end
-        all_nodes.sort { |n1, n2| (n1["ohai_time"] or 0) <=> (n2["ohai_time"] or 0) }.each do |node|
+        all_nodes.sort { |n1, n2|
+          if (config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse])
+            (n2["ohai_time"] or 0) <=> (n1["ohai_time"] or 0)
+          else
+            (n1["ohai_time"] or 0) <=> (n2["ohai_time"] or 0)
+          end
+        }.each do |node|
           if node.has_key?("ec2")
             fqdn = node['ec2']['public_hostname']
             ipaddress = node['ec2']['public_ipv4']
@@ -56,20 +72,20 @@ class Chef
           hours, minutes, seconds = time_difference_in_hms(node["ohai_time"])
           hours_text   = "#{hours} hour#{hours == 1 ? ' ' : 's'}"
           minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}"
-          run_list = ", #{node.run_list}." if config[:run_list]
+          run_list = "#{node.run_list}" if config[:run_list]
           if hours > 24
-            color = "RED"
+            color = :red
             text = hours_text
           elsif hours >= 1
-            color = "YELLOW"
+            color = :yellow
             text = hours_text
           else
-            color = "GREEN"
+            color = :green
             text = minutes_text
           end
 
           line_parts = Array.new
-          line_parts << "<%= color('#{text}', #{color}) %> ago" << node.name
+          line_parts << @ui.color(text, color) + " ago" << node.name
           line_parts << fqdn if fqdn
           line_parts << ipaddress if ipaddress
           line_parts << run_list if run_list
@@ -81,8 +97,7 @@ class Chef
             end
             line_parts << platform
           end
-
-          highline.say(line_parts.join(', ') + '.')
+          highline.say(line_parts.join(', ') + '.') unless (config[:hide_healthy] && hours < 1)
         end
 
       end
diff --git a/lib/chef/knife/tag_delete.rb b/lib/chef/knife/tag_delete.rb
index 82270bd..10751db 100644
--- a/lib/chef/knife/tag_delete.rb
+++ b/lib/chef/knife/tag_delete.rb
@@ -51,7 +51,7 @@ class Chef
         message = if deleted_tags.empty?
                     "Nothing has changed. The tags requested to be deleted do not exist."
                   else
-                    "Deleted the following tags: #{deleted_tags.join(", ")}."
+                    "Deleted tags #{deleted_tags.join(", ")} for node #{name}."
                   end
         ui.info(message)
       end
diff --git a/lib/chef/knife/upload.rb b/lib/chef/knife/upload.rb
new file mode 100644
index 0000000..8abd22b
--- /dev/null
+++ b/lib/chef/knife/upload.rb
@@ -0,0 +1,71 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Upload < Chef::ChefFS::Knife
+      banner "knife upload PATTERNS"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/command_line'
+      end
+
+      option :recurse,
+        :long => '--[no-]recurse',
+        :boolean => true,
+        :default => true,
+        :description => "List directories recursively."
+
+      option :purge,
+        :long => '--[no-]purge',
+        :boolean => true,
+        :default => false,
+        :description => "Delete matching local files and directories that do not exist remotely."
+
+      option :force,
+        :long => '--[no-]force',
+        :boolean => true,
+        :default => false,
+        :description => "Force upload of files even if they match (quicker for many files).  Will overwrite frozen cookbooks."
+
+      option :freeze,
+        :long => '--[no-]freeze',
+        :boolean => true,
+        :default => false,
+        :description => "Freeze cookbooks that get uploaded."
+
+      option :dry_run,
+        :long => '--dry-run',
+        :short => '-n',
+        :boolean => true,
+        :default => false,
+        :description => "Don't take action, only print what would happen"
+
+      option :diff,
+        :long => '--[no-]diff',
+        :boolean => true,
+        :default => true,
+        :description => 'Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff'
+
+      def run
+        if name_args.length == 0
+          show_usage
+          ui.fatal("Must specify at least one argument.  If you want to upload everything in this directory, type \"knife upload .\"")
+          exit 1
+        end
+
+        error = false
+        pattern_args.each do |pattern|
+          if Chef::ChefFS::FileSystem.copy_to(pattern, local_fs, chef_fs, config[:recurse] ? nil : 1, config, ui, proc { |entry| format_path(entry) })
+            error = true
+          end
+        end
+        if error
+          exit 1
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
new file mode 100644
index 0000000..fa889f2
--- /dev/null
+++ b/lib/chef/knife/user_create.rb
@@ -0,0 +1,93 @@
+#
+# 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 'chef/knife'
+
+class Chef
+  class Knife
+    class UserCreate < Knife
+
+      deps do
+        require 'chef/user'
+        require 'chef/json_compat'
+      end
+
+      option :file,
+        :short => "-f FILE",
+        :long  => "--file FILE",
+        :description => "Write the private key to a file"
+
+      option :admin,
+        :short => "-a",
+        :long  => "--admin",
+        :description => "Create the user as an admin",
+        :boolean => true
+
+      option :user_password,
+        :short => "-p PASSWORD",
+        :long => "--password PASSWORD",
+        :description => "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 USER (options)"
+
+      def run
+        @user_name = @name_args[0]
+
+        if @user_name.nil?
+          show_usage
+          ui.fatal("You must specify a user name")
+          exit 1
+        end
+
+        if config[:user_password].length == 0
+          show_usage
+          ui.fatal("You must specify a non-blank password")
+          exit 1
+        end
+
+        user = Chef::User.new
+        user.name(@user_name)
+        user.admin(config[:admin])
+        user.password config[:user_password]
+
+        if config[:user_key]
+          user.public_key File.read(File.expand_path(config[:user_key]))
+        end
+
+        output = edit_data(user)
+        user = Chef::User.from_hash(output).create
+
+        ui.info("Created #{user}")
+        if user.private_key
+          if config[:file]
+            File.open(config[:file], "w") do |f|
+              f.print(user.private_key)
+            end
+          else
+            puts user.private_key
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
new file mode 100644
index 0000000..b7af11b
--- /dev/null
+++ b/lib/chef/knife/user_delete.rb
@@ -0,0 +1,46 @@
+#
+# 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 'chef/knife'
+
+class Chef
+  class Knife
+    class UserDelete < Knife
+
+      deps do
+        require 'chef/user'
+        require 'chef/json_compat'
+      end
+
+      banner "knife user delete USER (options)"
+
+      def run
+        @user_name = @name_args[0]
+
+        if @user_name.nil?
+          show_usage
+          ui.fatal("You must specify a user name")
+          exit 1
+        end
+
+        delete_object(Chef::User, @user_name)
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
new file mode 100644
index 0000000..ae319c8
--- /dev/null
+++ b/lib/chef/knife/user_edit.rb
@@ -0,0 +1,53 @@
+#
+# 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 'chef/knife'
+
+class Chef
+  class Knife
+    class UserEdit < Knife
+
+      deps do
+        require 'chef/user'
+        require 'chef/json_compat'
+      end
+
+      banner "knife user edit USER (options)"
+
+      def run
+        @user_name = @name_args[0]
+
+        if @user_name.nil?
+          show_usage
+          ui.fatal("You must specify a user name")
+          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.")
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
new file mode 100644
index 0000000..5d2e735
--- /dev/null
+++ b/lib/chef/knife/user_list.rb
@@ -0,0 +1,42 @@
+#
+# 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 'chef/knife'
+
+class Chef
+  class Knife
+    class UserList < Knife
+
+      deps do
+        require 'chef/user'
+        require 'chef/json_compat'
+      end
+
+      banner "knife user list (options)"
+
+      option :with_uri,
+        :short => "-w",
+        :long => "--with-uri",
+        :description => "Show corresponding URIs"
+
+      def run
+        output(format_list_for_display(Chef::User.list))
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
new file mode 100644
index 0000000..946150e
--- /dev/null
+++ b/lib/chef/knife/user_reregister.rb
@@ -0,0 +1,59 @@
+#
+# 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 'chef/knife'
+
+class Chef
+  class Knife
+    class UserReregister < Knife
+
+      deps do
+        require 'chef/user'
+        require 'chef/json_compat'
+      end
+
+      banner "knife user reregister USER (options)"
+
+      option :file,
+        :short => "-f FILE",
+        :long  => "--file FILE",
+        :description => "Write the private key to a file"
+
+      def run
+        @user_name = @name_args[0]
+
+        if @user_name.nil?
+          show_usage
+          ui.fatal("You must specify a user name")
+          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)
+          end
+        else
+          ui.msg key
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
new file mode 100644
index 0000000..61ea101
--- /dev/null
+++ b/lib/chef/knife/user_show.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Steven Danna (<steve at opscode.com>)
+# Copyright:: Copyright (c) 2009 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/knife'
+
+class Chef
+  class Knife
+    class UserShow < Knife
+
+      include Knife::Core::MultiAttributeReturnOption
+
+      deps do
+        require 'chef/user'
+        require 'chef/json_compat'
+      end
+
+      banner "knife user show USER (options)"
+
+      def run
+        @user_name = @name_args[0]
+
+        if @user_name.nil?
+          show_usage
+          ui.fatal("You must specify a user name")
+          exit 1
+        end
+
+        user = Chef::User.load(@user_name)
+        output(format_for_display(user))
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/knife/xargs.rb b/lib/chef/knife/xargs.rb
new file mode 100644
index 0000000..dd8e848
--- /dev/null
+++ b/lib/chef/knife/xargs.rb
@@ -0,0 +1,267 @@
+require 'chef/chef_fs/knife'
+
+class Chef
+  class Knife
+    class Xargs < Chef::ChefFS::Knife
+      banner "knife xargs [COMMAND]"
+
+      category "path-based"
+
+      deps do
+        require 'chef/chef_fs/file_system'
+        require 'chef/chef_fs/file_system/not_found_error'
+      end
+
+      # TODO modify to remote-only / local-only pattern (more like delete)
+      option :local,
+        :long => '--local',
+        :boolean => true,
+        :description => "Xargs local files instead of remote"
+
+      option :patterns,
+        :long => '--pattern [PATTERN]',
+        :short => '-p [PATTERN]',
+        :description => "Pattern on command line (if these are not specified, a list of patterns is expected on standard input).  Multiple patterns may be passed in this way.",
+        :arg_arity => [1,-1]
+
+      option :diff,
+        :long => '--[no-]diff',
+        :default => true,
+        :boolean => true,
+        :description => "Whether to show a diff when files change (default: true)"
+
+      option :dry_run,
+        :long => '--dry-run',
+        :boolean => true,
+        :description => "Prevents changes from actually being uploaded to the server."
+
+      option :force,
+        :long => '--[no-]force',
+        :boolean => true,
+        :default => false,
+        :description => "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)"
+
+      option :replace_first,
+        :long => '--replace-first REPLACESTR',
+        :short => '-J REPLACESTR',
+        :description => "String to replace with filenames.  -J will only replace the FIRST occurrence of the replacement string."
+
+      option :replace_all,
+        :long => '--replace REPLACESTR',
+        :short => '-I REPLACESTR',
+        :description => "String to replace with filenames.  -I will replace ALL occurrence of the replacement string."
+
+      option :max_arguments_per_command,
+        :long => '--max-args MAXARGS',
+        :short => '-n MAXARGS',
+        :description => "Maximum number of arguments per command line."
+
+      option :max_command_line,
+        :long => '--max-chars LENGTH',
+        :short => '-s LENGTH',
+        :description => "Maximum size of command line, in characters"
+
+      option :verbose_commands,
+        :short => '-t',
+        :description => "Print command to be run on the command line"
+
+      option :null_separator,
+        :short => '-0',
+        :boolean => true,
+        :description => "Use the NULL character (\0) as a separator, instead of whitespace"
+
+      def run
+        error = false
+        # Get the matches (recursively)
+        files = []
+        pattern_args_from(get_patterns).each do |pattern|
+          Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).each do |result|
+            if result.dir?
+              # TODO option to include directories
+              ui.warn "#{format_path(result)}: is a directory.  Will not run #{command} on it."
+            else
+              files << result
+              ran = false
+
+              # If the command would be bigger than max command line, back it off a bit
+              # and run a slightly smaller command (with one less arg)
+              if config[:max_command_line]
+                command, tempfiles = create_command(files)
+                begin
+                  if command.length > config[:max_command_line].to_i
+                    if files.length > 1
+                      command, tempfiles_minus_one = create_command(files[0..-2])
+                      begin
+                        error = true if xargs_files(command, tempfiles_minus_one)
+                        files = [ files[-1] ]
+                        ran = true
+                      ensure
+                        destroy_tempfiles(tempfiles)
+                      end
+                    else
+                      error = true if xargs_files(command, tempfiles)
+                      files = [ ]
+                      ran = true
+                    end
+                  end
+                ensure
+                  destroy_tempfiles(tempfiles)
+                end
+              end
+
+              # If the command has hit the limit for the # of arguments, run it
+              if !ran && config[:max_arguments_per_command] && files.size >= config[:max_arguments_per_command].to_i
+                command, tempfiles = create_command(files)
+                begin
+                  error = true if xargs_files(command, tempfiles)
+                  files = []
+                  ran = true
+                ensure
+                  destroy_tempfiles(tempfiles)
+                end
+              end
+            end
+          end
+        end
+
+        # Any leftovers commands shall be run
+        if files.size > 0
+          command, tempfiles = create_command(files)
+          begin
+            error = true if xargs_files(command, tempfiles)
+          ensure
+            destroy_tempfiles(tempfiles)
+          end
+        end
+
+        if error
+          exit 1
+        end
+      end
+
+      def get_patterns
+        if config[:patterns]
+          [ config[:patterns] ].flatten
+        elsif config[:null_separator]
+          stdin.binmode
+          stdin.read.split("\000")
+        else
+          stdin.read.split(/\s+/)
+        end
+      end
+
+      def create_command(files)
+        command = name_args.join(' ')
+
+        # Create the (empty) tempfiles
+        tempfiles = {}
+        begin
+          # Create the temporary files
+          files.each do |file|
+            tempfile = Tempfile.new(file.name)
+            tempfiles[tempfile] = { :file => file }
+          end
+        rescue
+          destroy_tempfiles(files)
+          raise
+        end
+
+        # Create the command
+        paths = tempfiles.keys.map { |tempfile| tempfile.path }.join(' ')
+        if config[:replace_all]
+          final_command = command.gsub(config[:replace_all], paths)
+        elsif config[:replace_first]
+          final_command = command.sub(config[:replace_first], paths)
+        else
+          final_command = "#{command} #{paths}"
+        end
+
+        [final_command, tempfiles]
+      end
+
+      def destroy_tempfiles(tempfiles)
+        # Unlink the files now that we're done with them
+        tempfiles.keys.each { |tempfile| tempfile.close! }
+      end
+
+      def xargs_files(command, tempfiles)
+        error = false
+        # Create the temporary files
+        tempfiles.each_pair do |tempfile, file|
+          begin
+            value = file[:file].read
+            file[:value] = value
+            tempfile.open
+            tempfile.write(value)
+            tempfile.close
+          rescue Chef::ChefFS::FileSystem::OperationNotAllowedError => e
+            ui.error "#{format_path(e.entry)}: #{e.reason}."
+            error = true
+            tempfile.close!
+            tempfiles.delete(tempfile)
+            next
+          rescue Chef::ChefFS::FileSystem::NotFoundError => e
+            ui.error "#{format_path(e.entry)}: No such file or directory"
+            error = true
+            tempfile.close!
+            tempfiles.delete(tempfile)
+            next
+          end
+        end
+
+        return error if error && tempfiles.size == 0
+
+        # Run the command
+        if config[:verbose_commands] || Chef::Config[:verbosity] && Chef::Config[:verbosity] >= 1
+          output sub_filenames(command, tempfiles)
+        end
+        command_output = `#{command}`
+        command_output = sub_filenames(command_output, tempfiles)
+        stdout.write command_output
+
+        # Check if the output is different
+        tempfiles.each_pair do |tempfile, file|
+          # Read the new output
+          new_value = IO.binread(tempfile.path)
+
+          # Upload the output if different
+          if config[:force] || new_value != file[:value]
+            if config[:dry_run]
+              output "Would update #{format_path(file[:file])}"
+            else
+              file[:file].write(new_value)
+              output "Updated #{format_path(file[:file])}"
+            end
+          end
+
+          # Print a diff of what was uploaded
+          if config[:diff] && new_value != file[:value]
+            old_file = Tempfile.open(file[:file].name)
+            begin
+              old_file.write(file[:value])
+              old_file.close
+
+              diff = `diff -u #{old_file.path} #{tempfile.path}`
+              diff.gsub!(old_file.path, "#{format_path(file[:file])} (old)")
+              diff.gsub!(tempfile.path, "#{format_path(file[:file])} (new)")
+              stdout.write diff
+            ensure
+              old_file.close!
+            end
+          end
+        end
+
+        error
+      end
+
+      def sub_filenames(str, tempfiles)
+        tempfiles.each_pair do |tempfile, file|
+          str = str.gsub(tempfile.path, format_path(file[:file]))
+        end
+        str
+      end
+
+    end
+  end
+end
+
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index 7355ec7..131d706 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -18,6 +18,7 @@
 # limitations under the License.
 
 require 'logger'
+require 'chef/monologger'
 require 'mixlib/log'
 
 class Chef
@@ -25,8 +26,7 @@ class Chef
     extend Mixlib::Log
 
     # Force initialization of the primary log device (@logger)
-    init
-
+    init(MonoLogger.new(STDOUT))
 
     class Formatter
       def self.show_time=(*args)
diff --git a/lib/chef/mixin/check_helper.rb b/lib/chef/mixin/check_helper.rb
deleted file mode 100644
index b3a7835..0000000
--- a/lib/chef/mixin/check_helper.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.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.
-
-class Chef
-  module Mixin
-    module CheckHelper      
-      def set_if_args(thing, arguments)
-        raise ArgumentError, "Must call set_if_args with a block!" unless Kernel.block_given?
-        if arguments != nil
-          yield(arguments)
-        else
-          thing
-        end
-      end
-    end
-  end
-end
diff --git a/lib/chef/mixin/checksum.rb b/lib/chef/mixin/checksum.rb
index 7b716b6..1d9c99e 100644
--- a/lib/chef/mixin/checksum.rb
+++ b/lib/chef/mixin/checksum.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -17,16 +17,16 @@
 #
 
 require 'digest/sha2'
-require 'chef/checksum_cache'
+require 'chef/digester'
 
 class Chef
   module Mixin
     module Checksum
 
       def checksum(file)
-        Chef::ChecksumCache.checksum_for_file(file)
+        Chef::Digester.checksum_for_file(file)
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/mixin/command.rb b/lib/chef/mixin/command.rb
index 55c028f..fb75980 100644
--- a/lib/chef/mixin/command.rb
+++ b/lib/chef/mixin/command.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -24,6 +24,26 @@ require 'etc'
 
 class Chef
   module Mixin
+
+    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    # NOTE:
+    # The popen4 method upon which all the code here is based has a race
+    # condition where it may fail to read all of the data written to stdout and
+    # stderr after the child process exits. The tests for the code here
+    # occasionally fail because of this race condition, so they have been
+    # tagged "volatile".
+    #
+    # This code is considered deprecated, so it should not need to be modified
+    # frequently, if at all. HOWEVER, if you do modify the code here, you must
+    # explicitly enable volatile tests:
+    #
+    #   bundle exec rspec spec/unit/mixin/command_spec.rb -t volatile
+    #
+    # In addition, you should make a note that tests need to be run with
+    # volatile tests enabled on any pull request or bug report you submit with
+    # your patch.
+    #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
     module Command
       extend self
 
@@ -41,23 +61,32 @@ class Chef
 
       # === Parameters
       # args<Hash>: A number of required and optional arguments
-      #   command<String>, <Array>: A complete command with options to execute or a command and options as an Array 
+      #   command<String>, <Array>: A complete command with options to execute or a command and options as an Array
       #   creates<String>: The absolute path to a file that prevents the command from running if it exists
       #   cwd<String>: Working directory to execute command in, defaults to Dir.tmpdir
       #   timeout<String>: How many seconds to wait for the command to execute before timing out
       #   returns<String>: The single exit value command is expected to return, otherwise causes an exception
       #   ignore_failure<Boolean>: Whether to raise an exception on failure, or just return the status
       #   output_on_failure<Boolean>: Return output in raised exception regardless of Log.level
-      # 
+      #
       #   user<String>: The UID or user name of the user to execute the command as
       #   group<String>: The GID or group name of the group to execute the command as
       #   environment<Hash>: Pairs of environment variable names and their values to set before execution
       #
       # === Returns
       # Returns the exit status of args[:command]
-      def run_command(args={})         
+      def run_command(args={})
+        status, stdout, stderr = run_command_and_return_stdout_stderr(args)
+
+        status
+      end
+
+      # works same as above, except that it returns stdout and stderr
+      # requirement => platforms like solaris 9,10 has wierd issues where
+      # even in command failure the exit code is zero, so we need to lookup stderr.
+      def run_command_and_return_stdout_stderr(args={})
         command_output = ""
-        
+
         args[:ignore_failure] ||= false
         args[:output_on_failure] ||= false
 
@@ -68,28 +97,28 @@ class Chef
             return false
           end
         end
-        
+
         status, stdout, stderr = output_of_command(args[:command], args)
         command_output << "STDOUT: #{stdout}"
         command_output << "STDERR: #{stderr}"
         handle_command_failures(status, command_output, args)
-        
-        status
+
+        return status, stdout, stderr
       end
-      
+
       def output_of_command(command, args)
         Chef::Log.debug("Executing #{command}")
         stderr_string, stdout_string, status = "", "", nil
-        
+
         exec_processing_block = lambda do |pid, stdin, stdout, stderr|
           stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp
         end
-        
+
         args[:cwd] ||= Dir.tmpdir
         unless ::File.directory?(args[:cwd])
           raise Chef::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory"
         end
-        
+
         Dir.chdir(args[:cwd]) do
           if args[:timeout]
             begin
@@ -103,17 +132,17 @@ class Chef
           else
             status = popen4(command, args, &exec_processing_block)
           end
-          
+
           Chef::Log.debug("---- Begin output of #{command} ----")
           Chef::Log.debug("STDOUT: #{stdout_string}")
           Chef::Log.debug("STDERR: #{stderr_string}")
           Chef::Log.debug("---- End output of #{command} ----")
           Chef::Log.debug("Ran #{command} returned #{status.exitstatus}")
         end
-        
+
         return status, stdout_string, stderr_string
       end
-      
+
       def handle_command_failures(status, command_output, opts={})
         unless opts[:ignore_failure]
           opts[:returns] ||= 0
@@ -129,7 +158,7 @@ class Chef
           end
         end
       end
-      
+
       # Call #run_command but set LC_ALL to the system's current environment so it doesn't get changed to C.
       #
       # === Parameters
diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb
index 7b4ec7a..ece1699 100644
--- a/lib/chef/mixin/convert_to_class_name.rb
+++ b/lib/chef/mixin/convert_to_class_name.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -27,20 +27,20 @@ class Chef
         str.gsub!(/[^A-Za-z0-9_]/,'_')
         rname = nil
         regexp = %r{^(.+?)(_(.+))?$}
-        
+
         mn = str.match(regexp)
         if mn
           rname = mn[1].capitalize
 
           while mn && mn[3]
-            mn = mn[3].match(regexp)          
+            mn = mn[3].match(regexp)
             rname << mn[1].capitalize if mn
           end
         end
 
         rname
       end
-      
+
       def convert_to_snake_case(str, namespace=nil)
         str = str.dup
         str.sub!(/^#{namespace}(\:\:)?/, '') if namespace
@@ -49,17 +49,17 @@ class Chef
         str.sub!(/^\_/, "")
         str
       end
-      
+
       def snake_case_basename(str)
         with_namespace = convert_to_snake_case(str)
         with_namespace.split("::").last.sub(/^_/, '')
       end
-      
+
       def filename_to_qualified_string(base, filename)
         file_base = File.basename(filename, ".rb")
         base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/mixin/create_path.rb b/lib/chef/mixin/create_path.rb
index 9b5dba1..9d1248e 100644
--- a/lib/chef/mixin/create_path.rb
+++ b/lib/chef/mixin/create_path.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -18,21 +18,21 @@
 class Chef
   module Mixin
     module CreatePath
-      
+
       # Creates a given path, including all directories that lead up to it.
       # Like mkdir_p, but without the leaking.
       #
       # === Parameters
-      # file_path<String, Array>:: A string that represents the path to create, 
+      # file_path<String, Array>:: A string that represents the path to create,
       #   or an Array with the path-parts.
       #
       # === Returns
       # The created file_path.
       def create_path(file_path)
         unless file_path.kind_of?(String) || file_path.kind_of?(Array)
-          raise ArgumentError, "file_path must be a string or an array!" 
+          raise ArgumentError, "file_path must be a string or an array!"
         end
-        
+
         if file_path.kind_of?(String)
           file_path = File.expand_path(file_path).split(File::SEPARATOR)
           file_path.shift if file_path[0] == ''
@@ -41,17 +41,17 @@ class Chef
             file_path[0] = "#{File::SEPARATOR}#{file_path[0]}"
           end
         end
-                
+
         file_path.each_index do |i|
           create_path = File.join(file_path[0, i + 1])
           unless File.directory?(create_path)
             Chef::Log.debug("Creating directory #{create_path}")
             Dir.mkdir(create_path)
-          end 
+          end
         end
         File.expand_path(File.join(file_path))
       end
-  
+
     end
   end
 end
diff --git a/lib/chef/mixin/deep_merge.rb b/lib/chef/mixin/deep_merge.rb
index dd6e946..9fa8694 100644
--- a/lib/chef/mixin/deep_merge.rb
+++ b/lib/chef/mixin/deep_merge.rb
@@ -8,9 +8,9 @@
 # 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.
@@ -26,13 +26,25 @@ class Chef
     #   deep_merge is available under the MIT license from
     #   http://trac.misuse.org/science/wiki/DeepMerge
     module DeepMerge
+
+      class InvalidSubtractiveMerge < ArgumentError; end
+
+
+      OLD_KNOCKOUT_PREFIX = "!merge:".freeze
+
+      # Regex to match the "knockout prefix" that was used to indicate
+      # subtractive merging in Chef 10.x and previous. Subtractive merging is
+      # removed as of Chef 11, but we detect attempted use of it and raise an
+      # error (see: raise_if_knockout_used!)
+      OLD_KNOCKOUT_MATCH = %r[!merge].freeze
+
       extend self
 
       def merge(first, second)
         first  = Mash.new(first)  unless first.kind_of?(Mash)
         second = Mash.new(second) unless second.kind_of?(Mash)
 
-        DeepMerge.deep_merge(second, first, {:preserve_unmergeables => false})
+        DeepMerge.deep_merge(second, first)
       end
 
       # Inherited roles use the knockout_prefix array subtraction functionality
@@ -41,11 +53,11 @@ class Chef
         first  = Mash.new(first)  unless first.kind_of?(Mash)
         second = Mash.new(second) unless second.kind_of?(Mash)
 
-        DeepMerge.deep_merge(second, first, {:knockout_prefix => "!merge", :preserve_unmergeables => false})
+        DeepMerge.deep_merge(second, first)
       end
-    
+
       class InvalidParameter < StandardError; end
-      
+
       # Deep Merge core documentation.
       # deep_merge! method permits merging of arbitrary child elements. The two top level
       # elements must be hashes. These hashes can contain unlimited (to stack limit) levels
@@ -60,187 +72,96 @@ class Chef
       #   Results: {:x => [1,2,3,4,5,'6'], :y => 2}
       # By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
       # To avoid this, use "deep_merge" (no bang/exclamation mark)
-      # 
-      # Options:
-      #   Options are specified in the last parameter passed, which should be in hash format:
-      #   hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '!merge'})
-      #   :preserve_unmergeables  DEFAULT: false
-      #      Set to true to skip any unmergeable elements from source
-      #   :knockout_prefix        DEFAULT: nil
-      #      Set to string value to signify prefix which deletes elements from existing element
-      #      A colon is appended when indicating a specific value, eg:
-      #      :knockout_prefix => "dontmerge", is referenced as "dontmerge:foobar" in an array
-      #   :sort_merged_arrays     DEFAULT: false
-      #      Set to true to sort all arrays that are merged together
-      #   :unpack_arrays          DEFAULT: nil
-      #      Set to string value to run "Array::join" then "String::split" against all arrays
-      #   :merge_debug            DEFAULT: false
-      #      Set to true to get console output of merge process for debugging
-      #
-      # Selected Options Details:
-      # :knockout_prefix => The purpose of this is to provide a way to remove elements 
-      #   from existing Hash by specifying them in a special way in incoming hash
-      #    source = {:x => ['!merge:1', '2']}
-      #    dest   = {:x => ['1', '3']}
-      #    dest.ko_deep_merge!(source)
-      #    Results: {:x => ['2','3']}
-      #   Additionally, if the knockout_prefix is passed alone as a string, it will cause
-      #   the entire element to be removed:
-      #    source = {:x => '!merge'}
-      #    dest   = {:x => [1,2,3]}
-      #    dest.ko_deep_merge!(source)
-      #    Results: {:x => ""}
-      # :unpack_arrays => The purpose of this is to permit compound elements to be passed
-      #   in as strings and to be converted into discrete array elements
-      #   irsource = {:x => ['1,2,3', '4']}
-      #   dest   = {:x => ['5','6','7,8']}
-      #   dest.deep_merge!(source, {:unpack_arrays => ','})
-      #   Results: {:x => ['1','2','3','4','5','6','7','8'}
-      #   Why: If receiving data from an HTML form, this makes it easy for a checkbox 
-      #    to pass multiple values from within a single HTML element
-      # 
-      # There are many tests for this library - and you can learn more about the features
-      # and usages of deep_merge! by just browsing the test examples
-      def deep_merge!(source, dest, options = {})
-        # turn on this line for stdout debugging text
-        merge_debug = options[:merge_debug] || false
-        overwrite_unmergeable = !options[:preserve_unmergeables]
-        knockout_prefix = options[:knockout_prefix] || nil
-        raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!" if knockout_prefix == ""
-        raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!" if knockout_prefix && !overwrite_unmergeable
-        # if present: we will split and join arrays on this char before merging
-        array_split_char = options[:unpack_arrays] || false
-        # request that we sort together any arrays when they are merged
-        sort_merged_arrays = options[:sort_merged_arrays] || false
-        di = options[:debug_indent] || ''
-        # do nothing if source is nil
-        return dest if source.nil?
+      def deep_merge!(source, dest)
         # if dest doesn't exist, then simply copy source to it
-        if dest.nil? && overwrite_unmergeable
+        if dest.nil?
           dest = source; return dest
         end
 
-        puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug
-        if source.kind_of?(Hash)
-          puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug
+        raise_if_knockout_used!(source)
+        raise_if_knockout_used!(dest)
+        case source
+        when nil
+          dest
+        when Hash
           source.each do |src_key, src_value|
             if dest.kind_of?(Hash)
-              puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug
               if dest[src_key]
-                puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug
-                dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + '  '))
-              else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!)
-                puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug
-                # note: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others)
-                begin
-                  src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty)
-                rescue TypeError
-                  src_dup = src_value
-                end
-                dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + '  '))
+                dest[src_key] = deep_merge!(src_value, dest[src_key])
+              else # dest[src_key] doesn't exist so we take whatever source has
+                raise_if_knockout_used!(src_value)
+                dest[src_key] = src_value
               end
-            else # dest isn't a hash, so we overwrite it completely (if permitted)
-              if overwrite_unmergeable
-                puts "#{di}  overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over->  #{dest.inspect}" if merge_debug
-                dest = overwrite_unmergeables(source, dest, options)
-              end
-            end
-          end
-        elsif source.kind_of?(Array)
-          puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
-          # if we are instructed, join/split any source arrays before processing
-          if array_split_char
-            puts "#{di} split/join on source: #{source.inspect}" if merge_debug
-            source = source.join(array_split_char).split(array_split_char)
-            if dest.kind_of?(Array)
-              dest = dest.join(array_split_char).split(array_split_char)
-            end
-          end
-          # if there's a naked knockout_prefix in source, that means we are to truncate dest
-          ko_variants = [ knockout_prefix, "#{knockout_prefix}:" ]
-          ko_variants.each do |ko|
-            if source.index(ko)
-              dest = clear_or_nil(dest); source.delete(ko)
+            else # dest isn't a hash, so we overwrite it completely
+              dest = source
             end
           end
+        when Array
           if dest.kind_of?(Array)
-            if knockout_prefix
-              print "#{di} knocking out: " if merge_debug
-              # remove knockout prefix items from both source and dest
-              source.delete_if do |ko_item|
-                retval = false
-                item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}:}, "") : ko_item
-                if item != ko_item
-                  print "#{ko_item} - " if merge_debug
-                  dest.delete(item)
-                  dest.delete(ko_item)
-                  retval = true
-                end
-                retval
-              end
-              puts if merge_debug
-            end
-            puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug
             dest = dest | source
-            dest.sort! if sort_merged_arrays
-          elsif overwrite_unmergeable
-            puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug
-            dest = overwrite_unmergeables(source, dest, options)
+          else
+            dest = source
           end
+        when String
+          dest = source
         else # src_hash is not an array or hash, so we'll have to overwrite dest
-          puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug
-          dest = overwrite_unmergeables(source, dest, options)
+          dest = source
         end
-        puts "#{di}Returning #{dest.inspect}" if merge_debug
         dest
       end # deep_merge!
-     
-      # allows deep_merge! to uniformly handle overwriting of unmergeable entities
-      def overwrite_unmergeables(source, dest, options)
-        merge_debug = options[:merge_debug] || false
-        overwrite_unmergeable = !options[:preserve_unmergeables]
-        knockout_prefix = options[:knockout_prefix] || false
-        di = options[:debug_indent] || ''
-        if knockout_prefix && overwrite_unmergeable
-          if source.kind_of?(String) # remove knockout string from source before overwriting dest
-            if source == knockout_prefix
-              src_tmp = ""
-            else
-              src_tmp = source.gsub(%r{^#{knockout_prefix}:},"")
-            end
-          elsif source.kind_of?(Array) # remove all knockout elements before overwriting dest
-            src_tmp = source.delete_if {|ko_item| ko_item.kind_of?(String) && ko_item.match(%r{^#{knockout_prefix}:}) }
-          else
-            src_tmp = source
-          end
-          if src_tmp == source # if we didn't find a knockout_prefix then we just overwrite dest
-            puts "#{di}#{src_tmp.inspect} -over-> #{dest.inspect}" if merge_debug
-            dest = src_tmp
-          else # if we do find a knockout_prefix, then we just delete dest
-            puts "#{di}\"\" -over-> #{dest.inspect}" if merge_debug
-            dest = ""
+
+      def hash_only_merge(merge_onto, merge_with)
+        hash_only_merge!(merge_onto.dup, merge_with.dup)
+      end
+
+      # Deep merge without Array merge.
+      # `merge_onto` is the object that will "lose" in case of conflict.
+      # `merge_with` is the object whose values will replace `merge_onto`s
+      # values when there is a conflict.
+      def hash_only_merge!(merge_onto, merge_with)
+        # If there are two Hashes, recursively merge.
+        if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash)
+          merge_with.each do |key, merge_with_value|
+            merge_onto[key] = hash_only_merge!(merge_onto[key], merge_with_value)
           end
-        elsif overwrite_unmergeable
-          dest = source
+          merge_onto
+
+        # If merge_with is nil, don't replace merge_onto
+        elsif merge_with.nil?
+          merge_onto
+
+        # In all other cases, replace merge_onto with merge_with
+        else
+          merge_with
         end
-        dest
       end
 
-      def deep_merge(source, dest, options = {})
-        deep_merge!(source.dup, dest.dup, options)
+      # Checks for attempted use of subtractive merge, which was removed for
+      # Chef 11.0. If subtractive merge use is detected, will raise an
+      # InvalidSubtractiveMerge exception.
+      def raise_if_knockout_used!(obj)
+        if uses_knockout?(obj)
+          raise InvalidSubtractiveMerge, "subtractive merge with !merge is no longer supported"
+        end
       end
-     
-      def clear_or_nil(obj)
-        if obj.respond_to?(:clear)
-          obj.clear
+
+      # Checks for attempted use of subtractive merge in +obj+.
+      def uses_knockout?(obj)
+        case obj
+        when String
+          obj =~ OLD_KNOCKOUT_MATCH
+        when Array
+          obj.any? {|element| element.respond_to?(:gsub) && element =~ OLD_KNOCKOUT_MATCH }
         else
-          obj = nil
+          false
         end
-        obj
       end
-     
+
+      def deep_merge(source, dest)
+        deep_merge!(source.dup, dest.dup)
+      end
+
     end
-     
   end
 end
 
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index cc85c4e..489f27c 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -18,7 +18,42 @@
 
 class Chef
   module Mixin
+
+
+      def self.deprecated_constants
+        @deprecated_constants ||= {}
+      end
+
+      # Add a deprecated constant to the Chef::Mixin namespace.
+      # === Arguments
+      # * name: the constant name, as a relative symbol.
+      # * replacement: the constant to return instead.
+      # * message: A message telling the user what to do instead.
+      # === Example:
+      #   deprecate_constant(:RecipeDefinitionDSLCore, Chef::DSL::Recipe, <<-EOM)
+      #     Chef::Mixin::RecipeDefinitionDSLCore is deprecated, use Chef::DSL::Recipe instead.
+      #   EOM
+      def self.deprecate_constant(name, replacement, message)
+        deprecated_constants[name] = {:replacement => replacement, :message => message}
+      end
+
+      # Const missing hook to look up deprecated constants defined with
+      # deprecate_constant. Emits a warning to the logger and returns the
+      # replacement constant. Will call super, most likely causing an exception
+      # for the missing constant, if +name+ is not found in the
+      # deprecated_constants collection.
+      def self.const_missing(name)
+        if new_const = deprecated_constants[name]
+          Chef::Log.warn(new_const[:message])
+          Chef::Log.warn("Called from: \n#{caller[0...3].map {|l| "\t#{l}"}.join("\n")}")
+          new_const[:replacement]
+        else
+          super
+        end
+      end
+
     module Deprecation
+
       class DeprecatedObjectProxyBase
         KEEPERS = %w{__id__ __send__ instance_eval == equal? initialize object_id}
         instance_methods.each { |method_name| undef_method(method_name) unless KEEPERS.include?(method_name.to_s)}
diff --git a/lib/chef/mixin/enforce_ownership_and_permissions.rb b/lib/chef/mixin/enforce_ownership_and_permissions.rb
index e72595c..9c1e4dd 100644
--- a/lib/chef/mixin/enforce_ownership_and_permissions.rb
+++ b/lib/chef/mixin/enforce_ownership_and_permissions.rb
@@ -22,14 +22,14 @@ class Chef
   module Mixin
     module EnforceOwnershipAndPermissions
 
+      def access_controls
+        @access_controls ||= Chef::FileAccessControl.new(current_resource, new_resource, self)
+      end
+
       # will set the proper user, group and
       # permissions using a platform specific
       # version of Chef::FileAccessControl
-      def enforce_ownership_and_permissions(path=nil)
-        if path.nil? and new_resource.respond_to?(:path)
-          path = new_resource.path
-        end
-        access_controls = Chef::FileAccessControl.new(new_resource, path)
+      def enforce_ownership_and_permissions
         access_controls.set_all
         new_resource.updated_by_last_action(true) if access_controls.modified?
       end
diff --git a/lib/chef/mixin/file_class.rb b/lib/chef/mixin/file_class.rb
new file mode 100644
index 0000000..f6a663d
--- /dev/null
+++ b/lib/chef/mixin/file_class.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Mark Mzyk <mmzyk at opscode.com>
+# Author:: Seth Chisamore <schisamo at opscode.com>
+# Author:: Bryan McLellan <btm at opscode.com>
+# Copyright:: Copyright (c) 2011-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.
+#
+
+class Chef
+  module Mixin
+    module FileClass
+
+      def file_class
+        @host_os_file ||= if Chef::Platform.windows?
+          require 'chef/win32/file'
+          Chef::ReservedNames::Win32::File
+        else
+          ::File
+        end
+      end
+    end
+  end
+end
+
+
diff --git a/lib/chef/mixin/from_file.rb b/lib/chef/mixin/from_file.rb
index 609fe1d..0d1ddca 100644
--- a/lib/chef/mixin/from_file.rb
+++ b/lib/chef/mixin/from_file.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -20,9 +20,9 @@
 class Chef
   module Mixin
     module FromFile
-    
-      # Loads a given ruby file, and runs instance_eval against it in the context of the current 
-      # object.  
+
+      # Loads a given ruby file, and runs instance_eval against it in the context of the current
+      # object.
       #
       # Raises an IOError if the file cannot be found, or is not readable.
       def from_file(filename)
@@ -33,7 +33,7 @@ class Chef
         end
       end
 
-      # Loads a given ruby file, and runs class_eval against it in the context of the current 
+      # Loads a given ruby file, and runs class_eval against it in the context of the current
       # object.
       #
       # Raises an IOError if the file cannot be found, or is not readable.
diff --git a/lib/chef/mixin/language.rb b/lib/chef/mixin/language.rb
index 52b2065..f4df86b 100644
--- a/lib/chef/mixin/language.rb
+++ b/lib/chef/mixin/language.rb
@@ -16,234 +16,33 @@
 # limitations under the License.
 #
 
-require 'chef/search/query'
-require 'chef/data_bag'
-require 'chef/data_bag_item'
-require 'chef/encrypted_data_bag_item'
+require 'chef/dsl/platform_introspection'
+require 'chef/dsl/data_query'
+require 'chef/mixin/deprecation'
 
 class Chef
   module Mixin
-    module Language
 
-      # Implementation class for determining platform dependent values
-      class PlatformDependentValue
-
-        # Create a platform dependent value object.
-        # === Arguments
-        # platform_hash (Hash) a hash of the same structure as Chef::Platform,
-        # like this:
-        #   {
-        #     :debian => {:default => 'the value for all debian'}
-        #     [:centos, :redhat, :fedora] => {:default => "value for all EL variants"}
-        #     :ubuntu => { :default => "default for ubuntu", '10.04' => "value for 10.04 only"},
-        #     :default => "the default when nothing else matches"
-        #   }
-        # * platforms can be specified as Symbols or Strings
-        # * multiple platforms can be grouped by using an Array as the key
-        # * values for platforms need to be Hashes of the form:
-        #   {platform_version => value_for_that_version}
-        # * the exception to the above is the default value, which is given as
-        #   :default => default_value
-        def initialize(platform_hash)
-          @values = {}
-          platform_hash.each { |platforms, value| set(platforms, value)}
-        end
-
-        def value_for_node(node)
-          platform, version = node[:platform].to_s, node[:platform_version].to_s
-          if @values.key?(platform) && @values[platform].key?(version)
-            @values[platform][version]
-          elsif @values.key?(platform) && @values[platform].key?("default")
-            @values[platform]["default"]
-          elsif @values.key?("default")
-            @values["default"]
-          else
-            nil
-          end
-        end
-
-        private
-
-        def set(platforms, value)
-          if platforms.to_s == 'default'
-            @values["default"] = value
-          else
-            assert_valid_platform_values!(platforms, value)
-            Array(platforms).each { |platform| @values[platform.to_s] = normalize_keys(value)}
-            value
-          end
-        end
-
-        def normalize_keys(hash)
-          hash.inject({}) do |h, key_value|
-            keys, value = *key_value
-            Array(keys).each do |key|
-              h[key.to_s] = value
-            end
-            h
-          end
-        end
-
-        def assert_valid_platform_values!(platforms, value)
-          unless value.kind_of?(Hash)
-            msg = "platform dependent values must be specified in the format :platform => {:version => value} "
-            msg << "you gave a value #{value.inspect} for platform(s) #{platforms}"
-            raise ArgumentError, msg
-          end
-        end
-      end
-
-
-
-      # Given a hash similar to the one we use for Platforms, select a value from the hash.  Supports
-      # per platform defaults, along with a single base default. Arrays may be passed as hash keys and
-      # will be expanded.
-      #
-      # === Parameters
-      # platform_hash:: A platform-style hash.
-      #
-      # === Returns
-      # value:: Whatever the most specific value of the hash is.
-      def value_for_platform(platform_hash)
-        PlatformDependentValue.new(platform_hash).value_for_node(node)
-      end 
-
-      # Given a list of platforms, returns true if the current recipe is being run on a node with
-      # that platform, false otherwise.
-      #
-      # === Parameters
-      # args:: A list of platforms. Each platform can be in string or symbol format.
-      #
-      # === Returns
-      # true:: If the current platform is in the list
-      # false:: If the current platform is not in the list
-      def platform?(*args)
-        has_platform = false
-
-        args.flatten.each do |platform|
-          has_platform = true if platform.to_s == node[:platform]
-        end
-
-        has_platform
-      end
-
-
-
-     # Implementation class for determining platform family dependent values
-      class PlatformFamilyDependentValue
-
-        # Create a platform family dependent value object.
-        # === Arguments
-        # platform_family_hash (Hash) a map of platform families to values. 
-        # like this:
-        #   {
-        #     :rhel => "value for all EL variants"
-        #     :fedora =>  "value for fedora variants fedora and amazon" ,
-	#     [:fedora, :rhel] => "value for all known redhat variants"
-        #     :debian =>  "value for debian variants including debian, ubuntu, mint" ,
-        #     :default => "the default when nothing else matches"
-        #   }
-        # * platform families can be specified as Symbols or Strings
-        # * multiple platform families can be grouped by using an Array as the key
-        # * values for platform families can be any object, with no restrictions. Some examples: 
-	#   - [:stop, :start]
-	#   - "mysql-devel"
-        #   - { :key => "value" }
-        def initialize(platform_family_hash)
-          @values = {}
-	  @values["default"] = nil
-          platform_family_hash.each { |platform_families, value| set(platform_families, value)}
-        end
-
-        def value_for_node(node)
-	  if node.key?(:platform_family)
-            platform_family = node[:platform_family].to_s
-            if @values.key?(platform_family)
-              @values[platform_family]
-	    else
-              @values["default"]
-            end
-	  else
-            @values["default"]
-	  end
-        end
-
-        private
-
-        def set(platform_family, value)
-          if platform_family.to_s == 'default'
-            @values["default"] = value
-          else
-            Array(platform_family).each { |family| @values[family.to_s] = value }
-            value
-          end
-        end
-      end
-
-
-      # Given a hash mapping platform families to values, select a value from the hash. Supports a single
-      # base default if platform family is not in the map. Arrays may be passed as hash keys and will be 
-      # expanded.  
-      #
-      # === Parameters
-      # platform_family_hash:: A hash in the form { platform_family_name => value } 
-      #
-      # === Returns
-      # value:: Whatever the most specific value of the hash is.
-      def value_for_platform_family(platform_family_hash) 
-        PlatformFamilyDependentValue.new(platform_family_hash).value_for_node(node)
-      end
-      
-      # Given a list of platform families, returns true if the current recipe is being run on a 
-      # node within that platform family, false otherwise. 
-      #
-      # === Parameters
-      # args:: A list of platform families. Each platform family can be in string or symbol format.  
-      #
-      # === Returns
-      # true:: if the current node platform family is in the list. 
-      # false:: if the current node platform family is not in the list. 
-      def platform_family?(*args)
-        has_pf = false
-        args.flatten.each do |platform_family|
-	  has_pf = true if platform_family.to_s == node[:platform_family] 
-        end 
-        has_pf
-      end	
-      
-      def search(*args, &block)
-        # If you pass a block, or have at least the start argument, do raw result parsing
-        #
-        # Otherwise, do the iteration for the end user
-        if Kernel.block_given? || args.length >= 4
-          Chef::Search::Query.new.search(*args, &block)
-        else
-          results = Array.new
-          Chef::Search::Query.new.search(*args) do |o|
-            results << o
-          end
-          results
-        end
-      end
-
-      def data_bag(bag)
-        DataBag.validate_name!(bag.to_s)
-        rbag = DataBag.load(bag)
-        rbag.keys
-      rescue Exception
-        Log.error("Failed to list data bag items in data bag: #{bag.inspect}")
-        raise
-      end
-
-      def data_bag_item(bag, item)
-        DataBag.validate_name!(bag.to_s)
-        DataBagItem.validate_id!(item)
-        DataBagItem.load(bag, item)
-      rescue Exception
-        Log.error("Failed to load data bag item: #{bag.inspect} #{item.inspect}")
-        raise
-      end
+    # == [DEPRECATED] Chef::Mixin::DeprecatedLanguageModule
+    # This module is a temporary replacement for the previous
+    # Chef::Mixin::Language. That module's functionality was split into two
+    # modules, Chef::DSL::PlatformIntrospection, and Chef::DSL::DataQuery.
+    #
+    # This module includes both PlatformIntrospection and DataQuery to provide
+    # the same interfaces and behavior as the prior Mixin::Language.
+    #
+    # This module is loaded via const_missing hook when Chef::Mixin::Language
+    # is accessed. See chef/mixin/deprecation for details.
+    module DeprecatedLanguageModule
+
+      include Chef::DSL::PlatformIntrospection
+      include Chef::DSL::DataQuery
 
     end
+
+    deprecate_constant(:Language, DeprecatedLanguageModule, <<-EOM)
+Chef::Mixin::Language is deprecated. Use either (or both)
+Chef::DSL::PlatformIntrospection or Chef::DSL::DataQuery instead.
+EOM
   end
 end
diff --git a/lib/chef/mixin/language_include_attribute.rb b/lib/chef/mixin/language_include_attribute.rb
index 5d926a6..0be2614 100644
--- a/lib/chef/mixin/language_include_attribute.rb
+++ b/lib/chef/mixin/language_include_attribute.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -16,46 +16,20 @@
 # limitations under the License.
 #
 
-require 'chef/log'
+require 'chef/dsl/include_attribute'
+require 'chef/mixin/deprecation'
 
 class Chef
   module Mixin
-    module LanguageIncludeAttribute
-
-      # Loads the attribute file specified by the short name of the
-      # file, e.g., loads specified cookbook's
-      #   "attributes/mailservers.rb"
-      # if passed
-      #   "mailservers"
-      def include_attribute(*fully_qualified_attribute_short_filenames)
-        if self.kind_of?(Chef::Node)
-          node = self
-        else
-          node = @node
-        end
-
-        fully_qualified_attribute_short_filenames.flatten.each do |fully_qualified_attribute_short_filename|
-          if node.run_state[:seen_attributes].has_key?(fully_qualified_attribute_short_filename)
-            Chef::Log.debug("I am not loading attribute file #{fully_qualified_attribute_short_filename}, because I have already seen it.")
-            next
-          end
 
-          Chef::Log.debug("Loading Attribute #{fully_qualified_attribute_short_filename}")
-          node.run_state[:seen_attributes][fully_qualified_attribute_short_filename] = true
+    # DEPRECATED: This is just here for compatibility, use
+    # Chef::DSL::IncludeAttribute instead.
 
-          if amatch = fully_qualified_attribute_short_filename.match(/(.+?)::(.+)/)
-            cookbook_name = amatch[1].to_sym
-            node.load_attribute_by_short_filename(amatch[2], cookbook_name)
-          else
-            cookbook_name = fully_qualified_attribute_short_filename.to_sym
-            node.load_attribute_by_short_filename("default", cookbook_name)
-          end
-        end
-        true
-      end
+    deprecate_constant(:LanguageIncludeAttribute, Chef::DSL::IncludeAttribute, <<-EOM)
+Chef::Mixin::LanguageIncludeAttribute is deprecated. Use
+Chef::DSL::IncludeAttribute instead.
+EOM
 
-    end
   end
 end
-      
 
diff --git a/lib/chef/mixin/language_include_recipe.rb b/lib/chef/mixin/language_include_recipe.rb
index 1bb66e4..d85e5c6 100644
--- a/lib/chef/mixin/language_include_recipe.rb
+++ b/lib/chef/mixin/language_include_recipe.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -16,41 +16,17 @@
 # limitations under the License.
 #
 
-require 'chef/log'
+require 'chef/dsl/include_recipe'
+require 'chef/mixin/deprecation'
 
 class Chef
   module Mixin
-    module LanguageIncludeRecipe
-
-      def include_recipe(*recipe_names)
-        result_recipes = Array.new
-        recipe_names.flatten.each do |recipe_name|
-          if node.run_state[:seen_recipes].has_key?(recipe_name) or node.run_state[:seen_recipes].has_key?(recipe_name + "::default")
-            Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.")
-            next
-          end
-
-          result_recipes << load_recipe(recipe_name)
-        end
-        result_recipes
-      end
 
-      def load_recipe(recipe_name)
-          Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
-          node.run_state[:seen_recipes][recipe_name] = true
+    deprecate_constant(:LanguageIncludeRecipe, Chef::DSL::IncludeRecipe, <<-EOM)
+Chef::Mixin::LanguageIncludeRecipe is deprecated, use Chef::DSL::IncludeRecipe
+instead.
+EOM
 
-          cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name)
-
-          run_context = self.is_a?(Chef::RunContext) ? self : self.run_context
-          cookbook = run_context.cookbook_collection[cookbook_name]
-          cookbook.load_recipe(recipe_short_name, run_context)
-      end
-
-      def require_recipe(*args)
-        include_recipe(*args)
-      end
-
-    end
   end
 end
-      
+
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 649224f..a9799f7 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -16,10 +16,11 @@
 # limitations under the License.
 
 class Chef
-  
+  class DelayedEvaluator < Proc
+  end
   module Mixin
     module ParamsValidate
-      
+
       # Takes a hash of options, along with a map to validate them.  Returns the original
       # options hash, plus any changes that might have been made (through things like setting
       # default values in the validation map)
@@ -27,19 +28,19 @@ class Chef
       # For example:
       #
       #   validate({ :one => "neat" }, { :one => { :kind_of => String }})
-      # 
+      #
       # Would raise an exception if the value of :one above is not a kind_of? string.  Valid
       # map options are:
       #
       # :default:: Sets the default value for this parameter.
-      # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.  
+      # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
       #              The key will be inserted into the error message if the Proc does not return true:
       #                 "Option #{key}'s value #{value} #{message}!"
-      # :kind_of:: Ensure that the value is a kind_of?(Whatever).  If passed an array, it will ensure 
+      # :kind_of:: Ensure that the value is a kind_of?(Whatever).  If passed an array, it will ensure
       #            that the value is one of those types.
       # :respond_to:: Ensure that the value has a given method.  Takes one method name or an array of
       #               method names.
-      # :required:: Raise an exception if this parameter is missing. Valid values are true or false, 
+      # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
       #             by default, options are not required.
       # :regex:: Match the value of the paramater against a regular expression.
       # :equal_to:: Match the value of the paramater with ==.  An array means it can be equal to any
@@ -47,12 +48,12 @@ class Chef
       def validate(opts, map)
         #--
         # validate works by taking the keys in the validation map, assuming it's a hash, and
-        # looking for _pv_:symbol as methods.  Assuming it find them, it calls the right 
-        # one.  
+        # looking for _pv_:symbol as methods.  Assuming it find them, it calls the right
+        # one.
         #++
         raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash)
-        raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash)   
-        
+        raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash)
+
         map.each do |key, validation|
           unless key.kind_of?(Symbol) || key.kind_of?(String)
             raise ArgumentError, "Validation map keys must be symbols or strings!"
@@ -75,23 +76,32 @@ class Chef
         end
         opts
       end
-          
+
+      def lazy(&block)
+        DelayedEvaluator.new(&block)
+      end
+
       def set_or_return(symbol, arg, validation)
         iv_symbol = "@#{symbol.to_s}".to_sym
-        map = {
-          symbol => validation
-        }
-
         if arg == nil && self.instance_variable_defined?(iv_symbol) == true
-          self.instance_variable_get(iv_symbol)
+          ivar = self.instance_variable_get(iv_symbol)
+          if(ivar.is_a?(DelayedEvaluator))
+            validate({ symbol => ivar.call }, { symbol => validation })[symbol]
+          else
+            ivar
+          end
         else
-          opts = validate({ symbol => arg }, { symbol => validation })
-          self.instance_variable_set(iv_symbol, opts[symbol])
+          if(arg.is_a?(DelayedEvaluator))
+            val = arg
+          else
+            val = validate({ symbol => arg }, { symbol => validation })[symbol]
+          end
+          self.instance_variable_set(iv_symbol, val)
         end
       end
-            
+
       private
-      
+
         # Return the value of a parameter, or nil if it doesn't exist.
         def _pv_opts_lookup(opts, key)
           if opts.has_key?(key.to_s)
@@ -102,7 +112,7 @@ class Chef
             nil
           end
         end
-        
+
         # Raise an exception if the parameter is not found.
         def _pv_required(opts, key, is_required=true)
           if is_required
@@ -114,7 +124,7 @@ class Chef
             end
           end
         end
-        
+
         def _pv_equal_to(opts, key, to_be)
           value = _pv_opts_lookup(opts, key)
           unless value.nil?
@@ -127,7 +137,7 @@ class Chef
             end
           end
         end
-        
+
         # Raise an exception if the parameter is not a kind_of?(to_be)
         def _pv_kind_of(opts, key, to_be)
           value = _pv_opts_lookup(opts, key)
@@ -141,7 +151,7 @@ class Chef
             end
           end
         end
-        
+
         # Raise an exception if the parameter does not respond to a given set of methods.
         def _pv_respond_to(opts, key, method_name_list)
           value = _pv_opts_lookup(opts, key)
@@ -171,7 +181,7 @@ class Chef
             end
           end
         end
-      
+
         # Assign a default value to a parameter.
         def _pv_default(opts, key, default_value)
           value = _pv_opts_lookup(opts, key)
@@ -179,7 +189,7 @@ class Chef
             opts[key] = default_value
           end
         end
-        
+
         # Check a parameter against a regular expression.
         def _pv_regex(opts, key, regex)
           value = _pv_opts_lookup(opts, key)
@@ -197,7 +207,7 @@ class Chef
             end
           end
         end
-        
+
         # Check a parameter against a hash of proc's.
         def _pv_callbacks(opts, key, callbacks)
           raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
diff --git a/lib/chef/mixin/recipe_definition_dsl_core.rb b/lib/chef/mixin/recipe_definition_dsl_core.rb
index cf89600..704ee16 100644
--- a/lib/chef/mixin/recipe_definition_dsl_core.rb
+++ b/lib/chef/mixin/recipe_definition_dsl_core.rb
@@ -17,70 +17,19 @@
 # limitations under the License.
 #
 
-require 'chef/resource'
-require 'chef/resource_platform_map'
-require 'chef/mixin/convert_to_class_name'
-require 'chef/mixin/language'
+###
+# NOTE: This file and constant are here only for backwards compatibility.
+# New code should use Chef::DSL::Recipe instead.
+#
+# This constant (module name) will eventually be deprecated and then removed.
+###
 
-#--
-# UGH. this is a circular require that will cause an uninitialized constant
-# error, but this file really does depend on Chef::Recipe. oh well.
-# require 'chef/recipe'
+require 'chef/mixin/deprecation'
 
 class Chef
   module Mixin
-    module RecipeDefinitionDSLCore
-
-      include Chef::Mixin::ConvertToClassName
-      include Chef::Mixin::Language
-
-      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 run_context.definitions.has_key?(method_symbol)
-          # This dupes the high level object, but we still need to dup the params
-          new_def = run_context.definitions[method_symbol].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)
-        else
-          # Otherwise, we're rocking the regular resource call route.
-
-          # Checks the new platform => short_name => resource mapping initially
-          # then fall back to the older approach (Chef::Resource.const_get) for
-          # backward compatibility
-          resource_class = Chef::Resource.resource_for_node(method_symbol, run_context.node)
-
-          super unless resource_class
-          raise ArgumentError, "You must supply a name when declaring a #{method_symbol} resource" unless args.size > 0
-
-          # If we have a resource like this one, we want to steal its state
-          args << run_context
-          resource = resource_class.new(*args)
-          resource.load_prior_resource
-          resource.cookbook_name = cookbook_name
-          resource.recipe_name = @recipe_name
-          resource.params = @params
-          resource.source_line = caller[0]
-          # Determine whether this resource is being created in the context of an enclosing Provider
-          resource.enclosing_provider = self.is_a?(Chef::Provider) ? self : nil
-          # Evaluate resource attribute DSL
-          resource.instance_eval(&block) if block
-
-          # Run optional resource hook
-          resource.after_created
-
-          run_context.resource_collection.insert(resource)
-          resource
-        end
-      end
-
-    end
+    deprecate_constant(:RecipeDefinitionDSLCore, Chef::DSL::Recipe, <<-EOM)
+Chef::Mixin::RecipeDefinitionDSLCore is deprecated. Use Chef::DSL::Recipe instead.
+EOM
   end
 end
diff --git a/lib/chef/mixin/securable.rb b/lib/chef/mixin/securable.rb
index 65c1957..f77703f 100644
--- a/lib/chef/mixin/securable.rb
+++ b/lib/chef/mixin/securable.rb
@@ -59,24 +59,58 @@ class Chef
         )
       end
 
-      # TODO should this be separated into different files?
-      if RUBY_PLATFORM =~ /mswin|mingw|windows/
 
-        # supports params like this:
+      #==WindowsMacros
+      # Defines methods for adding attributes to a chef resource to describe
+      # Windows file security metadata.
+      #
+      # This module is meant to be used to extend a class (instead of
+      # `include`-ing). A class is automatically extended with this module when
+      # it includes WindowsSecurableAttributes.
+      # --
+      # TODO should this be separated into different files?
+      module WindowsMacros
+        # === rights_attribute
+        # "meta-method" for dynamically creating rights attributes on resources.
+        #
+        # Multiple rights attributes can be declared. This enables resources to
+        # have multiple rights attributes with separate runtime states.
+        #
+        # For example, +Chef::Resource::RemoteDirectory+ supports different
+        # rights on the directories and files by declaring separate rights
+        # attributes for each (rights and files_rights).
+        #
+        # ==== User Level API
+        # Given a resource that calls
+        #
+        #   rights_attribute(:rights)
+        #
+        # Then the resource DSL could be used like this:
         #
         #   rights :read, ["Administrators","Everyone"]
         #   rights :deny, "Pinky"
         #   rights :full_control, "Users", :applies_to_children => true
         #   rights :write, "John Keiser", :applies_to_children => :containers_only, :applies_to_self => false, :one_level_deep => true
         #
-        # should also also allow multiple right declarations
-        # in a single resource block as the data will be merged
-        # into a single internal hash
+        # ==== Internal Data Structure
+        # rights attributes support multiple right declarations
+        # in a single resource block--the data will be merged
+        # into a single internal hash.
+        #
+        # The internal representation is a hash with the following keys:
+        #
+        # * `:permissions`: Integer of Windows permissions flags, 1..2^32
+        # or one of `[:full_control, :modify, :read_execute, :read, :write]`
+        # * `:principals`:  String or Array of Strings represnting usernames on
+        # the system.
+        # * `:applies_to_children` (optional): Boolean
+        # * `:applies_to_self` (optional): Boolean
+        # * `:one_level_deep` (optional): Boolean
         #
-        # This method 'creates' rights attributes..this allows us to have
-        # multiple instances of the attribute with separate runtime states.
-        # See +Chef::Resource::RemoteDirectory+ for example usage (rights and files_rights)
-        def self.rights_attribute(name)
+        def rights_attribute(name)
+
+          # equivalent to something like:
+          # def rights(permissions=nil, principals=nil, args_hash=nil)
           define_method(name) do |*args|
             # Ruby 1.8 compat: default the arguments
             permissions = args.length >= 1 ? args[0] : nil
@@ -85,12 +119,12 @@ class Chef
             raise ArgumentError.new("wrong number of arguments (#{args.length} for 3)") if args.length >= 4
 
             rights = self.instance_variable_get("@#{name.to_s}".to_sym)
-            unless permissions == nil
+            unless permissions.nil?
               input = {
                 :permissions => permissions,
                 :principals => principals
               }
-              input.merge!(args_hash) if args_hash != nil
+              input.merge!(args_hash) unless args_hash.nil?
 
               validations = {:permissions => { :required => true },
                              :principals => { :required => true, :kind_of => [String, Array] },
@@ -134,10 +168,13 @@ class Chef
             )
           end
         end
+      end
+
+      #==WindowsSecurableAttributes
+      # Defines #inherits to describe Windows file security ACLs on the
+      # including class
+      module WindowsSecurableAttributes
 
-        # create a default 'rights' attribute
-        rights_attribute(:rights)
-        rights_attribute(:deny_rights)
 
         def inherits(arg=nil)
           set_or_return(
@@ -147,6 +184,22 @@ class Chef
           )
         end
       end
+
+      if RUBY_PLATFORM =~ /mswin|mingw|windows/
+        include WindowsSecurableAttributes
+      end
+
+      # Callback that fires when included; will extend the including class
+      # with WindowsMacros and define #rights and #deny_rights on it.
+      def self.included(including_class)
+        if RUBY_PLATFORM =~ /mswin|mingw|windows/
+          including_class.extend(WindowsMacros)
+          # create a default 'rights' attribute
+          including_class.rights_attribute(:rights)
+          including_class.rights_attribute(:deny_rights)
+        end
+      end
+
     end
   end
 end
diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb
index 4eaa509..f0c2ba2 100644
--- a/lib/chef/mixin/shell_out.rb
+++ b/lib/chef/mixin/shell_out.rb
@@ -15,13 +15,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# chef/shell_out has been deprecated in favor of mixlib/shellout
+# chef/shell_out is still required here to ensure backward compatibility
 require 'chef/shell_out'
+
+require 'mixlib/shellout'
 require 'chef/config'
 
 class Chef
   module Mixin
     module ShellOut
 
+      # shell_out! runs a command on the system and will raise an error if the command fails, which is what you want
+      # for debugging, shell_out and shell_out! both will display command output to the tty when the log level is debug
+      # Generally speaking, 'extend Chef::Mixin::ShellOut' in your recipes and include 'Chef::Mixin::ShellOut' in your LWRPs
+      # You can also call Mixlib::Shellout.new directly, but you lose all of the above functionality
+
       def shell_out(*command_args)
         cmd = Mixlib::ShellOut.new(*run_command_compatible_options(command_args))
         if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.debug?
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
index 78148d2..ae23336 100644
--- a/lib/chef/mixin/template.rb
+++ b/lib/chef/mixin/template.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -22,53 +22,180 @@ require 'erubis'
 class Chef
   module Mixin
     module Template
-      
+
+      # A compatibility wrapper around IO.binread so it works on Ruby 1.8.7.
+      # --
+      # Used in the TemplateContext class, but that method namespace is shared
+      # with user code, so we want to avoid adding methods there when possible.
+      def self.binread(file)
+        if IO.respond_to?(:binread)
+          IO.binread(file)
+        else
+          File.open(file, "rb") {|f| f.read }
+        end
+      end
+
+      # == ChefContext
+      # ChefContext was previously used to mix behavior into Erubis::Context so
+      # that it would be available to templates. This behavior has now moved to
+      # TemplateContext, but this module is still mixed in to the
+      # TemplateContext class so that any user code that modified ChefContext
+      # will continue to work correctly.
       module ChefContext
+      end
+
+      # == TemplateContext
+      # TemplateContext is the base context class for all templates in Chef. It
+      # defines user-facing extensions to the base Erubis::Context to provide
+      # enhanced features. Individual instances of TemplateContext can be
+      # extended to add logic to a specific template.
+      #
+      class TemplateContext < Erubis::Context
+
+        include ChefContext
+
+        attr_reader :_extension_modules
+
+        def initialize(variables)
+          super
+          @_extension_modules = []
+        end
+
+        ###
+        # USER FACING API
+        ###
+
+        # Returns the current node object, or raises an error if it's not set.
+        # Provides API consistency, allowing users to reference the node object
+        # by the bare `node` everywhere.
         def node
           return @node if @node
           raise "Could not find a value for node. If you are explicitly setting variables in a template, " +
                 "include a node variable if you plan to use it."
         end
-      end
-      
-      ::Erubis::Context.send(:include, ChefContext)
-      
-      # Render a template with Erubis.  Takes a template as a string, and a 
-      # context hash.  
-      def render_template(template, context)
-        begin
-          eruby = Erubis::Eruby.new(template)
-          output = eruby.evaluate(context)
-        rescue Object => e
-          raise TemplateError.new(e, template, context)
+
+        #
+        # Takes the name of the partial, plus a hash of options. Returns a
+        # string that contains the result of the evaluation of the partial.
+        #
+        # All variables from the parent template will be propagated down to
+        # the partial, unless you pass the +variables+ option (see below).
+        #
+        # Valid options are:
+        #
+        # :local:: If true then the partial name will be interpreted as the
+        #          path to a file on the local filesystem; if false (the
+        #          default) it will be looked up in the cookbook according to
+        #          the normal rules for templates.
+        # :source:: If specified then the partial will be looked up with this
+        #           name or path (according to the +local+ option) instead of
+        #           +partial_name+.
+        # :cookbook:: Search for the partial in the provided cookbook instead
+        #             of the cookbook that contains the top-level template.
+        # :variables:: A Hash of variable_name => value that will be made
+        #              available to the partial. If specified, none of the
+        #              variables from the master template will be, so if you
+        #              need them you will need to propagate them explicitly.
+        #
+        def render(partial_name, options = {})
+          raise "You cannot render partials in this context" unless @template_finder
+
+          partial_variables = options.delete(:variables) || _public_instance_variables
+          partial_context = self.class.new(partial_variables)
+          partial_context._extend_modules(@_extension_modules)
+
+          template_location = @template_finder.find(partial_name, options)
+          _render_template(Mixin::Template.binread(template_location), partial_context)
         end
-        Tempfile.open("chef-rendered-template") do |tempfile|
-          tempfile.print(output)
-          tempfile.close
-          yield tempfile
+
+        def render_template(template_location)
+          _render_template(Mixin::Template.binread(template_location), self)
+        end
+
+        def render_template_from_string(template)
+          _render_template(template, self)
+        end
+
+        ###
+        # INTERNAL PUBLIC API
+        ###
+
+        def _render_template(template, context)
+          begin
+            eruby = Erubis::Eruby.new(template)
+            output = eruby.evaluate(context)
+          rescue Object => e
+            raise TemplateError.new(e, template, context)
+          end
+
+          # CHEF-4399
+          # Erubis always emits unix line endings during template
+          # rendering. Chef used to convert line endings to the
+          # original line endings in the template. However this
+          # created problems in cases when cookbook developer is
+          # coding the cookbook on windows but using it on non-windows
+          # platforms.
+          # The safest solution is to make sure that native to the
+          # platform we are running on is used in order to minimize
+          # potential issues for the applications that will consume
+          # this template.
+
+          if Chef::Platform.windows?
+            output = output.gsub(/\r?\n/,"\r\n")
+          end
+
+          output
+        end
+
+        def _extend_modules(module_names)
+          module_names.each do |mod|
+            context_methods = [:node, :render, :render_template, :render_template_from_string]
+            context_methods.each do |core_method|
+              if mod.method_defined?(core_method) or mod.private_method_defined?(core_method)
+                Chef::Log.warn("Core template method `#{core_method}' overridden by extension module #{mod}")
+              end
+            end
+            extend(mod)
+            @_extension_modules << mod
+          end
+        end
+
+        # Collects instance variables set on the current object as a Hash
+        # suitable for creating a new TemplateContext. Instance variables that
+        # are only valid for this specific instance are omitted from the
+        # collection.
+        def _public_instance_variables
+          all_ivars = instance_variables
+          all_ivars.delete(:@_extension_modules)
+          all_ivars.inject({}) do |ivar_map, ivar_symbol_name|
+            value = instance_variable_get(ivar_symbol_name)
+            name_without_at = ivar_symbol_name.to_s[1..-1].to_sym
+            ivar_map[name_without_at] = value
+            ivar_map
+          end
         end
       end
-      
+
       class TemplateError < RuntimeError
         attr_reader :original_exception, :context
         SOURCE_CONTEXT_WINDOW = 2
-        
+
         def initialize(original_exception, template, context)
           @original_exception, @template, @context = original_exception, template, context
         end
-        
+
         def message
           @original_exception.message
         end
-        
+
         def line_number
           @line_number ||= $1.to_i if original_exception.backtrace.find {|line| line =~ /\(erubis\):(\d+)/ }
         end
-        
+
         def source_location
           "on line ##{line_number}"
         end
-        
+
         def source_listing
           @source_listing ||= begin
             lines = @template.split(/\n/)
@@ -89,7 +216,7 @@ class Chef
             output.join("\n")
           end
         end
-        
+
         def to_s
           "\n\n#{self.class} (#{message}) #{source_location}:\n\n" +
             "#{source_listing}\n\n  #{original_exception.backtrace.join("\n  ")}\n\n"
diff --git a/lib/chef/mixin/why_run.rb b/lib/chef/mixin/why_run.rb
new file mode 100644
index 0000000..d650e33
--- /dev/null
+++ b/lib/chef/mixin/why_run.rb
@@ -0,0 +1,332 @@
+#
+# Author:: Dan DeLeo ( <dan at opscode.com> )
+# Author:: Marc Paradise ( <marc 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.
+#
+
+class Chef
+  module Mixin
+    module WhyRun
+
+      # ==ConvergeActions
+      # ConvergeActions implements the logic for why run. A ConvergeActions
+      # object wraps a collection of actions, which consist of a descriptive
+      # string and a block/Proc. Actions are executed by calling #converge!
+      # When why_run mode is enabled, each action's description will be
+      # printed, but the block will not be called. Conversely, in normal mode,
+      # the block is called, but the message is not printed.
+      #
+      # In general, this class should be accessed through the API provided by
+      # Chef::Provider.
+      class ConvergeActions
+        attr_reader :actions
+
+        def initialize(resource, run_context, action)
+          @resource, @run_context = resource, run_context
+          @actions = []
+        end
+
+        def events
+          @run_context.events
+        end
+
+        # Adds an action to the list. +descriptions+ can either be an Array of
+        # Strings, or a single String describing the action; +block+ is a
+        # block/proc that implements the action.
+        def add_action(descriptions, &block)
+          @actions << [descriptions, block]
+          if !Chef::Config[:why_run]
+            block.call
+          end
+          events.resource_update_applied(@resource, @action, descriptions)
+        end
+
+        # True if there are no actions to execute.
+        def empty?
+          @actions.empty?
+        end
+      end
+
+      # == ResourceRequirements
+      # ResourceRequirements provides a framework for making assertions about
+      # the host system's state. It also provides a mechanism for making
+      # assumptions about what the system's state might have been when running
+      # in why run mode.
+      #
+      # For example, consider a recipe that consists of a package resource and
+      # a service resource. If the service's init script is installed by the
+      # package, and Chef is running in why run mode, then the service resource
+      # would fail when attempting to run `/etc/init.d/software-name status`.
+      # In order to provide a more useful approximation of what would happen in
+      # a real chef run, we want to instead assume that the service was created
+      # but isn't running. The logic would look like this:
+      #
+      #     # Hypothetical service provider demonstrating why run assumption logic.
+      #     # This isn't the actual API, it just shows the logic.
+      #     class HypotheticalServiceProvider < Chef::Provider
+      #
+      #       def load_current_resource
+      #         # Make sure we have the init script available:
+      #         if ::File.exist?("/etc/init.d/some-service"
+      #           # If the init script exists, proceed as normal:
+      #           status_cmd = shell_out("/etc/init.d/some-service status")
+      #           if status_cmd.success?
+      #             @current_resource.status(:running)
+      #           else
+      #             @current_resource.status(:stopped)
+      #           end
+      #         else
+      #           if whyrun_mode?
+      #             # If the init script is not available, and we're in why run mode,
+      #             # assume that some previous action would've created it:
+      #             log("warning: init script '/etc/init.d/some-service' is not available")
+      #             log("warning: assuming that the init script would have been created, assuming the state of 'some-service' is 'stopped'")
+      #             @current_resource.status(:stopped)
+      #           else
+      #             raise "expected init script /etc/init.d/some-service doesn't exist"
+      #           end
+      #         end
+      #       end
+      #
+      #     end
+      #
+      # In short, the code above does the following:
+      # * runs a test to determine if a requirement is met:
+      #   `::File.exist?("/etc/init.d/some-service"`
+      # * raises an error if the requirement is not met, and we're not in why
+      #   run mode.
+      # * if we *are* in why run mode, print a message explaining the
+      #   situation, and run some code that makes an assumption about what the
+      #   state of the system would be. In this case, we also skip the normal
+      #   `load_current_resource` logic
+      # * when the requirement *is* met, we run the normal `load_current_resource`
+      #   logic
+      #
+      # ResourceRequirements encapsulates the above logic in a more declarative API.
+      #
+      # === Examples
+      # Assertions and assumptions should be created through the WhyRun#assert
+      # method, which gets mixed in to providers. See that method's
+      # documentation for examples.
+      class ResourceRequirements
+
+        # Implements the logic for a single assertion/assumption. See the
+        # documentation for ResourceRequirements for full discussion.
+        class Assertion
+          class AssertionFailure < RuntimeError
+          end
+
+          def initialize
+            @block_action = false
+            @assertion_proc = nil
+            @failure_message = nil
+            @whyrun_message = nil
+            @resource_modifier = nil
+            @assertion_failed = false
+            @exception_type = AssertionFailure
+          end
+
+          # Defines the code block that determines if a requirement is met. The
+          # block should return a truthy value to indicate that the requirement
+          # is met, and a falsey value if the requirement is not met.
+          #   # in a provider:
+          #   assert(:some_action) do |a|
+          #     # This provider requires the file /tmp/foo to exist:
+          #     a.assertion { ::File.exist?("/tmp/foo") }
+          #   end
+          def assertion(&assertion_proc)
+            @assertion_proc = assertion_proc
+          end
+
+          # Defines the failure message, and optionally the Exception class to
+          # use when a requirement is not met. It works like `raise`:
+          #   # in a provider:
+          #   assert(:some_action) do |a|
+          #     # This example shows usage with 1 or 2 args by calling #failure_message twice.
+          #     # In practice you should only call this once per Assertion.
+          #
+          #     # Set the Exception class explicitly
+          #     a.failure_message(Chef::Exceptions::MissingRequiredFile, "File /tmp/foo doesn't exist")
+          #     # Fallback to the default error class (AssertionFailure)
+          #     a.failure_message("File /tmp/foo" doesn't exist")
+          #   end
+          def failure_message(*args)
+            case args.size
+            when 1
+              @failure_message = args[0]
+            when 2
+              @exception_type, @failure_message = args[0], args[1]
+            else
+              raise ArgumentError, "#{self.class}#failure_message takes 1 or 2 arguments, you gave #{args.inspect}"
+            end
+          end
+
+          # Defines a message and optionally provides a code block to execute
+          # when the requirement is not met and Chef is executing in why run
+          # mode
+          #
+          # If no failure_message is provided (above), then execution
+          # will be allowed to continue in both whyrun and non-whyrun
+          # mode
+          #
+          # With a service resource that requires /etc/init.d/service-name to exist:
+          #   # in a provider
+          #   assert(:start, :restart) do |a|
+          #     a.assertion { ::File.exist?("/etc/init.d/service-name") }
+          #     a.whyrun("Init script '/etc/init.d/service-name' doesn't exist, assuming a prior action would have created it.") do
+          #       # blindly assume that the service exists but is stopped in why run mode:
+          #       @new_resource.status(:stopped)
+          #     end
+          #   end
+          def whyrun(message, &resource_modifier)
+            @whyrun_message = message
+            @resource_modifier = resource_modifier
+          end
+
+          # Prevents associated actions from being invoked in whyrun mode.
+          # This will also stop further processing of assertions for a given action.
+          #
+          # An example from the template provider: if the source template doesn't exist
+          # we can't parse it in the action_create block of template - something that we do
+          # even in whyrun mode.  Because the source template may have been created in an earlier
+          # step, we still want to keep going in whyrun mode.
+          #
+          # assert(:create, :create_if_missing) do |a|
+          #   a.assertion { File::exists?(@new_resource.source) }
+          #   a.whyrun "Template source file does not exist, assuming it would have been created."
+          #   a.block_action!
+          # end
+          #
+          def block_action!
+            @block_action = true
+          end
+
+          def block_action?
+            @block_action
+          end
+
+          def assertion_failed?
+            @assertion_failed
+          end
+
+
+          # Runs the assertion/assumption logic. Will raise an Exception of the
+          # type specified in #failure_message (or AssertionFailure by default)
+          # if the requirement is not met and Chef is not running in why run
+          # mode. An exception will also be raised if running in why run mode
+          # and no why run message or block has been declared.
+          def run(action, events, resource)
+            if !@assertion_proc || !@assertion_proc.call
+              @assertion_failed = true
+              if Chef::Config[:why_run] && @whyrun_message
+                events.provider_requirement_failed(action, resource, @exception_type, @failure_message)
+                events.whyrun_assumption(action, resource, @whyrun_message) if @whyrun_message
+                @resource_modifier.call if @resource_modifier
+              else
+                if @failure_message
+                  events.provider_requirement_failed(action, resource, @exception_type, @failure_message)
+                  raise @exception_type, @failure_message
+                end
+              end
+            end
+          end
+        end
+
+        def initialize(resource, run_context)
+          @resource, @run_context = resource, run_context
+          @assertions = Hash.new {|h,k| h[k] = [] }
+          @blocked_actions = []
+        end
+
+        def events
+          @run_context.events
+        end
+
+        # Check to see if a given action is blocked by a failed assertion
+        #
+        # Takes the action name to be verified.
+        def action_blocked?(action)
+          @blocked_actions.include?(action)
+        end
+
+        # Define a new Assertion.
+        #
+        # Takes a list of action names for which the assertion should be made.
+        # ==== Examples:
+        # A File provider that requires the parent directory to exist:
+        #
+        #   assert(:create, :create_if_missing) do |a|
+        #     parent_dir = File.basename(@new_resource.path)
+        #     a.assertion { ::File.directory?(parent_dir) }
+        #     a.failure_message(Exceptions::ParentDirectoryDoesNotExist,
+        #                       "Can't create file #{@new_resource.path}: parent directory #{parent_dir} doesn't exist")
+        #     a.why_run("assuming parent directory #{parent_dir} would have been previously created"
+        #   end
+        #
+        # A service provider that requires the init script to exist:
+        #
+        #   assert(:start, :restart) do |a|
+        #     a.assertion { ::File.exist?(@new_resource.init_script) }
+        #     a.failure_message(Exceptions::MissingInitScript,
+        #                       "Can't check status of #{@new_resource}: init script #{@new_resource.init_script} is missing")
+        #     a.why_run("Assuming init script would have been created and service is stopped") do
+        #       @current_resource.status(:stopped)
+        #     end
+        #   end
+        #
+        # A File provider that will error out if you don't have permissions do
+        # delete the file, *even in why run mode*:
+        #
+        #   assert(:delete) do |a|
+        #     a.assertion { ::File.writable?(@new_resource.path) }
+        #     a.failure_message(Exceptions::InsufficientPrivileges,
+        #                       "You don't have sufficient privileges to delete #{@new_resource.path}")
+        #   end
+        #
+        # A Template provider that will prevent action execution but continue the run in
+        # whyrun mode if the template source is not available.
+        #   assert(:create, :create_if_missing) do |a|
+        #     a.assertion { File::exist?(@new_resource.source) }
+        #     a.failure_message Chef::Exceptions::TemplateError, "Template #{@new_resource.source} could not be found exist."
+        #     a.whyrun "Template source #{@new_resource.source} does not exist. Assuming it would have been created."
+        #     a.block_action!
+        #   end
+        #
+        #   assert(:delete) do |a|
+        #     a.assertion { ::File.writable?(@new_resource.path) }
+        #     a.failure_message(Exceptions::InsufficientPrivileges,
+        #                       "You don't have sufficient privileges to delete #{@new_resource.path}")
+        #   end
+        def assert(*actions)
+          assertion = Assertion.new
+          yield assertion
+          actions.each {|action| @assertions[action] << assertion }
+        end
+
+        # Run the assertion and assumption logic.
+        def run(action)
+          @assertions[action.to_sym].each do |a|
+            a.run(action, events, @resource)
+            if a.assertion_failed? and a.block_action?
+              @blocked_actions << action
+              return
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
new file mode 100644
index 0000000..c132786
--- /dev/null
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+require 'chef/exceptions'
+require 'win32/api' if Chef::Platform.windows?
+
+class Chef
+  module Mixin
+    module WindowsArchitectureHelper
+
+      def node_windows_architecture(node)
+        node[:kernel][:machine].to_sym
+      end
+
+      def wow64_architecture_override_required?(node, desired_architecture)
+        is_i386_windows_process? &&
+          node_windows_architecture(node) == :x86_64 &&
+          desired_architecture == :x86_64
+      end
+
+      def node_supports_windows_architecture?(node, desired_architecture)
+        assert_valid_windows_architecture!(desired_architecture)
+        return (node_windows_architecture(node) == :x86_64 ||
+                desired_architecture == :i386) ? true : false
+      end
+
+      def valid_windows_architecture?(architecture)
+        return (architecture == :x86_64) || (architecture == :i386)
+      end
+
+      def assert_valid_windows_architecture!(architecture)
+        if ! valid_windows_architecture?(architecture)
+          raise Chef::Exceptions::Win32ArchitectureIncorrect,
+          "The specified architecture was not valid. It must be one of :i386 or :x86_64"
+        end
+      end
+
+      def is_i386_windows_process?
+        Chef::Platform.windows? && 'X86'.casecmp(ENV['PROCESSOR_ARCHITECTURE']) == 0
+      end
+
+      def disable_wow64_file_redirection( node )
+        original_redirection_state = ['0'].pack('P')
+
+        if ( ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
+          win32_wow_64_disable_wow_64_fs_redirection =
+            ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32')
+
+          succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state)
+
+          if succeeded == 0
+            raise Win32APIError "Failed to disable Wow64 file redirection"
+          end
+
+        end
+
+        original_redirection_state
+      end
+
+      def restore_wow64_file_redirection( node, original_redirection_state )
+        if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
+          win32_wow_64_revert_wow_64_fs_redirection =
+            ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32')
+
+          succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state)
+
+          if succeeded == 0
+            raise Win32APIError "Failed to revert Wow64 file redirection"
+          end
+        end
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/mixin/xml_escape.rb b/lib/chef/mixin/xml_escape.rb
index dac2f0c..ceb45df 100644
--- a/lib/chef/mixin/xml_escape.rb
+++ b/lib/chef/mixin/xml_escape.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -18,26 +18,26 @@
 
 #--
 # Portions of this code are adapted from Sam Ruby's xchar.rb
-# http://intertwingly.net/stories/2005/09/28/xchar.rb 
+# http://intertwingly.net/stories/2005/09/28/xchar.rb
 #
 # Such code appears here under Sam's original MIT license, while portions of
 # this file are covered by the above Apache License.  For a completely MIT
 # licensed version, please see Sam's original.
 #
 # Thanks, Sam!
-# 
-# Copyright (c) 2005, Sam Ruby 
-# 
+#
+# Copyright (c) 2005, Sam Ruby
+#
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
 # in the Software without restriction, including without limitation the rights
 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 # copies of the Software, and to permit persons to whom the Software is
 # furnished to do so, subject to the following conditions:
-# 
+#
 # The above copyright notice and this permission notice shall be included in
 # all copies or substantial portions of the Software.
-# 
+#
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -99,7 +99,7 @@ class Chef
         }
 
         # http://www.w3.org/TR/REC-xml/#charsets
-        VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF), 
+        VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF),
           (0xE000..0xFFFD), (0x10000..0x10FFFF)]
 
         def xml_escape(unescaped_str)
@@ -118,7 +118,7 @@ class Chef
           char = PREDEFINED[char] || (char<128 ? char.chr : "&##{char};")
         end
       end
-      
+
       module FastXS
         extend self
 
diff --git a/lib/chef/mixins.rb b/lib/chef/mixins.rb
index e95acbd..17be162 100644
--- a/lib/chef/mixins.rb
+++ b/lib/chef/mixins.rb
@@ -1,5 +1,4 @@
 require 'chef/mixin/shell_out'
-require 'chef/mixin/check_helper'
 require 'chef/mixin/checksum'
 require 'chef/mixin/command'
 require 'chef/mixin/convert_to_class_name'
@@ -7,12 +6,9 @@ require 'chef/mixin/create_path'
 require 'chef/mixin/deep_merge'
 require 'chef/mixin/enforce_ownership_and_permissions'
 require 'chef/mixin/from_file'
-require 'chef/mixin/language'
-require 'chef/mixin/language_include_attribute'
-require 'chef/mixin/language_include_recipe'
 require 'chef/mixin/params_validate'
 require 'chef/mixin/path_sanity'
-require 'chef/mixin/recipe_definition_dsl_core'
 require 'chef/mixin/template'
 require 'chef/mixin/securable'
 require 'chef/mixin/xml_escape'
+
diff --git a/lib/chef/monkey_patches/dir.rb b/lib/chef/monkey_patches/dir.rb
deleted file mode 100644
index c86edcf..0000000
--- a/lib/chef/monkey_patches/dir.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.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.
-#
-
-if RUBY_VERSION < "1.8.6" || RUBY_PLATFORM =~ /mswin|mingw32|windows/
-  class Dir 
-    class << self 
-      alias_method :glob_, :glob 
-      # Adds a Dir.glob to Ruby 1.8.5, for compat
-      def glob(pattern, flags=0)
-        raise ArgumentError unless (
-          !pattern.nil? and (
-            pattern.is_a? Array and !pattern.empty?
-          ) or pattern.is_a? String
-        )
-        pattern.gsub!(/\\/, "/") if RUBY_PLATFORM =~ /mswin|mingw32|windows/
-        [pattern].flatten.inject([]) { |r, p| r + glob_(p, flags) }
-      end
-      alias_method :[], :glob 
-    end 
-  end 
-end 
diff --git a/lib/chef/monkey_patches/file.rb b/lib/chef/monkey_patches/file.rb
new file mode 100644
index 0000000..acc0ca7
--- /dev/null
+++ b/lib/chef/monkey_patches/file.rb
@@ -0,0 +1,26 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+if !File.respond_to?(:realpath)
+  require 'pathname'
+  class File
+    def self.realpath(path)
+      Pathname.new(path).realpath.to_s
+    end
+  end
+end
diff --git a/lib/chef/monkey_patches/fileutils.rb b/lib/chef/monkey_patches/fileutils.rb
new file mode 100644
index 0000000..f18bead
--- /dev/null
+++ b/lib/chef/monkey_patches/fileutils.rb
@@ -0,0 +1,65 @@
+#
+# Author:: Stephen Delano (<stephen 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.
+#
+
+# == FileUtils::Entry_ (Patch)
+# On Ruby 1.9.3 and earlier, FileUtils.cp_r(foo, bar, :preserve => true) fails
+# when attempting to copy a directory containing symlinks. This has been
+# patched in the trunk of Ruby, and this is a monkey patch of the offending
+# code.
+
+unless RUBY_VERSION =~ /^2/
+  require 'fileutils'
+
+  class FileUtils::Entry_
+    def copy_metadata(path)
+      st = lstat()
+      if !st.symlink?
+        File.utime st.atime, st.mtime, path
+      end
+      begin
+        if st.symlink?
+          begin
+            File.lchown st.uid, st.gid, path
+          rescue NotImplementedError
+          end
+        else
+          File.chown st.uid, st.gid, path
+        end
+      rescue Errno::EPERM
+        # clear setuid/setgid
+        if st.symlink?
+          begin
+            File.lchmod st.mode & 01777, path
+          rescue NotImplementedError
+          end
+        else
+          File.chmod st.mode & 01777, path
+        end
+      else
+        if st.symlink?
+          begin
+            File.lchmod st.mode, path
+          rescue NotImplementedError
+          end
+        else
+          File.chmod st.mode, path
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/monkey_patches/moneta.rb b/lib/chef/monkey_patches/moneta.rb
deleted file mode 100644
index 1c2895d..0000000
--- a/lib/chef/monkey_patches/moneta.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-#
-# Author:: Seth Chisamore (<schisamo at opscode.com>)
-# Copyright:: Copyright (c) 2011 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.
-#
-
-# ensure data is written and read in binary mode
-# stops "dump format error for symbol(0x75)" errors
-module Moneta
-  class BasicFile
-
-    def store(key, value, options = {})
-      ensure_directory_created(::File.dirname(path(key)))
-      ::File.open(path(key), "wb") do |file|
-        if @expires
-          data = {:value => value}
-          if options[:expires_in]
-            data[:expires_at] = Time.now + options[:expires_in]
-          end
-          contents = Marshal.dump(data)
-        else
-          contents = Marshal.dump(value)
-        end
-        file.puts(contents)
-      end
-    end
-
-    def raw_get(key)
-      if ::File.respond_to?(:binread)
-        data = ::File.binread(path(key))
-      else
-        data = ::File.open(path(key),"rb") { |f| f.read }
-      end
-      Marshal.load(data)
-    end
-
-  end
-end
diff --git a/lib/chef/monkey_patches/net-ssh-multi.rb b/lib/chef/monkey_patches/net-ssh-multi.rb
new file mode 100644
index 0000000..0f4dd66
--- /dev/null
+++ b/lib/chef/monkey_patches/net-ssh-multi.rb
@@ -0,0 +1,140 @@
+#
+# Author:: Serdar Sutay (<serdar 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.
+#
+
+# == net-ssh-multi gem patch for concurrency
+# net-ssh-multi gem has 2 bugs associated with the use of
+# :concurrent_connections option.
+# 1-) There is a race condition while fetching the next_session when
+# :concurrent_connections are set. @open_connections is being
+# incremented by the connection thread and sometimes
+# realize_pending_connections!() method can create more than required
+# connection threads before the @open_connections is set by the
+# previously created threads.
+# 2-) When :concurrent_connections is set, server classes are setup
+# with PendingConnection objects that always return true to busy?
+# calls. If a connection fails when :concurrent_connections is set,
+# server ends up returning true to all busy? calls since the session
+# object is not replaced. Due to this, main event loop (process()
+# function) never gets terminated.
+#
+# See: https://github.com/net-ssh/net-ssh-multi/pull/4
+
+require 'net/ssh/multi/version'
+
+if Net::SSH::Multi::Version::STRING == "1.1.0" || Net::SSH::Multi::Version::STRING == "1.2.0"
+
+  require 'net/ssh/multi'
+
+  module Net
+    module SSH
+      module Multi
+        class Server
+
+          # Make sure that server returns false if the ssh connection
+          # has failed.
+          def busy?(include_invisible=false)
+            !failed? && session && session.busy?(include_invisible)
+          end
+
+        end
+
+        class Session
+          def next_session(server, force=false) #:nodoc:
+            # don't retry a failed attempt
+            return nil if server.failed?
+
+            @session_mutex.synchronize do
+              if !force && concurrent_connections && concurrent_connections <= open_connections
+                connection = PendingConnection.new(server)
+                @pending_sessions << connection
+                return connection
+              end
+
+              # ===== PATCH START
+              # Only increment the open_connections count if the connection
+              # is not being forced. Incase of a force, it will already be
+              # incremented.
+              if !force
+                @open_connections += 1
+              end
+              # ===== PATCH END
+            end
+
+            begin
+              server.new_session
+
+              # I don't understand why this should be necessary--StandardError is a
+              # subclass of Exception, after all--but without explicitly rescuing
+              # StandardError, things like Errno::* and SocketError don't get caught
+              # here!
+            rescue Exception, StandardError => e
+              server.fail!
+              @session_mutex.synchronize { @open_connections -= 1 }
+
+              case on_error
+              when :ignore then
+                # do nothing
+              when :warn then
+                warn("error connecting to #{server}: #{e.class} (#{e.message})")
+              when Proc then
+                go = catch(:go) { on_error.call(server); nil }
+                case go
+                when nil, :ignore then # nothing
+                when :retry then retry
+                when :raise then raise
+                else warn "unknown 'go' command: #{go.inspect}"
+                end
+              else
+                raise
+              end
+
+              return nil
+            end
+          end
+
+          def realize_pending_connections! #:nodoc:
+            return unless concurrent_connections
+
+            server_list.each do |server|
+              server.close if !server.busy?(true)
+              server.update_session!
+            end
+
+            @connect_threads.delete_if { |t| !t.alive? }
+
+            count = concurrent_connections ? (concurrent_connections - open_connections) : @pending_sessions.length
+            count.times do
+              session = @pending_sessions.pop or break
+              # ===== PATCH START
+              # Increment the open_connections count here to prevent
+              # creation of connection thread again before that is
+              # incremented by the thread.
+              @session_mutex.synchronize { @open_connections += 1 }
+              # ===== PATCH END
+              @connect_threads << Thread.new do
+                session.replace_with(next_session(session.server, true))
+              end
+            end
+          end
+
+        end
+      end
+    end
+  end
+
+end
diff --git a/lib/chef/monkey_patches/net_http.rb b/lib/chef/monkey_patches/net_http.rb
new file mode 100644
index 0000000..ad4ba95
--- /dev/null
+++ b/lib/chef/monkey_patches/net_http.rb
@@ -0,0 +1,22 @@
+
+# Module gets mixed in to Net::HTTP exception classes so we can attach our
+# RESTRequest object to them and get the request parameters back out later.
+module ChefNetHTTPExceptionExtensions
+  attr_accessor :chef_rest_request
+end
+
+require 'net/http'
+module Net
+  class HTTPError
+    include ChefNetHTTPExceptionExtensions
+  end
+  class HTTPRetriableError
+    include ChefNetHTTPExceptionExtensions
+  end
+  class HTTPServerException
+    include ChefNetHTTPExceptionExtensions
+  end
+  class HTTPFatalError
+    include ChefNetHTTPExceptionExtensions
+  end
+end
diff --git a/lib/chef/monkey_patches/numeric.rb b/lib/chef/monkey_patches/numeric.rb
index 1f5ff14..f4612fd 100644
--- a/lib/chef/monkey_patches/numeric.rb
+++ b/lib/chef/monkey_patches/numeric.rb
@@ -8,7 +8,7 @@ end
 
 # String elements referenced with [] <= 1.8.6 return a Fixnum. Cheat to allow
 # for the simpler "test"[2].ord construct
-class Numeric 
+class Numeric
   def ord
     return self
   end
diff --git a/lib/chef/monkey_patches/regexp.rb b/lib/chef/monkey_patches/regexp.rb
index 9304209..8a7ee77 100644
--- a/lib/chef/monkey_patches/regexp.rb
+++ b/lib/chef/monkey_patches/regexp.rb
@@ -1,5 +1,5 @@
 # Copyright (c) 2009 Marc-Andre Lafortune
-# 
+#
 # Permission is hereby granted, free of charge, to any person obtaining
 # a copy of this software and associated documentation files (the
 # "Software"), to deal in the Software without restriction, including
@@ -7,10 +7,10 @@
 # distribute, sublicense, and/or sell copies of the Software, and to
 # permit persons to whom the Software is furnished to do so, subject to
 # the following conditions:
-# 
+#
 # The above copyright notice and this permission notice shall be
 # included in all copies or substantial portions of the Software.
-# 
+#
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -31,4 +31,4 @@ class Regexp
       end
     end
   end
-end
\ No newline at end of file
+end
diff --git a/lib/chef/monkey_patches/securerandom.rb b/lib/chef/monkey_patches/securerandom.rb
new file mode 100644
index 0000000..7a41a1d
--- /dev/null
+++ b/lib/chef/monkey_patches/securerandom.rb
@@ -0,0 +1,44 @@
+#
+# Author:: James Casey <james at opscode.com>
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# == SecureRandom (Patch)
+# On ruby 1.9, SecureRandom has a uuid method which generates a v4 UUID.  The
+# backport of SecureRandom to 1.8.7 is missing this method
+
+require 'securerandom'
+
+module SecureRandom
+  unless respond_to?(:uuid)
+    # SecureRandom.uuid generates a v4 random UUID (Universally Unique IDentifier).
+    #
+    #   p SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
+    #   p SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
+    #   p SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
+    #
+    # The version 4 UUID is purely random (except the version).
+    # It doesn't contain meaningful information such as MAC address, time, etc.
+    #
+    # See RFC 4122 for details of UUID.
+    def self.uuid
+      ary = self.random_bytes(16).unpack("NnnnnN")
+      ary[2] = (ary[2] & 0x0fff) | 0x4000
+      ary[3] = (ary[3] & 0x3fff) | 0x8000
+      "%08x-%04x-%04x-%04x-%04x%08x" % ary
+    end
+  end
+end
diff --git a/lib/chef/monkey_patches/string.rb b/lib/chef/monkey_patches/string.rb
index c77c5c8..f91e27d 100644
--- a/lib/chef/monkey_patches/string.rb
+++ b/lib/chef/monkey_patches/string.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -39,7 +39,7 @@ class String
   end
 end
 
-# <= 1.8.6 needs some ord! 
+# <= 1.8.6 needs some ord!
 class String
   unless method_defined?(:ord)
     def ord
diff --git a/lib/chef/monkey_patches/tempfile.rb b/lib/chef/monkey_patches/tempfile.rb
index 3135fb1..b9179f1 100644
--- a/lib/chef/monkey_patches/tempfile.rb
+++ b/lib/chef/monkey_patches/tempfile.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/monologger.rb b/lib/chef/monologger.rb
new file mode 100644
index 0000000..fed6051
--- /dev/null
+++ b/lib/chef/monologger.rb
@@ -0,0 +1,93 @@
+require 'logger'
+
+require 'pp'
+
+#== MonoLogger
+# A subclass of Ruby's stdlib Logger with all the mutex and logrotation stuff
+# ripped out.
+class MonoLogger < Logger
+
+  #
+  # === Synopsis
+  #
+  #   Logger.new(name, shift_age = 7, shift_size = 1048576)
+  #   Logger.new(name, shift_age = 'weekly')
+  #
+  # === Args
+  #
+  # +logdev+::
+  #   The log device.  This is a filename (String) or IO object (typically
+  #   +STDOUT+, +STDERR+, or an open file).
+  # +shift_age+::
+  #   Number of old log files to keep, *or* frequency of rotation (+daily+,
+  #   +weekly+ or +monthly+).
+  # +shift_size+::
+  #   Maximum logfile size (only applies when +shift_age+ is a number).
+  #
+  # === Description
+  #
+  # Create an instance.
+  #
+  def initialize(logdev)
+    @progname = nil
+    @level = DEBUG
+    @default_formatter = Formatter.new
+    @formatter = nil
+    @logdev = nil
+    if logdev
+      @logdev = LocklessLogDevice.new(logdev)
+    end
+  end
+
+
+  class LocklessLogDevice < LogDevice
+
+    def initialize(log = nil)
+      @dev = @filename = @shift_age = @shift_size = nil
+      if log.respond_to?(:write) and log.respond_to?(:close)
+        @dev = log
+      else
+        @dev = open_logfile(log)
+        @dev.sync = true
+        @filename = log
+      end
+    end
+
+    def write(message)
+      @dev.write(message)
+    rescue Exception => ignored
+      warn("log writing failed. #{ignored}")
+    end
+
+    def close
+      @dev.close rescue nil
+    end
+
+  private
+
+    def open_logfile(filename)
+      if (FileTest.exist?(filename))
+        open(filename, (File::WRONLY | File::APPEND))
+      else
+        create_logfile(filename)
+      end
+    end
+
+    def create_logfile(filename)
+      logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
+      logdev.sync = true
+      add_log_header(logdev)
+      logdev
+    end
+
+    def add_log_header(file)
+      file.write(
+        "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
+      )
+    end
+
+  end
+
+
+end
+
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 9486f67..007bd3c 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -21,19 +21,16 @@
 
 require 'forwardable'
 require 'chef/config'
-require 'chef/cookbook/cookbook_collection'
 require 'chef/nil_argument'
-require 'chef/mixin/check_helper'
 require 'chef/mixin/params_validate'
 require 'chef/mixin/from_file'
-require 'chef/mixin/language_include_attribute'
 require 'chef/mixin/deep_merge'
+require 'chef/dsl/include_attribute'
+require 'chef/dsl/platform_introspection'
 require 'chef/environment'
-require 'chef/couchdb'
 require 'chef/rest'
 require 'chef/run_list'
 require 'chef/node/attribute'
-require 'chef/index_queue'
 require 'chef/mash'
 require 'chef/json_compat'
 require 'chef/search/query'
@@ -43,144 +40,34 @@ class Chef
 
     extend Forwardable
 
-    def_delegators :construct_attributes, :keys, :each_key, :each_value, :key?, :has_key?
+    def_delegators :attributes, :keys, :each_key, :each_value, :key?, :has_key?
 
-    attr_accessor :recipe_list, :couchdb, :couchdb_rev, :run_state, :run_list
-    attr_accessor :override_attrs, :default_attrs, :normal_attrs, :automatic_attrs
-    attr_reader :couchdb_id
+    attr_accessor :recipe_list, :run_state, :run_list
 
-    # TODO: 5/18/2010 cw/timh. cookbook_collection should be removed
-    # from here and for any place it's needed, it should be accessed
-    # through a Chef::RunContext
-    attr_accessor :cookbook_collection
+    # RunContext will set itself as run_context via this setter when
+    # initialized. This is needed so DSL::IncludeAttribute (in particular,
+    # #include_recipe) can access the run_context to determine if an attributes
+    # file has been seen yet.
+    #--
+    # TODO: This is a pretty ugly way to solve that problem.
+    attr_accessor :run_context
 
-    include Chef::Mixin::CheckHelper
     include Chef::Mixin::FromFile
+    include Chef::DSL::IncludeAttribute
+    include Chef::DSL::PlatformIntrospection
+
     include Chef::Mixin::ParamsValidate
-    include Chef::Mixin::LanguageIncludeAttribute
-    include Chef::IndexQueue::Indexable
-
-    DESIGN_DOCUMENT = {
-      "version" => 11,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "node") {
-              emit(doc.name, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "node") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        },
-        "status" => {
-          "map" => <<-EOJS
-            function(doc) {
-              if (doc.chef_type == "node") {
-                var to_emit = { "name": doc.name, "chef_environment": doc.chef_environment };
-                if (doc["attributes"]["fqdn"]) {
-                  to_emit["fqdn"] = doc["attributes"]["fqdn"];
-                } else {
-                  to_emit["fqdn"] = "Undefined";
-                }
-                if (doc["attributes"]["ipaddress"]) {
-                  to_emit["ipaddress"] = doc["attributes"]["ipaddress"];
-                } else {
-                  to_emit["ipaddress"] = "Undefined";
-                }
-                if (doc["attributes"]["ohai_time"]) {
-                  to_emit["ohai_time"] = doc["attributes"]["ohai_time"];
-                } else {
-                  to_emit["ohai_time"] = "Undefined";
-                }
-                if (doc["attributes"]["uptime"]) {
-                  to_emit["uptime"] = doc["attributes"]["uptime"];
-                } else {
-                  to_emit["uptime"] = "Undefined";
-                }
-                if (doc["attributes"]["platform"]) {
-                  to_emit["platform"] = doc["attributes"]["platform"];
-                } else {
-                  to_emit["platform"] = "Undefined";
-                }
-                if (doc["attributes"]["platform_version"]) {
-                  to_emit["platform_version"] = doc["attributes"]["platform_version"];
-                } else {
-                  to_emit["platform_version"] = "Undefined";
-                }
-                if (doc["run_list"]) {
-                  to_emit["run_list"] = doc["run_list"];
-                } else {
-                  to_emit["run_list"] = "Undefined";
-                }
-                emit(doc.name, to_emit);
-              }
-            }
-          EOJS
-        },
-        "by_run_list" => {
-          "map" => <<-EOJS
-            function(doc) {
-              if (doc.chef_type == "node") {
-                if (doc['run_list']) {
-                  for (var i=0; i < doc.run_list.length; i++) {
-                    emit(doc['run_list'][i], doc.name);
-                  }
-                }
-              }
-            }
-          EOJS
-        },
-        "by_environment" => {
-          "map" => <<-EOJS
-            function(doc) {
-              if (doc.chef_type == "node") {
-                var env = (doc['chef_environment'] == null ? "_default" : doc['chef_environment']);
-                emit(env, doc.name);
-              }
-            }
-          EOJS
-        }
-      },
-    }
 
     # Create a new Chef::Node object.
-    def initialize(couchdb=nil)
+    def initialize
       @name = nil
 
       @chef_environment = '_default'
-      @normal_attrs = Mash.new
-      @override_attrs = Mash.new
-      @default_attrs = Mash.new
-      @automatic_attrs = Mash.new
       @run_list = Chef::RunList.new
 
-      @couchdb_rev = nil
-      @couchdb_id = nil
-      @couchdb = couchdb || Chef::CouchDB.new
-
-      @run_state = {
-        :template_cache => Hash.new,
-        :seen_recipes => Hash.new,
-        :seen_attributes => Hash.new
-      }
-      # TODO: 5/20/2010 need this here as long as other objects try to access
-      # the cookbook collection via Node, otherwise get NoMethodError on nil.
-      @cookbook_collection = CookbookCollection.new
-    end
+      @attributes = Chef::Node::Attribute.new({}, {}, {}, {})
 
-    def couchdb_id=(value)
-      @couchdb_id = value
-      @index_id = value
+      @run_state = {}
     end
 
     # Used by DSL
@@ -192,24 +79,6 @@ class Chef
       Chef::REST.new(Chef::Config[:chef_server_url])
     end
 
-    # Find a recipe for this Chef::Node by fqdn.  Will search first for
-    # Chef::Config["node_path"]/fqdn.rb, then hostname.rb, then default.rb.
-    #
-    # Returns a new Chef::Node object.
-    #
-    # Raises an ArgumentError if it cannot find the node.
-    def find_file(fqdn)
-      host_parts = fqdn.split(".")
-      hostname = host_parts[0]
-
-      [fqdn, hostname, "default"].each { |fname|
-       node_file = File.join(Chef::Config[:node_path], "#{fname.to_s}.rb")
-       return self.from_file(node_file) if File.exists?(node_file)
-     }
-
-      raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!"
-    end
-
     # Set the name of this Node, or return the current name.
     def name(arg=nil)
       if arg != nil
@@ -233,91 +102,114 @@ class Chef
       )
     end
 
-    # Used by the DSL
-    def attribute
-      construct_attributes
+    def chef_environment=(environment)
+      chef_environment(environment)
     end
 
-    def construct_attributes
-      Chef::Node::Attribute.new(normal_attrs, default_attrs, override_attrs, automatic_attrs)
-    end
+    alias :environment :chef_environment
 
-    def attribute=(value)
-      self.normal_attrs = value
+    def attributes
+      @attributes
     end
 
+    alias :attribute :attributes
+    alias :construct_attributes :attributes
+
     # Return an attribute of this node.  Returns nil if the attribute is not found.
     def [](attrib)
-      construct_attributes[attrib]
-    end
-
-    # Set an attribute of this node
-    def []=(attrib, value)
-      construct_attributes[attrib] = value
+      attributes[attrib]
     end
 
-    def store(attrib, value)
-      self[attrib] = value
-    end
-
-    # Set a normal attribute of this node, but auto-vivifiy any Mashes that
+    # Set a normal attribute of this node, but auto-vivify any Mashes that
     # might be missing
     def normal
-      attrs = construct_attributes
-      attrs.set_type = :normal
-      attrs.auto_vivifiy_on_read = true
-      attrs
+      attributes.set_unless_value_present = false
+      attributes.normal
     end
 
     alias_method :set, :normal
 
-    # Set a normal attribute of this node, auto-vivifiying any mashes that are
+    # Set a normal attribute of this node, auto-vivifying any mashes that are
     # missing, but if the final value already exists, don't set it
     def normal_unless
-      attrs = construct_attributes
-      attrs.set_type = :normal
-      attrs.auto_vivifiy_on_read = true
-      attrs.set_unless_value_present = true
-      attrs
+      attributes.set_unless_value_present = true
+      attributes.normal
     end
     alias_method :set_unless, :normal_unless
 
-    # Set a default of this node, but auto-vivifiy any Mashes that might
+    # Set a default of this node, but auto-vivify any Mashes that might
     # be missing
     def default
-      attrs = construct_attributes
-      attrs.set_type = :default
-      attrs.auto_vivifiy_on_read = true
-      attrs
+      attributes.set_unless_value_present = false
+      attributes.default
+    end
+
+    # Set a force default attribute. Intermediate mashes will be created by
+    # auto-vivify if necessary.
+    def default!
+      attributes.set_unless_value_present = false
+      attributes.default!
     end
 
-    # Set a default attribute of this node, auto-vivifiying any mashes that are
+    # Set a default attribute of this node, auto-vivifying any mashes that are
     # missing, but if the final value already exists, don't set it
     def default_unless
-      attrs = construct_attributes
-      attrs.set_type = :default
-      attrs.auto_vivifiy_on_read = true
-      attrs.set_unless_value_present = true
-      attrs
+      attributes.set_unless_value_present = true
+      attributes.default
     end
 
-    # Set an override attribute of this node, but auto-vivifiy any Mashes that
+    # Set an override attribute of this node, but auto-vivify any Mashes that
     # might be missing
     def override
-      attrs = construct_attributes
-      attrs.set_type = :override
-      attrs.auto_vivifiy_on_read = true
-      attrs
+      attributes.set_unless_value_present = false
+      attributes.override
     end
 
-    # Set an override attribute of this node, auto-vivifiying any mashes that
+    # Set a force override attribute. Intermediate mashes will be created by
+    # auto-vivify if needed.
+    def override!
+      attributes.set_unless_value_present = false
+      attributes.override!
+    end
+
+    # Set an override attribute of this node, auto-vivifying any mashes that
     # are missing, but if the final value already exists, don't set it
     def override_unless
-      attrs = construct_attributes
-      attrs.set_type = :override
-      attrs.auto_vivifiy_on_read = true
-      attrs.set_unless_value_present = true
-      attrs
+      attributes.set_unless_value_present = true
+      attributes.override
+    end
+
+
+    def override_attrs
+     attributes.override
+    end
+
+    def override_attrs=(new_values)
+      attributes.override = new_values
+    end
+
+    def default_attrs
+      attributes.default
+    end
+
+    def default_attrs=(new_values)
+      attributes.default = new_values
+    end
+
+    def normal_attrs
+      attributes.normal
+    end
+
+    def normal_attrs=(new_values)
+      attributes.normal = new_values
+    end
+
+    def automatic_attrs
+      attributes.automatic
+    end
+
+    def automatic_attrs=(new_values)
+      attributes.automatic = new_values
     end
 
     # Return true if this Node has a given attribute, false if not.  Takes either a symbol or
@@ -326,36 +218,33 @@ class Chef
     # Only works on the top level. Preferred way is to use the normal [] style
     # lookup and call attribute?()
     def attribute?(attrib)
-      construct_attributes.attribute?(attrib)
+      attributes.attribute?(attrib)
     end
 
     # Yield each key of the top level to the block.
     def each(&block)
-      construct_attributes.each(&block)
+      attributes.each(&block)
     end
 
     # Iterates over each attribute, passing the attribute and value to the block.
     def each_attribute(&block)
-      construct_attributes.each_attribute(&block)
+      attributes.each_attribute(&block)
     end
 
-    # Encouraged to only get used for lookups - while you can do sets from here, it's not as explicit
-    # as using the normal/default/override interface.
+    # Only works for attribute fetches, setting is no longer supported
     def method_missing(symbol, *args)
-      attrs = construct_attributes
-      attrs.send(symbol, *args)
+      attributes.send(symbol, *args)
     end
 
     # Returns true if this Node expects a given recipe, false if not.
     #
     # First, the run list is consulted to see whether the recipe is
     # explicitly included. If it's not there, it looks in
-    # run_state[:seen_recipes], which is populated by include_recipe
-    # statements in the DSL (and thus would not be in the run list).
+    # `node[:recipes]`, which is populated when the run_list is expanded
     #
     # NOTE: It's used by cookbook authors
     def recipe?(recipe_name)
-      run_list.include?(recipe_name) || run_state[:seen_recipes].include?(recipe_name)
+      run_list.include?(recipe_name) || Array(self[:recipes]).include?(recipe_name)
     end
 
     # Returns true if this Node expects a given role, false if not.
@@ -379,25 +268,33 @@ class Chef
       Chef::Log.debug("Extracting run list from JSON attributes provided on command line")
       consume_attributes(json_cli_attrs)
 
-      @automatic_attrs = ohai_data
+      self.automatic_attrs = ohai_data
 
       platform, version = Chef::Platform.find_platform_and_version(self)
       Chef::Log.debug("Platform is #{platform} version #{version}")
-      @automatic_attrs[:platform] = platform
-      @automatic_attrs[:platform_version] = version
+      self.automatic[:platform] = platform
+      self.automatic[:platform_version] = version
     end
 
     # Consumes the combined run_list and other attributes in +attrs+
     def consume_attributes(attrs)
       normal_attrs_to_merge = consume_run_list(attrs)
       Chef::Log.debug("Applying attributes from json file")
-      @normal_attrs = Chef::Mixin::DeepMerge.merge(@normal_attrs,normal_attrs_to_merge)
+      self.normal_attrs = Chef::Mixin::DeepMerge.merge(normal_attrs,normal_attrs_to_merge)
       self.tags # make sure they're defined
     end
 
     # Lazy initializer for tags attribute
     def tags
-      self[:tags] = [] unless attribute?(:tags)
+      normal[:tags] = [] unless attribute?(:tags)
+      normal[:tags]
+    end
+
+    def tag(*tags)
+      tags.each do |tag|
+        self.normal[:tags].push(tag.to_s) unless self[:tags].include? tag.to_s
+      end
+
       self[:tags]
     end
 
@@ -417,8 +314,8 @@ class Chef
     # Clear defaults and overrides, so that any deleted attributes
     # between runs are still gone.
     def reset_defaults_and_overrides
-      @default_attrs = Mash.new
-      @override_attrs = Mash.new
+      self.default.clear
+      self.override.clear
     end
 
     # Expands the node's run list and sets the default and override
@@ -435,12 +332,14 @@ class Chef
     # invalidated only when run_list is mutated?
     def expand!(data_source = 'server')
       expansion = run_list.expand(chef_environment, data_source)
-      raise Chef::Exceptions::MissingRole if expansion.errors?
+      raise Chef::Exceptions::MissingRole, expansion if expansion.errors?
 
       self.tags # make sure they're defined
 
-      @automatic_attrs[:recipes] = expansion.recipes
-      @automatic_attrs[:roles] = expansion.roles
+      automatic_attrs[:recipes] = expansion.recipes
+      automatic_attrs[:roles] = expansion.roles
+
+      apply_expansion_attributes(expansion)
 
       expansion
     end
@@ -448,13 +347,17 @@ class Chef
     # Apply the default and overrides attributes from the expansion
     # passed in, which came from roles.
     def apply_expansion_attributes(expansion)
-      load_chef_environment_object = (chef_environment == "_default" ? nil : Chef::Environment.load(chef_environment))
-      environment_default_attrs = load_chef_environment_object.nil? ? {} : load_chef_environment_object.default_attributes
-      default_before_roles = Chef::Mixin::DeepMerge.merge(default_attrs, environment_default_attrs)
-      @default_attrs = Chef::Mixin::DeepMerge.merge(default_before_roles, expansion.default_attrs)
-      environment_override_attrs = load_chef_environment_object.nil? ? {} : load_chef_environment_object.override_attributes
-      overrides_before_environments = Chef::Mixin::DeepMerge.merge(override_attrs, expansion.override_attrs)
-      @override_attrs = Chef::Mixin::DeepMerge.merge(overrides_before_environments, environment_override_attrs)
+      loaded_environment = if chef_environment == "_default"
+                             Chef::Environment.new.tap {|e| e.name("_default")}
+                           else
+                             Chef::Environment.load(chef_environment)
+                           end
+
+      attributes.env_default = loaded_environment.default_attributes
+      attributes.env_override = loaded_environment.override_attributes
+
+      attribute.role_default = expansion.default_attrs
+      attributes.role_override = expansion.override_attrs
     end
 
     # Transform the node to a Hash
@@ -478,36 +381,39 @@ class Chef
       display["chef_environment"] = chef_environment
       display["automatic"]        = automatic_attrs
       display["normal"]           = normal_attrs
-      display["default"]          = default_attrs
-      display["override"]         = override_attrs
+      display["default"]          = attributes.combined_default
+      display["override"]         = attributes.combined_override
       display["run_list"]         = run_list.run_list
       display
     end
 
     # Serialize this object as a hash
     def to_json(*a)
+      for_json.to_json(*a)
+    end
+
+    def for_json
       result = {
         "name" => name,
         "chef_environment" => chef_environment,
         'json_class' => self.class.name,
-        "automatic" => automatic_attrs,
-        "normal" => normal_attrs,
+        "automatic" => attributes.automatic,
+        "normal" => attributes.normal,
         "chef_type" => "node",
-        "default" => default_attrs,
-        "override" => override_attrs,
+        "default" => attributes.combined_default,
+        "override" => attributes.combined_override,
         #Render correctly for run_list items so malformed json does not result
         "run_list" => run_list.run_list.map { |item| item.to_s }
       }
-      result["_rev"] = couchdb_rev if couchdb_rev
-      result.to_json(*a)
+      result
     end
 
     def update_from!(o)
       run_list.reset!(o.run_list)
-      @automatic_attrs = o.automatic_attrs
-      @normal_attrs = o.normal_attrs
-      @override_attrs = o.override_attrs
-      @default_attrs = o.default_attrs
+      self.automatic_attrs = o.automatic_attrs
+      self.normal_attrs = o.normal_attrs
+      self.override_attrs = o.override_attrs
+      self.default_attrs = o.default_attrs
       chef_environment(o.chef_environment)
       self
     end
@@ -530,17 +436,9 @@ class Chef
       else
         o["recipes"].each { |r| node.recipes << r }
       end
-      node.couchdb_rev = o["_rev"] if o.has_key?("_rev")
-      node.couchdb_id = o["_id"] if o.has_key?("_id")
-      node.index_id = node.couchdb_id
       node
     end
 
-    def self.cdb_list_by_environment(environment, inflate=false, couchdb=nil)
-      rs = (couchdb || Chef::CouchDB.new).get_view("nodes", "by_environment", :include_docs => inflate, :startkey => environment, :endkey => environment)
-      inflate ? rs["rows"].collect {|r| r["doc"]} : rs["rows"].collect {|r| r["value"]}
-    end
-
     def self.list_by_environment(environment, inflate=false)
       if inflate
         response = Hash.new
@@ -551,14 +449,6 @@ class Chef
       end
     end
 
-    # List all the Chef::Node objects in the CouchDB.  If inflate is set to true, you will get
-    # the full list of all Nodes, fully inflated.
-    def self.cdb_list(inflate=false, couchdb=nil)
-      rs =(couchdb || Chef::CouchDB.new).list("nodes", inflate)
-      lookup = (inflate ? "value" : "key")
-      rs["rows"].collect { |r| r[lookup] }
-    end
-
     def self.list(inflate=false)
       if inflate
         response = Hash.new
@@ -571,19 +461,6 @@ class Chef
       end
     end
 
-    # Load a node by name from CouchDB
-    def self.cdb_load(name, couchdb=nil)
-      (couchdb || Chef::CouchDB.new).load("node", name)
-    end
-
-    def self.exists?(nodename, couchdb)
-      begin
-        self.cdb_load(nodename, couchdb)
-      rescue Chef::Exceptions::CouchDBNotFound
-        nil
-      end
-    end
-
     def self.find_or_create(node_name)
       load(node_name)
     rescue Net::HTTPServerException => e
@@ -604,27 +481,21 @@ class Chef
       Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes/#{name}")
     end
 
-    # Remove this node from the CouchDB
-    def cdb_destroy
-      couchdb.delete("node", name, couchdb_rev)
-    end
-
     # Remove this node via the REST API
     def destroy
       chef_server_rest.delete_rest("nodes/#{name}")
     end
 
-    # Save this node to the CouchDB
-    def cdb_save
-      @couchdb_rev = couchdb.store("node", name, self)["rev"]
-    end
-
     # Save this node via the REST API
     def save
       # Try PUT. If the node doesn't yet exist, PUT will return 404,
       # so then POST to create.
       begin
-        chef_server_rest.put_rest("nodes/#{name}", self)
+        if Chef::Config[:why_run]
+          Chef::Log.warn("In whyrun mode, so NOT performing node save.")
+        else
+          chef_server_rest.put_rest("nodes/#{name}", self)
+        end
       rescue Net::HTTPServerException => e
         raise e unless e.response.code == "404"
         chef_server_rest.post_rest("nodes", self)
@@ -638,41 +509,13 @@ class Chef
       self
     end
 
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("nodes", DESIGN_DOCUMENT)
-    end
-
     def to_s
       "node[#{name}]"
     end
 
-    # Load all attribute files for all cookbooks associated with this
-    # node.
-    def load_attributes
-      cookbook_collection.values.each do |cookbook|
-        cookbook.segment_filenames(:attributes).each do |segment_filename|
-          Chef::Log.debug("Node #{name} loading cookbook #{cookbook.name}'s attribute file #{segment_filename}")
-          self.from_file(segment_filename)
-        end
-      end
+    def <=>(other_node)
+      self.name <=> other_node.name
     end
 
-    # Used by DSL.
-    # Loads the attribute file specified by the short name of the
-    # file, e.g., loads specified cookbook's
-    #   "attributes/mailservers.rb"
-    # if passed
-    #   "mailservers"
-    def load_attribute_by_short_filename(name, src_cookbook_name)
-      src_cookbook = cookbook_collection[src_cookbook_name]
-      raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{src_cookbook_name} while loading attribute #{name}" unless src_cookbook
-
-      attribute_filename = src_cookbook.attribute_filenames_by_short_filename[name]
-      raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{name} in cookbook #{src_cookbook_name}" unless attribute_filename
-
-      self.from_file(attribute_filename)
-      self
-    end
   end
 end
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
index 66fc31c..66569cf 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -1,4 +1,4 @@
-#
+#--
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: AJ Christensen (<aj at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
@@ -17,471 +17,382 @@
 # limitations under the License.
 #
 
+require 'chef/node/immutable_collections'
+require 'chef/node/attribute_collections'
 require 'chef/mixin/deep_merge'
 require 'chef/log'
 
 class Chef
   class Node
-    class Attribute
-      HIDDEN_ATTRIBUES = [:@override, :@attribute, :@default, :@normal, :@automatic]
-
-      attr_accessor :normal,
-                    :default,
-                    :override,
-                    :automatic,
-                    :current_normal,
-                    :current_default,
-                    :current_override,
-                    :current_automatic,
-                    :auto_vivifiy_on_read,
-                    :set_unless_value_present,
-                    :set_type
+
+    # == Attribute
+    # Attribute implements a nested key-value (Hash) and flat collection
+    # (Array) data structure supporting multiple levels of precedence, such
+    # that a given key may have multiple values internally, but will only
+    # return the highest precedence value when reading.
+    class Attribute < Mash
+
+      include Immutablize
 
       include Enumerable
 
-      def initialize(normal, default, override, automatic, state=[])
-        @normal = normal
-        @current_normal = normal
-        @default = default
-        @current_default = default
-        @override = override
-        @current_override = override
-        @automatic = automatic
-        @current_automatic = automatic
-        @current_nesting_level = state
-        @auto_vivifiy_on_read = false
-        @set_unless_value_present = false
-        @set_type = nil
-        @has_been_read = false
-      end
-
-      def attribute
-        normal
-      end
-
-      def attribute=(value)
-        normal = value
-      end
-
-      def set_type_hash
-        case @set_type
-        when :normal
-          @normal
-        when :override
-          @override
-        when :default
-          @default
-        when :automatic
-          @automatic
-        end
-      end
-
-      # Reset our internal current_nesting_level to the top of every tree
-      def reset
-        @current_normal = @normal
-        @current_default = @default
-        @current_override = @override
-        @current_automatic = @automatic
-        @has_been_read = false
-        @current_nesting_level = []
-      end
-
-      def [](key)
-        @current_nesting_level << key
-
-        # We set this to so that we can cope with ||= as a setting.
-        # See the comments in []= for more details.
-        @has_been_read = true
-
-        # If we have a set type, our destiny is to write
-        if @set_type
-          a_value = @set_type == :automatic ? value_or_descend(current_automatic, key, auto_vivifiy_on_read) : nil
-          o_value = @set_type == :override ? value_or_descend(current_override, key, auto_vivifiy_on_read) : nil
-          n_value = @set_type == :normal ? value_or_descend(current_normal, key, auto_vivifiy_on_read) : nil
-          d_value = @set_type == :default ? value_or_descend(current_default, key, auto_vivifiy_on_read) : nil
-
-          determine_value(a_value, o_value, n_value, d_value)
-        # Our destiny is only to read, so we get the full list.
-        else
-          a_value = value_or_descend(current_automatic, key)
-          o_value = value_or_descend(current_override, key)
-          n_value = value_or_descend(current_normal, key)
-          d_value = value_or_descend(current_default, key)
-
-          determine_value(a_value, o_value, n_value, d_value)
-        end
-      end
-
-      def has_key?(key)
-        return true if component_has_key?(@default,key)
-        return true if component_has_key?(@automatic,key)
-        return true if component_has_key?(@normal,key)
-        return true if component_has_key?(@override,key)
-        false
-      end
-
-      alias :attribute? :has_key?
-      alias :include?   :has_key?
-      alias :key?       :has_key?
-      alias :member?    :has_key?
-
-      def each(&block)
-        get_keys.each do |key|
-          value = determine_value(
-            get_value(automatic, key),
-            get_value(override, key),
-            get_value(normal, key),
-            get_value(default, key)
-          )
-          block.call([key, value])
-        end
-      end
-
-      def each_pair(&block)
-        get_keys.each do |key|
-          value = determine_value(
-            get_value(automatic, key),
-            get_value(override, key),
-            get_value(normal, key),
-            get_value(default, key)
-          )
-          block.call(key, value)
-        end
-      end
-
-      def each_attribute(&block)
-        get_keys.each do |key|
-          value = determine_value(
-            get_value(automatic, key),
-            get_value(override, key),
-            get_value(normal, key),
-            get_value(default, key)
-          )
-          block.call(key, value)
-        end
-      end
-
-      def each_key(&block)
-        get_keys.each do |key|
-          block.call(key)
-        end
-      end
-
-      def each_value(&block)
-        get_keys.each do |key|
-          value = determine_value(
-            get_value(automatic, key),
-            get_value(override, key),
-            get_value(normal, key),
-            get_value(default, key)
-          )
-          block.call(value)
-        end
-      end
-
-      def empty?
-        get_keys.empty?
-      end
-
-      def fetch(key, default_value=nil, &block)
-        if get_keys.include? key
-          determine_value(
-            get_value(automatic, key),
-            get_value(override, key),
-            get_value(normal, key),
-            get_value(default, key)
-          )
-        elsif default_value
-          default_value
-        elsif block_given?
-          block.call(key)
-        else
-          raise IndexError, "Key #{key} does not exist"
-        end
-      end
-
-      # Writing this method hurts me a little bit.
-      #
-      # TODO: Refactor all this stuff so this kind of horror is no longer needed
-      #
-      # We have invented a new kind of duck-typing, we call it Madoff typing.
-      # We just lie and hope we die before you recognize our scheme. :)
-      def kind_of?(klass)
-        if klass == Hash || klass == Mash || klass == Chef::Node::Attribute
-          true
-        else
-          false
-        end
-      end
-
-      def has_value?(value)
-        self.any? do |k,v|
-          value == v
-        end
-      end
-
-      alias :value? :has_value?
-
-      def index(value)
-        index = self.find do |h|
-          value == h[1]
-        end
-        index.first if index.is_a? Array || nil
-      end
-
-      def values
-        self.collect { |h| h[1] }
-      end
-
-      def size
-        self.collect{}.length
-      end
-
-      alias :length :size
-
-      def get_keys
-        keys
-      end
-
-      def keys
-        tkeys = current_automatic ? current_automatic.keys : []
-        [ current_override, current_normal, current_default ].each do |attr_hash|
-          if attr_hash
-            attr_hash.keys.each do |key|
-              tkeys << key unless tkeys.include?(key)
+      # List of the component attribute hashes, in order of precedence, low to
+      # high.
+      COMPONENTS = [
+        :@default,
+        :@env_default,
+        :@role_default,
+        :@force_default,
+        :@normal,
+        :@override,
+        :@role_override,
+        :@env_override,
+        :@force_override,
+        :@automatic
+      ].freeze
+
+      DEFAULT_COMPONENTS = [
+        :@default,
+        :@env_default,
+        :@role_default,
+        :@force_default
+      ]
+
+
+      OVERRIDE_COMPONENTS = [
+        :@override,
+        :@role_override,
+        :@env_override,
+        :@force_override
+      ]
+
+      [:all?,
+       :any?,
+       :assoc,
+       :chunk,
+       :collect,
+       :collect_concat,
+       :compare_by_identity,
+       :compare_by_identity?,
+       :count,
+       :cycle,
+       :detect,
+       :drop,
+       :drop_while,
+       :each,
+       :each_cons,
+       :each_entry,
+       :each_key,
+       :each_pair,
+       :each_slice,
+       :each_value,
+       :each_with_index,
+       :each_with_object,
+       :empty?,
+       :entries,
+       :except,
+       :fetch,
+       :find,
+       :find_all,
+       :find_index,
+       :first,
+       :flat_map,
+       :flatten,
+       :grep,
+       :group_by,
+       :has_value?,
+       :include?,
+       :index,
+       :inject,
+       :invert,
+       :key,
+       :keys,
+       :length,
+       :map,
+       :max,
+       :max_by,
+       :merge,
+       :min,
+       :min_by,
+       :minmax,
+       :minmax_by,
+       :none?,
+       :one?,
+       :partition,
+       :rassoc,
+       :reduce,
+       :reject,
+       :reverse_each,
+       :select,
+       :size,
+       :slice_before,
+       :sort,
+       :sort_by,
+       :store,
+       :symbolize_keys,
+       :take,
+       :take_while,
+       :to_a,
+       :to_hash,
+       :to_set,
+       :value?,
+       :values,
+       :values_at,
+       :zip].each do |delegated_method|
+         class_eval(<<-METHOD_DEFN)
+            def #{delegated_method}(*args, &block)
+              merged_attributes.send(:#{delegated_method}, *args, &block)
             end
-          end
-        end
-        tkeys
-      end
-
-      def get_value(data_hash, key)
-        last = nil
-
-        if @current_nesting_level.length == 0
-          if data_hash.has_key?(key) && ! data_hash[key].nil?
-            return data_hash[key]
-          else
-            return nil
-          end
-        end
-
-        0.upto(@current_nesting_level.length) do |i|
-          if i == 0
-            last = auto_vivifiy(data_hash, @current_nesting_level[i])
-          elsif i == @current_nesting_level.length
-            fk = last[@current_nesting_level[i - 1]]
-            if fk.has_key?(key) && ! fk[key].nil?
-              return fk[key]
-            else
-              return nil
-            end
-          else
-            last = auto_vivifiy(last[@current_nesting_level[i - 1]], @current_nesting_level[i])
-          end
-        end
-      end
-
-      def hash_and_not_cna?(to_check)
-        (! to_check.kind_of?(Chef::Node::Attribute)) && to_check.respond_to?(:has_key?)
-      end
-
-      def determine_value(a_value, o_value, n_value, d_value)
-        if hash_and_not_cna?(a_value)
-          value = {}
-          value = Chef::Mixin::DeepMerge.merge(value, d_value) if hash_and_not_cna?(d_value)
-          value = Chef::Mixin::DeepMerge.merge(value, n_value) if hash_and_not_cna?(n_value)
-          value = Chef::Mixin::DeepMerge.merge(value, o_value) if hash_and_not_cna?(o_value)
-          value = Chef::Mixin::DeepMerge.merge(value, a_value)
-          value
-        elsif hash_and_not_cna?(o_value)
-          value = {}
-          value = Chef::Mixin::DeepMerge.merge(value, d_value) if hash_and_not_cna?(d_value)
-          value = Chef::Mixin::DeepMerge.merge(value, n_value) if hash_and_not_cna?(n_value)
-          value = Chef::Mixin::DeepMerge.merge(value, o_value)
-          value
-        elsif hash_and_not_cna?(n_value)
-          value = {}
-          value = Chef::Mixin::DeepMerge.merge(value, d_value) if hash_and_not_cna?(d_value)
-          value = Chef::Mixin::DeepMerge.merge(value, n_value)
-          value
-        elsif hash_and_not_cna?(d_value)
-          d_value
-        else
-          return a_value if ! a_value.nil?
-          return o_value if ! o_value.nil?
-          return n_value if ! n_value.nil?
-          return d_value if ! d_value.nil?
-          return nil
-        end
-      end
-
-      def []=(key, value)
-        # If we don't have one, then we'll pretend we're normal
-        @set_type ||= :normal
-
-        if set_unless_value_present
-          if get_value(set_type_hash, key) != nil
-            Chef::Log.debug("Not setting #{@current_nesting_level.join("/")}/#{key} to #{value.inspect} because it has a #{@set_type} value already")
-            return false
-          end
-        end
-
-        # If we have been read, and the key we are writing is the same
-        # as our parent, we have most like been ||='ed.  So we need to
-        # just rewind a bit.
-        #
-        # In practice, these objects are single use - this is just
-        # supporting one more single-use style.
-        @current_nesting_level.pop if @has_been_read && @current_nesting_level.last == key
-
-        set_value(set_type_hash, key, value)
-        value
-      end
-
-      def set_value(data_hash, key, value)
-        last = nil
-
-        # If there is no current_nesting_level, just set the value
-        if @current_nesting_level.length == 0
-          data_hash[key] = value
-          return data_hash
-        end
-
-        # Walk all the previous places we have been
-        0.upto(@current_nesting_level.length) do |i|
-          # If we are the first, we are top level, and should vivifiy the data_hash
-          if i == 0
-            last = auto_vivifiy(data_hash, @current_nesting_level[i])
-          # If we are one past the last current_nesting_level, we are adding a key to that hash with a value
-          elsif i == @current_nesting_level.length
-            last[@current_nesting_level[i - 1]][key] = value
-          # Otherwise, we're auto-vivifiy-ing an interim mash
-          else
-            last = auto_vivifiy(last[@current_nesting_level[i - 1]], @current_nesting_level[i])
-          end
-        end
-        data_hash
-      end
-
-      def auto_vivifiy_on_read?
-        auto_vivifiy_on_read
-      end
-
-      def auto_vivifiy(data_hash, key)
-        if data_hash.has_key?(key)
-          unless data_hash[key].respond_to?(:has_key?)
-            raise ArgumentError, "You tried to set a nested key, where the parent is not a hash-like object: #{@current_nesting_level.join("/")}/#{key} " unless auto_vivifiy_on_read
-          end
-        else
-          data_hash[key] = Mash.new
-        end
-        data_hash
-      end
-
-      def value_or_descend(data_hash, key, auto_vivifiy=false)
-        if auto_vivifiy
-          hash_to_vivifiy = auto_vivifiy(data_hash, key)
-          data_hash[key] = hash_to_vivifiy[key]
-        else
-          return nil if data_hash == nil
-          return nil unless data_hash.has_key?(key)
-        end
-
-        if data_hash[key].respond_to?(:has_key?)
-          cna = Chef::Node::Attribute.new(@normal, @default, @override, @automatic, @current_nesting_level)
-          cna.current_normal = current_normal.nil? ? Mash.new : current_normal[key]
-          cna.current_default   = current_default.nil? ? Mash.new : current_default[key]
-          cna.current_override  = current_override.nil? ? Mash.new : current_override[key]
-          cna.current_automatic  = current_automatic.nil? ? Mash.new : current_automatic[key]
-          cna.auto_vivifiy_on_read = auto_vivifiy_on_read
-          cna.set_unless_value_present = set_unless_value_present
-          cna.set_type = set_type
-          cna
-        else
-          data_hash[key]
-        end
-      end
-
-      # Fetches or sets the value, depending on if any arguments are given.
-      # ==== Fetching
-      # If no arguments are given, fetches the value:
-      #   node.network
-      #   => {network data}
-      # Getters will find either a string or symbol key.
-      # ==== Setting
-      # If arguments are given, a value will be set. Both normal setter and DSL
-      # style setters are allowed:
-      #   node.foo = "bar"
-      #   node.foo("bar")
-      # Both set node[:foo] = "bar"
-      def method_missing(symbol, *args)
-        if args.empty?
-          if key?(symbol)
-            self[symbol]
-          elsif key?(symbol.to_s)
-            self[symbol.to_s]
-          elsif auto_vivifiy_on_read?
-            self[symbol] = Mash.new
-            self[symbol]
-          else
-            raise ArgumentError, "Attribute #{symbol} is not defined!" unless auto_vivifiy_on_read
-          end
-        else
-          key_to_set = symbol.to_s[/^(.+)=$/, 1] || symbol
-          self[key_to_set] = (args.length == 1 ? args[0] : args)
-        end
-      end
-
-      def inspect
-        determine_value(current_automatic, current_override, current_normal, current_default)
-
-        "#<#{self.class} " << instance_variables.map{|iv|
-          iv.to_s + '=' + (HIDDEN_ATTRIBUES.include?(iv.to_sym) ? "{...}" : instance_variable_get(iv).inspect)
-        }.join(', ') << ">"
-      end
-
-      def to_hash
-        result = determine_value(current_automatic, current_override, current_normal, current_default)
-        if result.class == Hash
-          result
-        else
-          result.to_hash
-        end
-      end
-
-      def delete(key)
-        [@automatic, @override, @normal, @default].inject(nil) do |return_value, attrs|
-          deleted_value = delete_from_component(attrs, key)
-          return_value || deleted_value
-        end
-      end
-
-      def delete_from_component(component_attrs, key)
-        # get the Hash-like object at the current nesting level:
-        nested_attrs = value_at_current_nesting(component_attrs, key)
-
-        if nested_attrs.respond_to?(:delete)
-          nested_attrs.delete(key)
-        else
-          nil
-        end
-      end
-
-      def component_has_key?(component_attrs,key)
-        # get the Hash-like object at the current nesting level:
-        nested_attrs = value_at_current_nesting(component_attrs, key)
-        nested_attrs.respond_to?(:key?) && nested_attrs.key?(key)
-      end
-
-      def value_at_current_nesting(component_attrs, key)
-        @current_nesting_level.inject(component_attrs) do |subtree, intermediate_key|
-          # if the intermediate value isn't a hash or doesn't have the intermediate key,
-          # it can't have the bottom-level key we're looking for.
-          (subtree.respond_to?(:key?) && subtree[intermediate_key]) or (return false)
-        end
-      end
+         METHOD_DEFN
+       end
+
+
+       # return the cookbook level default attribute component
+       attr_reader :default
+
+       # return the role level default attribute component
+       attr_reader :role_default
+
+       # return the environment level default attribute component
+       attr_reader :env_default
+
+       # return the force_default level attribute component
+       attr_reader :force_default
+
+       # default! is the "advertised" method for force_default, but is
+       # implemented as an alias because instance variables can't (easily) have
+       # +!+ characters.
+       alias :default! :force_default
+
+       # return the "normal" level attribute component
+       attr_reader :normal
+
+       # return the cookbook level override attribute component
+       attr_reader :override
+
+       # return the role level override attribute component
+       attr_reader :role_override
+
+       # return the enviroment level override attribute component
+       attr_reader :env_override
+
+       # return the force override level attribute component
+       attr_reader :force_override
+
+       # +override!+ is the "advertised" method for +force_override+ but is
+       # implemented as an alias because instance variables can't easily have
+       # +!+ characters.
+       alias :override! :force_override
+
+       # return the automatic level attribute component
+       attr_reader :automatic
+
+       def initialize(normal, default, override, automatic)
+         @set_unless_present = false
+
+         @default = VividMash.new(self, default)
+         @env_default = VividMash.new(self, {})
+         @role_default = VividMash.new(self, {})
+         @force_default = VividMash.new(self, {})
+
+         @normal = VividMash.new(self, normal)
+
+         @override = VividMash.new(self, override)
+         @role_override = VividMash.new(self, {})
+         @env_override = VividMash.new(self, {})
+         @force_override = VividMash.new(self, {})
+
+         @automatic = VividMash.new(self, automatic)
+
+         @merged_attributes = nil
+         @combined_override = nil
+         @combined_default = nil
+       end
+
+       # Debug what's going on with an attribute. +args+ is a path spec to the
+       # attribute you're interested in. For example, to debug where the value
+       # of `node[:network][:default_interface]` is coming from, use:
+       #   debug_value(:network, :default_interface).
+       # The return value is an Array of Arrays. The first element is
+       # `["set_unless_enabled?", Boolean]`, which describes whether the
+       # attribute collection is in "set_unless" mode. The rest of the Arrays
+       # are pairs of `["precedence_level", value]`, where precedence level is
+       # the component, such as role default, normal, etc. and value is the
+       # attribute value set at that precedence level. If there is no value at
+       # that precedence level, +value+ will be the symbol +:not_present+.
+       def debug_value(*args)
+         components = COMPONENTS.map do |component|
+           ivar = instance_variable_get(component)
+           value = args.inject(ivar) do |so_far, key|
+             if so_far == :not_present
+               :not_present
+             elsif so_far.has_key?(key)
+               so_far[key]
+             else
+               :not_present
+             end
+           end
+           [component.to_s.sub(/^@/,""), value]
+         end
+         [["set_unless_enabled?", @set_unless_present]] + components
+       end
+
+       # Enables or disables `||=`-like attribute setting. See, e.g., Node#set_unless
+       def set_unless_value_present=(setting)
+         @set_unless_present = setting
+       end
+
+       # Clears merged_attributes, which will cause it to be recomputed on the
+       # next access.
+       def reset_cache
+         @merged_attributes = nil
+         @combined_default  = nil
+         @combined_override = nil
+         @set_unless_present = false
+       end
+
+       alias :reset :reset_cache
+
+       # Set the cookbook level default attribute component to +new_data+.
+       def default=(new_data)
+         reset
+         @default = VividMash.new(self, new_data)
+       end
+
+       # Set the role level default attribute component to +new_data+
+       def role_default=(new_data)
+         reset
+         @role_default = VividMash.new(self, new_data)
+       end
+
+       # Set the environment level default attribute component to +new_data+
+       def env_default=(new_data)
+         reset
+         @env_default = VividMash.new(self, new_data)
+       end
+
+       # Set the force_default (+default!+) level attributes to +new_data+
+       def force_default=(new_data)
+         reset
+         @force_default = VividMash.new(self, new_data)
+       end
+
+       # Set the normal level attribute component to +new_data+
+       def normal=(new_data)
+         reset
+         @normal = VividMash.new(self, new_data)
+       end
+
+       # Set the cookbook level override attribute component to +new_data+
+       def override=(new_data)
+         reset
+         @override = VividMash.new(self, new_data)
+       end
+
+       # Set the role level override attribute component to +new_data+
+       def role_override=(new_data)
+         reset
+         @role_override = VividMash.new(self, new_data)
+       end
+
+       # Set the environment level override attribute component to +new_data+
+       def env_override=(new_data)
+         reset
+         @env_override = VividMash.new(self, new_data)
+       end
+
+       def force_override=(new_data)
+         reset
+         @force_override = VividMash.new(self, new_data)
+       end
+
+       def automatic=(new_data)
+         reset
+         @automatic = VividMash.new(self, new_data)
+       end
+
+       def merged_attributes
+         @merged_attributes ||= begin
+                                  components = [merge_defaults, @normal, merge_overrides, @automatic]
+                                  resolved_attrs = components.inject(Mash.new) do |merged, component|
+                                    Chef::Mixin::DeepMerge.hash_only_merge(merged, component)
+                                  end
+                                  immutablize(resolved_attrs)
+                                end
+       end
+
+       def combined_override
+         @combined_override ||= immutablize(merge_overrides)
+       end
+
+       def combined_default
+         @combined_default ||= immutablize(merge_defaults)
+       end
+
+       def [](key)
+         merged_attributes[key]
+       end
+
+       def []=(key, value)
+         merged_attributes[key] = value
+       end
+
+       def has_key?(key)
+         COMPONENTS.any? do |component_ivar|
+           instance_variable_get(component_ivar).has_key?(key)
+         end
+       end
+
+       alias :attribute? :has_key?
+       alias :member? :has_key?
+       alias :include? :has_key?
+       alias :key? :has_key?
+
+       alias :each_attribute :each
+
+       def method_missing(symbol, *args)
+         if args.empty?
+           if key?(symbol)
+             self[symbol]
+           else
+             raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
+           end
+         elsif symbol.to_s =~ /=$/
+           key_to_set = symbol.to_s[/^(.+)=$/, 1]
+           self[key_to_set] = (args.length == 1 ? args[0] : args)
+         else
+           raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
+         end
+       end
+
+       def inspect
+         "#<#{self.class} " << (COMPONENTS + [:@merged_attributes, :@properties]).map{|iv|
+           "#{iv}=#{instance_variable_get(iv).inspect}"
+         }.join(', ') << ">"
+       end
+
+       def set_unless?
+         @set_unless_present
+       end
+
+       private
+
+       def merge_defaults
+         DEFAULT_COMPONENTS.inject(Mash.new) do |merged, component_ivar|
+           component_value = instance_variable_get(component_ivar)
+           Chef::Mixin::DeepMerge.merge(merged, component_value)
+         end
+       end
+
+       def merge_overrides
+         OVERRIDE_COMPONENTS.inject(Mash.new) do |merged, component_ivar|
+           component_value = instance_variable_get(component_ivar)
+           Chef::Mixin::DeepMerge.merge(merged, component_value)
+         end
+       end
+
 
     end
+
   end
 end
diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb
new file mode 100644
index 0000000..d5d496f
--- /dev/null
+++ b/lib/chef/node/attribute_collections.rb
@@ -0,0 +1,206 @@
+#--
+# Author:: Daniel DeLeo (<dan 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.
+#
+
+class Chef
+  class Node
+
+    # == AttrArray
+    # AttrArray is identical to Array, except that it keeps a reference to the
+    # "root" (Chef::Node::Attribute) object, and will trigger a cache
+    # invalidation on that object when mutated.
+    class AttrArray < Array
+
+      MUTATOR_METHODS = [
+        :<<,
+        :[]=,
+        :clear,
+        :collect!,
+        :compact!,
+        :default=,
+        :default_proc=,
+        :delete,
+        :delete_at,
+        :delete_if,
+        :fill,
+        :flatten!,
+        :insert,
+        :keep_if,
+        :map!,
+        :merge!,
+        :pop,
+        :push,
+        :update,
+        :reject!,
+        :reverse!,
+        :replace,
+        :select!,
+        :shift,
+        :slice!,
+        :sort!,
+        :sort_by!,
+        :uniq!,
+        :unshift
+      ]
+
+      # For all of the methods that may mutate an Array, we override them to
+      # also invalidate the cached merged_attributes on the root
+      # Node::Attribute object.
+      MUTATOR_METHODS.each do |mutator|
+        class_eval(<<-METHOD_DEFN, __FILE__, __LINE__)
+          def #{mutator}(*args, &block)
+            root.reset_cache
+            super
+          end
+        METHOD_DEFN
+      end
+
+      attr_reader :root
+
+      def initialize(root, data)
+        @root = root
+        super(data)
+      end
+
+      def dup
+        Array.new(map {|e| e.dup})
+      end
+
+    end
+
+    # == VividMash
+    # VividMash is identical to a Mash, with a few exceptions:
+    # * It has a reference to the root Chef::Node::Attribute to which it
+    #   belongs, and will trigger cache invalidation on that object when
+    #   mutated.
+    # * It auto-vivifies, that is a reference to a missing element will result
+    #   in the creation of a new VividMash for that key. (This only works when
+    #   using the element reference method, `[]` -- other methods, such as
+    #   #fetch, work as normal).
+    # * It supports a set_unless flag (via the root Attribute object) which
+    #   allows `||=` style behavior (`||=` does not work with
+    #   auto-vivification). This is only implemented for #[]=; methods such as
+    #   #store work as normal.
+    # * attr_accessor style element set and get are supported via method_missing
+    class VividMash < Mash
+      attr_reader :root
+
+      # Methods that mutate a VividMash. Each of them is overridden so that it
+      # also invalidates the cached merged_attributes on the root Attribute
+      # object.
+      MUTATOR_METHODS = [
+        :clear,
+        :delete,
+        :delete_if,
+        :keep_if,
+        :merge!,
+        :update,
+        :reject!,
+        :replace,
+        :select!,
+        :shift
+      ]
+
+      # For all of the mutating methods on Mash, override them so that they
+      # also invalidate the cached `merged_attributes` on the root Attribute
+      # object.
+      MUTATOR_METHODS.each do |mutator|
+        class_eval(<<-METHOD_DEFN, __FILE__, __LINE__)
+          def #{mutator}(*args, &block)
+            root.reset_cache
+            super
+          end
+        METHOD_DEFN
+      end
+
+      def initialize(root, data={})
+        @root = root
+        super(data)
+      end
+
+      def [](key)
+        value = super
+        if !key?(key)
+          value = self.class.new(root)
+          self[key] = value
+        else
+          value
+        end
+      end
+
+      def []=(key, value)
+        if set_unless? && key?(key)
+          self[key]
+        else
+          root.reset_cache
+          super
+        end
+      end
+
+      alias :attribute? :has_key?
+
+      def method_missing(symbol, *args)
+        # Calling `puts arg` implicitly calls #to_ary on `arg`. If `arg` does
+        # not implement #to_ary, ruby recognizes it as a single argument, and
+        # if it returns an Array, then ruby prints each element. If we don't
+        # account for that here, we'll auto-vivify a VividMash for the key
+        # :to_ary which creates an unwanted key and raises a TypeError.
+        if symbol == :to_ary
+          super
+        elsif args.empty?
+          self[symbol]
+        elsif symbol.to_s =~ /=$/
+          key_to_set = symbol.to_s[/^(.+)=$/, 1]
+          self[key_to_set] = (args.length == 1 ? args[0] : args)
+        else
+          raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'. To set an attribute, use `#{symbol}=value' instead."
+        end
+      end
+
+      def set_unless?
+        @root.set_unless?
+      end
+
+      def convert_key(key)
+        super
+      end
+
+      # Mash uses #convert_value to mashify values on input.
+      # We override it here to convert hash or array values to VividMash or
+      # AttrArray for consistency and to ensure that the added parts of the
+      # attribute tree will have the correct cache invalidation behavior.
+      def convert_value(value)
+        case value
+        when VividMash
+          value
+        when Hash
+          VividMash.new(root, value)
+        when Array
+          AttrArray.new(root, value)
+        else
+          value
+        end
+      end
+
+      def dup
+        Mash.new(self)
+      end
+
+    end
+
+  end
+end
diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb
new file mode 100644
index 0000000..f5b3a51
--- /dev/null
+++ b/lib/chef/node/immutable_collections.rb
@@ -0,0 +1,186 @@
+
+class Chef
+  class Node
+
+    module Immutablize
+      def immutablize(value)
+        case value
+        when Hash
+          ImmutableMash.new(value)
+        when Array
+          ImmutableArray.new(value)
+        else
+          value
+        end
+      end
+    end
+
+    # == ImmutableArray
+    # ImmutableArray is used to implement Array collections when reading node
+    # attributes.
+    #
+    # ImmutableArray acts like an ordinary Array, except:
+    # * Methods that mutate the array are overridden to raise an error, making
+    #   the collection more or less immutable.
+    # * Since this class stores values computed from a parent
+    #   Chef::Node::Attribute's values, it overrides all reader methods to
+    #   detect staleness and raise an error if accessed when stale.
+    class ImmutableArray < Array
+      include Immutablize
+
+      alias :internal_push :<<
+      private :internal_push
+
+      # A list of methods that mutate Array. Each of these is overridden to
+      # raise an error, making this instances of this class more or less
+      # immutable.
+      DISALLOWED_MUTATOR_METHODS = [
+        :<<,
+        :[]=,
+        :clear,
+        :collect!,
+        :compact!,
+        :default=,
+        :default_proc=,
+        :delete,
+        :delete_at,
+        :delete_if,
+        :fill,
+        :flatten!,
+        :insert,
+        :keep_if,
+        :map!,
+        :merge!,
+        :pop,
+        :push,
+        :update,
+        :reject!,
+        :reverse!,
+        :replace,
+        :select!,
+        :shift,
+        :slice!,
+        :sort!,
+        :sort_by!,
+        :uniq!,
+        :unshift
+      ]
+
+      def initialize(array_data)
+        array_data.each do |value|
+          internal_push(immutablize(value))
+        end
+      end
+
+      # Redefine all of the methods that mutate a Hash to raise an error when called.
+      # This is the magic that makes this object "Immutable"
+      DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name|
+        # Ruby 1.8 blocks can't have block arguments, so we must use string eval:
+        class_eval(<<-METHOD_DEFN, __FILE__, __LINE__)
+          def #{mutator_method_name}(*args, &block)
+            msg = "Node attributes are read-only when you do not specify which precedence level to set. " +
+            %Q(To set an attribute use code like `node.default["key"] = "value"')
+            raise Exceptions::ImmutableAttributeModification, msg
+          end
+        METHOD_DEFN
+      end
+
+      def dup
+        Array.new(map {|e| e.dup })
+      end
+
+    end
+
+    # == ImmutableMash
+    # ImmutableMash implements Hash/Dict behavior for reading values from node
+    # attributes.
+    #
+    # ImmutableMash acts like a Mash (Hash that is indifferent to String or
+    # Symbol keys), with some important exceptions:
+    # * Methods that mutate state are overridden to raise an error instead.
+    # * Methods that read from the collection are overriden so that they check
+    #   if the Chef::Node::Attribute has been modified since an instance of
+    #   this class was generated. An error is raised if the object detects that
+    #   it is stale.
+    # * Values can be accessed in attr_reader-like fashion via method_missing.
+    class ImmutableMash < Mash
+
+      include Immutablize
+
+      alias :internal_set :[]=
+      private :internal_set
+
+      DISALLOWED_MUTATOR_METHODS = [
+        :[]=,
+        :clear,
+        :collect!,
+        :default=,
+        :default_proc=,
+        :delete,
+        :delete_if,
+        :keep_if,
+        :map!,
+        :merge!,
+        :update,
+        :reject!,
+        :replace,
+        :select!,
+        :shift
+      ]
+
+      def initialize(mash_data)
+        mash_data.each do |key, value|
+          internal_set(key, immutablize(value))
+        end
+      end
+
+      alias :attribute? :has_key?
+
+      # Redefine all of the methods that mutate a Hash to raise an error when called.
+      # This is the magic that makes this object "Immutable"
+      DISALLOWED_MUTATOR_METHODS.each do |mutator_method_name|
+        # Ruby 1.8 blocks can't have block arguments, so we must use string eval:
+        class_eval(<<-METHOD_DEFN, __FILE__, __LINE__)
+        def #{mutator_method_name}(*args, &block)
+          msg = "Node attributes are read-only when you do not specify which precedence level to set. " +
+          %Q(To set an attribute use code like `node.default["key"] = "value"')
+          raise Exceptions::ImmutableAttributeModification, msg
+        end
+        METHOD_DEFN
+      end
+
+      def method_missing(symbol, *args)
+        if args.empty?
+          if key?(symbol)
+            self[symbol]
+          else
+            raise NoMethodError, "Undefined method or attribute `#{symbol}' on `node'"
+          end
+        # This will raise a ImmutableAttributeModification error:
+        elsif symbol.to_s =~ /=$/
+          key_to_set = symbol.to_s[/^(.+)=$/, 1]
+          self[key_to_set] = (args.length == 1 ? args[0] : args)
+        else
+          raise NoMethodError, "Undefined node attribute or method `#{symbol}' on `node'"
+        end
+      end
+
+      # Mash uses #convert_value to mashify values on input.
+      # Since we're handling this ourselves, override it to be a no-op
+      def convert_value(value)
+        value
+      end
+
+      # NOTE: #default and #default= are likely to be pretty confusing. For a
+      # regular ruby Hash, they control what value is returned for, e.g.,
+      #   hash[:no_such_key] #=> hash.default
+      # Of course, 'default' has a specific meaning in Chef-land
+
+      def dup
+        Mash.new(self)
+      end
+
+    end
+
+  end
+end
diff --git a/lib/chef/openid_registration.rb b/lib/chef/openid_registration.rb
deleted file mode 100644
index f7bb959..0000000
--- a/lib/chef/openid_registration.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.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 'chef/config'
-require 'chef/mixin/params_validate'
-require 'chef/couchdb'
-require 'chef/index_queue'
-require 'digest/sha1'
-require 'chef/json_compat'
-
-class Chef
-  class OpenIDRegistration
-    
-    attr_accessor :name, :salt, :validated, :password, :couchdb_rev, :admin
-    
-    include Chef::Mixin::ParamsValidate
-    include Chef::IndexQueue::Indexable
-    
-    DESIGN_DOCUMENT = {
-      "version" => 3,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-            function(doc) {
-              if (doc.chef_type == "openid_registration") {
-                emit(doc.name, doc);
-              }
-            }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "openid_registration") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        },
-        "validated" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "openid_registration") {
-              if (doc.validated == true) {
-                emit(doc.name, doc);
-              }
-            }
-          }
-          EOJS
-        },
-        "unvalidated" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "openid_registration") {
-              if (doc.validated == false) {
-                emit(doc.name, doc);
-              }
-            }
-          }
-          EOJS
-        },
-      },
-    }
-    
-    # Create a new Chef::OpenIDRegistration object.
-    def initialize()
-      @name = nil
-      @salt = nil
-      @password = nil
-      @validated = false
-      @admin = false
-      @couchdb_rev = nil
-      @couchdb = Chef::CouchDB.new
-    end
-    
-    def name=(n)
-      @name = n.gsub(/\./, '_')
-    end
-    
-    # Set the password for this object.
-    def set_password(password) 
-      @salt = generate_salt
-      @password = encrypt_password(@salt, password)      
-    end
-    
-    # Serialize this object as a hash 
-    def to_json(*a)
-      attributes = Hash.new
-      recipes = Array.new
-      result = {
-        'name' => @name,
-        'json_class' => self.class.name,
-        'salt' => @salt,
-        'password' => @password,
-        'validated' => @validated,
-        'admin' => @admin,
-        'chef_type' => 'openid_registration',
-      }
-      result["_rev"] = @couchdb_rev if @couchdb_rev
-      result.to_json(*a)
-    end
-    
-    # Create a Chef::Node from JSON
-    def self.json_create(o)
-      me = new
-      me.name = o["name"]
-      me.salt = o["salt"]
-      me.password = o["password"]
-      me.validated = o["validated"]
-      me.admin = o["admin"]
-      me.couchdb_rev = o["_rev"] if o.has_key?("_rev")
-      me
-    end
-    
-    # List all the Chef::OpenIDRegistration objects in the CouchDB.  If inflate is set to true, you will get
-    # the full list of all registration objects.  Otherwise, you'll just get the IDs
-    def self.list(inflate=false)
-      rs = Chef::CouchDB.new.list("registrations", inflate)
-      if inflate
-        rs["rows"].collect { |r| r["value"] }
-      else
-        rs["rows"].collect { |r| r["key"] }
-      end
-    end
-    
-    def self.cdb_list(*args)
-      list(*args)
-    end
-    
-    # Load an OpenIDRegistration by name from CouchDB
-    def self.load(name)
-      Chef::CouchDB.new.load("openid_registration", name)
-    end
-    
-    # Whether or not there is an OpenID Registration with this key.
-    def self.has_key?(name)
-      Chef::CouchDB.new.has_key?("openid_registration", name)
-    end
-    
-    # Remove this OpenIDRegistration from the CouchDB
-    def destroy
-      @couchdb.delete("openid_registration", @name, @couchdb_rev)
-    end
-    
-    # Save this OpenIDRegistration to the CouchDB
-    def save
-      results = @couchdb.store("openid_registration", @name, self)
-      @couchdb_rev = results["rev"]
-    end
-    
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      couchdb ||= Chef::CouchDB.new
-      couchdb.create_design_document("registrations", DESIGN_DOCUMENT)
-    end
-    
-    protected
-    
-      def generate_salt
-        salt = Time.now.to_s
-        chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
-        1.upto(30) { |i| salt << chars[rand(chars.size-1)] }
-        salt
-      end
-    
-      def encrypt_password(salt, password)
-        Digest::SHA1.hexdigest("--#{salt}--#{password}--")
-      end
-    
-  end
-end
diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb
index f18a44b..8f49473 100644
--- a/lib/chef/platform.rb
+++ b/lib/chef/platform.rb
@@ -16,461 +16,14 @@
 # limitations under the License.
 #
 
-require 'chef/config'
-require 'chef/log'
-require 'chef/mixin/params_validate'
-
-# Actually, this file depends on nearly every provider in chef, but actually
-# requiring them causes circular requires resulting in uninitialized constant
-# errors.
-require 'chef/provider'
-require 'chef/provider/log'
-require 'chef/provider/user'
-require 'chef/provider/group'
-require 'chef/provider/mount'
-require 'chef/provider/service'
-require 'chef/provider/package'
-
+require 'chef/platform/provider_mapping'
+require 'chef/platform/query_helpers'
 
 class Chef
   class Platform
 
-    class << self
-      attr_writer :platforms
-
-      def platforms
-        @platforms ||= {
-          :mac_os_x => {
-            :default => {
-              :package => Chef::Provider::Package::Macports,
-              :service => Chef::Provider::Service::Macosx,
-              :user => Chef::Provider::User::Dscl,
-              :group => Chef::Provider::Group::Dscl
-            }
-          },
-          :mac_os_x_server => {
-            :default => {
-              :package => Chef::Provider::Package::Macports,
-              :service => Chef::Provider::Service::Macosx,
-              :user => Chef::Provider::User::Dscl,
-              :group => Chef::Provider::Group::Dscl
-            }
-          },
-          :freebsd => {
-            :default => {
-              :group   => Chef::Provider::Group::Pw,
-              :package => Chef::Provider::Package::Freebsd,
-              :service => Chef::Provider::Service::Freebsd,
-              :user    => Chef::Provider::User::Pw,
-              :cron    => Chef::Provider::Cron
-            }
-          },
-          :ubuntu   => {
-            :default => {
-              :package => Chef::Provider::Package::Apt,
-              :service => Chef::Provider::Service::Debian,
-              :cron => Chef::Provider::Cron,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :linaro   => {
-            :default => {
-              :package => Chef::Provider::Package::Apt,
-              :service => Chef::Provider::Service::Debian,
-              :cron => Chef::Provider::Cron,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :linuxmint   => {
-            :default => {
-              :package => Chef::Provider::Package::Apt,
-              :service => Chef::Provider::Service::Upstart,
-              :cron => Chef::Provider::Cron,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :debian => {
-            :default => {
-              :package => Chef::Provider::Package::Apt,
-              :service => Chef::Provider::Service::Debian,
-              :cron => Chef::Provider::Cron,
-              :mdadm => Chef::Provider::Mdadm
-            },
-            "6.0" => {
-              :service => Chef::Provider::Service::Insserv
-            }
-          },
-          :xenserver   => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Yum,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :centos   => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Yum,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :amazon   => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Yum,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :scientific => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Yum,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :fedora   => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Yum,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :suse     => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Zypper,
-              :group => Chef::Provider::Group::Suse
-            }
-          },
-          :oracle  => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Yum,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :redhat   => {
-            :default => {
-              :service => Chef::Provider::Service::Redhat,
-              :cron => Chef::Provider::Cron,
-              :package => Chef::Provider::Package::Yum,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :gentoo   => {
-            :default => {
-              :package => Chef::Provider::Package::Portage,
-              :service => Chef::Provider::Service::Gentoo,
-              :cron => Chef::Provider::Cron,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :arch   => {
-            :default => {
-              :package => Chef::Provider::Package::Pacman,
-              :service => Chef::Provider::Service::Arch,
-              :cron => Chef::Provider::Cron,
-              :mdadm => Chef::Provider::Mdadm
-            }
-          },
-          :mswin => {
-            :default => {
-              :env =>  Chef::Provider::Env::Windows,
-              :service => Chef::Provider::Service::Windows,
-              :user => Chef::Provider::User::Windows,
-              :group => Chef::Provider::Group::Windows,
-              :mount => Chef::Provider::Mount::Windows
-            }
-          },
-          :mingw32 => {
-            :default => {
-              :env =>  Chef::Provider::Env::Windows,
-              :service => Chef::Provider::Service::Windows,
-              :user => Chef::Provider::User::Windows,
-              :group => Chef::Provider::Group::Windows,
-              :mount => Chef::Provider::Mount::Windows
-            }
-          },
-          :windows => {
-            :default => {
-              :env =>  Chef::Provider::Env::Windows,
-              :service => Chef::Provider::Service::Windows,
-              :user => Chef::Provider::User::Windows,
-              :group => Chef::Provider::Group::Windows,
-              :mount => Chef::Provider::Mount::Windows
-            }
-          },
-          :solaris  => {},
-          :openindiana => {
-            :default => {
-              :service => Chef::Provider::Service::Solaris,
-              :package => Chef::Provider::Package::Solaris,
-              :cron => Chef::Provider::Cron::Solaris,
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :opensolaris => {
-            :default => {
-              :service => Chef::Provider::Service::Solaris,
-              :package => Chef::Provider::Package::Solaris,
-              :cron => Chef::Provider::Cron::Solaris,
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :nexentacore => {
-            :default => {
-              :service => Chef::Provider::Service::Solaris,
-              :package => Chef::Provider::Package::Solaris,
-              :cron => Chef::Provider::Cron::Solaris,
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :solaris2 => {
-            :default => {
-              :service => Chef::Provider::Service::Solaris,
-              :package => Chef::Provider::Package::Solaris,
-              :cron => Chef::Provider::Cron::Solaris,
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :smartos => {
-            :default => {
-              :service => Chef::Provider::Service::Solaris,
-              :package => Chef::Provider::Package::SmartOS,
-              :cron => Chef::Provider::Cron::Solaris,
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :netbsd => {
-            :default => {
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :openbsd => {
-            :default => {
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :hpux => {
-            :default => {
-              :group => Chef::Provider::Group::Usermod
-            }
-          },
-          :aix => {
-            :default => {
-              :group => Chef::Provider::Group::Aix
-            }
-          },
-          :default  => {
-            :file => Chef::Provider::File,
-            :directory => Chef::Provider::Directory,
-            :link => Chef::Provider::Link,
-            :template => Chef::Provider::Template,
-            :remote_directory => Chef::Provider::RemoteDirectory,
-            :execute => Chef::Provider::Execute,
-            :mount => Chef::Provider::Mount::Mount,
-            :script => Chef::Provider::Script,
-            :service => Chef::Provider::Service::Init,
-            :perl => Chef::Provider::Script,
-            :python => Chef::Provider::Script,
-            :ruby => Chef::Provider::Script,
-            :bash => Chef::Provider::Script,
-            :csh => Chef::Provider::Script,
-            :user => Chef::Provider::User::Useradd,
-            :group => Chef::Provider::Group::Gpasswd,
-            :http_request => Chef::Provider::HttpRequest,
-            :route => Chef::Provider::Route,
-            :ifconfig => Chef::Provider::Ifconfig,
-            :ruby_block => Chef::Provider::RubyBlock,
-            :erl_call => Chef::Provider::ErlCall,
-            :log => Chef::Provider::Log::ChefLog
-          }
-        }
-      end
-
-      include Chef::Mixin::ParamsValidate
-
-      def find(name, version)
-        provider_map = platforms[:default].clone
-
-        name_sym = name
-        if name.kind_of?(String)
-          name.downcase!
-          name.gsub!(/\s/, "_")
-          name_sym = name.to_sym
-        end
-
-        if platforms.has_key?(name_sym)
-          if platforms[name_sym].has_key?(version)
-            Chef::Log.debug("Platform #{name.to_s} version #{version} found")
-            if platforms[name_sym].has_key?(:default)
-              provider_map.merge!(platforms[name_sym][:default])
-            end
-            provider_map.merge!(platforms[name_sym][version])
-          elsif platforms[name_sym].has_key?(:default)
-            provider_map.merge!(platforms[name_sym][:default])
-          end
-        else
-          Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)")
-        end
-        provider_map
-      end
-
-      def find_platform_and_version(node)
-        platform = nil
-        version = nil
-
-        if node[:platform]
-          platform = node[:platform]
-        elsif node.attribute?("os")
-          platform = node[:os]
-        end
-
-        raise ArgumentError, "Cannot find a platform for #{node}" unless platform
-
-        if node[:platform_version]
-          version = node[:platform_version]
-        elsif node[:os_version]
-          version = node[:os_version]
-        elsif node[:os_release]
-          version = node[:os_release]
-        end
-
-        raise ArgumentError, "Cannot find a version for #{node}" unless version
-
-        return platform, version
-      end
-
-      def provider_for_resource(resource)
-        node = resource.run_context && resource.run_context.node
-        raise ArgumentError, "Cannot find the provider for a resource with no run context set" unless node
-        find_provider_for_node(node, resource).new(resource, resource.run_context)
-      end
-
-      def provider_for_node(node, resource_type)
-        raise NotImplementedError, "#{self.class.name} no longer supports #provider_for_node"
-        find_provider_for_node(node, resource_type).new(node, resource_type)
-      end
-
-      def find_provider_for_node(node, resource_type)
-        platform, version = find_platform_and_version(node)
-        provider = find_provider(platform, version, resource_type)
-      end
-
-      def set(args)
-        validate(
-          args,
-          {
-            :platform => {
-              :kind_of => Symbol,
-              :required => false,
-            },
-            :version => {
-              :kind_of => String,
-              :required => false,
-            },
-            :resource => {
-              :kind_of => Symbol,
-            },
-            :provider => {
-              :kind_of => [ String, Symbol, Class ],
-            }
-          }
-        )
-        if args.has_key?(:platform)
-          if args.has_key?(:version)
-            if platforms.has_key?(args[:platform])
-              if platforms[args[:platform]].has_key?(args[:version])
-                platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider]
-              else
-                platforms[args[:platform]][args[:version]] = {
-                  args[:resource].to_sym => args[:provider]
-                }
-              end
-            else
-              platforms[args[:platform]] = {
-                args[:version] => {
-                  args[:resource].to_sym => args[:provider]
-                }
-              }
-            end
-          else
-            if platforms.has_key?(args[:platform])
-              if platforms[args[:platform]].has_key?(:default)
-                platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider]
-              else
-                platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } }
-              end
-            else
-              platforms[args[:platform]] = {
-                :default => {
-                  args[:resource].to_sym => args[:provider]
-                }
-              }
-            end
-          end
-        else
-          if platforms.has_key?(:default)
-            platforms[:default][args[:resource].to_sym] = args[:provider]
-          else
-            platforms[:default] = {
-              args[:resource].to_sym => args[:provider]
-            }
-          end
-        end
-      end
-
-      def find_provider(platform, version, resource_type)
-        pmap = Chef::Platform.find(platform, version)
-        provider_klass = explicit_provider(platform, version, resource_type) ||
-                         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?
-
-        provider_klass
-      end
-
-      def windows?
-        if RUBY_PLATFORM =~ /mswin|mingw|windows/
-          true
-        else
-          false
-        end
-      end
-
-      private
-
-        def explicit_provider(platform, version, resource_type)
-          resource_type.kind_of?(Chef::Resource) ? resource_type.provider : nil
-        end
-
-        def platform_provider(platform, version, resource_type)
-          pmap = Chef::Platform.find(platform, version)
-          rtkey = resource_type.kind_of?(Chef::Resource) ? resource_type.resource_name.to_sym : resource_type
-          pmap.has_key?(rtkey) ? pmap[rtkey] : nil
-        end
-
-        def resource_matching_provider(platform, version, resource_type)
-          if resource_type.kind_of?(Chef::Resource)
-            begin
-              Chef::Provider.const_get(resource_type.class.to_s.split('::').last)
-            rescue NameError
-              nil
-            end
-          else
-            nil
-          end
-        end
-
-    end
+    # Functionality for this class is defined in chef/platform/provider_mapping
+    # and chef/platform/query_helpers
 
   end
 end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
new file mode 100644
index 0000000..a252bdc
--- /dev/null
+++ b/lib/chef/platform/provider_mapping.rb
@@ -0,0 +1,554 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 'chef/config'
+require 'chef/log'
+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.
+require 'chef/provider'
+require 'chef/provider/log'
+require 'chef/provider/user'
+require 'chef/provider/group'
+require 'chef/provider/mount'
+require 'chef/provider/service'
+require 'chef/provider/package'
+require 'chef/provider/ifconfig'
+
+
+class Chef
+  class Platform
+
+    class << self
+      attr_writer :platforms
+
+      def platforms
+        @platforms ||= {
+          :mac_os_x => {
+            :default => {
+              :package => Chef::Provider::Package::Macports,
+              :service => Chef::Provider::Service::Macosx,
+              :user => Chef::Provider::User::Dscl,
+              :group => Chef::Provider::Group::Dscl
+            }
+          },
+          :mac_os_x_server => {
+            :default => {
+              :package => Chef::Provider::Package::Macports,
+              :service => Chef::Provider::Service::Macosx,
+              :user => Chef::Provider::User::Dscl,
+              :group => Chef::Provider::Group::Dscl
+            }
+          },
+          :freebsd => {
+            :default => {
+              :group   => Chef::Provider::Group::Pw,
+              :package => Chef::Provider::Package::Freebsd,
+              :service => Chef::Provider::Service::Freebsd,
+              :user    => Chef::Provider::User::Pw,
+              :cron    => Chef::Provider::Cron
+            }
+          },
+          :ubuntu   => {
+            :default => {
+              :package => Chef::Provider::Package::Apt,
+              :service => Chef::Provider::Service::Debian,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            },
+            ">= 11.10" => {
+              :ifconfig => Chef::Provider::Ifconfig::Debian
+            }
+          },
+          :gcel   => {
+            :default => {
+              :package => Chef::Provider::Package::Apt,
+              :service => Chef::Provider::Service::Debian,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :linaro   => {
+            :default => {
+              :package => Chef::Provider::Package::Apt,
+              :service => Chef::Provider::Service::Debian,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :raspbian   => {
+            :default => {
+              :package => Chef::Provider::Package::Apt,
+              :service => Chef::Provider::Service::Debian,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :linuxmint   => {
+            :default => {
+              :package => Chef::Provider::Package::Apt,
+              :service => Chef::Provider::Service::Upstart,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :debian => {
+            :default => {
+              :package => Chef::Provider::Package::Apt,
+              :service => Chef::Provider::Service::Debian,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            },
+            ">= 6.0" => {
+              :service => Chef::Provider::Service::Insserv
+            },
+            ">= 7.0" => {
+              :ifconfig => Chef::Provider::Ifconfig::Debian
+            }
+          },
+          :xenserver   => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :xcp   => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :centos   => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm,
+              :ifconfig => Chef::Provider::Ifconfig::Redhat
+            }
+          },
+          :amazon   => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :scientific => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :fedora   => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm,
+              :ifconfig => Chef::Provider::Ifconfig::Redhat
+            }
+          },
+          :opensuse     => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Zypper,
+              :group => Chef::Provider::Group::Suse
+            },
+            ">= 12.3" => {
+              :group => Chef::Provider::Group::Usermod
+            }
+          },
+          :suse     => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Zypper,
+              :group => Chef::Provider::Group::Suse
+            },
+            ###############################################
+            # TODO: Remove this after ohai update is released.
+            # Only OpenSuSE 12.3+ should use the Usermod group provider:
+            # Ohai before OHAI-339 is applied reports both OpenSuSE and SuSE
+            # Enterprise as "suse", Ohai after OHAI-339 will report OpenSuSE as
+            # "opensuse".
+            #
+            # In order to support OpenSuSE both before and after the Ohai
+            # change, I'm leaving this here. It needs to get removed before
+            # SuSE enterprise 12.3 ships.
+            ">= 12.3" => {
+              :group => Chef::Provider::Group::Usermod
+            }
+          },
+          :oracle  => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :redhat   => {
+            :default => {
+              :service => Chef::Provider::Service::Redhat,
+              :cron => Chef::Provider::Cron,
+              :package => Chef::Provider::Package::Yum,
+              :mdadm => Chef::Provider::Mdadm,
+              :ifconfig => Chef::Provider::Ifconfig::Redhat
+            }
+          },
+          :gentoo   => {
+            :default => {
+              :package => Chef::Provider::Package::Portage,
+              :service => Chef::Provider::Service::Gentoo,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :arch   => {
+            :default => {
+              :package => Chef::Provider::Package::Pacman,
+              :service => Chef::Provider::Service::Arch,
+              :cron => Chef::Provider::Cron,
+              :mdadm => Chef::Provider::Mdadm
+            }
+          },
+          :mswin => {
+            :default => {
+              :env =>  Chef::Provider::Env::Windows,
+              :service => Chef::Provider::Service::Windows,
+              :user => Chef::Provider::User::Windows,
+              :group => Chef::Provider::Group::Windows,
+              :mount => Chef::Provider::Mount::Windows
+            }
+          },
+          :mingw32 => {
+            :default => {
+              :env =>  Chef::Provider::Env::Windows,
+              :service => Chef::Provider::Service::Windows,
+              :user => Chef::Provider::User::Windows,
+              :group => Chef::Provider::Group::Windows,
+              :mount => Chef::Provider::Mount::Windows
+            }
+          },
+          :windows => {
+            :default => {
+              :env =>  Chef::Provider::Env::Windows,
+              :service => Chef::Provider::Service::Windows,
+              :user => Chef::Provider::User::Windows,
+              :group => Chef::Provider::Group::Windows,
+              :mount => Chef::Provider::Mount::Windows
+            }
+          },
+          :solaris  => {},
+          :openindiana => {
+            :default => {
+              :service => Chef::Provider::Service::Solaris,
+              :package => Chef::Provider::Package::Ips,
+              :cron => Chef::Provider::Cron::Solaris,
+              :group => Chef::Provider::Group::Usermod
+            }
+          },
+          :opensolaris => {
+            :default => {
+              :service => Chef::Provider::Service::Solaris,
+              :package => Chef::Provider::Package::Ips,
+              :cron => Chef::Provider::Cron::Solaris,
+              :group => Chef::Provider::Group::Usermod
+            }
+          },
+          :nexentacore => {
+            :default => {
+              :service => Chef::Provider::Service::Solaris,
+              :package => Chef::Provider::Package::Solaris,
+              :cron => Chef::Provider::Cron::Solaris,
+              :group => Chef::Provider::Group::Usermod
+            }
+          },
+          :omnios => {
+            :default => {
+              :service => Chef::Provider::Service::Solaris,
+              :package => Chef::Provider::Package::Ips,
+              :cron => Chef::Provider::Cron::Solaris,
+              :group => Chef::Provider::Group::Usermod,
+              :user => Chef::Provider::User::Solaris,
+            }
+          },
+          :solaris2 => {
+            :default => {
+              :service => Chef::Provider::Service::Solaris,
+              :package => Chef::Provider::Package::Ips,
+              :cron => Chef::Provider::Cron::Solaris,
+              :group => Chef::Provider::Group::Usermod,
+              :user => Chef::Provider::User::Solaris,
+            },
+            ">= 5.9" => {
+              :service => Chef::Provider::Service::Solaris,
+              :package => Chef::Provider::Package::Solaris,
+              :cron => Chef::Provider::Cron::Solaris,
+              :group => Chef::Provider::Group::Usermod,
+              :user => Chef::Provider::User::Solaris,
+            }
+          },
+          :smartos => {
+            :default => {
+              :service => Chef::Provider::Service::Solaris,
+              :package => Chef::Provider::Package::SmartOS,
+              :cron => Chef::Provider::Cron::Solaris,
+              :group => Chef::Provider::Group::Usermod
+            }
+          },
+          :netbsd => {
+            :default => {
+              :service => Chef::Provider::Service::Freebsd,
+              :group => Chef::Provider::Group::Groupmod
+            }
+          },
+          :openbsd => {
+            :default => {
+              :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,
+              :cron => Chef::Provider::Cron::Aix,
+              :package => Chef::Provider::Package::Aix
+            }
+          },
+          :default  => {
+            :file => Chef::Provider::File,
+            :directory => Chef::Provider::Directory,
+            :link => Chef::Provider::Link,
+            :template => Chef::Provider::Template,
+            :remote_directory => Chef::Provider::RemoteDirectory,
+            :execute => Chef::Provider::Execute,
+            :mount => Chef::Provider::Mount::Mount,
+            :script => Chef::Provider::Script,
+            :service => Chef::Provider::Service::Init,
+            :perl => Chef::Provider::Script,
+            :python => Chef::Provider::Script,
+            :ruby => Chef::Provider::Script,
+            :bash => Chef::Provider::Script,
+            :csh => Chef::Provider::Script,
+            :user => Chef::Provider::User::Useradd,
+            :group => Chef::Provider::Group::Gpasswd,
+            :http_request => Chef::Provider::HttpRequest,
+            :route => Chef::Provider::Route,
+            :ifconfig => Chef::Provider::Ifconfig,
+            :ruby_block => Chef::Provider::RubyBlock,
+            :erl_call => Chef::Provider::ErlCall,
+            :log => Chef::Provider::Log::ChefLog
+          }
+        }
+      end
+
+      include Chef::Mixin::ParamsValidate
+
+      def find(name, version)
+        provider_map = platforms[:default].clone
+
+        name_sym = name
+        if name.kind_of?(String)
+          name.downcase!
+          name.gsub!(/\s/, "_")
+          name_sym = name.to_sym
+        end
+
+        if platforms.has_key?(name_sym)
+          platform_versions = platforms[name_sym].select {|k, v| k != :default }
+          if platforms[name_sym].has_key?(:default)
+            provider_map.merge!(platforms[name_sym][:default])
+          end
+          platform_versions.each do |platform_version, provider|
+            begin
+              version_constraint = Chef::VersionConstraint::Platform.new(platform_version)
+              if version_constraint.include?(version)
+                Chef::Log.debug("Platform #{name.to_s} version #{version} found")
+                provider_map.merge!(provider)
+              end
+            rescue Chef::Exceptions::InvalidPlatformVersion
+              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
+
+      def find_platform_and_version(node)
+        platform = nil
+        version = nil
+
+        if node[:platform]
+          platform = node[:platform]
+        elsif node.attribute?("os")
+          platform = node[:os]
+        end
+
+        raise ArgumentError, "Cannot find a platform for #{node}" unless platform
+
+        if node[:platform_version]
+          version = node[:platform_version]
+        elsif node[:os_version]
+          version = node[:os_version]
+        elsif node[:os_release]
+          version = node[:os_release]
+        end
+
+        raise ArgumentError, "Cannot find a version for #{node}" unless version
+
+        return platform, version
+      end
+
+      def provider_for_resource(resource, action=:nothing)
+        node = resource.run_context && resource.run_context.node
+        raise ArgumentError, "Cannot find the provider for a resource with no run context set" unless node
+        provider = find_provider_for_node(node, resource).new(resource, resource.run_context)
+        provider.action = action
+        provider
+      end
+
+      def provider_for_node(node, resource_type)
+        raise NotImplementedError, "#{self.class.name} no longer supports #provider_for_node"
+        find_provider_for_node(node, resource_type).new(node, resource_type)
+      end
+
+      def find_provider_for_node(node, resource_type)
+        platform, version = find_platform_and_version(node)
+        find_provider(platform, version, resource_type)
+      end
+
+      def set(args)
+        validate(
+          args,
+          {
+            :platform => {
+              :kind_of => Symbol,
+              :required => false,
+            },
+            :version => {
+              :kind_of => String,
+              :required => false,
+            },
+            :resource => {
+              :kind_of => Symbol,
+            },
+            :provider => {
+              :kind_of => [ String, Symbol, Class ],
+            }
+          }
+        )
+        if args.has_key?(:platform)
+          if args.has_key?(:version)
+            if platforms.has_key?(args[:platform])
+              if platforms[args[:platform]].has_key?(args[:version])
+                platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider]
+              else
+                platforms[args[:platform]][args[:version]] = {
+                  args[:resource].to_sym => args[:provider]
+                }
+              end
+            else
+              platforms[args[:platform]] = {
+                args[:version] => {
+                  args[:resource].to_sym => args[:provider]
+                }
+              }
+            end
+          else
+            if platforms.has_key?(args[:platform])
+              if platforms[args[:platform]].has_key?(:default)
+                platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider]
+              else
+                platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } }
+              end
+            else
+              platforms[args[:platform]] = {
+                :default => {
+                  args[:resource].to_sym => args[:provider]
+                }
+              }
+            end
+          end
+        else
+          if platforms.has_key?(:default)
+            platforms[:default][args[:resource].to_sym] = args[:provider]
+          else
+            platforms[:default] = {
+              args[:resource].to_sym => args[:provider]
+            }
+          end
+        end
+      end
+
+      def find_provider(platform, version, resource_type)
+        provider_klass = explicit_provider(platform, version, resource_type) ||
+                         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?
+
+        provider_klass
+      end
+
+      private
+
+        def explicit_provider(platform, version, resource_type)
+          resource_type.kind_of?(Chef::Resource) ? resource_type.provider : nil
+        end
+
+        def platform_provider(platform, version, resource_type)
+          pmap = Chef::Platform.find(platform, version)
+          rtkey = resource_type.kind_of?(Chef::Resource) ? resource_type.resource_name.to_sym : resource_type
+          pmap.has_key?(rtkey) ? pmap[rtkey] : nil
+        end
+
+        def resource_matching_provider(platform, version, resource_type)
+          if resource_type.kind_of?(Chef::Resource)
+            begin
+              Chef::Provider.const_get(resource_type.class.to_s.split('::').last)
+            rescue NameError
+              nil
+            end
+          else
+            nil
+          end
+        end
+
+    end
+  end
+end
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
new file mode 100644
index 0000000..028a220
--- /dev/null
+++ b/lib/chef/platform/query_helpers.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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.
+#
+
+class Chef
+  class Platform
+
+    class << self
+      def windows?
+        if RUBY_PLATFORM =~ /mswin|mingw|windows/
+          true
+        else
+          false
+        end
+      end
+
+      def windows_server_2003?
+        return false unless windows?
+
+        require 'ruby-wmi'
+
+        host = WMI::Win32_OperatingSystem.find(:first)
+        (host.version && host.version.start_with?("5.2"))
+      end
+    end
+
+  end
+end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 2d4d6a6..3fb86d7 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -19,21 +19,39 @@
 
 require 'chef/mixin/from_file'
 require 'chef/mixin/convert_to_class_name'
-require 'chef/mixin/recipe_definition_dsl_core'
+require 'chef/dsl/recipe'
 require 'chef/mixin/enforce_ownership_and_permissions'
-
+require 'chef/mixin/why_run'
 class Chef
   class Provider
+    include Chef::DSL::Recipe
+    include Chef::Mixin::WhyRun
 
-    include Chef::Mixin::RecipeDefinitionDSLCore
-    include Chef::Mixin::EnforceOwnershipAndPermissions
+    attr_accessor :new_resource
+    attr_accessor :current_resource
+    attr_accessor :run_context
 
-    attr_accessor :new_resource, :current_resource, :run_context
+    #--
+    # TODO: this should be a reader, and the action should be passed in the
+    # constructor; however, many/most subclasses override the constructor so
+    # changing the arity would be a breaking change. Change this at the next
+    # break, e.g., Chef 11.
+    attr_accessor :action
 
     def initialize(new_resource, run_context)
       @new_resource = new_resource
+      @action = action
       @current_resource = nil
       @run_context = run_context
+      @converge_actions = nil
+    end
+
+    def whyrun_mode?
+      Chef::Config[:why_run]
+    end
+
+    def whyrun_supported?
+      false
     end
 
     def node
@@ -53,72 +71,107 @@ class Chef
       raise Chef::Exceptions::Override, "You must override load_current_resource in #{self.to_s}"
     end
 
+    def define_resource_requirements
+    end
+
+    def cleanup_after_converge
+    end
+
     def action_nothing
       Chef::Log.debug("Doing nothing for #{@new_resource.to_s}")
       true
     end
 
-    protected
+    def events
+      run_context.events
+    end
 
-    def recipe_eval(&block)
-      # This block has new resource definitions within it, which
-      # essentially makes it an in-line Chef run. Save our current
-      # run_context and create one anew, so the new Chef run only
-      # executes the embedded resources.
-      #
-      # TODO: timh,cw: 2010-5-14: This means that the resources within
-      # this block cannot interact with resources outside, e.g.,
-      # manipulating notifies.
-      saved_run_context = @run_context
-      @run_context = @run_context.dup
-      @run_context.resource_collection = Chef::ResourceCollection.new
-      instance_eval(&block)
-      Chef::Runner.new(@run_context).converge
+    def run_action(action=nil)
+      @action = action unless action.nil?
 
-      @run_context = saved_run_context
-    end
+      # TODO: it would be preferable to get the action to be executed in the
+      # constructor...
 
-    public
+      # 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
+        events.resource_current_state_loaded(@new_resource, @action, @current_resource)
+      elsif whyrun_mode? && !whyrun_supported?
+        events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
+      end
 
-    class << self
-      include Chef::Mixin::ConvertToClassName
+      define_resource_requirements
+      process_resource_requirements
+
+      # user-defined providers including LWRPs may
+      # not include whyrun support - if they don't support it
+      # we can't execute any actions while we're running in
+      # whyrun mode. Instead we 'fake' whyrun by documenting that
+      # we can't execute the action.
+      # in non-whyrun mode, this will still cause the action to be
+      # executed normally.
+      if whyrun_supported? && !requirements.action_blocked?(@action)
+        send("action_#{@action}")
+      elsif whyrun_mode?
+        events.resource_bypassed(@new_resource, @action, self)
+      else
+        send("action_#{@action}")
+      end
 
-      def build_from_file(cookbook_name, filename, run_context)
-        pname = filename_to_qualified_string(cookbook_name, filename)
+      set_updated_status
 
-        # Add log entry if we override an existing light-weight provider.
-        class_name = convert_to_class_name(pname)
-        overriding = Chef::Provider.const_defined?(class_name)
-        Chef::Log.info("#{class_name} light-weight provider already initialized -- overriding!") if overriding
+      cleanup_after_converge
+    end
 
-        new_provider_class = Class.new self do |cls|
+    def process_resource_requirements
+      requirements.run(:all_actions) unless @action == :nothing
+      requirements.run(@action)
+    end
 
-          def load_current_resource
-            # silence Chef::Exceptions::Override exception
-          end
+    def resource_updated?
+      !converge_actions.empty? || @new_resource.updated_by_last_action?
+    end
 
-          class << cls
-            include Chef::Mixin::FromFile
+    def set_updated_status
+      if !resource_updated?
+        events.resource_up_to_date(@new_resource, @action)
+      else
+        events.resource_updated(@new_resource, @action)
+        new_resource.updated_by_last_action(true)
+      end
+    end
 
-            # setup DSL's shortcut methods
-            def action(name, &block)
-              define_method("action_#{name.to_s}") do
-                instance_eval(&block)
-              end
-            end
-          end
+    def requirements
+      @requirements ||= ResourceRequirements.new(@new_resource, run_context)
+    end
 
-          # load provider definition from file
-          cls.class_from_file(filename)
-        end
+    def converge_by(descriptions, &block)
+      converge_actions.add_action(descriptions, &block)
+    end
 
-        # register new class as a Chef::Provider
-        pname = filename_to_qualified_string(cookbook_name, filename)
-        class_name = convert_to_class_name(pname)
-        Chef::Provider.const_set(class_name, new_provider_class)
-        Chef::Log.debug("Loaded contents of #{filename} into a provider named #{pname} defined in Chef::Provider::#{class_name}")
+    protected
+
+    def converge_actions
+      @converge_actions ||= ConvergeActions.new(@new_resource, run_context, @action)
+    end
+
+    def recipe_eval(&block)
+      # This block has new resource definitions within it, which
+      # essentially makes it an in-line Chef run. Save our current
+      # run_context and create one anew, so the new Chef run only
+      # executes the embedded resources.
+      #
+      # TODO: timh,cw: 2010-5-14: This means that the resources within
+      # this block cannot interact with resources outside, e.g.,
+      # manipulating notifies.
 
-        new_provider_class
+      converge_by ("evaluate block and run any associated actions") do
+        saved_run_context = @run_context
+        @run_context = @run_context.dup
+        @run_context.resource_collection = Chef::ResourceCollection.new
+        instance_eval(&block)
+        Chef::Runner.new(@run_context).converge
+        @run_context = saved_run_context
       end
     end
 
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
new file mode 100644
index 0000000..354a640
--- /dev/null
+++ b/lib/chef/provider/batch.rb
@@ -0,0 +1,35 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/windows_script'
+
+class Chef
+  class Provider
+    class Batch < Chef::Provider::WindowsScript
+
+      def initialize (new_resource, run_context)
+        super(new_resource, run_context, '.bat')
+      end
+
+      def flags
+        @new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c'
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/provider/breakpoint.rb b/lib/chef/provider/breakpoint.rb
index 8ac78f2..224e275 100644
--- a/lib/chef/provider/breakpoint.rb
+++ b/lib/chef/provider/breakpoint.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -19,18 +19,18 @@
 class Chef
   class Provider
     class Breakpoint < Chef::Provider
-      
+
       def load_current_resource
       end
-      
+
       def action_break
-        if defined?(Shef) && Shef.running?
+        if defined?(Shell) && Shell.running?
           run_context.resource_collection.iterator.pause
           @new_resource.updated_by_last_action(true)
           run_context.resource_collection.iterator
         end
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/provider/cookbook_file.rb b/lib/chef/provider/cookbook_file.rb
index 1eb47c3..d599507 100644
--- a/lib/chef/provider/cookbook_file.rb
+++ b/lib/chef/provider/cookbook_file.rb
@@ -16,85 +16,29 @@
 # limitations under the License.
 #
 
-require 'chef/file_access_control'
 require 'chef/provider/file'
-require 'tempfile'
+require 'chef/deprecation/provider/cookbook_file'
+require 'chef/deprecation/warnings'
 
 class Chef
   class Provider
     class CookbookFile < Chef::Provider::File
 
-      def load_current_resource
-        @current_resource = Chef::Resource::CookbookFile.new(@new_resource.name)
-        @new_resource.path.gsub!(/\\/, "/") # for Windows
-        @current_resource.path(@new_resource.path)
-        @current_resource
-      end
-
-      def action_create
-        assert_enclosing_directory_exists!
-        if file_cache_location && content_stale?
-          Chef::Log.debug("#{@new_resource} has new contents")
-          backup_new_resource
-          Tempfile.open(::File.basename(@new_resource.name)) do |staging_file|
-            Chef::Log.debug("#{@new_resource} staging #{file_cache_location} to #{staging_file.path}")
-            staging_file.close
-            stage_file_to_tmpdir(staging_file.path)
-            FileUtils.mv(staging_file.path, @new_resource.path)
-          end
-          Chef::Log.info("#{@new_resource} created file #{@new_resource.path}")
-          @new_resource.updated_by_last_action(true)
-        else
-          set_all_access_controls(@new_resource.path)
-        end
-        @new_resource.updated_by_last_action?
-      end
-
-      def action_create_if_missing
-        if ::File.exists?(@new_resource.path)
-          Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.")
-        else
-          action_create
-        end
-      end
-
-      def file_cache_location
-        @file_cache_location ||= begin
-          cookbook = run_context.cookbook_collection[resource_cookbook]
-          cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path)
-        end
-      end
+      extend Chef::Deprecation::Warnings
+      include Chef::Deprecation::Provider::CookbookFile
+      add_deprecation_warnings_for(Chef::Deprecation::Provider::CookbookFile.instance_methods)
 
-      # Determine the cookbook to get the file from. If new resource sets an
-      # explicit cookbook, use it, otherwise fall back to the implicit cookbook
-      # i.e., the cookbook the resource was declared in.
-      def resource_cookbook
-        @new_resource.cookbook || @new_resource.cookbook_name
+      def initialize(new_resource, run_context)
+        @content_class = Chef::Provider::CookbookFile::Content
+        super
       end
 
-      # Copy the file from the cookbook cache to a temporary location and then
-      # set its file access control settings.
-      def stage_file_to_tmpdir(staging_file_location)
-        FileUtils.cp(file_cache_location, staging_file_location)
-        set_all_access_controls(staging_file_location)
-      end
-
-      def set_all_access_controls(file)
-        access_controls = Chef::FileAccessControl.new(@new_resource, file)
-        access_controls.set_all
-        @new_resource.updated_by_last_action(access_controls.modified?)
-      end
-
-      def backup_new_resource
-        if ::File.exists?(@new_resource.path)
-          backup @new_resource.path
-        end
-      end
-
-      def content_stale?
-        ( ! ::File.exist?(@new_resource.path)) || ( ! compare_content)
+      def load_current_resource
+        @current_resource = Chef::Resource::CookbookFile.new(@new_resource.name)
+        super
       end
 
     end
   end
 end
+
diff --git a/lib/chef/provider/cookbook_file/content.rb b/lib/chef/provider/cookbook_file/content.rb
new file mode 100644
index 0000000..cb777dd
--- /dev/null
+++ b/lib/chef/provider/cookbook_file/content.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/file_content_management/content_base'
+require 'chef/file_content_management/tempfile'
+
+class Chef
+  class Provider
+    class CookbookFile
+      class Content < Chef::FileContentManagement::ContentBase
+
+        private
+
+        def file_for_provider
+          cookbook = run_context.cookbook_collection[resource_cookbook]
+          file_cache_location = cookbook.preferred_filename_on_disk_location(run_context.node, :files, @new_resource.source, @new_resource.path)
+          if file_cache_location.nil?
+            nil
+          else
+            tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
+            tempfile.close
+            Chef::Log.debug("#{@new_resource} staging #{file_cache_location} to #{tempfile.path}")
+            FileUtils.cp(file_cache_location, tempfile.path)
+            tempfile
+          end
+        end
+
+        def resource_cookbook
+          @new_resource.cookbook || @new_resource.cookbook_name
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 3065544..f6f062a 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -25,7 +25,7 @@ class Chef
     class Cron < Chef::Provider
       include Chef::Mixin::Command
 
-      CRON_PATTERN = /\A([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s(.*)/
+      CRON_PATTERN = /\A([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+)\s([-0-9*,\/]+|[a-zA-Z]{3})\s([-0-9*,\/]+|[a-zA-Z]{3})\s(.*)/
       ENV_PATTERN = /\A(\S+)=(\S*)/
 
       CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :command, :mailto, :path, :shell, :home, :environment]
@@ -37,10 +37,15 @@ class Chef
       end
       attr_accessor :cron_exists, :cron_empty
 
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         crontab_lines = []
         @current_resource = Chef::Resource::Cron.new(@new_resource.name)
         @current_resource.user(@new_resource.user)
+        @cron_exists = false
         if crontab = read_crontab
           cron_found = false
           crontab.each_line do |line|
@@ -89,14 +94,7 @@ class Chef
         newcron = String.new
         cron_found = false
 
-        newcron << "# Chef Name: #{new_resource.name}\n"
-        [ :mailto, :path, :shell, :home ].each do |v|
-          newcron << "#{v.to_s.upcase}=#{@new_resource.send(v)}\n" if @new_resource.send(v)
-        end
-        @new_resource.environment.each do |name, value|
-          newcron << "#{name}=#{value}\n"
-        end
-        newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n"
+        newcron = get_crontab_entry
 
         if @cron_exists
           unless cron_different?
@@ -125,20 +123,23 @@ class Chef
             end
             crontab << line
           end
+
           # Handle edge case where the Chef comment is the last line in the current crontab
           crontab << newcron if cron_found
 
-          write_crontab crontab
-          Chef::Log.info("#{@new_resource} updated crontab entry")
-          @new_resource.updated_by_last_action(true)
+          converge_by("update crontab entry for #{@new_resource}") do
+            write_crontab crontab
+            Chef::Log.info("#{@new_resource} updated crontab entry")
+          end
+
         else
           crontab = read_crontab unless @cron_empty
-
           crontab << newcron
 
-          write_crontab crontab
-          Chef::Log.info("#{@new_resource} added crontab entry")
-          @new_resource.updated_by_last_action(true)
+          converge_by("add crontab entry for #{@new_resource}") do
+            write_crontab crontab
+            Chef::Log.info("#{@new_resource} added crontab entry")
+          end
         end
       end
 
@@ -164,10 +165,12 @@ class Chef
             end
             crontab << line
           end
-
-          write_crontab crontab
-          Chef::Log.info("#{@new_resource} deleted crontab entry")
-          @new_resource.updated_by_last_action(true)
+          description = cron_found ? "remove #{@new_resource.name} from crontab" :
+            "save unmodified crontab"
+          converge_by(description) do
+            write_crontab crontab
+            Chef::Log.info("#{@new_resource} deleted crontab entry")
+          end
         end
       end
 
@@ -193,13 +196,33 @@ class Chef
       end
 
       def write_crontab(crontab)
+        write_exception = false
         status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr|
-          stdin.write crontab
+          begin
+            stdin.write crontab
+          rescue Errno::EPIPE => e
+            # popen4 could yield while child has already died.
+            write_exception = true
+            Chef::Log.debug("#{e.message}")
+          end
         end
-        if status.exitstatus > 0
+        if status.exitstatus > 0 || write_exception
           raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}"
         end
       end
+
+      def get_crontab_entry
+        newcron = ""
+        newcron << "# Chef Name: #{new_resource.name}\n"
+        [ :mailto, :path, :shell, :home ].each do |v|
+          newcron << "#{v.to_s.upcase}=#{@new_resource.send(v)}\n" if @new_resource.send(v)
+        end
+        @new_resource.environment.each do |name, value|
+          newcron << "#{name}=#{value}\n"
+        end
+        newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n"
+        newcron
+      end
     end
   end
 end
diff --git a/lib/chef/provider/cron/aix.rb b/lib/chef/provider/cron/aix.rb
new file mode 100644
index 0000000..473700b
--- /dev/null
+++ b/lib/chef/provider/cron/aix.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/provider/cron/unix"
+
+class Chef
+  class Provider
+    class Cron
+      class Aix < Chef::Provider::Cron::Unix
+
+        private
+
+        # For AIX we ignore env vars/[ :mailto, :path, :shell, :home ]
+        def get_crontab_entry
+          if env_vars_are_set?
+            raise Chef::Exceptions::Cron, "Aix cron entry does not support environment variables. Please set them in script and use script in cron."
+          end
+
+          newcron = ""
+          newcron << "# Chef Name: #{new_resource.name}\n"
+          newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday}"
+
+          newcron << " #{@new_resource.command}\n"
+          newcron
+        end
+
+        def env_vars_are_set?
+          @new_resource.environment.length > 0 || !@new_resource.mailto.nil? || !@new_resource.path.nil? || !@new_resource.shell.nil? || !@new_resource.home.nil?
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/cron/solaris.rb b/lib/chef/provider/cron/solaris.rb
index e0811ba..20fa7ab 100644
--- a/lib/chef/provider/cron/solaris.rb
+++ b/lib/chef/provider/cron/solaris.rb
@@ -1,15 +1,13 @@
 #
-# Author:: Bryan McLellan (btm at loftninjas.org)
-# Author:: Toomas Pelberg (toomasp at gmx.net)
-# Copyright:: Copyright (c) 2009 Bryan McLellan
-# Copyright:: Copyright (c) 2010 Toomas Pelberg
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#     http://www.apache.org/licenses/LICENSE-2.0
+# 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,
@@ -18,39 +16,7 @@
 # limitations under the License.
 #
 
-require 'chef/log'
-require 'chef/provider'
+require "chef/provider/cron/unix"
 
-class Chef
-  class Provider
-    class Cron
-      class Solaris < Chef::Provider::Cron
-
-        private
-
-        def read_crontab
-          crontab = nil
-          status = popen4("crontab -l #{@new_resource.user}") do |pid, stdin, stdout, stderr|
-            crontab = stdout.read
-          end
-          if status.exitstatus > 1
-            raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}"
-          end
-          crontab
-        end
-
-        def write_crontab(crontab)
-          tempcron = Tempfile.new("chef-cron")
-          tempcron << crontab
-          tempcron.flush
-          tempcron.chmod(0644)
-          status = run_command(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user)
-          tempcron.close!
-          if status.exitstatus > 0
-            raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{status.exitstatus}"
-          end
-        end
-      end
-    end
-  end
-end
+# Just to create an alias so 'Chef::Provider::Cron::Solaris' is exposed and accessible to existing consumers of class.
+Chef::Provider::Cron::Solaris = Chef::Provider::Cron::Unix
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
new file mode 100644
index 0000000..5cb1bcd
--- /dev/null
+++ b/lib/chef/provider/cron/unix.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Author:: Toomas Pelberg (toomasp at gmx.net)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# Copyright:: Copyright (c) 2010 Toomas Pelberg
+# 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/log'
+require 'chef/provider'
+
+class Chef
+  class Provider
+    class Cron
+      class Unix < Chef::Provider::Cron
+
+        private
+
+        def read_crontab
+          crontab = nil
+          status = popen4("crontab -l #{@new_resource.user}") do |pid, stdin, stdout, stderr|
+            crontab = stdout.read
+          end
+          if status.exitstatus > 1
+            raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}"
+          end
+          crontab
+        end
+
+        def write_crontab(crontab)
+          tempcron = Tempfile.new("chef-cron")
+          tempcron << crontab
+          tempcron.flush
+          tempcron.chmod(0644)
+          exit_status = 0
+          error_message = ""
+          begin
+            status, stdout, stderr = run_command_and_return_stdout_stderr(:command => "/usr/bin/crontab #{tempcron.path}",:user => @new_resource.user)
+            exit_status = status.exitstatus
+            # solaris9, 10 on some failures for example invalid 'mins' in crontab fails with exit code of zero :(
+            if stderr && stderr.include?("errors detected in input, no crontab file generated")
+              error_message = stderr
+              exit_status = 1
+            end
+          rescue Chef::Exceptions::Exec => e
+            Chef::Log.debug(e.message)
+            exit_status = 1
+            error_message = e.message
+          rescue ArgumentError => e
+            # usually raised on invalid user.
+            Chef::Log.debug(e.message)
+            exit_status = 1
+            error_message = e.message
+          end
+          tempcron.close!
+          if exit_status > 0
+            raise Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: #{exit_status}, message: #{error_message}"
+          end
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 9d24a4d..d1017db 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -18,13 +18,16 @@
 
 require "chef/mixin/command"
 require "chef/mixin/from_file"
+require "chef/monkey_patches/fileutils"
 require "chef/provider/git"
 require "chef/provider/subversion"
+require 'chef/dsl/recipe'
 
 class Chef
   class Provider
     class Deploy < Chef::Provider
 
+      include Chef::DSL::Recipe
       include Chef::Mixin::FromFile
       include Chef::Mixin::Command
 
@@ -33,6 +36,8 @@ class Chef
       def initialize(new_resource, run_context)
         super(new_resource, run_context)
 
+        # will resolve to either git or svn based on resource attributes,
+        # and will create a resource corresponding to that provider
         @scm_provider = new_resource.scm_provider.new(new_resource, run_context)
 
         # @configuration is not used by Deploy, it is only for backwards compat with
@@ -41,6 +46,10 @@ class Chef
         @configuration[:environment] = @configuration[:environment] && @configuration[:environment]["RAILS_ENV"]
       end
 
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         @scm_provider.load_current_resource
         @release_path = @new_resource.deploy_to + "/releases/#{release_slug}"
@@ -56,14 +65,40 @@ class Chef
         exec.group(@new_resource.group) if @new_resource.group
         exec.cwd(release_path) unless exec.cwd
         exec.environment(@new_resource.environment) unless exec.environment
-        exec
+        converge_by("execute #{command}") do
+          exec
+        end
+      end
+
+      def define_resource_requirements
+        requirements.assert(:rollback) do |a|
+          a.assertion { all_releases[-2] }
+          a.failure_message(RuntimeError, "There is no release to rollback to!")
+          #There is no reason to assume 2 deployments in a single chef run, hence fails in whyrun.
+        end
+
+        [ @new_resource.before_migrate, @new_resource.before_symlink,
+          @new_resource.before_restart, @new_resource.after_restart ].each do |script|
+          requirements.assert(:deploy, :force_deploy) do |a|
+            callback_file = "#{release_path}/#{script}"
+            a.assertion do
+              if script && script.class == String
+                ::File.exist?(callback_file)
+              else
+                true
+              end
+            end
+            a.failure_message(RuntimeError, "Can't find your callback file #{callback_file}")
+            a.whyrun("Would assume callback file #{callback_file} included in release")
+          end
+        end
+
       end
 
       def action_deploy
         save_release_state
-
-        if deployed?(release_path)
-          if current_release?(release_path) 
+        if deployed?(release_path )
+          if current_release?(release_path )
             Chef::Log.debug("#{@new_resource} is the latest version")
           else
             rollback_to release_path
@@ -72,16 +107,17 @@ class Chef
 
           with_rollback_on_error do
             deploy
-            @new_resource.updated_by_last_action(true)
           end
         end
       end
 
       def action_force_deploy
         if deployed?(release_path)
-          Chef::Log.info("Already deployed app at #{release_path}, forcing.")
-          FileUtils.rm_rf(release_path)
-          Chef::Log.info("#{@new_resource} forcing deploy of already deployed app at #{release_path}")
+          converge_by("delete deployed app at #{release_path} prior to force-deploy") do
+            Chef::Log.info("Already deployed app at #{release_path}, forcing.")
+            FileUtils.rm_rf(release_path)
+            Chef::Log.info("#{@new_resource} forcing deploy of already deployed app at #{release_path}")
+          end
         end
 
         # Alternatives:
@@ -90,7 +126,6 @@ class Chef
         # * Do nothing - because deploy is force, it will be retried in short time
         # Because last is simpliest, keep it
         deploy
-        @new_resource.updated_by_last_action(true)
       end
 
       def action_rollback
@@ -99,7 +134,6 @@ class Chef
 
       def rollback_to(target_release_path)
         @release_path = target_release_path
-        raise RuntimeError, "There is no release to rollback to!" unless @release_path
 
         rp_index = all_releases.index(release_path)
         releases_to_nuke = all_releases[(rp_index + 1)..-1]
@@ -107,17 +141,18 @@ class Chef
         rollback
 
         releases_to_nuke.each do |i|
-          Chef::Log.info "#{@new_resource} removing release: #{i}"
-          FileUtils.rm_rf i
+          converge_by("roll back by removing release #{i}") do
+            Chef::Log.info "#{@new_resource} removing release: #{i}"
+            FileUtils.rm_rf i
+          end
           release_deleted(i)
         end
-        @new_resource.updated_by_last_action(true)
       end
 
       def deploy
-        enforce_ownership
         verify_directories_exist
-        update_cached_repo
+        update_cached_repo # no converge-by - scm provider will dothis
+        enforce_ownership
         copy_cached_repo
         install_gems
         enforce_ownership
@@ -139,6 +174,7 @@ class Chef
         restart
       end
 
+
       def callback(what, callback_code=nil)
         @collection = Chef::ResourceCollection.new
         case callback_code
@@ -146,15 +182,9 @@ class Chef
           Chef::Log.info "#{@new_resource} running callback #{what}"
           recipe_eval(&callback_code)
         when String
-          callback_file = "#{release_path}/#{callback_code}"
-          unless ::File.exist?(callback_file)
-            raise RuntimeError, "Can't find your callback file #{callback_file}"
-          end
-          run_callback_from_file(callback_file)
+          run_callback_from_file("#{release_path}/#{callback_code}")
         when nil
           run_callback_from_file("#{release_path}/deploy/#{what}.rb")
-        else
-          raise RuntimeError, "You gave me a callback I don't know what to do with: #{callback_code.inspect}"
         end
       end
 
@@ -169,8 +199,10 @@ class Chef
             "#{key_and_val.first}='#{key_and_val.last}'"
           end.join(" ")
 
-          Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
-          run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info))
+          converge_by("execute migration command #{@new_resource.migration_command}") do
+            Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
+            run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info))
+          end
         end
       end
 
@@ -187,16 +219,25 @@ class Chef
             Chef::Log.info("#{@new_resource} restarting app with embedded recipe")
             recipe_eval(&restart_cmd)
           else
-            Chef::Log.info("#{@new_resource} restarting app")
-            run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path))
+            converge_by("restart app using command #{@new_resource.restart_command}") do
+              Chef::Log.info("#{@new_resource} restarting app")
+              run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path))
+            end
           end
         end
       end
 
       def cleanup!
-        all_releases[0..-6].each do |old_release|
-          Chef::Log.info "#{@new_resource} removing old release #{old_release}"
-          FileUtils.rm_rf(old_release)
+        converge_by("update release history data") do
+          release_created(release_path)
+        end
+
+        chop = -1 - @new_resource.keep_releases
+        all_releases[0..chop].each do |old_release|
+          converge_by("remove old release #{old_release}") do
+            Chef::Log.info "#{@new_resource} removing old release #{old_release}"
+            FileUtils.rm_rf(old_release)
+          end
           release_deleted(old_release)
         end
       end
@@ -207,6 +248,7 @@ class Chef
 
       def update_cached_repo
         if @new_resource.svn_force_export
+        # TODO assertion, non-recoverable - @scm_provider must be svn if force_export?
           svn_force_export
         else
           run_scm_sync
@@ -214,25 +256,30 @@ class Chef
       end
 
       def run_scm_sync
-        @scm_provider.action_sync
+        @scm_provider.run_action(:sync)
       end
 
       def svn_force_export
         Chef::Log.info "#{@new_resource} exporting source repository"
-        @scm_provider.action_force_export
+        @scm_provider.run_action(:force_export)
       end
 
       def copy_cached_repo
-        FileUtils.mkdir_p(@new_resource.deploy_to + "/releases")
-        run_command(:command => "cp -RPp #{::File.join(@new_resource.destination, ".")} #{release_path}")
-        Chef::Log.info "#{@new_resource} copied the cached checkout to #{release_path}"
-        release_created(release_path)
+        target_dir_path = @new_resource.deploy_to + "/releases"
+        converge_by("deploy from repo to #{@target_dir_path} ") do
+          FileUtils.rm_rf(release_path) if ::File.exist?(release_path)
+          FileUtils.mkdir_p(target_dir_path)
+          FileUtils.cp_r(::File.join(@new_resource.destination, "."), release_path, :preserve => true)
+          Chef::Log.info "#{@new_resource} copied the cached checkout to #{release_path}"
+        end
       end
 
       def enforce_ownership
-        FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to)
-        Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user
-        Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group
+        converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do
+          FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to)
+          Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user
+          Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group
+        end
       end
 
       def verify_directories_exist
@@ -241,44 +288,51 @@ class Chef
       end
 
       def link_current_release_to_production
-        FileUtils.rm_f(@new_resource.current_path)
-        begin
-          FileUtils.ln_sf(release_path, @new_resource.current_path)
-        rescue => e
-          raise Chef::Exceptions::FileNotFound.new("Cannot symlink current release to production: #{e.message}")
+        converge_by(["remove existing link at #{@new_resource.current_path}",
+                    "link release #{release_path} into production at #{@new_resource.current_path}"]) do
+          FileUtils.rm_f(@new_resource.current_path)
+          begin
+            FileUtils.ln_sf(release_path, @new_resource.current_path)
+          rescue => e
+            raise Chef::Exceptions::FileNotFound.new("Cannot symlink current release to production: #{e.message}")
+          end
+          Chef::Log.info "#{@new_resource} linked release #{release_path} into production at #{@new_resource.current_path}"
         end
-        Chef::Log.info "#{@new_resource} linked release #{release_path} into production at #{@new_resource.current_path}"
         enforce_ownership
       end
 
       def run_symlinks_before_migrate
         links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ")
-        @new_resource.symlink_before_migrate.each do |src, dest|
-          begin
-            FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}")
-          rescue => e
-            raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{@new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}")
+        converge_by("make pre-migration symlinks: #{links_info}") do
+          @new_resource.symlink_before_migrate.each do |src, dest|
+            begin
+              FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}")
+            rescue => e
+              raise Chef::Exceptions::FileNotFound.new("Cannot symlink #{@new_resource.shared_path}/#{src} to #{release_path}/#{dest} before migrate: #{e.message}")
+            end
           end
+          Chef::Log.info "#{@new_resource} made pre-migration symlinks"
         end
-        Chef::Log.info "#{@new_resource} made pre-migration symlinks"
       end
 
       def link_tempfiles_to_current_release
         dirs_info = @new_resource.create_dirs_before_symlink.join(",")
-        @new_resource.create_dirs_before_symlink.each do |dir| 
+        @new_resource.create_dirs_before_symlink.each do |dir|
           create_dir_unless_exists(release_path + "/#{dir}")
         end
-        Chef::Log.info("#{@new_resource} created directories before symlinking #{dirs_info}")
+        Chef::Log.info("#{@new_resource} created directories before symlinking: #{dirs_info}")
 
         links_info = @new_resource.symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ")
-        @new_resource.symlinks.each do |src, dest|
-          begin
-            FileUtils.ln_sf(::File.join(@new_resource.shared_path, src), ::File.join(release_path, dest))
-          rescue => e
-            raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(@new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}")
+        converge_by("link shared paths into current release:  #{links_info}") do
+          @new_resource.symlinks.each do |src, dest|
+            begin
+              FileUtils.ln_sf(::File.join(@new_resource.shared_path, src), ::File.join(release_path, dest))
+            rescue => e
+              raise Chef::Exceptions::FileNotFound.new("Cannot symlink shared data #{::File.join(@new_resource.shared_path, src)} to #{::File.join(release_path, dest)}: #{e.message}")
+            end
           end
+          Chef::Log.info("#{@new_resource} linked shared paths into current release: #{links_info}")
         end
-        Chef::Log.info("#{@new_resource} linked shared paths into current release: #{links_info}")
         run_symlinks_before_migrate
         enforce_ownership
       end
@@ -288,19 +342,25 @@ class Chef
 
       def purge_tempfiles_from_current_release
         log_info = @new_resource.purge_before_symlink.join(", ")
-        @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") }
-        Chef::Log.info("#{@new_resource} purged directories in checkout #{log_info}")
+        converge_by("purge directories in checkout #{log_info}") do
+          @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") }
+          Chef::Log.info("#{@new_resource} purged directories in checkout #{log_info}")
+        end
       end
 
       protected
 
       # Internal callback, called after copy_cached_repo.
       # Override if you need to keep state externally.
+      # Note that YOU are responsible for implementing whyrun-friendly behavior
+      # in any actions you take in this callback.
       def release_created(release_path)
       end
 
-      # Internal callback, called during cleanup! for each old release removed.
+      # Note that YOU are responsible for using appropriate whyrun nomenclature
       # Override if you need to keep state externally.
+      # Note that YOU are responsible for implementing whyrun-friendly behavior
+      # in any actions you take in this callback.
       def release_deleted(release_path)
       end
 
@@ -348,10 +408,10 @@ class Chef
       end
 
       def run_callback_from_file(callback_file)
-        if ::File.exist?(callback_file)
+        Chef::Log.info "#{@new_resource} queueing checkdeploy hook #{callback_file}"
+        recipe_eval do
           Dir.chdir(release_path) do
-            Chef::Log.info "#{@new_resource} running deploy hook #{callback_file}"
-            recipe_eval { from_file(callback_file) }
+            from_file(callback_file) if ::File.exist?(callback_file)
           end
         end
       end
@@ -361,20 +421,21 @@ class Chef
           Chef::Log.debug "#{@new_resource} not creating #{dir} because it already exists"
           return false
         end
-
-        begin
-          FileUtils.mkdir_p(dir)
-          Chef::Log.debug "#{@new_resource} created directory #{dir}"
-          if @new_resource.user
-            FileUtils.chown(@new_resource.user, nil, dir)
-            Chef::Log.debug("#{@new_resource} set user to #{@new_resource.user} for #{dir}")
-          end
-          if @new_resource.group
-            FileUtils.chown(nil, @new_resource.group, dir)
-            Chef::Log.debug("#{@new_resource} set group to #{@new_resource.group} for #{dir}")
+        converge_by("create new directory #{dir}") do
+          begin
+            FileUtils.mkdir_p(dir)
+            Chef::Log.debug "#{@new_resource} created directory #{dir}"
+            if @new_resource.user
+              FileUtils.chown(@new_resource.user, nil, dir)
+              Chef::Log.debug("#{@new_resource} set user to #{@new_resource.user} for #{dir}")
+            end
+            if @new_resource.group
+              FileUtils.chown(nil, @new_resource.group, dir)
+              Chef::Log.debug("#{@new_resource} set group to #{@new_resource.group} for #{dir}")
+            end
+          rescue => e
+            raise Chef::Exceptions::FileNotFound.new("Cannot create directory #{dir}: #{e.message}")
           end
-        rescue => e
-          raise Chef::Exceptions::FileNotFound.new("Cannot create directory #{dir}: #{e.message}")
         end
       end
 
@@ -382,19 +443,20 @@ class Chef
         yield
       rescue ::Exception => e
         if @new_resource.rollback_on_error
-          Chef::Log.warn "Error on deploying #{release_path}: #{e.message}" 
+          Chef::Log.warn "Error on deploying #{release_path}: #{e.message}"
           failed_release = release_path
-        
+
           if previous_release_path
             @release_path = previous_release_path
             rollback
           end
-
-          Chef::Log.info "Removing failed deploy #{failed_release}"
-          FileUtils.rm_rf failed_release
+          converge_by("remove failed deploy #{failed_release}") do
+            Chef::Log.info "Removing failed deploy #{failed_release}"
+            FileUtils.rm_rf failed_release
+          end
           release_deleted(failed_release)
         end
-        
+
         raise
       end
 
diff --git a/lib/chef/provider/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb
index 3728fc6..f1eb171 100644
--- a/lib/chef/provider/deploy/revision.rb
+++ b/lib/chef/provider/deploy/revision.rb
@@ -32,6 +32,25 @@ class Chef
           sorted_releases
         end
 
+        def action_deploy
+          validate_release_history!
+          super
+        end
+
+        def cleanup!
+          super
+
+          known_releases = sorted_releases
+
+          Dir["#{new_resource.deploy_to}/releases/*"].each do |release_dir|
+            unless known_releases.include?(release_dir)
+              converge_by("Remove unknown release in #{release_dir}") do
+                FileUtils.rm_rf(release_dir)
+              end
+            end
+          end
+        end
+
         protected
 
         def release_created(release)
@@ -57,6 +76,14 @@ class Chef
           cache
         end
 
+        def validate_release_history!
+          sorted_releases do |release_list|
+            release_list.each do |path|
+              release_list.delete(path) unless ::File.exist?(path)
+            end
+          end
+        end
+
         def sorted_releases_from_filesystem
           Dir.glob(new_resource.deploy_to + "/releases/*").sort_by { |d| ::File.ctime(d) }
         end
diff --git a/lib/chef/provider/deploy/timestamped.rb b/lib/chef/provider/deploy/timestamped.rb
index 9c2d55b..ce92116 100644
--- a/lib/chef/provider/deploy/timestamped.rb
+++ b/lib/chef/provider/deploy/timestamped.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -20,9 +20,9 @@ class Chef
   class Provider
     class Deploy
       class Timestamped < Chef::Provider::Deploy
-        
+
         protected
-        
+
         def release_slug
           Time.now.utc.strftime("%Y%m%d%H%M%S")
         end
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 4914f72..e3d2b89 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -26,43 +26,101 @@ require 'fileutils'
 class Chef
   class Provider
     class Directory < Chef::Provider::File
+
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         @current_resource = Chef::Resource::Directory.new(@new_resource.name)
         @current_resource.path(@new_resource.path)
-        if ::File.exist?(@current_resource.path) && ::File.directory?(@current_resource.path)
-          cstats = ::File.stat(@current_resource.path)
-          @current_resource.owner(cstats.uid)
-          @current_resource.group(cstats.gid)
-          @current_resource.mode("%o" % (cstats.mode & 007777))
+        if ::File.exists?(@current_resource.path) && @action != :create_if_missing
+          load_resource_attributes_from_file(@current_resource)
         end
         @current_resource
       end
 
+      def 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.
+          parent_directory = ::File.dirname(@new_resource.path)
+          a.assertion { @new_resource.recursive || ::File.directory?(parent_directory) }
+          a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist, cannot create #{@new_resource.path}")
+          a.whyrun("Assuming directory #{parent_directory} would have been created")
+        end
+
+        requirements.assert(:create) do |a|
+          parent_directory = ::File.dirname(@new_resource.path)
+          a.assertion do
+            if @new_resource.recursive
+              # find the lowest-level directory in @new_resource.path that already exists
+              # make sure we have write permissions to that directory
+              is_parent_writable = lambda do |base_dir|
+                base_dir = ::File.dirname(base_dir)
+                if ::File.exists?(base_dir)
+                  ::File.writable?(base_dir)
+                else
+                  is_parent_writable.call(base_dir)
+                end
+              end
+              is_parent_writable.call(@new_resource.path)
+            else
+              # in why run mode & parent directory does not exist no permissions check is required
+              # If not in why run, permissions must be valid and we rely on prior assertion that dir exists
+              if !whyrun_mode? || ::File.exists?(parent_directory)
+                ::File.writable?(parent_directory)
+              else
+                true
+              end
+            end
+          end
+          a.failure_message(Chef::Exceptions::InsufficientPermissions,
+            "Cannot create #{@new_resource} at #{@new_resource.path} due to insufficient permissions")
+        end
+
+        requirements.assert(:delete) do |a|
+          a.assertion do
+            if ::File.exists?(@new_resource.path)
+              ::File.directory?(@new_resource.path) && ::File.writable?(@new_resource.path)
+            else
+              true
+            end
+          end
+          a.failure_message(RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource.path}!")
+          # No why-run handling here:
+          #  * if we don't have permissions, this is unlikely to be changed earlier in the run
+          #  * if the target is a file (not a dir), there's no reasonable path by which this would have been changed
+        end
+      end
+
       def action_create
         unless ::File.exists?(@new_resource.path)
-          if @new_resource.recursive == true
-            ::FileUtils.mkdir_p(@new_resource.path)
-          else
-            ::Dir.mkdir(@new_resource.path)
+          converge_by("create new directory #{@new_resource.path}") do
+            if @new_resource.recursive == true
+              ::FileUtils.mkdir_p(@new_resource.path)
+            else
+              ::Dir.mkdir(@new_resource.path)
+            end
+            Chef::Log.info("#{@new_resource} created directory #{@new_resource.path}")
           end
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} created directory #{@new_resource.path}")
         end
-        enforce_ownership_and_permissions
+        do_acl_changes
+        do_selinux(true)
+        load_resource_attributes_from_file(@new_resource)
       end
 
       def action_delete
-        if ::File.directory?(@new_resource.path) && ::File.writable?(@new_resource.path)
-          if @new_resource.recursive == true
-            FileUtils.rm_rf(@new_resource.path)
-            Chef::Log.info("#{@new_resource} deleted #{@new_resource.path} recursively")
-          else
-            ::Dir.delete(@new_resource.path)
-            Chef::Log.info("#{@new_resource} deleted #{@new_resource.path}")
+        if ::File.exists?(@new_resource.path)
+          converge_by("delete existing directory #{@new_resource.path}") do
+            if @new_resource.recursive == true
+              FileUtils.rm_rf(@new_resource.path)
+              Chef::Log.info("#{@new_resource} deleted #{@new_resource.path} recursively")
+            else
+              ::Dir.delete(@new_resource.path)
+              Chef::Log.info("#{@new_resource} deleted #{@new_resource.path}")
+            end
           end
-          @new_resource.updated_by_last_action(true)
-        else
-          raise RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource_path}!" if ::File.exists?(@new_resource.path)
         end
       end
     end
diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb
index d3ac253..cdd494a 100644
--- a/lib/chef/provider/erl_call.rb
+++ b/lib/chef/provider/erl_call.rb
@@ -29,6 +29,10 @@ class Chef
         super(node, new_resource)
       end
 
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         true
       end
@@ -55,45 +59,46 @@ class Chef
 
         command = "erl_call -e #{distributed} #{node} #{cookie}"
 
-        begin
-          pid, stdin, stdout, stderr = popen4(command, :waitlast => true)
+        converge_by("run erlang block") do
+          begin
+            pid, stdin, stdout, stderr = popen4(command, :waitlast => true)
 
-          Chef::Log.debug("#{@new_resource} running")
-          Chef::Log.debug("#{@new_resource} command: #{command}")
-          Chef::Log.debug("#{@new_resource} code: #{@new_resource.code}")
+            Chef::Log.debug("#{@new_resource} running")
+            Chef::Log.debug("#{@new_resource} command: #{command}")
+            Chef::Log.debug("#{@new_resource} code: #{@new_resource.code}")
 
-          @new_resource.code.each_line { |line| stdin.puts(line.chomp) }
+            @new_resource.code.each_line { |line| stdin.puts(line.chomp) }
 
-          stdin.close
+            stdin.close
 
-          Chef::Log.debug("#{@new_resource} output: ")
+            Chef::Log.debug("#{@new_resource} output: ")
 
-          stdout_output = ""
-          stdout.each_line { |line| stdout_output << line }
-          stdout.close
+            stdout_output = ""
+            stdout.each_line { |line| stdout_output << line }
+            stdout.close
 
-          stderr_output = ""
-          stderr.each_line { |line| stderr_output << line }
-          stderr.close
+            stderr_output = ""
+            stderr.each_line { |line| stderr_output << line }
+            stderr.close
 
-          # fail if stderr contains anything
-          if stderr_output.length > 0
-            raise Chef::Exceptions::ErlCall, stderr_output
-          end
+            # fail if stderr contains anything
+            if stderr_output.length > 0
+              raise Chef::Exceptions::ErlCall, stderr_output
+            end
 
-          # fail if the first 4 characters aren't "{ok,"
-          unless stdout_output[0..3].include?('{ok,')
-            raise Chef::Exceptions::ErlCall, stdout_output
-          end
+            # fail if the first 4 characters aren't "{ok,"
+            unless stdout_output[0..3].include?('{ok,')
+              raise Chef::Exceptions::ErlCall, stdout_output
+            end
 
-          @new_resource.updated_by_last_action(true)
+            @new_resource.updated_by_last_action(true)
 
-          Chef::Log.debug("#{@new_resource} #{stdout_output}")
-          Chef::Log.info("#{@new_resouce} ran successfully")
-        ensure
-          Process.wait(pid) if pid
+            Chef::Log.debug("#{@new_resource} #{stdout_output}")
+            Chef::Log.info("#{@new_resouce} ran successfully")
+          ensure
+            Process.wait(pid) if pid
+          end
         end
-
       end
 
     end
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index da2fe7f..2907688 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -30,14 +30,16 @@ class Chef
         true
       end
 
+      def whyrun_supported?
+        true
+      end
+
       def action_run
         opts = {}
 
-        if sentinel_file = @new_resource.creates
-          if ::File.exists?(sentinel_file)
-            Chef::Log.debug("#{@new_resource} sentinel file #{sentinel_file} exists - nothing to do")
-            return false
-          end
+        if sentinel_file = sentinel_file_if_exists
+          Chef::Log.debug("#{@new_resource} sentinel file #{sentinel_file} exists - nothing to do")
+          return false
         end
 
         # original implementation did not specify a timeout, but ShellOut
@@ -54,12 +56,30 @@ class Chef
         if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info?
           opts[:live_stream] = STDOUT
         end
-
-        result = shell_out!(@new_resource.command, opts)
-        @new_resource.updated_by_last_action(true)
-        Chef::Log.info("#{@new_resource} ran successfully")
+        converge_by("execute #{@new_resource.command}") do
+          result = shell_out!(@new_resource.command, opts)
+          Chef::Log.info("#{@new_resource} ran successfully")
+        end
       end
 
+      private
+
+      def sentinel_file_if_exists
+        if sentinel_file = @new_resource.creates
+          relative = Pathname(sentinel_file).relative?
+          cwd = @new_resource.cwd
+          if relative && !cwd
+            Chef::Log.warn "You have provided relative path for execute#creates (#{sentinel_file}) without execute#cwd (see CHEF-3819)"
+          end
+
+          if ::File.exists?(sentinel_file)
+            sentinel_file
+          elsif cwd && relative
+            sentinel_file = ::File.join(cwd, sentinel_file)
+            sentinel_file if ::File.exists?(sentinel_file)
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index d409cb2..e727aa9 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -1,6 +1,7 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2008-2013 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,66 +20,112 @@
 require 'chef/config'
 require 'chef/log'
 require 'chef/resource/file'
-require 'chef/mixin/checksum'
 require 'chef/provider'
 require 'etc'
 require 'fileutils'
+require 'chef/scan_access_control'
+require 'chef/mixin/checksum'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/file_class'
+require 'chef/util/backup'
+require 'chef/util/diff'
+require 'chef/deprecation/provider/file'
+require 'chef/deprecation/warnings'
+require 'chef/file_content_management/deploy'
+
+# The Tao of File Providers:
+#  - the content provider must always return a tempfile that we can delete/mv
+#  - do_create_file shall always create the file first and obey umask when perms are not specified
+#  - do_contents_changes may assume the destination file exists (simplifies exception checking,
+#    and always gives us something to diff against)
+#  - do_contents_changes must restore the perms to the dest file and not obliterate them with
+#    random tempfile permissions
+#  - do_acl_changes may assume perms were not modified between lcr and when it runs (although the
+#    file may have been created)
 
 class Chef
   class Provider
     class File < Chef::Provider
+      include Chef::Mixin::EnforceOwnershipAndPermissions
       include Chef::Mixin::Checksum
+      include Chef::Mixin::ShellOut
+      include Chef::Util::Selinux
+      include Chef::Mixin::FileClass
 
-      def negative_complement(big)
-        if big > 1073741823 # Fixnum max
-          big -= (2**32) # diminished radix wrap to negative
+      extend Chef::Deprecation::Warnings
+      include Chef::Deprecation::Provider::File
+      add_deprecation_warnings_for(Chef::Deprecation::Provider::File.instance_methods)
+
+      attr_reader :deployment_strategy
+
+      def initialize(new_resource, run_context)
+        @content_class ||= Chef::Provider::File::Content
+        if new_resource.respond_to?(:atomic_update)
+          @deployment_strategy = Chef::FileContentManagement::Deploy.strategy(new_resource.atomic_update)
         end
-        big
+        super
       end
 
-      def octal_mode(mode)
-        ((mode.respond_to?(:oct) ? mode.oct : mode.to_i) & 007777)
+      def whyrun_supported?
+        true
       end
 
-      private :negative_complement, :octal_mode
-
       def load_current_resource
-        @current_resource = Chef::Resource::File.new(@new_resource.name)
-        @new_resource.path.gsub!(/\\/, "/") # for Windows
+        # Let children resources override constructing the @current_resource
+        @current_resource ||= Chef::Resource::File.new(@new_resource.name)
         @current_resource.path(@new_resource.path)
+        if ::File.exists?(@current_resource.path) && ::File.file?(::File.realpath(@current_resource.path))
+          if @action != :create_if_missing && @current_resource.respond_to?(:checksum)
+            @current_resource.checksum(checksum(@current_resource.path))
+          end
+          load_resource_attributes_from_file(@current_resource)
+        end
         @current_resource
       end
 
-      # Compare the content of a file.  Returns true if they are the same, false if they are not.
-      def compare_content
-        checksum(@current_resource.path) == new_resource_content_checksum
-      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
+        # Make sure the parent directory exists, otherwise fail.  For why-run assume it would have been created.
+        requirements.assert(:create, :create_if_missing, :touch) do |a|
+          parent_directory = ::File.dirname(@new_resource.path)
+          a.assertion { ::File.directory?(parent_directory) }
+          a.failure_message(Chef::Exceptions::EnclosingDirectoryDoesNotExist, "Parent directory #{parent_directory} does not exist.")
+          a.whyrun("Assuming directory #{parent_directory} would have been created")
+        end
+
+        # Make sure the file is deletable if it exists, otherwise fail.
+        if ::File.exists?(@new_resource.path)
+          requirements.assert(:delete) do |a|
+            a.assertion { ::File.writable?(@new_resource.path) }
+            a.failure_message(Chef::Exceptions::InsufficientPermissions,"File #{@new_resource.path} exists but is not writable so it cannot be deleted")
+          end
+        end
 
-      # Set the content of the file, assuming it is not set correctly already.
-      def set_content
-        unless compare_content
-          backup @new_resource.path if ::File.exists?(@new_resource.path)
-          ::File.open(@new_resource.path, "w") {|f| f.write @new_resource.content }
-          Chef::Log.info("#{@new_resource} contents updated")
-          @new_resource.updated_by_last_action(true)
+        error, reason, whyrun_message = inspect_existing_fs_entry
+        requirements.assert(:create) do |a|
+          a.assertion { error.nil? }
+          a.failure_message(error, reason)
+          a.whyrun(whyrun_message)
+          # Subsequent attempts to read the fs entry at the path (e.g., for
+          # calculating checksums) could blow up, so give up trying to continue
+          # why-running.
+          a.block_action!
         end
       end
 
       def action_create
-        assert_enclosing_directory_exists!
-        unless ::File.exists?(@new_resource.path)
-          ::File.open(@new_resource.path, "w+") {|f| f.write @new_resource.content }
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} created file #{@new_resource.path}")
-        else
-          set_content unless @new_resource.content.nil?
-        end
-        enforce_ownership_and_permissions
+        do_unlink
+        do_create_file
+        do_contents_changes
+        do_acl_changes
+        do_selinux
+        load_resource_attributes_from_file(@new_resource)
       end
 
       def action_create_if_missing
         if ::File.exists?(@new_resource.path)
-          Chef::Log.debug("File #{@new_resource.path} exists, taking no action.")
+          Chef::Log.debug("#{@new_resource} exists at #{@new_resource.path} taking no action.")
         else
           action_create
         end
@@ -86,67 +133,278 @@ class Chef
 
       def action_delete
         if ::File.exists?(@new_resource.path)
-          if ::File.writable?(@new_resource.path)
-            backup unless ::File.symlink?(@new_resource.path)
+          converge_by("delete file #{@new_resource.path}") do
+            do_backup unless file_class.symlink?(@new_resource.path)
             ::File.delete(@new_resource.path)
             Chef::Log.info("#{@new_resource} deleted file at #{@new_resource.path}")
-            @new_resource.updated_by_last_action(true)
-          else
-            raise "Cannot delete #{@new_resource} at #{@new_resource_path}!"
           end
         end
       end
 
       def action_touch
         action_create
-        time = Time.now
-        ::File.utime(time, time, @new_resource.path)
-        Chef::Log.info("#{@new_resource} updated atime and mtime to #{time}")
-        @new_resource.updated_by_last_action(true)
+        converge_by("update utime on file #{@new_resource.path}") do
+          time = Time.now
+          ::File.utime(time, time, @new_resource.path)
+          Chef::Log.info("#{@new_resource} updated atime and mtime to #{time}")
+        end
       end
 
-      def backup(file=nil)
-        file ||= @new_resource.path
-        if @new_resource.backup != false && @new_resource.backup > 0 && ::File.exist?(file)
-          time = Time.now
-          savetime = time.strftime("%Y%m%d%H%M%S")
-          backup_filename = "#{@new_resource.path}.chef-#{savetime}"
-          backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows
-          # if :file_backup_path is nil, we fallback to the old behavior of
-          # keeping the backup in the same directory. We also need to to_s it
-          # so we don't get a type error around implicit to_str conversions.
-          prefix = Chef::Config[:file_backup_path].to_s
-          backup_path = ::File.join(prefix, backup_filename)
-          FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path]
-          FileUtils.cp(file, backup_path, :preserve => true)
-          Chef::Log.info("#{@new_resource} backed up to #{backup_path}")
-
-          # Clean up after the number of backups
-          slice_number = @new_resource.backup
-          backup_files = Dir[::File.join(prefix, ".#{@new_resource.path}.chef-*")].sort { |a,b| b <=> a }
-          if backup_files.length >= @new_resource.backup
-            remainder = backup_files.slice(slice_number..-1)
-            remainder.each do |backup_to_delete|
-              FileUtils.rm(backup_to_delete)
-              Chef::Log.info("#{@new_resource} removed backup at #{backup_to_delete}")
+      # Implementation components *should* follow symlinks when managing access
+      # control (e.g., use chmod instead of lchmod even if the path we're
+      # managing is a symlink).
+      def manage_symlink_access?
+        false
+      end
+
+      private
+
+      # Handles resource requirements for action :create when some fs entry
+      # already exists at the destination path. For actions other than create,
+      # we don't care what kind of thing is at the destination path because:
+      # * for :create_if_missing, we're assuming the user wanted to avoid blowing away the non-file here
+      # * for :touch, we can modify perms of whatever is at this path, regardless of its type
+      # * for :delete, we can blow away whatever is here, regardless of its type
+      #
+      # For the action :create case, we need to deal with user-selectable
+      # behavior to see if we're in an error condition.
+      # * If there's no file at the destination path currently, we're cool to
+      #   create it.
+      # * If the fs entry that currently exists at the destination is a regular
+      #   file, we're cool to update it with new content.
+      # * If the fs entry is a symlink AND the resource has
+      #   `manage_symlink_source` enabled, we need to verify that the symlink is
+      #   a valid pointer to a real file. If it is, we can manage content and
+      #   permissions on the symlink source, otherwise, error.
+      # * If `manage_symlink_source` is not enabled, fall through.
+      # * If force_unlink is true, action :create will unlink whatever is in the way.
+      # * If force_unlink is false, we're in an exceptional situation, so we
+      #   want to error.
+      #
+      # Note that this method returns values to be used with requirement
+      # assertions, which then decide whether or not to raise or issue a
+      # warning for whyrun mode.
+      def inspect_existing_fs_entry
+        path = @new_resource.path
+
+        if !l_exist?(path)
+          [nil, nil, nil]
+        elsif real_file?(path)
+          [nil, nil, nil]
+        elsif file_class.symlink?(path) && @new_resource.manage_symlink_source
+          verify_symlink_sanity(path)
+        elsif file_class.symlink?(@new_resource.path) && @new_resource.manage_symlink_source.nil?
+          Chef::Log.warn("File #{path} managed by #{@new_resource} is really a symlink. Managing the source file instead.")
+          Chef::Log.warn("Disable this warning by setting `manage_symlink_source true` on the resource")
+          Chef::Log.warn("In a future Chef release, 'manage_symlink_source' will not be enabled by default")
+          verify_symlink_sanity(path)
+        elsif @new_resource.force_unlink
+          [nil, nil, nil]
+        else
+          [ Chef::Exceptions::FileTypeMismatch,
+            "File #{path} exists, but is a #{file_type_string(@new_resource.path)}, set force_unlink to true to remove",
+            "Assuming #{file_type_string(@new_resource.path)} at #{@new_resource.path} would have been removed by a previous resource"
+          ]
+        end
+      end
+
+      # Returns values suitable for use in a requirements assertion statement
+      # when managing symlink source. If we're managing symlink source we can
+      # hit 3 error cases:
+      # 1. Symlink to nowhere: File.realpath(symlink) -> raise Errno::ENOENT
+      # 2. Symlink loop: File.realpath(symlink) -> raise Errno::ELOOP
+      # 3. Symlink to not-a-real-file: File.realpath(symlink) -> (directory|blockdev|etc.)
+      # If any of the above apply, returns a 3-tuple of Exception class,
+      # exception message, whyrun message; otherwise returns a 3-tuple of nil.
+      def verify_symlink_sanity(path)
+        real_path = ::File.realpath(path)
+        if real_file?(real_path)
+          [nil, nil, nil]
+        else
+          [ Chef::Exceptions::FileTypeMismatch,
+            "File #{path} exists, but is a symlink to #{real_path} which is a #{file_type_string(real_path)}. " +
+            "Disable manage_symlink_source and set force_unlink to remove it.",
+            "Assuming symlink #{path} or source file #{real_path} would have been fixed by a previous resource"
+          ]
+        end
+      rescue Errno::ELOOP
+        [ Chef::Exceptions::InvalidSymlink,
+          "Symlink at #{path} (pointing to #{::File.readlink(path)}) exists but attempting to resolve it creates a loop",
+          "Assuming symlink loop would be fixed by a previous resource" ]
+      rescue Errno::ENOENT
+        [ Chef::Exceptions::InvalidSymlink,
+          "Symlink at #{path} (pointing to #{::File.readlink(path)}) exists but attempting to resolve it leads to a nonexistent file",
+          "Assuming symlink source would be created by a previous resource" ]
+      end
+
+
+      def content
+        @content ||= begin
+           load_current_resource if @current_resource.nil?
+           @content_class.new(@new_resource, @current_resource, @run_context)
+        end
+      end
+
+      def file_type_string(path)
+        case
+        when ::File.blockdev?(path)
+          "block device"
+        when ::File.chardev?(path)
+          "char device"
+        when ::File.directory?(path)
+          "directory"
+        when ::File.pipe?(path)
+          "pipe"
+        when ::File.socket?(path)
+          "socket"
+        when file_class.symlink?(path)
+          "symlink"
+        else
+          "unknown filetype"
+        end
+      end
+
+      def real_file?(path)
+        !file_class.symlink?(path) && ::File.file?(path)
+      end
+
+      # Similar to File.exist?, but also returns true in the case that the
+      # named file is a broken symlink.
+      def l_exist?(path)
+        ::File.exist?(path) || file_class.symlink?(path)
+      end
+
+      def unlink(path)
+        # Directories can not be unlinked. Remove them using FileUtils.
+        if ::File.directory?(path)
+          FileUtils.rm_rf(path)
+        else
+          ::File.unlink(path)
+        end
+      end
+
+      def do_unlink
+        @file_unlinked = false
+        if @new_resource.force_unlink
+          if !real_file?(@new_resource.path)
+            # unlink things that aren't normal files
+            description = "unlink #{file_type_string(@new_resource.path)} at #{@new_resource.path}"
+            converge_by(description) do
+              unlink(@new_resource.path)
             end
+            @current_resource.checksum = nil
+            @file_unlinked = true
           end
         end
       end
 
-      private
+      def file_unlinked?
+        @file_unlinked == true
+      end
+
+      def do_create_file
+        @file_created = false
+        if !::File.exists?(@new_resource.path) || file_unlinked?
+          converge_by("create new file #{@new_resource.path}") do
+            deployment_strategy.create(@new_resource.path)
+            Chef::Log.info("#{@new_resource} created file #{@new_resource.path}")
+          end
+          @file_created = true
+        end
+      end
+
+      # do_contents_changes needs to know if do_create_file created a file or not
+      def file_created?
+        @file_created == true
+      end
+
+      def do_backup(file = nil)
+        Chef::Util::Backup.new(@new_resource, file).backup!
+      end
+
+      def diff
+        @diff ||= Chef::Util::Diff.new
+      end
+
+      def update_file_contents
+        do_backup unless file_created?
+        deployment_strategy.deploy(tempfile.path, ::File.realpath(@new_resource.path))
+        Chef::Log.info("#{@new_resource} updated file contents #{@new_resource.path}")
+        @new_resource.checksum(checksum(@new_resource.path)) # for reporting
+      end
+
+      def do_contents_changes
+        # a nil tempfile is okay, means the resource has no content or no new content
+        return if tempfile.nil?
+        # but a tempfile that has no path or doesn't exist should not happen
+        if tempfile.path.nil? || !::File.exists?(tempfile.path)
+          raise "chef-client is confused, trying to deploy a file that has no path or does not exist..."
+        end
+        # the file? on the next line suppresses the case in why-run when we have a not-file here that would have otherwise been removed
+        if ::File.file?(@new_resource.path) && contents_changed?
+          diff.diff(@current_resource.path, tempfile.path)
+          @new_resource.diff( diff.for_reporting ) unless file_created?
+          description = [ "update content in file #{@new_resource.path} from #{short_cksum(@current_resource.checksum)} to #{short_cksum(checksum(tempfile.path))}" ]
+          description << diff.for_output
+          converge_by(description) do
+            update_file_contents
+          end
+        end
+        # unlink necessary to clean up in why-run mode
+        tempfile.unlink
+      end
 
-      def assert_enclosing_directory_exists!
-        enclosing_dir = ::File.dirname(@new_resource.path)
-        unless ::File.directory?(enclosing_dir)
-          msg = "Cannot create a file at #{@new_resource.path} because the enclosing directory (#{enclosing_dir}) does not exist"
-          raise Chef::Exceptions::EnclosingDirectoryDoesNotExist, msg
+      # This logic ideally will be  made into some kind of generic
+      # platform-dependent post-converge hook for file-like
+      # resources, but for now we only have the single selinux use
+      # case.
+      def do_selinux(recursive = false)
+        if resource_updated? && Chef::Config[:enable_selinux_file_permission_fixup]
+          if selinux_enabled?
+            converge_by("restore selinux security context") do
+              restore_security_context(::File.realpath(@new_resource.path), recursive)
+            end
+          else
+            Chef::Log.debug "selinux utilities can not be found. Skipping selinux permission fixup."
+          end
+        end
+      end
+
+      def do_acl_changes
+        if access_controls.requires_changes?
+          converge_by(access_controls.describe_changes) do
+            access_controls.set_all
+          end
         end
       end
 
-      def new_resource_content_checksum
-        @new_resource.content && Digest::SHA2.hexdigest(@new_resource.content)
+      def contents_changed?
+        checksum(tempfile.path) != @current_resource.checksum
       end
+
+      def tempfile
+        content.tempfile
+      end
+
+      def short_cksum(checksum)
+        return "none" if checksum.nil?
+        checksum.slice(0,6)
+      end
+
+      def load_resource_attributes_from_file(resource)
+
+        if Chef::Platform.windows?
+          # This is a work around for CHEF-3554.
+          # OC-6534: is tracking the real fix for this workaround.
+          # Add support for Windows equivalent, or implicit resource
+          # reporting won't work for Windows.
+          return
+        end
+        acl_scanner = ScanAccessControl.new(@new_resource, resource)
+        acl_scanner.set_all!
+      end
+
     end
   end
 end
+
diff --git a/lib/chef/provider/file/content.rb b/lib/chef/provider/file/content.rb
new file mode 100644
index 0000000..f82bc49
--- /dev/null
+++ b/lib/chef/provider/file/content.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/file_content_management/content_base'
+require 'chef/file_content_management/tempfile'
+
+class Chef
+  class Provider
+    class File
+      class Content < Chef::FileContentManagement::ContentBase
+        def file_for_provider
+          if @new_resource.content
+            tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
+            tempfile.write(@new_resource.content)
+            tempfile.close
+            tempfile
+          else
+            nil
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index e2eb05e..b22004e 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -21,7 +21,6 @@ require 'chef/log'
 require 'chef/provider'
 require 'chef/mixin/shell_out'
 require 'fileutils'
-require 'shellwords'
 
 class Chef
   class Provider
@@ -29,22 +28,56 @@ class Chef
 
       include Chef::Mixin::ShellOut
 
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
+        @resolved_reference = nil
         @current_resource = Chef::Resource::Git.new(@new_resource.name)
         if current_revision = find_current_revision
           @current_resource.revision current_revision
         end
       end
 
-      def action_checkout
-        assert_target_directory_valid!
+      def define_resource_requirements
+        # Parent directory of the target must exist.
+        requirements.assert(:checkout, :sync) do |a|
+          dirname = ::File.dirname(@new_resource.destination)
+          a.assertion { ::File.directory?(dirname) }
+          a.whyrun("Directory #{dirname} does not exist, this run will fail unless it has been previously created. Assuming it would have been created.")
+          a.failure_message(Chef::Exceptions::MissingParentDirectory,
+            "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{dirname} does not exist")
+        end
+
+
+        requirements.assert(:all_actions) do |a|
+          a.assertion { !(@new_resource.revision =~ /^origin\//) }
+          a.failure_message Chef::Exceptions::InvalidRemoteGitReference,
+             "Deploying remote branches is not supported. " +
+             "Specify the remote branch as a local branch for " +
+             "the git repository you're deploying from " +
+             "(ie: '#{@new_resource.revision.gsub('origin/', '')}' rather than '#{@new_resource.revision}')."
+        end
+
+        requirements.assert(:all_actions) do |a|
+          # this can't be recovered from in why-run mode, because nothing that
+          # we do in the course of a run is likely to create a valid target_revision
+          # if we can't resolve it up front.
+          a.assertion { target_revision != nil }
+          a.failure_message Chef::Exceptions::UnresolvableGitReference,
+            "Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. " +
+            "Verify your (case-sensitive) repository URL and revision.\n" +
+            "`git ls-remote` output: #{@resolved_reference}"
+        end
+      end
 
+      def action_checkout
         if target_dir_non_existent_or_empty?
           clone
           checkout
           enable_submodules
           add_remotes
-          @new_resource.updated_by_last_action(true)
         else
           Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory"
         end
@@ -52,13 +85,12 @@ class Chef
 
       def action_export
         action_checkout
-        FileUtils.rm_rf(::File.join(@new_resource.destination,".git"))
-        @new_resource.updated_by_last_action(true)
+        converge_by("complete the export by removing #{@new_resource.destination}.git after checkout") do
+          FileUtils.rm_rf(::File.join(@new_resource.destination,".git"))
+        end
       end
 
       def action_sync
-        assert_target_directory_valid!
-
         if existing_git_clone?
           current_rev = find_current_revision
           Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{target_revision}"
@@ -66,22 +98,13 @@ class Chef
             fetch_updates
             enable_submodules
             Chef::Log.info "#{@new_resource} updated to revision #{target_revision}"
-            @new_resource.updated_by_last_action(true)
           end
           add_remotes
         else
           action_checkout
-          @new_resource.updated_by_last_action(true)
         end
       end
 
-      def assert_target_directory_valid!
-        target_parent_directory = ::File.dirname(@new_resource.destination)
-        unless ::File.directory?(target_parent_directory)
-          msg = "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{target_parent_directory} does not exist"
-          raise Chef::Exceptions::MissingParentDirectory, msg
-        end
-      end
 
       def existing_git_clone?
         ::File.exist?(::File.join(@new_resource.destination, ".git"))
@@ -103,67 +126,92 @@ class Chef
       def add_remotes
         if (@new_resource.additional_remotes.length > 0)
           @new_resource.additional_remotes.each_pair do |remote_name, remote_url|
-            Chef::Log.info "#{@new_resource} adding git remote #{remote_name} = #{remote_url}"
-            command = "git remote add #{remote_name} #{remote_url}"
-            if shell_out(command, run_options(:cwd => @new_resource.destination, :log_level => :info)).exitstatus != 0
-              @new_resource.updated_by_last_action(true)
+            converge_by("add remote #{remote_name} from #{remote_url}") do
+              Chef::Log.info "#{@new_resource} adding git remote #{remote_name} = #{remote_url}"
+              setup_remote_tracking_branches(remote_name, remote_url)
             end
           end
         end
       end
 
       def clone
-        remote = @new_resource.remote
+        converge_by("clone from #{@new_resource.repository} into #{@new_resource.destination}") do
+          remote = @new_resource.remote
 
-        args = []
-        args << "-o #{remote}" unless remote == 'origin'
-        args << "--depth #{@new_resource.depth}" if @new_resource.depth
+          args = []
+          args << "-o #{remote}" unless remote == 'origin'
+          args << "--depth #{@new_resource.depth}" if @new_resource.depth
 
-        Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{@new_resource.destination}"
+          Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{@new_resource.destination}"
 
-        clone_cmd = "git clone #{args.join(' ')} #{@new_resource.repository} #{Shellwords.escape @new_resource.destination}"
-        shell_out!(clone_cmd, run_options(:log_level => :info))
+          clone_cmd = "git clone #{args.join(' ')} \"#{@new_resource.repository}\" \"#{@new_resource.destination}\""
+          shell_out!(clone_cmd, run_options)
+        end
       end
 
       def checkout
         sha_ref = target_revision
-        # checkout into a local branch rather than a detached HEAD
-        shell_out!("git checkout -b deploy #{sha_ref}", run_options(:cwd => @new_resource.destination))
-        Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} reference: #{sha_ref}"
+        converge_by("checkout ref #{sha_ref} branch #{@new_resource.revision}") do
+          # checkout into a local branch rather than a detached HEAD
+          shell_out!("git checkout -b deploy #{sha_ref}", run_options(:cwd => @new_resource.destination))
+          Chef::Log.info "#{@new_resource} checked out branch: #{@new_resource.revision} reference: #{sha_ref}"
+        end
       end
 
       def enable_submodules
         if @new_resource.enable_submodules
-          Chef::Log.info "#{@new_resource} enabling git submodules"
-          # the --recursive flag means we require git 1.6.5+ now, see CHEF-1827
-          command = "git submodule update --init --recursive"
-          shell_out!(command, run_options(:cwd => @new_resource.destination, :log_level => :info))
+          converge_by("enable git submodules for #{@new_resource}") do
+            Chef::Log.info "#{@new_resource} synchronizing git submodules"
+            command = "git submodule sync"
+            shell_out!(command, run_options(:cwd => @new_resource.destination))
+            Chef::Log.info "#{@new_resource} enabling git submodules"
+            # the --recursive flag means we require git 1.6.5+ now, see CHEF-1827
+            command = "git submodule update --init --recursive"
+            shell_out!(command, run_options(:cwd => @new_resource.destination))
+          end
         end
       end
 
       def fetch_updates
-        setup_remote_tracking_branches if @new_resource.remote != 'origin'
+        setup_remote_tracking_branches(@new_resource.remote, @new_resource.repository)
+        converge_by("fetch updates for #{@new_resource.remote}") do
+          # since we're in a local branch already, just reset to specified revision rather than merge
+          fetch_command = "git fetch #{@new_resource.remote} && git fetch #{@new_resource.remote} --tags && git reset --hard #{target_revision}"
+          Chef::Log.debug "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
+          shell_out!(fetch_command, run_options(:cwd => @new_resource.destination))
+        end
+      end
 
-        # since we're in a local branch already, just reset to specified revision rather than merge
-        fetch_command = "git fetch #{@new_resource.remote} && git fetch #{@new_resource.remote} --tags && git reset --hard #{target_revision}"
-        Chef::Log.debug "Fetching updates from #{new_resource.remote} and resetting to revision #{target_revision}"
-        shell_out!(fetch_command, run_options(:cwd => @new_resource.destination))
+      def setup_remote_tracking_branches(remote_name, remote_url)
+        converge_by("set up remote tracking branches for #{remote_url} at #{remote_name}") do
+          Chef::Log.debug "#{@new_resource} configuring remote tracking branches for repository #{remote_url} "+
+            "at remote #{remote_name}"
+          check_remote_command = "git config --get remote.#{remote_name}.url"
+          remote_status = shell_out!(check_remote_command, run_options(:cwd => @new_resource.destination, :returns => [0,1,2]))
+          case remote_status.exitstatus
+          when 0, 2
+            # * Status 0 means that we already have a remote with this name, so we should update the url
+            #   if it doesn't match the url we want.
+            # * Status 2 means that we have multiple urls assigned to the same remote (not a good idea)
+            #   which we can fix by replacing them all with our target url (hence the --replace-all option)
+
+            if multiple_remotes?(remote_status) || !remote_matches?(remote_url,remote_status)
+              update_remote_url_command = "git config --replace-all remote.#{remote_name}.url #{remote_url}"
+              shell_out!(update_remote_url_command, run_options(:cwd => @new_resource.destination))
+            end
+          when 1
+            add_remote_command = "git remote add #{remote_name} #{remote_url}"
+            shell_out!(add_remote_command, run_options(:cwd => @new_resource.destination))
+          end
+        end
       end
 
-      # Use git-config to setup a remote tracking branches. Could use
-      # git-remote but it complains when a remote of the same name already
-      # exists, git-config will just silenty overwrite the setting every
-      # time. This could cause wierd-ness in the remote cache if the url
-      # changes between calls, but as long as the repositories are all
-      # based from each other it should still work fine.
-      def setup_remote_tracking_branches
-        command = []
+      def multiple_remotes?(check_remote_command_result)
+        check_remote_command_result.exitstatus == 2
+      end
 
-        Chef::Log.debug "#{@new_resource} configuring remote tracking branches for repository #{@new_resource.repository} "+
-                        "at remote #{@new_resource.remote}"
-        command << "git config remote.#{@new_resource.remote}.url #{@new_resource.repository}"
-        command << "git config remote.#{@new_resource.remote}.fetch +refs/heads/*:refs/remotes/#{@new_resource.remote}/*"
-        shell_out!(command.join(" && "), run_options(:cwd => @new_resource.destination))
+      def remote_matches?(remote_url, check_remote_command_result)
+        check_remote_command_result.stdout.strip.eql?(remote_url)
       end
 
       def current_revision_matches_target_revision?
@@ -172,13 +220,10 @@ class Chef
 
       def target_revision
         @target_revision ||= begin
-          assert_revision_not_remote
-
           if sha_hash?(@new_resource.revision)
             @target_revision = @new_resource.revision
           else
-            resolved_reference = remote_resolve_reference
-            @target_revision = extract_revision(resolved_reference)
+            @target_revision = remote_resolve_reference
           end
         end
       end
@@ -187,8 +232,38 @@ class Chef
 
       def remote_resolve_reference
         Chef::Log.debug("#{@new_resource} resolving remote reference")
-        command = git('ls-remote', @new_resource.repository, @new_resource.revision)
-        shell_out!(command, run_options).stdout
+        # The sha pointed to by an annotated tag is identified by the
+        # '^{}' suffix appended to the tag. In order to resolve
+        # annotated tags, we have to search for "revision*" and
+        # post-process. Special handling for 'HEAD' to ignore a tag
+        # named 'HEAD'.
+        rev_pattern = case @new_resource.revision
+                      when '', 'HEAD'
+                        'HEAD'
+                      else
+                        @new_resource.revision + '*'
+                      end
+        command = git("ls-remote \"#{@new_resource.repository}\"", rev_pattern)
+        @resolved_reference = shell_out!(command, run_options).stdout
+        ref_lines = @resolved_reference.split("\n")
+        refs = ref_lines.map { |line| line.split("\t") }
+        # first try for ^{} indicating the commit pointed to by an
+        # annotated tag
+        tagged_commit = refs.find { |m| m[1].end_with?("#{@new_resource.revision}^{}") }
+        # It is possible for a user to create a tag named 'HEAD'.
+        # Using such a degenerate annotated tag would be very
+        # confusing. We avoid the issue by disallowing the use of
+        # annotated tags named 'HEAD'.
+        if tagged_commit && rev_pattern != 'HEAD'
+          tagged_commit[0]
+        else
+          found = refs.find { |m| m[1].end_with?(@new_resource.revision) }
+          if found
+            found[0]
+          else
+            nil
+          end
+        end
       end
 
       private
@@ -198,12 +273,7 @@ class Chef
         run_opts[:group] = @new_resource.group if @new_resource.group
         run_opts[:environment] = {"GIT_SSH" => @new_resource.ssh_wrapper} if @new_resource.ssh_wrapper
         run_opts[:log_tag] = @new_resource.to_s
-        run_opts[:log_level] ||= :debug
-        if run_opts[:log_level] == :info
-          if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info?
-            run_opts[:live_stream] = STDOUT
-          end
-        end
+        run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout
         run_opts
       end
 
@@ -219,27 +289,6 @@ class Chef
         string =~ /^[0-9a-f]{40}$/
       end
 
-      def assert_revision_not_remote
-        if @new_resource.revision =~ /^origin\//
-          reference = @new_resource.revision
-          error_text =  "Deploying remote branches is not supported. " +
-                        "Specify the remote branch as a local branch for " +
-                        "the git repository you're deploying from " +
-                        "(ie: '#{reference.gsub('origin/', '')}' rather than '#{reference}')."
-          raise RuntimeError, error_text
-        end
-      end
-
-      def extract_revision(resolved_reference)
-        unless resolved_reference =~ /^([0-9a-f]{40})\s+(\S+)/
-          msg = "Unable to parse SHA reference for '#{@new_resource.revision}' in repository '#{@new_resource.repository}'. "
-          msg << "Verify your (case-sensitive) repository URL and revision.\n"
-          msg << "`git ls-remote` output: #{resolved_reference}"
-          raise Chef::Exceptions::UnresolvableGitReference, msg
-        end
-        $1
-      end
-
     end
   end
 end
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
index a9cea02..eacb033 100644
--- a/lib/chef/provider/group.rb
+++ b/lib/chef/provider/group.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -18,7 +18,6 @@
 
 require 'chef/provider'
 require 'chef/mixin/command'
-require 'chef/resource/group'
 require 'etc'
 
 class Chef
@@ -26,16 +25,21 @@ class Chef
     class Group < Chef::Provider
       include Chef::Mixin::Command
       attr_accessor :group_exists
-      
+      attr_accessor :change_desc
+
+      def whyrun_supported?
+        true
+      end
+
       def initialize(new_resource, run_context)
         super
         @group_exists = true
       end
-      
+
       def load_current_resource
         @current_resource = Chef::Resource::Group.new(@new_resource.name)
         @current_resource.group_name(@new_resource.group_name)
-        
+
         group_info = nil
         begin
           group_info = Etc.getgrnam(@new_resource.group_name)
@@ -43,79 +47,100 @@ class Chef
           @group_exists = false
           Chef::Log.debug("#{@new_resource} group does not exist")
         end
-        
+
         if group_info
           @new_resource.gid(group_info.gid) unless @new_resource.gid
           @current_resource.gid(group_info.gid)
           @current_resource.members(group_info.mem)
         end
-        
+
         @current_resource
       end
-      
-      # Check to see if a group needs any changes
+
+      def define_resource_requirements
+        requirements.assert(:modify) do |a|
+          a.assertion { @group_exists }
+          a.failure_message(Chef::Exceptions::Group, "Cannot modify #{@new_resource} - group does not exist!")
+          a.whyrun("Group #{@new_resource} does not exist. Unless it would have been created earlier in this run, this attempt to modify it would fail.")
+        end
+      end
+
+      # Check to see if a group needs any changes. Populate
+      # @change_desc with a description of why a change must occur
       #
       # ==== Returns
       # <true>:: If a change is required
       # <false>:: If a change is not required
       def compare_group
-        return true if @new_resource.gid != @current_resource.gid
+        @change_desc = nil
+        if @new_resource.gid != @current_resource.gid
+          @change_desc = "change gid #{@current_resource.gid} to #{@new_resource.gid}"
+          return true
+        end
 
         if(@new_resource.append)
+          missing_members = []
           @new_resource.members.each do |member|
             next if @current_resource.members.include?(member)
+            missing_members << member
+          end
+          if missing_members.length > 0
+            @change_desc = "add missing member(s): #{missing_members.join(", ")}"
             return true
           end
         else
-          return true if @new_resource.members != @current_resource.members
+          if @new_resource.members != @current_resource.members
+            @change_desc = "replace group members with new list of members"
+            return true
+          end
         end
-
         return false
       end
-      
+
       def action_create
         case @group_exists
         when false
-          create_group
-          Chef::Log.info("#{@new_resource} created")
-          @new_resource.updated_by_last_action(true)
-        else 
+          converge_by("create #{@new_resource}") do
+            create_group
+            Chef::Log.info("#{@new_resource} created")
+          end
+        else
           if compare_group
-            manage_group
-            Chef::Log.info("#{@new_resource} altered")
-            @new_resource.updated_by_last_action(true)
+            converge_by(["alter group #{@new_resource}", @change_desc ]) do
+              manage_group
+              Chef::Log.info("#{@new_resource} altered")
+            end
           end
         end
       end
-      
+
       def action_remove
         if @group_exists
-          remove_group
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} removed")
+          converge_by("remove group #{@new_resource}") do
+            remove_group
+            Chef::Log.info("#{@new_resource} removed")
+          end
         end
       end
-      
+
       def action_manage
         if @group_exists && compare_group
-          manage_group 
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} managed")
+          converge_by(["manage group #{@new_resource}", @change_desc]) do
+            manage_group
+            Chef::Log.info("#{@new_resource} managed")
+          end
         end
       end
-      
+
       def action_modify
-        if @group_exists 
-          if compare_group
+        if compare_group
+          converge_by(["modify group #{@new_resource}", @change_desc]) do
             manage_group
-            @new_resource.updated_by_last_action(true)
             Chef::Log.info("#{@new_resource} modified")
           end
-        else
-          raise Chef::Exceptions::Group, "Cannot modify #{@new_resource} - group does not exist!"
         end
       end
-      
+
       def create_group
         raise NotImplementedError, "subclasses of Chef::Provider::Group should define #create_group"
       end
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
index 0f739c6..d0b2a4d 100644
--- a/lib/chef/provider/group/dscl.rb
+++ b/lib/chef/provider/group/dscl.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -38,7 +38,7 @@ class Chef
           raise(Chef::Exceptions::Group,"dscl error: #{result.inspect}") if result[2] =~ /No such key: /
           return result[2]
         end
-        
+
         # This is handled in providers/group.rb by Etc.getgrnam()
         # def group_exists?(group)
         #   groups = safe_dscl("list /Groups")
@@ -84,17 +84,25 @@ class Chef
           end
         end
 
+        def define_resource_requirements
+          super
+          requirements.assert(:all_actions) do |a|
+            a.assertion { ::File.exists?("/usr/bin/dscl") }
+            a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{@new_resource.name}"
+            # No whyrun alternative: this component should be available in the base install of any given system that uses it
+          end
+        end
+
         def load_current_resource
           super
-          raise Chef::Exceptions::Group, "Could not find binary /usr/bin/dscl for #{@new_resource}" unless ::File.exists?("/usr/bin/dscl")
         end
-        
+
         def create_group
           dscl_create_group
           set_gid
           set_members
         end
-        
+
         def manage_group
           if @new_resource.group_name && (@current_resource.group_name != @new_resource.group_name)
             dscl_create_group
@@ -106,12 +114,12 @@ class Chef
             set_members
           end
         end
-        
+
         def dscl_create_group
           safe_dscl("create /Groups/#{@new_resource.group_name}")
           safe_dscl("create /Groups/#{@new_resource.group_name} Password '*'")
         end
-        
+
         def remove_group
           safe_dscl("delete /Groups/#{@new_resource.group_name}")
         end
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
index 78100dc..2638b82 100644
--- a/lib/chef/provider/group/gpasswd.rb
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -28,8 +28,15 @@ class Chef
 
         def load_current_resource
           super
+        end
 
-          raise Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}" unless ::File.exists?("/usr/bin/gpasswd")
+        def define_resource_requirements
+          super
+          requirements.assert(:all_actions) do |a|
+            a.assertion { ::File.exists?("/usr/bin/gpasswd") }
+            a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}"
+            # No whyrun alternative: this component should be available in the base install of any given system that uses it
+          end
         end
 
         def modify_group_members
diff --git a/lib/chef/provider/group/groupadd.rb b/lib/chef/provider/group/groupadd.rb
index 720d857..45ae308 100644
--- a/lib/chef/provider/group/groupadd.rb
+++ b/lib/chef/provider/group/groupadd.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -20,7 +20,7 @@ class Chef
   class Provider
     class Group
       class Groupadd < Chef::Provider::Group
-        
+
         def required_binaries
           [ "/usr/sbin/groupadd",
             "/usr/sbin/groupmod",
@@ -29,8 +29,16 @@ class Chef
 
         def load_current_resource
           super
+        end
+
+        def define_resource_requirements
+          super
           required_binaries.each do |required_binary|
-            raise Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}" unless ::File.exists?(required_binary)
+            requirements.assert(:all_actions) do |a|
+              a.assertion { ::File.exists?(required_binary) }
+              a.failure_message Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}"
+              # No whyrun alternative: this component should be available in the base install of any given system that uses it
+            end
           end
         end
 
@@ -40,9 +48,9 @@ class Chef
           command << set_options
           command << groupadd_options
           run_command(:command => command)
-          modify_group_members    
+          modify_group_members
         end
-        
+
         # Manage the group when it already exists
         def manage_group
           command = "groupmod"
@@ -50,12 +58,12 @@ class Chef
           run_command(:command => command)
           modify_group_members
         end
-        
+
         # Remove the group
         def remove_group
           run_command(:command => "groupdel #{@new_resource.group_name}")
         end
-        
+
         def modify_group_members
           raise Chef::Exceptions::Group, "you must override modify_group_members in #{self.to_s}"
         end
@@ -79,6 +87,7 @@ class Chef
         def groupadd_options
           opts = ''
           opts << " -r" if @new_resource.system
+          opts << " -o" if @new_resource.non_unique
           opts
         end
 
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
new file mode 100644
index 0000000..10fc680
--- /dev/null
+++ b/lib/chef/provider/group/groupmod.rb
@@ -0,0 +1,120 @@
+#
+# Author:: Dan Crosta (<dcrosta at late.am>)
+# 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 'chef/mixin/shell_out'
+
+class Chef
+  class Provider
+    class Group
+      class Groupmod < Chef::Provider::Group
+
+        include Chef::Mixin::ShellOut
+
+        def load_current_resource
+          super
+          [ "group", "user" ].each do |binary|
+            raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/#{binary} for #{@new_resource}" unless ::File.exists?("/usr/sbin/#{binary}")
+          end
+        end
+
+        # Create the group
+        def create_group
+          command = "group add"
+          command << set_options
+          shell_out!(command)
+
+          add_group_members(@new_resource.members)
+        end
+
+        # Manage the group when it already exists
+        def manage_group
+          if @new_resource.append
+            to_add = @new_resource.members.dup
+            to_add.reject! { |user| @current_resource.members.include?(user) }
+
+            to_delete = Array.new
+
+            Chef::Log.debug("#{@new_resource} not changing group members, the group has no members to add") if to_add.empty?
+          else
+            to_add = @new_resource.members.dup
+            to_add.reject! { |user| @current_resource.members.include?(user) }
+
+            to_delete = @current_resource.members.dup
+            to_delete.reject! { |user| @new_resource.members.include?(user) }
+
+            Chef::Log.debug("#{@new_resource} setting group members to: none") if @new_resource.members.empty?
+          end
+
+          if to_delete.empty?
+            # If we are only adding new members to this group, then
+            # call add_group_members with only those users
+            add_group_members(to_add)
+          else
+            Chef::Log.debug("#{@new_resource} removing members #{to_delete.join(', ')}")
+
+            # This is tricky, but works: rename the existing group to
+            # "<name>_bak", create a new group with the same GID and
+            # "<name>", then set correct members on that group
+            rename = "group mod -n #{@new_resource.group_name}_bak #{@new_resource.group_name}"
+            shell_out!(rename)
+
+            create = "group add"
+            create << set_options(:overwrite_gid => true)
+            shell_out!(create)
+
+            # Ignore to_add here, since we're replacing the group we
+            # have to add all members who should be in the group.
+            add_group_members(@new_resource.members)
+
+            remove = "group del #{@new_resource.group_name}_bak"
+            shell_out!(remove)
+          end
+        end
+
+        # Remove the group
+        def remove_group
+          shell_out!("group del #{@new_resource.group_name}")
+        end
+
+        # Adds a list of usernames to the group using `user mod`
+        def add_group_members(members)
+          Chef::Log.debug("#{@new_resource} adding members #{members.join(', ')}") if !members.empty?
+          members.each do |user|
+            shell_out!("user mod -G #{@new_resource.group_name} #{user}")
+          end
+        end
+
+        # Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
+        #
+        # ==== Returns
+        # <string>:: A string containing the option and then the quoted value
+        def set_options(overwrite_gid=false)
+          opts = ""
+          if overwrite_gid || @new_resource.gid && (@current_resource.gid != @new_resource.gid)
+            opts << " -g '#{@new_resource.gid}'"
+          end
+          if overwrite_gid
+            opts << " -o"
+          end
+          opts << " #{@new_resource.group_name}"
+          opts
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 19ef1a9..66da828 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -20,12 +20,21 @@ class Chef
   class Provider
     class Group
       class Pw < Chef::Provider::Group
-        
+
         def load_current_resource
           super
-          raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{@new_resource}" unless ::File.exists?("/usr/sbin/pw")
         end
-        
+
+        def define_resource_requirements
+          super
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { ::File.exists?("/usr/sbin/pw") }
+            a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/pw for #{@new_resource}"
+            # No whyrun alternative: this component should be available in the base install of any given system that uses it
+          end
+        end
+
         # Create the group
         def create_group
           command = "pw groupadd"
@@ -33,7 +42,7 @@ class Chef
           command << set_members_option
           run_command(:command => command)
         end
-        
+
         # Manage the group when it already exists
         def manage_group
           command = "pw groupmod"
@@ -41,12 +50,12 @@ class Chef
           command << set_members_option
           run_command(:command => command)
         end
-        
+
         # Remove the group
         def remove_group
           run_command(:command => "pw groupdel #{@new_resource.group_name}")
         end
-        
+
         # Little bit of magic as per Adam's useradd provider to pull and assign the command line flags
         #
         # ==== Returns
@@ -77,7 +86,7 @@ class Chef
           end
           opt
         end
-        
+
       end
     end
   end
diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb
index 22486eb..4c343bd 100644
--- a/lib/chef/provider/group/suse.rb
+++ b/lib/chef/provider/group/suse.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -28,8 +28,15 @@ class Chef
 
         def load_current_resource
           super
+        end
 
-          raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource}" unless ::File.exists?("/usr/sbin/groupmod")
+        def define_resource_requirements
+          super
+          requirements.assert(:all_actions) do |a|
+            a.assertion { ::File.exists?("/usr/sbin/groupmod") }
+            a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/groupmod for #{@new_resource.name}"
+            # No whyrun alternative: this component should be available in the base install of any given system that uses it
+          end
         end
 
         def modify_group_members
diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb
index ffc494c..5788ac8 100644
--- a/lib/chef/provider/group/usermod.rb
+++ b/lib/chef/provider/group/usermod.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -22,18 +22,32 @@ class Chef
   class Provider
     class Group
       class Usermod < Chef::Provider::Group::Groupadd
-        
+
         def load_current_resource
           super
+        end
+
+        def define_resource_requirements
+          super
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { ::File.exists?("/usr/sbin/usermod") }
+            a.failure_message Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}"
+            # No whyrun alternative: this component should be available in the base install of any given system that uses it
+          end
 
-          raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}" unless ::File.exists?("/usr/sbin/usermod")
+          requirements.assert(:modify, :create) do |a|
+            a.assertion { @new_resource.members.empty? || @new_resource.append }
+            a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self.to_s}, must set append true in group"
+            # No whyrun alternative - this action is simply not supported.
+          end
         end
 
         def modify_group_members
           case node[:platform]
-          when "openbsd", "netbsd", "aix"
+          when "openbsd", "netbsd", "aix", "solaris2", "smartos"
             append_flags = "-G"
-          when "solaris"
+          when "solaris", "suse", "opensuse"
             append_flags = "-a -G"
           end
 
@@ -42,10 +56,7 @@ class Chef
               @new_resource.members.each do |member|
                 Chef::Log.debug("#{@new_resource} appending member #{member} to group #{@new_resource.group_name}")
                 run_command(:command => "usermod #{append_flags} #{@new_resource.group_name} #{member}" )
-
               end
-            else
-              raise Chef::Exceptions::Group, "setting group members directly is not supported by #{self.to_s}"
             end
           else
             Chef::Log.debug("#{@new_resource} not changing group members, the group has no members")
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
index 8828040..da12366 100644
--- a/lib/chef/provider/group/windows.rb
+++ b/lib/chef/provider/group/windows.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -25,16 +25,16 @@ class Chef
   class Provider
     class Group
       class Windows < Chef::Provider::Group
-        
+
         def initialize(new_resource,run_context)
           super
-          @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.name)
+          @net_group = Chef::Util::Windows::NetGroup.new(@new_resource.group_name)
         end
 
         def load_current_resource
           @current_resource = Chef::Resource::Group.new(@new_resource.name)
           @current_resource.group_name(@new_resource.group_name)
-        
+
           members = nil
           begin
             members = @net_group.local_get_members
@@ -49,12 +49,12 @@ class Chef
 
           @current_resource
         end
-        
+
         def create_group
           @net_group.local_add
           manage_group
         end
-        
+
         def manage_group
           if @new_resource.append
             begin
@@ -68,11 +68,11 @@ class Chef
             @net_group.local_set_members(@new_resource.members)
           end
         end
-        
+
         def remove_group
           @net_group.local_delete
         end
-        
+
       end
     end
   end
diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb
index 6fbb482..1e0aa8b 100644
--- a/lib/chef/provider/http_request.rb
+++ b/lib/chef/provider/http_request.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -17,98 +17,95 @@
 #
 
 require 'tempfile'
+require 'chef/http/simple'
 
 class Chef
   class Provider
     class HttpRequest < Chef::Provider
-      
-      attr_accessor :rest
-      
+
+      attr_accessor :http
+
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
-        @rest = Chef::REST.new(@new_resource.url, nil, nil)
+        @http = Chef::HTTP::Simple.new(@new_resource.url)
       end
 
       # Send a HEAD request to @new_resource.url, with ?message=@new_resource.message
       def action_head
         message = check_message(@new_resource.message)
-        modified = @rest.run_request(
-          :HEAD,
-          @rest.create_url("#{@new_resource.url}?message=#{message}"),
-          @new_resource.headers,
-          false,
-          10,
-          false
+        # returns true from Chef::REST if returns 2XX (Net::HTTPSuccess)
+        modified = @http.head(
+          "#{@new_resource.url}?message=#{message}",
+          @new_resource.headers
         )
-        @new_resource.updated_by_last_action(modified)
         Chef::Log.info("#{@new_resource} HEAD to #{@new_resource.url} successful")
         Chef::Log.debug("#{@new_resource} HEAD request response: #{modified}")
+        # :head is usually used to trigger notifications, which converge_by now does
+        if modified
+          converge_by("#{@new_resource} HEAD to #{@new_resource.url} returned modified, trigger notifications") {}
+        end
       end
 
       # Send a GET request to @new_resource.url, with ?message=@new_resource.message
-      def action_get  
-        message = check_message(@new_resource.message)
-        body = @rest.run_request(
-          :GET, 
-          @rest.create_url("#{@new_resource.url}?message=#{message}"),
-          @new_resource.headers,
-          false,
-          10,
-          false
-        )
-        @new_resource.updated_by_last_action(true)
-        Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful")
-        Chef::Log.debug("#{@new_resource} GET request response: #{body}")
+      def action_get
+        converge_by("#{@new_resource} GET to #{@new_resource.url}") do
+
+          message = check_message(@new_resource.message)
+          body = @http.get(
+            "#{@new_resource.url}?message=#{message}",
+            @new_resource.headers
+          )
+          Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful")
+          Chef::Log.debug("#{@new_resource} GET request response: #{body}")
+        end
       end
-      
+
       # Send a PUT request to @new_resource.url, with the message as the payload
-      def action_put 
-        message = check_message(@new_resource.message)
-        body = @rest.run_request(
-          :PUT,
-          @rest.create_url("#{@new_resource.url}"),
-          @new_resource.headers,
-          message,
-          10,
-          false
-        )
-        @new_resource.updated_by_last_action(true)
-        Chef::Log.info("#{@new_resource} PUT to #{@new_resource.url} successful")
-        Chef::Log.debug("#{@new_resource} PUT request response: #{body}")
+      def action_put
+        converge_by("#{@new_resource} PUT to #{@new_resource.url}") do
+          message = check_message(@new_resource.message)
+          body = @http.put(
+            "#{@new_resource.url}",
+            message,
+            @new_resource.headers
+          )
+          Chef::Log.info("#{@new_resource} PUT to #{@new_resource.url} successful")
+          Chef::Log.debug("#{@new_resource} PUT request response: #{body}")
+        end
       end
-      
+
       # Send a POST request to @new_resource.url, with the message as the payload
       def action_post
-        message = check_message(@new_resource.message)
-        body = @rest.run_request(
-          :POST,
-          @rest.create_url("#{@new_resource.url}"),
-          @new_resource.headers,
-          message,
-          10,
-          false
-        )
-        @new_resource.updated_by_last_action(true)
-        Chef::Log.info("#{@new_resource} POST to #{@new_resource.url} message: #{message.inspect} successful")
-        Chef::Log.debug("#{@new_resource} POST request response: #{body}")
+        converge_by("#{@new_resource} POST to #{@new_resource.url}") do
+          message = check_message(@new_resource.message)
+          body = @http.post(
+            "#{@new_resource.url}",
+            message,
+            @new_resource.headers
+          )
+          Chef::Log.info("#{@new_resource} POST to #{@new_resource.url} message: #{message.inspect} successful")
+          Chef::Log.debug("#{@new_resource} POST request response: #{body}")
+        end
       end
-      
+
       # Send a DELETE request to @new_resource.url
       def action_delete
-        body = @rest.run_request(
-          :DELETE,
-          @rest.create_url("#{@new_resource.url}"),
-          @new_resource.headers,
-          false,
-          10,
-          false
-        )
-        @new_resource.updated_by_last_action(true)
-        Chef::Log.info("#{@new_resource} DELETE to #{@new_resource.url} successful")
-        Chef::Log.debug("#{@new_resource} DELETE request response: #{body}")
+        converge_by("#{@new_resource} DELETE to #{@new_resource.url}") do
+          body = @http.delete(
+            "#{@new_resource.url}",
+            @new_resource.headers
+          )
+          @new_resource.updated_by_last_action(true)
+          Chef::Log.info("#{@new_resource} DELETE to #{@new_resource.url} successful")
+          Chef::Log.debug("#{@new_resource} DELETE request response: #{body}")
+        end
       end
-      
+
       private
-        
+
         def check_message(message)
           if message.kind_of?(Proc)
             message.call
@@ -116,7 +113,7 @@ class Chef
             message
           end
         end
-      
+
     end
   end
 end
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 5b35a8d..31f88e5 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -19,17 +19,18 @@
 require 'chef/log'
 require 'chef/mixin/command'
 require 'chef/provider'
+require 'chef/exceptions'
 require 'erb'
 
 #  Recipe example:
 #
 #    int = {Hash with your network settings...}
 #
-#    ifconfig  int['ip'] do 
-#      ignore_failure  true 
-#      device  int['dev'] 
-#      mask    int['mask']  
-#      gateway int['gateway'] 
+#    ifconfig  int['ip'] do
+#      ignore_failure  true
+#      device  int['dev']
+#      mask    int['mask']
+#      gateway int['gateway']
 #      mtu     int['mtu']
 #    end
 
@@ -38,12 +39,26 @@ class Chef
     class Ifconfig < Chef::Provider
       include Chef::Mixin::Command
 
+      attr_accessor :config_template
+      attr_accessor :config_path
+
+      def initialize(new_resource, run_context)
+        super(new_resource, run_context)
+        @config_template = nil
+        @config_path = nil
+      end
+
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
 
+        @ifconfig_success = true
         @interfaces = {}
 
-        status = popen4("ifconfig") do |pid, stdin, stdout, stderr|
+        @status = popen4("ifconfig") do |pid, stdin, stdout, stderr|
           stdout.each do |line|
 
             if !line[0..9].strip.empty?
@@ -61,7 +76,7 @@ class Chef
               @interface = @interfaces.fetch(@new_resource.device)
 
               @current_resource.target(@new_resource.target)
-              @current_resource.device(@int_name)
+              @current_resource.device(@new_resource.device)
               @current_resource.inet_addr(@interface["inet_addr"])
               @current_resource.hwaddr(@interface["hwaddr"])
               @current_resource.bcast(@interface["bcast"])
@@ -71,64 +86,63 @@ class Chef
             end
           end
         end
+        @current_resource
+      end
 
-        unless status.exitstatus == 0
-          raise Chef::Exception::Ifconfig, "ifconfig failed - #{status.inspect}!"
+      def define_resource_requirements
+        requirements.assert(:all_actions) do |a|
+          a.assertion { @status.exitstatus == 0 }
+          a.failure_message Chef::Exceptions::Ifconfig, "ifconfig failed - #{@status.inspect}!"
+          # no whyrun - if the base ifconfig used in load_current_resource fails
+          # there's no reasonable action that could have been taken in the course of
+          # a chef run to fix it.
         end
-
-        @current_resource
       end
 
       def action_add
-        # check to see if load_current_resource found ifconfig
+        # check to see if load_current_resource found interface in ifconfig
         unless @current_resource.inet_addr
-          unless @new_resource.device == "lo"
-            command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
-            command << " netmask #{@new_resource.mask}" if @new_resource.mask
-            command << " metric #{@new_resource.metric}" if @new_resource.metric
-            command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+          unless @new_resource.device == loopback_device
+            command = add_command
+            converge_by ("run #{command} to add #{@new_resource}") do
+              run_command(
+                :command => command
+              )
+              Chef::Log.info("#{@new_resource} added")
+              # Write out the config files
+              generate_config
+            end
           end
-
-          run_command(
-            :command => command
-          )
-          Chef::Log.info("#{@new_resource} added")
-          @new_resource.updated_by_last_action(true)
         end
-
-        # Write out the config files
-        generate_config
       end
 
       def action_enable
         # check to see if load_current_resource found ifconfig
         # enables, but does not manage config files
         unless @current_resource.inet_addr
-          unless @new_resource.device == "lo"
-            command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
-            command << " netmask #{@new_resource.mask}" if @new_resource.mask
-            command << " metric #{@new_resource.metric}" if @new_resource.metric
-            command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+          unless @new_resource.device == loopback_device
+            command = enable_command
+            converge_by ("run #{command} to enable #{@new_resource}") do
+              run_command(
+                :command => command
+              )
+              Chef::Log.info("#{@new_resource} enabled")
+            end
           end
-
-          run_command(
-            :command => command
-          )
-          Chef::Log.info("#{@new_resource} enabled")
-          @new_resource.updated_by_last_action(true)
         end
       end
 
       def action_delete
         # check to see if load_current_resource found the interface
         if @current_resource.device
-          command = "ifconfig #{@new_resource.device} down"
-          run_command(
-            :command => command
-          )
-          delete_config
-          Chef::Log.info("#{@new_resource} deleted")
-          @new_resource.updated_by_last_action(true)
+          command = delete_command
+          converge_by ("run #{command} to delete #{@new_resource}") do
+            run_command(
+              :command => command
+            )
+            delete_config
+            Chef::Log.info("#{@new_resource} deleted")
+          end
         else
           Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
         end
@@ -138,58 +152,73 @@ class Chef
         # check to see if load_current_resource found the interface
         # disables, but leaves config files in place.
         if @current_resource.device
-          command = "ifconfig #{@new_resource.device} down"
-          run_command(
-            :command => command
-          )
-          Chef::Log.info("#{@new_resource} disabled")
-          @new_resource.updated_by_last_action(true)
+          command = disable_command
+          converge_by ("run #{command} to disable #{@new_resource}") do
+            run_command(
+              :command => command
+            )
+            Chef::Log.info("#{@new_resource} disabled")
+          end
         else
           Chef::Log.debug("#{@new_resource} does not exist - nothing to do")
         end
       end
 
+      def can_generate_config?
+        ! @config_template.nil? and ! @config_path.nil?
+      end
+
       def generate_config
+        return unless can_generate_config?
         b = binding
-        case node[:platform]
-        when "centos","redhat","fedora"
-          content = %{
-<% if @new_resource.device %>DEVICE=<%= @new_resource.device %><% end %>
-<% if @new_resource.onboot %>ONBOOT=<%= @new_resource.onboot %><% end %>
-<% if @new_resource.bootproto %>BOOTPROTO=<%= @new_resource.bootproto %><% end %>
-<% if @new_resource.target %>IPADDR=<%= @new_resource.target %><% end %>
-<% if @new_resource.mask %>NETMASK=<%= @new_resource.mask %><% end %>
-<% if @new_resource.network %>NETWORK=<%= @new_resource.network %><% end %>
-<% if @new_resource.bcast %>BROADCAST=<%= @new_resource.bcast %><% end %>
-<% if @new_resource.onparent %>ONPARENT=<%= @new_resource.onparent %><% end %>
-          }
-          template = ::ERB.new(content)
-          network_file = ::File.new("/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}", "w")
+        template = ::ERB.new(@config_template)
+        converge_by ("generate configuration file : #{@config_path}") do
+          network_file = ::File.new(@config_path, "w")
           network_file.puts(template.result(b))
           network_file.close
-          Chef::Log.info("#{@new_resource} created configuration file")
-        when "debian","ubuntu"
-          # template
-        when "slackware"
-          # template
         end
+        Chef::Log.info("#{@new_resource} created configuration file")
       end
 
       def delete_config
+        return unless can_generate_config?
         require 'fileutils'
-        case node[:platform]
-        when "centos","redhat","fedora"
-          ifcfg_file = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
-          if ::File.exist?(ifcfg_file)
-            FileUtils.rm_f(ifcfg_file, :verbose => false, :force => true)
+        if ::File.exist?(@config_path)
+          converge_by ("delete the #{@config_path}") do
+            FileUtils.rm_f(@config_path, :verbose => false)
           end
-        when "debian","ubuntu"
-          # delete configs
-        when "slackware"
-          # delete configs
         end
+        Chef::Log.info("#{@new_resource} deleted configuration file")
+      end
+
+      private
+      def add_command
+        command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+        command << " netmask #{@new_resource.mask}" if @new_resource.mask
+        command << " metric #{@new_resource.metric}" if @new_resource.metric
+        command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+        command
       end
 
+      def enable_command
+        command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+        command << " netmask #{@new_resource.mask}" if @new_resource.mask
+        command << " metric #{@new_resource.metric}" if @new_resource.metric
+        command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
+        command
+      end
+
+      def disable_command
+        "ifconfig #{@new_resource.device} down"
+      end
+
+      def delete_command
+        "ifconfig #{@new_resource.device} down"
+      end
+
+      def loopback_device
+        'lo'
+      end
     end
   end
 end
diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb
new file mode 100644
index 0000000..460b1ba
--- /dev/null
+++ b/lib/chef/provider/ifconfig/aix.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Kaustubh Deorukhkar (kaustubh at clogeny.com)
+# Copyright:: Copyright (c) 2013 Opscode, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/ifconfig'
+
+class Chef
+  class Provider
+    class Ifconfig
+      class Aix < Chef::Provider::Ifconfig
+
+        def load_current_resource
+          @current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
+
+          @interface_exists = false
+          found_interface = false
+          interface = {}
+
+          @status = popen4("ifconfig -a") do |pid, stdin, stdout, stderr|
+            stdout.each do |line|
+
+              if !found_interface
+                if line =~ /^(\S+):\sflags=(\S+)/
+                  # We have interface name, if this is the interface for @current_resource, load info else skip till next interface is found.
+                  if $1 == @new_resource.device
+                    # Found interface
+                    found_interface = true
+                    @interface_exists = true
+                    @current_resource.target(@new_resource.target)
+                    @current_resource.device($1)
+                    interface[:flags] = $2
+                    @current_resource.metric($1) if line =~ /metric\s(\S+)/
+                  end
+                end
+              else
+                # parse interface related information, stop when next interface is found.
+                if line =~ /^(\S+):\sflags=(\S+)/
+                  # we are done parsing interface info and hit another one, so stop.
+                  found_interface = false
+                  break
+                else
+                  if found_interface
+                    # read up interface info
+                    @current_resource.inet_addr($1) if line =~ /inet\s(\S+)\s/
+                    @current_resource.bcast($1) if line =~ /broadcast\s(\S+)/
+                    @current_resource.mask(hex_to_dec_netmask($1)) if line =~ /netmask\s(\S+)\s/
+                  end
+                end
+              end
+            end
+          end
+
+          @current_resource
+        end
+
+        private
+        def add_command
+          # ifconfig changes are temporary, chdev persist across reboots.
+          raise Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action" if @new_resource.metric
+          command = "chdev -l #{@new_resource.device} -a netaddr=#{@new_resource.name}"
+          command << " -a netmask=#{@new_resource.mask}" if @new_resource.mask
+          command << " -a mtu=#{@new_resource.mtu}" if @new_resource.mtu
+          command
+        end
+
+        def delete_command
+          # ifconfig changes are temporary, chdev persist across reboots.
+          "chdev -l #{@new_resource.device} -a state=down"
+        end
+
+        def loopback_device
+          "lo0"
+        end
+
+        def hex_to_dec_netmask(netmask)
+          # example '0xffff0000' -> '255.255.0.0'
+          dec = netmask[2..3].to_i(16).to_s(10)
+          [4,6,8].each { |n| dec = dec + "." + netmask[n..n+1].to_i(16).to_s(10) }
+          dec
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb
new file mode 100644
index 0000000..821f4fe
--- /dev/null
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Xabier de Zuazo (xabier at onddo.com)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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/provider/ifconfig'
+require 'chef/util/file_edit'
+
+class Chef
+  class Provider
+    class Ifconfig
+      class Debian < Chef::Provider::Ifconfig
+
+        def initialize(new_resource, run_context)
+          super(new_resource, run_context)
+          @config_template = %{
+<% if @new_resource.device %>
+<% if @new_resource.onboot == "yes" %>auto <%= @new_resource.device %><% end %>
+<% case @new_resource.bootproto
+   when "dhcp" %>
+iface <%= @new_resource.device %> inet dhcp
+<% when "bootp" %>
+iface <%= @new_resource.device %> inet bootp
+<% else %>
+iface <%= @new_resource.device %> inet static
+    <% if @new_resource.target %>address <%= @new_resource.target %><% end %>
+    <% if @new_resource.mask %>netmask <%= @new_resource.mask %><% end %>
+    <% if @new_resource.network %>network <%= @new_resource.network %><% end %>
+    <% if @new_resource.bcast %>broadcast <%= @new_resource.bcast %><% end %>
+    <% if @new_resource.metric %>metric <%= @new_resource.metric %><% end %>
+    <% if @new_resource.hwaddr %>hwaddress <%= @new_resource.hwaddr %><% end %>
+    <% if @new_resource.mtu %>mtu <%= @new_resource.mtu %><% end %>
+<% end %>
+<% end %>
+          }
+          @config_path = "/etc/network/interfaces.d/ifcfg-#{@new_resource.device}"
+        end
+
+        def generate_config
+          check_interfaces_config
+          super
+        end
+
+        protected
+
+        def check_interfaces_config
+          converge_by ('modify configuration file : /etc/network/interfaces') do
+            Dir.mkdir('/etc/network/interfaces.d') unless ::File.directory?('/etc/network/interfaces.d')
+            conf = Chef::Util::FileEdit.new('/etc/network/interfaces')
+            conf.insert_line_if_no_match('^\s*source\s+/etc/network/interfaces[.]d/[*]\s*$', 'source /etc/network/interfaces.d/*')
+            conf.write_file
+          end
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb
new file mode 100644
index 0000000..ef35b0e
--- /dev/null
+++ b/lib/chef/provider/ifconfig/redhat.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Xabier de Zuazo (xabier at onddo.com)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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/provider/ifconfig'
+
+class Chef
+  class Provider
+    class Ifconfig
+      class Redhat < Chef::Provider::Ifconfig
+
+        def initialize(new_resource, run_context)
+          super(new_resource, run_context)
+          @config_template = %{
+<% if @new_resource.device %>DEVICE=<%= @new_resource.device %><% end %>
+<% if @new_resource.onboot == "yes" %>ONBOOT=<%= @new_resource.onboot %><% end %>
+<% if @new_resource.bootproto %>BOOTPROTO=<%= @new_resource.bootproto %><% end %>
+<% if @new_resource.target %>IPADDR=<%= @new_resource.target %><% end %>
+<% if @new_resource.mask %>NETMASK=<%= @new_resource.mask %><% end %>
+<% if @new_resource.network %>NETWORK=<%= @new_resource.network %><% end %>
+<% if @new_resource.bcast %>BROADCAST=<%= @new_resource.bcast %><% end %>
+<% if @new_resource.onparent %>ONPARENT=<%= @new_resource.onparent %><% end %>
+<% if @new_resource.hwaddr %>HWADDR=<%= @new_resource.hwaddr %><% end %>
+<% if @new_resource.metric %>METRIC=<%= @new_resource.metric %><% end %>
+<% if @new_resource.mtu %>MTU=<%= @new_resource.mtu %><% end %>
+          }
+          @config_path = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb
index 9debd70..7776b59 100644
--- a/lib/chef/provider/link.rb
+++ b/lib/chef/provider/link.rb
@@ -19,31 +19,18 @@
 require 'chef/config'
 require 'chef/log'
 require 'chef/mixin/shell_out'
+require 'chef/mixin/file_class'
 require 'chef/resource/link'
 require 'chef/provider'
+require 'chef/scan_access_control'
 
 class Chef
   class Provider
     class Link < Chef::Provider
-      include Chef::Mixin::ShellOut
 
-      def file_class
-        @host_os_file ||= if Chef::Platform.windows?
-          require 'chef/win32/file'
-          begin
-            Chef::ReservedNames::Win32::File.verify_links_supported!
-          rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e
-            message = "Link resource is not supported on this version of Windows"
-            message << ": #{node[:kernel][:name]}" if node
-            message << " (#{node[:platform_version]})" if node
-            Chef::Log.fatal(message)
-            raise e
-          end
-          Chef::ReservedNames::Win32::File
-        else
-          ::File
-        end
-      end
+      include Chef::Mixin::EnforceOwnershipAndPermissions
+      include Chef::Mixin::ShellOut
+      include Chef::Mixin::FileClass
 
       def negative_complement(big)
         if big > 1073741823 # Fixnum max
@@ -54,6 +41,10 @@ class Chef
 
       private :negative_complement
 
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         @current_resource = Chef::Resource::Link.new(@new_resource.name)
         @current_resource.target_file(@new_resource.target_file)
@@ -62,9 +53,6 @@ class Chef
           @current_resource.to(
             canonicalize(file_class.readlink(@current_resource.target_file))
           )
-          cstats = ::File.lstat(@current_resource.target_file)
-          @current_resource.owner(cstats.uid)
-          @current_resource.group(cstats.gid)
         else
           @current_resource.link_type(:hard)
           if ::File.exists?(@current_resource.target_file)
@@ -77,9 +65,25 @@ class Chef
             end
           end
         end
+        ScanAccessControl.new(@new_resource, @current_resource).set_all!
         @current_resource
       end
 
+      def define_resource_requirements
+        requirements.assert(:delete) do |a|
+          a.assertion do
+            if @current_resource.to
+              @current_resource.link_type == @new_resource.link_type and
+              (@current_resource.link_type == :symbolic  or @current_resource.to != '')
+            else
+              true
+            end
+          end
+          a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type.to_s} link."
+          a.whyrun("Would assume the link at #{@new_resource.target_file} was previously created")
+        end
+      end
+
       def canonicalize(path)
         Chef::Platform.windows? ? path.gsub('/', '\\') : path
       end
@@ -87,51 +91,49 @@ class Chef
       def action_create
         if @current_resource.to != canonicalize(@new_resource.to) ||
            @current_resource.link_type != @new_resource.link_type
-          if @new_resource.link_type == :symbolic
-            if @current_resource.to # nil if target_file does not exist
+          if @current_resource.to # nil if target_file does not exist
+            converge_by("unlink existing file at #{@new_resource.target_file}") do
               ::File.unlink(@new_resource.target_file)
             end
-            file_class.symlink(canonicalize(@new_resource.to), at new_resource.target_file)
-            Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.to} -> #{@new_resource.target_file}")
-            Chef::Log.info("#{@new_resource} created")
+          end
+          if @new_resource.link_type == :symbolic
+            converge_by("create symlink at #{@new_resource.target_file} to #{@new_resource.to}") do
+              file_class.symlink(canonicalize(@new_resource.to), at new_resource.target_file)
+              Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.to} -> #{@new_resource.target_file}")
+              Chef::Log.info("#{@new_resource} created")
+            end
           elsif @new_resource.link_type == :hard
-            if @current_resource.to # nil if target_file does not exist
-              ::File.unlink(@new_resource.target_file)
+            converge_by("create hard link at #{@new_resource.target_file} to #{@new_resource.to}") do
+              file_class.link(@new_resource.to, @new_resource.target_file)
+              Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.to} -> #{@new_resource.target_file}")
+              Chef::Log.info("#{@new_resource} created")
             end
-            file_class.link(@new_resource.to, @new_resource.target_file)
-            Chef::Log.debug("#{@new_resource} created #{@new_resource.link_type} link from #{@new_resource.to} -> #{@new_resource.target_file}")
-            Chef::Log.info("#{@new_resource} created")
           end
-          @new_resource.updated_by_last_action(true)
         end
         if @new_resource.link_type == :symbolic
-          enforce_ownership_and_permissions @new_resource.target_file
+          if access_controls.requires_changes?
+            converge_by(access_controls.describe_changes) do
+              access_controls.set_all
+            end
+          end
         end
       end
 
       def action_delete
         if @current_resource.to # Exists
-          if @current_resource.link_type == @new_resource.link_type
-            unless @current_resource.link_type == :hard && @current_resource.to == ''
-              ::File.delete(@new_resource.target_file)
-              Chef::Log.info("#{@new_resource} deleted")
-              @new_resource.updated_by_last_action(true)
-              return
-            end
+          converge_by("delete link at #{@new_resource.target_file}") do
+            ::File.delete(@new_resource.target_file)
+            Chef::Log.info("#{@new_resource} deleted")
           end
-          raise Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type.to_s} link."
         end
       end
-    end
 
-    # private
-    # def hardlink?(target, to)
-    #   s = file_class()
-    #   if file_class.respond_to?(:hardlink?)
-    #     file_class.hardlink?(target)
-    #   else
-    #     ::File.stat(target).ino == ::File.stat(to).ino
-    #   end
-    # end
+      # Implementation components *should not* follow symlinks when managing
+      # access control (e.g., use lchmod instead of chmod) if the resource is a
+      # symlink.
+      def manage_symlink_access?
+        @new_resource.link_type == :symbolic
+      end
+    end
   end
 end
diff --git a/lib/chef/provider/log.rb b/lib/chef/provider/log.rb
index 5d0417e..1c970cc 100644
--- a/lib/chef/provider/log.rb
+++ b/lib/chef/provider/log.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -32,13 +32,13 @@ class Chef
         def load_current_resource
           true
         end
-      
+
         # Write the log to Chef's log
         #
         # === Return
         # true:: Always return true
         def action_write
-          Chef::Log.send(@new_resource.level, @new_resource.name)
+          Chef::Log.send(@new_resource.level, @new_resource.message)
           @new_resource.updated_by_last_action(true)
         end
 
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
new file mode 100644
index 0000000..90ce70a
--- /dev/null
+++ b/lib/chef/provider/lwrp_base.rb
@@ -0,0 +1,150 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008-2012 Opscode, Inc.
+# 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/provider'
+
+class Chef
+  class Provider
+
+    # == Chef::Provider::LWRPBase
+    # Base class from which LWRP providers inherit.
+    class LWRPBase < Provider
+
+      # Chef::Provider::LWRPBase::InlineResources
+      # Implementation of inline resource convergence for LWRP providers. See
+      # Provider::LWRPBase.use_inline_resources for a longer explanation.
+      #
+      # This code is restricted to a module so that it can be selectively
+      # applied to providers on an opt-in basis.
+      module InlineResources
+
+        # Class methods for InlineResources. Overrides the `action` DSL method
+        # with one that enables inline resource convergence.
+        module ClassMethods
+          # Defines an action method on the provider, using
+          # recipe_eval_with_update_check to execute the given block.
+          def action(name, &block)
+            define_method("action_#{name}") do
+              recipe_eval_with_update_check(&block)
+            end
+          end
+        end
+
+        # Executes the given block in a temporary run_context with its own
+        # resource collection. After the block is executed, any resources
+        # declared inside are converged, and if any are updated, the
+        # new_resource will be marked updated.
+        def recipe_eval_with_update_check(&block)
+          saved_run_context = @run_context
+          temp_run_context = @run_context.dup
+          @run_context = temp_run_context
+          @run_context.resource_collection = Chef::ResourceCollection.new
+
+          return_value = instance_eval(&block)
+          Chef::Runner.new(@run_context).converge
+          return_value
+        ensure
+          @run_context = saved_run_context
+          if temp_run_context.resource_collection.any? {|r| r.updated? }
+            new_resource.updated_by_last_action(true)
+          end
+        end
+
+      end
+
+      extend Chef::Mixin::ConvertToClassName
+      extend Chef::Mixin::FromFile
+
+      include Chef::DSL::Recipe
+
+      # These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
+      # They are not included by its replacment, Chef::DSL::Recipe, but
+      # they may be used in existing LWRPs.
+      include Chef::DSL::PlatformIntrospection
+      include Chef::DSL::DataQuery
+
+      def self.build_from_file(cookbook_name, filename, run_context)
+        provider_name = filename_to_qualified_string(cookbook_name, filename)
+
+        # Add log entry if we override an existing light-weight provider.
+        class_name = convert_to_class_name(provider_name)
+
+        if Chef::Provider.const_defined?(class_name)
+          Chef::Log.info("#{class_name} light-weight provider already initialized -- overriding!")
+        end
+
+        provider_class = Class.new(self)
+        provider_class.class_from_file(filename)
+
+        class_name = convert_to_class_name(provider_name)
+        Chef::Provider.const_set(class_name, provider_class)
+        Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}")
+
+        provider_class
+      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
+
+      # DSL for defining a provider's actions.
+      def self.action(name, &block)
+        define_method("action_#{name}") do
+          instance_eval(&block)
+        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
+
+    end
+  end
+end
diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb
index 98bf810..51c9b8d 100644
--- a/lib/chef/provider/mdadm.rb
+++ b/lib/chef/provider/mdadm.rb
@@ -24,11 +24,14 @@ class Chef
   class Provider
     class Mdadm < Chef::Provider
 
-      #include Chef::Mixin::Command
       include Chef::Mixin::ShellOut
 
       def popen4
-        raise Exception, "deprecated, bitches"
+        raise Exception, "deprecated"
+      end
+
+      def whyrun_supported?
+        true
       end
 
       def load_current_resource
@@ -36,30 +39,24 @@ class Chef
         @current_resource.raid_device(@new_resource.raid_device)
         Chef::Log.debug("#{@new_resource} checking for software raid device #{@current_resource.raid_device}")
 
-        mdadm = shell_out!("mdadm --detail --scan")
-        exists = mdadm.stdout.include?(@new_resource.raid_device)
-        #exists = false
-        # popen4(command) do |pid, stdin, stdout, stderr|
-        #   stdout.each do |line|
-        #     if line.include? @new_resource.raid_device
-        #       exists = true
-        #     end
-        #   end
-        # end
+        device_not_found = 4
+        mdadm = shell_out!("mdadm --detail --test #{@new_resource.raid_device}", :returns => [0,device_not_found])
+        exists = (mdadm.status == 0)
         @current_resource.exists(exists)
       end
 
       def action_create
         unless @current_resource.exists
-          command = "yes | mdadm --create #{@new_resource.raid_device} --chunk=#{@new_resource.chunk} --level #{@new_resource.level}"
-          command << " --metadata=#{@new_resource.metadata}"
-          command << " --bitmap=#{@new_resource.bitmap}" if @new_resource.bitmap
-          command << " --raid-devices #{@new_resource.devices.length} #{@new_resource.devices.join(" ")}"
-          Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
-          #pid, stdin, stdout, stderr = popen4(command)
-          shell_out!(command)
-          Chef::Log.info("#{@new_resource} created raid device (#{@new_resource.raid_device})")
-          @new_resource.updated_by_last_action(true)
+          converge_by("create RAID device #{new_resource.raid_device}") do
+            command = "yes | mdadm --create #{@new_resource.raid_device} --level #{@new_resource.level}"
+            command << " --chunk=#{@new_resource.chunk}" unless @new_resource.level == 1
+            command << " --metadata=#{@new_resource.metadata}"
+            command << " --bitmap=#{@new_resource.bitmap}" if @new_resource.bitmap
+            command << " --raid-devices #{@new_resource.devices.length} #{@new_resource.devices.join(" ")}"
+            Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+            shell_out!(command)
+            Chef::Log.info("#{@new_resource} created raid device (#{@new_resource.raid_device})")
+          end
         else
           Chef::Log.debug("#{@new_resource} raid device already exists, skipping create (#{@new_resource.raid_device})")
         end
@@ -67,11 +64,12 @@ class Chef
 
       def action_assemble
         unless @current_resource.exists
-          command = "yes | mdadm --assemble #{@new_resource.raid_device} #{@new_resource.devices.join(" ")}"
-          Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
-          shell_out!(command)
-          Chef::Log.info("#{@new_resource} assembled raid device (#{@new_resource.raid_device})")
-          @new_resource.updated_by_last_action(true)
+          converge_by("assemble RAID device #{new_resource.raid_device}") do
+            command = "yes | mdadm --assemble #{@new_resource.raid_device} #{@new_resource.devices.join(" ")}"
+            Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+            shell_out!(command)
+            Chef::Log.info("#{@new_resource} assembled raid device (#{@new_resource.raid_device})")
+          end
         else
           Chef::Log.debug("#{@new_resource} raid device already exists, skipping assemble (#{@new_resource.raid_device})")
         end
@@ -79,11 +77,12 @@ class Chef
 
       def action_stop
         if @current_resource.exists
-          command = "yes | mdadm --stop #{@new_resource.raid_device}"
-          Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
-          shell_out!(command)
-          Chef::Log.info("#{@new_resource} stopped raid device (#{@new_resource.raid_device})")
-          @new_resource.updated_by_last_action(true)
+          converge_by("stop RAID device #{new_resource.raid_device}") do
+            command = "yes | mdadm --stop #{@new_resource.raid_device}"
+            Chef::Log.debug("#{@new_resource} mdadm command: #{command}")
+            shell_out!(command)
+            Chef::Log.info("#{@new_resource} stopped raid device (#{@new_resource.raid_device})")
+          end
         else
           Chef::Log.debug("#{@new_resource} raid device doesn't exist (#{@new_resource.raid_device}) - not stopping")
         end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index c9004d2..5f58baa 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -26,12 +26,22 @@ class Chef
 
       include Chef::Mixin::Command
 
+
+      def whyrun_supported?
+        true
+      end
+
+      def load_current_resource
+        true
+      end
+
       def action_mount
         unless @current_resource.mounted
-          status = mount_fs()
-          if status
-            @new_resource.updated_by_last_action(true)
-            Chef::Log.info("#{@new_resource} mounted")
+          converge_by("mount #{@current_resource.device} to #{@current_resource.mount_point}") do
+            status = mount_fs()
+            if status
+              Chef::Log.info("#{@new_resource} mounted")
+            end
           end
         else
           Chef::Log.debug("#{@new_resource} is already mounted")
@@ -40,10 +50,11 @@ class Chef
 
       def action_umount
         if @current_resource.mounted
-          status = umount_fs()
-          if status
-            @new_resource.updated_by_last_action(true)
-            Chef::Log.info("#{@new_resource} unmounted")
+          converge_by("unmount #{@current_resource.device}") do
+            status = umount_fs()
+            if status
+              Chef::Log.info("#{@new_resource} unmounted")
+            end
           end
         else
           Chef::Log.debug("#{@new_resource} is already unmounted")
@@ -55,37 +66,40 @@ class Chef
           raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount"
         else
           if @current_resource.mounted
-            status = remount_fs()
-            if status
-              @new_resource.updated_by_last_action(true)
-              Chef::Log.info("#{@new_resource} remounted")
+            converge_by("remount #{@current_resource.device}") do
+              status = remount_fs()
+              if status
+                Chef::Log.info("#{@new_resource} remounted")
+              end
             end
           else
             Chef::Log.debug("#{@new_resource} not mounted, nothing to remount")
           end
         end
       end
-      
+
       def action_enable
-        unless @current_resource.enabled
-          status = enable_fs
-          if status
-            @new_resource.updated_by_last_action(true)
-            Chef::Log.info("#{@new_resource} enabled")
-          else
-            Chef::Log.debug("#{@new_resource} already enabled")
+        unless @current_resource.enabled && mount_options_unchanged?
+          converge_by("remount #{@current_resource.device}") do
+            status = enable_fs
+            if status
+              Chef::Log.info("#{@new_resource} enabled")
+            else
+              Chef::Log.debug("#{@new_resource} already enabled")
+            end
           end
         end
       end
-      
+
       def action_disable
         if @current_resource.enabled
-          status = disable_fs
-          if status
-            @new_resource.updated_by_last_action(true)            
-            Chef::Log.info("#{@new_resource} disabled")
-          else
-            Chef::Log.debug("#{@new_resource} already disabled")
+          converge_by("remount #{@current_resource.device}") do
+            status = disable_fs
+            if status
+              Chef::Log.info("#{@new_resource} disabled")
+            else
+              Chef::Log.debug("#{@new_resource} already disabled")
+            end
           end
         end
       end
@@ -101,14 +115,14 @@ class Chef
       def remount_fs
         raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount"
       end
-      
+
       def enable_fs
-        raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"        
+        raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
       end
-      
+
       def disable_fs
-        raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"        
-      end      
+        raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
+      end
     end
   end
 end
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
new file mode 100644
index 0000000..0d7e11a
--- /dev/null
+++ b/lib/chef/provider/mount/aix.rb
@@ -0,0 +1,179 @@
+#
+# Author::
+# Copyright:: Copyright (c) 2009 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/provider/mount'
+
+class Chef
+  class Provider
+    class Mount
+      class Aix < Chef::Provider::Mount::Mount
+
+        # Override for aix specific handling
+        def initialize(new_resource, run_context)
+          super
+          # options and fstype are set to "defaults" and "auto" respectively in the Mount Resource class. These options are not valid for AIX, override them.
+          if @new_resource.options[0] == "defaults"
+            @new_resource.options.clear
+          end
+          if @new_resource.fstype == "auto"
+            @new_resource.fstype = nil
+          end
+        end
+
+        def enabled?
+          # Check to see if there is an entry in /etc/filesystems. Last entry for a volume wins. Using command "lsfs" to fetch entries.
+          enabled = false
+
+          # lsfs o/p = #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct
+          # search only for current mount point
+          shell_out("lsfs -c #{@new_resource.mount_point}").stdout.each_line do | line |
+            case line
+            when /^#\s/
+              next
+            when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}:(\S+):(\[\S+\])?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+              # mount point entry with ipv6 address for nodename (ipv6 address use ':')
+              enabled = true
+              @current_resource.fstype($1)
+              @current_resource.options($5)
+              Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems")
+              next
+            when /^#{Regexp.escape(@new_resource.mount_point)}:#{device_fstab_regex}::(\S+):(\S+)?:(\S+)?:(\S+):(\S+):(\S+):(\S+)/
+              # mount point entry with hostname or ipv4 address
+              enabled = true
+              @current_resource.fstype($1)
+              @current_resource.options($5)
+              Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/filesystems")
+              next
+            when /^#{Regexp.escape(@new_resource.mount_point)}/
+              enabled=false
+              Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/filesystems")
+            end
+          end
+          @current_resource.enabled(enabled)
+        end
+
+        def mounted?
+          mounted = false
+          shell_out!("mount").stdout.each_line do |line|
+            if network_device?
+              device_details = device_fstab.split(":")
+              search_device = device_details[1]
+            else
+              search_device = device_fstab_regex
+            end
+            case line
+            when /#{search_device}\s+#{Regexp.escape(@new_resource.mount_point)}/
+              mounted = true
+              Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}")
+            when /^[\/\w]+\s+#{Regexp.escape(@new_resource.mount_point)}\s+/
+              mounted = false
+              Chef::Log.debug("Found conflicting mount point #{@new_resource.mount_point} in /etc/fstab")
+            end
+          end
+          @current_resource.mounted(mounted)
+        end
+
+        def mount_fs
+          unless @current_resource.mounted
+            mountable?
+            command = "mount -v #{@new_resource.fstype}"
+
+            if !(@new_resource.options.nil? || @new_resource.options.empty?)
+              command << " -o #{@new_resource.options.join(',')}"
+            end
+
+            command << case @new_resource.device_type
+            when :device
+              " #{device_real}"
+            when :label
+              " -L #{@new_resource.device}"
+            when :uuid
+              " -U #{@new_resource.device}"
+            end
+            command << " #{@new_resource.mount_point}"
+            shell_out!(command)
+            Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
+          else
+            Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
+          end
+        end
+
+        def remount_command
+          if !(@new_resource.options.nil? || @new_resource.options.empty?)
+            return "mount -o remount,#{@new_resource.options.join(',')} #{@new_resource.device} #{@new_resource.mount_point}"
+          else
+            return "mount -o remount #{@new_resource.device} #{@new_resource.mount_point}"
+          end
+        end
+
+        def enable_fs
+          if @current_resource.enabled && mount_options_unchanged?
+            Chef::Log.debug("#{@new_resource} is already enabled - nothing to do")
+            return nil
+          end
+
+          if @current_resource.enabled
+            # The current options don't match what we have, so
+            # disable, then enable.
+            disable_fs
+          end
+          ::File.open("/etc/filesystems", "a") do |fstab|
+            fstab.puts("#{@new_resource.mount_point}:")
+            if network_device?
+              device_details = device_fstab.split(":")
+              fstab.puts("\tdev\t\t= #{device_details[1]}")
+              fstab.puts("\tnodename\t\t= #{device_details[0]}")
+            else
+              fstab.puts("\tdev\t\t= #{device_fstab}")
+            end
+            fstab.puts("\tvfs\t\t= #{@new_resource.fstype}")
+            fstab.puts("\tmount\t\t= false")
+            fstab.puts "\toptions\t\t= #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty?
+            Chef::Log.debug("#{@new_resource} is enabled at #{@new_resource.mount_point}")
+          end
+        end
+
+        def disable_fs
+          contents = []
+          if @current_resource.enabled
+            found_device = false
+            ::File.open("/etc/filesystems", "r").each_line do |line|
+              case line
+              when /^\/.+:\s*$/
+                if line =~ /#{Regexp.escape(@new_resource.mount_point)}+:/
+                  found_device = true
+                else
+                  found_device = false
+                end
+              end
+              if !found_device
+                contents << line
+              end
+            end
+            ::File.open("/etc/filesystems", "w") do |fstab|
+              contents.each { |line| fstab.puts line}
+            end
+          else
+            Chef::Log.debug("#{@new_resource} is not enabled - nothing to do")
+          end
+        end
+
+    end
+   end
+  end
+end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 9a85a90..25dfd42 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -39,17 +39,17 @@ class Chef
           mounted?
           enabled?
         end
-        
+
         def mountable?
           # only check for existence of non-remote devices
           if (device_should_exist? && !::File.exists?(device_real) )
             raise Chef::Exceptions::Mount, "Device #{@new_resource.device} does not exist"
-          elsif( !::File.exists?(@new_resource.mount_point) )
+          elsif( @new_resource.mount_point != "none" && !::File.exists?(@new_resource.mount_point) )
             raise Chef::Exceptions::Mount, "Mount point #{@new_resource.mount_point} does not exist"
           end
           return true
         end
-        
+
         def enabled?
           # Check to see if there is a entry in /etc/fstab. Last entry for a volume wins.
           enabled = false
@@ -72,17 +72,27 @@ class Chef
           end
           @current_resource.enabled(enabled)
         end
-        
+
         def mounted?
           mounted = false
+
+          # "mount" outputs the mount points as real paths. Convert
+          # the mount_point of the resource to a real path in case it
+          # contains symlinks in its parents dirs.
+          real_mount_point = if ::File.exists? @new_resource.mount_point
+                               ::File.realpath(@new_resource.mount_point)
+                             else
+                               @new_resource.mount_point
+                             end
+
           shell_out!("mount").stdout.each_line do |line|
             case line
-            when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(@new_resource.mount_point)}/
+            when /^#{device_mount_regex}\s+on\s+#{Regexp.escape(real_mount_point)}/
               mounted = true
-              Chef::Log.debug("Special device #{device_logstring} mounted as #{@new_resource.mount_point}")
-            when /^([\/\w])+\son\s#{Regexp.escape(@new_resource.mount_point)}\s+/
+              Chef::Log.debug("Special device #{device_logstring} mounted as #{real_mount_point}")
+            when /^([\/\w])+\son\s#{Regexp.escape(real_mount_point)}\s+/
               mounted = false
-              Chef::Log.debug("Special device #{$~[1]} mounted as #{@new_resource.mount_point}")
+              Chef::Log.debug("Special device #{$~[1]} mounted as #{real_mount_point}")
             end
           end
           @current_resource.mounted(mounted)
@@ -118,9 +128,13 @@ class Chef
           end
         end
 
+        def remount_command
+           return "mount -o remount #{@new_resource.mount_point}"
+        end
+
         def remount_fs
           if @current_resource.mounted and @new_resource.supports[:remount]
-            shell_out!("mount -o remount #{@new_resource.mount_point}")
+            shell_out!(remount_command)
             @new_resource.updated_by_last_action(true)
             Chef::Log.debug("#{@new_resource} is remounted at #{@new_resource.mount_point}")
           elsif @current_resource.mounted
@@ -137,7 +151,7 @@ class Chef
             Chef::Log.debug("#{@new_resource} is already enabled - nothing to do")
             return nil
           end
-          
+
           if @current_resource.enabled
             # The current options don't match what we have, so
             # disable, then enable.
@@ -152,7 +166,7 @@ class Chef
         def disable_fs
           if @current_resource.enabled
             contents = []
-            
+
             found = false
             ::File.readlines("/etc/fstab").reverse_each do |line|
               if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}/
@@ -163,7 +177,7 @@ class Chef
                 contents << line
               end
             end
-            
+
             ::File.open("/etc/fstab", "w") do |fstab|
               contents.reverse_each { |line| fstab.puts line}
             end
@@ -177,7 +191,8 @@ class Chef
         end
 
         def device_should_exist?
-          ( not network_device? ) &&
+          ( @new_resource.device != "none" ) &&
+            ( not network_device? ) &&
             ( not %w[ tmpfs fuse ].include? @new_resource.fstype )
         end
 
@@ -195,7 +210,7 @@ class Chef
         end
 
         def device_real
-          if @real_device == nil 
+          if @real_device == nil
             if @new_resource.device_type == :device
               @real_device = @new_resource.device
             else
@@ -225,7 +240,11 @@ class Chef
             # ignore trailing slash
             Regexp.escape(device_real)+"/?"
           elsif ::File.symlink?(device_real)
-            "(?:#{Regexp.escape(device_real)})|(?:#{Regexp.escape(::File.readlink(device_real))})"
+            # This regular expression tries to match device_real. If that does not match it will try to match the target of device_real.
+            # So given a symlink like this:
+            # /dev/mapper/vgroot-tmp.vol -> /dev/dm-9
+            # First it will try to match "/dev/mapper/vgroot-tmp.vol". If there is no match it will try matching for "/dev/dm-9".
+            "(?:#{Regexp.escape(device_real)}|#{Regexp.escape(::File.readlink(device_real))})"
           else
             Regexp.escape(device_real)
           end
@@ -238,14 +257,14 @@ class Chef
             device_fstab
           end
         end
-        
+
         def mount_options_unchanged?
           @current_resource.fstype == @new_resource.fstype and
           @current_resource.options == @new_resource.options and
           @current_resource.dump == @new_resource.dump and
           @current_resource.pass == @new_resource.pass
         end
-        
+
       end
     end
   end
diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb
index dced0d3..02aa789 100644
--- a/lib/chef/provider/mount/windows.rb
+++ b/lib/chef/provider/mount/windows.rb
@@ -59,7 +59,10 @@ class Chef
 
         def mount_fs
           unless @current_resource.mounted
-            @mount.add(@new_resource.device)
+            @mount.add(:remote => @new_resource.device,
+                       :username => @new_resource.username,
+                       :domainname => @new_resource.domain,
+                       :password => @new_resource.password)
             Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
           else
             Chef::Log.debug("#{@new_resource} is already mounted at #{@new_resource.mount_point}")
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
index c34ba7d..c686f67 100644
--- a/lib/chef/provider/ohai.rb
+++ b/lib/chef/provider/ohai.rb
@@ -22,20 +22,25 @@ class Chef
   class Provider
     class Ohai < Chef::Provider
 
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         true
       end
 
       def action_reload
-        ohai = ::Ohai::System.new
-        if @new_resource.plugin
-          ohai.require_plugin @new_resource.plugin
-        else
-          ohai.all_plugins
+        converge_by("re-run ohai and merge results into node attributes") do
+          ohai = ::Ohai::System.new
+          if @new_resource.plugin
+            ohai.require_plugin @new_resource.plugin
+          else
+            ohai.all_plugins
+          end
+          node.automatic_attrs.merge! ohai.data
+          Chef::Log.info("#{@new_resource} reloaded")
         end
-        node.automatic_attrs.merge! ohai.data
-        Chef::Log.info("#{@new_resource} reloaded")
-        @new_resource.updated_by_last_action(true)
       end
     end
   end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 540e043..c7692a9 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -28,64 +28,83 @@ class Chef
       include Chef::Mixin::Command
 
       attr_accessor :candidate_version
-
       def initialize(new_resource, run_context)
         super
         @candidate_version = nil
       end
 
+      def whyrun_supported?
+        true
+      end
+
+      def load_current_resource
+      end
+
+      def define_resource_requirements
+        requirements.assert(:install) do |a|
+          a.assertion { ((@new_resource.version != nil) && !(target_version_already_installed?)) \
+            || !(@current_resource.version.nil? && candidate_version.nil?)  }
+          a.failure_message(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{@new_resource.package_name}")
+          a.whyrun("Assuming a repository that offers #{@new_resource.package_name} would have been configured")
+        end
+
+        requirements.assert(:upgrade) do |a|
+          # Can't upgrade what we don't have
+          a.assertion  { !(@current_resource.version.nil? && candidate_version.nil?) }
+          a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{@new_resource.package_name}")
+          a.whyrun("Assuming a repository that offers #{@new_resource.package_name} would have been configured")
+        end
+      end
+
       def action_install
         # If we specified a version, and it's not the current version, move to the specified version
-        if @new_resource.version != nil && !target_version_already_installed?
+        if !@new_resource.version.nil? && !(target_version_already_installed?)
           install_version = @new_resource.version
         # If it's not installed at all, install it
-        elsif @current_resource.version == nil
+        elsif @current_resource.version.nil?
           install_version = candidate_version
         else
           Chef::Log.debug("#{@new_resource} is already installed - nothing to do")
           return
         end
 
-        unless install_version
-          raise(Chef::Exceptions::Package, "No version specified, and no candidate version available for #{@new_resource.package_name}")
-        end
-
-
         # We need to make sure we handle the preseed file
         if @new_resource.response_file
-          preseed_package(@new_resource.package_name, install_version)
+          if preseed_file = get_preseed_file(@new_resource.package_name, install_version)
+            converge_by("preseed package #{@new_resource.package_name}") do
+              preseed_package(preseed_file)
+            end
+          end
         end
-
-        status = install_package(@new_resource.package_name, install_version)
-        if status
-          @new_resource.updated_by_last_action(true)
+        description = install_version ? "version #{install_version} of" : ""
+        converge_by("install #{description} package #{@new_resource.package_name}") do
+          @new_resource.version(install_version)
+          install_package(@new_resource.package_name, install_version)
         end
-        Chef::Log.info("#{@new_resource} installed version #{install_version}")
       end
 
       def action_upgrade
-        # Can't upgrade what we don't have
-        if @current_resource.version.nil? && candidate_version.nil?
-          raise(Chef::Exceptions::Package, "No candidate version available for #{@new_resource.package_name}")
-        elsif candidate_version.nil?
+        if candidate_version.nil?
           Chef::Log.debug("#{@new_resource} no candidate version - nothing to do")
         elsif @current_resource.version == candidate_version
           Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
         else
+          @new_resource.version(candidate_version)
           orig_version = @current_resource.version || "uninstalled"
-          status = upgrade_package(@new_resource.package_name, candidate_version)
-          if status
-            @new_resource.updated_by_last_action(true)
+          converge_by("upgrade package #{@new_resource.package_name} from #{orig_version} to #{candidate_version}") do
+            upgrade_package(@new_resource.package_name, candidate_version)
+            Chef::Log.info("#{@new_resource} upgraded from #{orig_version} to #{candidate_version}")
           end
-          Chef::Log.info("#{@new_resource} upgraded from #{orig_version} to #{candidate_version}")
         end
       end
 
       def action_remove
         if removing_package?
-          remove_package(@current_resource.package_name, @new_resource.version)
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} removed")
+          description = @new_resource.version ? "version #{@new_resource.version} of " :  ""
+          converge_by("remove #{description} package #{@current_resource.package_name}") do
+            remove_package(@current_resource.package_name, @new_resource.version)
+            Chef::Log.info("#{@new_resource} removed")
+          end
         else
           Chef::Log.debug("#{@new_resource} package does not exist - nothing to do")
         end
@@ -105,9 +124,11 @@ class Chef
 
       def action_purge
         if removing_package?
-          purge_package(@current_resource.package_name, @new_resource.version)
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} purged")
+          description = @new_resource.version ? "version #{@new_resource.version} of" : ""
+          converge_by("purge #{description} package #{@current_resource.package_name}") do
+            purge_package(@current_resource.package_name, @new_resource.version)
+            Chef::Log.info("#{@new_resource} purged")
+          end
         end
       end
 
@@ -122,15 +143,15 @@ class Chef
           return
         end
 
-        status = preseed_package(@new_resource.package_name, @current_resource.version)
-        unless status then
+        if preseed_file = get_preseed_file(@new_resource.package_name, @current_resource.version)
+          converge_by("reconfigure package #{@new_resource.package_name}") do
+            preseed_package(preseed_file)
+            reconfig_package(@new_resource.package_name, @current_resource.version)
+            Chef::Log.info("#{@new_resource} reconfigured")
+          end
+        else
           Chef::Log.debug("#{@new_resource} preseeding has not changed - nothing to do")
-          return
         end
-
-        status = reconfig_package(@new_resource.package_name, @current_resource.version)
-        @new_resource.updated_by_last_action(true) if status
-        Chef::Log.info("#{@new_resource} reconfigured")
       end
 
       def install_package(name, version)
@@ -149,8 +170,8 @@ class Chef
         raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :purge"
       end
 
-      def preseed_package(name, version)
-        raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support pre-seeding package install/upgrade instructions - don't ask it to!"
+      def preseed_package(file)
+        raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support pre-seeding package install/upgrade instructions"
       end
 
       def reconfig_package(name, version)
@@ -159,7 +180,7 @@ class Chef
 
       def get_preseed_file(name, version)
         resource = preseed_resource(name, version)
-        resource.run_action('create')
+        resource.run_action(:create)
         Chef::Log.debug("#{@new_resource} fetched preseed file to #{resource.path}")
 
         if resource.updated_by_last_action?
@@ -177,21 +198,21 @@ class Chef
 
         Chef::Log.debug("#{@new_resource} fetching preseed file to #{cache_seed_to}")
 
-        begin
+
+        if template_available?(@new_resource.response_file)
+          Chef::Log.debug("#{@new_resource} fetching preseed file via Template")
           remote_file = Chef::Resource::Template.new(cache_seed_to, run_context)
-          remote_file.cookbook_name = @new_resource.cookbook_name
-          remote_file.source(@new_resource.response_file)
-          remote_file.backup(false)
-          provider = Chef::Platform.provider_for_resource(remote_file)
-          provider.template_location
-        rescue
-          Chef::Log.debug("#{@new_resource} fetching preseed file via Template resource failed, fallback to CookbookFile resource")
+        elsif cookbook_file_available?(@new_resource.response_file)
+          Chef::Log.debug("#{@new_resource} fetching preseed file via cookbook_file")
           remote_file = Chef::Resource::CookbookFile.new(cache_seed_to, run_context)
-          remote_file.cookbook_name = @new_resource.cookbook_name
-          remote_file.source(@new_resource.response_file)
-          remote_file.backup(false)
+        else
+          message = "No template or cookbook file found for response file #{@new_resource.response_file}"
+          raise Chef::Exceptions::FileNotFound, message
         end
 
+        remote_file.cookbook_name = @new_resource.cookbook_name
+        remote_file.source(@new_resource.response_file)
+        remote_file.backup(false)
         remote_file
       end
 
@@ -199,12 +220,20 @@ class Chef
         options ? " #{options}" : ""
       end
 
-    private
-
       def target_version_already_installed?
         @new_resource.version == @current_resource.version
       end
 
+      private
+
+      def template_available?(path)
+        run_context.has_template_in_cookbook?(@new_resource.cookbook_name, path)
+      end
+
+      def cookbook_file_available?(path)
+        run_context.has_cookbook_file_in_cookbook?(@new_resource.cookbook_name, path)
+      end
+
     end
   end
 end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
new file mode 100644
index 0000000..4df0ea7
--- /dev/null
+++ b/lib/chef/provider/package/aix.rb
@@ -0,0 +1,146 @@
+#
+# Author:: Deepali Jagtap
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+require 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+  class Provider
+    class Package
+      class Aix < Chef::Provider::Package
+
+        include Chef::Mixin::GetSourceFromPackage
+
+        def define_resource_requirements
+          super
+          requirements.assert(:install) do |a|
+            a.assertion { @new_resource.source }
+            a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+          end
+          requirements.assert(:all_actions) do |a|
+            a.assertion { !@new_resource.source || @package_source_found }
+            a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+            a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
+          end
+        end
+
+        def load_current_resource
+          @current_resource = Chef::Resource::Package.new(@new_resource.name)
+          @current_resource.package_name(@new_resource.package_name)
+          @new_resource.version(nil)
+
+          if @new_resource.source
+            @package_source_found = ::File.exists?(@new_resource.source)
+            if @package_source_found
+              Chef::Log.debug("#{@new_resource} checking pkg status")
+              status = popen4("installp -L -d #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+                package_found = false
+                stdout.each do |line|
+                  case line
+                  when /#{@new_resource.package_name}:/
+                    package_found = true
+                    fields = line.split(":")
+                    @new_resource.version(fields[2])
+                  end
+                end
+              end
+            end
+          end
+
+          Chef::Log.debug("#{@new_resource} checking install state")
+          status = popen4("lslpp -lcq #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+            stdout.each do |line|
+              case line
+              when /#{@current_resource.package_name}/
+                fields = line.split(":")
+                Chef::Log.debug("#{@new_resource} version #{fields[2]} is already installed")
+                @current_resource.version(fields[2])
+              end
+            end
+          end
+
+          unless status.exitstatus == 0 || status.exitstatus == 1
+            raise Chef::Exceptions::Package, "lslpp failed - #{status.inspect}!"
+          end
+
+          @current_resource
+        end
+
+        def candidate_version
+          return @candidate_version if @candidate_version
+          status = popen4("installp -L -d #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+            stdout.each_line do |line|
+              case line
+              when /\w:{Regexp.escape(@new_resource.package_name)}:(.*)/
+                fields = line.split(":")
+                @candidate_version = fields[2]
+                @new_resource.version(fields[2])
+                Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}")
+              end
+            end
+          end
+          unless status.exitstatus == 0
+            raise Chef::Exceptions::Package, "installp -L -d #{@new_resource.source} - #{status.inspect}!"
+          end
+          @candidate_version
+        end
+
+        #
+        # The install/update action needs to be tested with various kinds of packages
+        # on AIX viz. packages with or without licensing file dependencies, packages
+        # with dependencies on other packages which will help to test additional
+        # options of installp.
+        # So far, the code has been tested only with standalone packages.
+        #
+        def install_package(name, version)
+          Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
+          if @new_resource.options.nil?
+            run_command_with_systems_locale(
+                    :command => "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
+            run_command_with_systems_locale(
+              :command => "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
+
+        alias_method :upgrade_package, :install_package
+
+        def remove_package(name, version)
+          if @new_resource.options.nil?
+            run_command_with_systems_locale(
+                    :command => "installp -u #{name}"
+                  )
+            Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+          else
+            run_command_with_systems_locale(
+              :command => "installp -u #{expand_options(@new_resource.options)} #{name}"
+            )
+            Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+          end
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index b80986c..dc7b3f2 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -37,13 +37,16 @@ class Chef
           @current_resource
         end
 
+        def default_release_options
+          # Use apt::Default-Release option only if provider was explicitly defined
+          "-o APT::Default-Release=#{@new_resource.default_release}" if @new_resource.provider && @new_resource.default_release
+        end
+
         def check_package_state(package)
           Chef::Log.debug("#{@new_resource} checking package status for #{package}")
           installed = false
-          # Use apt cache release option only if provider was explicitly defined
-          aptcache_options = "-o APT::Default-Release=#{@new_resource.default_release}" if @new_resource.provider && @new_resource.default_release
 
-          shell_out!("apt-cache#{expand_options(aptcache_options)} policy #{package}").stdout.each_line do |line|
+          shell_out!("apt-cache#{expand_options(default_release_options)} policy #{package}").stdout.each_line do |line|
             case line
             when /^\s{2}Installed: (.+)$/
               installed_version = $1
@@ -62,7 +65,8 @@ class Chef
                 @is_virtual_package = true
                 showpkg = shell_out!("apt-cache showpkg #{package}").stdout
                 providers = Hash.new
-                showpkg.rpartition(/Reverse Provides:? #{$/}/)[2].each_line do |line|
+                # Returns all lines after 'Reverse Provides:'
+                showpkg.rpartition(/Reverse Provides:\s*#{$/}/)[2].each_line do |line|
                   provider, version = line.split
                   providers[provider] = version
                 end
@@ -87,12 +91,7 @@ class Chef
         def install_package(name, version)
           package_name = "#{name}=#{version}"
           package_name = name if @is_virtual_package
-          run_command_with_systems_locale(
-            :command => "apt-get -q -y#{expand_options(@new_resource.options)} install #{package_name}",
-            :environment => {
-              "DEBIAN_FRONTEND" => "noninteractive"
-            }
-          )
+          run_noninteractive("apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}")
         end
 
         def upgrade_package(name, version)
@@ -101,44 +100,30 @@ class Chef
 
         def remove_package(name, version)
           package_name = "#{name}"
-          run_command_with_systems_locale(
-            :command => "apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}",
-            :environment => {
-              "DEBIAN_FRONTEND" => "noninteractive"
-            }
-          )
+          run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}")
         end
 
         def purge_package(name, version)
-          run_command_with_systems_locale(
-            :command => "apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}",
-            :environment => {
-              "DEBIAN_FRONTEND" => "noninteractive"
-            }
-          )
+          run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}")
         end
 
-        def preseed_package(name, version)
-          preseed_file = get_preseed_file(name, version)
-          if preseed_file
-            Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
-            run_command_with_systems_locale(
-              :command => "debconf-set-selections #{preseed_file}",
-              :environment => {
-                "DEBIAN_FRONTEND" => "noninteractive"
-              }
-            )
-          end
+        def preseed_package(preseed_file)
+          Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
+          run_noninteractive("debconf-set-selections #{preseed_file}")
         end
 
         def reconfig_package(name, version)
           Chef::Log.info("#{@new_resource} reconfiguring")
-          run_command_with_systems_locale(
-            :command => "dpkg-reconfigure #{name}",
-            :environment => {
-              "DEBIAN_FRONTEND" => "noninteractive"
-            }
-          )
+          run_noninteractive("dpkg-reconfigure #{name}")
+        end
+
+        private
+
+        # Runs command via shell_out with magic environment to disable
+        # interactive prompts. Command is run with default localization rather
+        # than forcing locale to "C", so command output may not be stable.
+        def run_noninteractive(command)
+          shell_out!(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 3f94f26..8ec1ad5 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -30,35 +30,48 @@ class Chef
         DPKG_VERSION = /^Version: (.+)$/
 
         include Chef::Mixin::GetSourceFromPackage
+        def define_resource_requirements
+          super
+          requirements.assert(:install) do |a|
+            a.assertion{ not @new_resource.source.nil? }
+            a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+          end
+
+          # TODO this was originally written for any action in which .source is provided
+          # but would it make more sense to only look at source if the action is :install?
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @source_exists }
+            a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+            a.whyrun "Assuming it would have been previously downloaded."
+          end
+        end
 
         def load_current_resource
+          @source_exists = true
           @current_resource = Chef::Resource::Package.new(@new_resource.name)
           @current_resource.package_name(@new_resource.package_name)
           @new_resource.version(nil)
 
-          # if the source was not set, and we're installing, fail
-          if Array(@new_resource.action).include?(:install) && @new_resource.source.nil?
-            raise Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
-          end
-
-          # We only -need- source for action install
           if @new_resource.source
-            unless ::File.exists?(@new_resource.source)
-              raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
-            end
-
-            # Get information from the package if supplied
-            Chef::Log.debug("#{@new_resource} checking dpkg status")
-            status = popen4("dpkg-deb -W #{@new_resource.source}") do |pid, stdin, stdout, stderr|
-              stdout.each_line do |line|
-                if pkginfo = DPKG_INFO.match(line)
-                  @current_resource.package_name(pkginfo[1])
-                  @new_resource.version(pkginfo[2])
+            @source_exists = ::File.exists?(@new_resource.source)
+            if @source_exists
+              # Get information from the package if supplied
+              Chef::Log.debug("#{@new_resource} checking dpkg status")
+              status = popen4("dpkg-deb -W #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+                stdout.each_line do |line|
+                  if pkginfo = DPKG_INFO.match(line)
+                    @current_resource.package_name(pkginfo[1])
+                    @new_resource.version(pkginfo[2])
+                  end
                 end
               end
+            else
+              # Source provided but not valid means we can't safely do further processing
+              return
             end
+
           end
-          
+
           # Check to see if it is installed
           package_installed = nil
           Chef::Log.debug("#{@new_resource} checking install state")
@@ -79,10 +92,10 @@ class Chef
           unless status.exitstatus == 0 || status.exitstatus == 1
             raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!"
           end
-          
+
           @current_resource
         end
-     
+
         def install_package(name, version)
           run_command_with_systems_locale(
             :command => "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}",
@@ -100,7 +113,7 @@ class Chef
             }
           )
         end
-      
+
         def purge_package(name, version)
           run_command_with_systems_locale(
             :command => "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}",
diff --git a/lib/chef/provider/package/freebsd.rb b/lib/chef/provider/package/freebsd.rb
index afdd0d8..f9cb5eb 100644
--- a/lib/chef/provider/package/freebsd.rb
+++ b/lib/chef/provider/package/freebsd.rb
@@ -37,7 +37,7 @@ class Chef
 
         def current_installed_version
           pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
-          pkg_info.stdout[/^#{package_name}-(.+)/, 1]
+          pkg_info.stdout[/^#{Regexp.escape(package_name)}-(.+)/, 1]
         end
 
         def port_path
@@ -52,7 +52,7 @@ class Chef
           # Otherwise look up the path to the ports directory using 'whereis'
           else
             whereis = shell_out!("whereis -s #{@new_resource.package_name}", :env => nil)
-            unless path = whereis.stdout[/^#{@new_resource.package_name}:\s+(.+)$/, 1]
+            unless path = whereis.stdout[/^#{Regexp.escape(@new_resource.package_name)}:\s+(.+)$/, 1]
               raise Chef::Exceptions::Package, "Could not find port with the name #{@new_resource.package_name}"
             end
             path
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
new file mode 100644
index 0000000..2c6d98d
--- /dev/null
+++ b/lib/chef/provider/package/ips.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Jason J. W. Williams (<williamsjj at digitar.com>)
+# Author:: Stephen Nelson-Smith (<sns at opscode.com>)
+# Copyright:: Copyright (c) 2011 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 'open3'
+require 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/shell_out'
+
+class Chef
+  class Provider
+    class Package
+      class Ips < Chef::Provider::Package
+
+        include Chef::Mixin::ShellOut
+        attr_accessor :virtual
+
+        def define_resource_requirements
+          super
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { ! @candidate_version.nil? }
+            a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.package_name} not found"
+            a.whyrun "Assuming package #{@new_resource.package_name} would have been made available."
+          end
+        end
+
+        def load_current_resource
+          @current_resource = Chef::Resource::Package.new(@new_resource.name)
+          @current_resource.package_name(@new_resource.name)
+          check_package_state(@new_resource.package_name)
+          @current_resource
+        end
+
+        def check_package_state(package)
+          Chef::Log.debug("Checking package status for #{package}")
+          installed = false
+          depends = false
+
+          shell_out!("pkg info -r #{package}").stdout.each_line do |line|
+            case line
+            when /^\s+State: Installed/
+              installed = true
+            when /^\s+Version: (.*)/
+              @candidate_version = $1.split[0]
+              if installed
+                @current_resource.version($1)
+              else
+                @current_resource.version(nil)
+              end
+            end
+          end
+
+          return installed
+        end
+
+        def install_package(name, version)
+          package_name = "#{name}@#{version}"
+          normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}"
+          if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license
+            command = normal_command.gsub('-q', '-q --accept')
+          else
+            command = normal_command
+          end
+          begin
+            run_command_with_systems_locale(:command => command)
+          rescue
+          end
+        end
+
+        def upgrade_package(name, version)
+          install_package(name, version)
+        end
+
+        def remove_package(name, version)
+          package_name = "#{name}@#{version}"
+          run_command_with_systems_locale(
+            :command => "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}"
+          )
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index fd33788..6ef303e 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -44,7 +44,7 @@ class Chef
         def install_package(name, version)
           unless @current_resource.version == version
             command = "port#{expand_options(@new_resource.options)} install #{name}"
-            command << " @#{version}" if version and !version.empty? 
+            command << " @#{version}" if version and !version.empty?
             run_command_with_systems_locale(
               :command => command
             )
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index 0df909a..2e8bb78 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -24,7 +24,7 @@ class Chef
   class Provider
     class Package
       class Pacman < Chef::Provider::Package
-      
+
         def load_current_resource
           @current_resource = Chef::Resource::Package.new(@new_resource.name)
           @current_resource.package_name(@new_resource.package_name)
@@ -53,10 +53,19 @@ class Chef
         def candidate_version
           return @candidate_version if @candidate_version
 
+          repos = ["extra","core","community"]
+
+          if(::File.exists?("/etc/pacman.conf"))
+            pacman = ::File.read("/etc/pacman.conf")
+            repos = pacman.scan(/\[(.+)\]/).flatten
+          end
+
+          package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
+
           status = popen4("pacman -Ss #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
             stdout.each do |line|
               case line
-                when /^(extra|core|community)\/#{Regexp.escape(@new_resource.package_name)} (.+)$/
+                when /^(#{package_repos})\/#{Regexp.escape(@new_resource.package_name)} (.+)$/
                   # $2 contains a string like "4.4.0-1 (kde kdenetwork)" or "3.10-4 (base)"
                   # simply split by space and use first token
                   @candidate_version = $2.split(" ").first
@@ -75,27 +84,27 @@ class Chef
           @candidate_version
 
         end
-        
+
         def install_package(name, version)
           run_command_with_systems_locale(
             :command => "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
           )
         end
-      
+
         def upgrade_package(name, version)
           install_package(name, version)
         end
-      
+
         def remove_package(name, version)
           run_command_with_systems_locale(
             :command => "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
           )
         end
-      
+
         def purge_package(name, version)
           remove_package(name, version)
         end
-      
+
       end
     end
   end
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index eb13e98..ea8e37e 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -58,14 +58,19 @@ class Chef
 
         def parse_emerge(package, txt)
           availables = {}
-          package_without_category = package.split("/").last
           found_package_name = nil
 
           txt.each_line do |line|
             if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
-              found_package_name = $&.strip
-              if found_package_name == package || found_package_name.split("/").last == package_without_category
-                availables[found_package_name] = nil
+              found_package_name = $&.gsub(/\*/, '').strip
+              if package =~ /\// #the category is specified
+                if found_package_name == package
+                  availables[found_package_name] = nil
+                end
+              else #the category is not specified
+                if found_package_name.split("/").last == package
+                  availables[found_package_name] = nil
+                end
               end
             end
 
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index 5654db3..616a78a 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -27,16 +27,35 @@ class Chef
 
         include Chef::Mixin::GetSourceFromPackage
 
+        def define_resource_requirements
+          super
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @package_source_exists }
+            a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+            a.whyrun "Assuming package #{@new_resource.name} would have been made available."
+          end
+          requirements.assert(:all_actions) do |a|
+            a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) }
+            a.failure_message Chef::Exceptions::Package, "Unable to determine current version due to RPM failure. Detail: #{@rpm_status.inspect}"
+            a.whyrun "Assuming current version would have been determined for package#{@new_resource.name}."
+          end
+        end
+
         def load_current_resource
+          @package_source_provided = true
+          @package_source_exists = true
+
           @current_resource = Chef::Resource::Package.new(@new_resource.name)
           @current_resource.package_name(@new_resource.package_name)
           @new_resource.version(nil)
-          
+
           if @new_resource.source
             unless ::File.exists?(@new_resource.source)
-              raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+              @package_source_exists = false
+              return
             end
-            
+
             Chef::Log.debug("#{@new_resource} checking rpm status")
             status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr|
               stdout.each do |line|
@@ -49,12 +68,13 @@ class Chef
             end
           else
             if Array(@new_resource.action).include?(:install)
-              raise Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+              @package_source_exists = false
+              return
             end
           end
-          
+
           Chef::Log.debug("#{@new_resource} checking install state")
-          status = popen4("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+          @rpm_status = popen4("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
             stdout.each do |line|
               case line
               when /([\w\d_.-]+)\s([\w\d_.-]+)/
@@ -63,14 +83,11 @@ class Chef
               end
             end
           end
-          
-          unless status.exitstatus == 0 || status.exitstatus == 1
-            raise Chef::Exceptions::Package, "rpm failed - #{status.inspect}!"
-          end
-          
+
+
           @current_resource
         end
-        
+
         def install_package(name, version)
           unless @current_resource.version
             run_command_with_systems_locale(
@@ -82,9 +99,9 @@ class Chef
             )
           end
         end
-        
+
         alias_method :upgrade_package, :install_package
-        
+
         def remove_package(name, version)
           if version
             run_command_with_systems_locale(
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index e560e4b..b423c19 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -31,7 +31,14 @@ require 'rubygems/version'
 require 'rubygems/dependency'
 require 'rubygems/spec_fetcher'
 require 'rubygems/platform'
-require 'rubygems/format'
+
+# 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/dependency_installer'
 require 'rubygems/uninstaller'
 require 'rubygems/specification'
@@ -65,7 +72,7 @@ class Chef
             raise NotImplementedError
           end
 
-          ## 
+          ##
           # A rubygems specification object containing the list of gemspecs for all
           # available gems in the gem installation.
           # Implemented by subclasses
@@ -106,6 +113,22 @@ class Chef
           end
 
           ##
+          # Extracts the gemspec from a (on-disk) gem package.
+          # === Returns
+          # Gem::Specification
+          #
+          #--
+          # Compatibility note: Rubygems 1.x uses Gem::Format, 2.0 moved this
+          # code into Gem::Package.
+          def spec_from_file(file)
+            if defined?(Gem::Format) and Gem::Package.respond_to?(:open)
+              Gem::Format.from_file_by_path(file).spec
+            else
+              Gem::Package.new(file).spec
+            end
+          end
+
+          ##
           # Determines the candidate version for a gem from a .gem file on disk
           # and checks if it matches the version contraints in +gem_dependency+
           # === Returns
@@ -114,7 +137,7 @@ class Chef
           # nil           returns nil if the gem on disk doesn't match the
           #               version constraints for +gem_dependency+
           def candidate_version_from_file(gem_dependency, source)
-            spec = Gem::Format.from_file_by_path(source).spec
+            spec = spec_from_file(source)
             if spec.satisfies_requirement?(gem_dependency)
               logger.debug {"#{@new_resource} found candidate gem version #{spec.version} from local gem package #{source}"}
               spec.version
@@ -142,17 +165,26 @@ class Chef
           # Find the newest gem version available from Gem.sources that satisfies
           # the constraints of +gem_dependency+
           def find_newest_remote_version(gem_dependency, *sources)
-            # DependencyInstaller sorts the results such that the last one is
-            # always the one it considers best.
-            spec_with_source = dependency_installer.find_gems_with_sources(gem_dependency).last
+            available_gems = dependency_installer.find_gems_with_sources(gem_dependency)
+            spec, source = if available_gems.respond_to?(:last)
+              # DependencyInstaller sorts the results such that the last one is
+              # always the one it considers best.
+              spec_with_source = available_gems.last
+              spec_with_source && spec_with_source
+            else
+              # Rubygems 2.0 returns a Gem::Available set, which is a
+              # collection of AvailableSet::Tuple structs
+              available_gems.pick_best!
+              best_gem = available_gems.set.first
+              best_gem && [best_gem.spec, best_gem.source]
+            end
 
-            spec = spec_with_source && spec_with_source[0]
-            version = spec && spec_with_source[0].version
+            version = spec && spec.version
             if version
-              logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{spec_with_source[1]}" }
+              logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{source}" }
               version
             else
-              source_list = sources.compact.empty? ? "[#{Gem.sources.join(', ')}]" : "[#{sources.join(', ')}]"
+              source_list = sources.compact.empty? ? "[#{Gem.sources.to_a.join(', ')}]" : "[#{sources.join(', ')}]"
               logger.warn { "#{@new_resource} failed to find gem #{gem_dependency} from #{source_list}" }
               nil
             end
@@ -323,6 +355,7 @@ class Chef
         include Chef::Mixin::ShellOut
 
         attr_reader :gem_env
+        attr_reader :cleanup_gem_env
 
         def logger
           Chef::Log.logger
@@ -332,6 +365,7 @@ class Chef
 
         def initialize(new_resource, run_context=nil)
           super
+          @cleanup_gem_env = true
           if new_resource.gem_binary
             if new_resource.options && new_resource.options.kind_of?(Hash)
               msg =  "options cannot be given as a hash when using an explicit gem_binary\n"
@@ -343,12 +377,18 @@ class Chef
           elsif is_omnibus? && (!@new_resource.instance_of? Chef::Resource::ChefGem)
             # Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef
             # Default to installing somewhere more functional
+            if new_resource.options && new_resource.options.kind_of?(Hash)
+              msg = "options should be a string instead of a hash\n"
+              msg << "in #{new_resource} from #{new_resource.source_line}"
+              raise ArgumentError, msg
+            end
             gem_location = find_gem_by_path
             @new_resource.gem_binary gem_location
             @gem_env = AlternateGemEnvironment.new(gem_location)
             Chef::Log.debug("#{@new_resource} using gem '#{gem_location}'")
           else
             @gem_env = CurrentGemEnvironment.new
+            @cleanup_gem_env = false
             Chef::Log.debug("#{@new_resource} using gem from running ruby environment")
           end
         end
@@ -385,6 +425,9 @@ class Chef
           # URI.parse gets confused by MS Windows paths with forward slashes.
           scheme = nil if scheme =~ /^[a-z]$/
           %w{http https}.include?(scheme)
+        rescue URI::InvalidURIError
+          Chef::Log.debug("#{@new_resource} failed to parse source '#{@new_resource.source}' as a URI, assuming a local path")
+          false
         end
 
         def current_version
@@ -430,6 +473,13 @@ class Chef
           @current_resource
         end
 
+        def cleanup_after_converge
+          if @cleanup_gem_env
+            logger.debug { "#{@new_resource} resetting gem environment to default" }
+            Gem.clear_paths
+          end
+        end
+
         def candidate_version
           @candidate_version ||= begin
             if target_version_already_installed?
@@ -487,7 +537,7 @@ class Chef
           if version
             shell_out!("#{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!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
           end
         end
 
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index a3ef1e5..28d56dd 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -2,6 +2,7 @@
 # Authors:: Trevor O (trevoro at joyent.com)
 #           Bryan McLellan (btm at loftninjas.org)
 #           Matthew Landauer (matthew at openaustralia.org)
+#           Ben Rockwood (benr at joyent.com)
 # Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
 # License:: Apache License, Version 2.0
 #
@@ -17,10 +18,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-# Notes
-#
-#  * Supports installing using a local package name
-#  * Otherwise reverts to installing from the pkgsrc repositories URL
 
 require 'chef/provider/package'
 require 'chef/mixin/shell_out'
@@ -36,47 +33,61 @@ class Chef
 
 
         def load_current_resource
-					Chef::Log.debug("#{@new_resource} loading current resource")
-					@current_resource = Chef::Resource::Package.new(@new_resource.name)
-					@current_resource.package_name(@new_resource.package_name)
-					@current_resource.version(nil)
+          Chef::Log.debug("#{@new_resource} loading current resource")
+          @current_resource = Chef::Resource::Package.new(@new_resource.name)
+          @current_resource.package_name(@new_resource.package_name)
+          @current_resource.version(nil)
           check_package_state(@new_resource.package_name)
-					@current_resource # modified by check_package_state
-				end
-				
-				def check_package_state(name)
-					Chef::Log.debug("#{@new_resource} checking package #{name}")
-					# XXX
-					version = nil
-					info = shell_out!("pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1])
-					
-					if info.stdout
-						version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
+          @current_resource # modified by check_package_state
+        end
+
+        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])
+
+          if info.stdout
+            version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
+          end
+
+          if !version
+            @current_resource.version(nil)
+          else
+            @current_resource.version(version)
           end
+        end
 
-					if !version
-						@current_resource.version(nil)
-					else
-						@current_resource.version(version)
-					end
+        def candidate_version
+          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.stdout.each_line do |line|
+            case line
+            when /^#{new_resource.package_name}/
+              name, version = line.split[0].split(/-([^-]+)$/)
+            end
+          end
+          @candidate_version = version
+          version
         end
 
         def install_package(name, version)
-					Chef::Log.debug("#{@new_resource} installing package #{name}-#{version}")
-					package = "#{name}-#{version}"
-          out = shell_out!("pkgin -y install #{package}", :env => nil)
+          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)
         end
 
-				def upgrade_package(name, version)
-					Chef::Log.debug("#{@new_resource} upgrading package #{name}-#{version}")
-					install_package(name, version)
-				end
+        def upgrade_package(name, version)
+          Chef::Log.debug("#{@new_resource} upgrading package #{name} version #{version}")
+          install_package(name, version)
+        end
 
-				def remove_package(name, version)
-					Chef::Log.debug("#{@new_resource} removing package #{name}-#{version}")
-					package = "#{name}-#{version}"
-          out = shell_out!("pkgin -y remove #{package}", :env => nil)
-				end
+        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)
+        end
 
       end
     end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index f87c30d..0f45b61 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -31,6 +31,18 @@ class Chef
         #   super
         #   @current_resource = Chef::Resource::Package.new(@new_resource.name)
         # end
+        def define_resource_requirements
+          super
+          requirements.assert(:install) do |a|
+            a.assertion { @new_resource.source }
+            a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+          end
+          requirements.assert(:all_actions) do |a|
+            a.assertion { !@new_resource.source || @package_source_found }
+            a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+            a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
+          end
+        end
 
         def load_current_resource
           @current_resource = Chef::Resource::Package.new(@new_resource.name)
@@ -38,21 +50,18 @@ class Chef
           @new_resource.version(nil)
 
           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 pkg status")
-            status = popen4("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
-              stdout.each do |line|
-                case line
-                when /VERSION:\s+(.+)/
-                  @new_resource.version($1)
+            @package_source_found = ::File.exists?(@new_resource.source)
+            if @package_source_found
+              Chef::Log.debug("#{@new_resource} checking pkg status")
+              status = popen4("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+                stdout.each do |line|
+                  case line
+                  when /VERSION:\s+(.+)/
+                    @new_resource.version($1)
+                  end
                 end
               end
             end
-          elsif Array(@new_resource.action).include?(:install)
-            raise Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
           end
 
           Chef::Log.debug("#{@new_resource} checking install state")
@@ -98,13 +107,23 @@ class Chef
         def install_package(name, version)
           Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
           if @new_resource.options.nil?
+            if ::File.directory?(@new_resource.source) # CHEF-4469
+              command = "pkgadd -n -d #{@new_resource.source} #{@new_resource.package_name}"
+            else
+              command = "pkgadd -n -d #{@new_resource.source} all"
+            end
             run_command_with_systems_locale(
-                    :command => "pkgadd -n -d #{@new_resource.source} all"
+                    :command => command
                   )
             Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
           else
+            if ::File.directory?(@new_resource.source) # CHEF-4469
+              command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}"
+            else
+              command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
+            end
             run_command_with_systems_locale(
-              :command => "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
+              :command => command
             )
             Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
           end
diff --git a/lib/chef/provider/package/yum-dump.py b/lib/chef/provider/package/yum-dump.py
index 99136ec..a8f3995 100644
--- a/lib/chef/provider/package/yum-dump.py
+++ b/lib/chef/provider/package/yum-dump.py
@@ -6,9 +6,9 @@
 # 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.
@@ -107,6 +107,14 @@ def setup(yb, options):
     elif YUM_MAJOR == 2:
       yb.conf.setConfigOption('cache', options.cache)
 
+  # Handle repo toggle via id or glob exactly like yum
+  for opt, repos in options.repo_control:
+      for repo in repos:
+        if opt == '--enablerepo':
+            yb.repos.enableRepo(repo)
+        elif opt == '--disablerepo':
+            yb.repos.disableRepo(repo)
+
   return 0
 
 def dump_packages(yb, list, output_provides):
@@ -239,6 +247,12 @@ def yum_dump(options):
       print >> sys.stderr, "yum-dump Unlock Error: %s" % e
       return 200
 
+# Preserve order of enable/disable repo args like yum does
+def gather_repo_opts(option, opt, value, parser):
+  if getattr(parser.values, option.dest, None) is None:
+    setattr(parser.values, option.dest, [])
+  getattr(parser.values, option.dest).append((opt, value.split(',')))
+
 def main():
   usage = "Usage: %prog [options]\n" + \
           "Output a list of installed, available and re-installable packages via yum"
@@ -261,6 +275,12 @@ def main():
   parser.add_option("-a", "--available",
                     action="store_const", const="available", dest="package_list", default="all",
                     help="output only available and re-installable packages")
+  parser.add_option("--enablerepo",
+                    action="callback",  callback=gather_repo_opts, type="string", dest="repo_control", default=[],
+                    help="enable disabled repositories by id or glob")
+  parser.add_option("--disablerepo",
+                    action="callback",  callback=gather_repo_opts, type="string", dest="repo_control", default=[],
+                    help="disable repositories by id or glob")
 
   (options, args) = parser.parse_args()
 
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 9048048..f56d314 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -16,8 +16,10 @@
 # limitations under the License.
 #
 
+require 'chef/config'
 require 'chef/provider/package'
 require 'chef/mixin/command'
+require 'chef/mixin/shell_out'
 require 'chef/resource/package'
 require 'singleton'
 require 'chef/mixin/get_source_from_package'
@@ -645,6 +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::ShellOut
           include Singleton
 
           def initialize
@@ -664,12 +667,16 @@ class Chef
 
             @allow_multi_install = []
 
+            @extra_repo_control = nil
+
             # these are for subsequent runs if we are on an interval
             Chef::Client.when_run_starts do
               YumCache.instance.reload
             end
           end
 
+          attr_reader :extra_repo_control
+
           # Cache management
           #
 
@@ -693,13 +700,19 @@ class Chef
               raise ArgumentError, "Unexpected value in next_refresh: #{@next_refresh}"
             end
 
+            if @extra_repo_control
+              opts << " #{@extra_repo_control}"
+            end
+
             one_line = false
             error = nil
 
             helper = ::File.join(::File.dirname(__FILE__), 'yum-dump.py')
+            status = nil
 
-            status = popen4("/usr/bin/python #{helper}#{opts}", :waitlast => true) do |pid, stdin, stdout, stderr|
-              stdout.each do |line|
+            begin
+              status = shell_out!("/usr/bin/python #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
+              status.stdout.each_line do |line|
                 one_line = true
 
                 line.chomp!
@@ -746,7 +759,10 @@ class Chef
                 @rpmdb << pkg
               end
 
-              error = stderr.readlines
+              error = status.stderr
+            rescue Mixlib::ShellOut::CommandTimeout => e
+              Chef::Log.error("#{helper} exceeded timeout #{Chef::Config[:yum_timeout]}")
+              raise(e)
             end
 
             if status.exitstatus != 0
@@ -848,6 +864,22 @@ class Chef
             @allow_multi_install
           end
 
+          def enable_extra_repo_control(arg)
+            # Don't touch cache if it's the same repos as the last load
+            unless @extra_repo_control == arg
+              @extra_repo_control = arg
+              reload
+            end
+          end
+
+          def disable_extra_repo_control
+            # Only force reload when set
+            if @extra_repo_control
+              @extra_repo_control = nil
+              reload
+            end
+          end
+
           private
 
           def version(package_name, arch=nil, is_available=false, is_installed=false)
@@ -915,6 +947,7 @@ class Chef
         end # YumCache
 
         include Chef::Mixin::GetSourceFromPackage
+        include Chef::Mixin::ShellOut
 
         def initialize(new_resource, run_context)
           super
@@ -957,7 +990,7 @@ class Chef
         end
 
         def yum_command(command)
-          status, stdout, stderr = output_of_command(command, {})
+          status, stdout, stderr = output_of_command(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
@@ -974,7 +1007,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, stdout, stderr = output_of_command(command, {})
+                status, stdout, stderr = output_of_command(command, {:timeout => Chef::Config[:yum_timeout]})
                 break
               end
             end
@@ -995,8 +1028,25 @@ class Chef
             @yum.reload
           end
 
+          if @new_resource.options
+            repo_control = []
+            @new_resource.options.split.each do |opt|
+              if opt =~ %r{--(enable|disable)repo=.+}
+                repo_control << opt
+              end
+            end
+
+            if repo_control.size > 0
+              @yum.enable_extra_repo_control(repo_control.join(" "))
+            else
+              @yum.disable_extra_repo_control
+            end
+          else
+            @yum.disable_extra_repo_control
+          end
+
           # At this point package_name could be:
-          # 
+          #
           # 1) a package name, eg: "foo"
           # 2) a package name.arch, eg: "foo.i386"
           # 3) or a dependency, eg: "foo >= 1.1"
@@ -1021,13 +1071,11 @@ class Chef
             end
 
             Chef::Log.debug("#{@new_resource} checking rpm status")
-            status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr|
-              stdout.each do |line|
-                case line
-                when /([\w\d_.-]+)\s([\w\d_.-]+)/
-                  @current_resource.package_name($1)
-                  @new_resource.version($2)
-                end
+            shell_out!("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)
+                @new_resource.version($2)
               end
             end
           end
@@ -1106,7 +1154,7 @@ class Chef
         # Hacky - better overall solution? Custom compare in Package provider?
         def action_upgrade
           # Could be uninstalled or have no candidate
-          if @current_resource.version.nil? || candidate_version.nil? 
+          if @current_resource.version.nil? || candidate_version.nil?
             super
           # Ensure the candidate is newer
           elsif RPMVersion.parse(candidate_version) > RPMVersion.parse(@current_resource.version)
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index 4372746..b288d5d 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -1,14 +1,17 @@
+# -*- coding: utf-8 -*-
 #
-# Author:: Adam Jacob (<adam at opscode.com>)
+# Authors:: Adam Jacob (<adam at opscode.com>)
+#           Ionuț Arțăriși (<iartarisi at suse.cz>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
+#             Copyright (c) 2013 SUSE Linux GmbH
 # 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.
@@ -19,14 +22,16 @@
 require 'chef/provider/package'
 require 'chef/mixin/command'
 require 'chef/resource/package'
+require 'chef/mixin/shell_out'
 require 'singleton'
 
 class Chef
   class Provider
     class Package
-      class Zypper < Chef::Provider::Package  
-      
- 
+      class Zypper < Chef::Provider::Package
+
+        include Chef::Mixin::ShellOut
+
         def load_current_resource
           @current_resource = Chef::Resource::Package.new(@new_resource.name)
           @current_resource.package_name(@new_resource.package_name)
@@ -36,7 +41,7 @@ class Chef
           version=''
           oud_version=''
           Chef::Log.debug("#{@new_resource} checking zypper")
-          status = popen4("zypper info #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+          status = popen4("zypper --non-interactive info #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
             stdout.each do |line|
               case line
               when /^Version: (.+)$/
@@ -45,7 +50,7 @@ class Chef
               when /^Installed: Yes$/
                 is_installed=true
                 Chef::Log.debug("#{@new_resource} is installed")
-                
+
               when /^Installed: No$/
                 is_installed=false
                 Chef::Log.debug("#{@new_resource} is not installed")
@@ -61,12 +66,12 @@ class Chef
             @candidate_version=version
             @current_resource.version(nil)
           end
- 
+
           if is_installed==true
             if is_out_of_date==true
               @current_resource.version(oud_version)
               @candidate_version=version
-            else 
+            else
               @current_resource.version(version)
               @candidate_version=version
             end
@@ -75,69 +80,54 @@ class Chef
           unless status.exitstatus == 0
             raise Chef::Exceptions::Package, "zypper failed - #{status.inspect}!"
           end
-          
+
           @current_resource
         end
-        
-        #Gets the zypper Version from command output (Returns Floating Point number)
+
         def zypper_version()
           `zypper -V 2>&1`.scan(/\d+/).join(".").to_f
         end
 
         def install_package(name, version)
-          if zypper_version < 1.0
-            run_command(
-              :command => "zypper install -y #{name}"
-            )
-          elsif version
-            run_command(
-              :command => "zypper -n --no-gpg-checks install -l  #{name}=#{version}"
-            )
-          else
-            run_command(
-              :command => "zypper -n --no-gpg-checks install -l  #{name}"
-            )
-          end
+          zypper_package("install --auto-agree-with-licenses", name, version)
         end
 
         def upgrade_package(name, version)
-          if zypper_version < 1.0
-            run_command(
-              :command => "zypper install -y #{name}"
-            )
-          elsif version
-            run_command(
-              :command => "zypper -n --no-gpg-checks install -l #{name}=#{version}"
-            )
-          else
-            run_command(
-              :command => "zypper -n --no-gpg-checks install -l #{name}"
-            )
-          end
+          install_package(name, version)
         end
 
         def remove_package(name, version)
+          zypper_package("remove", name, version)
+        end
+
+        def purge_package(name, version)
+          zypper_package("remove --clean-deps", name, version)
+        end
+
+        private
+        def zypper_package(command, pkgname, version)
+          version = "=#{version}" unless version.empty?
           if zypper_version < 1.0
-            run_command(
-              :command => "zypper remove -y #{name}"
-            )
-          elsif version
-            run_command(
-              :command => "zypper -n --no-gpg-checks remove  #{name}=#{version}"
-            )
+            shell_out!("zypper#{gpg_checks} #{command} -y #{pkgname}")
           else
-            run_command(
-              :command => "zypper -n --no-gpg-checks remove  #{name}"
-            )
+            shell_out!("zypper --non-interactive#{gpg_checks} "+
+                      "#{command} #{pkgname}#{version}")
           end
-            
-         
         end
-      
-        def purge_package(name, version)
-          remove_package(name, version)
+
+        def gpg_checks()
+          case Chef::Config[:zypper_check_gpg]
+          when true
+            ""
+          when false
+            " --no-gpg-checks"
+          when nil
+            Chef::Log.warn("Chef::Config[:zypper_check_gpg] was not set. " +
+              "All packages will be installed without gpg signature checks. " +
+              "This is a security hazard.")
+            " --no-gpg-checks"
+          end
         end
-      
       end
     end
   end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
new file mode 100644
index 0000000..c459cdf
--- /dev/null
+++ b/lib/chef/provider/powershell_script.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/windows_script'
+
+class Chef
+  class Provider
+    class PowershellScript < Chef::Provider::WindowsScript
+
+      protected
+
+      EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -eq $true) {exit 0} elseif ( $LASTEXITCODE -ne 0) {exit $LASTEXITCODE} else { exit 1 }"
+      EXIT_STATUS_RESET_SCRIPT = "$LASTEXITCODE=0\n"
+
+      # 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 NormalizeScriptExitStatus( code )
+        @code = (! code.nil?) ? ( EXIT_STATUS_RESET_SCRIPT + code + EXIT_STATUS_NORMALIZATION_SCRIPT ) : nil
+      end
+
+      public
+
+      def initialize (new_resource, run_context)
+        super(new_resource, run_context, '.ps1')
+        NormalizeScriptExitStatus(new_resource.code)
+      end
+
+      def flags
+        default_flags = [
+          "-NoLogo",
+          "-NonInteractive",
+          "-NoProfile",
+          "-ExecutionPolicy RemoteSigned",
+          # 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"
+        ]
+
+        interpreter_flags = default_flags.join(' ')
+
+        if ! (@new_resource.flags.nil?)
+          interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
+        end
+
+        interpreter_flags
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
new file mode 100644
index 0000000..b7bcdd9
--- /dev/null
+++ b/lib/chef/provider/registry_key.rb
@@ -0,0 +1,156 @@
+#
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+#
+# Copyright:: 2011, Opscode, 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/config'
+require 'chef/log'
+require 'chef/resource/file'
+require 'chef/mixin/checksum'
+require 'chef/provider'
+require 'etc'
+require 'fileutils'
+require 'chef/scan_access_control'
+require 'chef/mixin/shell_out'
+require 'chef/win32/registry'
+
+class Chef
+
+  class Provider
+    class RegistryKey < Chef::Provider
+      include Chef::Mixin::Checksum
+      include Chef::Mixin::ShellOut
+
+      def whyrun_supported?
+        true
+      end
+
+      def running_on_windows!
+        unless Chef::Platform.windows?
+          raise Chef::Exceptions::Win32NotWindows, "Attempt to manipulate the windows registry on a non-windows node"
+        end
+      end
+
+      def load_current_resource
+        running_on_windows!
+        @current_resource ||= Chef::Resource::RegistryKey.new(@new_resource.key, run_context)
+        @current_resource.key(@new_resource.key)
+        @current_resource.architecture(@new_resource.architecture)
+        @current_resource.recursive(@new_resource.recursive)
+        if registry.key_exists?(@new_resource.key)
+          @current_resource.values(registry.get_values(@new_resource.key))
+        end
+        values_to_hash(@current_resource.values)
+        @current_resource
+      end
+
+      def registry
+        @registry ||= Chef::Win32::Registry.new(@run_context, @new_resource.architecture)
+      end
+
+     def values_to_hash(values)
+        if values
+         @name_hash = Hash[values.map { |val| [val[:name], val] }]
+        else
+          @name_hash = {}
+        end
+      end
+
+      def define_resource_requirements
+        requirements.assert(:create, :create_if_missing, :delete, :delete_key) do |a|
+          a.assertion{ registry.hive_exists?(@new_resource.key) }
+          a.failure_message(Chef::Exceptions::Win32RegHiveMissing, "Hive #{@new_resource.key.split("\\").shift} does not exist")
+        end
+        requirements.assert(:create) do |a|
+          a.assertion{ registry.key_exists?(@new_resource.key) }
+          a.whyrun("Key #{@new_resource.key} does not exist. Unless it would have been created before, attempt to modify its values would fail.")
+        end
+        requirements.assert(:create, :create_if_missing) do |a|
+          #If keys missing in the path and recursive == false
+          a.assertion{ !registry.keys_missing?(@current_resource.key) || @new_resource.recursive }
+          a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "Intermediate keys missing but recursive is set to false")
+          a.whyrun("Intermediate keys in #{@new_resource.key} go not exist. Unless they would have been created earlier, attempt to modify them would fail.")
+        end
+        requirements.assert(:delete_key) do |a|
+          #If key to be deleted has subkeys but recurssive == false
+          a.assertion{ !registry.key_exists?(@new_resource.key) || !registry.has_subkeys?(@new_resource.key) || @new_resource.recursive }
+          a.failure_message(Chef::Exceptions::Win32RegNoRecursive, "#{@new_resource.key} has subkeys but recursive is set to false.")
+          a.whyrun("#{@current_resource.key} has subkeys, but recursive is set to false. attempt to delete would fails unless subkeys were deleted prior to this action.")
+        end
+      end
+
+      def action_create
+        unless registry.key_exists?(@current_resource.key)
+          converge_by("create key #{@new_resource.key}") do
+            registry.create_key(@new_resource.key, @new_resource.recursive)
+          end
+        end
+        @new_resource.values.each do |value|
+          if @name_hash.has_key?(value[:name])
+            current_value = @name_hash[value[:name]]
+            unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
+              converge_by("set value #{value}") do
+                registry.set_value(@new_resource.key, value)
+              end
+            end
+          else
+            converge_by("set value #{value}") do
+              registry.set_value(@new_resource.key, value)
+            end
+          end
+        end
+      end
+
+      def action_create_if_missing
+        unless registry.key_exists?(@new_resource.key)
+          converge_by("create key #{@new_resource.key}") do
+            registry.create_key(@new_resource.key, @new_resource.recursive)
+          end
+        end
+        @new_resource.values.each do |value|
+          unless @name_hash.has_key?(value[:name])
+            converge_by("create value #{value}") do
+              registry.set_value(@new_resource.key, value)
+            end
+          end
+        end
+      end
+
+      def action_delete
+        if registry.key_exists?(@new_resource.key)
+          @new_resource.values.each do |value|
+            if @name_hash.has_key?(value[:name])
+              converge_by("delete value #{value}") do
+                registry.delete_value(@new_resource.key, value)
+              end
+            end
+          end
+        end
+      end
+
+      def action_delete_key
+        if registry.key_exists?(@new_resource.key)
+          converge_by("delete key #{@new_resource.key}") do
+            registry.delete_key(@new_resource.key, @new_resource.recursive)
+          end
+        end
+      end
+
+    end
+  end
+end
+
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index 9a0f67b..129c520 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.rb
@@ -20,6 +20,7 @@ require 'chef/provider/file'
 require 'chef/provider/directory'
 require 'chef/resource/directory'
 require 'chef/resource/remote_file'
+require 'chef/mixin/file_class'
 require 'chef/platform'
 require 'uri'
 require 'tempfile'
@@ -30,14 +31,16 @@ class Chef
   class Provider
     class RemoteDirectory < Chef::Provider::Directory
 
+      include Chef::Mixin::FileClass
+
       def action_create
         super
+        files_to_purge = Set.new(Dir.glob(::File.join(@new_resource.path, '**', '*'),
+                                          ::File::FNM_DOTMATCH).select do |name|
+                                   name !~ /(?:^|#{Regexp.escape(::File::SEPARATOR)})\.\.?$/
+                                 end)
+
 
-        files_to_purge = Set.new(
-          Dir.glob(::File.join(@new_resource.path, '**', '*'), ::File::FNM_DOTMATCH).select do |name|
-            name !~ /(?:^|#{Regexp.escape(::File::SEPARATOR)})\.\.?$/
-          end
-        )
         files_to_transfer.each do |cookbook_file_relative_path|
           create_cookbook_file(cookbook_file_relative_path)
           # the file is removed from the purge list
@@ -62,17 +65,31 @@ class Chef
       def purge_unmanaged_files(unmanaged_files)
         if @new_resource.purge
           unmanaged_files.sort.reverse.each do |f|
-            if ::File.directory?(f) && !::File.symlink?(f)
-              Dir::rmdir(f)
-              Chef::Log.debug("#{@new_resource} removed directory #{f}")
+            # file_class comes from Chef::Mixin::FileClass
+            if ::File.directory?(f) && !Chef::Platform.windows? && !file_class.symlink?(f.dup)
+              # Linux treats directory symlinks as files
+              # Remove a directory as a directory when not on windows if it is not a symlink
+              purge_directory(f)
+            elsif ::File.directory?(f) && Chef::Platform.windows?
+              # Windows treats directory symlinks as directories so we delete them here
+              purge_directory(f)
             else
-              ::File.delete(f)
-              Chef::Log.debug("#{@new_resource} deleted file #{f}")
+              converge_by("delete unmanaged file #{f}") do
+                ::File.delete(f)
+                Chef::Log.debug("#{@new_resource} deleted file #{f}")
+              end
             end
           end
         end
       end
 
+      def purge_directory(dir)
+        converge_by("delete unmanaged directory #{dir}") do
+          Dir::rmdir(dir)
+          Chef::Log.debug("#{@new_resource} removed directory #{dir}")
+        end
+      end
+
       def files_to_transfer
         cookbook = run_context.cookbook_collection[resource_cookbook]
         files = cookbook.relative_filenames_in_preferred_directory(node, :files, @new_resource.source)
@@ -116,10 +133,10 @@ class Chef
             cookbook_file.rights(permission, *args)
           end
         end
-        cookbook_file.mode(@new_resource.files_mode)    if @new_resource.files_mode
-        cookbook_file.group(@new_resource.files_group)  if @new_resource.files_group
-        cookbook_file.owner(@new_resource.files_owner)  if @new_resource.files_owner
-        cookbook_file.backup(@new_resource.files_backup) if @new_resource.files_backup
+        cookbook_file.mode(@new_resource.files_mode)       if @new_resource.files_mode
+        cookbook_file.group(@new_resource.files_group)     if @new_resource.files_group
+        cookbook_file.owner(@new_resource.files_owner)     if @new_resource.files_owner
+        cookbook_file.backup(@new_resource.files_backup)   if @new_resource.files_backup
 
         cookbook_file
       end
@@ -153,6 +170,10 @@ class Chef
         dir
       end
 
+      def whyrun_supported?
+        true
+      end
+
     end
   end
 end
diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb
index 8f5c61a..d62f4aa 100644
--- a/lib/chef/provider/remote_file.rb
+++ b/lib/chef/provider/remote_file.rb
@@ -1,4 +1,5 @@
 #
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
@@ -17,112 +18,28 @@
 #
 
 require 'chef/provider/file'
-require 'chef/rest'
-require 'uri'
-require 'tempfile'
-require 'net/https'
+require 'chef/deprecation/provider/remote_file'
+require 'chef/deprecation/warnings'
 
 class Chef
   class Provider
     class RemoteFile < Chef::Provider::File
 
-      def load_current_resource
-        super
-        @current_resource.checksum(checksum(@current_resource.path)) if ::File.exist?(@current_resource.path)
-      end
-
-      def action_create
-        assert_enclosing_directory_exists!
-
-        Chef::Log.debug("#{@new_resource} checking for changes")
-
-        if current_resource_matches_target_checksum?
-          Chef::Log.debug("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating")
-        else
-          Chef::REST.new(@new_resource.source, nil, nil, http_client_opts).fetch(@new_resource.source) do |raw_file|
-            if matches_current_checksum?(raw_file)
-              Chef::Log.debug "#{@new_resource} target and source checksums are the same - not updating"
-            else
-              backup_new_resource
-              FileUtils.cp raw_file.path, @new_resource.path
-              Chef::Log.info "#{@new_resource} updated"
-              @new_resource.updated_by_last_action(true)
-            end
-          end
-        end
-        enforce_ownership_and_permissions
-
-        @new_resource.updated_by_last_action?
-      end
-
-      def action_create_if_missing
-        if ::File.exists?(@new_resource.path)
-          Chef::Log.debug("#{@new_resource} exists, taking no action.")
-        else
-          action_create
-        end
-      end
-
-      def current_resource_matches_target_checksum?
-        @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{Regexp.escape(@new_resource.checksum)}/
-      end
+      extend Chef::Deprecation::Warnings
+      include Chef::Deprecation::Provider::RemoteFile
+      add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteFile.instance_methods)
 
-      def matches_current_checksum?(candidate_file)
-        Chef::Log.debug "#{@new_resource} checking for file existence of #{@new_resource.path}"
-        if ::File.exists?(@new_resource.path)
-          Chef::Log.debug "#{@new_resource} file exists at #{@new_resource.path}"
-          @new_resource.checksum(checksum(candidate_file.path))
-          Chef::Log.debug "#{@new_resource} target checksum: #{@current_resource.checksum}"
-          Chef::Log.debug "#{@new_resource} source checksum: #{@new_resource.checksum}"
-
-          @new_resource.checksum == @current_resource.checksum
-        else
-          Chef::Log.debug "#{@new_resource} creating #{@new_resource.path}"
-          false
-        end
-      end
-
-      def backup_new_resource
-        if ::File.exists?(@new_resource.path)
-          Chef::Log.debug "#{@new_resource} checksum changed from #{@current_resource.checksum} to #{@new_resource.checksum}"
-          backup @new_resource.path
-        end
-      end
-
-      def source_file(source, current_checksum, &block)
-        if absolute_uri?(source)
-          fetch_from_uri(source, &block)
-        elsif !Chef::Config[:solo]
-          fetch_from_chef_server(source, current_checksum, &block)
-        else
-          fetch_from_local_cookbook(source, &block)
-        end
-      end
-
-      def http_client_opts
-        opts={}
-        # CHEF-3140
-        # 1. If it's already compressed, trying to compress it more will
-        # probably be counter-productive.
-        # 2. Some servers are misconfigured so that you GET $URL/file.tgz but
-        # they respond with content type of tar and content encoding of gzip,
-        # which tricks Chef::REST into decompressing the response body. In this
-        # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
-        # which is not what you wanted.
-        if @new_resource.path =~ /gz$/ or @new_resource.source =~ /gz$/
-          opts[:disable_gzip] = true
-        end
-        opts
+      def initialize(new_resource, run_context)
+        @content_class = Chef::Provider::RemoteFile::Content
+        super
       end
 
-      private
-
-      def absolute_uri?(source)
-        URI.parse(source).absolute?
-      rescue URI::InvalidURIError
-        false
+      def load_current_resource
+        @current_resource = Chef::Resource::RemoteFile.new(@new_resource.name)
+        super
       end
 
     end
   end
 end
+
diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb
new file mode 100644
index 0000000..27c7246
--- /dev/null
+++ b/lib/chef/provider/remote_file/cache_control_data.rb
@@ -0,0 +1,165 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'stringio'
+require 'chef/file_cache'
+require 'chef/json_compat'
+require 'chef/digester'
+require 'chef/exceptions'
+
+class Chef
+  class Provider
+    class RemoteFile
+
+      # == CacheControlData
+      # Implements per-uri storage of cache control data for a remote resource
+      # along with a sanity check checksum of the file in question.
+      # Provider::RemoteFile protocol implementation classes can use this
+      # information to avoid re-fetching files when the current copy is up to
+      # date. The way this information is used is protocol-dependent. For HTTP,
+      # this information is sent to the origin server via headers to make a
+      # conditional GET request.
+      #
+      # == API
+      # The general shape of the API is active-record-the-pattern-like. New
+      # instances should be instantiated via
+      # `CacheControlData.load_and_validate`, which will do a find-or-create
+      # operation and then sanity check the data against the checksum of the
+      # current copy of the file. If there is no data or the sanity check
+      # fails, the `etag` and `mtime` attributes will be set to nil; otherwise
+      # they are populated with the previously saved values.
+      #
+      # After fetching a file, the CacheControlData instance should be updated
+      # with new etag, mtime and checksum values in whatever format is
+      # preferred by the protocol used. Then call #save to save the data to disk.
+      class CacheControlData
+
+        def self.load_and_validate(uri, current_copy_checksum)
+          ccdata = new(uri)
+          ccdata.load
+          ccdata.validate!(current_copy_checksum)
+          ccdata
+        end
+
+        # Entity Tag of the resource. HTTP-specific. See also:
+        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.2
+        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19
+        attr_accessor :etag
+
+        # Last modified time of the remote resource. Different protocols will
+        # use different types for this field (e.g., string representation of a
+        # specific date format, integer, etc.) For HTTP-specific references,
+        # see:
+        # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
+        # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.1
+        # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
+        attr_accessor :mtime
+
+        # SHA2-256 Hash of the file as last fetched.
+        attr_accessor :checksum
+
+        # URI of the resource as a String. This is the "primary key" used for
+        # storage and retrieval.
+        attr_reader :uri
+
+        def initialize(uri)
+          uri = uri.dup
+          uri.password = "XXXX" unless uri.userinfo.nil?
+          @uri = uri.to_s
+        end
+
+        def load
+          if previous_cc_data = load_data
+            apply(previous_cc_data)
+            self
+          else
+            false
+          end
+        end
+
+        def validate!(current_copy_checksum)
+          if current_copy_checksum.nil? or checksum != current_copy_checksum
+            reset!
+            false
+          else
+            true
+          end
+        end
+
+        # Saves the data to disk using Chef::FileCache. The filename is a
+        # sanitized version of the URI with a MD5 of the same URI appended (to
+        # avoid collisions between different URIs having the same sanitized
+        # form).
+        def save
+          Chef::FileCache.store("remote_file/#{sanitized_cache_file_basename}", json_data)
+        end
+
+        # :nodoc:
+        # JSON representation of this object for storage.
+        def json_data
+          Chef::JSONCompat.to_json(hash_data)
+        end
+
+        private
+
+        def hash_data
+          as_hash = {}
+          as_hash["etag"]     = etag
+          as_hash["mtime"]    = mtime
+          as_hash["checksum"] = checksum
+          as_hash
+        end
+
+        def reset!
+          @etag, @mtime = nil, nil
+        end
+
+        def apply(previous_cc_data)
+          @etag = previous_cc_data["etag"]
+          @mtime = previous_cc_data["mtime"]
+          @checksum = previous_cc_data["checksum"]
+        end
+
+        def load_data
+          Chef::JSONCompat.from_json(load_json_data)
+        rescue Chef::Exceptions::FileNotFound, Yajl::ParseError
+          false
+        end
+
+        def load_json_data
+          Chef::FileCache.load("remote_file/#{sanitized_cache_file_basename}")
+        end
+
+        def sanitized_cache_file_basename
+          # Scrub and truncate in accordance with the goals of keeping the name
+          # human-readable but within the bounds of local file system
+          # path length limits
+          scrubbed_uri = uri.gsub(/\W/, '_')[0..63]
+          uri_md5 = Chef::Digester.instance.generate_md5_checksum(StringIO.new(uri))
+          "#{scrubbed_uri}-#{uri_md5}.json"
+        end
+
+      end
+    end
+  end
+end
+
+
diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb
new file mode 100644
index 0000000..7f9e233
--- /dev/null
+++ b/lib/chef/provider/remote_file/content.rb
@@ -0,0 +1,75 @@
+#
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'rest_client'
+require 'uri'
+require 'tempfile'
+require 'chef/file_content_management/content_base'
+
+class Chef
+  class Provider
+    class RemoteFile
+      class Content < Chef::FileContentManagement::ContentBase
+
+        private
+
+        def file_for_provider
+          Chef::Log.debug("#{@new_resource} checking for changes")
+
+          if current_resource_matches_target_checksum?
+            Chef::Log.debug("#{@new_resource} checksum matches target checksum (#{@new_resource.checksum}) - not updating")
+          else
+            sources = @new_resource.source
+            raw_file = try_multiple_sources(sources)
+          end
+          raw_file
+        end
+
+        # Given an array of source uris, iterate through them until one does not fail
+        def try_multiple_sources(sources)
+          sources = sources.dup
+          source = sources.shift
+          begin
+            uri = URI.parse(source)
+            raw_file = grab_file_from_uri(uri)
+          rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPFatalError, Net::FTPError => e
+            Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e.to_s}")
+            if source = sources.shift
+              Chef::Log.info("#{@new_resource} trying to download from another mirror")
+              retry
+            else
+              raise e
+            end
+          end
+          raw_file
+        end
+
+        # Given a source uri, return a Tempfile, or a File that acts like a Tempfile (close! method)
+        def grab_file_from_uri(uri)
+          Chef::Provider::RemoteFile::Fetcher.for_resource(uri, @new_resource, @current_resource).fetch
+        end
+
+        def current_resource_matches_target_checksum?
+          @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{Regexp.escape(@new_resource.checksum)}/
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/remote_file/fetcher.rb b/lib/chef/provider/remote_file/fetcher.rb
new file mode 100644
index 0000000..c3a098e
--- /dev/null
+++ b/lib/chef/provider/remote_file/fetcher.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+class Chef
+  class Provider
+    class RemoteFile
+      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)
+          else
+            raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported"
+          end
+        end
+
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/provider/remote_file/ftp.rb b/lib/chef/provider/remote_file/ftp.rb
new file mode 100644
index 0000000..7f3fdbf
--- /dev/null
+++ b/lib/chef/provider/remote_file/ftp.rb
@@ -0,0 +1,184 @@
+#
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# 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 'uri'
+require 'tempfile'
+require 'net/ftp'
+require 'chef/provider/remote_file'
+require 'chef/file_content_management/tempfile'
+
+class Chef
+  class Provider
+    class RemoteFile
+      class FTP
+
+        attr_reader :uri
+        attr_reader :new_resource
+        attr_reader :current_resource
+
+        def initialize(uri, new_resource, current_resource)
+          @uri = uri
+          @new_resource = new_resource
+          @current_resource = current_resource
+          validate_typecode!
+          validate_path!
+        end
+
+        def hostname
+          @uri.host
+        end
+
+        def port
+          @uri.port
+        end
+
+        def use_passive_mode?
+          ! new_resource.ftp_active_mode
+        end
+
+        def typecode
+          uri.typecode
+        end
+
+        def user
+          if uri.userinfo
+            URI.unescape(uri.user)
+          else
+            'anonymous'
+          end
+        end
+
+        def pass
+          if uri.userinfo
+            URI.unescape(uri.password)
+          else
+            nil
+          end
+        end
+
+        def directories
+          parse_path if @directories.nil?
+          @directories
+        end
+
+        def filename
+          parse_path if @filename.nil?
+          @filename
+        end
+
+
+        def fetch
+          with_connection do
+            get
+          end
+        end
+
+        def ftp
+          @ftp ||= Net::FTP.new
+        end
+
+        private
+
+        def with_proxy_env
+          saved_socks_env = ENV['SOCKS_SERVER']
+          ENV['SOCKS_SERVER'] = proxy_uri(@uri).to_s
+          yield
+        ensure
+          ENV['SOCKS_SERVER'] = saved_socks_env
+        end
+
+        def with_connection
+          with_proxy_env do
+            connect
+            yield
+          end
+        ensure
+          disconnect
+        end
+
+        def validate_typecode!
+          # Only support ascii and binary types
+          if typecode and /\A[ai]\z/ !~ typecode
+            raise ArgumentError, "invalid typecode: #{typecode.inspect}"
+          end
+        end
+
+        def validate_path!
+          parse_path
+        end
+
+        def connect
+          # The access sequence is defined by RFC 1738
+          ftp.connect(hostname, port)
+          ftp.passive = use_passive_mode?
+          ftp.login(user, pass)
+          directories.each do |cwd|
+            ftp.voidcmd("CWD #{cwd}")
+          end
+        end
+
+        def disconnect
+          ftp.close
+        end
+
+        # Fetches using Net::FTP, returns a Tempfile with the content
+        def get
+          tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
+          if typecode
+            ftp.voidcmd("TYPE #{typecode.upcase}")
+          end
+          ftp.getbinaryfile(filename, tempfile.path)
+          tempfile.close if tempfile
+          tempfile
+        end
+
+        #adapted from buildr/lib/buildr/core/transports.rb via chef/rest/rest_client.rb
+        def proxy_uri(uri)
+          proxy = Chef::Config["ftp_proxy"]
+          proxy = URI.parse(proxy) if String === proxy
+          if Chef::Config["ftp_proxy_user"]
+            proxy.user = Chef::Config["ftp_proxy_user"]
+          end
+          if Chef::Config["ftp_proxy_pass"]
+            proxy.password = Chef::Config["ftp_proxy_pass"]
+          end
+          excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact
+          excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
+          return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
+        end
+
+        def parse_path
+          path = uri.path.sub(%r{\A/}, '%2F') # re-encode the beginning slash because uri library decodes it.
+          directories = path.split(%r{/}, -1)
+          directories.each {|d|
+            d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
+          }
+          unless filename = directories.pop
+            raise ArgumentError, "no filename: #{path.inspect}"
+          end
+          if filename.length == 0 || filename.end_with?( "/" )
+            raise ArgumentError, "no filename: #{path.inspect}"
+          end
+
+          @directories, @filename = directories, filename
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb
new file mode 100644
index 0000000..f17ab5a
--- /dev/null
+++ b/lib/chef/provider/remote_file/http.rb
@@ -0,0 +1,117 @@
+#
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# 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/http/simple'
+require 'chef/digester'
+require 'chef/provider/remote_file'
+require 'chef/provider/remote_file/cache_control_data'
+
+class Chef
+  class Provider
+    class RemoteFile
+
+      class HTTP
+
+        attr_reader :uri
+        attr_reader :new_resource
+        attr_reader :current_resource
+
+        # Parse the uri into instance variables
+        def initialize(uri, new_resource, current_resource)
+          @uri = uri
+          @new_resource = new_resource
+          @current_resource = current_resource
+        end
+
+        def headers
+          conditional_get_headers.merge(new_resource.headers)
+        end
+
+        def conditional_get_headers
+          cache_control_headers = {}
+          if last_modified = cache_control_data.mtime and want_mtime_cache_control?
+            cache_control_headers["if-modified-since"] = last_modified
+          end
+          if etag = cache_control_data.etag and want_etag_cache_control?
+            cache_control_headers["if-none-match"] = etag
+          end
+          Chef::Log.debug("Cache control headers: #{cache_control_headers.inspect}")
+          cache_control_headers
+        end
+
+        def fetch
+          http = Chef::HTTP::Simple.new(uri, http_client_opts)
+          tempfile = http.streaming_request(uri, headers)
+          if tempfile
+            update_cache_control_data(tempfile, http.last_response)
+            tempfile.close
+          end
+          tempfile
+        end
+
+        private
+
+        def update_cache_control_data(tempfile, response)
+          cache_control_data.checksum = Chef::Digester.checksum_for_file(tempfile.path)
+          cache_control_data.mtime = last_modified_time_from(response)
+          cache_control_data.etag = etag_from(response)
+          cache_control_data.save
+        end
+
+        def cache_control_data
+          @cache_control_data ||= CacheControlData.load_and_validate(uri, current_resource.checksum)
+        end
+
+        def want_mtime_cache_control?
+          new_resource.use_last_modified
+        end
+
+        def want_etag_cache_control?
+          new_resource.use_etag
+        end
+
+        def last_modified_time_from(response)
+          response['last_modified'] || response['date']
+        end
+
+        def etag_from(response)
+          response['etag']
+        end
+
+        def http_client_opts
+          opts={}
+          # CHEF-3140
+          # 1. If it's already compressed, trying to compress it more will
+          # probably be counter-productive.
+          # 2. Some servers are misconfigured so that you GET $URL/file.tgz but
+          # they respond with content type of tar and content encoding of gzip,
+          # which tricks Chef::REST into decompressing the response body. In this
+          # case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
+          # which is not what you wanted.
+          if uri.to_s =~ /gz$/
+            Chef::Log.debug("turning gzip compression off due to filename ending in gz")
+            opts[:disable_gzip] = true
+          end
+          opts
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb
new file mode 100644
index 0000000..b3b2301
--- /dev/null
+++ b/lib/chef/provider/remote_file/local_file.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# 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 'uri'
+require 'tempfile'
+require 'chef/provider/remote_file'
+
+class Chef
+  class Provider
+    class RemoteFile
+      class LocalFile
+
+        attr_reader :uri
+        attr_reader :new_resource
+
+        def initialize(uri, new_resource, current_resource)
+          @new_resource = new_resource
+          @uri = uri
+        end
+
+        # Fetches the file at uri, returning a Tempfile-like File handle
+        def fetch
+          tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
+          Chef::Log.debug("#{new_resource} staging #{uri.path} to #{tempfile.path}")
+          FileUtils.cp(uri.path, tempfile.path)
+          tempfile.close if tempfile
+          tempfile
+        end
+
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/resource_update.rb b/lib/chef/provider/resource_update.rb
new file mode 100644
index 0000000..54f2573
--- /dev/null
+++ b/lib/chef/provider/resource_update.rb
@@ -0,0 +1,55 @@
+
+class Chef
+  class Provider
+
+    # {
+    #    "run_id" : "1000",
+    #    "resource" : {
+    #         "type" : "file",
+    #         "name" : "/etc/passwd",
+    #         "start_time" : "2012-01-09T08:15:30-05:00",
+    #         "end_time" : "2012-01-09T08:15:30-05:00",
+    #         "status" : "modified",
+    #         "initial_state" : "exists",
+    #         "final_state" : "modified",
+    #         "before" : {
+    #              "group" : "root",
+    #              "owner" : "root",
+    #              "checksum" : "xyz"
+    #         },
+    #         "after" : {
+    #              "group" : "root",
+    #              "owner" : "root",
+    #              "checksum" : "abc"
+    #         },
+    #         "delta" : "escaped delta goes here"
+    #    },
+    #    "event_data" : ""
+    # }
+
+    class ResourceUpdate
+
+      attr_accessor :type
+      attr_accessor :name
+      attr_accessor :duration #ms
+      attr_accessor :status
+      attr_accessor :initial_state
+      attr_accessor :final_state
+      attr_accessor :initial_properties
+      attr_accessor :final_properties
+      attr_accessor :event_data # e.g., a diff.
+
+      def initial_state_from_resource(resource)
+        @initial_properties = resource.to_hash
+      end
+
+      def updated_state_from_resource(resource)
+        @final_properties = resource.to_hash
+      end
+
+    end
+  end
+end
+
+
+
diff --git a/lib/chef/provider/route.rb b/lib/chef/provider/route.rb
index 784df78..208a4f4 100644
--- a/lib/chef/provider/route.rb
+++ b/lib/chef/provider/route.rb
@@ -80,6 +80,10 @@ class Chef::Provider::Route < Chef::Provider
       end
     end
 
+    def whyrun_supported?
+      true
+    end
+
     def load_current_resource
       self.is_running = false
 
@@ -128,10 +132,10 @@ class Chef::Provider::Route < Chef::Provider
         Chef::Log.debug("#{@new_resource} route already active - nothing to do")
       else
         command = generate_command(:add)
-
-        run_command( :command => command )
-        Chef::Log.info("#{@new_resource} added")
-        @new_resource.updated_by_last_action(true)
+        converge_by ("run #{ command } to add route") do
+          run_command( :command => command )
+          Chef::Log.info("#{@new_resource} added")
+        end
       end
 
       #for now we always write the file (ugly but its what it is)
@@ -141,13 +145,16 @@ class Chef::Provider::Route < Chef::Provider
     def action_delete
       if is_running
         command = generate_command(:delete)
-
-        run_command( :command => command )
-        Chef::Log.info("#{@new_resource} removed")
-        @new_resource.updated_by_last_action(true)
+        converge_by ("run #{ command } to delete route ") do
+          run_command( :command => command )
+          Chef::Log.info("#{@new_resource} removed")
+        end
       else
         Chef::Log.debug("#{@new_resource} route does not exist - nothing to do")
       end
+
+      #for now we always write the file (ugly but its what it is)
+      generate_config
     end
 
     def generate_config
@@ -165,9 +172,10 @@ class Chef::Provider::Route < Chef::Provider
             end
 
             conf[dev] = String.new if conf[dev].nil?
-            if resource.action == :add
+            case @action
+            when :add
               conf[dev] << config_file_contents(:add, :target => resource.target, :netmask => resource.netmask, :gateway => resource.gateway)
-            else
+            when :delete
               # need to do this for the case when the last route on an int
               # is removed
               conf[dev] << config_file_contents(:delete)
@@ -175,10 +183,13 @@ class Chef::Provider::Route < Chef::Provider
           end
         end
         conf.each do |k, v|
-          network_file = ::File.new("/etc/sysconfig/network-scripts/route-#{k}", "w")
-          network_file.puts(conf[k])
-          Chef::Log.debug("#{@new_resource} writing route.#{k}\n#{conf[k]}")
-          network_file.close
+          network_file_name = "/etc/sysconfig/network-scripts/route-#{k}"
+          converge_by ("write route route.#{k}\n#{conf[k]} to #{ network_file_name }") do
+            network_file = ::File.new(network_file_name, "w")
+            network_file.puts(conf[k])
+            Chef::Log.debug("#{@new_resource} writing route.#{k}\n#{conf[k]}")
+            network_file.close
+          end
         end
       end
     end
diff --git a/lib/chef/provider/ruby_block.rb b/lib/chef/provider/ruby_block.rb
index 1e7fa72..b0d94a3 100644
--- a/lib/chef/provider/ruby_block.rb
+++ b/lib/chef/provider/ruby_block.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -20,15 +20,23 @@
 class Chef
   class Provider
     class RubyBlock < Chef::Provider
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         true
       end
 
-      def action_create
-        @new_resource.block.call
-				Chef::Log.info("#{@new_resource} called")
-        @new_resource.updated_by_last_action(true)
+      def action_run
+        converge_by("execute the ruby block #{@new_resource.name}") do
+          @new_resource.block.call
+          Chef::Log.info("#{@new_resource} called")
+        end
       end
+
+      alias :action_create :action_run
+
     end
   end
 end
diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb
index 5c0d52e..4aacf4f 100644
--- a/lib/chef/provider/script.rb
+++ b/lib/chef/provider/script.rb
@@ -23,16 +23,23 @@ class Chef
   class Provider
     class Script < Chef::Provider::Execute
 
+      def initialize(new_resource, run_context)
+        super
+        @code = @new_resource.code
+      end
+
       def action_run
-        script_file.puts(@new_resource.code)
+        script_file.puts(@code)
         script_file.close
 
         set_owner_and_group
 
-        @new_resource.command("\"#{@new_resource.interpreter}\" #{@new_resource.flags} \"#{script_file.path}\"")
+        @new_resource.command("\"#{interpreter}\" #{flags} \"#{script_file.path}\"")
         super
-      ensure
-        unlink_script_file
+        converge_by(nil) do
+          # ensure script is unlinked at end of converge!
+          unlink_script_file
+        end
       end
 
       def set_owner_and_group
@@ -50,6 +57,13 @@ class Chef
         @script_file && @script_file.close!
       end
 
+      def interpreter
+        @new_resource.interpreter
+      end
+
+      def flags
+        @new_resource.flags
+      end
     end
   end
 end
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 678159e..968f9bf 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -30,67 +30,103 @@ class Chef
         @enabled = nil
       end
 
+      def whyrun_supported?
+        true
+      end
+
+     def load_new_resource_state
+        # If the user didn't specify a change in enabled state,
+        # it will be the same as the old resource
+       if ( @new_resource.enabled.nil? )
+         @new_resource.enabled(@current_resource.enabled)
+       end
+       if ( @new_resource.running.nil? )
+         @new_resource.running(@current_resource.running)
+       end
+     end
+
+      def shared_resource_requirements
+      end
+
+      def define_resource_requirements
+       requirements.assert(:reload) do |a|
+         a.assertion { @new_resource.supports[:reload] || @new_resource.reload_command }
+         a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
+         # if a service is not declared to support reload, that won't
+         # typically change during the course of a run - so no whyrun
+         # alternative here.
+       end
+      end
+
       def action_enable
         if @current_resource.enabled
           Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
         else
-          if enable_service
-            @new_resource.updated_by_last_action(true)
+          converge_by("enable service #{@new_resource}") do
+            enable_service
             Chef::Log.info("#{@new_resource} enabled")
           end
         end
+        load_new_resource_state
+        @new_resource.enabled(true)
       end
 
       def action_disable
         if @current_resource.enabled
-          if disable_service
-            @new_resource.updated_by_last_action(true)
+          converge_by("disable service #{@new_resource}") do
+            disable_service
             Chef::Log.info("#{@new_resource} disabled")
           end
         else
           Chef::Log.debug("#{@new_resource} already disabled - nothing to do")
         end
+        load_new_resource_state
+        @new_resource.enabled(false)
       end
 
       def action_start
         unless @current_resource.running
-          if start_service
-            @new_resource.updated_by_last_action(true)
+          converge_by("start service #{@new_resource}") do
+            start_service
             Chef::Log.info("#{@new_resource} started")
           end
         else
           Chef::Log.debug("#{@new_resource} already running - nothing to do")
-        end 
+        end
+        load_new_resource_state
+        @new_resource.running(true)
       end
 
       def action_stop
         if @current_resource.running
-          if stop_service
-            @new_resource.updated_by_last_action(true)
+          converge_by("stop service #{@new_resource}") do
+            stop_service
             Chef::Log.info("#{@new_resource} stopped")
           end
         else
           Chef::Log.debug("#{@new_resource} already stopped - nothing to do")
-        end 
+        end
+        load_new_resource_state
+        @new_resource.running(false)
       end
-      
+
       def action_restart
-        if restart_service
-          @new_resource.updated_by_last_action(true)
+        converge_by("restart service #{@new_resource}") do
+          restart_service
           Chef::Log.info("#{@new_resource} restarted")
         end
+        load_new_resource_state
+        @new_resource.running(true)
       end
 
       def action_reload
-        unless (@new_resource.supports[:reload] || @new_resource.reload_command)
-          raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
-        end
         if @current_resource.running
-          if reload_service
-            @new_resource.updated_by_last_action(true)
+          converge_by("reload service #{@new_resource}") do
+            reload_service
             Chef::Log.info("#{@new_resource} reloaded")
           end
         end
+        load_new_resource_state
       end
 
       def enable_service
@@ -107,8 +143,8 @@ class Chef
 
       def stop_service
         raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :stop"
-      end 
-      
+      end
+
       def restart_service
         raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :restart"
       end
@@ -116,7 +152,22 @@ class Chef
       def reload_service
         raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :restart"
       end
- 
+
+      protected
+
+      def default_init_command
+        if @new_resource.init_command
+          @new_resource.init_command
+        elsif self.instance_variable_defined?(:@init_command)
+          @init_command
+        end
+      end
+
+      def custom_command_for_action?(action)
+        method_name = "#{action}_command".to_sym
+        @new_resource.respond_to?(method_name) &&
+          !!@new_resource.send(method_name)
+      end
     end
   end
 end
diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb
index 4f8e3e0..8c8216c 100644
--- a/lib/chef/provider/service/arch.rb
+++ b/lib/chef/provider/service/arch.rb
@@ -27,14 +27,11 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
   end
 
   def load_current_resource
-
-    raise Chef::Exceptions::Service unless ::File.exists?("/etc/rc.conf")
-    raise Chef::Exceptions::Service unless ::File.read("/etc/rc.conf").match(/DAEMONS=\((.*)\)/m)
-
+    raise Chef::Exceptions::Service, "Could not find /etc/rc.conf"  unless ::File.exists?("/etc/rc.conf")
+    raise Chef::Exceptions::Service, "No DAEMONS found in /etc/rc.conf"  unless ::File.read("/etc/rc.conf").match(/DAEMONS=\((.*)\)/m)
     super
 
     @current_resource.enabled(daemons.include?(@current_resource.service_name))
-
     @current_resource
   end
 
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 9cdf973..d788d58 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -29,28 +29,52 @@ class Chef
 
         def load_current_resource
           super
-
+          @priority_success = true
+          @rcd_status = nil
           @current_resource.priority(get_priority)
           @current_resource.enabled(service_currently_enabled?(@current_resource.priority))
           @current_resource
         end
 
-        def assert_update_rcd_available
-          unless ::File.exists? "/usr/sbin/update-rc.d"
-            raise Chef::Exceptions::Service, "/usr/sbin/update-rc.d does not exist!"
+        def define_resource_requirements
+          # do not call super here, inherit only shared_requirements
+          shared_resource_requirements
+          requirements.assert(:all_actions) do |a|
+            update_rcd = "/usr/sbin/update-rc.d"
+            a.assertion { ::File.exists? update_rcd }
+            a.failure_message Chef::Exceptions::Service, "#{update_rcd} does not exist!"
+            # no whyrun recovery - this is a base system component of debian
+            # distros and must be present
+          end
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @priority_success }
+            a.failure_message  Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} failed - #{@rcd_status.inspect}"
+            # This can happen if the service is not yet installed,so we'll fake it.
+            a.whyrun ["Unable to determine priority of service, assuming service would have been correctly installed earlier in the run.",
+                      "Assigning temporary priorities to continue.",
+                      "If this service is not properly installed prior to this point, this will fail."] do
+              temp_priorities = {"6"=>[:stop, "20"],
+                "0"=>[:stop, "20"],
+                "1"=>[:stop, "20"],
+                "2"=>[:start, "20"],
+                "3"=>[:start, "20"],
+                "4"=>[:start, "20"],
+                "5"=>[:start, "20"]}
+              @current_resource.priority(temp_priorities)
+            end
           end
         end
 
         def get_priority
-          assert_update_rcd_available
           priority = {}
 
-          status = popen4("/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} remove") do |pid, stdin, stdout, stderr|
+          @rcd_status = popen4("/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} remove") do |pid, stdin, stdout, stderr|
 
             [stdout, stderr].each do |iop|
               iop.each_line do |line|
                 if UPDATE_RC_D_PRIORITIES =~ line
-                  # priority[runlevel] = [ S|K, priority ] 
+                  # priority[runlevel] = [ S|K, priority ]
                   # S = Start, K = Kill
                   # debian runlevels: 0 Halt, 1 Singleuser, 2 Multiuser, 3-5 == 2, 6 Reboot
                   priority[$1] = [($2 == "S" ? :start : :stop), $3]
@@ -62,18 +86,22 @@ class Chef
             end
           end
 
-          unless status.exitstatus == 0
-            raise Chef::Exceptions::Service, "/usr/sbin/update-rc.d -n -f #{@current_resource.service_name} failed - #{status.inspect}"
+          # Reduce existing priority back to an integer if appropriate, picking
+          # runlevel 2 as a baseline
+          if priority[2] && [2..5].all? { |runlevel| priority[runlevel] == priority[2] }
+            priority = priority[2].last
+          end
+
+          unless @rcd_status.exitstatus == 0
+            @priority_success = false
           end
           priority
         end
 
         def service_currently_enabled?(priority)
           enabled = false
-
           priority.each { |runlevel, arguments|
             Chef::Log.debug("#{@new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}")
-            
             # if we are in a update-rc.d default startup runlevel && we start in this runlevel
             if (2..5).include?(runlevel.to_i) && arguments[0] == :start
               enabled = true
@@ -83,14 +111,28 @@ class Chef
           enabled
         end
 
-        def enable_service()
+        # Override method from parent to ensure priority is up-to-date
+        def action_enable
+          if @current_resource.enabled && @current_resource.priority == @new_resource.priority
+            Chef::Log.debug("#{@new_resource} already enabled - nothing to do")
+          else
+            converge_by("enable service #{@new_resource}") do
+              enable_service
+              Chef::Log.info("#{@new_resource} enabled")
+            end
+          end
+          load_new_resource_state
+          @new_resource.enabled(true)
+        end
+
+        def enable_service
           if @new_resource.priority.is_a? Integer
             run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
             run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults #{@new_resource.priority} #{100 - @new_resource.priority}")
           elsif @new_resource.priority.is_a? Hash
-            # we call the same command regardless of we're enabling or disabling  
+            # we call the same command regardless of we're enabling or disabling
             # users passing a Hash are responsible for setting their own start priorities
-            set_priority()
+            set_priority
           else # No priority, go with update-rc.d defaults
             run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
             run_command(:command => "/usr/sbin/update-rc.d #{@new_resource.service_name} defaults")
@@ -98,23 +140,23 @@ class Chef
 
         end
 
-        def disable_service()
+        def disable_service
           if @new_resource.priority.is_a? Integer
             # Stop processes in reverse order of start using '100 - start_priority'
             run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
             run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .")
           elsif @new_resource.priority.is_a? Hash
-            # we call the same command regardless of we're enabling or disabling  
+            # we call the same command regardless of we're enabling or disabling
             # users passing a Hash are responsible for setting their own stop priorities
-            set_priority()
-          else 
+            set_priority
+          else
             # no priority, using '100 - 20 (update-rc.d default)' to stop in reverse order of start
             run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} remove")
             run_command(:command => "/usr/sbin/update-rc.d -f #{@new_resource.service_name} stop 80 2 3 4 5 .")
           end
         end
 
-        def set_priority()
+        def set_priority
           args = ""
           @new_resource.priority.each do |level, o|
             action = o[0]
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index aaa73ef..cb0f6b0 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -30,23 +30,27 @@ class Chef
         def load_current_resource
           @current_resource = Chef::Resource::Service.new(@new_resource.name)
           @current_resource.service_name(@new_resource.service_name)
-
+          @rcd_script_found = true
+          @enabled_state_found = false
           # Determine if we're talking about /etc/rc.d or /usr/local/etc/rc.d
           if ::File.exists?("/etc/rc.d/#{current_resource.service_name}")
             @init_command = "/etc/rc.d/#{current_resource.service_name}"
           elsif ::File.exists?("/usr/local/etc/rc.d/#{current_resource.service_name}")
             @init_command = "/usr/local/etc/rc.d/#{current_resource.service_name}"
           else
-            raise Chef::Exceptions::Service, "#{@new_resource}: unable to locate the rc.d script"
+            @rcd_script_found = false
+            return
           end
           Chef::Log.debug("#{@current_resource} found at #{@init_command}")
-
           determine_current_status!
-
-          if ::File.exists?("/etc/rc.conf")
+          # Default to disabled if the service doesn't currently exist
+          # at all
+          var_name = service_enable_variable_name
+          if ::File.exists?("/etc/rc.conf") && var_name
             read_rc_conf.each do |line|
               case line
-              when /#{Regexp.escape(service_enable_variable_name)}="(\w+)"/
+              when /#{Regexp.escape(var_name)}="(\w+)"/
+                @enabled_state_found = true
                 if $1 =~ /[Yy][Ee][Ss]/
                   @current_resource.enabled true
                 elsif $1 =~ /[Nn][Oo][Nn]?[Oo]?[Nn]?[Ee]?/
@@ -57,11 +61,33 @@ class Chef
           end
           unless @current_resource.enabled
             Chef::Log.debug("#{@new_resource.name} enable/disable state unknown")
+            @current_resource.enabled false
           end
 
           @current_resource
         end
 
+        def define_resource_requirements
+          shared_resource_requirements
+          requirements.assert(:start, :enable, :reload, :restart) do |a|
+            a.assertion { @rcd_script_found }
+            a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the rc.d script"
+          end
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @enabled_state_found }
+            # for consistentcy with original behavior, this will not fail in non-whyrun mode;
+            # rather it will silently set enabled state=>false
+            a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run.  Assuming disabled."
+          end
+
+          requirements.assert(:start, :enable, :reload, :restart) do |a|
+            a.assertion { @rcd_script_found && service_enable_variable_name != nil }
+            a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{@init_command} and rcvar"
+            # No recovery in whyrun mode - the init file is present but not correct.
+          end
+        end
+
         def start_service
           if @new_resource.start_command
             super
@@ -80,6 +106,7 @@ class Chef
 
         def restart_service
           if @new_resource.restart_command
+
             super
           elsif @new_resource.supports[:restart]
             shell_out!("#{@init_command} fastrestart")
@@ -100,21 +127,29 @@ class Chef
           end
         end
 
-
         # The variable name used in /etc/rc.conf for enabling this service
         def service_enable_variable_name
           # Look for name="foo" in the shell script @init_command. Use this for determining the variable name in /etc/rc.conf
           # corresponding to this service
           # For example: to enable the service mysql-server with the init command /usr/local/etc/rc.d/mysql-server, you need
-          # to set mysql_enable="YES" in /etc/rc.conf
-          makefile = ::File.open(@init_command)
-          makefile.each do |line|
-            case line
-            when /^name="?(\w+)"?/
-              return $1 + "_enable"
+          # to set mysql_enable="YES" in /etc/rc.conf$
+          if @rcd_script_found
+            ::File.open(@init_command) do |rcscript|
+              rcscript.each_line do |line|
+                if line =~ /^name="?(\w+)"?/
+                  return $1 + "_enable"
+                end
+              end
             end
+            # 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]
+            return sn
           end
-          raise Chef::Exceptions::Service, "Could not find name=\"service\" line in #{@init_command}"
+          # Fallback allows us to keep running in whyrun mode when
+          # the script does not exist.
+          @new_resource.service_name
         end
 
         def set_service_enable(value)
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 2a72f75..ba4edc5 100644
--- a/lib/chef/provider/service/gentoo.rb
+++ b/lib/chef/provider/service/gentoo.rb
@@ -25,13 +25,12 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
 
     @new_resource.supports[:status] = true
     @new_resource.supports[:restart] = true
-
+    @found_script = false
     super
-    
-    raise Chef::Exceptions::Service unless ::File.exists?("/sbin/rc-update")
-    
+
     @current_resource.enabled(
       Dir.glob("/etc/runlevels/**/#{@current_resource.service_name}").any? do |file|
+        @found_script = true
         exists = ::File.exists? file
         readable = ::File.readable? file
         Chef::Log.debug "#{@new_resource} exists: #{exists}, readable: #{readable}"
@@ -42,11 +41,26 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
 
     @current_resource
   end
-  
+
+  def define_resource_requirements
+    requirements.assert(:all_actions) do |a|
+      a.assertion { ::File.exists?("/sbin/rc-update") }
+      a.failure_message Chef::Exceptions::Service, "/sbin/rc-update does not exist"
+      # no whyrun recovery -t his is a core component whose presence is
+      # unlikely to be affected by what we do in the course of a chef run
+    end
+
+    requirements.assert(:all_actions) do |a|
+      a.assertion { @found_script }
+      # No failure, just informational output from whyrun
+      a.whyrun "Could not find service #{@new_resource.service_name} under any runlevel"
+    end
+  end
+
   def enable_service()
     run_command(:command => "/sbin/rc-update add #{@new_resource.service_name} default")
   end
-  
+
   def disable_service()
     run_command(:command => "/sbin/rc-update del #{@new_resource.service_name} default")
   end
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index 494527b..63ba8fa 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -33,11 +33,26 @@ class Chef
           @init_command = "/etc/init.d/#{@new_resource.service_name}"
         end
 
+        def define_resource_requirements
+          # do not call super here, inherit only shared_requirements
+          shared_resource_requirements
+          requirements.assert(:start, :stop, :restart, :reload) do |a|
+            a.assertion do
+              custom_command_for_action?(action) || ::File.exist?(default_init_command)
+            end
+            a.failure_message(Chef::Exceptions::Service, "#{default_init_command} does not exist!")
+            a.whyrun("Init script '#{default_init_command}' doesn't exist, assuming a prior action would have created it.") do
+              # blindly assume that the service exists but is stopped in why run mode:
+              @status_load_success = false
+            end
+          end
+        end
+
         def start_service
           if @new_resource.start_command
             super
           else
-            shell_out!("#{@init_command} start")
+            shell_out!("#{default_init_command} start")
           end
         end
 
@@ -45,7 +60,7 @@ class Chef
           if @new_resource.stop_command
             super
           else
-            shell_out!("#{@init_command} stop")
+            shell_out!("#{default_init_command} stop")
           end
         end
 
@@ -53,7 +68,7 @@ class Chef
           if @new_resource.restart_command
             super
           elsif @new_resource.supports[:restart]
-            shell_out!("#{@init_command} restart")
+            shell_out!("#{default_init_command} restart")
           else
             stop_service
             sleep 1
@@ -65,7 +80,7 @@ class Chef
           if @new_resource.reload_command
             super
           elsif @new_resource.supports[:reload]
-            shell_out!("#{@init_command} reload")
+            shell_out!("#{default_init_command} reload")
           end
         end
       end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 3215237..cb9c28e 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -32,9 +32,9 @@ class Chef
           if Dir.glob("/etc/rc**/S*#{@current_resource.service_name}").empty?
             @current_resource.enabled false
           else
-            @current_resource.enabled true 
+            @current_resource.enabled true
           end
-  
+
           @current_resource
         end
 
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 69a17bb..ee2719d 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -24,7 +24,7 @@ class Chef
   class Provider
     class Service
       class Invokercd < Chef::Provider::Service::Init
-        
+
         def initialize(new_resource, run_context)
           super
           @init_command = "/usr/sbin/invoke-rc.d #{@new_resource.service_name}"
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index a22b2f0..4f2de2c 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -24,27 +24,57 @@ class Chef
       class Macosx < Chef::Provider::Service::Simple
         include Chef::Mixin::ShellOut
 
-        PLIST_DIRS = %w{~/Library/LaunchAgents
-                         /Library/LaunchAgents
+        def self.gather_plist_dirs
+          locations = %w{/Library/LaunchAgents
                          /Library/LaunchDaemons
                          /System/Library/LaunchAgents
                          /System/Library/LaunchDaemons }
 
+          locations << "#{ENV['HOME']}/Library/LaunchAgents" if ENV['HOME']
+          locations
+        end
+
+        PLIST_DIRS = gather_plist_dirs
+
         def load_current_resource
           @current_resource = Chef::Resource::Service.new(@new_resource.name)
           @current_resource.service_name(@new_resource.service_name)
+          @plist_size = 0
           @plist = find_service_plist
           set_service_status
 
           @current_resource
         end
 
-        def enable_service
-          raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
-        end
+        def define_resource_requirements
+          #super
+          requirements.assert(:enable) do |a|
+            a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
+          end
+
+          requirements.assert(:disable) do |a|
+            a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
+          end
+
+          requirements.assert(:reload) do |a|
+            a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
+          end
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @plist_size < 2 }
+            a.failure_message Chef::Exceptions::Service, "Several plist files match service name. Please use full service name."
+          end
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @plist_size > 0 }
+            # No failrue here in original code - so we also will not
+            # fail. Instead warn that the service is potentially missing
+            a.whyrun "Assuming that the service would have been previously installed and is currently disabled." do
+              @current_resource.enabled(false)
+              @current_resource.running(false)
+            end
+          end
 
-        def disable_service
-          raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
         end
 
         def start_service
@@ -81,11 +111,10 @@ class Chef
           end
         end
 
-        def reload_service
-          raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
-        end
 
         def set_service_status
+          return if @plist == nil
+
           @current_resource.enabled(!@plist.nil?)
 
           if @current_resource.enabled
@@ -111,12 +140,8 @@ class Chef
             entries = Dir.glob("#{::File.expand_path(dir)}/*#{@current_resource.service_name}*.plist")
             entries.any? ? results << entries : results
           end
-
           plists.flatten!
-          if plists.size > 1
-            raise Chef::Exceptions::Service, "Several plist files match service name. Please use full service name."
-          end
-
+          @plist_size = plists.size
           plists.first
         end
       end
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 4423cb3..06be9f3 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -25,25 +25,43 @@ class Chef
     class Service
       class Redhat < Chef::Provider::Service::Init
         include Chef::Mixin::ShellOut
-        
+
         CHKCONFIG_ON = /\d:on/
-        
+        CHKCONFIG_MISSING = /No such/
+
         def initialize(new_resource, run_context)
           super
            @init_command = "/sbin/service #{@new_resource.service_name}"
            @new_resource.supports[:status] = true
-         end
-        
-        def load_current_resource
-          unless ::File.exists? "/sbin/chkconfig"
-            raise Chef::Exceptions::Service, "/sbin/chkconfig does not exist!"
+           @service_missing = false
+        end
+
+        def define_resource_requirements
+          shared_resource_requirements
+
+          requirements.assert(:all_actions) do |a|
+            chkconfig_file = "/sbin/chkconfig"
+            a.assertion { ::File.exists? chkconfig_file  }
+            a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!"
+          end
+
+          requirements.assert(:start, :enable, :reload, :restart) do |a|
+            a.assertion { !@service_missing }
+            a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!"
+            a.whyrun "Assuming service would be disabled. The init script is not presently installed."
           end
-          
+        end
+
+        def load_current_resource
           super
-          
-          chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1])
-          @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
-          @current_resource        
+
+          if ::File.exists?("/sbin/chkconfig")
+            chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1])
+            @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+            @service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING)
+          end
+
+          @current_resource
         end
 
         def enable_service()
@@ -53,7 +71,6 @@ class Chef
         def disable_service()
           shell_out! "/sbin/chkconfig #{@new_resource.service_name} off"
         end
-        
       end
     end
   end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index b51eeab..288b5f5 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -31,25 +31,65 @@ class Chef
           @current_resource = Chef::Resource::Service.new(@new_resource.name)
           @current_resource.service_name(@new_resource.service_name)
 
+          @status_load_success = true
+          @ps_command_failed = false
+
           determine_current_status!
 
           @current_resource
         end
 
-        def start_service
-          if @new_resource.start_command
-            shell_out!(@new_resource.start_command)
-          else
-            raise Chef::Exceptions::Service, "#{self.to_s} requires that start_command to be set"
+        def whyrun_supported?
+          true
+        end
+
+        def shared_resource_requirements
+          super
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @status_load_success }
+            a.whyrun ["Service status not available. Assuming a prior action would have installed the service.", "Assuming status of not running."]
           end
         end
 
-        def stop_service
-          if @new_resource.stop_command
-            shell_out!(@new_resource.stop_command)
-          else
-            raise Chef::Exceptions::Service, "#{self.to_s} requires that stop_command to be set"
+        def define_resource_requirements
+          # FIXME? need reload from service.rb
+          shared_resource_requirements
+          requirements.assert(:start) do |a|
+            a.assertion { @new_resource.start_command }
+            a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that start_command be set"
+          end
+          requirements.assert(:stop) do |a|
+            a.assertion { @new_resource.stop_command }
+            a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that stop_command be set"
+          end
+
+          requirements.assert(:restart) do |a|
+            a.assertion { @new_resource.restart_command  || ( @new_resource.start_command && @new_resource.stop_command ) }
+            a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires a restart_command or both start_command and stop_command be set in order to perform a restart"
+          end
+
+          requirements.assert(:reload) do |a|
+            a.assertion { @new_resource.reload_command }
+            a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} requires a reload_command be set in order to perform a reload"
+          end
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion { @new_resource.status_command or @new_resource.supports[:status] or
+              (!ps_cmd.nil? and !ps_cmd.empty?) }
+            a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
           end
+          requirements.assert(:all_actions) do |a|
+            a.assertion { !@ps_command_failed }
+            a.failure_message Chef::Exceptions::Service, "Command #{ps_cmd} failed to execute, cannot determine service current status"
+          end
+        end
+
+        def start_service
+          shell_out!(@new_resource.start_command)
+        end
+
+        def stop_service
+          shell_out!(@new_resource.stop_command)
         end
 
         def restart_service
@@ -63,11 +103,7 @@ class Chef
         end
 
         def reload_service
-          if @new_resource.reload_command
-            shell_out!(@new_resource.reload_command)
-          else
-            raise Chef::Exceptions::Service, "#{self.to_s} requires that reload_command to be set"
-          end
+          shell_out!(@new_resource.reload_command)
         end
 
       protected
@@ -80,29 +116,32 @@ class Chef
                 @current_resource.running true
                 Chef::Log.debug("#{@new_resource} is running")
               end
-            rescue Mixlib::ShellOut::ShellCommandFailed
+            rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
+            # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+            # Temporarily catching different types of exceptions here until we get Shellout fixed.
+            # TODO: Remove the line before one we get the ShellOut fix.
+              @status_load_success = false
               @current_resource.running false
               nil
             end
 
           elsif @new_resource.supports[:status]
             Chef::Log.debug("#{@new_resource} supports status, running")
-
             begin
-              if shell_out("#{@init_command} status").exitstatus == 0
+              if shell_out("#{default_init_command} status").exitstatus == 0
                 @current_resource.running true
                 Chef::Log.debug("#{@new_resource} is running")
               end
-            rescue Mixlib::ShellOut::ShellCommandFailed
+            # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+            # Temporarily catching different types of exceptions here until we get Shellout fixed.
+            # TODO: Remove the line before one we get the ShellOut fix.
+            rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
+              @status_load_success = false
               @current_resource.running false
               nil
             end
-          elsif
+          else
             Chef::Log.debug "#{@new_resource} falling back to process table inspection"
-            if ps_cmd.nil? or ps_cmd.empty?
-              raise Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this nodes 'command.ps' attribute"
-            end
-
             r = Regexp.new(@new_resource.pattern)
             Chef::Log.debug "#{@new_resource} attempting to match '#{@new_resource.pattern}' (#{r.inspect}) against process list"
             begin
@@ -112,10 +151,14 @@ class Chef
                   break
                 end
               end
+
               @current_resource.running false unless @current_resource.running
               Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
-            rescue Mixlib::ShellOut::ShellCommandFailed
-              raise Chef::Exceptions::Service, "Command #{ps_cmd} failed"
+            # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+            # Temporarily catching different types of exceptions here until we get Shellout fixed.
+            # TODO: Remove the line before one we get the ShellOut fix.
+            rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
+              @ps_command_failed = true
             end
           end
         end
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
index ef0ae10..4bdb6fb 100644
--- a/lib/chef/provider/service/solaris.rb
+++ b/lib/chef/provider/service/solaris.rb
@@ -16,6 +16,7 @@
 # limitations under the License.
 #
 
+require 'chef/mixin/shell_out'
 require 'chef/provider/service'
 require 'chef/mixin/command'
 
@@ -23,6 +24,7 @@ class Chef
   class Provider
     class Service
       class Solaris < Chef::Provider::Service
+        include Chef::Mixin::ShellOut
 
         def initialize(new_resource, run_context=nil)
           super
@@ -30,6 +32,7 @@ class Chef
           @status_command = "/bin/svcs -l"
         end
 
+
         def load_current_resource
           @current_resource = Chef::Resource::Service.new(@new_resource.name)
           @current_resource.service_name(@new_resource.service_name)
@@ -41,23 +44,22 @@ class Chef
         end
 
         def enable_service
-          run_command(:command => "#{@init_command} enable #{@new_resource.service_name}")
-          return service_status.enabled
+          shell_out!("#{default_init_command} enable -s #{@new_resource.service_name}")
         end
 
         def disable_service
-          run_command(:command => "#{@init_command} disable #{@new_resource.service_name}")
-          return service_status.enabled
+          shell_out!("#{default_init_command} disable -s #{@new_resource.service_name}")
         end
 
         alias_method :stop_service, :disable_service
         alias_method :start_service, :enable_service
 
         def reload_service
-          run_command(:command => "#{@init_command} refresh #{@new_resource.service_name}")
+          shell_out!("#{default_init_command} refresh #{@new_resource.service_name}")
         end
 
         def restart_service
+          ## svcadm restart doesn't supports sync(-s) option
           disable_service
           return enable_service
         end
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 749cb3b..89077c5 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -24,6 +24,7 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
   def load_current_resource
     @current_resource = Chef::Resource::Service.new(@new_resource.name)
     @current_resource.service_name(@new_resource.service_name)
+    @status_check_success = true
 
     if @new_resource.status_command
       Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
@@ -33,7 +34,9 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
           @current_resource.running(true)
         end
       rescue Chef::Exceptions::Exec
+        @status_check_success = false
         @current_resource.running(false)
+        @current_resource.enabled(false)
         nil
       end
     else
@@ -44,6 +47,16 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
     @current_resource
   end
 
+  def define_resource_requirements
+    shared_resource_requirements
+    requirements.assert(:all_actions) do |a|
+      a.assertion { @status_check_success }
+      # We won't stop in any case, but in whyrun warn and tell what we're doing.
+      a.whyrun ["Failed to determine status of #{@new_resource}, using command #{@new_resource.status_command}.",
+        "Assuming service would have been installed and is disabled"]
+    end
+  end
+
   def start_service
     if @current_resource.running
       Chef::Log.debug("#{@new_resource} already running, not starting")
@@ -86,11 +99,11 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
 
   def enable_service
     run_command_with_systems_locale(:command => "/bin/systemctl enable #{@new_resource.service_name}")
-  end 
+  end
 
   def disable_service
     run_command_with_systems_locale(:command => "/bin/systemctl disable #{@new_resource.service_name}")
-  end 
+  end
 
   def is_active?
     run_command_with_systems_locale({:command => "/bin/systemctl is-active #{@new_resource.service_name}", :ignore_failure => true}) == 0
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 7da3dd3..67ca649 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -26,7 +26,7 @@ class Chef
     class Service
       class Upstart < Chef::Provider::Service::Simple
         UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
-       
+
         # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in.
         # In chef, when we ask a service to start, we expect it to have started before performing the next step
         # since we have top down dependencies. Which is to say we may follow witha resource next that requires
@@ -40,17 +40,17 @@ class Chef
           # TODO: re-evaluate if this is needed after integrating cookbook fix
           raise ArgumentError, "run_context cannot be nil" unless run_context
           super
-          
+
           run_context.node
-          
+
           @job = @new_resource.service_name
-          
+
           if @new_resource.parameters
             @new_resource.parameters.each do |key, value|
               @job << " #{key}=#{value}"
             end
           end
-          
+
           platform, version = Chef::Platform.find_platform_and_version(run_context.node)
           if platform == "ubuntu" && (8.04..9.04).include?(version.to_f)
             @upstart_job_dir = "/etc/event.d"
@@ -59,6 +59,30 @@ class Chef
             @upstart_job_dir = "/etc/init"
             @upstart_conf_suffix = ".conf"
           end
+
+          @command_success = true # new_resource.status_command= false, means upstart used
+          @config_file_found = true
+          @upstart_command_success = true
+        end
+
+        def define_resource_requirements
+          # Do not call super, only call shared requirements
+          shared_resource_requirements
+          requirements.assert(:all_actions) do |a|
+            if !@command_success
+              whyrun_msg = @new_resource.status_command ? "Provided status command #{@new_resource.status_command} failed." :
+                "Could not determine upstart state for service"
+            end
+            a.assertion { @command_success }
+            # no failure here, just document the assumptions made.
+            a.whyrun "#{whyrun_msg} Assuming service installed and not running."
+          end
+
+          requirements.assert(:all_actions) do |a|
+            a.assertion  { @config_file_found }
+            # no failure here, just document the assumptions made.
+            a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled."
+          end
         end
 
         def load_current_resource
@@ -76,6 +100,7 @@ class Chef
                 @current_resource.running true
               end
             rescue Chef::Exceptions::Exec
+              @command_success = false
               @current_resource.running false
               nil
             end
@@ -87,11 +112,11 @@ class Chef
                 @current_resource.running false
               end
             rescue Chef::Exceptions::Exec
+              @command_success = false
               @current_resource.running false
               nil
             end
           end
-
           # Get enabled/disabled state by reading job configuration file
           if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
             Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
@@ -110,6 +135,7 @@ class Chef
               end
             end
           else
+            @config_file_found = false
             Chef::Log.debug("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
             @current_resource.enabled false
           end
@@ -150,7 +176,7 @@ class Chef
             super
           # Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start.
           # Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883
-          else @new_resource.supports[:restart]
+          else
             if @current_resource.running
               run_command_with_systems_locale(:command => "/sbin/restart #{@job}")
             else
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index 57d3d75..ba51e53 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -33,6 +33,10 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
   AUTO_START = 'auto start'
   DISABLED = 'disabled'
 
+  def whyrun_supported?
+    false
+  end
+
   def load_current_resource
     @current_resource = Chef::Resource::Service.new(@new_resource.name)
     @current_resource.service_name(@new_resource.service_name)
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
index 08ad716..6ceb3e5 100644
--- a/lib/chef/provider/subversion.rb
+++ b/lib/chef/provider/subversion.rb
@@ -17,6 +17,8 @@
 #
 
 
+#TODO subversion and git should both extend from a base SCM provider.
+
 require 'chef/log'
 require 'chef/provider'
 require 'chef/mixin/command'
@@ -30,6 +32,10 @@ class Chef
 
       include Chef::Mixin::Command
 
+      def whyrun_supported?
+        true
+      end
+
       def load_current_resource
         @current_resource = Chef::Resource::Subversion.new(@new_resource.name)
 
@@ -40,30 +46,40 @@ class Chef
         end
       end
 
+      def define_resource_requirements
+        requirements.assert(:all_actions)  do |a|
+          # Make sure the parent dir exists, or else fail.
+          # for why run, print a message explaining the potential error.
+          parent_directory = ::File.dirname(@new_resource.destination)
+          a.assertion { ::File.directory?(parent_directory) }
+          a.failure_message(Chef::Exceptions::MissingParentDirectory,
+            "Cannot clone #{@new_resource} to #{@new_resource.destination}, the enclosing directory #{parent_directory} does not exist")
+          a.whyrun("Directory #{parent_directory} does not exist, assuming it would have been created")
+        end
+      end
+
       def action_checkout
-        assert_target_directory_valid!
-        if target_dir_non_existant_or_empty?
-          run_command(run_options(:command => checkout_command))
-          @new_resource.updated_by_last_action(true)
+        if target_dir_non_existent_or_empty?
+          converge_by("perform checkout of #{@new_resource.repository} into #{@new_resource.destination}") do
+            run_command(run_options(:command => checkout_command))
+          end
         else
           Chef::Log.debug "#{@new_resource} checkout destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
         end
       end
 
       def action_export
-        assert_target_directory_valid!
-        if target_dir_non_existant_or_empty?
-          run_command(run_options(:command => export_command))
-          @new_resource.updated_by_last_action(true)
+        if target_dir_non_existent_or_empty?
+          action_force_export
         else
           Chef::Log.debug "#{@new_resource} export destination #{@new_resource.destination} already exists or is a non-empty directory - nothing to do"
         end
       end
 
       def action_force_export
-        assert_target_directory_valid!
-        run_command(run_options(:command => export_command))
-        @new_resource.updated_by_last_action(true)
+        converge_by("export #{@new_resource.repository} into #{@new_resource.destination}") do
+          run_command(run_options(:command => export_command))
+        end
       end
 
       def action_sync
@@ -72,13 +88,13 @@ class Chef
           current_rev = find_current_revision
           Chef::Log.debug "#{@new_resource} current revision: #{current_rev} target revision: #{revision_int}"
           unless current_revision_matches_target_revision?
-            run_command(run_options(:command => sync_command))
-            Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}"
-            @new_resource.updated_by_last_action(true)
+            converge_by("sync #{@new_resource.destination} from #{@new_resource.repository}") do
+              run_command(run_options(:command => sync_command))
+              Chef::Log.info "#{@new_resource} updated to revision: #{revision_int}"
+            end
           end
         else
           action_checkout
-          @new_resource.updated_by_last_action(true)
         end
       end
 
@@ -140,6 +156,7 @@ class Chef
       def run_options(run_opts={})
         run_opts[:user] = @new_resource.user if @new_resource.user
         run_opts[:group] = @new_resource.group if @new_resource.group
+        run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout
         run_opts
       end
 
@@ -181,9 +198,11 @@ class Chef
       def scm(*args)
         ['svn', *args].compact.join(" ")
       end
-      
-      # TODO these methods are the same as the git provider...need to REFACTOR
-      # ...the subversion and git providers should extend from the same parent
+
+
+      def target_dir_non_existent_or_empty?
+        !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
+      end
       def assert_target_directory_valid!
         target_parent_directory = ::File.dirname(@new_resource.destination)
         unless ::File.directory?(target_parent_directory)
@@ -191,10 +210,6 @@ class Chef
           raise Chef::Exceptions::MissingParentDirectory, msg
         end
       end
-
-      def target_dir_non_existant_or_empty?
-        !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
-      end
     end
   end
 end
diff --git a/lib/chef/provider/template.rb b/lib/chef/provider/template.rb
index b2f3f48..217f9e5 100644
--- a/lib/chef/provider/template.rb
+++ b/lib/chef/provider/template.rb
@@ -17,88 +17,42 @@
 # limitations under the License.
 #
 
+require 'chef/provider/template_finder'
 require 'chef/provider/file'
-require 'chef/mixin/template'
-require 'chef/mixin/checksum'
-require 'chef/file_access_control'
+require 'chef/deprecation/provider/template'
+require 'chef/deprecation/warnings'
+
 
 class Chef
   class Provider
-
     class Template < Chef::Provider::File
 
-      include Chef::Mixin::Checksum
-      include Chef::Mixin::Template
+      extend Chef::Deprecation::Warnings
+      include Chef::Deprecation::Provider::Template
+      add_deprecation_warnings_for(Chef::Deprecation::Provider::Template.instance_methods)
 
-      def load_current_resource
+      def initialize(new_resource, run_context)
+        @content_class = Chef::Provider::Template::Content
         super
-        @current_resource.checksum(checksum(@current_resource.path)) if ::File.exist?(@current_resource.path)
       end
 
-      def action_create
-        render_with_context(template_location) do |rendered_template|
-          rendered(rendered_template)
-          if ::File.exist?(@new_resource.path) && content_matches?
-            Chef::Log.debug("#{@new_resource} content has not changed.")
-            set_all_access_controls(@new_resource.path)
-          else
-            backup
-            set_all_access_controls(rendered_template.path)
-            FileUtils.mv(rendered_template.path, @new_resource.path)
-            Chef::Log.info("#{@new_resource} updated content")
-            @new_resource.updated_by_last_action(true)
-          end
-        end
+      def load_current_resource
+        @current_resource = Chef::Resource::Template.new(@new_resource.name)
+        super
       end
 
-      def action_create_if_missing
-        if ::File.exists?(@new_resource.path)
-          Chef::Log.debug("#{@new_resource} exists - taking no action")
-        else
-          action_create
-        end
-      end
+      def define_resource_requirements
+        super
 
-      def template_location
-        @template_file_cache_location ||= begin
-          if @new_resource.local
-            @new_resource.source
-          else
-            cookbook = run_context.cookbook_collection[resource_cookbook]
-            cookbook.preferred_filename_on_disk_location(node, :templates, @new_resource.source)
-          end
+        requirements.assert(:create, :create_if_missing) do |a|
+          a.assertion { ::File::exists?(content.template_location) }
+          a.failure_message "Template source #{content.template_location} could not be found."
+          a.whyrun "Template source #{content.template_location} does not exist. Assuming it would have been created."
+          a.block_action!
         end
       end
-      
-      def resource_cookbook
-        @new_resource.cookbook || @new_resource.cookbook_name
-      end
-
-      def rendered(rendered_template)
-        @new_resource.checksum(checksum(rendered_template.path))
-        Chef::Log.debug("Current content's checksum:  #{@current_resource.checksum}")
-        Chef::Log.debug("Rendered content's checksum: #{@new_resource.checksum}")
-      end
-
-      def content_matches?
-        @current_resource.checksum == @new_resource.checksum
-      end
-
-      def set_all_access_controls(file)
-        access_controls = Chef::FileAccessControl.new(@new_resource, file)
-        access_controls.set_all
-        @new_resource.updated_by_last_action(access_controls.modified?)
-      end
-
-      private
-
-      def render_with_context(template_location, &block)
-        context = {}
-        context.merge!(@new_resource.variables)
-        context[:node] = node
-        render_template(IO.read(template_location), context, &block)
-      end
 
     end
   end
 end
+
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
new file mode 100644
index 0000000..472ea33
--- /dev/null
+++ b/lib/chef/provider/template/content.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/mixin/template'
+require 'chef/file_content_management/content_base'
+
+class Chef
+  class Provider
+    class Template
+
+      class Content < Chef::FileContentManagement::ContentBase
+
+        include Chef::Mixin::Template
+
+        def template_location
+          @template_file_cache_location ||= begin
+            template_finder.find(@new_resource.source, :local => @new_resource.local, :cookbook => @new_resource.cookbook)
+          end
+        end
+
+        private
+
+        def file_for_provider
+          context = TemplateContext.new(@new_resource.variables)
+          context[:node] = @run_context.node
+          context[:template_finder] = template_finder
+          context._extend_modules(@new_resource.helper_modules)
+          output = context.render_template(template_location)
+
+          tempfile = Tempfile.open("chef-rendered-template")
+          tempfile.binmode
+          tempfile.write(output)
+          tempfile.close
+          tempfile
+        end
+
+        def template_finder
+          @template_finder ||= begin
+            TemplateFinder.new(run_context, @new_resource.cookbook_name, @run_context.node)
+          end
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/provider/template_finder.rb b/lib/chef/provider/template_finder.rb
new file mode 100644
index 0000000..1596a32
--- /dev/null
+++ b/lib/chef/provider/template_finder.rb
@@ -0,0 +1,61 @@
+#--
+# Author:: Andrea Campi (<andrea.campi at zephirworks.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.
+#
+
+class Chef
+  class Provider
+
+    class TemplateFinder
+
+      def initialize(run_context, cookbook_name, node)
+        @run_context = run_context
+        @cookbook_name = cookbook_name
+        @node = node
+      end
+
+      def find(template_name, options = {})
+        template_name = template_source_name(template_name, options)
+
+        if options[:local]
+          return template_name
+        end
+
+        cookbook_name = find_cookbook_name(options)
+        cookbook = @run_context.cookbook_collection[cookbook_name]
+
+        cookbook.preferred_filename_on_disk_location(@node, :templates, template_name)
+      end
+
+    protected
+      def template_source_name(name, options)
+        if options[:source]
+          options[:source]
+        else
+          name
+        end
+      end
+
+      def find_cookbook_name(options)
+        if options[:cookbook]
+          options[:cookbook]
+        else
+          @cookbook_name
+        end
+      end
+    end
+  end
+end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index 2c0471f..738f766 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -18,35 +18,40 @@
 
 require 'chef/provider'
 require 'chef/mixin/command'
-require 'chef/resource/user'
 require 'etc'
 
 class Chef
   class Provider
     class User < Chef::Provider
-      
+
       include Chef::Mixin::Command
-      
+
       attr_accessor :user_exists, :locked
-      
+
       def initialize(new_resource, run_context)
         super
         @user_exists = true
         @locked = nil
+        @shadow_lib_ok = true
+        @group_name_resolved = true
       end
-  
+
       def convert_group_name
         if @new_resource.gid.is_a? String
           @new_resource.gid(Etc.getgrnam(@new_resource.gid).gid)
         end
       rescue ArgumentError => e
-        raise Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}"
+        @group_name_resolved = false
+      end
+
+      def whyrun_supported?
+        true
       end
-      
+
       def load_current_resource
         @current_resource = Chef::Resource::User.new(@new_resource.name)
         @current_resource.username(@new_resource.username)
-      
+
         begin
           user_info = Etc.getpwnam(@new_resource.username)
         rescue ArgumentError => e
@@ -54,7 +59,7 @@ class Chef
           Chef::Log.debug("#{@new_resource} user does not exist")
           user_info = nil
         end
-        
+
         if user_info
           @current_resource.uid(user_info.uid)
           @current_resource.gid(user_info.gid)
@@ -62,54 +67,81 @@ class Chef
           @current_resource.home(user_info.dir)
           @current_resource.shell(user_info.shell)
           @current_resource.password(user_info.passwd)
-        
+
           if @new_resource.password && @current_resource.password == 'x'
             begin
               require 'shadow'
             rescue LoadError
-              raise Chef::Exceptions::MissingLibrary, "You must have ruby-shadow installed for password support!"
+              @shadow_lib_ok = false
             else
               shadow_info = Shadow::Passwd.getspnam(@new_resource.username)
               @current_resource.password(shadow_info.sp_pwdp)
             end
           end
-          
-          if @new_resource.gid
-            convert_group_name
-          end
+
+          convert_group_name if @new_resource.gid
         end
-        
+
         @current_resource
       end
 
+      def define_resource_requirements
+        requirements.assert(:all_actions) do |a|
+          a.assertion { @group_name_resolved }
+          a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}"
+          a.whyrun "group name #{@new_resource.gid} does not exist.  This will cause group assignment to fail.  Assuming this group will have been created previously."
+        end
+        requirements.assert(:all_actions) do |a|
+          a.assertion { @shadow_lib_ok }
+          a.failure_message Chef::Exceptions::MissingLibrary, "You must have ruby-shadow installed for password support!"
+          a.whyrun "ruby-shadow is not installed. Attempts to set user password will cause failure.  Assuming that this gem will have been previously installed." +
+                   "Note that user update converge may report false-positive on the basis of mismatched password. "
+        end
+        requirements.assert(:modify, :lock, :unlock) do |a|
+          a.assertion { @user_exists }
+          a.failure_message(Chef::Exceptions::User, "Cannot modify user #{@new_resource} - does not exist!")
+          a.whyrun("Assuming user #{@new_resource} would have been created")
+        end
+      end
+
       # Check to see if the user needs any changes
       #
       # === Returns
       # <true>:: If a change is required
       # <false>:: If the users are identical
       def compare_user
-        [ :uid, :gid, :comment, :home, :shell, :password ].any? do |user_attrib|
+        changed = [ :comment, :home, :shell, :password ].select do |user_attrib|
           !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
         end
+
+        changed += [ :uid, :gid ].select do |user_attrib|
+          !@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib).to_s != @current_resource.send(user_attrib).to_s
+        end
+
+        changed.any?
       end
-      
+
       def action_create
+
         if !@user_exists
-          create_user
-          Chef::Log.info("#{@new_resource} created")
-          @new_resource.updated_by_last_action(true)
+          converge_by("create user #{@new_resource}") do
+            create_user
+            Chef::Log.info("#{@new_resource} created")
+          end
         elsif compare_user
-          manage_user
-          Chef::Log.info("#{@new_resource} altered")
-          @new_resource.updated_by_last_action(true)
+          converge_by("alter user #{@new_resource}") do
+            manage_user
+            Chef::Log.info("#{@new_resource} altered")
+          end
         end
       end
-      
+
       def action_remove
         if @user_exists
-          remove_user
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} removed")
+          converge_by("remove user #{@new_resource}") do
+            remove_user
+            Chef::Log.info("#{@new_resource} removed")
+          end
         end
       end
 
@@ -119,9 +151,10 @@ class Chef
 
       def action_manage
         if @user_exists && compare_user
-          manage_user
-          @new_resource.updated_by_last_action(true)
-          Chef::Log.info("#{@new_resource} managed")
+          converge_by("manage user #{@new_resource}") do
+            manage_user
+            Chef::Log.info("#{@new_resource} managed")
+          end
         end
       end
 
@@ -130,28 +163,22 @@ class Chef
       end
 
       def action_modify
-        if @user_exists
-          if compare_user
+        if compare_user
+          converge_by("modify user #{@new_resource}") do
             manage_user
-            @new_resource.updated_by_last_action(true)
             Chef::Log.info("#{@new_resource} modified")
           end
-        else
-          raise Chef::Exceptions::User, "Cannot modify user - does not exist!"
         end
       end
 
       def action_lock
-        if @user_exists
-          if check_lock() == false
+        if check_lock() == false
+          converge_by("lock the user #{@new_resource}") do
             lock_user
-            @new_resource.updated_by_last_action(true)
             Chef::Log.info("#{@new_resource} locked")
-          else
-            Chef::Log.debug("#{@new_resource} already locked - nothing to do")
           end
-        else
-          raise Chef::Exceptions::User, "Cannot lock user - does not exist!"
+         else
+          Chef::Log.debug("#{@new_resource} already locked - nothing to do")
         end
       end
 
@@ -164,19 +191,16 @@ class Chef
       end
 
       def action_unlock
-        if @user_exists
-          if check_lock() == true
+        if check_lock() == true
+          converge_by("unlock user #{@new_resource}") do
             unlock_user
-            @new_resource.updated_by_last_action(true)
             Chef::Log.info("#{@new_resource} unlocked")
-          else
-            Chef::Log.debug("#{@new_resource} already unlocked - nothing to do")
           end
         else
-          raise Chef::Exceptions::User, "Cannot unlock user - does not exist!"
+          Chef::Log.debug("#{@new_resource} already unlocked - nothing to do")
         end
       end
-      
+
       def unlock_user
         raise NotImplementedError
       end
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
index 19a755a..b019316 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -25,10 +25,10 @@ class Chef
     class User
       class Dscl < Chef::Provider::User
         include Chef::Mixin::ShellOut
-        
+
         NFS_HOME_DIRECTORY        = %r{^NFSHomeDirectory: (.*)$}
         AUTHENTICATION_AUTHORITY  = %r{^AuthenticationAuthority: (.*)$}
-        
+
         def dscl(*args)
           shell_out("dscl . -#{args.join(' ')}")
         end
@@ -80,7 +80,7 @@ class Chef
           return safe_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") if (@new_resource.home.nil? || @new_resource.home.empty?)
           if @new_resource.supports[:manage_home]
             validate_home_dir_specification!
-            
+
             if (@current_resource.home == @new_resource.home) && !new_home_exists?
               ditto_home
             elsif !current_home_exists? && !new_home_exists?
@@ -105,7 +105,7 @@ class Chef
         end
 
         def shadow_hash_set?
-          user_data = safe_dscl("read /Users/#{@new_resource.username}") 
+          user_data = safe_dscl("read /Users/#{@new_resource.username}")
           if user_data =~ /AuthenticationAuthority: / && user_data =~ /ShadowHash/
             true
           else
@@ -116,7 +116,7 @@ class Chef
         def modify_password
           if @new_resource.password
             shadow_hash = nil
-            
+
             Chef::Log.debug("#{new_resource} updating password")
             if osx_shadow_hash?(@new_resource.password)
               shadow_hash = @new_resource.password.upcase
@@ -134,11 +134,11 @@ class Chef
               shadow_hash = String.new("00000000"*155)
               shadow_hash[168] = salted_sha1
             end
-            
+
             ::File.open("/var/db/shadow/hash/#{guid}",'w',0600) do |output|
               output.puts shadow_hash
             end
-            
+
             unless shadow_hash_set?
               safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';ShadowHash;'")
             end
@@ -159,29 +159,37 @@ class Chef
           dscl_set_shell
           modify_password
         end
-        
+
         def manage_user
           dscl_create_user    if diverged?(:username)
           dscl_create_comment if diverged?(:comment)
           set_uid             if diverged?(:uid)
-          dscl_set_gid        if diverged?(:uid)
+          dscl_set_gid        if diverged?(:gid)
           modify_home         if diverged?(:home)
           dscl_set_shell      if diverged?(:shell)
           modify_password     if diverged?(:password)
         end
-        
+
         def dscl_create_user
-          safe_dscl("create /Users/#{@new_resource.username}")              
+          safe_dscl("create /Users/#{@new_resource.username}")
         end
-        
+
         def dscl_create_comment
           safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
         end
-        
+
         def dscl_set_gid
+          unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
+            begin
+              possible_gid = safe_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
+            rescue Chef::Exceptions::DsclCommandFailed => e
+              raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
+            end
+            @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
+          end
           safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
         end
-        
+
         def dscl_set_shell
           if @new_resource.password || ::File.exists?("#{@new_resource.shell}")
             safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
@@ -189,10 +197,10 @@ class Chef
             safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
           end
         end
-        
+
         def remove_user
           if @new_resource.supports[:manage_home]
-            user_info = safe_dscl("read /Users/#{@new_resource.username}") 
+            user_info = safe_dscl("read /Users/#{@new_resource.username}")
             if nfs_home_match = user_info.match(NFS_HOME_DIRECTORY)
               #nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory")
               #nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"")
@@ -220,7 +228,7 @@ class Chef
             false
           end
         end
-        
+
         def check_lock
           return @locked = locked?
         end
@@ -228,27 +236,27 @@ class Chef
         def lock_user
           safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
         end
-        
+
         def unlock_user
           auth_info = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority")
           auth_string = auth_info.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip#.gsub!(/[; ]*$/,"")
           safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
         end
-        
+
         def validate_home_dir_specification!
           unless @new_resource.home =~ /^\//
-            raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'") 
+            raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
           end
         end
-        
+
         def current_home_exists?
           ::File.exist?("#{@current_resource.home}")
         end
-        
+
         def new_home_exists?
-          ::File.exist?("#{@new_resource.home}")          
+          ::File.exist?("#{@new_resource.home}")
         end
-        
+
         def ditto_home
           skel = "/System/Library/User Template/English.lproj"
           raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
@@ -258,7 +266,7 @@ class Chef
 
         def move_home
           Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
-          
+
           src = @current_resource.home
           FileUtils.mkdir_p(@new_resource.home)
           files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
@@ -266,11 +274,11 @@ class Chef
           ::FileUtils.rmdir(src)
           ::FileUtils.chown_R(@new_resource.username, at new_resource.gid.to_s, at new_resource.home)
         end
-        
+
         def diverged?(parameter)
           parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
         end
-        
+
         def parameter_updated?(parameter)
           not (@new_resource.send(parameter) == @current_resource.send(parameter))
         end
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index 4f6393d..9f7a169 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -34,20 +34,20 @@ class Chef
           run_command(:command => command)
           modify_password
         end
-        
+
         def manage_user
           command = "pw usermod"
           command << set_options
           run_command(:command => command)
           modify_password
         end
-        
+
         def remove_user
           command = "pw userdel #{@new_resource.username}"
           command << " -r" if @new_resource.supports[:manage_home]
           run_command(:command => command)
         end
-        
+
         def check_lock
           case @current_resource.password
           when /^\*LOCKED\*/
@@ -57,18 +57,18 @@ class Chef
           end
           @locked
         end
-        
+
         def lock_user
           run_command(:command => "pw lock #{@new_resource.username}")
         end
-        
+
         def unlock_user
           run_command(:command => "pw unlock #{@new_resource.username}")
         end
-        
+
         def set_options
           opts = " #{@new_resource.username}"
-          
+
           field_list = {
             'comment' => "-c",
             'home' => "-d",
@@ -91,7 +91,7 @@ class Chef
           end
           opts
         end
-      
+
         def modify_password
           if @current_resource.password != @new_resource.password
             Chef::Log.debug("#{new_resource} updating password")
@@ -99,7 +99,7 @@ class Chef
             status = popen4(command, :waitlast => true) do |pid, stdin, stdout, stderr|
               stdin.puts "#{@new_resource.password}"
             end
-            
+
             unless status.exitstatus == 0
               raise Chef::Exceptions::User, "pw failed - #{status.inspect}!"
             end
diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb
new file mode 100644
index 0000000..0658981
--- /dev/null
+++ b/lib/chef/provider/user/solaris.rb
@@ -0,0 +1,90 @@
+#
+# Author:: Stephen Nelson-Smith (<sns at opscode.com>)
+# Author:: Jon Ramsey (<jonathon.ramsey at gmail.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.
+
+class Chef
+  class Provider
+    class User
+      class Solaris < Chef::Provider::User::Useradd
+        UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
+
+        attr_writer :password_file
+
+        def initialize(new_resource, run_context)
+          @password_file = "/etc/shadow"
+          super
+        end
+
+        def create_user
+          super
+          manage_password
+        end
+
+        def manage_user
+          manage_password
+          super
+        end
+
+      private
+
+        def manage_password
+          if @current_resource.password != @new_resource.password && @new_resource.password
+            Chef::Log.debug("#{@new_resource} setting password to #{@new_resource.password}")
+            write_shadow_file
+          end
+        end
+
+        def write_shadow_file
+          buffer = Tempfile.new("shadow", "/etc")
+          ::File.open(@password_file) do |shadow_file|
+            shadow_file.each do |entry|
+              user = entry.split(":").first
+              if user == @new_resource.username
+                buffer.write(updated_password(entry))
+              else
+                buffer.write(entry)
+              end
+            end
+          end
+          buffer.close
+
+          # FIXME: mostly duplicates code with file provider deploying a file
+          mode = ::File.stat(@password_file).mode & 07777
+          uid  = ::File.stat(@password_file).uid
+          gid  = ::File.stat(@password_file).gid
+
+          FileUtils.chown uid, gid, buffer.path
+          FileUtils.chmod mode, buffer.path
+
+          FileUtils.mv buffer.path, @password_file
+        end
+
+        def updated_password(entry)
+          fields = entry.split(":")
+          fields[1] = @new_resource.password
+          fields[2] = days_since_epoch
+          fields.join(":")
+        end
+
+        def days_since_epoch
+          (Time.now.to_i / 86400).floor
+        end
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
index 1913480..1789a4e 100644
--- a/lib/chef/provider/user/useradd.rb
+++ b/lib/chef/provider/user/useradd.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -17,110 +17,124 @@
 #
 
 require 'pathname'
+require 'chef/mixin/shell_out'
 require 'chef/provider/user'
 
 class Chef
   class Provider
-    class User 
+    class User
       class Useradd < Chef::Provider::User
+
+        include Chef::Mixin::ShellOut
+
         UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]]
 
         def create_user
           command = compile_command("useradd") do |useradd|
-            useradd << universal_options
-            useradd << useradd_options
+            useradd.concat(universal_options)
+            useradd.concat(useradd_options)
           end
-          run_command(:command => command)
+          shell_out!(*command)
         end
-        
+
         def manage_user
-          command = compile_command("usermod") { |u| u << universal_options }
-          run_command(:command => command)
+          if universal_options != ""
+            command = compile_command("usermod") do |u|
+              u.concat(universal_options)
+            end
+            shell_out!(*command)
+          end
         end
-        
+
         def remove_user
-          command = "userdel"
-          command << " -r" if managing_home_dir?
-          command << " #{@new_resource.username}"
-          run_command(:command => command)
+          command = [ "userdel" ]
+          command << "-r" if managing_home_dir?
+          command << new_resource.username
+          shell_out!(*command)
         end
-        
+
         def check_lock
-          status = popen4("passwd -S #{@new_resource.username}") do |pid, stdin, stdout, stderr|
-            status_line = stdout.gets.split(' ')
-            case status_line[1]
-            when /^P/
-              @locked = false
-            when /^N/
-              @locked = false
-            when /^L/
-              @locked = true
-            end
+          # we can get an exit code of 1 even when it's successful on
+          # rhel/centos (redhat bug 578534). See additional error checks below.
+          passwd_s = shell_out!("passwd", "-S", new_resource.username, :returns => [0,1])
+          status_line = passwd_s.stdout.split(' ')
+          case status_line[1]
+          when /^P/
+            @locked = false
+          when /^N/
+            @locked = false
+          when /^L/
+            @locked = true
           end
 
-          unless status.exitstatus == 0
+          unless passwd_s.exitstatus == 0
             raise_lock_error = false
-            # we can get an exit code of 1 even when it's successful on rhel/centos (redhat bug 578534)
-            if status.exitstatus == 1 && ['redhat', 'centos'].include?(node[:platform])
-              passwd_version_status = popen4('rpm -q passwd') do |pid, stdin, stdout, stderr|
-                passwd_version = stdout.gets.chomp
+            if ['redhat', 'centos'].include?(node[:platform])
+              passwd_version_check = shell_out!('rpm -q passwd')
+              passwd_version = passwd_version_check.stdout.chomp
 
-                unless passwd_version == 'passwd-0.73-1'
-                  raise_lock_error = true
-                end
+              unless passwd_version == 'passwd-0.73-1'
+                raise_lock_error = true
               end
             else
               raise_lock_error = true
             end
 
-            raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if raise_lock_error
+            raise Chef::Exceptions::User, "Cannot determine if #{new_resource} is locked!" if raise_lock_error
           end
 
           @locked
         end
-        
+
         def lock_user
-          run_command(:command => "usermod -L #{@new_resource.username}")
+          shell_out!("usermod", "-L", new_resource.username)
         end
-        
+
         def unlock_user
-          run_command(:command => "usermod -U #{@new_resource.username}")
+          shell_out!("usermod", "-U", new_resource.username)
         end
 
         def compile_command(base_command)
+          base_command = Array(base_command)
           yield base_command
-          base_command << " #{@new_resource.username}"
+          base_command << new_resource.username
           base_command
         end
-        
+
         def universal_options
-          opts = ''
-          
-          UNIVERSAL_OPTIONS.each do |field, option|
-            if @current_resource.send(field) != @new_resource.send(field)
-              if @new_resource.send(field)
-                Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}")
-                opts << " #{option} '#{@new_resource.send(field)}'"
+          @universal_options ||=
+            begin
+              opts = []
+              # magic allows UNIVERSAL_OPTIONS to be overridden in a subclass
+              self.class::UNIVERSAL_OPTIONS.each do |field, option|
+                update_options(field, option, opts)
+              end
+              if updating_home?
+                if managing_home_dir?
+                  Chef::Log.debug("#{new_resource} managing the users home directory")
+                  opts << "-d" << new_resource.home << "-m"
+                else
+                  Chef::Log.debug("#{new_resource} setting home to #{new_resource.home}")
+                  opts << "-d" << new_resource.home
+                end
               end
+              opts << "-o" if new_resource.non_unique || new_resource.supports[:non_unique]
+              opts
             end
-          end
-          if updating_home?
-            if managing_home_dir?
-              Chef::Log.debug("#{@new_resource} managing the users home directory")
-              opts << " -d '#{@new_resource.home}'"
-            else
-              Chef::Log.debug("#{@new_resource} setting home to #{@new_resource.home}")
-              opts << " -d '#{@new_resource.home}'"
+        end
+
+        def update_options(field, option, opts)
+          if @current_resource.send(field).to_s != new_resource.send(field).to_s
+            if new_resource.send(field)
+              Chef::Log.debug("#{new_resource} setting #{field} to #{new_resource.send(field)}")
+              opts << option << new_resource.send(field).to_s
             end
           end
-          opts << " -o" if @new_resource.non_unique || @new_resource.supports[:non_unique]
-          opts
         end
 
         def useradd_options
-          opts = ''
-          opts << " -m" if updating_home? && managing_home_dir?
-          opts << " -r" if @new_resource.system
+          opts = []
+          opts << "-r" if new_resource.system
           opts
         end
 
@@ -129,12 +143,12 @@ class Chef
           # Pathname#cleanpath does a better job than ::File::expand_path (on both unix and windows)
           # ::File.expand_path("///tmp") == ::File.expand_path("/tmp") => false
           # ::File.expand_path("\\tmp") => "C:/tmp"
-          return true if @current_resource.home.nil? && @new_resource.home
-          @new_resource.home and Pathname.new(@current_resource.home).cleanpath != Pathname.new(@new_resource.home).cleanpath
+          return true if @current_resource.home.nil? && new_resource.home
+          new_resource.home and Pathname.new(@current_resource.home).cleanpath != Pathname.new(new_resource.home).cleanpath
         end
 
         def managing_home_dir?
-          @new_resource.manage_home || @new_resource.supports[:manage_home]
+          new_resource.manage_home || new_resource.supports[:manage_home]
         end
 
       end
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index 6bbb2a0..350f3ff 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -71,23 +71,23 @@ class Chef
         def create_user
           @net_user.add(set_options)
         end
-        
+
         def manage_user
           @net_user.update(set_options)
         end
-        
+
         def remove_user
           @net_user.delete
         end
-        
+
         def check_lock
           @net_user.check_enabled
         end
-        
+
         def lock_user
           @net_user.disable_account
         end
-        
+
         def unlock_user
           @net_user.enable_account
         end
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
new file mode 100644
index 0000000..08a2ea7
--- /dev/null
+++ b/lib/chef/provider/windows_script.rb
@@ -0,0 +1,73 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/provider/script'
+require 'chef/mixin/windows_architecture_helper'
+
+class Chef
+  class Provider
+    class WindowsScript < Chef::Provider::Script
+
+      protected
+
+      include Chef::Mixin::WindowsArchitectureHelper
+
+      def initialize( new_resource, run_context, script_extension='')
+        super( new_resource, run_context )
+        @script_extension = script_extension
+
+        target_architecture = new_resource.architecture.nil? ?
+          node_windows_architecture(run_context.node) : new_resource.architecture
+
+        @is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture)
+
+        if ( target_architecture == :i386 ) && ! is_i386_windows_process?
+          raise Chef::Exceptions::Win32ArchitectureIncorrect,
+          "Support for the i386 architecture from a 64-bit Ruby runtime is not yet implemented"
+        end
+      end
+
+      public
+
+      def action_run
+        wow64_redirection_state = nil
+
+        if @is_wow64
+          wow64_redirection_state = disable_wow64_file_redirection(@run_context.node)
+        end
+
+        begin
+          super
+        rescue
+          raise
+        ensure
+          if ! wow64_redirection_state.nil?
+            restore_wow64_file_redirection(@run_context.node, wow64_redirection_state)
+          end
+        end
+      end
+
+      def script_file
+        base_script_name = "chef-script"
+        temp_file_arguments = [ base_script_name, @script_extension ]
+
+        @script_file ||= Tempfile.open(temp_file_arguments)
+      end
+    end
+  end
+end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index 1e395fa..50099e8 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -16,10 +16,12 @@
 # limitations under the License.
 #
 
+require 'chef/provider/batch'
 require 'chef/provider/breakpoint'
 require 'chef/provider/cookbook_file'
 require 'chef/provider/cron'
 require 'chef/provider/cron/solaris'
+require 'chef/provider/cron/aix'
 require 'chef/provider/deploy'
 require 'chef/provider/directory'
 require 'chef/provider/env'
@@ -36,6 +38,7 @@ require 'chef/provider/ohai'
 require 'chef/provider/mdadm'
 require 'chef/provider/mount'
 require 'chef/provider/package'
+require 'chef/provider/powershell_script'
 require 'chef/provider/remote_directory'
 require 'chef/provider/remote_file'
 require 'chef/provider/route'
@@ -52,6 +55,7 @@ require 'chef/provider/package/apt'
 require 'chef/provider/package/dpkg'
 require 'chef/provider/package/easy_install'
 require 'chef/provider/package/freebsd'
+require 'chef/provider/package/ips'
 require 'chef/provider/package/macports'
 require 'chef/provider/package/pacman'
 require 'chef/provider/package/portage'
@@ -61,6 +65,7 @@ require 'chef/provider/package/yum'
 require 'chef/provider/package/zypper'
 require 'chef/provider/package/solaris'
 require 'chef/provider/package/smartos'
+require 'chef/provider/package/aix'
 
 require 'chef/provider/service/arch'
 require 'chef/provider/service/debian'
@@ -81,18 +86,38 @@ require 'chef/provider/user/dscl'
 require 'chef/provider/user/pw'
 require 'chef/provider/user/useradd'
 require 'chef/provider/user/windows'
+require 'chef/provider/user/solaris'
 
 require 'chef/provider/group/aix'
 require 'chef/provider/group/dscl'
 require 'chef/provider/group/gpasswd'
 require 'chef/provider/group/groupadd'
+require 'chef/provider/group/groupmod'
 require 'chef/provider/group/pw'
 require 'chef/provider/group/suse'
 require 'chef/provider/group/usermod'
 require 'chef/provider/group/windows'
 
 require 'chef/provider/mount/mount'
+require 'chef/provider/mount/aix'
 require 'chef/provider/mount/windows'
 
 require 'chef/provider/deploy/revision'
 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/fetcher'
+
+require "chef/provider/lwrp_base"
+require 'chef/provider/registry_key'
+
+require 'chef/provider/file/content'
+require 'chef/provider/remote_file/content'
+require 'chef/provider/cookbook_file/content'
+require 'chef/provider/template/content'
+
+require 'chef/provider/ifconfig/redhat'
+require 'chef/provider/ifconfig/debian'
+require 'chef/provider/ifconfig/aix'
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index 33dfde7..0c688cb 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -18,10 +18,13 @@
 #
 
 
-require 'chef/mixin/recipe_definition_dsl_core'
+require 'chef/dsl/recipe'
+require 'chef/dsl/data_query'
+require 'chef/dsl/platform_introspection'
+require 'chef/dsl/include_recipe'
+require 'chef/dsl/registry_helper'
+
 require 'chef/mixin/from_file'
-require 'chef/mixin/language'
-require 'chef/mixin/language_include_recipe'
 
 require 'chef/mixin/deprecation'
 
@@ -30,10 +33,13 @@ class Chef
   # A Recipe object is the context in which Chef recipes are evaluated.
   class Recipe
 
+    include Chef::DSL::DataQuery
+    include Chef::DSL::PlatformIntrospection
+    include Chef::DSL::IncludeRecipe
+    include Chef::DSL::Recipe
+    include Chef::DSL::RegistryHelper
+
     include Chef::Mixin::FromFile
-    include Chef::Mixin::Language
-    include Chef::Mixin::LanguageIncludeRecipe
-    include Chef::Mixin::RecipeDefinitionDSLCore
     include Chef::Mixin::Deprecation
 
     attr_accessor :cookbook_name, :recipe_name, :recipe, :params, :run_context
@@ -75,26 +81,9 @@ class Chef
       run_context.resource_collection.find(*args)
     end
 
-    # Sets a tag, or list of tags, for this node.  Syntactic sugar for
-    # run_context.node[:tags].
-    #
-    # With no arguments, returns the list of tags.
-    #
-    # === Parameters
-    # tags<Array>:: A list of tags to add - can be a single string
-    #
-    # === Returns
-    # tags<Array>:: The contents of run_context.node[:tags]
+    # This was moved to Chef::Node#tag, redirecting here for compatability
     def tag(*tags)
-      if tags.length > 0
-        tags.each do |tag|
-          tag = tag.to_s
-          run_context.node[:tags] << tag unless run_context.node[:tags].include?(tag)
-        end
-        run_context.node[:tags]
-      else
-        run_context.node[:tags]
-      end
+      run_context.node.tag(*tags)
     end
 
     # Returns true if the node is tagged with *all* of the supplied +tags+.
@@ -121,7 +110,7 @@ class Chef
     # tags<Array>:: The current list of run_context.node[:tags]
     def untag(*tags)
       tags.each do |tag|
-        run_context.node[:tags].delete(tag)
+        run_context.node.normal[:tags].delete(tag)
       end
     end
 
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 2fd3942..997c614 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -18,10 +18,12 @@
 #
 
 require 'chef/mixin/params_validate'
-require 'chef/mixin/check_helper'
-require 'chef/mixin/language'
+require 'chef/dsl/platform_introspection'
+require 'chef/dsl/data_query'
+require 'chef/dsl/registry_helper'
 require 'chef/mixin/convert_to_class_name'
 require 'chef/resource/conditional'
+require 'chef/resource/conditional_action_not_nothing'
 require 'chef/resource_collection'
 require 'chef/resource_platform_map'
 require 'chef/node'
@@ -41,19 +43,34 @@ class Chef
         other_notification.resource == resource && other_notification.action == action
       end
 
+      # If resource and/or notifying_resource is not a resource object, this will look them up in the resource collection
+      # and fix the references from strings to actual Resource objects.
       def resolve_resource_reference(resource_collection)
-        return resource if resource.kind_of?(Chef::Resource)
+        return resource if resource.kind_of?(Chef::Resource) && notifying_resource.kind_of?(Chef::Resource)
 
+        if not(resource.kind_of?(Chef::Resource))
+          fix_resource_reference(resource_collection)
+        end
+
+        if not(notifying_resource.kind_of?(Chef::Resource))
+          fix_notifier_reference(resource_collection)
+        end
+      end
+
+      # This will look up the resource if it is not a Resource Object.  It will complain if it finds multiple
+      # resources, can't find a resource, or gets invalid syntax.
+      def fix_resource_reference(resource_collection)
         matching_resource = resource_collection.find(resource)
         if Array(matching_resource).size > 1
           msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple resources, "\
-                "but can only notify one resource. Notifying resource was defined on #{notifying_resource.source_line}"
+          "but can only notify one resource. Notifying resource was defined on #{notifying_resource.source_line}"
           raise Chef::Exceptions::InvalidResourceReference, msg
         end
         self.resource = matching_resource
+
       rescue Chef::Exceptions::ResourceNotFound => e
         err = Chef::Exceptions::ResourceNotFound.new(<<-FAIL)
-Resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
+resource #{notifying_resource} is configured to notify resource #{resource} with action #{action}, \
 but #{resource} cannot be found in the resource collection. #{notifying_resource} is defined in \
 #{notifying_resource.source_line}
 FAIL
@@ -69,17 +86,127 @@ F
         raise err
       end
 
+      # This will look up the notifying_resource if it is not a Resource Object.  It will complain if it finds multiple
+      # resources, can't find a resource, or gets invalid syntax.
+      def fix_notifier_reference(resource_collection)
+        matching_notifier = resource_collection.find(notifying_resource)
+        if Array(matching_notifier).size > 1
+          msg = "Notification #{self} from #{notifying_resource} was created with a reference to multiple notifying "\
+          "resources, but can only originate from one resource.  Destination resource was defined "\
+          "on #{resource.source_line}"
+          raise Chef::Exceptions::InvalidResourceReference, msg
+        end
+        self.notifying_resource = matching_notifier
+
+      rescue Chef::Exceptions::ResourceNotFound => e
+        err = Chef::Exceptions::ResourceNotFound.new(<<-FAIL)
+Resource #{resource} is configured to receive notifications from #{notifying_resource} with action #{action}, \
+but #{notifying_resource} cannot be found in the resource collection. #{resource} is defined in \
+#{resource.source_line}
+FAIL
+        err.set_backtrace(e.backtrace)
+        raise err
+      rescue Chef::Exceptions::InvalidResourceSpecification => e
+          err = Chef::Exceptions::InvalidResourceSpecification.new(<<-F)
+Resource #{resource} is configured to receive notifications from  #{notifying_resource} with action #{action}, \
+but #{notifying_resource.inspect} is not valid syntax to look up a resource in the resource collection. Notification \
+is defined near #{resource.source_line}
+F
+          err.set_backtrace(e.backtrace)
+        raise err
+      end
+
     end
 
-    FORBIDDEN_IVARS = [:@run_context, :@node, :@not_if, :@only_if]
-    HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@node]
+    FORBIDDEN_IVARS = [:@run_context, :@node, :@not_if, :@only_if, :@enclosing_provider]
+    HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@node, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider]
 
-    include Chef::Mixin::CheckHelper
+    include Chef::DSL::DataQuery
     include Chef::Mixin::ParamsValidate
-    include Chef::Mixin::Language
+    include Chef::DSL::PlatformIntrospection
+    include Chef::DSL::RegistryHelper
     include Chef::Mixin::ConvertToClassName
     include Chef::Mixin::Deprecation
 
+    extend Chef::Mixin::ConvertToClassName
+
+
+    if Module.method(:const_defined?).arity == 1
+      def self.strict_const_defined?(const)
+        const_defined?(const)
+      end
+    else
+      def self.strict_const_defined?(const)
+        const_defined?(const, false)
+      end
+    end
+
+    # Track all subclasses of Resource. This is used so names can be looked up
+    # when attempting to deserialize from JSON. (See: json_compat)
+    def self.resource_classes
+      # Using a class variable here ensures we have one variable to track
+      # subclasses shared by the entire class hierarchy; without this, each
+      # subclass would have its own list of subclasses.
+      @@resource_classes ||= []
+    end
+
+    # Callback when subclass is defined. Adds subclass to list of subclasses.
+    def self.inherited(subclass)
+      resource_classes << subclass
+    end
+
+    # Look up a subclass by +class_name+ which should be a string that matches
+    # `Subclass.name`
+    def self.find_subclass_by_name(class_name)
+      resource_classes.first {|c| c.name == class_name }
+    end
+
+    # Set or return the list of "state attributes" implemented by the Resource
+    # subclass. State attributes are attributes that describe the desired state
+    # of the system, such as file permissions or ownership. In general, state
+    # attributes are attributes that could be populated by examining the state
+    # of the system (e.g., File.stat can tell you the permissions on an
+    # existing file). Contrarily, attributes that are not "state attributes"
+    # usually modify the way Chef itself behaves, for example by providing
+    # additional options for a package manager to use when installing a
+    # package.
+    #
+    # This list is used by the Chef client auditing system to extract
+    # information from resources to describe changes made to the system.
+    def self.state_attrs(*attr_names)
+      @state_attrs ||= []
+      @state_attrs = attr_names unless attr_names.empty?
+
+      # Return *all* state_attrs that this class has, including inherited ones
+      if superclass.respond_to?(:state_attrs)
+        superclass.state_attrs + @state_attrs
+      else
+        @state_attrs
+      end
+    end
+
+    # Set or return the "identity attribute" for this resource class. This is
+    # generally going to be the "name attribute" for this resource. In other
+    # words, the resource type plus this attribute uniquely identify a given
+    # bit of state that chef manages. For a File resource, this would be the
+    # path, for a package resource, it will be the package name. This will show
+    # up in chef-client's audit records as a searchable field.
+    def self.identity_attr(attr_name=nil)
+      @identity_attr ||= nil
+      @identity_attr = attr_name if attr_name
+
+      # If this class doesn't have an identity attr, we'll defer to the superclass:
+      if @identity_attr || !superclass.respond_to?(:identity_attr)
+        @identity_attr
+      else
+        superclass.identity_attr
+      end
+    end
+
+    def self.dsl_name
+      convert_to_snake_case(name, 'Chef::Resource')
+    end
+
     attr_accessor :params
     attr_accessor :provider
     attr_accessor :allowed_actions
@@ -97,10 +224,10 @@ F
     attr_reader :not_if_args
     attr_reader :only_if_args
 
+    attr_reader :elapsed_time
+
     # Each notify entry is a resource/action pair, modeled as an
     # Struct with a #resource and #action member
-    attr_reader :immediate_notifications
-    attr_reader :delayed_notifications
 
     def initialize(name, run_context=nil)
       @name = name
@@ -119,13 +246,32 @@ F
       @retry_delay = 2
       @not_if = []
       @only_if = []
-      @immediate_notifications = Array.new
-      @delayed_notifications = Array.new
       @source_line = nil
+      @elapsed_time = 0
 
       @node = run_context ? deprecated_ivar(run_context.node, :node, :warn) : nil
     end
 
+    # Returns a Hash of attribute => value for the state attributes declared in
+    # the resource's class definition.
+    def state
+      self.class.state_attrs.inject({}) do |state_attrs, attr_name|
+        state_attrs[attr_name] = send(attr_name)
+        state_attrs
+      end
+    end
+
+    # Returns the value of the identity attribute, if declared. Falls back to
+    # #name if no identity attribute is declared.
+    def identity
+      if identity_attr = self.class.identity_attr
+        send(identity_attr)
+      else
+        name
+      end
+    end
+
+
     def updated=(true_or_false)
       Chef::Log.warn("Chef::Resource#updated=(true|false) is deprecated. Please call #updated_by_last_action(true|false) instead.")
       Chef::Log.warn("Called from:")
@@ -152,14 +298,18 @@ F
     def load_prior_resource
       begin
         prior_resource = run_context.resource_collection.lookup(self.to_s)
-        Chef::Log.debug("Setting #{self.to_s} to the state of the prior #{self.to_s}")
+        # if we get here, there is a prior resource (otherwise we'd have jumped
+        # to the rescue clause).
+        Chef::Log.warn("Cloning resource attributes for #{self.to_s} from prior resource (CHEF-3694)")
+        Chef::Log.warn("Previous #{prior_resource}: #{prior_resource.source_line}") if prior_resource.source_line
+        Chef::Log.warn("Current  #{self}: #{self.source_line}") if self.source_line
         prior_resource.instance_variables.each do |iv|
-          unless iv.to_sym == :@source_line || iv.to_sym == :@action
+          unless iv.to_sym == :@source_line || iv.to_sym == :@action || iv.to_sym == :@not_if || iv.to_sym == :@only_if
             self.instance_variable_set(iv, prior_resource.instance_variable_get(iv))
           end
         end
         true
-      rescue Chef::Exceptions::ResourceNotFound => e
+      rescue Chef::Exceptions::ResourceNotFound
         true
       end
     end
@@ -206,17 +356,19 @@ F
     end
 
     def name(name=nil)
-      set_if_args(@name, name) do
+      if !name.nil?
         raise ArgumentError, "name must be a string!" unless name.kind_of?(String)
         @name = name
       end
+      @name
     end
 
     def noop(tf=nil)
-      set_if_args(@noop, tf) do
+      if !tf.nil?
         raise ArgumentError, "noop must be true or false!" unless tf == true || tf == false
         @noop = tf
       end
+      @noop
     end
 
     def ignore_failure(arg=nil)
@@ -247,32 +399,15 @@ F
       ignore_failure(arg)
     end
 
-    def notifies(*args)
-      unless ( args.size > 0 && args.size < 4)
-        raise ArgumentError, "Wrong number of arguments for notifies: should be 1-3 arguments, you gave #{args.inspect}"
-      end
+    # Sets up a notification from this resource to the resource specified by +resource_spec+.
+    def notifies(action, resource_spec, timing=:delayed)
+      # when using old-style resources(:template => "/foo.txt") style, you
+      # could end up with multiple resources.
+      resources = [ resource_spec ].flatten
+      resources.each do |resource|
 
-      if args.size > 1 # notifies(:action, resource) OR notifies(:action, resource, :immediately)
-        add_notification(*args)
-      else
-        # This syntax is so weird. surely people will just give us one hash?
-        notifications = args.flatten
-        notifications.each do |resources_notifications|
-          resources_notifications.each do |resource, notification|
-            action, timing = notification[0], notification[1]
-            Chef::Log.debug "Adding notification from resource #{self} to `#{resource.inspect}' => `#{notification.inspect}'"
-            add_notification(action, resource, timing)
-          end
-        end
-      end
-    rescue NoMethodError
-      Chef::Log.fatal("Error processing notifies(#{args.inspect}) on #{self}")
-      raise
-    end
+        validate_resource_spec!(resource_spec)
 
-    def add_notification(action, resources, timing=:delayed)
-      resources = [resources].flatten
-      resources.each do |resource|
         case timing.to_s
         when 'delayed'
           notifies_delayed(action, resource)
@@ -291,16 +426,24 @@ F
     # resolve_resource_reference on each in turn, causing them to
     # resolve lazy/forward references.
     def resolve_notification_references
-      @immediate_notifications.each { |n| n.resolve_resource_reference(run_context.resource_collection) }
-      @delayed_notifications.each {|n| n.resolve_resource_reference(run_context.resource_collection) }
+      run_context.immediate_notifications(self).each { |n| n.resolve_resource_reference(run_context.resource_collection) }
+      run_context.delayed_notifications(self).each {|n| n.resolve_resource_reference(run_context.resource_collection) }
     end
 
     def notifies_immediately(action, resource_spec)
-      @immediate_notifications << Notification.new(resource_spec, action, self)
+      run_context.notifies_immediately(Notification.new(resource_spec, action, self))
     end
 
     def notifies_delayed(action, resource_spec)
-      @delayed_notifications << Notification.new(resource_spec, action, self)
+      run_context.notifies_delayed(Notification.new(resource_spec, action, self))
+    end
+
+    def immediate_notifications
+      run_context.immediate_notifications(self)
+    end
+
+    def delayed_notifications
+      run_context.delayed_notifications(self)
     end
 
     def resources(*args)
@@ -310,11 +453,21 @@ F
     def subscribes(action, resources, timing=:delayed)
       resources = [resources].flatten
       resources.each do |resource|
+        if resource.is_a?(String)
+          resource = Chef::Resource.new(resource, run_context)
+        end
+        if resource.run_context.nil?
+          resource.run_context = run_context
+        end
         resource.notifies(action, self, timing)
       end
       true
     end
 
+    def validate_resource_spec!(resource_spec)
+      run_context.resource_collection.validate_lookup_spec!(resource_spec)
+    end
+
     def is(*args)
       if args.size == 1
         args.first
@@ -329,13 +482,17 @@ F
 
     def to_text
       ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS
-      text = "# Declared in #{@source_line}\n"
-      text << convert_to_snake_case(self.class.name, 'Chef::Resource') + "(\"#{name}\") do\n"
+      text = "# Declared in #{@source_line}\n\n"
+      text << self.class.dsl_name + "(\"#{name}\") do\n"
       ivars.each do |ivar|
         if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
-          text << "  #{ivar.to_s.sub(/^@/,'')}(#{value.inspect})\n"
+          value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect
+          text << "  #{ivar.to_s.sub(/^@/,'')} #{value_string}\n"
         end
       end
+      [@not_if, @only_if].flatten.each do |conditional|
+        text << "  #{conditional.to_text}\n"
+      end
       text << "end\n"
     end
 
@@ -355,7 +512,7 @@ F
       safe_ivars.each do |iv|
         instance_vars[iv.to_s.sub(/^@/, '')] = instance_variable_get(iv)
       end
-      results = {
+      {
         'json_class' => self.class.name,
         'instance_vars' => instance_vars
       }
@@ -420,17 +577,40 @@ F
     end
 
     def defined_at
+      # The following regexp should match these two sourceline formats:
+      #   /some/path/to/file.rb:80:in `wombat_tears'
+      #   C:/some/path/to/file.rb:80 in 1`wombat_tears'
+      # extracting the path to the source file and the line number.
+      (file, line_no) = source_line.match(/(.*):(\d+):?.*$/).to_a[1,2] if source_line
       if cookbook_name && recipe_name && source_line
-        "#{cookbook_name}::#{recipe_name} line #{source_line.split(':')[1]}"
+        "#{cookbook_name}::#{recipe_name} line #{line_no}"
       elsif source_line
-        file, line_no = source_line.split(':')
         "#{file} line #{line_no}"
       else
         "dynamically defined"
       end
     end
 
-    def run_action(action)
+    def cookbook_version
+      if cookbook_name
+        run_context.cookbook_collection[cookbook_name]
+      end
+    end
+
+    def events
+      run_context.events
+    end
+
+    def run_action(action, notification_type=nil, notifying_resource=nil)
+      # reset state in case of multiple actions on the same resource.
+      @elapsed_time = 0
+      start_time = Time.now
+      events.resource_action_start(self, action, notification_type, notifying_resource)
+      # Try to resolve lazy/forward references in notifications again to handle
+      # the case where the resource was defined lazily (ie. in a ruby_block)
+      resolve_notification_references
+      validate_action(action)
+
       if Chef::Config[:verbose_logging] || Chef::Log.level == :debug
         # This can be noisy
         Chef::Log.info("Processing #{self} action #{action} (#{defined_at})")
@@ -441,29 +621,50 @@ F
       updated_by_last_action(false)
 
       begin
-        return if should_skip?
-        # leverage new platform => short_name => resource
-        # which requires explicitly setting provider in
-        # resource class
-        if self.provider
-          provider = self.provider.new(self, self.run_context)
-        else # fall back to old provider resolution
-          provider = Chef::Platform.provider_for_resource(self)
-        end
-        provider.load_current_resource
-        provider.send("action_#{action}")
-      rescue => e
+        return if should_skip?(action)
+        provider_for_action(action).run_action
+      rescue Exception => e
         if ignore_failure
-          Chef::Log.error("#{self} (#{defined_at}) had an error: #{e.message}")
+          Chef::Log.error("#{self} (#{defined_at}) had an error: #{e.message}; ignore_failure is set, continuing")
+          events.resource_failed(self, action, e)
+        elsif retries > 0
+          events.resource_failed_retriable(self, action, retries, e)
+          @retries -= 1
+          Chef::Log.info("Retrying execution of #{self}, #{retries} attempt(s) left")
+          sleep retry_delay
+          retry
         else
-          Chef::Log.error("#{self} (#{defined_at}) has had an error")
-          new_exception = e.exception("#{self} (#{defined_at}) had an error: #{e.class.name}: #{e.message}")
-          new_exception.set_backtrace(e.backtrace)
-          raise new_exception
+          events.resource_failed(self, action, e)
+          raise customize_exception(e)
         end
+      ensure
+        @elapsed_time = Time.now - start_time
+        events.resource_completed(self)
       end
     end
 
+    def validate_action(action)
+      raise ArgumentError, "nil is not a valid action for resource #{self}" if action.nil?
+    end
+
+    def provider_for_action(action)
+      # leverage new platform => short_name => resource
+      # which requires explicitly setting provider in
+      # resource class
+      if self.provider
+        provider = self.provider.new(self, self.run_context)
+        provider.action = action
+        provider
+      else # fall back to old provider resolution
+        Chef::Platform.provider_for_resource(self, action)
+      end
+    end
+
+    def customize_exception(e)
+      new_exception = e.exception("#{self} (#{defined_at}) had an error: #{e.class.name}: #{e.message}")
+      new_exception.set_backtrace(e.backtrace)
+      new_exception
+    end
     # Evaluates not_if and only_if conditionals. Returns a falsey value if any
     # of the conditionals indicate that this resource should be skipped, i.e.,
     # if an only_if evaluates to false or a not_if evaluates to true.
@@ -472,14 +673,17 @@ F
     # "fails" its check. Subsequent conditionals are not evaluated, so in
     # general it's not a good idea to rely on side effects from not_if or
     # only_if commands/blocks being evaluated.
-    def should_skip?
-      conditionals = only_if + not_if
-      return false if conditionals.empty?
+    #
+    # Also skips conditional checking when the action is :nothing
+    def should_skip?(action)
+      conditional_action = ConditionalActionNotNothing.new(action)
 
+      conditionals = [ conditional_action ] + only_if + not_if
       conditionals.find do |conditional|
         if conditional.continue?
           false
         else
+          events.resource_skipped(self, action, conditional)
           Chef::Log.debug("Skipping #{self} due to #{conditional.description}")
           true
         end
@@ -512,95 +716,6 @@ F
       nil
     end
 
-    extend Chef::Mixin::ConvertToClassName
-
-    def self.attribute(attr_name, validation_opts={})
-      # This atrocity is the only way to support 1.8 and 1.9 at the same time
-      # When you're ready to drop 1.8 support, do this:
-      # define_method attr_name.to_sym do |arg=nil|
-      # etc.
-      shim_method=<<-SHIM
-      def #{attr_name}(arg=nil)
-        _set_or_return_#{attr_name}(arg)
-      end
-      SHIM
-      class_eval(shim_method)
-
-      define_method("_set_or_return_#{attr_name.to_s}".to_sym) do |arg|
-        set_or_return(attr_name.to_sym, arg, validation_opts)
-      end
-    end
-
-    def self.build_from_file(cookbook_name, filename, run_context)
-      rname = filename_to_qualified_string(cookbook_name, filename)
-
-      # Add log entry if we override an existing light-weight resource.
-      class_name = convert_to_class_name(rname)
-      overriding = Chef::Resource.const_defined?(class_name)
-      Chef::Log.info("#{class_name} light-weight resource already initialized -- overriding!") if overriding
-
-      new_resource_class = Class.new self do |cls|
-
-        # default initialize method that ensures that when initialize is finally
-        # wrapped (see below), super is called in the event that the resource
-        # definer does not implement initialize
-        def initialize(name, run_context)
-          super(name, run_context)
-        end
-
-        @actions_to_create = []
-
-        class << cls
-          include Chef::Mixin::FromFile
-
-          attr_accessor :run_context
-          attr_reader :action_to_set_default
-
-          def node
-            self.run_context.node
-          end
-
-          def actions_to_create
-            @actions_to_create
-          end
-
-          define_method(:default_action) do |action_name|
-            actions_to_create.push(action_name)
-            @action_to_set_default = action_name
-          end
-
-          define_method(:actions) do |*action_names|
-            actions_to_create.push(*action_names)
-          end
-        end
-
-        # set the run context in the class instance variable
-        cls.run_context = run_context
-
-        # load resource definition from file
-        cls.class_from_file(filename)
-
-        # create a new constructor that wraps the old one and adds the actions
-        # specified in the DSL
-        old_init = instance_method(:initialize)
-
-        define_method(:initialize) do |name, *optional_args|
-          args_run_context = optional_args.shift
-          @resource_name = rname.to_sym
-          old_init.bind(self).call(name, args_run_context)
-          @action = self.class.action_to_set_default || @action
-          allowed_actions.push(self.class.actions_to_create).flatten!
-        end
-      end
-
-      # register new class as a Chef::Resource
-      class_name = convert_to_class_name(rname)
-      Chef::Resource.const_set(class_name, new_resource_class)
-      Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
-
-      new_resource_class
-    end
-
     # Resources that want providers namespaced somewhere other than
     # Chef::Provider can set the namespace with +provider_base+
     # Ex:
diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb
index 524abbb..050cf83 100644
--- a/lib/chef/resource/apt_package.rb
+++ b/lib/chef/resource/apt_package.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -22,7 +22,7 @@ require 'chef/provider/package/apt'
 class Chef
   class Resource
     class AptPackage < Chef::Resource::Package
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :apt_package
diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb
index 374bca9..c56de5f 100644
--- a/lib/chef/resource/bash.rb
+++ b/lib/chef/resource/bash.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,7 +21,7 @@ require 'chef/resource/script'
 class Chef
   class Resource
     class Bash < Chef::Resource::Script
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :bash
diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb
new file mode 100644
index 0000000..576e6b4
--- /dev/null
+++ b/lib/chef/resource/batch.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource/windows_script'
+
+class Chef
+  class Resource
+    class Batch < Chef::Resource::WindowsScript
+
+      def initialize(name, run_context=nil)
+        super(name, run_context, :batch, "cmd.exe")
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb
new file mode 100644
index 0000000..2d78483
--- /dev/null
+++ b/lib/chef/resource/bff_package.rb
@@ -0,0 +1,36 @@
+#
+# Author:: Deepali Jagtap (<deepali.jagtap at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require '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
+        @provider = Chef::Provider::Package::Aix
+      end
+
+    end
+  end
+end
+
+
diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb
index 34aeae6..83c397b 100644
--- a/lib/chef/resource/breakpoint.rb
+++ b/lib/chef/resource/breakpoint.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -22,7 +22,7 @@ require 'chef/resource'
 class Chef
   class Resource
     class Breakpoint < Chef::Resource
-      
+
       def initialize(action="break", *args)
         @name = caller.first
         super(@name, *args)
diff --git a/lib/chef/resource/conditional.rb b/lib/chef/resource/conditional.rb
index 4e656d7..60f65e1 100644
--- a/lib/chef/resource/conditional.rb
+++ b/lib/chef/resource/conditional.rb
@@ -83,11 +83,23 @@ class Chef
         @block.call
       end
 
+      def short_description
+        @positivity
+      end
+
       def description
         cmd_or_block = @command ? "command `#{@command}`" : "ruby block"
         "#{@positivity} #{cmd_or_block}"
       end
 
+      def to_text
+        if @command
+          "#{positivity} \"#{@command}\""
+        else
+          "#{@positivity} { #code block }"
+        end
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/conditional_action_not_nothing.rb b/lib/chef/resource/conditional_action_not_nothing.rb
new file mode 100644
index 0000000..f6c34b2
--- /dev/null
+++ b/lib/chef/resource/conditional_action_not_nothing.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Xabier de Zuazo (<xabier at onddo.com>)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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 Resource
+    class ConditionalActionNotNothing
+
+      attr_reader :current_action
+
+      def initialize(current_action)
+        @current_action = current_action
+      end
+
+      def continue?
+        # @positivity == not_if
+        @current_action != :nothing
+      end
+
+      def short_description
+        description
+      end
+
+      def description
+        "action :nothing"
+      end
+
+      def to_text
+        "not_if { action == :nothing }"
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb
index 2552578..de758ae 100644
--- a/lib/chef/resource/cookbook_file.rb
+++ b/lib/chef/resource/cookbook_file.rb
@@ -1,6 +1,7 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008, 2011 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb
index 7dfb86b..dfbb91f 100644
--- a/lib/chef/resource/cron.rb
+++ b/lib/chef/resource/cron.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Bryan McLellan (btm at loftninjas.org)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2009 Bryan McLellan
 # 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.
@@ -21,7 +22,11 @@ require 'chef/resource'
 class Chef
   class Resource
     class Cron < Chef::Resource
-      
+
+      identity_attr :command
+
+      state_attrs :minute, :hour, :day, :month, :weekday, :user
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :cron
@@ -181,9 +186,9 @@ class Chef
           :kind_of => Hash
         )
       end
-      
+
       private
-      
+
       # On Ruby 1.8, Kernel#Integer will happily do this for you. On 1.9, no.
       def integerize(integerish)
         Integer(integerish)
diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb
index 6e871e8..95aa8af 100644
--- a/lib/chef/resource/csh.rb
+++ b/lib/chef/resource/csh.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,7 +21,7 @@ require 'chef/resource/script'
 class Chef
   class Resource
     class Csh < Chef::Resource::Script
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :csh
diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb
index 0993c91..76478ed 100644
--- a/lib/chef/resource/deploy.rb
+++ b/lib/chef/resource/deploy.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -51,7 +52,11 @@ class Chef
     class Deploy < Chef::Resource
 
       provider_base Chef::Provider::Deploy
-      
+
+      identity_attr :repository
+
+      state_attrs :deploy_to, :revision
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :deploy
@@ -75,6 +80,7 @@ class Chef
         @provider = Chef::Provider::Deploy::Timestamped
         @allowed_actions.push(:force_deploy, :deploy, :rollback)
         @additional_remotes = Hash[]
+        @keep_releases = 5
       end
 
       # where the checked out/cloned code goes
@@ -295,6 +301,14 @@ class Chef
         )
       end
 
+       # The number of old release directories to keep around after cleanup
+      def keep_releases(arg=nil)
+        [set_or_return(
+          :keep_releases,
+          arg,
+          :kind_of => [ Integer ]), 1].max
+      end
+
       # An array of paths, relative to your app's root, to be purged from a
       # SCM clone/checkout before symlinking. Use this to get rid of files and
       # directories you want to be shared between releases.
@@ -375,7 +389,7 @@ class Chef
         arg ||= block
         set_or_return(:after_restart, arg, :kind_of => [Proc, String])
       end
-      
+
       def additional_remotes(arg=nil)
         set_or_return(
           :additional_remotes,
@@ -384,6 +398,19 @@ class Chef
         )
       end
 
+      # FIXME The Deploy resource may be passed to an SCM provider as its
+      # resource.  The SCM provider knows that SCM resources can specify a
+      # timeout for SCM operations. The deploy resource must therefore support
+      # a timeout method, but the timeout it describes is for SCM operations,
+      # not the overall deployment. This is potentially confusing.
+      def timeout(arg=nil)
+        set_or_return(
+          :timeout,
+          arg,
+          :kind_of => Integer
+        )
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/deploy_revision.rb b/lib/chef/resource/deploy_revision.rb
index 55a3e38..ceac26e 100644
--- a/lib/chef/resource/deploy_revision.rb
+++ b/lib/chef/resource/deploy_revision.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -18,9 +18,9 @@
 
 class Chef
   class Resource
-    
+
     # Convenience class for using the deploy resource with the revision
-    # deployment strategy (provider) 
+    # deployment strategy (provider)
     class DeployRevision < Chef::Resource::Deploy
       def initialize(*args, &block)
         super
@@ -28,13 +28,13 @@ class Chef
         @provider = Chef::Provider::Deploy::Revision
       end
     end
-    
+
     class DeployBranch < Chef::Resource::DeployRevision
       def initialize(*args, &block)
         super
         @resource_name = :deploy_branch
       end
     end
-    
+
   end
 end
diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb
index 729948b..423c0bb 100644
--- a/lib/chef/resource/directory.rb
+++ b/lib/chef/resource/directory.rb
@@ -1,6 +1,7 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008, 2011 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -24,6 +25,11 @@ require 'chef/mixin/securable'
 class Chef
   class Resource
     class Directory < Chef::Resource
+
+      identity_attr :path
+
+      state_attrs :group, :mode, :owner
+
       include Chef::Mixin::Securable
 
       provides :directory, :on_platforms => :all
diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb
index 02886e8..2fb5b5c 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -22,13 +22,13 @@ require 'chef/provider/package/dpkg'
 class Chef
   class Resource
     class DpkgPackage < Chef::Resource::Package
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :dpkg_package
         @provider = Chef::Provider::Package::Dpkg
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/easy_install_package.rb
index 10e80bd..f25e1ac 100644
--- a/lib/chef/resource/easy_install_package.rb
+++ b/lib/chef/resource/easy_install_package.rb
@@ -21,7 +21,7 @@ require 'chef/resource/package'
 class Chef
   class Resource
     class EasyInstallPackage < Chef::Resource::Package
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :easy_install_package
diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/env.rb
index ed3442c..4b5fe6c 100644
--- a/lib/chef/resource/env.rb
+++ b/lib/chef/resource/env.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Doug MacEachern (<dougm at vmware.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2010 VMware, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -20,6 +21,10 @@ class Chef
   class Resource
     class Env < Chef::Resource
 
+      identity_attr :key_name
+
+      state_attrs :value
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :env
diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb
index 00d8fd1..959856a 100644
--- a/lib/chef/resource/erl_call.rb
+++ b/lib/chef/resource/erl_call.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Joe Williams (<joe at joetify.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2009 Joe Williams
 # License:: Apache License, Version 2.0
 #
@@ -23,7 +24,9 @@ class Chef
     class ErlCall < Chef::Resource
 
       # erl_call : http://erlang.org/doc/man/erl_call.html
-      
+
+      identity_attr :code
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :erl_call
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 5e86494..6c07bf9 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -22,6 +23,8 @@ class Chef
   class Resource
     class Execute < Chef::Resource
 
+      identity_attr :command
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :execute
@@ -52,7 +55,7 @@ class Chef
         set_or_return(
           :command,
           arg,
-          :kind_of => [ String ]
+          :kind_of => [ String, Array ]
         )
       end
 
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index 84de76f..676cbf2 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -18,6 +18,7 @@
 #
 
 require 'chef/resource'
+require 'chef/platform/query_helpers'
 require 'chef/provider/file'
 require 'chef/mixin/securable'
 
@@ -26,6 +27,17 @@ class Chef
     class File < Chef::Resource
       include Chef::Mixin::Securable
 
+      identity_attr :path
+
+      if Platform.windows?
+        # Use Windows rights instead of standard *nix permissions
+        state_attrs :checksum, :rights, :deny_rights
+      else
+        state_attrs :checksum, :owner, :group, :mode
+      end
+
+      attr_writer :checksum
+
       provides :file, :on_platforms => :all
 
       def initialize(name, run_context=nil)
@@ -36,8 +48,13 @@ class Chef
         @action = "create"
         @allowed_actions.push(:create, :delete, :touch, :create_if_missing)
         @provider = Chef::Provider::File
+        @atomic_update = Chef::Config[:file_atomic_update]
+        @force_unlink = false
+        @manage_symlink_source = nil
+        @diff = nil
       end
 
+
       def content(arg=nil)
         set_or_return(
           :content,
@@ -70,6 +87,38 @@ class Chef
         )
       end
 
+      def diff(arg=nil)
+        set_or_return(
+          :diff,
+          arg,
+          :kind_of => String
+        )
+      end
+
+      def atomic_update(arg=nil)
+        set_or_return(
+          :atomic_update,
+          arg,
+          :kind_of => [ TrueClass, FalseClass ]
+        )
+      end
+
+      def force_unlink(arg=nil)
+        set_or_return(
+          :force_unlink,
+          arg,
+          :kind_of => [ TrueClass, FalseClass ]
+        )
+      end
+
+      def manage_symlink_source(arg=nil)
+        set_or_return(
+          :manage_symlink_source,
+          arg,
+          :kind_of => [ TrueClass, FalseClass ]
+        )
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index 9a9a849..94286ea 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -22,13 +22,13 @@ require 'chef/provider/package/freebsd'
 class Chef
   class Resource
     class FreebsdPackage < Chef::Resource::Package
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :freebsd_package
         @provider = Chef::Provider::Package::Freebsd
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb
index c582f9b..17f14c8 100644
--- a/lib/chef/resource/group.rb
+++ b/lib/chef/resource/group.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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.
@@ -19,7 +20,11 @@
 class Chef
   class Resource
     class Group < Chef::Resource
-      
+
+      identity_attr :group_name
+
+      state_attrs :members
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :group
@@ -28,9 +33,10 @@ class Chef
         @members = []
         @action = :create
         @append = false
+        @non_unique = false
         @allowed_actions.push(:create, :remove, :modify, :manage)
       end
-      
+
       def group_name(arg=nil)
         set_or_return(
           :group_name,
@@ -38,12 +44,12 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def gid(arg=nil)
         set_or_return(
           :gid,
           arg,
-          :kind_of => [ Integer ]
+          :kind_of => [ String, Integer ]
         )
       end
 
@@ -57,7 +63,7 @@ class Chef
       end
 
       alias_method :users, :members
- 
+
       def append(arg=nil)
         set_or_return(
           :append,
@@ -73,6 +79,14 @@ class Chef
           :kind_of => [ TrueClass, FalseClass ]
         )
       end
+
+      def non_unique(arg=nil)
+        set_or_return(
+          :non_unique,
+          arg,
+          :kind_of => [ TrueClass, FalseClass ]
+        )
+      end
     end
   end
 end
diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb
index c850bb7..47f6286 100644
--- a/lib/chef/resource/http_request.rb
+++ b/lib/chef/resource/http_request.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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.
@@ -21,7 +22,9 @@ require 'chef/resource'
 class Chef
   class Resource
     class HttpRequest < Chef::Resource
-      
+
+      identity_attr :url
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :http_request
@@ -31,7 +34,7 @@ class Chef
         @headers = {}
         @allowed_actions.push(:get, :put, :post, :delete, :head, :options)
       end
-      
+
       def url(args=nil)
         set_or_return(
           :url,
@@ -39,8 +42,9 @@ class Chef
           :kind_of => String
         )
       end
-      
-      def message(args=nil)
+
+      def message(args=nil, &block)
+        args = block if block_given?
         set_or_return(
           :message,
           args,
@@ -55,7 +59,7 @@ class Chef
           :kind_of => Hash
         )
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb
index e738e46..c289dda 100644
--- a/lib/chef/resource/ifconfig.rb
+++ b/lib/chef/resource/ifconfig.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Jason K. Jackson (jasonjackson at gmail.com)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2009 Jason K. Jackson
 # 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.
@@ -21,7 +22,11 @@ require 'chef/resource'
 class Chef
   class Resource
     class Ifconfig < Chef::Resource
-      
+
+      identity_attr :device
+
+      state_attrs :inet_addr, :mask
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :ifconfig
@@ -34,7 +39,7 @@ class Chef
         @bcast = nil
         @mtu = nil
         @metric = nil
-        @device = nil 
+        @device = nil
         @onboot = nil
         @network = nil
         @bootproto = nil
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
new file mode 100644
index 0000000..88c6e9a
--- /dev/null
+++ b/lib/chef/resource/ips_package.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Jason Williams (<williamsjj at digitar.com>)
+# Copyright:: Copyright (c) 2011 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/resource/package'
+require 'chef/provider/package/ips'
+
+class Chef
+  class Resource
+    class IpsPackage < ::Chef::Resource::Package
+      def initialize(name, run_context = nil)
+        super(name, run_context)
+        @resource_name = :ips_package
+        @provider      = Chef::Provider::Package::Ips
+        @allowed_actions = [ :install, :remove, :upgrade ]
+        @accept_license = false
+      end
+
+      def accept_license(arg=nil)
+        set_or_return(
+          :purge,
+          arg,
+          :kind_of => [ TrueClass, FalseClass ]
+        )
+      end
+    end
+  end
+end
diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb
index 8032a25..e53b386 100644
--- a/lib/chef/resource/link.rb
+++ b/lib/chef/resource/link.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -24,9 +25,14 @@ class Chef
     class Link < Chef::Resource
       include Chef::Mixin::Securable
 
-       provides :link, :on_platform  => :all
+      provides :link, :on_platform  => :all
+
+      identity_attr :target_file
+
+      state_attrs :to, :owner, :group
 
       def initialize(name, run_context=nil)
+        verify_links_supported!
         super
         @resource_name = :link
         @to = nil
@@ -62,6 +68,42 @@ class Chef
         )
       end
 
+      def group(arg=nil)
+        set_or_return(
+          :group,
+          arg,
+          :regex => Chef::Config[:group_valid_regex]
+        )
+      end
+
+      def owner(arg=nil)
+        set_or_return(
+          :owner,
+          arg,
+          :regex => Chef::Config[:user_valid_regex]
+        )
+      end
+
+      # make link quack like a file (XXX: not for public consumption)
+      def path
+        @target_file
+      end
+
+      private
+      def verify_links_supported!
+        # On certain versions of windows links are not supported. Make
+        # sure we are not on such a platform.
+
+        if Chef::Platform.windows?
+          require 'chef/win32/file'
+          begin
+            Chef::ReservedNames::Win32::File.verify_links_supported!
+          rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e
+            Chef::Log.fatal("Link resource is not supported on this version of Windows")
+            raise e
+          end
+        end
+      end
     end
   end
 end
diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb
index 95f9952..391c3b5 100644
--- a/lib/chef/resource/log.rb
+++ b/lib/chef/resource/log.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Cary Penniman (<cary at rightscale.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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.
@@ -17,23 +18,25 @@
 #
 class Chef
   class Resource
-
-    # Sends a string from a recipe to a log provider
-    #
-    # log "some string to log" do
-    #   level :info  # (default)  also supports :warn, :debug, and :error
-    # end
-    #    
-    # === Example
-    # log "your string to log" 
-    #
-    # or 
-    #
-    # log "a debug string" { level :debug }
-    #  
     class Log < Chef::Resource
-      
-      # Initialize log resource with a name as the string to log 
+
+      identity_attr :message
+
+      # Sends a string from a recipe to a log provider
+      #
+      # log "some string to log" do
+      #   level :info  # (default)  also supports :warn, :debug, and :error
+      # end
+      #
+      # === Example
+      # log "your string to log"
+      #
+      # or
+      #
+      # log "a debug string" { level :debug }
+      #
+
+      # Initialize log resource with a name as the string to log
       #
       # === Parameters
       # name<String>:: Message to log
@@ -44,8 +47,18 @@ class Chef
         @resource_name = :log
         @level = :info
         @action = :write
+        @allowed_actions.push(:write)
+        @message = name
       end
-      
+
+      def message(arg=nil)
+        set_or_return(
+          :message,
+          arg,
+          :kind_of => String
+        )
+      end
+
       # <Symbol> Log level, one of :debug, :info, :warn, :error or :fatal
       def level(arg=nil)
         set_or_return(
@@ -54,9 +67,9 @@ class Chef
           :equal_to => [ :debug, :info, :warn, :error, :fatal ]
         )
       end
-      
+
     end
-  end  
+  end
 end
 
 
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
new file mode 100644
index 0000000..3309ee3
--- /dev/null
+++ b/lib/chef/resource/lwrp_base.rb
@@ -0,0 +1,132 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008-2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource'
+
+class Chef
+  class Resource
+
+    # == Chef::Resource::LWRPBase
+    # Base class for LWRP resources. Adds DSL sugar on top of Chef::Resource,
+    # so attributes, default action, etc. can be defined with pleasing syntax.
+    class LWRPBase < Resource
+
+      NULL_ARG = Object.new
+
+      extend Chef::Mixin::ConvertToClassName
+      extend Chef::Mixin::FromFile
+
+      # Evaluates the LWRP resource file and instantiates a new Resource class.
+      def self.build_from_file(cookbook_name, filename, run_context)
+        rname = filename_to_qualified_string(cookbook_name, filename)
+
+        # Add log entry if we override an existing light-weight resource.
+        class_name = convert_to_class_name(rname)
+        if Resource.strict_const_defined?(class_name)
+          old_class = Resource.send(:remove_const, class_name)
+          # CHEF-3432 -- Chef::Resource keeps a list of subclasses; need to
+          # remove old ones from the list when replacing.
+          resource_classes.delete(old_class)
+          Chef::Log.info("#{class_name} light-weight resource already initialized -- overriding!")
+        end
+
+        resource_class = Class.new(self)
+
+        resource_class.resource_name = rname
+        resource_class.run_context = run_context
+        resource_class.class_from_file(filename)
+
+        Chef::Resource.const_set(class_name, resource_class)
+        Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
+
+        resource_class
+      end
+
+      # Set the resource snake_case name. Should only be called via
+      # build_from_file.
+      def self.resource_name=(resource_name)
+        @resource_name = resource_name
+      end
+
+      # Returns the resource snake_case name
+      def self.resource_name
+        @resource_name
+      end
+
+      # Define an attribute on this resource, including optional validation
+      # parameters.
+      def self.attribute(attr_name, validation_opts={})
+        # Ruby 1.8 doesn't support default arguments to blocks, but we have to
+        # use define_method with a block to capture +validation_opts+.
+        # Workaround this by defining two methods :(
+        class_eval(<<-SHIM, __FILE__, __LINE__)
+          def #{attr_name}(arg=nil)
+            _set_or_return_#{attr_name}(arg)
+          end
+        SHIM
+
+        define_method("_set_or_return_#{attr_name.to_s}".to_sym) do |arg|
+          set_or_return(attr_name.to_sym, arg, validation_opts)
+        end
+      end
+
+      # Sets the default action
+      def self.default_action(action_name=NULL_ARG)
+        unless action_name.equal?(NULL_ARG)
+          valid_actions.push(action_name)
+          @default_action = action_name
+        end
+        @default_action
+      end
+
+      # Adds +action_names+ to the list of valid actions for this resource.
+      def self.actions(*action_names)
+        valid_actions.push(*action_names)
+      end
+
+      def self.valid_actions
+        @valid_actions ||= []
+      end
+
+      # 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
+
+      def self.run_context
+        @run_context
+      end
+
+      def self.node
+        run_context.node
+      end
+
+      # Default initializer. Sets the default action and allowed actions.
+      def initialize(name, run_context=nil)
+        super(name, run_context)
+        @resource_name = self.class.resource_name.to_sym
+        @action = self.class.default_action
+        allowed_actions.push(self.class.valid_actions).flatten!
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 911d3c1..c9434c9 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb
index 14e9c65..46a85b2 100644
--- a/lib/chef/resource/mdadm.rb
+++ b/lib/chef/resource/mdadm.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Joe Williams (<joe at joetify.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2009 Joe Williams
 # License:: Apache License, Version 2.0
 #
@@ -22,6 +23,10 @@ class Chef
   class Resource
     class Mdadm < Chef::Resource
 
+      identity_attr :raid_device
+
+      state_attrs :devices, :level, :chunk
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :mdadm
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index e9b3533..4998463 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Joshua Timberman (<joshua at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2009 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.
@@ -21,7 +22,11 @@ require 'chef/resource'
 class Chef
   class Resource
     class Mount < Chef::Resource
-      
+
+      identity_attr :device
+
+      state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :mount
@@ -37,8 +42,11 @@ class Chef
         @action = :mount
         @supports = { :remount => false }
         @allowed_actions.push(:mount, :umount, :remount, :enable, :disable)
+        @username = nil
+        @password = nil
+        @domain = nil
       end
-      
+
       def mount_point(arg=nil)
         set_or_return(
           :mount_point,
@@ -46,7 +54,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def device(arg=nil)
         set_or_return(
           :device,
@@ -54,7 +62,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def device_type(arg=nil)
         real_arg = arg.kind_of?(String) ? arg.to_sym : arg
         set_or_return(
@@ -71,7 +79,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def options(arg=nil)
         if arg.is_a?(String)
           converted_arg = arg.gsub(/,/, ' ').split(/ /)
@@ -84,7 +92,7 @@ class Chef
           :kind_of => [ Array ]
         )
       end
-      
+
       def dump(arg=nil)
         set_or_return(
           :dump,
@@ -92,7 +100,7 @@ class Chef
           :kind_of => [ Integer, FalseClass ]
         )
       end
-      
+
       def pass(arg=nil)
         set_or_return(
           :pass,
@@ -100,7 +108,7 @@ class Chef
           :kind_of => [ Integer, FalseClass ]
         )
       end
-      
+
       def mounted(arg=nil)
         set_or_return(
           :mounted,
@@ -116,7 +124,7 @@ class Chef
           :kind_of => [ TrueClass, FalseClass ]
         )
       end
-        
+
       def supports(args={})
         if args.is_a? Array
           args.each { |arg| @supports[arg] = true }
@@ -126,9 +134,32 @@ class Chef
           @supports
         end
       end
-      
+
+      def username(arg=nil)
+        set_or_return(
+          :username,
+          arg,
+          :kind_of => [ String ]
+        )
+      end
+
+      def password(arg=nil)
+        set_or_return(
+          :password,
+          arg,
+          :kind_of => [ String ]
+        )
+      end
+
+      def domain(arg=nil)
+        set_or_return(
+          :domain,
+          arg,
+          :kind_of => [ String ]
+        )
+      end
+
     end
   end
 end
 
-        
diff --git a/lib/chef/resource/ohai.rb b/lib/chef/resource/ohai.rb
index 91da1a4..b567db4 100644
--- a/lib/chef/resource/ohai.rb
+++ b/lib/chef/resource/ohai.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Michael Leinartas (<mleinartas at gmail.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2010 Michael Leinartas
 # 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.
@@ -19,10 +20,15 @@
 class Chef
   class Resource
     class Ohai < Chef::Resource
-      
+
+      identity_attr :name
+
+      state_attrs :plugin
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :ohai
+        @name = name
         @allowed_actions.push(:reload)
         @action = :reload
         @plugin = nil
@@ -35,6 +41,14 @@ class Chef
           :kind_of => [ String ]
         )
       end
+
+      def name(arg=nil)
+        set_or_return(
+          :name,
+          arg,
+          :kind_of => [ String ]
+        )
+      end
     end
   end
 end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index 3357a09..eaad3e2 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -22,17 +23,21 @@ class Chef
   class Resource
     class Package < Chef::Resource
 
+      identity_attr :package_name
+
+      state_attrs :version, :options
+
       def initialize(name, run_context=nil)
         super
-        @resource_name = :package
-        @package_name = name
-        @version = nil
+        @action = :install
+        @allowed_actions.push(:install, :upgrade, :remove, :purge, :reconfig)
         @candidate_version = nil
+        @options = nil
+        @package_name = name
+        @resource_name = :package
         @response_file = nil
         @source = nil
-        @action = :install
-        @options = nil
-        @allowed_actions.push(:install, :upgrade, :remove, :purge, :reconfig)
+        @version = nil
       end
 
       def package_name(arg=nil)
@@ -74,7 +79,6 @@ class Chef
 	  :kind_of => [ String ]
 	)
       end
-
     end
   end
 end
diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/resource/pacman_package.rb
index d66c93b..2894e41 100644
--- a/lib/chef/resource/pacman_package.rb
+++ b/lib/chef/resource/pacman_package.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,13 +21,13 @@ require 'chef/resource/package'
 class Chef
   class Resource
     class PacmanPackage < Chef::Resource::Package
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :pacman_package
         @provider = Chef::Provider::Package::Pacman
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb
index d3cf696..546f639 100644
--- a/lib/chef/resource/perl.rb
+++ b/lib/chef/resource/perl.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,7 +21,7 @@ require 'chef/resource/script'
 class Chef
   class Resource
     class Perl < Chef::Resource::Script
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :perl
diff --git a/lib/chef/resource/portage_package.rb b/lib/chef/resource/portage_package.rb
index fc72381..42c0356 100644
--- a/lib/chef/resource/portage_package.rb
+++ b/lib/chef/resource/portage_package.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,13 +21,13 @@ 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
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb
new file mode 100644
index 0000000..cbd81b1
--- /dev/null
+++ b/lib/chef/resource/powershell_script.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource/windows_script'
+
+class Chef
+  class Resource
+    class PowershellScript < Chef::Resource::WindowsScript
+
+      def initialize(name, run_context=nil)
+        super(name, run_context, :powershell_script, "powershell.exe")
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb
index 85a5348..f340afd 100644
--- a/lib/chef/resource/python.rb
+++ b/lib/chef/resource/python.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,7 +21,7 @@ require 'chef/resource/script'
 class Chef
   class Resource
     class Python < Chef::Resource::Script
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :python
diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb
new file mode 100644
index 0000000..2b5d077
--- /dev/null
+++ b/lib/chef/resource/registry_key.rb
@@ -0,0 +1,86 @@
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+#
+# Copyright:: 2011, Opscode, 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/provider/registry_key'
+require 'chef/resource'
+
+class Chef
+  class Resource
+    class RegistryKey < Chef::Resource
+
+      identity_attr :key
+      state_attrs :values
+
+      def initialize(name, run_context=nil)
+        super
+        @resource_name = :registry_key
+        @action = :create
+        @architecture = :machine
+        @recursive = false
+        @key = name
+        @values = []
+        @allowed_actions.push(:create, :create_if_missing, :delete, :delete_key)
+      end
+
+      def key(arg=nil)
+        set_or_return(
+          :key,
+          arg,
+          :kind_of => String
+        )
+      end
+      def values(arg=nil)
+        if not arg.nil?
+          if arg.is_a?(Hash)
+            @values = [ arg ]
+          elsif arg.is_a?(Array)
+            @values = arg
+          else
+            raise ArgumentError, "Bad type for RegistryKey resource, use Hash or Array"
+          end
+          @values.each do |v|
+            raise ArgumentError, "Missing name key in RegistryKey values hash" unless v.has_key?(:name)
+            raise ArgumentError, "Missing type key in RegistryKey values hash" unless v.has_key?(:type)
+            raise ArgumentError, "Missing data key in RegistryKey values hash" unless v.has_key?(:data)
+            v.each_key do |key|
+              raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless [:name,:type,:data].include?(key)
+            end
+            raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String)
+            raise Argument Error "Type of type => #{v[:name]} should be symbol" unless v[:type].is_a?(Symbol)
+          end
+        elsif self.instance_variable_defined?(:@values) == true
+          @values
+        end
+      end
+      def recursive(arg=nil)
+        set_or_return(
+          :recursive,
+          arg,
+          :kind_of => [TrueClass, FalseClass]
+        )
+      end
+      def architecture(arg=nil)
+        set_or_return(
+          :architecture,
+          arg,
+          :kind_of => Symbol
+        )
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb
index 20bbc88..0f7e0eb 100644
--- a/lib/chef/resource/remote_directory.rb
+++ b/lib/chef/resource/remote_directory.rb
@@ -1,5 +1,6 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -27,6 +28,10 @@ class Chef
 
       provides :remote_directory, :on_platforms => :all
 
+      identity_attr :path
+
+      state_attrs :files_owner, :files_group, :files_mode
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :remote_directory
@@ -46,6 +51,12 @@ class Chef
         @provider = Chef::Provider::RemoteDirectory
       end
 
+      if Chef::Platform.windows?
+        # create a second instance of the 'rights' attribute
+        rights_attribute(:files_rights)
+      end
+
+
       def source(args=nil)
         set_or_return(
           :source,
@@ -78,11 +89,6 @@ class Chef
         )
       end
 
-      if Chef::Platform.windows?
-        # create a second instance of the 'rights' attribute
-        Chef::Mixin::Securable.rights_attribute(:files_rights)
-      end
-
       def files_mode(arg=nil)
         set_or_return(
           :files_mode,
diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb
index bfbfd30..24d2562 100644
--- a/lib/chef/resource/remote_file.rb
+++ b/lib/chef/resource/remote_file.rb
@@ -32,55 +32,92 @@ class Chef
         super
         @resource_name = :remote_file
         @action = "create"
-        @source = ::File.basename(name)
-        @cookbook = nil
+        @source = []
+        @use_etag = true
+        @use_last_modified = true
+        @ftp_active_mode = false
+        @headers = {}
         @provider = Chef::Provider::RemoteFile
       end
 
-      def source(args=nil)
+      def source(*args)
+        if not args.empty?
+          args = Array(args).flatten
+          validate_source(args)
+          @source = args
+        elsif self.instance_variable_defined?(:@source) == true
+          @source
+        end
+      end
+
+      def checksum(args=nil)
         set_or_return(
-          :source,
+          :checksum,
           args,
           :kind_of => String
         )
       end
 
-      def cookbook(args=nil)
+      # Disable or enable ETag and Last Modified conditional GET. Equivalent to
+      #   use_etag(true_or_false)
+      #   use_last_modified(true_or_false)
+      def use_conditional_get(true_or_false)
+        use_etag(true_or_false)
+        use_last_modified(true_or_false)
+      end
+
+      def use_etag(args=nil)
         set_or_return(
-          :cookbook,
+          :use_etag,
           args,
-          :kind_of => String
+          :kind_of => [ TrueClass, FalseClass ]
         )
       end
 
-      def checksum(args=nil)
+      alias :use_etags :use_etag
+
+      def use_last_modified(args=nil)
         set_or_return(
-          :checksum,
+          :use_last_modified,
           args,
-          :kind_of => String
+          :kind_of => [ TrueClass, FalseClass ]
         )
       end
 
-      # The provider that should be used for this resource.
-      # === Returns:
-      # Chef::Provider::RemoteFile    when the source is an absolute URI, like
-      #                               http://www.google.com/robots.txt
-      # Chef::Provider::CookbookFile  when the source is a relative URI, like
-      #                               'myscript.pl', 'dir/config.conf'
-      def provider
-        if absolute_uri?(source)
-          Chef::Provider::RemoteFile
-        else
-          Chef::Log.warn("remote_file is deprecated for fetching files from cookbooks. Use cookbook_file instead")
-          Chef::Log.warn("From #{self.to_s} on #{source_line}")
-          Chef::Provider::CookbookFile
-        end
+      def ftp_active_mode(args=nil)
+        set_or_return(
+          :ftp_active_mode,
+          args,
+          :kind_of => [ TrueClass, FalseClass ]
+        )
+      end
+
+      def headers(args=nil)
+        set_or_return(
+          :headers,
+          args,
+          :kind_of => Hash
+        )
+      end
+
+      def after_created
+        validate_source(@source)
       end
 
       private
 
+      def validate_source(source)
+        raise ArgumentError, "#{resource_name} has an empty source" if source.empty?
+        source.each do |src|
+          unless absolute_uri?(src)
+            raise Exceptions::InvalidRemoteFileURI,
+              "#{src.inspect} is not a valid `source` parameter for #{resource_name}. `source` must be an absolute URI or an array of URIs."
+          end
+        end
+      end
+
       def absolute_uri?(source)
-        URI.parse(source).absolute?
+        source.kind_of?(String) and URI.parse(source).absolute?
       rescue URI::InvalidURIError
         false
       end
diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb
index 1a568dd..942905d 100644
--- a/lib/chef/resource/route.rb
+++ b/lib/chef/resource/route.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Bryan McLellan (btm at loftninjas.org)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2009 Bryan McLellan
 # 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.
@@ -21,17 +22,21 @@ require 'chef/resource'
 class Chef
   class Resource
     class Route < Chef::Resource
-      
+
+      identity_attr :target
+
+      state_attrs :netmask, :gateway
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :route
         @target = name
-        @action = :add
+        @action = [:add]
         @allowed_actions.push(:add, :delete)
         @netmask = nil
         @gateway = nil
         @metric = nil
-        @device = nil 
+        @device = nil
         @route_type = :host
         @networking = nil
         @networking_ipv6 = nil
diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb
index 7ab1202..200a963 100644
--- a/lib/chef/resource/rpm_package.rb
+++ b/lib/chef/resource/rpm_package.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb
index 7617839..605d27b 100644
--- a/lib/chef/resource/ruby.rb
+++ b/lib/chef/resource/ruby.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,7 +21,7 @@ require 'chef/resource/script'
 class Chef
   class Resource
     class Ruby < Chef::Resource::Script
-      
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :ruby
diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb
index b204185..d9b8954 100644
--- a/lib/chef/resource/ruby_block.rb
+++ b/lib/chef/resource/ruby_block.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -20,12 +20,15 @@
 class Chef
   class Resource
     class RubyBlock < Chef::Resource
-      
+
+      identity_attr :block_name
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :ruby_block
-        @action = "create"
-        @allowed_actions.push(:create)
+        @action = "run"
+        @allowed_actions << :create << :run
+        @block_name = name
       end
 
       def block(&block)
@@ -35,6 +38,14 @@ class Chef
           @block
         end
       end
+
+      def block_name(arg=nil)
+        set_or_return(
+          :block_name,
+          arg,
+          :kind_of => String
+        )
+      end
     end
   end
 end
diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb
index 132dad6..d9a3729 100644
--- a/lib/chef/resource/scm.rb
+++ b/lib/chef/resource/scm.rb
@@ -23,6 +23,10 @@ class Chef
   class Resource
     class Scm < Chef::Resource
 
+      identity_attr :destination
+
+      state_attrs :revision
+
       def initialize(name, run_context=nil)
         super
         @destination = name
@@ -142,6 +146,14 @@ class Chef
         )
       end
 
+      def timeout(arg=nil)
+        set_or_return(
+          :timeout,
+          arg,
+          :kind_of => Integer
+        )
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index e6a8e56..8cc9c6f 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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.
@@ -21,7 +22,9 @@ require 'chef/resource/execute'
 class Chef
   class Resource
     class Script < Chef::Resource::Execute
-      
+
+      identity_attr :command
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :script
@@ -30,7 +33,7 @@ class Chef
         @interpreter = nil
         @flags = nil
       end
-      
+
       def code(arg=nil)
         set_or_return(
           :code,
@@ -38,7 +41,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def interpreter(arg=nil)
         set_or_return(
           :interpreter,
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index fdbde35..befa4be 100644
--- a/lib/chef/resource/service.rb
+++ b/lib/chef/resource/service.rb
@@ -1,14 +1,15 @@
 #
 # Author:: AJ Christensen (<aj at hjksolutions.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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.
@@ -21,7 +22,11 @@ require 'chef/resource'
 class Chef
   class Resource
     class Service < Chef::Resource
-      
+
+      identity_attr :service_name
+
+      state_attrs :enabled, :running
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :service
@@ -35,13 +40,14 @@ class Chef
         @status_command = nil
         @restart_command = nil
         @reload_command = nil
+        @init_command = nil
         @priority = nil
         @action = "nothing"
         @startup_type = :automatic
         @supports = { :restart => false, :reload => false, :status => false }
         @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload)
       end
-      
+
       def service_name(arg=nil)
         set_or_return(
           :service_name,
@@ -49,7 +55,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       # regex for match against ps -ef when !supports[:has_status] && status == nil
       def pattern(arg=nil)
         set_or_return(
@@ -103,6 +109,19 @@ class Chef
         )
       end
 
+      # The path to the init script associated with the service. On many
+      # distributions this is '/etc/init.d/SERVICE_NAME' by default. In
+      # non-standard configurations setting this value will save having to
+      # specify overrides for the start_command, stop_command and
+      # restart_command attributes.
+      def init_command(arg=nil)
+        set_or_return(
+          :init_command,
+          arg,
+          :kind_of => [ String ]
+        )
+      end
+
       # if the service is enabled or not
       def enabled(arg=nil)
         set_or_return(
diff --git a/lib/chef/resource/smartos_package.rb b/lib/chef/resource/smartos_package.rb
index 315481b..0f4f6d8 100644
--- a/lib/chef/resource/smartos_package.rb
+++ b/lib/chef/resource/smartos_package.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,16 +21,18 @@ require 'chef/provider/package/smartos'
 
 class Chef
   class Resource
-    class SmartOSPackage < Chef::Resource::Package
-        
+    class SmartosPackage < Chef::Resource::Package
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :smartos_package
         @provider = Chef::Provider::Package::SmartOS
       end
-      
+
     end
   end
 end
 
-
+# Backwards compatability
+# @todo remove in Chef 12
+Chef::Resource::SmartOSPackage = Chef::Resource::SmartosPackage
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index becf023..3513703 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Toomas Pelberg (<toomasp at gmx.net>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Author:: Prabhu Das (<prabhu.das at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
-# 
+#
 #     http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,13 +23,13 @@ require 'chef/provider/package/solaris'
 class Chef
   class Resource
     class SolarisPackage < Chef::Resource::Package
-        
-      def initialize(name, collection=nil, node=nil)
-        super(name, collection, node)
+
+      def initialize(name, run_context=nil)
+        super
         @resource_name = :solaris_package
         @provider = Chef::Provider::Package::Solaris
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb
index 97f48c3..04fec9b 100644
--- a/lib/chef/resource/subversion.rb
+++ b/lib/chef/resource/subversion.rb
@@ -1,14 +1,15 @@
 #
 # Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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.
@@ -21,7 +22,7 @@ require "chef/resource/scm"
 class Chef
   class Resource
     class Subversion < Chef::Resource::Scm
-      
+
       def initialize(name, run_context=nil)
         super
         @svn_arguments = '--no-auth-cache'
@@ -30,7 +31,7 @@ class Chef
         @provider = Chef::Provider::Subversion
         allowed_actions << :force_export
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 26472bf..9cba6f1 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -1,6 +1,7 @@
 #
 # Author:: Adam Jacob (<adam at opscode.com>)
 # Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
 # Copyright:: Copyright (c) 2008, 2011 Opscode, Inc.
 # License:: Apache License, Version 2.0
 #
@@ -28,6 +29,9 @@ class Chef
 
       provides :template, :on_platforms => :all
 
+      attr_reader :inline_helper_blocks
+      attr_reader :inline_helper_modules
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :template
@@ -37,6 +41,9 @@ class Chef
         @local = false
         @variables = Hash.new
         @provider = Chef::Provider::Template
+        @inline_helper_blocks = {}
+        @inline_helper_modules = []
+        @helper_modules = []
       end
 
       def source(file=nil)
@@ -71,6 +78,144 @@ class Chef
         )
       end
 
+      # Declares a helper method to be defined in the template context when
+      # rendering.
+      #
+      # === Example:
+      #
+      # ==== Basic usage:
+      # Given the following helper:
+      #   helper(:static_value) { "hello from helper" }
+      # A template with the following code:
+      #   <%= static_value %>
+      # Will render as;
+      #   hello from helper
+      #
+      # ==== Referencing Instance Variables:
+      # Any instance variables available to the template can be referenced in
+      # the method body. For example, you can simplify accessing app-specific
+      # node attributes like this:
+      #   helper(:app) { @node[:my_app_attributes] }
+      # And use it in a template like this:
+      #   <%= app[:listen_ports] %>
+      # This is equivalent to the non-helper template code:
+      #   <%= @node[:my_app_attributes][:listen_ports] %>
+      #
+      # ==== Method Arguments:
+      # Helper methods can also take arguments. The syntax available for
+      # argument specification will be dependent on ruby version. Ruby 1.8 only
+      # supports a subset of the argument specification syntax available for
+      # method definition, whereas 1.9 supports the full syntax.
+      #
+      # Continuing the above example of simplifying attribute access, we can
+      # define a helper to look up app-specific attributes like this:
+      #   helper(:app) { |setting| @node[:my_app_attributes][setting] }
+      # The template can then look up attributes like this:
+      #   <%= app(:listen_ports) %>
+      def helper(method_name, &block)
+        unless block_given?
+          raise Exceptions::ValidationFailed,
+            "`helper(:method)` requires a block argument (e.g., `helper(:method) { code }`)"
+        end
+
+        unless method_name.kind_of?(Symbol)
+          raise Exceptions::ValidationFailed,
+            "method_name argument to `helper(method_name)` must be a symbol (e.g., `helper(:method) { code }`)"
+        end
+
+        @inline_helper_blocks[method_name] = block
+      end
+
+      # Declares a module to define helper methods in the template's context
+      # when rendering. There are two primary forms.
+      #
+      # === Inline Module Definition
+      # When a block is given, the block is used to define a module which is
+      # then mixed in to the template context w/ `extend`.
+      #
+      # ==== Inline Module Example
+      # Given the following code in the template resource:
+      #   helpers do
+      #     # Add "syntax sugar" for referencing app-specific attributes
+      #     def app(attribute)
+      #       @node[:my_app_attributes][attribute]
+      #     end
+      #   end
+      # You can use it in the template like so:
+      #   <%= app(:listen_ports) %>
+      # Which is equivalent to:
+      #   <%= @node[:my_app_attributes][:listen_ports] %>
+      #
+      # === External Module Form
+      # When a module name is given, the template context will be extended with
+      # that module. This is the recommended way to customize template contexts
+      # when you need to define more than an handful of helper functions (but
+      # also try to keep your template helpers from getting out of hand--if you
+      # have very complex logic in your template helpers, you should further
+      # extract your code into separate libraries).
+      #
+      # ==== External Module Example
+      # To extract the above inline module code to a library, you'd create a
+      # library file like this:
+      #   module MyTemplateHelper
+      #     # Add "syntax sugar" for referencing app-specific attributes
+      #     def app(attribute)
+      #       @node[:my_app_attributes][attribute]
+      #     end
+      #   end
+      # And in the template resource:
+      #   helpers(MyTemplateHelper)
+      # The template code in the above example will work unmodified.
+      def helpers(module_name=nil,&block)
+        if block_given? and !module_name.nil?
+          raise Exceptions::ValidationFailed,
+            "Passing both a module and block to #helpers is not supported. Call #helpers multiple times instead"
+        elsif block_given?
+          @inline_helper_modules << block
+        elsif module_name.kind_of?(::Module)
+          @helper_modules << module_name
+        elsif module_name.nil?
+          raise Exceptions::ValidationFailed,
+            "#helpers requires either a module name or inline module code as a block.\n" +
+            "e.g.: helpers do; helper_code; end;\n" +
+            "OR: helpers(MyHelpersModule)"
+        else
+          raise Exceptions::ValidationFailed,
+            "Argument to #helpers must be a module. You gave #{module_name.inspect} (#{module_name.class})"
+        end
+      end
+
+      # Compiles all helpers from inline method definitions, inline module
+      # definitions, and external modules into an Array of Modules. The context
+      # object for the template is extended with these modules to provide
+      # per-resource template logic.
+      def helper_modules
+        compiled_helper_methods + compiled_helper_modules + @helper_modules
+      end
+
+      private
+
+      # compiles helper methods into a module that can be included in template context
+      def compiled_helper_methods
+        if inline_helper_blocks.empty?
+          []
+        else
+          resource_helper_blocks = inline_helper_blocks
+          helper_mod = Module.new do
+            resource_helper_blocks.each do |method_name, method_body|
+              define_method(method_name, &method_body)
+            end
+          end
+          [ helper_mod ]
+        end
+      end
+
+      def compiled_helper_modules
+        @inline_helper_modules.map do |module_body|
+          Module.new(&module_body)
+        end
+      end
+
     end
   end
 end
diff --git a/lib/chef/resource/timestamped_deploy.rb b/lib/chef/resource/timestamped_deploy.rb
index d89274b..4032ae9 100644
--- a/lib/chef/resource/timestamped_deploy.rb
+++ b/lib/chef/resource/timestamped_deploy.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -18,9 +18,9 @@
 
 class Chef
   class Resource
-    
+
     # Convenience class for using the deploy resource with the timestamped
-    # deployment strategy (provider) 
+    # deployment strategy (provider)
     class TimestampedDeploy < Chef::Resource::Deploy
       def initialize(*args, &block)
         super(*args, &block)
diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb
index 2a8941e..357d6d1 100644
--- a/lib/chef/resource/user.rb
+++ b/lib/chef/resource/user.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,7 +21,11 @@ require 'chef/resource'
 class Chef
   class Resource
     class User < Chef::Resource
-      
+
+      identity_attr :username
+
+      state_attrs :uid, :gid, :home
+
       def initialize(name, run_context=nil)
         super
         @resource_name = :user
@@ -36,13 +40,13 @@ class Chef
         @manage_home = false
         @non_unique = false
         @action = :create
-        @supports = { 
+        @supports = {
           :manage_home => false,
           :non_unique => false
         }
         @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
       end
-      
+
       def username(arg=nil)
         set_or_return(
           :username,
@@ -50,7 +54,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def comment(arg=nil)
         set_or_return(
           :comment,
@@ -58,7 +62,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def uid(arg=nil)
         set_or_return(
           :uid,
@@ -66,7 +70,7 @@ class Chef
           :kind_of => [ String, Integer ]
         )
       end
-      
+
       def gid(arg=nil)
         set_or_return(
           :gid,
@@ -74,9 +78,9 @@ class Chef
           :kind_of => [ String, Integer ]
         )
       end
-      
+
       alias_method :group, :gid
-      
+
       def home(arg=nil)
         set_or_return(
           :home,
@@ -84,7 +88,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def shell(arg=nil)
         set_or_return(
           :shell,
@@ -92,7 +96,7 @@ class Chef
           :kind_of => [ String ]
         )
       end
-      
+
       def password(arg=nil)
         set_or_return(
           :password,
@@ -124,7 +128,7 @@ class Chef
           :kind_of => [ TrueClass, FalseClass ]
         )
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb
new file mode 100644
index 0000000..2b563f5
--- /dev/null
+++ b/lib/chef/resource/windows_script.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource/script'
+require 'chef/mixin/windows_architecture_helper'
+
+class Chef
+  class Resource
+    class WindowsScript < Chef::Resource::Script
+
+      protected
+
+      def initialize(name, run_context, resource_name, interpreter_command)
+        super(name, run_context)
+        @interpreter = interpreter_command
+        @resource_name = resource_name
+      end
+
+      include Chef::Mixin::WindowsArchitectureHelper
+
+      public
+
+      def architecture(arg=nil)
+        assert_architecture_compatible!(arg) if ! arg.nil?
+        result = set_or_return(
+          :architecture,
+          arg,
+          :kind_of => Symbol
+        )
+      end
+
+      protected
+
+      def assert_architecture_compatible!(desired_architecture)
+        if ! node_supports_windows_architecture?(node, desired_architecture)
+          raise Chef::Exceptions::Win32ArchitectureIncorrect,
+          "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'"
+        end
+      end
+
+      def node
+        run_context && run_context.node
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index bcb1f65..dff70bc 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index 51858c5..a528a18 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -7,9 +7,9 @@
 # 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.
@@ -23,7 +23,15 @@ require 'chef/resource_collection/stepable_iterator'
 class Chef
   class ResourceCollection
     include Enumerable
-    
+
+    # Matches a multiple resource lookup specification,
+    # e.g., "service[nginx,unicorn]"
+    MULTIPLE_RESOURCE_MATCH = /^(.+)\[(.+?),(.+)\]$/
+
+    # Matches a single resource lookup specification,
+    # e.g., "service[nginx]"
+    SINGLE_RESOURCE_MATCH = /^(.+)\[(.+)\]$/
+
     attr_reader :iterator
 
     def initialize
@@ -31,18 +39,18 @@ class Chef
       @resources_by_name = Hash.new
       @insert_after_idx = nil
     end
-    
+
     def all_resources
       @resources
     end
-    
+
     def [](index)
       @resources[index]
     end
-    
+
     def []=(index, arg)
       is_chef_resource(arg)
-      @resources[index] = arg 
+      @resources[index] = arg
       @resources_by_name[arg.to_s] = index
     end
 
@@ -50,10 +58,14 @@ class Chef
       args.flatten.each do |a|
         is_chef_resource(a)
         @resources << a
-        @resources_by_name[a.to_s] = @resources.length - 1 
+        @resources_by_name[a.to_s] = @resources.length - 1
       end
+      self
     end
 
+    # 'push' is an alias method to <<
+    alias_method :push, :<<
+
     def insert(resource)
       is_chef_resource(resource)
       if @insert_after_idx
@@ -67,20 +79,12 @@ class Chef
         end
         @resources_by_name[resource.to_s] = @insert_after_idx + 1
         @insert_after_idx += 1
-      else  
+      else
         @resources << resource
         @resources_by_name[resource.to_s] = @resources.length - 1
       end
     end
-    
-    def push(*args)
-      args.flatten.each do |arg|
-        is_chef_resource(arg)
-        @resources.push(arg)
-        @resources_by_name[arg.to_s] = @resources.length - 1
-      end
-    end
-  
+
     def each
       @resources.each do |resource|
         yield resource
@@ -94,13 +98,17 @@ class Chef
         yield resource
       end
     end
-    
+
     def each_index
       @resources.each_index do |i|
         yield i
       end
     end
-    
+
+    def empty?
+      @resources.empty?
+    end
+
     def lookup(resource)
       lookup_by = nil
       if resource.kind_of?(Chef::Resource)
@@ -125,7 +133,7 @@ class Chef
     # find("file[foobar]", "file[baz]")
     # find("file[foobar,baz]")
     #
-    # Returns the matching resource, or an Array of matching resources. 
+    # Returns the matching resource, or an Array of matching resources.
     #
     # Raises an ArgumentError if you feed it bad lookup information
     # Raises a Runtime Error if it can't find the resources you are looking for.
@@ -145,12 +153,42 @@ class Chef
       flat_results = results.flatten
       flat_results.length == 1 ? flat_results[0] : flat_results
     end
-    
+
     # resources is a poorly named, but we have to maintain it for back
     # compat.
     alias_method :resources, :find
-    
-    # Serialize this object as a hash 
+
+
+    # Returns true if +query_object+ is a valid string for looking up a
+    # resource, or raises InvalidResourceSpecification if not.
+    # === Arguments
+    # * query_object should be a string of the form
+    # "resource_type[resource_name]", a single element Hash (e.g., :service =>
+    # "apache2"), or a Chef::Resource (this is the happy path). Other arguments
+    # will raise an exception.
+    # === Returns
+    # * true returns true for all valid input.
+    # === Raises
+    # * Chef::Exceptions::InvalidResourceSpecification for all invalid input.
+    def validate_lookup_spec!(query_object)
+      case query_object
+      when Chef::Resource
+        true
+      when SINGLE_RESOURCE_MATCH, MULTIPLE_RESOURCE_MATCH
+        true
+      when Hash
+        true
+      when String
+        raise Chef::Exceptions::InvalidResourceSpecification,
+          "The string `#{query_object}' is not valid for resource collection lookup. Correct syntax is `resource_type[resource_name]'"
+      else
+        raise Chef::Exceptions::InvalidResourceSpecification,
+          "The object `#{query_object.inspect}' is not valid for resource collection lookup. " +
+          "Use a String like `resource_type[resource_name]' or a Chef::Resource object"
+      end
+    end
+
+    # Serialize this object as a hash
     def to_json(*a)
       instance_vars = Hash.new
       self.instance_variables.each do |iv|
@@ -162,7 +200,7 @@ class Chef
       }
       results.to_json(*a)
     end
-    
+
     def self.json_create(o)
       collection = self.new()
       o["instance_vars"].each do |k,v|
@@ -172,7 +210,7 @@ class Chef
     end
 
     private
-    
+
       def find_resource_by_hash(arg)
         results = Array.new
         arg.each do |resource_name, name_list|
@@ -188,28 +226,28 @@ class Chef
       def find_resource_by_string(arg)
         results = Array.new
         case arg
-        when /^(.+)\[(.+?),(.+)\]$/
+        when MULTIPLE_RESOURCE_MATCH
           resource_type = $1
           arg =~ /^.+\[(.+)\]$/
           resource_list = $1
           resource_list.split(",").each do |name|
-            resource_name = "#{resource_type}[#{name}]" 
+            resource_name = "#{resource_type}[#{name}]"
             results << lookup(resource_name)
           end
-        when /^(.+)\[(.+)\]$/
+        when SINGLE_RESOURCE_MATCH
           resource_type = $1
           name = $2
           resource_name = "#{resource_type}[#{name}]"
           results << lookup(resource_name)
         else
-          raise ArgumentError, "You must have a string like resource_type[name]!"
+          raise ArgumentError, "Bad string format #{arg}, you must have a string like resource_type[name]!"
         end
         return results
       end
 
       def is_chef_resource(arg)
         unless arg.kind_of?(Chef::Resource)
-          raise ArgumentError, "Members must be Chef::Resource's" 
+          raise ArgumentError, "Members must be Chef::Resource's"
         end
         true
       end
diff --git a/lib/chef/resource_collection/stepable_iterator.rb b/lib/chef/resource_collection/stepable_iterator.rb
index ec1e244..4d5fc1f 100644
--- a/lib/chef/resource_collection/stepable_iterator.rb
+++ b/lib/chef/resource_collection/stepable_iterator.rb
@@ -5,9 +5,9 @@
 # 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.
@@ -18,94 +18,94 @@
 class Chef
   class ResourceCollection
     class StepableIterator
-      
+
       def self.for_collection(new_collection)
         instance = new(new_collection)
         instance
       end
-      
+
       attr_accessor :collection
       attr_reader :position
-      
+
       def initialize(collection=[])
         @position = 0
         @paused = false
         @collection = collection
       end
-      
+
       def size
         collection.size
       end
-      
+
       def each(&block)
         reset_iteration(block)
         @iterator_type = :element
         iterate
       end
-      
+
       def each_index(&block)
         reset_iteration(block)
         @iterator_type = :index
         iterate
       end
-      
+
       def each_with_index(&block)
         reset_iteration(block)
         @iterator_type = :element_with_index
         iterate
       end
-      
+
       def paused?
         @paused
       end
-      
+
       def pause
         @paused = true
       end
-      
+
       def resume
         @paused = false
         iterate
       end
-      
+
       def rewind
         @position = 0
       end
-      
+
       def skip_back(skips=1)
         @position -= skips
       end
-      
+
       def skip_forward(skips=1)
         @position += skips
       end
-      
+
       def step
         return nil if @position == size
         call_iterator_block
         @position += 1
       end
-      
+
       def iterate_on(iteration_type, &block)
         @iterator_type = iteration_type
         @iterator_block = block
       end
-      
+
       private
-      
+
       def reset_iteration(iterator_block)
         @iterator_block = iterator_block
         @position = 0
         @paused = false
       end
-      
+
       def iterate
         while @position < size && !paused?
           step
         end
         collection
       end
-      
+
       def call_iterator_block
         case @iterator_type
         when :element
@@ -118,7 +118,7 @@ class Chef
           raise "42error: someone forgot to set @iterator_type, wtf?"
         end
       end
-      
+
     end
   end
 end
diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb
index a0160c5..278114e 100644
--- a/lib/chef/resource_definition.rb
+++ b/lib/chef/resource_definition.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -21,19 +21,19 @@ require 'chef/mixin/params_validate'
 
 class Chef
   class ResourceDefinition
-    
+
     include Chef::Mixin::FromFile
     include Chef::Mixin::ParamsValidate
-    
+
     attr_accessor :name, :params, :recipe, :node
-    
+
     def initialize(node=nil)
       @name = nil
       @params = Hash.new
       @recipe = nil
       @node = node
     end
-    
+
     def define(resource_name, prototype_params=nil, &block)
       unless resource_name.kind_of?(Symbol)
         raise ArgumentError, "You must use a symbol when defining a new resource!"
@@ -52,14 +52,14 @@ class Chef
       end
       true
     end
-    
+
     # When we do the resource definition, we're really just setting new values for
     # the paramaters we prototyped at the top.  This method missing is as simple as
     # it gets.
     def method_missing(symbol, *args)
       @params[symbol] = args.length == 1 ? args[0] : args
     end
-    
+
     def to_s
       "#{name.to_s}"
     end
diff --git a/lib/chef/resource_definition_list.rb b/lib/chef/resource_definition_list.rb
index b958624..5501409 100644
--- a/lib/chef/resource_definition_list.rb
+++ b/lib/chef/resource_definition_list.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
new file mode 100644
index 0000000..d299490
--- /dev/null
+++ b/lib/chef/resource_reporter.rb
@@ -0,0 +1,316 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Prajakta Purohit (prajakta at opscode.com>)
+# Auther:: Tyler Cloke (<tyler 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 'uri'
+require 'chef/monkey_patches/securerandom'
+require 'chef/event_dispatch/base'
+
+class Chef
+  class ResourceReporter < EventDispatch::Base
+
+    class ResourceReport < Struct.new(:new_resource,
+                                      :current_resource,
+                                      :action,
+                                      :exception,
+                                      :elapsed_time)
+
+      def self.new_with_current_state(new_resource, action, current_resource)
+        report = new
+        report.new_resource = new_resource
+        report.action = action
+        report.current_resource = current_resource
+        report
+      end
+
+      def self.new_for_exception(new_resource, action)
+        report = new
+        report.new_resource = new_resource
+        report.action = action
+        report
+      end
+
+      def for_json
+        as_hash = {}
+        as_hash["type"]   = new_resource.class.dsl_name
+        as_hash["name"]   = new_resource.name
+        as_hash["id"]     = new_resource.identity
+        as_hash["after"]  = state(new_resource)
+        as_hash["before"] = current_resource ? state(current_resource) : {}
+        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?
+
+        # TODO: rename as "action"
+        as_hash["result"] = action.to_s
+        if success?
+        else
+          #as_hash["result"] = "failed"
+        end
+        if new_resource.cookbook_name
+          as_hash["cookbook_name"] = new_resource.cookbook_name
+          as_hash["cookbook_version"] = new_resource.cookbook_version.version
+        end
+
+        as_hash
+      end
+
+      def finish
+        self.elapsed_time = new_resource.elapsed_time
+      end
+
+      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
+    attr_reader :status
+    attr_reader :exception
+    attr_reader :run_id
+    attr_reader :error_descriptions
+
+    PROTOCOL_VERSION = '0.1.0'
+
+    def initialize(rest_client)
+      if Chef::Config[:enable_reporting] && !Chef::Config[:why_run]
+        @reporting_enabled = true
+      else
+        @reporting_enabled = false
+      end
+      @updated_resources = []
+      @total_res_count = 0
+      @pending_update  = nil
+      @status = "success"
+      @exception = nil
+      @run_id = SecureRandom.uuid
+      @rest_client = rest_client
+      @error_descriptions = {}
+    end
+
+    def run_started(run_status)
+      @run_status = run_status
+
+      if reporting_enabled?
+        begin
+          resource_history_url = "reports/nodes/#{node_name}/runs"
+          server_response = @rest_client.post_rest(resource_history_url, {:action => :start, :run_id => @run_id,
+                                                                          :start_time => start_time.to_s}, headers)
+        rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
+          handle_error_starting_run(e, resource_history_url)
+        end
+      end
+    end
+
+    def handle_error_starting_run(e, url)
+      message = "Reporting error starting run. URL: #{url} "
+      code = if e.response.code
+               e.response.code.to_s
+             else
+               "Exception Code Empty"
+             end
+
+      if !e.response || (code != "404" && code != "406")
+        exception = "Exception: #{code} "
+        if Chef::Config[:enable_reporting_url_fatals]
+          reporting_status = "Reporting fatals enabled. Aborting run. "
+          Chef::Log.error(message + exception + reporting_status)
+          raise
+        else
+          reporting_status = "Disabling reporting for run."
+          Chef::Log.info(message + exception + reporting_status)
+        end
+      else
+        reason = "Received #{code}. "
+        if code == "406"
+          reporting_status = "Client version not supported. Please update the client. Disabling reporting for run."
+          Chef::Log.info(message + reason + reporting_status)
+        else
+          reporting_status = "Disabling reporting for run."
+          Chef::Log.debug(message + reason + reporting_status)
+        end
+      end
+
+      @reporting_enabled = false
+    end
+
+    def resource_current_state_loaded(new_resource, action, current_resource)
+      unless nested_resource?(new_resource)
+        @pending_update = ResourceReport.new_with_current_state(new_resource, action, current_resource)
+      end
+    end
+
+    def resource_up_to_date(new_resource, action)
+      @total_res_count += 1
+      @pending_update = nil unless nested_resource?(new_resource)
+    end
+
+    def resource_skipped(resource, action, conditional)
+      @total_res_count += 1
+      @pending_update = nil unless nested_resource?(resource)
+    end
+
+    def resource_updated(new_resource, action)
+      @total_res_count += 1
+    end
+
+    def resource_failed(new_resource, action, exception)
+      @total_res_count += 1
+      unless nested_resource?(new_resource)
+        @pending_update ||= ResourceReport.new_for_exception(new_resource, action)
+        @pending_update.exception = exception
+      end
+      description = Formatters::ErrorMapper.resource_failed(new_resource, action, exception)
+      @error_descriptions = description.for_json
+    end
+
+    def resource_completed(new_resource)
+      if @pending_update && !nested_resource?(new_resource)
+        @pending_update.finish
+        @updated_resources << @pending_update
+        @pending_update = nil
+      end
+    end
+
+    def run_completed(node)
+      @status = "success"
+      post_reporting_data
+    end
+
+    def run_failed(exception)
+      @exception = exception
+      @status = "failure"
+      # 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
+      end
+    end
+
+    def post_reporting_data
+      if reporting_enabled?
+        run_data = prepare_run_data
+        resource_history_url = "reports/nodes/#{node_name}/runs/#{@run_id}"
+        Chef::Log.info("Sending resource update report (run-id: #{@run_id})")
+        Chef::Log.debug run_data.inspect
+        compressed_data = encode_gzip(run_data.to_json)
+        begin
+          Chef::Log.debug("Sending compressed run data...")
+          # Since we're posting compressed data we can not directly call post_rest which expects JSON
+          reporting_url = @rest_client.create_url(resource_history_url)
+          @rest_client.raw_http_request(:POST, reporting_url, headers({'Content-Encoding' => 'gzip'}), compressed_data)
+        rescue Net::HTTPServerException => e
+          if e.response.code.to_s == "400"
+            Chef::FileCache.store("failed-reporting-data.json", Chef::JSONCompat.to_json_pretty(run_data), 0640)
+            Chef::Log.error("Failed to post reporting data to server (HTTP 400), saving to #{Chef::FileCache.load("failed-reporting-data.json", false)}")
+          else
+            Chef::Log.error("Failed to post reporting data to server (HTTP #{e.response.code.to_s})")
+          end
+        end
+      else
+        Chef::Log.debug("Server doesn't support resource history, skipping resource report.")
+      end
+    end
+
+    def headers(additional_headers = {})
+      options = {'X-Ops-Reporting-Protocol-Version' => PROTOCOL_VERSION}
+      options.merge(additional_headers)
+    end
+
+    def node_name
+      @run_status.node.name
+    end
+
+    def start_time
+      @run_status.start_time
+    end
+
+    def end_time
+      @run_status.end_time
+    end
+
+    def prepare_run_data
+      run_data = {}
+      run_data["action"] = "end"
+      run_data["resources"] = updated_resources.map do |resource_record|
+        resource_record.for_json
+      end
+      run_data["status"] = @status
+      run_data["run_list"] = @run_status.node.run_list.to_json
+      run_data["total_res_count"] = @total_res_count.to_s
+      run_data["data"] = {}
+      run_data["start_time"] = start_time.to_s
+      run_data["end_time"] = end_time.to_s
+
+      if exception
+        exception_data = {}
+        exception_data["class"] = exception.inspect
+        exception_data["message"] = exception.message
+        exception_data["backtrace"] = exception.backtrace.to_json
+        exception_data["description"] =  @error_descriptions
+        run_data["data"]["exception"] = exception_data
+      end
+      run_data
+    end
+
+    def run_list_expand_failed(node, exception)
+      description = Formatters::ErrorMapper.run_list_expand_failed(node, exception)
+      @error_descriptions = description.for_json
+    end
+
+    def cookbook_resolution_failed(expanded_run_list, exception)
+      description = Formatters::ErrorMapper.cookbook_resolution_failed(expanded_run_list, exception)
+      @error_descriptions = description.for_json
+    end
+
+    def cookbook_sync_failed(cookbooks, exception)
+      description = Formatters::ErrorMapper.cookbook_sync_failed(cookbooks, exception)
+      @error_descriptions = description.for_json
+    end
+
+    def reporting_enabled?
+      @reporting_enabled
+    end
+
+    private
+
+    # If we are getting messages about a resource while we are in the middle of
+    # another resource's update, we assume that the nested resource is just the
+    # implementation of a provider, and we want to hide it from the reporting
+    # output.
+    def nested_resource?(new_resource)
+      @pending_update && @pending_update.new_resource != new_resource
+    end
+
+    def encode_gzip(data)
+      "".tap do |out|
+        Zlib::GzipWriter.wrap(StringIO.new(out)){|gz| gz << data }
+      end
+    end
+
+  end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index e751e75..d0a27d8 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -18,6 +18,7 @@
 
 require 'chef/resource/apt_package'
 require 'chef/resource/bash'
+require 'chef/resource/batch'
 require 'chef/resource/breakpoint'
 require 'chef/resource/cookbook_file'
 require 'chef/resource/chef_gem'
@@ -33,6 +34,7 @@ require 'chef/resource/erl_call'
 require 'chef/resource/execute'
 require 'chef/resource/file'
 require 'chef/resource/freebsd_package'
+require 'chef/resource/ips_package'
 require 'chef/resource/gem_package'
 require 'chef/resource/git'
 require 'chef/resource/group'
@@ -48,10 +50,13 @@ require 'chef/resource/package'
 require 'chef/resource/pacman_package'
 require 'chef/resource/perl'
 require 'chef/resource/portage_package'
+require 'chef/resource/powershell_script'
 require 'chef/resource/python'
+require 'chef/resource/registry_key'
 require 'chef/resource/remote_directory'
 require 'chef/resource/remote_file'
 require 'chef/resource/rpm_package'
+require 'chef/resource/solaris_package'
 require 'chef/resource/route'
 require 'chef/resource/ruby'
 require 'chef/resource/ruby_block'
@@ -64,3 +69,5 @@ require 'chef/resource/template'
 require 'chef/resource/timestamped_deploy'
 require 'chef/resource/user'
 require 'chef/resource/yum_package'
+require 'chef/resource/lwrp_base'
+require 'chef/resource/bff_package'
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index 0f1a18f..04ee0b0 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -20,99 +20,73 @@
 # limitations under the License.
 #
 
-require 'zlib'
-require 'net/https'
-require 'uri'
-require 'chef/json_compat'
 require 'tempfile'
-require 'chef/rest/auth_credentials'
-require 'chef/rest/rest_request'
-require 'chef/monkey_patches/string'
+require 'chef/http'
+class Chef
+  class HTTP; end
+  class REST < HTTP; end
+end
+
+require 'chef/http/authenticator'
+require 'chef/http/decompressor'
+require 'chef/http/json_input'
+require 'chef/http/json_to_model_output'
+require 'chef/http/cookie_manager'
 require 'chef/config'
+require 'chef/exceptions'
+require 'chef/platform/query_helpers'
 
 class Chef
   # == Chef::REST
   # Chef's custom REST client with built-in JSON support and RSA signed header
   # authentication.
-  class REST
+  class REST < HTTP
 
-    class NoopInflater
-      def inflate(chunk)
-        chunk
-      end
-    end
+    # Backwards compatibility for things that use
+    # Chef::REST::RESTRequest or its constants
+    RESTRequest = HTTP::HTTPRequest
 
-    attr_reader :auth_credentials
     attr_accessor :url, :cookies, :sign_on_redirect, :redirect_limit
 
-    CONTENT_ENCODING  = "content-encoding".freeze
-    GZIP              = "gzip".freeze
-    DEFLATE           = "deflate".freeze
-    IDENTITY          = "identity".freeze
+    attr_reader :authenticator
 
     # Create a REST client object. The supplied +url+ is used as the base for
     # all subsequent requests. For example, when initialized with a base url
     # http://localhost:4000, a call to +get_rest+ with 'nodes' will make an
     # HTTP GET request to http://localhost:4000/nodes
     def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={})
-      @url = url
-      @cookies = CookieJar.instance
-      @default_headers = options[:headers] || {}
-      @auth_credentials = AuthCredentials.new(client_name, signing_key_filename)
-      @sign_on_redirect, @sign_request = true, true
-      @redirects_followed = 0
-      @redirect_limit = 10
-      @disable_gzip = false
-      handle_options(options)
+      options[:client_name] = client_name
+      options[:signing_key_filename] = signing_key_filename
+      super(url, options)
+
+      @decompressor = Decompressor.new(options)
+      @authenticator = Authenticator.new(options)
+
+      @middlewares << JSONInput.new(options)
+      @middlewares << JSONToModelOutput.new(options)
+      @middlewares << CookieManager.new(options)
+      @middlewares << @decompressor
+      @middlewares << @authenticator
     end
 
     def signing_key_filename
-      @auth_credentials.key_file
+      authenticator.signing_key_filename
+    end
+
+    def auth_credentials
+      authenticator.auth_credentials
     end
 
     def client_name
-      @auth_credentials.client_name
+      authenticator.client_name
     end
 
     def signing_key
-      @auth_credentials.raw_key
+      authenticator.raw_key
     end
 
-    # Register the client
-    #--
-    # Requires you to load chef/api_client beforehand. explicit require is removed since
-    # most users of this class have no need for chef/api_client. This functionality
-    # should be moved anyway...
-    def register(name=Chef::Config[:node_name], destination=Chef::Config[:client_key])
-      if (File.exists?(destination) &&  !File.writable?(destination))
-        raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?"
-      end
-      nc = Chef::ApiClient.new
-      nc.name(name)
-
-      catch(:done) do
-        retries = config[:client_registration_retries] || 5
-        0.upto(retries) do |n|
-          begin
-            response = nc.save(true, true)
-            Chef::Log.debug("Registration response: #{response.inspect}")
-            raise Chef::Exceptions::CannotWritePrivateKey, "The response from the server did not include a private key!" unless response.has_key?("private_key")
-            # Write out the private key
-            ::File.open(destination, "w") {|f|
-              f.chmod(0600)
-              f.print(response["private_key"])
-            }
-            throw :done
-          rescue IOError
-            raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination}"
-          rescue Net::HTTPFatalError => e
-            Chef::Log.warn("Failed attempt #{n} of #{retries+1} on client creation")
-            raise unless e.response.code == "500"
-          end
-        end
-      end
-
-      true
+    def sign_requests?
+      authenticator.sign_requests?
     end
 
     # Send an HTTP GET request to the path
@@ -123,28 +97,21 @@ class Chef
     # path:: The path to GET
     # raw:: Whether you want the raw body returned, or JSON inflated.  Defaults
     #   to JSON inflated.
-    def get_rest(path, raw=false, headers={})
+    def get(path, raw=false, headers={})
       if raw
-        streaming_request(create_url(path), headers)
+        streaming_request(path, headers)
       else
-        api_request(:GET, create_url(path), headers)
+        request(:GET, path, headers)
       end
     end
 
-    # Send an HTTP DELETE request to the path
-    def delete_rest(path, headers={})
-      api_request(:DELETE, create_url(path), headers)
-    end
+    alias :get_rest :get
 
-    # Send an HTTP POST request to the path
-    def post_rest(path, json, headers={})
-      api_request(:POST, create_url(path), headers, json)
-    end
+    alias :delete_rest :delete
 
-    # Send an HTTP PUT request to the path
-    def put_rest(path, json, headers={})
-      api_request(:PUT, create_url(path), headers, json)
-    end
+    alias :post_rest :post
+
+    alias :put_rest :put
 
     # Streams a download to a tempfile, then yields the tempfile to a block.
     # After the download, the tempfile will be closed and unlinked.
@@ -155,321 +122,76 @@ class Chef
       streaming_request(create_url(path), headers) {|tmp_file| yield tmp_file }
     end
 
-    def create_url(path)
-      if path =~ /^(http|https):\/\//
-        URI.parse(path)
-      else
-        URI.parse("#{@url}/#{path}")
-      end
-    end
-
-    def sign_requests?
-      auth_credentials.sign_requests? && @sign_request
-    end
-
-    # ==== DEPRECATED
-    # Use +api_request+ instead
-    #--
-    # Actually run an HTTP request.  First argument is the HTTP method,
-    # which should be one of :GET, :PUT, :POST or :DELETE.  Next is the
-    # URL, then an object to include in the body (which will be converted with
-    # .to_json). The limit argument is unused, it is present for backwards
-    # compatibility. Configure the redirect limit with #redirect_limit=
-    # instead.
-    #
-    # Typically, you won't use this method -- instead, you'll use one of
-    # the helper methods (get_rest, post_rest, etc.)
-    #
-    # Will return the body of the response on success.
-    def run_request(method, url, headers={}, data=false, limit=nil, raw=false)
-      json_body = data ? Chef::JSONCompat.to_json(data) : nil
-      # Force encoding to binary to fix SSL related EOFErrors
-      # cf. http://tickets.opscode.com/browse/CHEF-2363
-      # http://redmine.ruby-lang.org/issues/5233
-      json_body.force_encoding(Encoding::BINARY) if json_body.respond_to?(:force_encoding)
-      headers = build_headers(method, url, headers, json_body, raw)
-
-      tf, response_body = nil, nil
-
-      retriable_rest_request(method, url, json_body, headers) do |rest_request|
-
-        res = rest_request.call do |response|
-          if raw
-            tf = stream_to_tempfile(url, response)
-          else
-            response_body = decompress_body(response)
-          end
-        end
-
-        case res
-        when Net::HTTPSuccess
-          if res['content-type'] =~ /json/
-            Chef::JSONCompat.from_json(response_body)
-          else
-            if method == :HEAD
-              true
-            elsif raw
-              tf
-            else
-              response_body
-            end
-          end
-        when Net::HTTPNotModified # Must be tested before Net::HTTPRedirection because it's subclass.
-          false
-        when Net::HTTPRedirection
-          follow_redirect {run_request(method, create_url(res['location']), headers, false, nil, raw)}
-        else
-          if res['content-type'] =~ /json/
-            exception = Chef::JSONCompat.from_json(response_body)
-            msg = "HTTP Request Returned #{res.code} #{res.message}: "
-            msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s)
-            Chef::Log.warn(msg)
-          end
-          res.error!
-        end
-      end
-    end
+    alias :api_request :request
 
-    # Runs an HTTP request to a JSON API. File Download not supported.
-    def api_request(method, url, headers={}, data=false)
-      json_body = data ? Chef::JSONCompat.to_json(data) : nil
-      # Force encoding to binary to fix SSL related EOFErrors
-      # cf. http://tickets.opscode.com/browse/CHEF-2363
-      # http://redmine.ruby-lang.org/issues/5233
-      json_body.force_encoding(Encoding::BINARY) if json_body.respond_to?(:force_encoding)
-      headers = build_headers(method, url, headers, json_body)
-
-      retriable_rest_request(method, url, json_body, headers) do |rest_request|
-        response = rest_request.call {|r| r.read_body}
-
-        response_body = decompress_body(response)
-
-        if response.kind_of?(Net::HTTPSuccess)
-          if response['content-type'] =~ /json/
-            Chef::JSONCompat.from_json(response_body.chomp)
-          else
-            Chef::Log.warn("Expected JSON response, but got content-type '#{response['content-type']}'")
-            response_body
-          end
-        elsif redirect_location = redirected_to(response)
-          follow_redirect {api_request(:GET, create_url(redirect_location))}
-        else
-          # have to decompress the body before making an exception for it. But the body could be nil.
-          response.body.replace(decompress_body(response)) if response.body.respond_to?(:replace)
-
-          if response['content-type'] =~ /json/
-            exception = Chef::JSONCompat.from_json(response_body)
-            msg = "HTTP Request Returned #{response.code} #{response.message}: "
-            msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s)
-            Chef::Log.info(msg)
-          end
-          response.error!
-        end
-      end
-    end
+    # Do a HTTP request where no middleware is loaded (e.g. JSON input/output
+    # conversion) but the standard Chef Authentication headers are added to the
+    # request.
+    def raw_http_request(method, path, headers, data)
+      url = create_url(path)
+      method, url, headers, data = @authenticator.handle_request(method, url, headers, data)
 
-    def decompress_body(response)
-      if gzip_disabled?
-        response.body
-      else
-        case response[CONTENT_ENCODING]
-        when GZIP
-          Chef::Log.debug "decompressing gzip response"
-          Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body)
-        when DEFLATE
-          Chef::Log.debug "decompressing deflate response"
-          Zlib::Inflate.inflate(response.body)
-        else
-          response.body
-        end
-      end
-    end
+      response, rest_request, return_value = send_http_request(method, url, headers, data)
+      response.error! unless success_response?(response)
+      return_value
+    rescue Exception => exception
+      log_failed_request(response, return_value) unless response.nil?
 
-    # Makes a streaming download request. <b>Doesn't speak JSON.</b>
-    # Streams the response body to a tempfile. If a block is given, it's
-    # passed to Tempfile.open(), which means that the tempfile will automatically
-    # be unlinked after the block is executed.
-    #
-    # If no block is given, the tempfile is returned, which means it's up to
-    # you to unlink the tempfile when you're done with it.
-    def streaming_request(url, headers, &block)
-      headers = build_headers(:GET, url, headers, nil, true)
-      retriable_rest_request(:GET, url, nil, headers) do |rest_request|
-        tempfile = nil
-        response = rest_request.call do |r|
-          if block_given? && r.kind_of?(Net::HTTPSuccess)
-            begin
-              tempfile = stream_to_tempfile(url, r, &block)
-              yield tempfile
-            ensure
-              tempfile.close!
-            end
-          else
-            tempfile = stream_to_tempfile(url, r)
-          end
-        end
-        if response.kind_of?(Net::HTTPSuccess)
-          tempfile
-        elsif redirect_location = redirected_to(response)
-          # TODO: test tempfile unlinked when following redirects.
-          tempfile && tempfile.close!
-          follow_redirect {streaming_request(create_url(redirect_location), {}, &block)}
-        else
-          tempfile && tempfile.close!
-          response.error!
-        end
+      if exception.respond_to?(:chef_rest_request=)
+        exception.chef_rest_request = rest_request
       end
+      raise
     end
 
-    def retriable_rest_request(method, url, req_body, headers)
-      rest_request = Chef::REST::RESTRequest.new(method, url, req_body, headers)
+    # Deprecated:
+    # Responsibilities of this method have been split up. The #http_client is
+    # now responsible for making individual requests, while
+    # #retrying_http_errors handles error/retry logic.
+    def retriable_http_request(method, url, req_body, headers)
+      rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers)
 
       Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
 
-      http_attempts = 0
-
-      begin
-        http_attempts += 1
-
-        res = yield rest_request
-
-      rescue SocketError, Errno::ETIMEDOUT => e
-        e.message.replace "Error connecting to #{url} - #{e.message}"
-        raise e
-      rescue Errno::ECONNREFUSED
-        if http_retry_count - http_attempts + 1 > 0
-          Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}")
-          sleep(http_retry_delay)
-          retry
-        end
-        raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up"
-      rescue Timeout::Error
-        if http_retry_count - http_attempts + 1 > 0
-          Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}")
-          sleep(http_retry_delay)
-          retry
-        end
-        raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up"
-      rescue Net::HTTPFatalError => e
-        if http_retry_count - http_attempts + 1 > 0
-          sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts)
-          Chef::Log.error("Server returned error for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s")
-          sleep(sleep_time)
-          retry
-        end
-        raise
+      retrying_http_errors(url) do
+        yield rest_request
       end
     end
 
-    def authentication_headers(method, url, json_body=nil)
-      request_params = {:http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}"}
-      request_params[:body] ||= ""
-      auth_credentials.signature_headers(request_params)
-    end
-
-    def http_retry_delay
-      config[:http_retry_delay]
-    end
-
-    def http_retry_count
-      config[:http_retry_count]
+    # Customized streaming behavior; sets the accepted content type to "*/*"
+    # if not otherwise specified for compatibility purposes
+    def streaming_request(url, headers, &block)
+      headers["Accept"] ||= "*/*"
+      super
     end
 
-    def config
-      Chef::Config
-    end
+    alias :retriable_rest_request :retriable_http_request
 
     def follow_redirect
-      raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit
-      @redirects_followed += 1
-      Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}")
-      if @sign_on_redirect
-        yield
-      else
-        @sign_request = false
-        yield
+      unless @sign_on_redirect
+        @authenticator.sign_request = false
       end
+      super
     ensure
-      @redirects_followed = 0
-      @sign_request = true
+      @authenticator.sign_request = true
     end
 
-    private
-
-    def redirected_to(response)
-      return nil  unless response.kind_of?(Net::HTTPRedirection)
-      # Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this
-      return nil  if response.kind_of?(Net::HTTPNotModified)
-      response['location']
-    end
+    public :create_url
 
-    def build_headers(method, url, headers={}, json_body=false, raw=false)
-      headers                 = @default_headers.merge(headers)
-      #headers['Accept']       = "application/json" unless raw
-      headers['Accept']       = "application/json" unless raw
-      headers["Content-Type"] = 'application/json' if json_body
-      headers['Content-Length'] = json_body.bytesize.to_s if json_body
-      headers[RESTRequest::ACCEPT_ENCODING] = RESTRequest::ENCODING_GZIP_DEFLATE unless gzip_disabled?
-      headers.merge!(authentication_headers(method, url, json_body)) if sign_requests?
-      headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers]
-      headers
+    def http_client(base_url=nil)
+      base_url ||= url
+      BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
     end
 
-    def stream_to_tempfile(url, response)
-      tf = Tempfile.open("chef-rest")
-      if Chef::Platform.windows?
-        tf.binmode #required for binary files on Windows platforms
-      end
-      Chef::Log.debug("Streaming download from #{url.to_s} to tempfile #{tf.path}")
-      # Stolen from http://www.ruby-forum.com/topic/166423
-      # Kudos to _why!
-      size, total = 0, response.header['Content-Length'].to_i
-
-      inflater = if gzip_disabled?
-        NoopInflater.new
-      else
-        case response[CONTENT_ENCODING]
-        when GZIP
-          Chef::Log.debug "decompressing gzip stream"
-          Zlib::Inflate.new(Zlib::MAX_WBITS + 16)
-        when DEFLATE
-          Chef::Log.debug "decompressing inflate stream"
-          Zlib::Inflate.new
-        else
-          NoopInflater.new
-        end
-      end
-
-      response.read_body do |chunk|
-        tf.write(inflater.inflate(chunk))
-        size += chunk.size
-      end
-      tf.close
-      tf
-    rescue Exception
-      tf.close!
-      raise
-    end
+    ############################################################################
+    # DEPRECATED
+    ############################################################################
 
-    # gzip is disabled using the disable_gzip => true option in the
-    # constructor. When gzip is disabled, no 'Accept-Encoding' header will be
-    # set, and the response will not be decompressed, no matter what the
-    # Content-Encoding header of the response is. The intended use case for
-    # this is to work around situations where you request +file.tar.gz+, but
-    # the server responds with a content type of tar and a content encoding of
-    # gzip, tricking the client into decompressing the response so you end up
-    # with a tar archive (no gzip) named file.tar.gz
-    def gzip_disabled?
-      @disable_gzip
+    def decompress_body(body)
+      @decompressor.decompress_body(body)
     end
 
-    def handle_options(opts)
-      opts.each do |name, value|
-        case name.to_s
-        when 'disable_gzip'
-          @disable_gzip = value
-        end
-      end
+    def authentication_headers(method, url, json_body=nil)
+      authenticator.authentication_headers(method, url, json_body)
     end
 
   end
diff --git a/lib/chef/rest/auth_credentials.rb b/lib/chef/rest/auth_credentials.rb
deleted file mode 100644
index ec93410..0000000
--- a/lib/chef/rest/auth_credentials.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Thom May (<thom at clearairturbulence.org>)
-# Author:: Nuo Yan (<nuo at opscode.com>)
-# Author:: Christopher Brown (<cb at opscode.com>)
-# Author:: Christopher Walters (<cw at opscode.com>)
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-require 'chef/exceptions'
-require 'chef/log'
-require 'mixlib/authentication/signedheaderauth'
-
-class Chef
-  class REST
-    class AuthCredentials
-      attr_reader :key_file, :client_name, :key, :raw_key
-
-      def initialize(client_name=nil, key_file=nil)
-        @client_name, @key_file = client_name, key_file
-        load_signing_key if sign_requests?
-      end
-
-      def sign_requests?
-        !!key_file
-      end
-
-      def signature_headers(request_params={})
-        raise ArgumentError, "Cannot sign the request without a client name, check that :node_name is assigned" if client_name.nil?
-        Chef::Log.debug("Signing the request as #{client_name}")
-
-        # params_in = {:http_method => :GET, :path => "/clients", :body => "", :host => "localhost"}
-        request_params             = request_params.dup
-        request_params[:timestamp] = Time.now.utc.iso8601
-        request_params[:user_id]   = client_name
-        host = request_params.delete(:host) || "localhost"
-
-        sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(request_params)
-        signed =  sign_obj.sign(key).merge({:host => host})
-        signed.inject({}){|memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo}
-      end
-
-      private
-
-      def load_signing_key
-        @raw_key = IO.read(key_file).strip
-        @key = OpenSSL::PKey::RSA.new(@raw_key)
-      rescue SystemCallError, IOError => e
-        Chef::Log.warn "Failed to read the private key #{key_file}: #{e.inspect}"
-        raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!"
-      rescue OpenSSL::PKey::RSAError
-        msg = "The file #{key_file} does not contain a correctly formatted private key.\n"
-        msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'"
-        raise Chef::Exceptions::InvalidPrivateKey, msg
-      end
-
-    end
-  end
-end
diff --git a/lib/chef/rest/cookie_jar.rb b/lib/chef/rest/cookie_jar.rb
deleted file mode 100644
index e313770..0000000
--- a/lib/chef/rest/cookie_jar.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Thom May (<thom at clearairturbulence.org>)
-# Author:: Nuo Yan (<nuo at opscode.com>)
-# Author:: Christopher Brown (<cb at opscode.com>)
-# Author:: Christopher Walters (<cw at opscode.com>)
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2009, 2010 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 'singleton'
-
-class Chef
-  class REST
-    class CookieJar < Hash
-      include Singleton
-    end
-  end
-end
diff --git a/lib/chef/rest/rest_request.rb b/lib/chef/rest/rest_request.rb
deleted file mode 100644
index 4ff0016..0000000
--- a/lib/chef/rest/rest_request.rb
+++ /dev/null
@@ -1,229 +0,0 @@
-#--
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Thom May (<thom at clearairturbulence.org>)
-# Author:: Nuo Yan (<nuo at opscode.com>)
-# Author:: Christopher Brown (<cb at opscode.com>)
-# Author:: Christopher Walters (<cw at opscode.com>)
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2009, 2010 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 'uri'
-require 'net/http'
-require 'chef/rest/cookie_jar'
-
-# To load faster, we only want ohai's version string.
-# However, in ohai before 0.6.0, the version is defined
-# in ohai, not ohai/version
-begin
-  require 'ohai/version' #used in user agent string.
-rescue LoadError
-  require 'ohai'
-end
-
-require 'chef/version'
-
-class Chef
-  class REST
-    class RESTRequest
-
-      engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
-
-      UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)"
-      DEFAULT_UA = "Chef Client" << UA_COMMON
-
-      USER_AGENT = "User-Agent".freeze
-
-      ACCEPT_ENCODING = "Accept-Encoding".freeze
-      ENCODING_GZIP_DEFLATE = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3".freeze
-
-      GET     = "get".freeze
-      PUT     = "put".freeze
-      POST    = "post".freeze
-      DELETE  = "delete".freeze
-      HEAD    = "head".freeze
-
-      HTTPS = "https".freeze
-
-      SLASH = "/".freeze
-
-      def self.user_agent=(ua)
-        @user_agent = ua
-      end
-
-      def self.user_agent
-        @user_agent ||= DEFAULT_UA
-      end
-
-      attr_reader :method, :url, :headers, :http_client, :http_request
-
-      def initialize(method, url, req_body, base_headers={})
-        @method, @url = method, url
-        @request_body = nil
-        @cookies = CookieJar.instance
-        configure_http_client
-        build_headers(base_headers)
-        configure_http_request(req_body)
-      end
-
-      def host
-        @url.host
-      end
-
-      def port
-        @url.port
-      end
-
-      def query
-        @url.query
-      end
-
-      def path
-        @url.path.empty? ? SLASH : @url.path
-      end
-
-      def call
-        hide_net_http_bug do
-          http_client.request(http_request) do |response|
-            store_cookie(response)
-            yield response if block_given?
-            response
-          end
-        end
-      end
-
-      def config
-        Chef::Config
-      end
-
-      private
-
-      def hide_net_http_bug
-        yield
-      rescue NoMethodError => e
-        # http://redmine.ruby-lang.org/issues/show/2708
-        # http://redmine.ruby-lang.org/issues/show/2758
-        if e.to_s =~ /#{Regexp.escape(%q|undefined method `closed?' for nil:NilClass|)}/
-          Chef::Log.debug("Rescued error in http connect, re-raising as Errno::ECONNREFUSED to hide bug in net/http")
-          Chef::Log.debug("#{e.class.name}: #{e.to_s}")
-          Chef::Log.debug(e.backtrace.join("\n"))
-          raise Errno::ECONNREFUSED, "Connection refused attempting to contact #{url.scheme}://#{host}:#{port}"
-        else
-          raise
-        end
-      end
-
-      def store_cookie(response)
-        if response['set-cookie']
-          @cookies["#{host}:#{port}"] = response['set-cookie']
-        end
-      end
-
-      def build_headers(headers)
-        @headers = headers.dup
-        # TODO: need to set accept somewhere else
-        # headers.merge!('Accept' => "application/json") unless raw
-        @headers['X-Chef-Version'] = ::Chef::VERSION
-        @headers[ACCEPT_ENCODING] = ENCODING_GZIP_DEFLATE
-
-        if @cookies.has_key?("#{host}:#{port}")
-          @headers['Cookie'] = @cookies["#{host}:#{port}"]
-        end
-      end
-
-      #adapted from buildr/lib/buildr/core/transports.rb
-      def proxy_uri
-        proxy = Chef::Config["#{url.scheme}_proxy"]
-        proxy = URI.parse(proxy) if String === proxy
-        excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact
-        excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
-        return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
-      end
-
-      def configure_http_client
-        http_proxy = proxy_uri
-        if http_proxy.nil?
-          @http_client = Net::HTTP.new(host, port)
-        else
-          Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
-          user = Chef::Config["#{url.scheme}_proxy_user"]
-          pass = Chef::Config["#{url.scheme}_proxy_pass"]
-          @http_client = Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass).new(host, port)
-        end
-        if url.scheme == HTTPS
-          @http_client.use_ssl = true
-          if config[:ssl_verify_mode] == :verify_none
-            @http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE
-          elsif config[:ssl_verify_mode] == :verify_peer
-            @http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER
-          end
-          if config[:ssl_ca_path]
-            unless ::File.exist?(config[:ssl_ca_path])
-              raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist"
-            end
-            @http_client.ca_path = config[:ssl_ca_path]
-          elsif config[:ssl_ca_file]
-            unless ::File.exist?(config[:ssl_ca_file])
-              raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist"
-            end
-            @http_client.ca_file = config[:ssl_ca_file]
-          end
-          if (config[:ssl_client_cert] || config[:ssl_client_key])
-            unless (config[:ssl_client_cert] && config[:ssl_client_key])
-              raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together"
-            end
-            unless ::File.exists?(config[:ssl_client_cert])
-              raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist"
-            end
-            unless ::File.exists?(config[:ssl_client_key])
-              raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist"
-            end
-            @http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert]))
-            @http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key]))
-          end
-        end
-
-        @http_client.read_timeout = config[:rest_timeout]
-      end
-
-
-      def configure_http_request(request_body=nil)
-        req_path = "#{path}"
-        req_path << "?#{query}" if query
-
-        @http_request = case method.to_s.downcase
-        when GET
-          Net::HTTP::Get.new(req_path, headers)
-        when POST
-          Net::HTTP::Post.new(req_path, headers)
-        when PUT
-          Net::HTTP::Put.new(req_path, headers)
-        when DELETE
-          Net::HTTP::Delete.new(req_path, headers)
-        when HEAD
-          Net::HTTP::Head.new(req_path, headers)
-        else
-          raise ArgumentError, "You must provide :GET, :PUT, :POST, :DELETE or :HEAD as the method"
-        end
-
-        @http_request.body = request_body if (request_body && @http_request.request_body_permitted?)
-        # Optionally handle HTTP Basic Authentication
-        @http_request.basic_auth(url.user, url.password) if url.user
-        @http_request[USER_AGENT] = self.class.user_agent
-      end
-
-    end
-  end
-end
diff --git a/lib/chef/role.rb b/lib/chef/role.rb
index c428472..6ad58b8 100644
--- a/lib/chef/role.rb
+++ b/lib/chef/role.rb
@@ -21,9 +21,7 @@
 require 'chef/config'
 require 'chef/mixin/params_validate'
 require 'chef/mixin/from_file'
-require 'chef/couchdb'
 require 'chef/run_list'
-require 'chef/index_queue'
 require 'chef/mash'
 require 'chef/json_compat'
 require 'chef/search/query'
@@ -33,51 +31,14 @@ class Chef
 
     include Chef::Mixin::FromFile
     include Chef::Mixin::ParamsValidate
-    include Chef::IndexQueue::Indexable
-
-    DESIGN_DOCUMENT = {
-      "version" => 6,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "role") {
-              emit(doc.name, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "role") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        }
-      }
-    }
-
-    attr_accessor :couchdb_rev, :couchdb
-    attr_reader :couchdb_id
 
     # Create a new Chef::Role object.
-    def initialize(couchdb=nil)
+    def initialize
       @name = ''
       @description = ''
       @default_attributes = Mash.new
       @override_attributes = Mash.new
       @env_run_lists = {"_default" => Chef::RunList.new}
-      @couchdb_rev = nil
-      @couchdb_id = nil
-      @couchdb = couchdb || Chef::CouchDB.new
-    end
-
-    def couchdb_id=(value)
-      @couchdb_id = value
-      self.index_id = value
     end
 
     def chef_server_rest
@@ -177,7 +138,6 @@ class Chef
           accumulator
         end
       }
-      result["_rev"] = couchdb_rev if couchdb_rev
       result
     end
 
@@ -214,20 +174,9 @@ class Chef
       end
       role.env_run_lists(env_run_list_hash)
 
-      role.couchdb_rev = o["_rev"] if o.has_key?("_rev")
-      role.index_id = role.couchdb_id
-      role.couchdb_id = o["_id"] if o.has_key?("_id")
       role
     end
 
-    # List all the Chef::Role objects in the CouchDB.  If inflate is set to true, you will get
-    # the full list of all Roles, fully inflated.
-    def self.cdb_list(inflate=false, couchdb=nil)
-      rs = (couchdb || Chef::CouchDB.new).list("roles", inflate)
-      lookup = (inflate ? "value" : "key")
-      rs["rows"].collect { |r| r[lookup] }
-    end
-
     # Get the list of all roles from the API.
     def self.list(inflate=false)
       if inflate
@@ -241,24 +190,11 @@ class Chef
       end
     end
 
-    # Load a role by name from CouchDB
-    def self.cdb_load(name, couchdb=nil)
-      (couchdb || Chef::CouchDB.new).load("role", name)
-    end
-
     # Load a role by name from the API
     def self.load(name)
       chef_server_rest.get_rest("roles/#{name}")
     end
 
-    def self.exists?(rolename, couchdb)
-      begin
-        self.cdb_load(rolename, couchdb)
-      rescue Chef::Exceptions::CouchDBNotFound
-        nil
-      end
-    end
-
     def environment(env_name)
       chef_server_rest.get_rest("roles/#{@name}/environments/#{env_name}")
     end
@@ -267,21 +203,11 @@ class Chef
       chef_server_rest.get_rest("roles/#{@name}/environments")
     end
 
-    # Remove this role from the CouchDB
-    def cdb_destroy
-      couchdb.delete("role", @name, couchdb_rev)
-    end
-
     # Remove this role via the REST API
     def destroy
       chef_server_rest.delete_rest("roles/#{@name}")
     end
 
-    # Save this role to the CouchDB
-    def cdb_save
-      self.couchdb_rev = couchdb.store("role", @name, self)["rev"]
-    end
-
     # Save this role via the REST API
     def save
       begin
@@ -299,11 +225,6 @@ class Chef
       self
     end
 
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("roles", DESIGN_DOCUMENT)
-    end
-
     # As a string
     def to_s
       "role[#{@name}]"
@@ -312,37 +233,24 @@ class Chef
     # Load a role from disk - prefers to load the JSON, but will happily load
     # the raw rb files as well.
     def self.from_disk(name, force=nil)
-      js_file = File.join(Chef::Config[:role_path], "#{name}.json")
-      rb_file = File.join(Chef::Config[:role_path], "#{name}.rb")
-
-      if File.exists?(js_file) || force == "json"
-        # from_json returns object.class => json_class in the JSON.
-        Chef::JSONCompat.from_json(IO.read(js_file))
-      elsif File.exists?(rb_file) || force == "ruby"
-        role = Chef::Role.new
-        role.name(name)
-        role.from_file(rb_file)
-        role
-      else
-        raise Chef::Exceptions::RoleNotFound, "Role '#{name}' could not be loaded from disk"
-      end
-    end
-
-    # Sync all the json roles with couchdb from disk
-    def self.sync_from_disk_to_couchdb
-      Dir[File.join(Chef::Config[:role_path], "*.json")].each do |role_file|
-        short_name = File.basename(role_file, ".json")
-        Chef::Log.warn("Loading #{short_name}")
-        r = Chef::Role.from_disk(short_name, "json")
-        begin
-          couch_role = Chef::Role.cdb_load(short_name)
-          r.couchdb_rev = couch_role.couchdb_rev
-          Chef::Log.debug("Replacing role #{short_name} with data from #{role_file}")
-        rescue Chef::Exceptions::CouchDBNotFound
-          Chef::Log.debug("Creating role #{short_name} with data from #{role_file}")
+      paths = Array(Chef::Config[:role_path])
+
+      paths.each do |p|
+        js_file = File.join(p, "#{name}.json")
+        rb_file = File.join(p, "#{name}.rb")
+
+        if File.exists?(js_file) || force == "json"
+          # from_json returns object.class => json_class in the JSON.
+          return Chef::JSONCompat.from_json(IO.read(js_file))
+        elsif File.exists?(rb_file) || force == "ruby"
+          role = Chef::Role.new
+          role.name(name)
+          role.from_file(rb_file)
+          return role
         end
-        r.cdb_save
       end
+
+      raise Chef::Exceptions::RoleNotFound, "Role '#{name}' could not be loaded from disk"
     end
 
   end
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 6a173f5..4d43111 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -18,109 +18,211 @@
 # limitations under the License.
 
 require 'chef/resource_collection'
+require 'chef/cookbook_version'
 require 'chef/node'
 require 'chef/role'
 require 'chef/log'
-require 'chef/mixin/language_include_recipe'
+require 'chef/recipe'
+require 'chef/run_context/cookbook_compiler'
 
 class Chef
+
   # == Chef::RunContext
   # Value object that loads and tracks the context of a Chef run
   class RunContext
 
-    # Used to load the node's recipes after expanding its run list
-    include Chef::Mixin::LanguageIncludeRecipe
+    # Chef::Node object for this run
+    attr_reader :node
+
+    # Chef::CookbookCollection for this run
+    attr_reader :cookbook_collection
+
+    # Resource Definitions for this run. Populated when the files in
+    # +definitions/+ are evaluated (this is triggered by #load).
+    attr_reader :definitions
 
-    attr_reader :node, :cookbook_collection, :definitions
+    ###
+    # These need to be settable so deploy can run a resource_collection
+    # independent of any cookbooks via +recipe_eval+
 
-    # Needs to be settable so deploy can run a resource_collection independent
-    # of any cookbooks.
+    # The Chef::ResourceCollection for this run. Populated by evaluating
+    # recipes, which is triggered by #load. (See also: CookbookCompiler)
     attr_accessor :resource_collection
 
+    # A Hash containing the immediate notifications triggered by resources
+    # during the converge phase of the chef run.
+    attr_accessor :immediate_notification_collection
+
+    # A Hash containing the delayed (end of run) notifications triggered by
+    # resources during the converge phase of the chef run.
+    attr_accessor :delayed_notification_collection
+
+    # Event dispatcher for this run.
+    attr_reader :events
+
     # Creates a new Chef::RunContext object and populates its fields. This object gets
     # used by the Chef Server to generate a fully compiled recipe list for a node.
     #
     # === Returns
     # object<Chef::RunContext>:: Duh. :)
-    def initialize(node, cookbook_collection)
+    def initialize(node, cookbook_collection, events)
       @node = node
       @cookbook_collection = cookbook_collection
       @resource_collection = Chef::ResourceCollection.new
+      @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
+      @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
       @definitions = Hash.new
+      @loaded_recipes = {}
+      @loaded_attributes = {}
+      @events = events
 
-      # TODO: 5/18/2010 cw/timh - See note on Chef::Node's
-      # cookbook_collection attr_accessor
-      node.cookbook_collection = cookbook_collection
+      @node.run_context = self
     end
 
+    # Triggers the compile phase of the chef run. Implemented by
+    # Chef::RunContext::CookbookCompiler
     def load(run_list_expansion)
-      load_libraries
-      load_lwrp_providers
-      load_lwrp_resources
-      load_attributes
-      load_resource_definitions
-
-      # Precendence rules state that roles' attributes come after
-      # cookbooks. Now we've loaded attributes from cookbooks with
-      # load_attributes, apply the expansion attributes (loaded from
-      # roles) to the node.
-      @node.apply_expansion_attributes(run_list_expansion)
-
-      run_list_expansion.recipes.each do |recipe|
-        # TODO: timh/cw, 5-14-2010: It's distasteful to be including
-        # the DSL in a class outside the context of the DSL
-        include_recipe(recipe)
-      end
+      compiler = CookbookCompiler.new(self, run_list_expansion, events)
+      compiler.compile
     end
 
-
-    private
-
-    def load_libraries
-      foreach_cookbook_load_segment(:libraries) do |cookbook_name, filename|
-        Chef::Log.debug("Loading cookbook #{cookbook_name}'s library file: #{filename}")
-        Kernel.load(filename)
+    # Adds an immediate notification to the
+    # +immediate_notification_collection+. The notification should be a
+    # Chef::Resource::Notification or duck type.
+    def notifies_immediately(notification)
+      nr = notification.notifying_resource
+      if nr.instance_of?(Chef::Resource)
+        @immediate_notification_collection[nr.name] << notification
+      else
+        @immediate_notification_collection[nr.to_s] << notification
       end
     end
 
-    def load_lwrp_providers
-      foreach_cookbook_load_segment(:providers) do |cookbook_name, filename|
-        Chef::Log.debug("Loading cookbook #{cookbook_name}'s providers from #{filename}")
-        Chef::Provider.build_from_file(cookbook_name, filename, self)
+    # Adds a delayed notification to the +delayed_notification_collection+. The
+    # notification should be a Chef::Resource::Notification or duck type.
+    def notifies_delayed(notification)
+      nr = notification.notifying_resource
+      if nr.instance_of?(Chef::Resource)
+        @delayed_notification_collection[nr.name] << notification
+      else
+        @delayed_notification_collection[nr.to_s] << notification
       end
     end
 
-    def load_lwrp_resources
-      foreach_cookbook_load_segment(:resources) do |cookbook_name, filename|
-        Chef::Log.debug("Loading cookbook #{cookbook_name}'s resources from #{filename}")
-        Chef::Resource.build_from_file(cookbook_name, filename, self)
+    def immediate_notifications(resource)
+      if resource.instance_of?(Chef::Resource)
+        return @immediate_notification_collection[resource.name]
+      else
+        return @immediate_notification_collection[resource.to_s]
       end
     end
 
-    def load_attributes
-      node.load_attributes
+    def delayed_notifications(resource)
+      if resource.instance_of?(Chef::Resource)
+        return @delayed_notification_collection[resource.name]
+      else
+        return @delayed_notification_collection[resource.to_s]
+      end
     end
 
-    def load_resource_definitions
-      foreach_cookbook_load_segment(:definitions) do |cookbook_name, filename|
-        Chef::Log.debug("Loading cookbook #{cookbook_name}'s definitions from #{filename}")
-        resourcelist = Chef::ResourceDefinitionList.new
-        resourcelist.from_file(filename)
-        definitions.merge!(resourcelist.defines) do |key, oldval, newval|
-          Chef::Log.info("Overriding duplicate definition #{key}, new definition found in #{filename}")
-          newval
+    # Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe
+    def include_recipe(*recipe_names)
+      result_recipes = Array.new
+      recipe_names.flatten.each do |recipe_name|
+        if result = load_recipe(recipe_name)
+          result_recipes << result
         end
       end
+      result_recipes
     end
 
-    def foreach_cookbook_load_segment(segment, &block)
-      cookbook_collection.each do |cookbook_name, cookbook|
-        segment_filenames = cookbook.segment_filenames(segment)
-        segment_filenames.each do |segment_filename|
-          block.call(cookbook_name, segment_filename)
-        end
+    # Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe
+    def load_recipe(recipe_name)
+      Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
+
+      cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name)
+      if loaded_fully_qualified_recipe?(cookbook_name, recipe_short_name)
+        Chef::Log.debug("I am not loading #{recipe_name}, because I have already seen it.")
+        false
+      else
+        loaded_recipe(cookbook_name, recipe_short_name)
+
+        cookbook = cookbook_collection[cookbook_name]
+        cookbook.load_recipe(recipe_short_name, self)
       end
     end
 
+    # Looks up an attribute file given the +cookbook_name+ and
+    # +attr_file_name+. Used by DSL::IncludeAttribute
+    def resolve_attribute(cookbook_name, attr_file_name)
+      cookbook = cookbook_collection[cookbook_name]
+      raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook
+
+      attribute_filename = cookbook.attribute_filenames_by_short_filename[attr_file_name]
+      raise Chef::Exceptions::AttributeNotFound, "could not find filename for attribute #{attr_file_name} in cookbook #{cookbook_name}" unless attribute_filename
+
+      attribute_filename
+    end
+
+    # An Array of all recipes that have been loaded. This is stored internally
+    # as a Hash, so ordering is not preserved when using ruby 1.8.
+    #
+    # Recipe names are given in fully qualified form, e.g., the recipe "nginx"
+    # will be given as "nginx::default"
+    #
+    # To determine if a particular recipe has been loaded, use #loaded_recipe?
+    def loaded_recipes
+      @loaded_recipes.keys
+    end
+
+    # An Array of all attributes files that have been loaded. Stored internally
+    # using a Hash, so order is not preserved on ruby 1.8.
+    #
+    # Attribute file names are given in fully qualified form, e.g.,
+    # "nginx::default" instead of "nginx".
+    def loaded_attributes
+      @loaded_attributes.keys
+    end
+
+    def loaded_fully_qualified_recipe?(cookbook, recipe)
+      @loaded_recipes.has_key?("#{cookbook}::#{recipe}")
+    end
+
+    # Returns true if +recipe+ has been loaded, false otherwise. Default recipe
+    # names are expanded, so `loaded_recipe?("nginx")` and
+    # `loaded_recipe?("nginx::default")` are valid and give identical results.
+    def loaded_recipe?(recipe)
+      cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
+      loaded_fully_qualified_recipe?(cookbook, recipe_name)
+    end
+
+    def loaded_fully_qualified_attribute?(cookbook, attribute_file)
+      @loaded_attributes.has_key?("#{cookbook}::#{attribute_file}")
+    end
+
+    def loaded_attribute(cookbook, attribute_file)
+      @loaded_attributes["#{cookbook}::#{attribute_file}"] = true
+    end
+
+    ##
+    # Cookbook File Introspection
+
+    def has_template_in_cookbook?(cookbook, template_name)
+      cookbook = cookbook_collection[cookbook]
+      cookbook.has_template_for_node?(node, template_name)
+    end
+
+    def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
+      cookbook = cookbook_collection[cookbook]
+      cookbook.has_cookbook_file_for_node?(node, cb_file_name)
+    end
+
+
+    private
+
+    def loaded_recipe(cookbook, recipe)
+      @loaded_recipes["#{cookbook}::#{recipe}"] = true
+    end
+
   end
 end
diff --git a/lib/chef/run_context/cookbook_compiler.rb b/lib/chef/run_context/cookbook_compiler.rb
new file mode 100644
index 0000000..0a05061
--- /dev/null
+++ b/lib/chef/run_context/cookbook_compiler.rb
@@ -0,0 +1,280 @@
+#
+# Author:: Daniel DeLeo (<dan 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 'chef/log'
+require 'chef/recipe'
+require 'chef/resource/lwrp_base'
+require 'chef/provider/lwrp_base'
+require 'chef/resource_definition_list'
+
+class Chef
+  class RunContext
+
+    # Implements the compile phase of the chef run by loading/eval-ing files
+    # from cookbooks in the correct order and in the correct context.
+    class CookbookCompiler
+      attr_reader :events
+      attr_reader :run_list_expansion
+
+      def initialize(run_context, run_list_expansion, events)
+        @run_context = run_context
+        @events = events
+        @run_list_expansion = run_list_expansion
+        @cookbook_order = nil
+      end
+
+      # Chef::Node object for the current run.
+      def node
+        @run_context.node
+      end
+
+      # Chef::CookbookCollection object for the current run
+      def cookbook_collection
+        @run_context.cookbook_collection
+      end
+
+      # Resource Definitions from the compiled cookbooks. This is populated by
+      # calling #compile_resource_definitions (which is called by #compile)
+      def definitions
+        @run_context.definitions
+      end
+
+      # Run the compile phase of the chef run. Loads files in the following order:
+      # * Libraries
+      # * Attributes
+      # * LWRPs
+      # * Resource Definitions
+      # * Recipes
+      #
+      # Recipes are loaded in precisely the order specified by the expanded run_list.
+      #
+      # Other files are loaded in an order derived from the expanded run_list
+      # and the dependencies declared by cookbooks' metadata. See
+      # #cookbook_order for more information.
+      def compile
+        compile_libraries
+        compile_attributes
+        compile_lwrps
+        compile_resource_definitions
+        compile_recipes
+      end
+
+      # Extracts the cookbook names from the expanded run list, then iterates
+      # over the list, recursing through dependencies to give a run_list
+      # ordered array of cookbook names with no duplicates. Dependencies appear
+      # before the cookbook(s) that depend on them.
+      def cookbook_order
+        @cookbook_order ||= begin
+          ordered_cookbooks = []
+          seen_cookbooks = {}
+          run_list_expansion.recipes.each do |recipe|
+            cookbook = Chef::Recipe.parse_recipe_name(recipe).first
+            add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, cookbook)
+          end
+          Chef::Log.debug("Cookbooks to compile: #{ordered_cookbooks.inspect}")
+          ordered_cookbooks
+        end
+      end
+
+      # Loads library files from cookbooks according to #cookbook_order.
+      def compile_libraries
+        @events.library_load_start(count_files_by_segment(:libraries))
+        cookbook_order.each do |cookbook|
+          load_libraries_from_cookbook(cookbook)
+        end
+        @events.library_load_complete
+      end
+
+      # Loads attributes files from cookbooks. Attributes files are loaded
+      # according to #cookbook_order; within a cookbook, +default.rb+ is loaded
+      # first, then the remaining attributes files in lexical sort order.
+      def compile_attributes
+        @events.attribute_load_start(count_files_by_segment(:attributes))
+        cookbook_order.each do |cookbook|
+          load_attributes_from_cookbook(cookbook)
+        end
+        @events.attribute_load_complete
+      end
+
+      # Loads LWRPs according to #cookbook_order. Providers are loaded before
+      # resources on a cookbook-wise basis.
+      def compile_lwrps
+        lwrp_file_count = count_files_by_segment(:providers) + count_files_by_segment(:resources)
+        @events.lwrp_load_start(lwrp_file_count)
+        cookbook_order.each do |cookbook|
+          load_lwrps_from_cookbook(cookbook)
+        end
+        @events.lwrp_load_complete
+      end
+
+      # Loads resource definitions according to #cookbook_order
+      def compile_resource_definitions
+        @events.definition_load_start(count_files_by_segment(:definitions))
+        cookbook_order.each do |cookbook|
+          load_resource_definitions_from_cookbook(cookbook)
+        end
+        @events.definition_load_complete
+      end
+
+      # Iterates over the expanded run_list, loading each recipe in turn.
+      def compile_recipes
+        @events.recipe_load_start(run_list_expansion.recipes.size)
+        run_list_expansion.recipes.each do |recipe|
+          begin
+            @run_context.load_recipe(recipe)
+          rescue Chef::Exceptions::RecipeNotFound => e
+            @events.recipe_not_found(e)
+            raise
+          rescue Exception => e
+            path = resolve_recipe(recipe)
+            @events.recipe_file_load_failed(path, e)
+            raise
+          end
+        end
+        @events.recipe_load_complete
+      end
+
+      private
+
+      def load_attributes_from_cookbook(cookbook_name)
+        list_of_attr_files = files_in_cookbook_by_segment(cookbook_name, :attributes).dup
+        if default_file = list_of_attr_files.find {|path| File.basename(path) == "default.rb" }
+          list_of_attr_files.delete(default_file)
+          load_attribute_file(cookbook_name.to_s, default_file)
+        end
+
+        list_of_attr_files.each do |filename|
+          load_attribute_file(cookbook_name.to_s, filename)
+        end
+      end
+
+      def load_attribute_file(cookbook_name, filename)
+        Chef::Log.debug("Node #{node.name} loading cookbook #{cookbook_name}'s attribute file #{filename}")
+        attr_file_basename = ::File.basename(filename, ".rb")
+        node.include_attribute("#{cookbook_name}::#{attr_file_basename}")
+      rescue Exception => e
+        @events.attribute_file_load_failed(filename, e)
+        raise
+      end
+
+      def load_libraries_from_cookbook(cookbook_name)
+        files_in_cookbook_by_segment(cookbook_name, :libraries).each do |filename|
+          begin
+            Chef::Log.debug("Loading cookbook #{cookbook_name}'s library file: #{filename}")
+            Kernel.load(filename)
+            @events.library_file_loaded(filename)
+          rescue Exception => e
+            @events.library_file_load_failed(filename, e)
+            raise
+          end
+        end
+      end
+
+      def load_lwrps_from_cookbook(cookbook_name)
+        files_in_cookbook_by_segment(cookbook_name, :providers).each do |filename|
+          load_lwrp_provider(cookbook_name, filename)
+        end
+        files_in_cookbook_by_segment(cookbook_name, :resources).each do |filename|
+          load_lwrp_resource(cookbook_name, filename)
+        end
+      end
+
+      def load_lwrp_provider(cookbook_name, filename)
+        Chef::Log.debug("Loading cookbook #{cookbook_name}'s providers from #{filename}")
+        Chef::Provider::LWRPBase.build_from_file(cookbook_name, filename, self)
+        @events.lwrp_file_loaded(filename)
+      rescue Exception => e
+        @events.lwrp_file_load_failed(filename, e)
+        raise
+      end
+
+      def load_lwrp_resource(cookbook_name, filename)
+        Chef::Log.debug("Loading cookbook #{cookbook_name}'s resources from #{filename}")
+        Chef::Resource::LWRPBase.build_from_file(cookbook_name, filename, self)
+        @events.lwrp_file_loaded(filename)
+      rescue Exception => e
+        @events.lwrp_file_load_failed(filename, e)
+        raise
+      end
+
+
+      def load_resource_definitions_from_cookbook(cookbook_name)
+        files_in_cookbook_by_segment(cookbook_name, :definitions).each do |filename|
+          begin
+            Chef::Log.debug("Loading cookbook #{cookbook_name}'s definitions from #{filename}")
+            resourcelist = Chef::ResourceDefinitionList.new
+            resourcelist.from_file(filename)
+            definitions.merge!(resourcelist.defines) do |key, oldval, newval|
+              Chef::Log.info("Overriding duplicate definition #{key}, new definition found in #{filename}")
+              newval
+            end
+            @events.definition_file_loaded(filename)
+          rescue Exception => e
+            @events.definition_file_load_failed(filename, e)
+            raise
+          end
+        end
+      end
+
+      # Builds up the list of +ordered_cookbooks+ by first recursing through the
+      # dependencies of +cookbook+, and then adding +cookbook+ to the list of
+      # +ordered_cookbooks+. A cookbook is skipped if it appears in
+      # +seen_cookbooks+, otherwise it is added to the set of +seen_cookbooks+
+      # before its dependencies are processed.
+      def add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, cookbook)
+        return false if seen_cookbooks.key?(cookbook)
+
+        seen_cookbooks[cookbook] = true
+        each_cookbook_dep(cookbook) do |dependency|
+          add_cookbook_with_deps(ordered_cookbooks, seen_cookbooks, dependency)
+        end
+        ordered_cookbooks << cookbook
+      end
+
+
+      def count_files_by_segment(segment)
+        cookbook_collection.inject(0) do |count, cookbook_by_name|
+          count + cookbook_by_name[1].segment_filenames(segment).size
+        end
+      end
+
+      # Lists the local paths to files in +cookbook+ of type +segment+
+      # (attribute, recipe, etc.), sorted lexically.
+      def files_in_cookbook_by_segment(cookbook, segment)
+        cookbook_collection[cookbook].segment_filenames(segment).sort
+      end
+
+      # Yields the name, as a symbol, of each cookbook depended on by
+      # +cookbook_name+ in lexical sort order.
+      def each_cookbook_dep(cookbook_name, &block)
+        cookbook = cookbook_collection[cookbook_name]
+        cookbook.metadata.dependencies.keys.sort.map{|x| x.to_sym}.each(&block)
+      end
+
+      # Given a +recipe_name+, finds the file associated with the recipe.
+      def resolve_recipe(recipe_name)
+        cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name)
+        cookbook = cookbook_collection[cookbook_name]
+        cookbook.recipe_filenames_by_name[recipe_short_name]
+      end
+
+
+    end
+
+  end
+end
diff --git a/lib/chef/run_list.rb b/lib/chef/run_list.rb
index cb77691..684c5e1 100644
--- a/lib/chef/run_list.rb
+++ b/lib/chef/run_list.rb
@@ -29,7 +29,7 @@ class Chef
     include Enumerable
     include Chef::Mixin::ParamsValidate
 
-    # @run_list_items is an array of RunListItems that describe the items to 
+    # @run_list_items is an array of RunListItems that describe the items to
     # execute in order. RunListItems can load from and convert to the string
     # forms users set on roles and nodes.
     # For example:
@@ -67,6 +67,7 @@ class Chef
     end
 
     alias :push :<<
+    alias :add :<<
 
     def ==(other)
       if other.kind_of?(Chef::RunList)
@@ -154,12 +155,9 @@ class Chef
         RunListExpansionFromDisk.new(environment, @run_list_items)
       when 'server'
         RunListExpansionFromAPI.new(environment, @run_list_items, opts[:rest])
-      when 'couchdb'
-        RunListExpansionFromCouchDB.new(environment, @run_list_items, opts[:couchdb])
       end
     end
 
 
   end
 end
-
diff --git a/lib/chef/run_list/run_list_expansion.rb b/lib/chef/run_list/run_list_expansion.rb
index a8e7aeb..73665f3 100644
--- a/lib/chef/run_list/run_list_expansion.rb
+++ b/lib/chef/run_list/run_list_expansion.rb
@@ -21,7 +21,6 @@ require 'chef/mash'
 require 'chef/mixin/deep_merge'
 
 require 'chef/role'
-require 'chef/couchdb'
 require 'chef/rest'
 
 class Chef
@@ -40,18 +39,24 @@ class Chef
 
       attr_reader :override_attrs
 
-      attr_reader :errors
-
       attr_reader :environment
 
+      attr_reader :missing_roles_with_including_role
+
       # The data source passed to the constructor. Not used in this class.
       # In subclasses, this is a couchdb or Chef::REST object pre-configured
       # to fetch roles from their correct location.
       attr_reader :source
 
+      # Returns a Hash of the form "including_role" => "included_role_or_recipe".
+      # This can be used to show the expanded run list (ordered) graph.
+      # ==== Caveats
+      # * Duplicate roles are not shown.
+      attr_reader :run_list_trace
+
       def initialize(environment, run_list_items, source=nil)
         @environment = environment
-        @errors = Array.new
+        @missing_roles_with_including_role = Array.new
 
         @run_list_items = run_list_items.dup
         @source = source
@@ -62,11 +67,12 @@ class Chef
         @recipes = Chef::RunList::VersionedRecipeList.new
 
         @applied_roles = {}
+        @run_list_trace = Hash.new {|h, key| h[key] = [] }
       end
 
       # Did we find any errors (expanding roles)?
       def errors?
-        @errors.length > 0
+        @missing_roles_with_including_role.length > 0
       end
 
       alias :invalid? :errors?
@@ -83,10 +89,10 @@ class Chef
       # Chef::Role  in most cases
       # false       if the role has already been applied
       # nil         if the role does not exist
-      def inflate_role(role_name)
+      def inflate_role(role_name, included_by)
         return false if applied_role?(role_name) # Prevent infinite loops
         applied_role(role_name)
-        fetch_role(role_name)
+        fetch_role(role_name, included_by)
       end
 
       def apply_role_attributes(role)
@@ -107,7 +113,7 @@ class Chef
       end
 
       # In subclasses, this method will fetch the role from the data source.
-      def fetch_role(name)
+      def fetch_role(name, included_by)
         raise NotImplementedError
       end
 
@@ -115,12 +121,16 @@ class Chef
       # exception is raised.  We do add an entry in the errors collection.
       # === Returns
       # nil
-      def role_not_found(name)
-        Chef::Log.error("Role #{name} is in the runlist but does not exist. Skipping expand.")
-        @errors << name
+      def role_not_found(name, included_by)
+        Chef::Log.error("Role #{name} (included by '#{included_by}') is in the runlist but does not exist. Skipping expand.")
+        @missing_roles_with_including_role << [name, included_by]
         nil
       end
 
+      def errors
+        @missing_roles_with_including_role.map {|item| item.first }
+      end
+
       private
 
       # these methods modifies internal state based on arguments, so hide it.
@@ -129,18 +139,20 @@ class Chef
         @applied_roles[role_name] = true
       end
 
-      def expand_run_list_items(items)
+      def expand_run_list_items(items, included_by="top level")
         if entry = items.shift
+          @run_list_trace[included_by.to_s] << entry.to_s
+
           case entry.type
           when :recipe
             recipes.add_recipe(entry.name, entry.version)
           when :role
-            if role = inflate_role(entry.name)
-              expand_run_list_items(role.run_list_for(@environment).run_list_items)
+            if role = inflate_role(entry.name, included_by)
+              expand_run_list_items(role.run_list_for(@environment).run_list_items, role)
               apply_role_attributes(role)
             end
           end
-          expand_run_list_items(items)
+          expand_run_list_items(items, included_by)
         end
       end
 
@@ -149,10 +161,10 @@ class Chef
     # Expand a run list from disk. Suitable for chef-solo
     class RunListExpansionFromDisk < RunListExpansion
 
-      def fetch_role(name)
+      def fetch_role(name, included_by)
         Chef::Role.from_disk(name)
       rescue Chef::Exceptions::RoleNotFound
-        role_not_found(name)
+        role_not_found(name, included_by)
       end
 
     end
@@ -161,33 +173,19 @@ class Chef
     class RunListExpansionFromAPI < RunListExpansion
 
       def rest
-        @rest ||= (source || Chef::REST.new(Chef::Config[:role_url]))
+        @rest ||= (source || Chef::REST.new(Chef::Config[:chef_server_url]))
       end
 
-      def fetch_role(name)
+      def fetch_role(name, included_by)
         rest.get_rest("roles/#{name}")
       rescue Net::HTTPServerException => e
         if e.message == '404 "Not Found"'
-          role_not_found(name)
+          role_not_found(name, included_by)
         else
           raise
         end
       end
     end
 
-    # Expand a run list from couchdb. Used in chef-server-api
-    class RunListExpansionFromCouchDB < RunListExpansion
-
-      def couchdb
-        source
-      end
-
-      def fetch_role(name)
-        Chef::Role.cdb_load(name, couchdb)
-      rescue Chef::Exceptions::CouchDBNotFound
-        role_not_found(name)
-      end
-
-    end
   end
 end
diff --git a/lib/chef/run_lock.rb b/lib/chef/run_lock.rb
new file mode 100644
index 0000000..972db1a
--- /dev/null
+++ b/lib/chef/run_lock.rb
@@ -0,0 +1,149 @@
+#
+# Author:: Daniel DeLeo (<dan 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 'chef/mixin/create_path'
+require 'fcntl'
+if Chef::Platform.windows?
+  require 'chef/win32/mutex'
+end
+
+class Chef
+
+  # == Chef::RunLock
+  # Provides an interface for acquiring and releasing a system-wide exclusive
+  # lock.
+  #
+  # Used by Chef::Client to ensure only one instance of chef-client (or solo)
+  # is modifying the system at a time.
+  class RunLock
+    include Chef::Mixin::CreatePath
+
+    attr_reader :runlock
+    attr_reader :mutex
+    attr_reader :runlock_file
+
+    # Create a new instance of RunLock
+    # === Arguments
+    # * :lockfile::: the full path to the lockfile.
+    def initialize(lockfile)
+      @runlock_file = lockfile
+      @runlock = nil
+      @mutex = nil
+    end
+
+    # Acquire the system-wide lock. Will block indefinitely if another process
+    # already has the lock.
+    #
+    # Each call to acquire should have a corresponding call to #release.
+    #
+    # The implementation is based on File#flock (see also: flock(2)).
+    #
+    # Either acquire() or test() methods should be called in order to
+    # get the ownership of run_lock.
+    def acquire
+      wait unless test
+    end
+
+    #
+    # Tests and if successful acquires the system-wide lock.
+    # Returns true if the lock is acquired, false otherwise.
+    #
+    # Either acquire() or test() methods should be called in order to
+    # get the ownership of run_lock.
+    def test
+      # ensure the runlock_file path exists
+      create_path(File.dirname(runlock_file))
+      @runlock = File.open(runlock_file,'a+')
+
+      if Chef::Platform.windows?
+        acquire_win32_mutex
+      else
+        # If we support FD_CLOEXEC, then use it.
+        # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not
+        # ruby-1.8.7/1.9.3
+        if Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC')
+          runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC)
+        end
+        # Flock will return 0 if it can acquire the lock otherwise it
+        # will return false
+        if runlock.flock(File::LOCK_NB|File::LOCK_EX) == 0
+          true
+        else
+          false
+        end
+      end
+    end
+
+    #
+    # Waits until acquiring the system-wide lock.
+    #
+    def wait
+      runpid = runlock.read.strip.chomp
+      Chef::Log.warn("Chef client #{runpid} is running, will wait for it to finish and then run.")
+      if Chef::Platform.windows?
+        mutex.wait
+      else
+        runlock.flock(File::LOCK_EX)
+      end
+    end
+
+    def save_pid
+      runlock.truncate(0)
+      runlock.rewind # truncate doesn't reset position to 0.
+      runlock.write(Process.pid.to_s)
+      # flush the file fsync flushes the system buffers
+      # in addition to ruby buffers
+      runlock.fsync
+    end
+
+    # Release the system-wide lock.
+    def release
+      if runlock
+        if Chef::Platform.windows?
+          mutex.release
+        else
+          runlock.flock(File::LOCK_UN)
+        end
+        runlock.close
+        # Don't unlink the pid file, if another chef-client was waiting, it
+        # won't be recreated. Better to leave a "dead" pid file than not have
+        # it available if you need to break the lock.
+        reset
+      end
+    end
+
+    private
+
+    def reset
+      @runlock = nil
+      @mutex = nil
+    end
+
+    # Since flock mechanism doesn't exist on windows we are using
+    # platform Mutex.
+    # We are creating a "Global" mutex here so that non-admin
+    # users can not DoS chef-client by creating the same named
+    # mutex we are using locally.
+    # Mutex name is case-sensitive contrary to other things in
+    # windows. "\" is the only invalid character.
+    def acquire_win32_mutex
+      @mutex = Chef::ReservedNames::Win32::Mutex.new("Global\\#{runlock_file.gsub(/[\\]/, "/").downcase}")
+      mutex.test
+    end
+  end
+end
+
diff --git a/lib/chef/run_status.rb b/lib/chef/run_status.rb
index 7c899f8..9354f78 100644
--- a/lib/chef/run_status.rb
+++ b/lib/chef/run_status.rb
@@ -23,6 +23,8 @@
 # Chef run.
 class Chef::RunStatus
 
+  attr_reader :events
+
   attr_reader :run_context
 
   attr_writer :run_context
@@ -35,8 +37,9 @@ class Chef::RunStatus
 
   attr_writer :exception
 
-  def initialize(node)
+  def initialize(node, events)
     @node = node
+    @events = events
   end
 
   def node
diff --git a/lib/chef/runner.rb b/lib/chef/runner.rb
index b7a5bce..6125fe5 100644
--- a/lib/chef/runner.rb
+++ b/lib/chef/runner.rb
@@ -18,10 +18,10 @@
 # limitations under the License.
 #
 
+require 'chef/exceptions'
 require 'chef/mixin/params_validate'
 require 'chef/node'
 require 'chef/resource_collection'
-require 'chef/platform'
 
 class Chef
   # == Chef::Runner
@@ -39,25 +39,25 @@ class Chef
       @delayed_actions  = []
     end
 
+    def events
+      @run_context.events
+    end
+
     # Determine the appropriate provider for the given resource, then
     # execute it.
-    def run_action(resource, action)
-      # Try to resolve lazy/forward references in notifications again to handle
-      # the case where the resource was defined lazily (ie. in a ruby_block)
-      resource.resolve_notification_references
-
-      resource.run_action(action)
+    def run_action(resource, action, notification_type=nil, notifying_resource=nil)
+      resource.run_action(action, notification_type, notifying_resource)
 
       # Execute any immediate and queue up any delayed notifications
       # associated with the resource, but only if it was updated *this time*
       # we ran an action on it.
       if resource.updated_by_last_action?
-        resource.immediate_notifications.each do |notification|
+        run_context.immediate_notifications(resource).each do |notification|
           Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (immediate)")
-          run_action(notification.resource, notification.action)
+          run_action(notification.resource, notification.action, :immediate, resource)
         end
 
-        resource.delayed_notifications.each do |notification|
+        run_context.delayed_notifications(resource).each do |notification|
           if delayed_actions.any? { |existing_notification| existing_notification.duplicates?(notification) }
             Chef::Log.info( "#{resource} not queuing delayed action #{notification.action} on #{notification.resource}"\
                             " (delayed), as it's already been queued")
@@ -78,32 +78,40 @@ class Chef
 
       # Execute each resource.
       run_context.resource_collection.execute_each_resource do |resource|
-        begin
-          Chef::Log.debug("Processing #{resource} on #{run_context.node.name}")
-
-          # Execute each of this resource's actions.
-          Array(resource.action).each {|action| run_action(resource, action)}
-        rescue => e
-          Chef::Log.error("#{resource} (#{resource.source_line}) had an error:\n#{e}\n#{e.backtrace.join("\n")}")
-          if resource.retries > 0
-            resource.retries -= 1
-            Chef::Log.info("Retrying execution of #{resource}, #{resource.retries} attempt(s) left")
-            sleep resource.retry_delay
-            retry
-          end
-          raise e unless resource.ignore_failure
-        end
+        Array(resource.action).each {|action| run_action(resource, action)}
       end
 
-      # Run all our :delayed actions
+    rescue Exception => e
+      Chef::Log.info "Running queued delayed notifications before re-raising exception"
+      run_delayed_notifications(e)
+    else
+      run_delayed_notifications(nil)
+      true
+    end
+
+    private
+
+    # Run all our :delayed actions
+    def run_delayed_notifications(error=nil)
+      collected_failures = Exceptions::MultipleFailures.new
+      collected_failures.client_run_failure(error) unless error.nil?
       delayed_actions.each do |notification|
-        Chef::Log.info( "#{notification.notifying_resource} sending #{notification.action}"\
-                        " action to #{notification.resource} (delayed)")
-        # Struct of resource/action to call
-        run_action(notification.resource, notification.action)
+        result = run_delayed_notification(notification)
+        if result.kind_of?(Exception)
+          collected_failures.notification_failure(result)
+        end
       end
+      collected_failures.raise!
+    end
 
+    def run_delayed_notification(notification)
+      Chef::Log.info( "#{notification.notifying_resource} sending #{notification.action}"\
+                      " action to #{notification.resource} (delayed)")
+      # Struct of resource/action to call
+      run_action(notification.resource, notification.action, :delayed)
       true
+    rescue Exception => e
+      e
     end
   end
 end
diff --git a/lib/chef/sandbox.rb b/lib/chef/sandbox.rb
index 4d05a1d..28bc4ff 100644
--- a/lib/chef/sandbox.rb
+++ b/lib/chef/sandbox.rb
@@ -1,153 +1,20 @@
-#
-# Author:: Tim Hinderliter (<tim at opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# 
-#     http://www.apache.org/licenses/LICENSE-2.0
-# 
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-require 'chef/log'
-require 'uuidtools'
-
 class Chef
   class Sandbox
-    attr_accessor :is_completed, :create_time
-    alias_method :is_completed?, :is_completed
-    attr_reader :guid
-    
-    alias :name :guid
-    
-    attr_accessor :couchdb, :couchdb_id, :couchdb_rev
-
-    # list of checksum ids
-    attr_accessor :checksums
-
-    DESIGN_DOCUMENT = {
-      "version" => 1,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-          function(doc) { 
-            if (doc.chef_type == "sandbox") {
-              emit(doc.guid, doc);
-            }
-          }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "sandbox") {
-              emit(doc.guid, doc.guid);
-            }
-          }
-          EOJS
-        },
-        "all_incomplete" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "sandbox" && !doc.is_completed) {
-              emit(doc.guid, doc.guid);
-            }
-          }
-          EOJS
-        },
-        "all_completed" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "sandbox" && doc.is_completed) {
-              emit(doc.guid, doc.guid);
-            }
-          }
-          EOJS
-        },
-      }
-    }
-    
-    # Creates a new Chef::Sandbox object.  
+    # I DO NOTHING!!
+
+    # So, the reason we have a completely empty class here is so that
+    # Chef 11 clients do not choke when interacting with Chef 10
+    # servers.  The original Chef::Sandbox class (that actually did
+    # things) has been removed since its functionality is no longer
+    # needed for Chef 11.  However, since we still use the JSON gem
+    # and make use of its "auto-inflation" of classes (driven by the
+    # contents of the 'json_class' key in all of our JSON), any
+    # sandbox responses from a Chef 10 server to a Chef 11 client
+    # would cause knife to crash.  The JSON gem would attempt to
+    # auto-inflate based on a "json_class": "Chef::Sandbox" hash
+    # entry, but would not be able to find a Chef::Sandbox class!
     #
-    # === Returns
-    # object<Chef::Sandbox>:: Duh. :)
-    def initialize(guid=nil, couchdb=nil)
-      @guid = guid || UUIDTools::UUID.random_create.to_s.gsub(/\-/,'').downcase
-      @is_completed = false
-      @create_time = Time.now.iso8601
-      @checksums = Array.new
-    end
-
-    def include?(checksum)
-      @checksums.include?(checksum)
-    end
-
-    alias :member? :include?
-
-    def to_json(*a)
-      result = {
-        :guid => guid,
-        :name => name,   # same as guid, used for id_map
-        :checksums => checksums,
-        :create_time => create_time,
-        :is_completed => is_completed,
-        :json_class => self.class.name,
-        :chef_type => 'sandbox'
-      }
-      result["_rev"] = @couchdb_rev if @couchdb_rev
-      result.to_json(*a)
-    end
-
-    def self.json_create(o)
-      sandbox = new(o['guid'])
-      sandbox.checksums = o['checksums']
-      sandbox.create_time = o['create_time']
-      sandbox.is_completed = o['is_completed']
-      if o.has_key?('_rev')
-        sandbox.couchdb_rev = o["_rev"]
-        o.delete("_rev")
-      end
-      if o.has_key?("_id")
-        sandbox.couchdb_id = o["_id"]
-        #sandbox.index_id = sandbox.couchdb_id
-        o.delete("_id")
-      end
-      sandbox
-    end
-
-    ##
-    # Couchdb
-    ##
-    
-    def self.create_design_document(couchdb=nil)
-      (couchdb || Chef::CouchDB.new).create_design_document("sandboxes", DESIGN_DOCUMENT)
-    end
-    
-    def self.cdb_list(inflate=false, couchdb=nil)
-      rs = (couchdb || Chef::CouchDB.new).list("sandboxes", inflate)
-      lookup = (inflate ? "value" : "key")
-      rs["rows"].collect { |r| r[lookup] }            
-    end
-
-    def self.cdb_load(guid, couchdb=nil)
-      # Probably want to look for a view here at some point
-      (couchdb || Chef::CouchDB.new).load("sandbox", guid)
-    end
-
-    def cdb_destroy
-      (couchdb || Chef::CouchDB.new).delete("sandbox", guid, @couchdb_rev)
-    end
-
-    def cdb_save(couchdb=nil)
-      @couchdb_rev = (couchdb || Chef::CouchDB.new).store("sandbox", guid, self)["rev"]
-    end
-
+    # This is a workaround until such time as we can completely remove
+    # the reliance on the "json_class" field.
   end
 end
diff --git a/lib/chef/scan_access_control.rb b/lib/chef/scan_access_control.rb
new file mode 100644
index 0000000..01630a7
--- /dev/null
+++ b/lib/chef/scan_access_control.rb
@@ -0,0 +1,138 @@
+#--
+# Author:: Daniel DeLeo (<dan 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.
+#
+
+class Chef
+  # == ScanAccessControl
+  # Reads Access Control Settings on a file and writes them out to a resource
+  # (should be the current_resource), attempting to match the style used by the
+  # new resource, that is, if users are specified with usernames in
+  # new_resource, then the uids from stat will be looked up and usernames will
+  # be added to current_resource.
+  #
+  # === Why?
+  # FileAccessControl objects may operate on a temporary file, in which case we
+  # won't know if the access control settings changed (ex: rendering a template
+  # with both a change in content and ownership). For auditing purposes, we
+  # need to record the current state of a file system entity.
+  #--
+  # Not yet sure if this is the optimal way to solve the problem. But it's
+  # progress towards the end goal.
+  #
+  # TODO: figure out if all this works with OS X's negative uids
+  # TODO: windows
+  class ScanAccessControl
+
+    attr_reader :new_resource
+    attr_reader :current_resource
+
+    def initialize(new_resource, current_resource)
+      @new_resource, @current_resource = new_resource, current_resource
+    end
+
+    # Modifies @current_resource, setting the current access control state.
+    def set_all!
+      if ::File.exist?(new_resource.path)
+        set_owner
+        set_group
+        set_mode
+      else
+        # leave the values as nil.
+      end
+    end
+
+    # Set the owner attribute of +current_resource+ to whatever the current
+    # state is. Attempts to match the format given in new_resource: if the
+    # new_resource specifies the owner as a string, the username for the uid
+    # will be looked up and owner will be set to the username, and vice versa.
+    def set_owner
+      @current_resource.owner(current_owner)
+    end
+
+    def current_owner
+      case new_resource.owner
+      when String, nil
+        lookup_uid
+      when Integer
+        stat.uid
+      else
+        Chef::Log.error("The `owner` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.owner.inspect})")
+        raise ArgumentError, "cannot resolve #{new_resource.owner.inspect} to uid, owner must be a string or integer"
+      end
+    end
+
+    def lookup_uid
+      unless (pwent = Etc.getpwuid(stat.uid)).nil?
+        pwent.name
+      else
+        stat.uid
+      end
+    rescue ArgumentError
+      stat.uid
+    end
+
+    # Set the group attribute of +current_resource+ to whatever the current state is.
+    def set_group
+      @current_resource.group(current_group)
+    end
+
+    def current_group
+      case new_resource.group
+      when String, nil
+        lookup_gid
+      when Integer
+        stat.gid
+      else
+        Chef::Log.error("The `group` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.owner.inspect})")
+        raise ArgumentError, "cannot resolve #{new_resource.group.inspect} to gid, group must be a string or integer"
+      end
+    end
+
+    def lookup_gid
+      unless (pwent = Etc.getgrgid(stat.gid)).nil?
+        pwent.name
+      else
+        stat.gid
+      end
+    rescue ArgumentError
+      stat.gid
+    end
+
+    def set_mode
+      @current_resource.mode(current_mode)
+    end
+
+    def current_mode
+      case new_resource.mode
+      when String, Integer, nil
+        "0#{(stat.mode & 07777).to_s(8)}"
+      else
+        Chef::Log.error("The `mode` parameter of the #@new_resource resource is set to an invalid value (#{new_resource.mode.inspect})")
+        raise ArgumentError, "Invalid value #{new_resource.mode.inspect} for `mode` on resource #@new_resource"
+      end
+    end
+
+    def stat
+      @stat ||= if @new_resource.instance_of?(Chef::Resource::Link)
+        ::File.lstat(@new_resource.path)
+      else
+        realpath = ::File.realpath(@new_resource.path)
+        ::File.stat(realpath)
+      end
+    end
+  end
+end
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index d147bc0..4869ec1 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -31,7 +31,7 @@ class Chef
       attr_accessor :rest
 
       def initialize(url=nil)
-        @rest = Chef::REST.new(url ||Chef::Config[:search_url])
+        @rest = Chef::REST.new(url ||Chef::Config[:chef_server_url])
       end
 
       # Search Solr for objects of a given type, for a given query. If you give
@@ -53,7 +53,7 @@ class Chef
       end
 
       def list_indexes
-        response = @rest.get_rest("search")
+        @rest.get_rest("search")
       end
 
       private
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
new file mode 100644
index 0000000..e9e7593
--- /dev/null
+++ b/lib/chef/server_api.rb
@@ -0,0 +1,41 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/http'
+require 'chef/http/authenticator'
+require 'chef/http/cookie_manager'
+require 'chef/http/decompressor'
+require 'chef/http/json_input'
+require 'chef/http/json_output'
+
+class Chef
+  class ServerAPI < Chef::HTTP
+
+    def initialize(url = Chef::Config[:chef_server_url], options = {})
+      options[:client_name] ||= Chef::Config[:node_name]
+      options[:signing_key_filename] ||= Chef::Config[:client_key]
+      super(url, options)
+    end
+
+    use Chef::HTTP::JSONInput
+    use Chef::HTTP::JSONOutput
+    use Chef::HTTP::CookieManager
+    use Chef::HTTP::Decompressor
+    use Chef::HTTP::Authenticator
+  end
+end
\ No newline at end of file
diff --git a/lib/chef/shef.rb b/lib/chef/shef.rb
deleted file mode 100644
index a971918..0000000
--- a/lib/chef/shef.rb
+++ /dev/null
@@ -1,327 +0,0 @@
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
-# 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 'singleton'
-require 'pp'
-require 'etc'
-require 'mixlib/cli'
-
-require 'chef'
-require 'chef/version'
-require 'chef/client'
-require 'chef/config'
-
-require 'chef/shef/shef_session'
-require 'chef/shef/ext'
-require 'chef/json_compat'
-
-# = Shef
-# Shef is Chef in an IRB session. Shef can interact with a Chef server via the
-# REST API, and run and debug recipes interactively.
-module Shef
-  LEADERS = Hash.new("")
-  LEADERS[Chef::Recipe] = ":recipe"
-  LEADERS[Chef::Node]   = ":attributes"
-
-  class << self
-    attr_accessor :client_type
-    attr_accessor :options
-    attr_accessor :env
-    attr_writer   :editor
-  end
-
-  # Start the irb REPL with shef's customizations
-  def self.start
-    setup_logger
-    # FUGLY HACK: irb gives us no other choice.
-    irb_help = [:help, :irb_help, IRB::ExtendCommandBundle::NO_OVERRIDE]
-    IRB::ExtendCommandBundle.instance_variable_get(:@ALIASES).delete(irb_help)
-
-    parse_opts
-
-    # HACK: this duplicates the functions of IRB.start, but we have to do it
-    # to get access to the main object before irb starts.
-    ::IRB.setup(nil)
-
-    irb = IRB::Irb.new
-
-    init(irb.context.main)
-
-
-    irb_conf[:IRB_RC].call(irb.context) if irb_conf[:IRB_RC]
-    irb_conf[:MAIN_CONTEXT] = irb.context
-
-    trap("SIGINT") do
-      irb.signal_handle
-    end
-
-    catch(:IRB_EXIT) do
-      irb.eval_input
-    end
-  end
-
-  def self.setup_logger
-    Chef::Config[:log_level] ||= :warn
-    Chef::Log.init(STDERR)
-    Mixlib::Authentication::Log.logger = Ohai::Log.logger = Chef::Log.logger
-    Chef::Log.level = Chef::Config[:log_level] || :warn
-  end
-
-  # Shef assumes it's running whenever it is defined
-  def self.running?
-    true
-  end
-
-  # Set the irb_conf object to something other than IRB.conf
-  # usful for testing.
-  def self.irb_conf=(conf_hash)
-    @irb_conf = conf_hash
-  end
-
-  def self.irb_conf
-    @irb_conf || IRB.conf
-  end
-
-  def self.configure_irb
-    irb_conf[:HISTORY_FILE] = "~/.shef_history"
-    irb_conf[:SAVE_HISTORY] = 1000
-
-    irb_conf[:IRB_RC] = lambda do |conf|
-      m = conf.main
-
-      conf.prompt_c       = "chef#{leader(m)} > "
-      conf.return_format  = " => %s \n"
-      conf.prompt_i       = "chef#{leader(m)} > "
-      conf.prompt_n       = "chef#{leader(m)} ?> "
-      conf.prompt_s       = "chef#{leader(m)}%l> "
-    end
-  end
-
-  def self.leader(main_object)
-    env_string = Shef.env ? " (#{Shef.env})" : ""
-    LEADERS[main_object.class] + env_string
-  end
-
-  def self.session
-    unless client_type.instance.node_built?
-      puts "Session type: #{client_type.session_type}"
-      client_type.instance.reset!
-    end
-    client_type.instance
-  end
-
-  def self.init(main)
-    parse_json
-    configure_irb
-
-    session # trigger ohai run + session load
-
-    session.node.consume_attributes(@json_attribs)
-
-    Extensions.extend_context_object(main)
-
-    main.version
-    puts
-
-    puts "run `help' for help, `exit' or ^D to quit."
-    puts
-    puts "Ohai2u#{greeting}!"
-  end
-
-  def self.greeting
-    " #{Etc.getlogin}@#{Shef.session.node.fqdn}"
-  rescue NameError, ArgumentError
-    ""
-  end
-
-  def self.parse_json
-    # HACK: copied verbatim from chef/application/client, because it's not
-    # reusable as written there :(
-    if Chef::Config[:json_attribs]
-      begin
-        json_io = open(Chef::Config[:json_attribs])
-      rescue SocketError => error
-        fatal!("I cannot connect to #{Chef::Config[:json_attribs]}", 2)
-      rescue Errno::ENOENT => error
-        fatal!("I cannot find #{Chef::Config[:json_attribs]}", 2)
-      rescue Errno::EACCES => error
-        fatal!("Permissions are incorrect on #{Chef::Config[:json_attribs]}. Please chmod a+r #{Chef::Config[:json_attribs]}", 2)
-      rescue Exception => error
-        fatal!("Got an unexpected error reading #{Chef::Config[:json_attribs]}: #{error.message}", 2)
-      end
-
-      begin
-        @json_attribs = Chef::JSONCompat.from_json(json_io.read)
-      rescue JSON::ParserError => error
-        fatal!("Could not parse the provided JSON file (#{Chef::Config[:json_attribs]})!: " + error.message, 2)
-      end
-    end
-  end
-
-  def self.fatal!(message, exit_status)
-    Chef::Log.fatal(message)
-    exit exit_status
-  end
-
-  def self.client_type
-    type = Shef::StandAloneSession
-    type = Shef::SoloSession   if Chef::Config[:shef_solo]
-    type = Shef::ClientSession if Chef::Config[:client]
-    type = Shef::DoppelGangerSession if Chef::Config[:doppelganger]
-    type
-  end
-
-  def self.parse_opts
-    @options = Options.new
-    @options.parse_opts
-  end
-
-  def self.editor
-    @editor || Chef::Config[:editor] || ENV['EDITOR']
-  end
-
-  class Options
-    include Mixlib::CLI
-
-    def self.footer(text=nil)
-      @footer = text if text
-      @footer
-    end
-
-    banner("shef #{Chef::VERSION}\n\nUsage: shef [NAMED_CONF] (OPTIONS)")
-
-    footer(<<-FOOTER)
-When no CONFIG is specified, shef attempts to load a default configuration file:
-* If a NAMED_CONF is given, shef will load ~/.chef/NAMED_CONF/shef.rb
-* If no NAMED_CONF is given shef will load ~/.chef/shef.rb if it exists
-* Shef falls back to loading /etc/chef/client.rb or /etc/chef/solo.rb if -z or
-  -s options are given and no shef.rb can be found. 
-FOOTER
-
-    option :config_file,
-      :short => "-c CONFIG",
-      :long  => "--config CONFIG",
-      :description => "The configuration file to use"
-
-    option :help,
-      :short        => "-h",
-      :long         => "--help",
-      :description  => "Show this message",
-      :on           => :tail,
-      :boolean      => true,
-      :proc         => proc { print_help }
-
-    option :log_level,
-      :short  => "-l LOG_LEVEL",
-      :long   => '--log-level LOG_LEVEL',
-      :description => "Set the logging level",
-      :proc         => proc { |level| Chef::Log.level = level.to_sym }
-
-    option :standalone,
-      :short        => "-a",
-      :long         => "--standalone",
-      :description  => "standalone shef session",
-      :default      => true,
-      :boolean      => true
-
-    option :shef_solo,
-      :short        => "-s",
-      :long         => "--solo",
-      :description  => "chef-solo shef session",
-      :boolean      => true,
-      :proc         => proc {Chef::Config[:solo] = true}
-
-    option :client,
-      :short        => "-z",
-      :long         => "--client",
-      :description  => "chef-client shef session",
-      :boolean      => true
-
-    option :json_attribs,
-      :short => "-j JSON_ATTRIBS",
-      :long => "--json-attributes JSON_ATTRIBS",
-      :description => "Load attributes from a JSON file or URL",
-      :proc => nil
-
-    option :chef_server_url,
-      :short => "-S CHEFSERVERURL",
-      :long => "--server CHEFSERVERURL",
-      :description => "The chef server URL",
-      :proc => nil
-
-    option :version,
-      :short        => "-v",
-      :long         => "--version",
-      :description  => "Show chef version",
-      :boolean      => true,
-      :proc         => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
-      :exit         => 0
-
-    def self.print_help
-      instance = new
-      instance.parse_options([])
-      puts instance.opt_parser
-      puts
-      puts footer
-      puts
-      exit 1
-    end
-
-    def self.setup!
-      self.new.parse_opts
-    end
-
-    def parse_opts
-      remainder = parse_options
-      environment = remainder.first
-      # We have to nuke ARGV to make sure irb's option parser never sees it.
-      # otherwise, IRB complains about command line switches it doesn't recognize.
-      ARGV.clear
-      config[:config_file] = config_file_for_shef_mode(environment)
-      config_msg = config[:config_file] || "none (standalone shef session)"
-      puts "loading configuration: #{config_msg}"
-      Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exists?(config[:config_file]) && File.readable?(config[:config_file])
-      Chef::Config.merge!(config)
-    end
-
-    private
-
-    def config_file_for_shef_mode(environment)
-      if config[:config_file]
-        config[:config_file]
-      elsif environment && ENV['HOME']
-        Shef.env = environment
-        config_file_to_try = ::File.join(ENV['HOME'], '.chef', environment, 'shef.rb')
-        unless ::File.exist?(config_file_to_try)
-          puts "could not find shef config for environment #{environment} at #{config_file_to_try}"
-          exit 1
-        end
-        config_file_to_try
-      elsif ENV['HOME'] && ::File.exist?(File.join(ENV['HOME'], '.chef', 'shef.rb'))
-        File.join(ENV['HOME'], '.chef', 'shef.rb')
-      elsif config[:solo]
-        "/etc/chef/solo.rb"
-      elsif config[:client]
-        "/etc/chef/client.rb"
-      else
-        nil
-      end
-    end
-
-  end
-
-end
diff --git a/lib/chef/shef/ext.rb b/lib/chef/shef/ext.rb
index d7131d6..8f03de2 100644
--- a/lib/chef/shef/ext.rb
+++ b/lib/chef/shef/ext.rb
@@ -1,6 +1,6 @@
 #--
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# Author:: Joshua Timberman (<joshua 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");
@@ -16,554 +16,4 @@
 # limitations under the License.
 #
 
-require 'tempfile'
-require 'chef/recipe'
-require 'fileutils'
-require 'chef/version'
-require 'chef/shef/shef_session'
-require 'chef/shef/model_wrapper'
-require 'chef/shef/shef_rest'
-require 'chef/json_compat'
-
-module Shef
-  module Extensions
-
-    Help = Struct.new(:cmd, :desc, :explanation)
-
-    # Extensions to be included in every 'main' object in shef. These objects
-    # are extended with this module.
-    module ObjectCoreExtensions
-
-      def ensure_session_select_defined
-        # irb breaks if you prematurely define IRB::JobMangager
-        # so these methods need to be defined at the latest possible time.
-        unless jobs.respond_to?(:select_session_by_context)
-          def jobs.select_session_by_context(&block)
-            @jobs.select { |job| block.call(job[1].context.main)}
-          end
-        end
-
-        unless jobs.respond_to?(:session_select)
-          def jobs.select_shef_session(target_context)
-            session = if target_context.kind_of?(Class)
-              select_session_by_context { |main| main.kind_of?(target_context) }
-            else
-              select_session_by_context { |main| main.equal?(target_context) }
-            end
-            Array(session.first)[1]
-          end
-        end
-      end
-
-      def find_or_create_session_for(context_obj)
-        ensure_session_select_defined
-        if subsession = jobs.select_shef_session(context_obj)
-          jobs.switch(subsession)
-        else
-          irb(context_obj)
-        end
-      end
-
-      def help_banner
-        banner = []
-        banner << ""
-        banner << "Shef Help"
-        banner << "".ljust(80, "=")
-        banner << "| " + "Command".ljust(25) + "| " + "Description"
-        banner << "".ljust(80, "=")
-
-        self.all_help_descriptions.each do |help_text|
-          banner << "| " + help_text.cmd.ljust(25) + "| " + help_text.desc
-        end
-        banner << "".ljust(80, "=")
-        banner << "\n"
-        banner << "Use help(:command) to get detailed help with individual commands"
-        banner << "\n"
-        banner.join("\n")
-      end
-
-      def explain_command(method_name)
-        help = self.all_help_descriptions.find { |h| h.cmd.to_s == method_name.to_s }
-        if help
-          puts ""
-          puts "Command: #{method_name}"
-          puts "".ljust(80, "=")
-          puts help.explanation || help.desc
-          puts "".ljust(80, "=")
-          puts ""
-        else
-          puts ""
-          puts "command #{method_name} not found or no help available"
-          puts ""
-        end
-      end
-
-      # helpfully returns +:on+ so we can have sugary syntax like `tracing on'
-      def on
-        :on
-      end
-
-      # returns +:off+ so you can just do `tracing off'
-      def off
-        :off
-      end
-
-      def help_descriptions
-        @help_descriptions ||= []
-      end
-
-      def all_help_descriptions
-        help_descriptions
-      end
-
-      def desc(help_text)
-        @desc = help_text
-      end
-
-      def explain(explain_text)
-        @explain = explain_text
-      end
-
-      def subcommands(subcommand_help={})
-        @subcommand_help = subcommand_help
-      end
-
-      def singleton_method_added(mname)
-        if @desc
-          help_descriptions << Help.new(mname.to_s, @desc.to_s, @explain)
-          @desc, @explain = nil, nil
-        end
-        if @subcommand_help
-          @subcommand_help.each do |subcommand, text|
-            help_descriptions << Help.new("#{mname}.#{subcommand}", text.to_s, nil)
-          end
-        end
-        @subcommand_help = {}
-      end
-
-    end
-
-    module String
-      def on_off_to_bool
-        case self
-        when "on"
-          true
-        when "off"
-          false
-        else
-          self
-        end
-      end
-    end
-
-    module Symbol
-      def on_off_to_bool
-        self.to_s.on_off_to_bool
-      end
-    end
-
-    module TrueClass
-      def to_on_off_str
-        "on"
-      end
-
-      def on_off_to_bool
-        self
-      end
-    end
-
-    module FalseClass
-      def to_on_off_str
-        "off"
-      end
-
-      def on_off_to_bool
-        self
-      end
-    end
-
-    # Methods that have associated help text need to be dynamically added
-    # to the main irb objects, so we define them in a proc and later
-    # instance_eval the proc in the object.
-    ObjectUIExtensions = Proc.new do
-      extend Shef::Extensions::ObjectCoreExtensions
-
-      desc "prints this help message"
-      explain(<<-E)
-## SUMMARY ##
-  When called with no argument, +help+ prints a table of all shef commands. When
-  called with an argument COMMAND, +help+ prints a detailed explanation of the
-  command if available, or the description if no explanation is available.
-E
-      def help(commmand=nil)
-        if commmand
-          explain_command(commmand)
-        else
-          puts help_banner
-        end
-        :ucanhaz_halp
-      end
-      alias :halp :help
-
-      desc "prints information about chef"
-      def version
-        puts  "This is shef, the Chef shell.\n" +
-              " Chef Version: #{::Chef::VERSION}\n" +
-              " http://www.opscode.com/chef\n" +
-              " http://wiki.opscode.com/display/chef/Home"
-        :ucanhaz_automation
-      end
-      alias :shef :version
-
-      desc "switch to recipe mode"
-      def recipe
-        find_or_create_session_for Shef.session.recipe
-        :recipe
-      end
-
-      desc "switch to attributes mode"
-      def attributes
-        find_or_create_session_for Shef.session.node
-        :attributes
-      end
-
-      desc "run chef using the current recipe"
-      def run_chef
-        Chef::Log.level = :debug
-        session = Shef.session
-        runrun = Chef::Runner.new(session.run_context).converge
-        Chef::Log.level = :info
-        runrun
-      end
-
-      desc "returns an object to control a paused chef run"
-      subcommands :resume       => "resume the chef run",
-                  :step         => "run only the next resource",
-                  :skip_back    => "move back in the run list",
-                  :skip_forward => "move forward in the run list"
-      def chef_run
-        Shef.session.resource_collection.iterator
-      end
-
-      desc "resets the current recipe"
-      def reset
-        Shef.session.reset!
-      end
-
-      desc "assume the identity of another node."
-      def become_node(node_name)
-        Shef::DoppelGangerSession.instance.assume_identity(node_name)
-        :doppelganger
-      end
-      alias :doppelganger :become_node
-
-      desc "turns printout of return values on or off"
-      def echo(on_or_off)
-        conf.echo = on_or_off.on_off_to_bool
-      end
-
-      desc "says if echo is on or off"
-      def echo?
-        puts "echo is #{conf.echo.to_on_off_str}"
-      end
-
-      desc "turns on or off tracing of execution. *verbose*"
-      def tracing(on_or_off)
-        conf.use_tracer = on_or_off.on_off_to_bool
-        tracing?
-      end
-      alias :trace :tracing
-
-      desc "says if tracing is on or off"
-      def tracing?
-        puts "tracing is #{conf.use_tracer.to_on_off_str}"
-      end
-      alias :trace? :tracing?
-
-      desc "simple ls style command"
-      def ls(directory)
-        Dir.entries(directory)
-      end
-    end
-
-    MainContextExtensions = Proc.new do
-      desc "returns the current node (i.e., this host)"
-      def node
-        Shef.session.node
-      end
-
-      desc "pretty print the node's attributes"
-      def ohai(key=nil)
-        pp(key ? node.attribute[key] : node.attribute)
-      end
-    end
-
-    RESTApiExtensions = Proc.new do
-      desc "edit an object in your EDITOR"
-      explain(<<-E)
-## SUMMARY ##
-  +edit(object)+ allows you to edit any object that can be converted to JSON.
-  When finished editing, this method will return the edited object:
-
-      new_node = edit(existing_node)
-
-## EDITOR SELECTION ##
-  Shef looks for an editor using the following logic
-  1. Looks for an EDITOR set by Shef.editor = "EDITOR"
-  2. Looks for an EDITOR configured in your shef config file
-  3. Uses the value of the EDITOR environment variable
-E
-      def edit(object)
-        unless Shef.editor
-          puts "Please set your editor with Shef.editor = \"vim|emacs|mate|ed\""
-          return :failburger
-        end
-
-        filename = "shef-edit-#{object.class.name}-"
-        if object.respond_to?(:name)
-          filename += object.name
-        elsif object.respond_to?(:id)
-          filename += object.id
-        end
-
-        edited_data = Tempfile.open([filename, ".js"]) do |tempfile|
-          tempfile.sync = true
-          tempfile.puts Chef::JSONCompat.to_json(object)
-          system("#{Shef.editor.to_s} #{tempfile.path}")
-          tempfile.rewind
-          tempfile.read
-        end
-
-        Chef::JSONCompat.from_json(edited_data)
-      end
-
-      desc "Find and edit API clients"
-      explain(<<-E)
-## SUMMARY ##
-  +clients+ allows you to query you chef server for information about your api
-  clients.
-
-## LIST ALL CLIENTS ##
-  To see all clients on the system, use
-
-      clients.all #=> [<Chef::ApiClient...>, ...]
-
-  If the output from all is too verbose, or you're only interested in a specific
-  value from each of the objects, you can give a code block to +all+:
-
-      clients.all { |client| client.name } #=> [CLIENT1_NAME, CLIENT2_NAME, ...]
-
-## SHOW ONE CLIENT ##
-  To see a specific client, use
-
-      clients.show(CLIENT_NAME)
-
-## SEARCH FOR CLIENTS ##
-  You can also search for clients using +find+ or +search+. You can use the
-  familiar string search syntax:
-
-      clients.search("KEY:VALUE")
-
-  Just as the +all+ subcommand, the +search+ subcommand can use a code block to
-  filter or transform the information returned from the search:
-
-      clients.search("KEY:VALUE") { |c| c.name }
-
-  You can also use a Hash based syntax, multiple search conditions will be 
-  joined with AND.
-
-      clients.find :KEY => :VALUE, :KEY2 => :VALUE2, ...
-
-## BULK-EDIT CLIENTS ##
-                    **BE CAREFUL, THIS IS DESTRUCTIVE**
-  You can bulk edit API Clients using the +transform+ subcommand, which requires
-  a code block. Each client will be saved after the code block is run. If the
-  code block returns +nil+ or +false+, that client will be skipped:
-
-      clients.transform("*:*") do |client|
-        if client.name =~ /borat/i
-          client.admin(false)
-          true
-        else
-          nil
-        end
-      end
-
-  This will strip the admin privileges from any client named after borat.
-E
-      subcommands :all        => "list all api clients",
-                  :show       => "load an api client by name",
-                  :search     => "search for API clients",
-                  :transform  => "edit all api clients via a code block and save them"
-      def clients
-        @clients ||= Shef::ModelWrapper.new(Chef::ApiClient, :client)
-      end
-
-      desc "Find and edit cookbooks"
-      subcommands :all        => "list all cookbooks",
-                  :show       => "load a cookbook by name",
-                  :transform  => "edit all cookbooks via a code block and save them"
-      def cookbooks
-        @cookbooks ||= Shef::ModelWrapper.new(Chef::CookbookVersion)
-      end
-
-      desc "Find and edit nodes via the API"
-      explain(<<-E)
-## SUMMARY ##
-  +nodes+ Allows you to query your chef server for information about your nodes.
-
-## LIST ALL NODES ##
-  You can list all nodes using +all+ or +list+
-
-      nodes.all #=> [<Chef::Node...>, <Chef::Node...>, ...]
-
-  To limit the information returned for each node, pass a code block to the +all+
-  subcommand:
-
-      nodes.all { |node| node.name } #=> [NODE1_NAME, NODE2_NAME, ...]
-
-## SHOW ONE NODE ##
-  You can show the data for a single node using the +show+ subcommand:
-
-      nodes.show("NODE_NAME") => <Chef::Node @name="NODE_NAME" ...>
-
-## SEARCH FOR NODES ##
-  You can search for nodes using the +search+ or +find+ subcommands:
-
-      nodes.find(:name => "app*") #=> [<Chef::Node @name="app1.example.com" ...>, ...]
-
-  Similarly to +all+, you can pass a code block to limit or transform the
-  information returned:
-
-      nodes.find(:name => "app#") { |node| node.ec2 } 
-
-## BULK EDIT NODES ##
-              **BE CAREFUL, THIS OPERATION IS DESTRUCTIVE**
-
-  Bulk edit nodes by passing a code block to the +transform+ or +bulk_edit+
-  subcommand. The block will be applied to each matching node, and then the node
-  will be saved. If the block returns +nil+ or +false+, that node will be 
-  skipped.
-
-      nodes.transform do |node|
-        if node.fqdn =~ /.*\\.preprod\\.example\\.com/
-          node.set[:environment] = "preprod"
-        end
-      end
-
-  This will assign the attribute to every node with a FQDN matching the regex.
-E
-      subcommands :all        => "list all nodes",
-                  :show       => "load a node by name",
-                  :search     => "search for nodes",
-                  :transform  => "edit all nodes via a code block and save them"
-      def nodes
-        @nodes ||= Shef::ModelWrapper.new(Chef::Node)
-      end
-
-      desc "Find and edit roles via the API"
-      explain(<<-E)
-## SUMMARY ##
-  +roles+ allows you to query and edit roles on your Chef server.
-
-## SUBCOMMANDS ##
-  * all       (list)
-  * show      (load)
-  * search    (find)
-  * transform (bulk_edit)
-
-## SEE ALSO ##
-  See the help for +nodes+ for more information about the subcommands.
-E
-      subcommands :all        => "list all roles",
-                  :show       => "load a role by name",
-                  :search     => "search for roles",
-                  :transform  => "edit all roles via a code block and save them"
-      def roles
-        @roles ||= Shef::ModelWrapper.new(Chef::Role)
-      end
-
-      desc "Find and edit +databag_name+ via the api"
-      explain(<<-E)
-## SUMMARY ##
-  +databags(DATABAG_NAME)+ allows you to query and edit data bag items on your
-  Chef server. Unlike other commands for working with data on the server, 
-  +databags+ requires the databag name as an argument, for example:
-    databags(:users).all
-
-## SUBCOMMANDS ##
-  * all       (list)
-  * show      (load)
-  * search    (find)
-  * transform (bulk_edit)
-
-## SEE ALSO ##
-  See the help for +nodes+ for more information about the subcommands.
-
-E
-      subcommands :all        => "list all items in the data bag",
-                  :show       => "load a data bag item by id",
-                  :search     => "search for items in the data bag",
-                  :transform  => "edit all items via a code block and save them"
-      def databags(databag_name)
-        @named_databags_wrappers ||= {}
-        @named_databags_wrappers[databag_name] ||= Shef::NamedDataBagWrapper.new(databag_name)
-      end
-
-      desc "A REST Client configured to authenticate with the API"
-      def api
-        @rest = Shef::ShefREST.new(Chef::Config[:chef_server_url])
-      end
-
-    end
-
-    RecipeUIExtensions = Proc.new do
-      alias :original_resources :resources
-
-      desc "list all the resources on the current recipe"
-      def resources(*args)
-        if args.empty?
-          pp run_context.resource_collection.instance_variable_get(:@resources_by_name).keys
-        else
-          pp resources = original_resources(*args)
-          resources
-        end
-      end
-    end
-
-    def self.extend_context_object(obj)
-      obj.instance_eval(&ObjectUIExtensions)
-      obj.instance_eval(&MainContextExtensions)
-      obj.instance_eval(&RESTApiExtensions)
-      obj.extend(FileUtils)
-      obj.extend(Chef::Mixin::Language)
-    end
-
-    def self.extend_context_node(node_obj)
-      node_obj.instance_eval(&ObjectUIExtensions)
-    end
-
-    def self.extend_context_recipe(recipe_obj)
-      recipe_obj.instance_eval(&ObjectUIExtensions)
-      recipe_obj.instance_eval(&RecipeUIExtensions)
-    end
-
-  end
-end
-
-class String
-  include Shef::Extensions::String
-end
-
-class Symbol
-  include Shef::Extensions::Symbol
-end
-
-class TrueClass
-  include Shef::Extensions::TrueClass
-end
-
-class FalseClass
-  include Shef::Extensions::FalseClass
-end
-
+require 'chef/shell/ext'
diff --git a/lib/chef/shef/model_wrapper.rb b/lib/chef/shef/model_wrapper.rb
deleted file mode 100644
index 290230d..0000000
--- a/lib/chef/shef/model_wrapper.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-#--
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# 
-#     http://www.apache.org/licenses/LICENSE-2.0
-# 
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/mixin/convert_to_class_name'
-require 'chef/mixin/language'
-
-module Shef
-  class ModelWrapper
-
-    include Chef::Mixin::ConvertToClassName
-
-    attr_reader :model_symbol
-
-    def initialize(model_class, symbol=nil)
-      @model_class = model_class
-      @model_symbol = symbol || convert_to_snake_case(model_class.name, "Chef").to_sym
-    end
-
-    def search(query)
-      return all if query.to_s == "all"
-      results = []
-      Chef::Search::Query.new.search(@model_symbol, format_query(query)) do |obj|
-        if block_given?
-          results << yield(obj)
-        else
-          results << obj
-        end
-      end
-      results
-    end
-
-    alias :find :search
-
-    def all(&block)
-      all_objects = list_objects
-      block_given? ? all_objects.map(&block) : all_objects
-    end
-
-    alias :list :all
-
-    def show(obj_id)
-      @model_class.load(obj_id)
-    end
-
-    alias :load :show
-
-    def transform(what_to_transform, &block)
-      if what_to_transform == :all
-        objects_to_transform = list_objects
-      else
-        objects_to_transform = search(what_to_transform)
-      end
-      objects_to_transform.each do |obj|
-        if result = yield(obj)
-          obj.save
-        end
-      end
-    end
-
-    alias :bulk_edit :transform
-
-    private
-
-    # paper over inconsistencies in the model classes APIs, and return the objects
-    # the user wanted instead of the URI=>object stuff
-    def list_objects
-      objects = @model_class.method(:list).arity == 0? @model_class.list : @model_class.list(true)
-      objects.map { |obj| Array(obj).find {|o| o.kind_of?(@model_class)} }
-    end
-
-    def format_query(query)
-      if query.respond_to?(:keys)
-        query.map { |key, value| "#{key}:#{value}" }.join(" AND ")
-      else
-        query
-      end
-    end
-  end
-
-  class NamedDataBagWrapper < ModelWrapper
-
-    def initialize(databag_name)
-      @model_symbol = @databag_name = databag_name
-    end
-
-
-    alias :list :all
-
-    def show(item)
-      Chef::DataBagItem.load(@databag_name, item)
-    end
-
-    private
-
-    def list_objects
-      all_items = []
-      Chef::Search::Query.new.search(@databag_name) do |item|
-        all_items << item
-      end
-      all_items
-    end
-
-  end
-
-end
diff --git a/lib/chef/shef/shef_rest.rb b/lib/chef/shef/shef_rest.rb
deleted file mode 100644
index c067728..0000000
--- a/lib/chef/shef/shef_rest.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-#--
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-# 
-#     http://www.apache.org/licenses/LICENSE-2.0
-# 
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-module Shef
-  class ShefREST < Chef::REST
-
-    alias :get    :get_rest
-    alias :put    :put_rest
-    alias :post   :post_rest
-    alias :delete :delete_rest
-
-  end
-end
diff --git a/lib/chef/shef/shef_session.rb b/lib/chef/shef/shef_session.rb
deleted file mode 100644
index 465a081..0000000
--- a/lib/chef/shef/shef_session.rb
+++ /dev/null
@@ -1,284 +0,0 @@
-#--
-# Author:: Daniel DeLeo (<dan at kallistec.com>)
-# Author:: Tim Hinderliter (<tim at opscode.com>)
-# Copyright:: Copyright (c) 2009 Daniel DeLeo
-# Copyright:: Copyright (c) 2011 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/recipe'
-require 'chef/run_context'
-require 'chef/config'
-require 'chef/client'
-require 'chef/cookbook/cookbook_collection'
-require 'chef/cookbook_loader'
-require 'chef/run_list/run_list_expansion'
-
-module Shef
-  class ShefSession
-    include Singleton
-
-    def self.session_type(type=nil)
-      @session_type = type if type
-      @session_type
-    end
-
-    attr_accessor :node, :compile, :recipe, :run_context
-    attr_reader :node_attributes, :client
-    def initialize
-      @node_built = false
-    end
-
-    def node_built?
-      !!@node_built
-    end
-
-    def reset!
-      loading do
-        rebuild_node
-        @node = client.node
-        shorten_node_inspect
-        Shef::Extensions.extend_context_node(@node)
-        rebuild_context
-        node.consume_attributes(node_attributes) if node_attributes
-        @recipe = Chef::Recipe.new(nil, nil, run_context)
-        Shef::Extensions.extend_context_recipe(@recipe)
-        @node_built = true
-      end
-    end
-
-    def node_attributes=(attrs)
-      @node_attributes = attrs
-      @node.consume_attributes(@node_attributes)
-    end
-
-    def resource_collection
-      run_context.resource_collection
-    end
-
-    def run_context
-      @run_context ||= rebuild_context
-    end
-
-    def definitions
-      nil
-    end
-
-    def cookbook_loader
-      nil
-    end
-
-    def save_node
-      raise "Not Supported! #{self.class.name} doesn't support #save_node, maybe you need to run shef in client mode?"
-    end
-
-    def rebuild_context
-      raise "Not Implemented! :rebuild_collection should be implemented by subclasses"
-    end
-
-    private
-
-    def loading
-      show_loading_progress
-      begin
-        yield
-      rescue => e
-        loading_complete(false)
-        raise e
-      else
-        loading_complete(true)
-      end
-    end
-
-    def show_loading_progress
-      print "Loading"
-      @loading = true
-      @dot_printer = Thread.new do
-        while @loading
-          print "."
-          sleep 0.5
-        end
-      end
-    end
-
-    def loading_complete(success)
-      @loading = false
-      @dot_printer.join
-      msg = success ? "done.\n\n" : "epic fail!\n\n"
-      print msg
-    end
-
-    def shorten_node_inspect
-      def @node.inspect
-        "<Chef::Node:0x#{self.object_id.to_s(16)} @name=\"#{self.name}\">"
-      end
-    end
-
-    def rebuild_node
-      raise "Not Implemented! :rebuild_node should be implemented by subclasses"
-    end
-
-  end
-
-  class StandAloneSession < ShefSession
-
-    session_type :standalone
-
-    def rebuild_context
-      @run_context = Chef::RunContext.new(@node, {}) # no recipes
-      @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", [])) # empty recipe list
-    end
-
-    private
-
-    def rebuild_node
-      Chef::Config[:solo] = true
-      @client = Chef::Client.new
-      @client.run_ohai
-      @client.build_node
-    end
-
-  end
-
-  class SoloSession < ShefSession
-
-    session_type :solo
-
-    def definitions
-      @run_context.definitions
-    end
-
-    def rebuild_context
-      @run_status = Chef::RunStatus.new(@node)
-      Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, Chef::Config[:cookbook_path]) }
-      @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new(Chef::CookbookLoader.new(Chef::Config[:cookbook_path])))
-      @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", []))
-      @run_status.run_context = run_context
-    end
-
-    private
-
-    def rebuild_node
-      # Tell the client we're chef solo so it won't try to contact the server
-      Chef::Config[:solo] = true
-      @client = Chef::Client.new
-      @client.run_ohai
-      @client.build_node
-    end
-
-  end
-
-  class ClientSession < SoloSession
-
-    session_type :client
-
-    def save_node
-      @client.save_node
-    end
-
-    def rebuild_context
-      @run_status = Chef::RunStatus.new(@node)
-      Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, Chef::REST.new(Chef::Config[:server_url])) }
-      cookbook_hash = @client.sync_cookbooks
-      @run_context = Chef::RunContext.new(node, Chef::CookbookCollection.new(cookbook_hash))
-      @run_context.load(Chef::RunList::RunListExpansionFromAPI.new("_default", []))
-      @run_status.run_context = run_context
-    end
-
-    private
-
-    def rebuild_node
-      # Make sure the client knows this is not chef solo
-      Chef::Config[:solo] = false
-      @client = Chef::Client.new
-      @client.run_ohai
-      @client.register
-      @client.build_node
-    end
-
-  end
-
-  class DoppelGangerClient < Chef::Client
-
-    attr_reader :node_name
-
-    def initialize(node_name)
-      @node_name = node_name
-      @ohai = Ohai::System.new
-    end
-
-    # Run the very smallest amount of ohai we can get away with and still
-    # hope to have things work. Otherwise we're not very good doppelgangers
-    def run_ohai
-      @ohai.require_plugin('os')
-    end
-
-    # DoppelGanger implementation of build_node. preserves as many of the node's
-    # attributes, and does not save updates to the server
-    def build_node
-      Chef::Log.debug("Building node object for #{@node_name}")
-      @node = Chef::Node.find_or_create(node_name)
-      ohai_data = @ohai.data.merge(@node.automatic_attrs)
-      @node.consume_external_attrs(ohai_data,nil)
-      @run_list_expansion = @node.expand!('server')
-      @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
-      Chef::Log.info("Run List is [#{@node.run_list}]")
-      Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
-      @node
-    end
-
-    def register
-      @rest = Chef::REST.new(Chef::Config[:chef_server_url], Chef::Config[:node_name], Chef::Config[:client_key])
-    end
-
-  end
-
-  class DoppelGangerSession < ClientSession
-
-    session_type "doppelganger client"
-
-    def save_node
-      puts "A doppelganger should think twice before saving the node"
-    end
-
-    def assume_identity(node_name)
-      Chef::Config[:doppelganger] = @node_name = node_name
-      reset!
-    rescue Exception => e
-      puts "#{e.class.name}: #{e.message}"
-      puts Array(e.backtrace).join("\n")
-      puts
-      puts "* " * 40
-      puts "failed to assume the identity of node '#{node_name}', resetting"
-      puts "* " * 40
-      puts
-      Chef::Config[:doppelganger] = false
-      @node_built = false
-      Shef.session
-    end
-
-    def rebuild_node
-      # Make sure the client knows this is not chef solo
-      Chef::Config[:solo] = false
-      @client = DoppelGangerClient.new(@node_name)
-      @client.run_ohai
-      @client.register
-      @client.build_node
-      @client.sync_cookbooks
-    end
-
-  end
-
-end
diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb
new file mode 100644
index 0000000..0788962
--- /dev/null
+++ b/lib/chef/shell.rb
@@ -0,0 +1,313 @@
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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 'singleton'
+require 'pp'
+require 'etc'
+require 'mixlib/cli'
+
+require 'chef'
+require 'chef/version'
+require 'chef/client'
+require 'chef/config'
+require 'chef/config_fetcher'
+
+require 'chef/shell/shell_session'
+require 'chef/shell/ext'
+require 'chef/json_compat'
+
+# = Shell
+# Shell is Chef in an IRB session. Shell can interact with a Chef server via the
+# REST API, and run and debug recipes interactively.
+module Shell
+  LEADERS = Hash.new("")
+  LEADERS[Chef::Recipe] = ":recipe"
+  LEADERS[Chef::Node]   = ":attributes"
+
+  class << self
+    attr_accessor :client_type
+    attr_accessor :options
+    attr_accessor :env
+    attr_writer   :editor
+  end
+
+  # Start the irb REPL with chef-shell's customizations
+  def self.start
+    setup_logger
+    # FUGLY HACK: irb gives us no other choice.
+    irb_help = [:help, :irb_help, IRB::ExtendCommandBundle::NO_OVERRIDE]
+    IRB::ExtendCommandBundle.instance_variable_get(:@ALIASES).delete(irb_help)
+
+    parse_opts
+
+    # HACK: this duplicates the functions of IRB.start, but we have to do it
+    # to get access to the main object before irb starts.
+    ::IRB.setup(nil)
+
+    irb = IRB::Irb.new
+
+    init(irb.context.main)
+
+
+    irb_conf[:IRB_RC].call(irb.context) if irb_conf[:IRB_RC]
+    irb_conf[:MAIN_CONTEXT] = irb.context
+
+    trap("SIGINT") do
+      irb.signal_handle
+    end
+
+    catch(:IRB_EXIT) do
+      irb.eval_input
+    end
+  end
+
+  def self.setup_logger
+    Chef::Config[:log_level] ||= :warn
+    # If log_level is auto, change it to warn
+    Chef::Config[:log_level] = :warn if Chef::Config[:log_level] == :auto
+    Chef::Log.init(STDERR)
+    Mixlib::Authentication::Log.logger = Ohai::Log.logger = Chef::Log.logger
+    Chef::Log.level = Chef::Config[:log_level] || :warn
+  end
+
+  # Shell assumes it's running whenever it is defined
+  def self.running?
+    true
+  end
+
+  # Set the irb_conf object to something other than IRB.conf
+  # usful for testing.
+  def self.irb_conf=(conf_hash)
+    @irb_conf = conf_hash
+  end
+
+  def self.irb_conf
+    @irb_conf || IRB.conf
+  end
+
+  def self.configure_irb
+    irb_conf[:HISTORY_FILE] = "~/.chef/chef_shell_history"
+    irb_conf[:SAVE_HISTORY] = 1000
+
+    irb_conf[:IRB_RC] = lambda do |conf|
+      m = conf.main
+
+      conf.prompt_c       = "chef#{leader(m)} > "
+      conf.return_format  = " => %s \n"
+      conf.prompt_i       = "chef#{leader(m)} > "
+      conf.prompt_n       = "chef#{leader(m)} ?> "
+      conf.prompt_s       = "chef#{leader(m)}%l> "
+    end
+  end
+
+  def self.leader(main_object)
+    env_string = Shell.env ? " (#{Shell.env})" : ""
+    LEADERS[main_object.class] + env_string
+  end
+
+  def self.session
+    unless client_type.instance.node_built?
+      puts "Session type: #{client_type.session_type}"
+      client_type.instance.reset!
+    end
+    client_type.instance
+  end
+
+  def self.init(main)
+    parse_json
+    configure_irb
+
+    session # trigger ohai run + session load
+
+    session.node.consume_attributes(@json_attribs)
+
+    Extensions.extend_context_object(main)
+
+    main.version
+    puts
+
+    puts "run `help' for help, `exit' or ^D to quit."
+    puts
+    puts "Ohai2u#{greeting}!"
+  end
+
+  def self.greeting
+    " #{Etc.getlogin}@#{Shell.session.node.fqdn}"
+  rescue NameError, ArgumentError
+    ""
+  end
+
+  def self.parse_json
+    if Chef::Config[:json_attribs]
+      config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs])
+      @json_attribs = config_fetcher.fetch_json
+    end
+  end
+
+  def self.fatal!(message, exit_status)
+    Chef::Log.fatal(message)
+    exit exit_status
+  end
+
+  def self.client_type
+    type = Shell::StandAloneSession
+    type = Shell::SoloSession   if Chef::Config[:shell_solo]
+    type = Shell::ClientSession if Chef::Config[:client]
+    type = Shell::DoppelGangerSession if Chef::Config[:doppelganger]
+    type
+  end
+
+  def self.parse_opts
+    @options = Options.new
+    @options.parse_opts
+  end
+
+  def self.editor
+    @editor || Chef::Config[:editor] || ENV['EDITOR']
+  end
+
+  class Options
+    include Mixlib::CLI
+
+    def self.footer(text=nil)
+      @footer = text if text
+      @footer
+    end
+
+    banner("chef-shell #{Chef::VERSION}\n\nUsage: chef-shell [NAMED_CONF] (OPTIONS)")
+
+    footer(<<-FOOTER)
+When no CONFIG is specified, chef-shell attempts to load a default configuration file:
+* If a NAMED_CONF is given, chef-shell will load ~/.chef/NAMED_CONF/chef_shell.rb
+* If no NAMED_CONF is given chef-shell will load ~/.chef/chef_shell.rb if it exists
+* chef-shell falls back to loading /etc/chef/client.rb or /etc/chef/solo.rb if -z or
+  -s options are given and no chef_shell.rb can be found.
+FOOTER
+
+    option :config_file,
+      :short => "-c CONFIG",
+      :long  => "--config CONFIG",
+      :description => "The configuration file to use"
+
+    option :help,
+      :short        => "-h",
+      :long         => "--help",
+      :description  => "Show this message",
+      :on           => :tail,
+      :boolean      => true,
+      :proc         => proc { print_help }
+
+    option :log_level,
+      :short  => "-l LOG_LEVEL",
+      :long   => '--log-level LOG_LEVEL',
+      :description => "Set the logging level",
+      :proc         => proc { |level| Chef::Config.log_level = level.to_sym; Shell.setup_logger }
+
+    option :standalone,
+      :short        => "-a",
+      :long         => "--standalone",
+      :description  => "standalone session",
+      :default      => true,
+      :boolean      => true
+
+    option :shell_solo,
+      :short        => "-s",
+      :long         => "--solo",
+      :description  => "chef-solo session",
+      :boolean      => true,
+      :proc         => proc {Chef::Config[:solo] = true}
+
+    option :client,
+      :short        => "-z",
+      :long         => "--client",
+      :description  => "chef-client session",
+      :boolean      => true
+
+    option :json_attribs,
+      :short => "-j JSON_ATTRIBS",
+      :long => "--json-attributes JSON_ATTRIBS",
+      :description => "Load attributes from a JSON file or URL",
+      :proc => nil
+
+    option :chef_server_url,
+      :short => "-S CHEFSERVERURL",
+      :long => "--server CHEFSERVERURL",
+      :description => "The chef server URL",
+      :proc => nil
+
+    option :version,
+      :short        => "-v",
+      :long         => "--version",
+      :description  => "Show chef version",
+      :boolean      => true,
+      :proc         => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
+      :exit         => 0
+
+    def self.print_help
+      instance = new
+      instance.parse_options([])
+      puts instance.opt_parser
+      puts
+      puts footer
+      puts
+      exit 1
+    end
+
+    def self.setup!
+      self.new.parse_opts
+    end
+
+    def parse_opts
+      remainder = parse_options
+      environment = remainder.first
+      # We have to nuke ARGV to make sure irb's option parser never sees it.
+      # otherwise, IRB complains about command line switches it doesn't recognize.
+      ARGV.clear
+      config[:config_file] = config_file_for_shell_mode(environment)
+      config_msg = config[:config_file] || "none (standalone session)"
+      puts "loading configuration: #{config_msg}"
+      Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exists?(config[:config_file]) && File.readable?(config[:config_file])
+      Chef::Config.merge!(config)
+    end
+
+    private
+
+    def config_file_for_shell_mode(environment)
+      if config[:config_file]
+        config[:config_file]
+      elsif environment && ENV['HOME']
+        Shell.env = environment
+        config_file_to_try = ::File.join(ENV['HOME'], '.chef', environment, 'chef_shell.rb')
+        unless ::File.exist?(config_file_to_try)
+          puts "could not find chef-shell config for environment #{environment} at #{config_file_to_try}"
+          exit 1
+        end
+        config_file_to_try
+      elsif ENV['HOME'] && ::File.exist?(File.join(ENV['HOME'], '.chef', 'chef_shell.rb'))
+        File.join(ENV['HOME'], '.chef', 'chef_shell.rb')
+      elsif config[:solo]
+        "/etc/chef/solo.rb"
+      elsif config[:client]
+        "/etc/chef/client.rb"
+      else
+        nil
+      end
+    end
+
+  end
+
+end
diff --git a/lib/chef/shell/ext.rb b/lib/chef/shell/ext.rb
new file mode 100644
index 0000000..30d841e
--- /dev/null
+++ b/lib/chef/shell/ext.rb
@@ -0,0 +1,593 @@
+#--
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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 'tempfile'
+require 'chef/recipe'
+require 'fileutils'
+require 'chef/dsl/platform_introspection'
+require 'chef/version'
+require 'chef/shell/shell_session'
+require 'chef/shell/model_wrapper'
+require 'chef/shell/shell_rest'
+require 'chef/json_compat'
+
+module Shell
+  module Extensions
+
+    Help = Struct.new(:cmd, :desc, :explanation)
+
+    # Extensions to be included in every 'main' object in chef-shell.
+    # These objects are extended with this module.
+    module ObjectCoreExtensions
+
+      def ensure_session_select_defined
+        # irb breaks if you prematurely define IRB::JobMangager
+        # so these methods need to be defined at the latest possible time.
+        unless jobs.respond_to?(:select_session_by_context)
+          def jobs.select_session_by_context(&block)
+            @jobs.select { |job| block.call(job[1].context.main)}
+          end
+        end
+
+        unless jobs.respond_to?(:session_select)
+          def jobs.select_shell_session(target_context)
+            session = if target_context.kind_of?(Class)
+              select_session_by_context { |main| main.kind_of?(target_context) }
+            else
+              select_session_by_context { |main| main.equal?(target_context) }
+            end
+            Array(session.first)[1]
+          end
+        end
+      end
+
+      def find_or_create_session_for(context_obj)
+        ensure_session_select_defined
+        if subsession = jobs.select_shell_session(context_obj)
+          jobs.switch(subsession)
+        else
+          irb(context_obj)
+        end
+      end
+
+      def help_banner
+        banner = []
+        banner << ""
+        banner << "chef-shell Help"
+        banner << "".ljust(80, "=")
+        banner << "| " + "Command".ljust(25) + "| " + "Description"
+        banner << "".ljust(80, "=")
+
+        self.all_help_descriptions.each do |help_text|
+          banner << "| " + help_text.cmd.ljust(25) + "| " + help_text.desc
+        end
+        banner << "".ljust(80, "=")
+        banner << "\n"
+        banner << "Use help(:command) to get detailed help with individual commands"
+        banner << "\n"
+        banner.join("\n")
+      end
+
+      def explain_command(method_name)
+        help = self.all_help_descriptions.find { |h| h.cmd.to_s == method_name.to_s }
+        if help
+          puts ""
+          puts "Command: #{method_name}"
+          puts "".ljust(80, "=")
+          puts help.explanation || help.desc
+          puts "".ljust(80, "=")
+          puts ""
+        else
+          puts ""
+          puts "command #{method_name} not found or no help available"
+          puts ""
+        end
+      end
+
+      # helpfully returns +:on+ so we can have sugary syntax like `tracing on'
+      def on
+        :on
+      end
+
+      # returns +:off+ so you can just do `tracing off'
+      def off
+        :off
+      end
+
+      def help_descriptions
+        @help_descriptions ||= []
+      end
+
+      def all_help_descriptions
+        help_descriptions
+      end
+
+      def desc(help_text)
+        @desc = help_text
+      end
+
+      def explain(explain_text)
+        @explain = explain_text
+      end
+
+      def subcommands(subcommand_help={})
+        @subcommand_help = subcommand_help
+      end
+
+      def singleton_method_added(mname)
+        if @desc
+          help_descriptions << Help.new(mname.to_s, @desc.to_s, @explain)
+          @desc, @explain = nil, nil
+        end
+        if @subcommand_help
+          @subcommand_help.each do |subcommand, text|
+            help_descriptions << Help.new("#{mname}.#{subcommand}", text.to_s, nil)
+          end
+        end
+        @subcommand_help = {}
+      end
+
+    end
+
+    module String
+      def on_off_to_bool
+        case self
+        when "on"
+          true
+        when "off"
+          false
+        else
+          self
+        end
+      end
+    end
+
+    module Symbol
+      def on_off_to_bool
+        self.to_s.on_off_to_bool
+      end
+    end
+
+    module TrueClass
+      def to_on_off_str
+        "on"
+      end
+
+      def on_off_to_bool
+        self
+      end
+    end
+
+    module FalseClass
+      def to_on_off_str
+        "off"
+      end
+
+      def on_off_to_bool
+        self
+      end
+    end
+
+    # Methods that have associated help text need to be dynamically added
+    # to the main irb objects, so we define them in a proc and later
+    # instance_eval the proc in the object.
+    ObjectUIExtensions = Proc.new do
+      extend Shell::Extensions::ObjectCoreExtensions
+
+      desc "prints this help message"
+      explain(<<-E)
+## SUMMARY ##
+  When called with no argument, +help+ prints a table of all
+  chef-shell commands. When called with an argument COMMAND, +help+
+  prints a detailed explanation of the command if available, or the
+  description if no explanation is available.
+E
+      def help(commmand=nil)
+        if commmand
+          explain_command(commmand)
+        else
+          puts help_banner
+        end
+        :ucanhaz_halp
+      end
+      alias :halp :help
+
+      desc "prints information about chef"
+      def version
+        puts  "This is the chef-shell.\n" +
+              " Chef Version: #{::Chef::VERSION}\n" +
+              " http://www.opscode.com/chef\n" +
+              " http://wiki.opscode.com/display/chef/Home"
+        :ucanhaz_automation
+      end
+      alias :shell :version
+
+      desc "switch to recipe mode"
+      def recipe_mode
+        find_or_create_session_for Shell.session.recipe
+        :recipe
+      end
+
+      desc "switch to attributes mode"
+      def attributes_mode
+        find_or_create_session_for Shell.session.node
+        :attributes
+      end
+
+      desc "run chef using the current recipe"
+      def run_chef
+        Chef::Log.level = :debug
+        session = Shell.session
+        runrun = Chef::Runner.new(session.run_context).converge
+        Chef::Log.level = :info
+        runrun
+      end
+
+      desc "returns an object to control a paused chef run"
+      subcommands :resume       => "resume the chef run",
+                  :step         => "run only the next resource",
+                  :skip_back    => "move back in the run list",
+                  :skip_forward => "move forward in the run list"
+      def chef_run
+        Shell.session.resource_collection.iterator
+      end
+
+      desc "resets the current recipe"
+      def reset
+        Shell.session.reset!
+      end
+
+      desc "assume the identity of another node."
+      def become_node(node_name)
+        Shell::DoppelGangerSession.instance.assume_identity(node_name)
+        :doppelganger
+      end
+      alias :doppelganger :become_node
+
+      desc "turns printout of return values on or off"
+      def echo(on_or_off)
+        conf.echo = on_or_off.on_off_to_bool
+      end
+
+      desc "says if echo is on or off"
+      def echo?
+        puts "echo is #{conf.echo.to_on_off_str}"
+      end
+
+      desc "turns on or off tracing of execution. *verbose*"
+      def tracing(on_or_off)
+        conf.use_tracer = on_or_off.on_off_to_bool
+        tracing?
+      end
+      alias :trace :tracing
+
+      desc "says if tracing is on or off"
+      def tracing?
+        puts "tracing is #{conf.use_tracer.to_on_off_str}"
+      end
+      alias :trace? :tracing?
+
+      desc "simple ls style command"
+      def ls(directory)
+        Dir.entries(directory)
+      end
+    end
+
+    MainContextExtensions = Proc.new do
+      desc "returns the current node (i.e., this host)"
+      def node
+        Shell.session.node
+      end
+
+      desc "pretty print the node's attributes"
+      def ohai(key=nil)
+        pp(key ? node.attribute[key] : node.attribute)
+      end
+    end
+
+    RESTApiExtensions = Proc.new do
+      desc "edit an object in your EDITOR"
+      explain(<<-E)
+## SUMMARY ##
+  +edit(object)+ allows you to edit any object that can be converted to JSON.
+  When finished editing, this method will return the edited object:
+
+      new_node = edit(existing_node)
+
+## EDITOR SELECTION ##
+  chef-shell looks for an editor using the following logic
+  1. Looks for an EDITOR set by Shell.editor = "EDITOR"
+  2. Looks for an EDITOR configured in your chef-shell config file
+  3. Uses the value of the EDITOR environment variable
+E
+      def edit(object)
+        unless Shell.editor
+          puts "Please set your editor with Shell.editor = \"vim|emacs|mate|ed\""
+          return :failburger
+        end
+
+        filename = "chef-shell-edit-#{object.class.name}-"
+        if object.respond_to?(:name)
+          filename += object.name
+        elsif object.respond_to?(:id)
+          filename += object.id
+        end
+
+        edited_data = Tempfile.open([filename, ".js"]) do |tempfile|
+          tempfile.sync = true
+          tempfile.puts Chef::JSONCompat.to_json(object)
+          system("#{Shell.editor.to_s} #{tempfile.path}")
+          tempfile.rewind
+          tempfile.read
+        end
+
+        Chef::JSONCompat.from_json(edited_data)
+      end
+
+      desc "Find and edit API clients"
+      explain(<<-E)
+## SUMMARY ##
+  +clients+ allows you to query you chef server for information about your api
+  clients.
+
+## LIST ALL CLIENTS ##
+  To see all clients on the system, use
+
+      clients.all #=> [<Chef::ApiClient...>, ...]
+
+  If the output from all is too verbose, or you're only interested in a specific
+  value from each of the objects, you can give a code block to +all+:
+
+      clients.all { |client| client.name } #=> [CLIENT1_NAME, CLIENT2_NAME, ...]
+
+## SHOW ONE CLIENT ##
+  To see a specific client, use
+
+      clients.show(CLIENT_NAME)
+
+## SEARCH FOR CLIENTS ##
+  You can also search for clients using +find+ or +search+. You can use the
+  familiar string search syntax:
+
+      clients.search("KEY:VALUE")
+
+  Just as the +all+ subcommand, the +search+ subcommand can use a code block to
+  filter or transform the information returned from the search:
+
+      clients.search("KEY:VALUE") { |c| c.name }
+
+  You can also use a Hash based syntax, multiple search conditions will be
+  joined with AND.
+
+      clients.find :KEY => :VALUE, :KEY2 => :VALUE2, ...
+
+## BULK-EDIT CLIENTS ##
+                    **BE CAREFUL, THIS IS DESTRUCTIVE**
+  You can bulk edit API Clients using the +transform+ subcommand, which requires
+  a code block. Each client will be saved after the code block is run. If the
+  code block returns +nil+ or +false+, that client will be skipped:
+
+      clients.transform("*:*") do |client|
+        if client.name =~ /borat/i
+          client.admin(false)
+          true
+        else
+          nil
+        end
+      end
+
+  This will strip the admin privileges from any client named after borat.
+E
+      subcommands :all        => "list all api clients",
+                  :show       => "load an api client by name",
+                  :search     => "search for API clients",
+                  :transform  => "edit all api clients via a code block and save them"
+      def clients
+        @clients ||= Shell::ModelWrapper.new(Chef::ApiClient, :client)
+      end
+
+      desc "Find and edit cookbooks"
+      subcommands :all        => "list all cookbooks",
+                  :show       => "load a cookbook by name",
+                  :transform  => "edit all cookbooks via a code block and save them"
+      def cookbooks
+        @cookbooks ||= Shell::ModelWrapper.new(Chef::CookbookVersion)
+      end
+
+      desc "Find and edit nodes via the API"
+      explain(<<-E)
+## SUMMARY ##
+  +nodes+ Allows you to query your chef server for information about your nodes.
+
+## LIST ALL NODES ##
+  You can list all nodes using +all+ or +list+
+
+      nodes.all #=> [<Chef::Node...>, <Chef::Node...>, ...]
+
+  To limit the information returned for each node, pass a code block to the +all+
+  subcommand:
+
+      nodes.all { |node| node.name } #=> [NODE1_NAME, NODE2_NAME, ...]
+
+## SHOW ONE NODE ##
+  You can show the data for a single node using the +show+ subcommand:
+
+      nodes.show("NODE_NAME") => <Chef::Node @name="NODE_NAME" ...>
+
+## SEARCH FOR NODES ##
+  You can search for nodes using the +search+ or +find+ subcommands:
+
+      nodes.find(:name => "app*") #=> [<Chef::Node @name="app1.example.com" ...>, ...]
+
+  Similarly to +all+, you can pass a code block to limit or transform the
+  information returned:
+
+      nodes.find(:name => "app#") { |node| node.ec2 }
+
+## BULK EDIT NODES ##
+              **BE CAREFUL, THIS OPERATION IS DESTRUCTIVE**
+
+  Bulk edit nodes by passing a code block to the +transform+ or +bulk_edit+
+  subcommand. The block will be applied to each matching node, and then the node
+  will be saved. If the block returns +nil+ or +false+, that node will be
+  skipped.
+
+      nodes.transform do |node|
+        if node.fqdn =~ /.*\\.preprod\\.example\\.com/
+          node.set[:environment] = "preprod"
+        end
+      end
+
+  This will assign the attribute to every node with a FQDN matching the regex.
+E
+      subcommands :all        => "list all nodes",
+                  :show       => "load a node by name",
+                  :search     => "search for nodes",
+                  :transform  => "edit all nodes via a code block and save them"
+      def nodes
+        @nodes ||= Shell::ModelWrapper.new(Chef::Node)
+      end
+
+      desc "Find and edit roles via the API"
+      explain(<<-E)
+## SUMMARY ##
+  +roles+ allows you to query and edit roles on your Chef server.
+
+## SUBCOMMANDS ##
+  * all       (list)
+  * show      (load)
+  * search    (find)
+  * transform (bulk_edit)
+
+## SEE ALSO ##
+  See the help for +nodes+ for more information about the subcommands.
+E
+      subcommands :all        => "list all roles",
+                  :show       => "load a role by name",
+                  :search     => "search for roles",
+                  :transform  => "edit all roles via a code block and save them"
+      def roles
+        @roles ||= Shell::ModelWrapper.new(Chef::Role)
+      end
+
+      desc "Find and edit +databag_name+ via the api"
+      explain(<<-E)
+## SUMMARY ##
+  +databags(DATABAG_NAME)+ allows you to query and edit data bag items on your
+  Chef server. Unlike other commands for working with data on the server,
+  +databags+ requires the databag name as an argument, for example:
+    databags(:users).all
+
+## SUBCOMMANDS ##
+  * all       (list)
+  * show      (load)
+  * search    (find)
+  * transform (bulk_edit)
+
+## SEE ALSO ##
+  See the help for +nodes+ for more information about the subcommands.
+
+E
+      subcommands :all        => "list all items in the data bag",
+                  :show       => "load a data bag item by id",
+                  :search     => "search for items in the data bag",
+                  :transform  => "edit all items via a code block and save them"
+      def databags(databag_name)
+        @named_databags_wrappers ||= {}
+        @named_databags_wrappers[databag_name] ||= Shell::NamedDataBagWrapper.new(databag_name)
+      end
+
+      desc "Find and edit environments via the API"
+      explain(<<-E)
+## SUMMARY ##
+  +environments+ allows you to query and edit environments on your Chef server.
+
+## SUBCOMMANDS ##
+  * all       (list)
+  * show      (load)
+  * search    (find)
+  * transform (bulk_edit)
+
+## SEE ALSO ##
+  See the help for +nodes+ for more information about the subcommands.
+E
+      subcommands :all        => "list all environments",
+                  :show       => "load an environment by name",
+                  :search     => "search for environments",
+                  :transform  => "edit all environments via a code block and save them"
+      def environments
+        @environments ||= Shell::ModelWrapper.new(Chef::Environment)
+      end
+
+      desc "A REST Client configured to authenticate with the API"
+      def api
+        @rest = Shell::ShellREST.new(Chef::Config[:chef_server_url])
+      end
+
+    end
+
+    RecipeUIExtensions = Proc.new do
+      alias :original_resources :resources
+
+      desc "list all the resources on the current recipe"
+      def resources(*args)
+        if args.empty?
+          pp run_context.resource_collection.instance_variable_get(:@resources_by_name).keys
+        else
+          pp resources = original_resources(*args)
+          resources
+        end
+      end
+    end
+
+    def self.extend_context_object(obj)
+      obj.instance_eval(&ObjectUIExtensions)
+      obj.instance_eval(&MainContextExtensions)
+      obj.instance_eval(&RESTApiExtensions)
+      obj.extend(FileUtils)
+      obj.extend(Chef::DSL::PlatformIntrospection)
+      obj.extend(Chef::DSL::DataQuery)
+    end
+
+    def self.extend_context_node(node_obj)
+      node_obj.instance_eval(&ObjectUIExtensions)
+    end
+
+    def self.extend_context_recipe(recipe_obj)
+      recipe_obj.instance_eval(&ObjectUIExtensions)
+      recipe_obj.instance_eval(&RecipeUIExtensions)
+    end
+
+  end
+end
+
+class String
+  include Shell::Extensions::String
+end
+
+class Symbol
+  include Shell::Extensions::Symbol
+end
+
+class TrueClass
+  include Shell::Extensions::TrueClass
+end
+
+class FalseClass
+  include Shell::Extensions::FalseClass
+end
diff --git a/lib/chef/shell/model_wrapper.rb b/lib/chef/shell/model_wrapper.rb
new file mode 100644
index 0000000..7ee39de
--- /dev/null
+++ b/lib/chef/shell/model_wrapper.rb
@@ -0,0 +1,120 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/mixin/convert_to_class_name'
+require 'chef/mixin/language'
+
+module Shell
+  class ModelWrapper
+
+    include Chef::Mixin::ConvertToClassName
+
+    attr_reader :model_symbol
+
+    def initialize(model_class, symbol=nil)
+      @model_class = model_class
+      @model_symbol = symbol || convert_to_snake_case(model_class.name, "Chef").to_sym
+    end
+
+    def search(query)
+      return all if query.to_s == "all"
+      results = []
+      Chef::Search::Query.new.search(@model_symbol, format_query(query)) do |obj|
+        if block_given?
+          results << yield(obj)
+        else
+          results << obj
+        end
+      end
+      results
+    end
+
+    alias :find :search
+
+    def all(&block)
+      all_objects = list_objects
+      block_given? ? all_objects.map(&block) : all_objects
+    end
+
+    alias :list :all
+
+    def show(obj_id)
+      @model_class.load(obj_id)
+    end
+
+    alias :load :show
+
+    def transform(what_to_transform, &block)
+      if what_to_transform == :all
+        objects_to_transform = list_objects
+      else
+        objects_to_transform = search(what_to_transform)
+      end
+      objects_to_transform.each do |obj|
+        if result = yield(obj)
+          obj.save
+        end
+      end
+    end
+
+    alias :bulk_edit :transform
+
+    private
+
+    # paper over inconsistencies in the model classes APIs, and return the objects
+    # the user wanted instead of the URI=>object stuff
+    def list_objects
+      objects = @model_class.method(:list).arity == 0? @model_class.list : @model_class.list(true)
+      objects.map { |obj| Array(obj).find {|o| o.kind_of?(@model_class)} }
+    end
+
+    def format_query(query)
+      if query.respond_to?(:keys)
+        query.map { |key, value| "#{key}:#{value}" }.join(" AND ")
+      else
+        query
+      end
+    end
+  end
+
+  class NamedDataBagWrapper < ModelWrapper
+
+    def initialize(databag_name)
+      @model_symbol = @databag_name = databag_name
+    end
+
+
+    alias :list :all
+
+    def show(item)
+      Chef::DataBagItem.load(@databag_name, item)
+    end
+
+    private
+
+    def list_objects
+      all_items = []
+      Chef::Search::Query.new.search(@databag_name) do |item|
+        all_items << item
+      end
+      all_items
+    end
+
+  end
+
+end
diff --git a/lib/chef/shell/shell_rest.rb b/lib/chef/shell/shell_rest.rb
new file mode 100644
index 0000000..a485a0a
--- /dev/null
+++ b/lib/chef/shell/shell_rest.rb
@@ -0,0 +1,28 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+module Shell
+  class ShellREST < Chef::REST
+
+    alias :get    :get_rest
+    alias :put    :put_rest
+    alias :post   :post_rest
+    alias :delete :delete_rest
+
+  end
+end
diff --git a/lib/chef/shell/shell_session.rb b/lib/chef/shell/shell_session.rb
new file mode 100644
index 0000000..2f4d937
--- /dev/null
+++ b/lib/chef/shell/shell_session.rb
@@ -0,0 +1,298 @@
+#--
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# Copyright:: Copyright (c) 2011 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/recipe'
+require 'chef/run_context'
+require 'chef/config'
+require 'chef/client'
+require 'chef/cookbook/cookbook_collection'
+require 'chef/cookbook_loader'
+require 'chef/run_list/run_list_expansion'
+require 'chef/formatters/base'
+require 'chef/formatters/doc'
+require 'chef/formatters/minimal'
+
+module Shell
+  class ShellSession
+    include Singleton
+
+    def self.session_type(type=nil)
+      @session_type = type if type
+      @session_type
+    end
+
+    attr_accessor :node, :compile, :recipe, :run_context
+    attr_reader :node_attributes, :client
+    def initialize
+      @node_built = false
+      formatter = Chef::Formatters.new(Chef::Config.formatter, STDOUT, STDERR)
+      @events = Chef::EventDispatch::Dispatcher.new(formatter)
+    end
+
+    def node_built?
+      !!@node_built
+    end
+
+    def reset!
+      loading do
+        rebuild_node
+        @node = client.node
+        shorten_node_inspect
+        Shell::Extensions.extend_context_node(@node)
+        rebuild_context
+        node.consume_attributes(node_attributes) if node_attributes
+        @recipe = Chef::Recipe.new(nil, nil, run_context)
+        Shell::Extensions.extend_context_recipe(@recipe)
+        @node_built = true
+      end
+    end
+
+    def node_attributes=(attrs)
+      @node_attributes = attrs
+      @node.consume_attributes(@node_attributes)
+    end
+
+    def resource_collection
+      run_context.resource_collection
+    end
+
+    def run_context
+      @run_context ||= rebuild_context
+    end
+
+    def definitions
+      nil
+    end
+
+    def cookbook_loader
+      nil
+    end
+
+    def save_node
+      raise "Not Supported! #{self.class.name} doesn't support #save_node, maybe you need to run chef-shell in client mode?"
+    end
+
+    def rebuild_context
+      raise "Not Implemented! :rebuild_collection should be implemented by subclasses"
+    end
+
+    private
+
+    def loading
+      show_loading_progress
+      begin
+        yield
+      rescue => e
+        loading_complete(false)
+        raise e
+      else
+        loading_complete(true)
+      end
+    end
+
+    def show_loading_progress
+      print "Loading"
+      @loading = true
+      @dot_printer = Thread.new do
+        while @loading
+          print "."
+          sleep 0.5
+        end
+      end
+    end
+
+    def loading_complete(success)
+      @loading = false
+      @dot_printer.join
+      msg = success ? "done.\n\n" : "epic fail!\n\n"
+      print msg
+    end
+
+    def shorten_node_inspect
+      def @node.inspect
+        "<Chef::Node:0x#{self.object_id.to_s(16)} @name=\"#{self.name}\">"
+      end
+    end
+
+    def rebuild_node
+      raise "Not Implemented! :rebuild_node should be implemented by subclasses"
+    end
+
+  end
+
+  class StandAloneSession < ShellSession
+
+    session_type :standalone
+
+    def rebuild_context
+      cookbook_collection = Chef::CookbookCollection.new({})
+      @run_context = Chef::RunContext.new(@node, cookbook_collection, @events) # no recipes
+      @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", [])) # empty recipe list
+    end
+
+    private
+
+    def rebuild_node
+      Chef::Config[:solo] = true
+      @client = Chef::Client.new
+      @client.run_ohai
+      @client.load_node
+      @client.build_node
+    end
+
+  end
+
+  class SoloSession < ShellSession
+
+    session_type :solo
+
+    def definitions
+      @run_context.definitions
+    end
+
+    def rebuild_context
+      @run_status = Chef::RunStatus.new(@node, @events)
+      Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, Chef::Config[:cookbook_path]) }
+      cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
+      cl.load_cookbooks
+      cookbook_collection = Chef::CookbookCollection.new(cl)
+      @run_context = Chef::RunContext.new(node, cookbook_collection, @events)
+      @run_context.load(Chef::RunList::RunListExpansionFromDisk.new("_default", []))
+      @run_status.run_context = run_context
+    end
+
+    private
+
+    def rebuild_node
+      # Tell the client we're chef solo so it won't try to contact the server
+      Chef::Config[:solo] = true
+      @client = Chef::Client.new
+      @client.run_ohai
+      @client.load_node
+      @client.build_node
+    end
+
+  end
+
+  class ClientSession < SoloSession
+
+    session_type :client
+
+    def save_node
+      @client.save_node
+    end
+
+    def rebuild_context
+      @run_status = Chef::RunStatus.new(@node, @events)
+      Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, Chef::REST.new(Chef::Config[:server_url])) }
+      cookbook_hash = @client.sync_cookbooks
+      cookbook_collection = Chef::CookbookCollection.new(cookbook_hash)
+      @run_context = Chef::RunContext.new(node, cookbook_collection, @events)
+      @run_context.load(@node.run_list.expand(@node.chef_environment))
+      @run_status.run_context = run_context
+    end
+
+    private
+
+    def rebuild_node
+      # Make sure the client knows this is not chef solo
+      Chef::Config[:solo] = false
+      @client = Chef::Client.new
+      @client.run_ohai
+      @client.register
+      @client.load_node
+      @client.build_node
+    end
+
+  end
+
+  class DoppelGangerClient < Chef::Client
+
+    attr_reader :node_name
+
+    def initialize(node_name)
+      @node_name = node_name
+      @ohai = Ohai::System.new
+    end
+
+    # Run the very smallest amount of ohai we can get away with and still
+    # hope to have things work. Otherwise we're not very good doppelgangers
+    def run_ohai
+      @ohai.require_plugin('os')
+    end
+
+    # DoppelGanger implementation of build_node. preserves as many of the node's
+    # attributes, and does not save updates to the server
+    def build_node
+      Chef::Log.debug("Building node object for #{@node_name}")
+      @node = Chef::Node.find_or_create(node_name)
+      ohai_data = @ohai.data.merge(@node.automatic_attrs)
+      @node.consume_external_attrs(ohai_data,nil)
+      @run_list_expansion = @node.expand!('server')
+      @expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
+      Chef::Log.info("Run List is [#{@node.run_list}]")
+      Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
+      @node
+    end
+
+    def register
+      @rest = Chef::REST.new(Chef::Config[:chef_server_url], Chef::Config[:node_name], Chef::Config[:client_key])
+    end
+
+  end
+
+  class DoppelGangerSession < ClientSession
+
+    session_type "doppelganger client"
+
+    def save_node
+      puts "A doppelganger should think twice before saving the node"
+    end
+
+    def assume_identity(node_name)
+      Chef::Config[:doppelganger] = @node_name = node_name
+      reset!
+    rescue Exception => e
+      puts "#{e.class.name}: #{e.message}"
+      puts Array(e.backtrace).join("\n")
+      puts
+      puts "* " * 40
+      puts "failed to assume the identity of node '#{node_name}', resetting"
+      puts "* " * 40
+      puts
+      Chef::Config[:doppelganger] = false
+      @node_built = false
+      Shell.session
+    end
+
+    def rebuild_node
+      # Make sure the client knows this is not chef solo
+      Chef::Config[:solo] = false
+      @client = DoppelGangerClient.new(@node_name)
+      @client.run_ohai
+      @client.register
+      @client.load_node
+      @client.build_node
+      @client.sync_cookbooks
+    end
+
+  end
+
+end
diff --git a/lib/chef/shell_out.rb b/lib/chef/shell_out.rb
index d126834..3febe36 100644
--- a/lib/chef/shell_out.rb
+++ b/lib/chef/shell_out.rb
@@ -2,5 +2,12 @@ require 'mixlib/shellout'
 
 class Chef
   class ShellOut < Mixlib::ShellOut
+
+    def initialize(*args)
+      Chef::Log.warn("Chef::ShellOut is deprecated, please use Mixlib::ShellOut")
+      called_from = caller[0..3].inject("Called from:\n") {|msg, trace_line| msg << "  #{trace_line}\n" }
+      Chef::Log.warn(called_from)
+      super
+    end
   end
 end
diff --git a/lib/chef/solr_query.rb b/lib/chef/solr_query.rb
deleted file mode 100644
index 3360ecc..0000000
--- a/lib/chef/solr_query.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Author:: Seth Falcon (<seth at opscode.com>)
-# Copyright:: Copyright (c) 2009-2011 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/mixin/xml_escape'
-require 'chef/log'
-require 'chef/config'
-require 'chef/couchdb'
-require 'chef/solr_query/solr_http_request'
-require 'chef/solr_query/query_transform'
-
-class Chef
-  class SolrQuery
-
-    ID_KEY = "X_CHEF_id_CHEF_X"
-    DEFAULT_PARAMS = Mash.new(:start => 0, :rows => 1000, :sort => "#{ID_KEY} asc", :wt => 'json', :indent => 'off').freeze
-    FILTER_PARAM_MAP = {:database => 'X_CHEF_database_CHEF_X', :type => "X_CHEF_type_CHEF_X", :data_bag  => 'data_bag'}
-    VALID_PARAMS = [:start,:rows,:sort,:q,:type]
-    BUILTIN_SEARCH_TYPES = ["role","node","client","environment"]
-    DATA_BAG_ITEM = 'data_bag_item'
-
-    include Chef::Mixin::XMLEscape
-
-    attr_accessor :query
-    attr_accessor :params
-
-    # Create a new Query object - takes the solr_url and optional
-    # Chef::CouchDB object to inflate objects into.
-    def initialize(couchdb = nil)
-      @filter_query = {}
-      @params = {}
-
-      if couchdb.nil?
-        @database = Chef::Config[:couchdb_database]
-        @couchdb = Chef::CouchDB.new(nil, Chef::Config[:couchdb_database])
-      else
-        unless couchdb.respond_to?(:couchdb_database)
-          Chef::Log.warn("Passing the database name to Chef::Solr::Query initialization is deprecated. Please pass in the Chef::CouchDB object instead.")
-          @database = couchdb
-          @couchdb = Chef::CouchDB.new(nil, couchdb)
-        else
-          @database = couchdb.couchdb_database
-          @couchdb = couchdb
-        end
-      end
-    end
-
-    def self.from_params(params, couchdb=nil)
-      query = new(couchdb)
-      query.params = VALID_PARAMS.inject({}) do |p, param_name|
-        p[param_name] = params[param_name] if params.key?(param_name)
-        p
-      end
-      query.update_filter_query_from_params
-      query.update_query_from_params
-      query
-    end
-
-    def filter_by(filter_query_params)
-      filter_query_params.each do |key, value|
-        @filter_query[FILTER_PARAM_MAP[key]] = value
-      end
-    end
-
-    def filter_query
-      @filter_query.map { |param, value| "+#{param}:#{value}" }.join(' ')
-    end
-
-    def filter_by_type(type)
-      case type
-      when *BUILTIN_SEARCH_TYPES
-        filter_by(:type => type)
-      else
-        filter_by(:type => DATA_BAG_ITEM, :data_bag => type)
-      end
-    end
-
-    def update_filter_query_from_params
-      filter_by(:database => @database)
-      filter_by_type(params.delete(:type))
-    end
-
-    def update_query_from_params
-      original_query = URI.decode(params.delete(:q) || "*:*")
-      @query = Chef::SolrQuery::QueryTransform.transform(original_query)
-    end
-
-    def objects
-      if !object_ids.empty?
-        @bulk_objects ||= @couchdb.bulk_get(object_ids)
-        Chef::Log.debug { "Bulk get of objects: #{@bulk_objects.inspect}" }
-        @bulk_objects
-      else
-        []
-      end
-    end
-
-    def object_ids
-      @object_ids ||= results["response"]["docs"].map { |d| d[ID_KEY] }
-    end
-
-    def results
-      @results ||= SolrHTTPRequest.select(self.to_hash)
-    end
-
-    # Search Solr for objects of a given type, for a given query.
-    def search
-      { "rows" => objects, "start" => results["response"]["start"],
-        "total" => results["response"]["numFound"] }
-    end
-
-    def to_hash
-      options = DEFAULT_PARAMS.merge(params)
-      options[:fq] = filter_query
-      options[:q] = @query
-      options
-    end
-
-    START_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".freeze
-    START_DELETE_BY_QUERY = "<delete><query>".freeze
-    END_DELETE_BY_QUERY = "</query></delete>\n".freeze
-    COMMIT = "<commit/>\n".freeze
-
-    def commit(opts={})
-      SolrHTTPRequest.update("#{START_XML}#{COMMIT}")
-    end
-
-    def delete_database(db)
-      query_data = xml_escape("X_CHEF_database_CHEF_X:#{db}")
-      xml = "#{START_XML}#{START_DELETE_BY_QUERY}#{query_data}#{END_DELETE_BY_QUERY}"
-      SolrHTTPRequest.update(xml)
-      commit
-    end
-
-    def rebuild_index(db=Chef::Config[:couchdb_database])
-      delete_database(db)
-
-      results = {}
-      [Chef::ApiClient, Chef::Node, Chef::Role, Chef::Environment].each do |klass|
-        results[klass.name] = reindex_all(klass) ? "success" : "failed"
-      end
-      databags = Chef::DataBag.cdb_list(true)
-      Chef::Log.info("Reloading #{databags.size.to_s} #{Chef::DataBag} objects into the indexer")
-      databags.each { |i| i.add_to_index; i.list(true).each { |x| x.add_to_index } }
-      results[Chef::DataBag.name] = "success"
-      results
-    end
-
-    def reindex_all(klass, metadata={})
-      begin
-        items = klass.cdb_list(true)
-        Chef::Log.info("Reloading #{items.size.to_s} #{klass.name} objects into the indexer")
-        items.each { |i| i.add_to_index }
-      rescue Net::HTTPServerException => e
-        # 404s are okay, there might not be any of that kind of object...
-        if e.message =~ /Not Found/
-          Chef::Log.warn("Could not load #{klass.name} objects from couch for re-indexing (this is ok if you don't have any of these)")
-          return false
-        else
-          raise e
-        end
-      rescue Exception => e
-        Chef::Log.fatal("Chef encountered an error while attempting to load #{klass.name} objects back into the index")
-        raise e
-      end
-      true
-    end
-
-
-  end
-end
diff --git a/lib/chef/solr_query/lucene.treetop b/lib/chef/solr_query/lucene.treetop
deleted file mode 100644
index 76da879..0000000
--- a/lib/chef/solr_query/lucene.treetop
+++ /dev/null
@@ -1,150 +0,0 @@
-#
-# Author:: Seth Falcon (<seth at opscode.com>)
-# Copyright:: Copyright (c) 2010-2011 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.
-#
-
-grammar Lucene
-
-  rule body
-    (expression / space)* <Body>
-  end
-
-  rule expression
-    operation / group / field / field_range / term / string
-  end
-
-  rule term
-    keyword valid_letter+ <Term> / !keyword !"?" valid_letter <Term>
-  end
-
-  rule field
-    field_name ":" (term/string/group) <Field>
-  end
-
-  rule field_range
-    field_name ":" "[" range_value " TO " range_value "]" <InclFieldRange>
-    /
-    field_name ":" "{" range_value " TO " range_value "}" <ExclFieldRange>
-  end
-
-  rule field_name
-    !keyword valid_letter+ <FieldName>
-  end
-
-  rule range_value
-    valid_letter+ <RangeValue> / "*" <RangeValue>
-  end
-
-  rule group
-    space? '(' body ')' space? <Group>
-  end
-
-  rule operation
-    binary_op / unary_op / fuzzy_op / boost_op
-  end
-
-  rule unary_op
-    not_op / required_op / prohibited_op
-  end
-
-  rule binary_op
-    (group / field / field_range / term) space? boolean_operator space+ body <BinaryOp>
-  end
-
-  rule boolean_operator
-    and_operator / or_operator
-  end
-
-  rule and_operator
-    'AND' <AndOperator> / '&&' <AndOperator>
-  end
-
-  rule or_operator
-    'OR' <OrOperator> / '||' <OrOperator>
-  end
-
-  rule not_op
-    not_operator space (group / field / field_range / term / string) <UnaryOp>
-    /
-    bang_operator space? (group / field / field_range / term / string) <UnaryOp>
-  end
-
-  rule not_operator
-    'NOT' <NotOperator>
-  end
-
-  rule bang_operator
-    '!' <NotOperator>
-  end
-
-  rule required_op
-    !valid_letter required_operator (term/string) <UnaryOp>
-    /
-    required_operator (term/string) <UnaryOp>
-  end
-  
-  rule required_operator
-    '+' <RequiredOperator>
-  end
-
-  rule prohibited_op
-    !valid_letter prohibited_operator (field/field_range/term/string) <UnaryOp>
-  end
-
-  rule prohibited_operator
-    '-' <ProhibitedOperator>
-  end
-
-  rule boost_op
-    (term/string) '^' fuzzy_param <BoostOp>
-  end
-
-  rule fuzzy_op
-    (term/string) '~' fuzzy_param? (space / !valid_letter) <FuzzyOp>
-  end
-
-  rule fuzzy_param
-    [0-9] '.'? [0-9] <FuzzyParam> / [0-9]+ <FuzzyParam>
-  end
-
-  rule string
-    '"' term (space term)* '"' <Phrase>
-  end
-
-  rule keyword
-    'AND' / 'OR' / 'NOT'
-  end
-
-  rule valid_letter
-    start_letter+ ([a-zA-Z0-9*?_.-] / '\\' special_char)*
-  end
-
-  rule start_letter
-    [a-zA-Z0-9._*] / '\\' special_char
-  end
-
-  rule end_letter
-    [a-zA-Z0-9*?_.] / '\\' special_char
-  end
-
-  rule special_char
-    [-+&|!(){}\[\]^"~*?:\\]
-  end
-
-  rule space
-    [\s]+
-  end
-end
diff --git a/lib/chef/solr_query/lucene_nodes.rb b/lib/chef/solr_query/lucene_nodes.rb
deleted file mode 100644
index c2d7777..0000000
--- a/lib/chef/solr_query/lucene_nodes.rb
+++ /dev/null
@@ -1,285 +0,0 @@
-#
-# Author:: Seth Falcon (<seth at opscode.com>)
-# Copyright:: Copyright (c) 2010-2011 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 'treetop'
-
-module Lucene
-  SEP = "__=__"
-
-  class Term < Treetop::Runtime::SyntaxNode
-    def to_array
-      "T:#{self.text_value}"
-    end
-
-    def transform
-      self.text_value
-    end
-  end
-
-  class Field < Treetop::Runtime::SyntaxNode
-    def to_array
-      field = self.elements[0].text_value
-      term = self.elements[1].to_array
-      "(F:#{field} #{term})"
-    end
-
-    def transform
-      field = self.elements[0].text_value
-      term = self.elements[1]
-      if term.is_a? Phrase
-        str = term.transform
-        # remove quotes
-        str = str[1 ... (str.length - 1)]
-        "content:\"#{field}#{SEP}#{str}\""
-      else
-        "content:#{field}#{SEP}#{term.transform}"
-      end
-    end
-  end
-
-  class FieldRange < Treetop::Runtime::SyntaxNode
-
-    def to_array
-      field = self.elements[0].text_value
-      range_start = self.elements[1].to_array
-      range_end = self.elements[2].to_array
-      "(FR:#{field} #{left}#{range_start}#{right} #{left}#{range_end}#{right})"
-    end
-
-    def transform
-      field = self.elements[0].text_value
-      range_start = self.elements[1].transform
-      range_end = self.elements[2].transform
-      # FIXME: handle special cases for missing start/end
-      if ("*" == range_start && "*" == range_end)
-        "content:#{field}#{SEP}*"
-      elsif "*" == range_end
-        "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}\\ufff0#{right}"
-      elsif "*" == range_start
-        "content:#{left}#{field}#{SEP} TO #{field}#{SEP}#{range_end}#{right}"
-      else
-        "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}#{range_end}#{right}"
-      end
-    end
-
-  end
-
-  class InclFieldRange < FieldRange
-    def left
-      "["
-    end
-    def right
-      "]"
-    end
-  end
-
-  class ExclFieldRange < FieldRange
-    def left
-      "{"
-    end
-    def right
-      "}"
-    end
-  end
-
-  class RangeValue < Treetop::Runtime::SyntaxNode
-    def to_array
-      self.text_value
-    end
-
-    def transform
-      to_array
-    end
-  end
-
-  class FieldName < Treetop::Runtime::SyntaxNode
-    def to_array
-      self.text_value
-    end
-
-    def transform
-      to_array
-    end
-  end
-
-
-  class Body < Treetop::Runtime::SyntaxNode
-    def to_array
-      self.elements.map { |x| x.to_array }.join(" ")
-    end
-
-    def transform
-      self.elements.map { |x| x.transform }.join(" ")
-    end
-  end
-
-  class Group < Treetop::Runtime::SyntaxNode
-    def to_array
-      "(" + self.elements[0].to_array + ")"
-    end
-
-    def transform
-      "(" + self.elements[0].transform + ")"
-    end
-  end
-
-  class BinaryOp < Treetop::Runtime::SyntaxNode
-    def to_array
-      op = self.elements[1].to_array
-      a = self.elements[0].to_array
-      b = self.elements[2].to_array
-      "(#{op} #{a} #{b})"
-    end
-
-    def transform
-      op = self.elements[1].transform
-      a = self.elements[0].transform
-      b = self.elements[2].transform
-      "#{a} #{op} #{b}"
-    end
-  end
-
-  class AndOperator < Treetop::Runtime::SyntaxNode
-    def to_array
-      "OP:AND"
-    end
-
-    def transform
-      "AND"
-    end
-  end
-
-    class OrOperator < Treetop::Runtime::SyntaxNode
-    def to_array
-      "OP:OR"
-    end
-
-    def transform
-      "OR"
-    end
-  end
-
-  class FuzzyOp < Treetop::Runtime::SyntaxNode
-    def to_array
-      a = self.elements[0].to_array
-      param = self.elements[1]
-      if param
-        "(OP:~ #{a} #{param.to_array})"
-      else
-        "(OP:~ #{a})"
-      end
-    end
-
-    def transform
-      a = self.elements[0].transform
-      param = self.elements[1]
-      if param
-        "#{a}~#{param.transform}"
-      else
-        "#{a}~"
-      end
-    end
-  end
-
-  class BoostOp < Treetop::Runtime::SyntaxNode
-    def to_array
-      a = self.elements[0].to_array
-      param = self.elements[1]
-      "(OP:^ #{a} #{param.to_array})"
-    end
-
-    def transform
-      a = self.elements[0].transform
-      param = self.elements[1]
-      "#{a}^#{param.transform}"
-    end
-  end
-
-  class FuzzyParam < Treetop::Runtime::SyntaxNode
-    def to_array
-      self.text_value
-    end
-
-    def transform
-      self.text_value
-    end
-  end
-
-  class UnaryOp < Treetop::Runtime::SyntaxNode
-    def to_array
-      op = self.elements[0].to_array
-      a = self.elements[1].to_array
-      "(#{op} #{a})"
-    end
-
-    def transform
-      op = self.elements[0].transform
-      a = self.elements[1].transform
-      spc = case op
-            when "+", "-"
-              ""
-            else
-              " "
-            end
-      "#{op}#{spc}#{a}"
-    end
-
-  end
-  
-  class NotOperator < Treetop::Runtime::SyntaxNode
-    def to_array
-      "OP:NOT"
-    end
-
-    def transform
-      "NOT"
-    end
-
-  end
-
-  class RequiredOperator < Treetop::Runtime::SyntaxNode
-    def to_array
-      "OP:+"
-    end
-
-    def transform
-      "+"
-    end
-
-  end
-
-  class ProhibitedOperator < Treetop::Runtime::SyntaxNode
-    def to_array
-      "OP:-"
-    end
-
-    def transform
-      "-"
-    end
-  end
-
-  class Phrase < Treetop::Runtime::SyntaxNode
-    def to_array
-      "STR:#{self.text_value}"
-    end
-
-    def transform
-      "#{self.text_value}"
-    end
-  end
-end
diff --git a/lib/chef/solr_query/query_transform.rb b/lib/chef/solr_query/query_transform.rb
deleted file mode 100644
index 529f9de..0000000
--- a/lib/chef/solr_query/query_transform.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-#
-# Author:: Seth Falcon (<seth at opscode.com>)
-# Copyright:: Copyright (c) 2010-2011 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 'treetop'
-require 'chef/solr_query/lucene_nodes'
-
-class Chef
-  class Exceptions
-    class QueryParseError < StandardError
-    end
-  end
-end
-
-class Chef
-  class SolrQuery
-    class QueryTransform
-      @@base_path = File.expand_path(File.dirname(__FILE__))
-      Treetop.load(File.join(@@base_path, 'lucene.treetop'))
-      @@parser = LuceneParser.new
-
-      def self.parse(data)
-        tree = @@parser.parse(data)
-        msg = "Parse error at offset: #{@@parser.index}\n"
-        msg += "Reason: #{@@parser.failure_reason}"
-        raise Chef::Exceptions::QueryParseError, msg if tree.nil?
-        self.clean_tree(tree)
-        tree.to_array
-      end
-
-      def self.transform(data)
-        return "*:*" if data == "*:*"
-        tree = @@parser.parse(data)
-        msg = "Parse error at offset: #{@@parser.index}\n"
-        msg += "Reason: #{@@parser.failure_reason}"
-        raise Chef::Exceptions::QueryParseError, msg if tree.nil?
-        self.clean_tree(tree)
-        tree.transform
-      end
-
-      private
-
-      def self.clean_tree(root_node)
-        return if root_node.elements.nil?
-        root_node.elements.delete_if do |node|
-          node.class.name == "Treetop::Runtime::SyntaxNode"
-        end
-        root_node.elements.each { |node| self.clean_tree(node) }
-      end
-    end
-  end
-end
diff --git a/lib/chef/solr_query/solr_http_request.rb b/lib/chef/solr_query/solr_http_request.rb
deleted file mode 100644
index e7ce1d5..0000000
--- a/lib/chef/solr_query/solr_http_request.rb
+++ /dev/null
@@ -1,132 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Daniel DeLeo (<dan at opscode.com>)
-# Copyright:: Copyright (c) 2009-2011 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 'net/http'
-require 'uri'
-require 'chef/json_compat'
-require 'chef/config'
-
-class Chef
-  class SolrQuery
-    class SolrHTTPRequest
-      CLASS_FOR_METHOD = {:GET => Net::HTTP::Get, :POST => Net::HTTP::Post}
-
-      TEXT_XML = {"Content-Type" => "text/xml"}
-
-      def self.solr_url=(solr_url)
-        @solr_url = solr_url
-        @http_client = nil
-        @url_prefix = nil
-      end
-
-      def self.solr_url
-        @solr_url || Chef::Config[:solr_url]
-      end
-
-      def self.http_client
-        @http_client ||= begin
-          uri = URI.parse(solr_url)
-          http_client = Net::HTTP.new(uri.host, uri.port)
-          http_client.use_ssl = true if uri.port == 443
-          http_client
-        end
-      end
-
-      def self.url_prefix
-        @url_prefix ||= begin
-          uri = URI.parse(solr_url)
-          if uri.path == ""
-            "/solr"
-          else
-            uri.path.gsub(%r{/$}, '')
-          end
-        end
-      end
-
-      def self.select(params={})
-        url = "#{url_prefix}/select?#{url_join(params)}"
-        Chef::Log.debug("Sending #{url} to Solr")
-        request = new(:GET, url)
-        json_response = request.run("Search Query to Solr '#{solr_url}#{url}'")
-        Chef::JSONCompat.from_json(json_response)
-      end
-
-      def self.update(doc)
-        url = "#{url_prefix}/update"
-        Chef::Log.debug("POSTing document to SOLR:\n#{doc}")
-        request = new(:POST, url, TEXT_XML) { |req| req.body = doc.to_s }
-        request.run("POST to Solr '#{solr_url}#{url}', data: #{doc}")
-      end
-
-      def self.url_join(params_hash={})
-        params = params_hash.inject("") do |param_str, params|
-          param_str << "#{params[0]}=#{escape(params[1])}&"
-        end
-        params.chop! # trailing &
-        params
-      end
-
-      def self.escape(s)
-        s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
-          '%'+$1.unpack('H2'*$1.size).join('%').upcase
-        }.tr(' ', '+')
-      end
-
-      def initialize(method, url, headers=nil)
-        args = headers ? [url, headers] : url
-        @request = CLASS_FOR_METHOD[method].new(*args)
-        yield @request if block_given?
-      end
-
-      def http_client
-        self.class.http_client
-      end
-
-      def solr_url
-        self.class.solr_url
-      end
-
-      def run(description="HTTP Request to Solr")
-        response = http_client.request(@request)
-        request_failed!(response, description) unless response.kind_of?(Net::HTTPSuccess)
-        response.body
-      rescue NoMethodError => e
-        # http://redmine.ruby-lang.org/issues/show/2708
-        # http://redmine.ruby-lang.org/issues/show/2758
-        if e.to_s =~ /#{Regexp.escape(%q|undefined method 'closed?' for nil:NilClass|)}/
-          Chef::Log.fatal("#{description} failed.  Chef::Exceptions::SolrConnectionError exception: Errno::ECONNREFUSED (net/http undefined method closed?) attempting to contact #{solr_url}")
-          Chef::Log.debug("Rescued error in http connect, treating it as Errno::ECONNREFUSED to hide bug in net/http")
-          Chef::Log.debug(e.backtrace.join("\n"))
-          raise Chef::Exceptions::SolrConnectionError, "Errno::ECONNREFUSED: Connection refused attempting to contact #{solr_url}"
-        else
-          raise
-        end
-      end
-
-      def request_failed!(response, description='HTTP call')
-        Chef::Log.fatal("#{description} failed (#{response.class} #{response.code} #{response.message})")
-        response.error!
-      rescue Timeout::Error, Errno::EINVAL, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT => e
-        Chef::Log.debug(e.backtrace.join("\n"))
-        raise Chef::Exceptions::SolrConnectionError, "#{e.class.name}: #{e.to_s}"
-      end
-
-    end
-  end
-end
diff --git a/lib/chef/streaming_cookbook_uploader.rb b/lib/chef/streaming_cookbook_uploader.rb
index df90e00..9e638f6 100644
--- a/lib/chef/streaming_cookbook_uploader.rb
+++ b/lib/chef/streaming_cookbook_uploader.rb
@@ -15,22 +15,23 @@ class Chef
   class StreamingCookbookUploader
 
     DefaultHeaders = { 'accept' => 'application/json', 'x-chef-version' => ::Chef::VERSION }
-    
+
     class << self
 
       def post(to_url, user_id, secret_key_filename, params = {}, headers = {})
         make_request(:post, to_url, user_id, secret_key_filename, params, headers)
       end
-      
+
       def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
         make_request(:put, to_url, user_id, secret_key_filename, params, headers)
       end
-      
+
       def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
+        Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader instead.')
         boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ'
         parts = []
         content_file = nil
-        
+
         timestamp = Time.now.utc.iso8601
         secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename))
 
@@ -53,31 +54,31 @@ class Chef
           end
           parts << StringPart.new("--" + boundary + "--\r\n")
         end
-        
+
         body_stream = MultipartStream.new(parts)
-        
+
         timestamp = Time.now.utc.iso8601
-        
+
         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}")
-        
+
         # 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 
+        # wasn't a valid file or wasn't included. Extract the body (with
         # multi-part delimiters intact) to sign the request.
         # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
         # always hash the entire request body. In the file case it would just be
         # expanded multipart text - the entire body of the POST.
         content_body = parts.inject("") { |result,part| result + part.read(0, part.size) }
         content_file.rewind if content_file # we consumed the file for the above operation, so rewind it.
-        
+
         signing_options = {
           :http_method=>http_verb,
           :path=>url.path,
           :user_id=>user_id,
           :timestamp=>timestamp}
         (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
-        
+
         headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
 
         content_file.rewind if content_file
@@ -90,11 +91,11 @@ class Chef
                 Net::HTTP::Put.new(url.path, headers)
               when :post
                 Net::HTTP::Post.new(url.path, headers)
-              end              
+              end
         req.content_length = body_stream.size
         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
@@ -107,30 +108,31 @@ class Chef
         # TODO: stop the following madness!
         class << res
           alias :to_s :body
-          
+
           # BUGBUG this makes the response compatible with what respsonse_steps expects to test headers (response.headers[] -> response[])
           def headers
             self
           end
-          
+
           def status
             code.to_i
           end
         end
         res
       end
-      
+
     end
 
     class StreamPart
       def initialize(stream, size)
+        Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader::StreamPart class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader::StreamPart instead.')
         @stream, @size = stream, size
       end
-      
+
       def size
         @size
       end
-      
+
       # read the specified amount from the stream
       def read(offset, how_much)
         @stream.read(how_much)
@@ -139,9 +141,10 @@ class Chef
 
     class StringPart
       def initialize(str)
+        Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader::StringPart class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader::StringPart instead.')
         @str = str
       end
-      
+
       def size
         @str.length
       end
@@ -154,30 +157,31 @@ class Chef
 
     class MultipartStream
       def initialize(parts)
+        Chef::Log.warn('[DEPRECATED] StreamingCookbookUploader::MultipartStream class is deprecated. It will be removed in Chef 12. Please use CookbookSiteStreamingUploader::MultipartStream instead.')
         @parts = parts
         @part_no = 0
         @part_offset = 0
       end
-      
+
       def size
         @parts.inject(0) {|size, part| size + part.size}
       end
-      
+
       def read(how_much)
         return nil if @part_no >= @parts.size
 
         how_much_current_part = @parts[@part_no].size - @part_offset
-        
+
         how_much_current_part = if how_much_current_part > how_much
                                   how_much
                                 else
                                   how_much_current_part
                                 end
-        
+
         how_much_next_part = how_much - how_much_current_part
 
         current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
-        
+
         # recurse into the next part if the current one was not large enough
         if how_much_next_part > 0
           @part_no += 1
@@ -194,7 +198,7 @@ class Chef
         end
       end
     end
-    
+
   end
 
 
diff --git a/lib/chef/tasks/chef_repo.rake b/lib/chef/tasks/chef_repo.rake
index 6f839a4..704557e 100644
--- a/lib/chef/tasks/chef_repo.rake
+++ b/lib/chef/tasks/chef_repo.rake
@@ -6,9 +6,9 @@
 # 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.
@@ -51,7 +51,7 @@ task :update do
       pull = true if line =~ /\[remote "origin"\]/
     end
     if pull
-      sh %{git pull} 
+      sh %{git pull}
     else
       puts "* Skipping git pull, no origin specified"
     end
@@ -86,14 +86,14 @@ end
 def create_cookbook(dir)
   raise "Must provide a COOKBOOK=" unless ENV["COOKBOOK"]
   puts "** Creating cookbook #{ENV["COOKBOOK"]}"
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "attributes")}" 
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "recipes")}" 
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "definitions")}" 
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "libraries")}" 
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "resources")}" 
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "providers")}" 
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "files", "default")}" 
-  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "templates", "default")}" 
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "attributes")}"
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "recipes")}"
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "definitions")}"
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "libraries")}"
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "resources")}"
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "providers")}"
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "files", "default")}"
+  sh "mkdir -p #{File.join(dir, ENV["COOKBOOK"], "templates", "default")}"
   unless File.exists?(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb"))
     open(File.join(dir, ENV["COOKBOOK"], "recipes", "default.rb"), "w") do |file|
       file.puts <<-EOH
@@ -110,9 +110,9 @@ EOH
 # 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.
@@ -156,7 +156,7 @@ end
 def create_metadata(dir)
   raise "Must provide a COOKBOOK=" unless ENV["COOKBOOK"]
   puts "** Creating metadata for cookbook: #{ENV["COOKBOOK"]}"
-  
+
   case NEW_COOKBOOK_LICENSE
   when :apachev2
     license = "Apache 2.0"
@@ -188,8 +188,8 @@ task :ssl_cert do
   fqdn =~ /^(.+?)\.(.+)$/
   hostname = $1
   domain = $2
-  keyfile = fqdn.gsub("*", "wildcard")
   raise "Must provide FQDN!" unless fqdn && hostname && domain
+  keyfile = fqdn.gsub("*", "wildcard")
   puts "** Creating self signed SSL Certificate for #{fqdn}"
   sh("(cd #{CADIR} && openssl genrsa 2048 > #{keyfile}.key)")
   sh("(cd #{CADIR} && chmod 644 #{keyfile}.key)")
@@ -288,7 +288,7 @@ namespace :databag do
         end
       end
     else
-      puts "ERROR: Could not find any databags, skipping it"  
+      puts "ERROR: Could not find any databags, skipping it"
     end
   end
 
@@ -327,7 +327,7 @@ EOH
       end
     else
       puts "ERROR: Could not find your databag (#{databag}), skipping it"
-    end   
+    end
   end
 
 end
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
new file mode 100644
index 0000000..3f592e4
--- /dev/null
+++ b/lib/chef/user.rb
@@ -0,0 +1,182 @@
+#
+# 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'
+
+class Chef
+  class User
+
+    include Chef::Mixin::FromFile
+    include Chef::Mixin::ParamsValidate
+
+    def initialize
+      @name = ''
+      @public_key = nil
+      @private_key = nil
+      @password = nil
+      @admin = false
+    end
+
+    def name(arg=nil)
+      set_or_return(:name, arg,
+                    :regex => /^[a-z0-9\-_]+$/)
+    end
+
+    def admin(arg=nil)
+      set_or_return(:admin,
+                    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 = {
+        "name" => @name,
+        "public_key" => @public_key,
+        "admin" => @admin
+      }
+      result["private_key"] = @private_key if @private_key
+      result["password"] = @password if @password
+      result
+    end
+
+    def to_json(*a)
+      to_hash.to_json(*a)
+    end
+
+    def destroy
+      Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("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)
+      Chef::User.from_hash(self.to_hash.merge(new_user))
+    end
+
+    def update(new_key=false)
+      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)
+      Chef::User.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
+
+    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 })
+      private_key(reregistered_self["private_key"])
+      self
+    end
+
+    def to_s
+      "user[#{@name}]"
+    end
+
+    def inspect
+      "Chef::User name:'#{name}' admin:'#{admin.inspect}'" +
+      "public_key:'#{public_key}' private_key:#{private_key}"
+    end
+
+    # Class Methods
+
+    def self.from_hash(user_hash)
+      user = Chef::User.new
+      user.name user_hash['name']
+      user.private_key user_hash['private_key'] if user_hash.key?('private_key')
+      user.password user_hash['password'] if user_hash.key?('password')
+      user.public_key user_hash['public_key']
+      user.admin user_hash['admin']
+      user
+    end
+
+    def self.from_json(json)
+      Chef::User.from_hash(Chef::JSONCompat.from_json(json))
+    end
+
+    class << self
+      alias_method :json_create, :from_json
+    end
+
+    def self.list(inflate=false)
+      response = if inflate
+                   users = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
+                   users.map do |name|
+                     Chef::User.load(name)
+                   end
+                 else
+                   Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
+                 end
+      if response.is_a? Array
+        transform_ohc_list_response(response)
+      else
+        response
+      end
+    end
+
+    def self.load(name)
+      response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}")
+      Chef::User.from_hash(response)
+    end
+
+    private
+
+    # Gross.  Transforms an API response in the form of:
+    # [ { "user" => { "username" => USERNAME }}, ...]
+    # into the form
+    # { "USERNAME" => "URI" }
+    def self.transform_ohc_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
+  end
+end
diff --git a/lib/chef/util/backup.rb b/lib/chef/util/backup.rb
new file mode 100644
index 0000000..43e3434
--- /dev/null
+++ b/lib/chef/util/backup.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+  class Util
+    class Backup
+      attr_reader :new_resource
+      attr_accessor :path
+
+      def initialize(new_resource, path = nil)
+        @new_resource = new_resource
+        @path = path.nil? ? new_resource.path : path
+      end
+
+      def backup!
+        if @new_resource.backup != false && @new_resource.backup > 0 && ::File.exist?(path)
+          do_backup
+          # Clean up after the number of backups
+          slice_number = @new_resource.backup
+          backup_files = sorted_backup_files
+          if backup_files.length >= @new_resource.backup
+            remainder = backup_files.slice(slice_number..-1)
+            remainder.each do |backup_to_delete|
+              delete_backup(backup_to_delete)
+            end
+          end
+        end
+      end
+
+      private
+
+      def backup_filename
+        @backup_filename ||= begin
+          time = Time.now
+          nanoseconds = sprintf("%6f", time.to_f).split('.')[1]
+          savetime = time.strftime("%Y%m%d%H%M%S.#{nanoseconds}")
+          backup_filename = "#{path}.chef-#{savetime}"
+          backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows
+        end
+      end
+
+      def prefix
+        # if :file_backup_path is nil, we fallback to the old behavior of
+        # keeping the backup in the same directory. We also need to to_s it
+        # so we don't get a type error around implicit to_str conversions.
+        @prefix ||= Chef::Config[:file_backup_path].to_s
+      end
+
+      def backup_path
+        @backup_path ||= ::File.join(prefix, backup_filename)
+      end
+
+      def do_backup
+        FileUtils.mkdir_p(::File.dirname(backup_path)) if Chef::Config[:file_backup_path]
+        FileUtils.cp(path, backup_path, :preserve => true)
+        Chef::Log.info("#{@new_resource} backed up to #{backup_path}")
+      end
+
+      def delete_backup(backup_file)
+        FileUtils.rm(backup_file)
+        Chef::Log.info("#{@new_resource} removed backup at #{backup_file}")
+      end
+
+      def sorted_backup_files
+        Dir[::File.join(prefix, ".#{path}.chef-*")].sort { |a,b| b <=> a }
+      end
+    end
+  end
+end
+
diff --git a/lib/chef/util/diff.rb b/lib/chef/util/diff.rb
new file mode 100644
index 0000000..7bce52d
--- /dev/null
+++ b/lib/chef/util/diff.rb
@@ -0,0 +1,188 @@
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Some portions of this file are derived from material in the diff-lcs
+# project licensed under the terms of the MIT license, provided below.
+#
+# Copyright:: Copyright (c) 2004-2013 Austin Ziegler
+# License:: MIT
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of this Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE
+# SOFTWARE.
+
+require 'diff/lcs'
+require 'diff/lcs/hunk'
+
+class Chef
+  class Util
+    class Diff
+      # @todo: to_a, to_s, to_json, inspect defs, accessors for @diff and @error
+      # @todo: move coercion to UTF-8 into to_json
+      # @todo: replace shellout to diff -u with diff-lcs gem
+
+      def for_output
+        # formatted output to a terminal uses arrays of strings and returns error strings
+        @diff.nil? ? [ @error ] : @diff
+      end
+
+      def for_reporting
+        # caller needs to ensure that new files aren't posted to resource reporting
+        return nil if @diff.nil?
+        @diff.join("\\n")
+      end
+
+      def use_tempfile_if_missing(file)
+        tempfile = nil
+        unless File.exists?(file)
+          Chef::Log.debug("file #{file} does not exist to diff against, using empty tempfile")
+          tempfile = Tempfile.new("chef-diff")
+          file = tempfile.path
+        end
+        yield file
+        unless tempfile.nil?
+          tempfile.close
+          tempfile.unlink
+        end
+      end
+
+      def diff(old_file, new_file)
+        use_tempfile_if_missing(old_file) do |old_file|
+          use_tempfile_if_missing(new_file) do |new_file|
+            @error = do_diff(old_file, new_file)
+          end
+        end
+      end
+      
+      # produces a unified-output-format diff with 3 lines of context
+      # ChefFS uses udiff() directly
+      def udiff(old_file, new_file)
+        diff_str = ""
+        file_length_difference = 0
+
+        old_data = IO.readlines(old_file).map { |e| e.chomp }
+        new_data = IO.readlines(new_file).map { |e| e.chomp }
+        diff_data = ::Diff::LCS.diff(old_data, new_data)
+
+        return diff_str if old_data.empty? && new_data.empty?
+        return "No differences encountered\n" if diff_data.empty?
+
+        # write diff header (standard unified format)
+        ft = File.stat(old_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
+        diff_str << "--- #{old_file}\t#{ft}\n"
+        ft = File.stat(new_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
+        diff_str << "+++ #{new_file}\t#{ft}\n"
+
+        # loop over diff hunks. if a hunk overlaps with the last hunk,
+        # join them. otherwise, print out the old one.
+        old_hunk = hunk = nil
+        diff_data.each do |piece|
+          begin
+            hunk = ::Diff::LCS::Hunk.new(old_data, new_data, piece, 3, file_length_difference)
+            file_length_difference = hunk.file_length_difference
+            next unless old_hunk
+            next if hunk.merge(old_hunk)
+            diff_str << old_hunk.diff(:unified) << "\n"
+          ensure
+            old_hunk = hunk
+          end
+        end
+        diff_str << old_hunk.diff(:unified) << "\n"
+        return diff_str
+      end
+
+      private
+
+      def do_diff(old_file, new_file)
+        if Chef::Config[:diff_disabled]
+          return "(diff output suppressed by config)"
+        end
+
+        diff_filesize_threshold = Chef::Config[:diff_filesize_threshold]
+        diff_output_threshold = Chef::Config[:diff_output_threshold]
+
+        if ::File.size(old_file) > diff_filesize_threshold || ::File.size(new_file) > diff_filesize_threshold
+          return "(file sizes exceed #{diff_filesize_threshold} bytes, diff output suppressed)"
+        end
+
+        # MacOSX(BSD?) diff will *sometimes* happily spit out nasty binary diffs
+        return "(current file is binary, diff output suppressed)" if is_binary?(old_file)
+        return "(new content is binary, diff output suppressed)" if is_binary?(new_file)
+
+        begin
+          Chef::Log.debug("running: diff -u #{old_file} #{new_file}")
+          diff_str = udiff(old_file, new_file)
+
+        rescue Exception => e
+          # Should *not* receive this, but in some circumstances it seems that
+          # an exception can be thrown even using shell_out instead of shell_out!
+          return "Could not determine diff. Error: #{e.message}"
+        end
+
+        if !diff_str.empty? && diff_str != "No differences encountered\n"
+          if diff_str.length > diff_output_threshold
+            return "(long diff of over #{diff_output_threshold} characters, diff output suppressed)"
+          else
+            diff_str = encode_diff_for_json(diff_str)
+            @diff = diff_str.split("\n")
+            return "(diff available)"
+          end
+        else
+          return "(no diff)"
+        end
+      end
+
+      def is_binary?(path)
+        File.open(path) do |file|
+          # XXX: this slurps into RAM, but we should have already checked our diff has a reasonable size
+          buff = file.read
+          buff = "" if buff.nil?
+          begin
+            return buff !~ /\A[\s[:print:]]*\z/m
+          rescue ArgumentError => e
+            return true if e.message =~ /invalid byte sequence/
+            raise
+          end
+        end
+      end
+
+      def encode_diff_for_json(diff_str)
+        if Object.const_defined? :Encoding
+          diff_str.encode!('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?')
+        end
+        return diff_str
+      end
+
+    end
+  end
+end
+
diff --git a/lib/chef/util/file_edit.rb b/lib/chef/util/file_edit.rb
index ce37bdc..ea0a523 100644
--- a/lib/chef/util/file_edit.rb
+++ b/lib/chef/util/file_edit.rb
@@ -33,7 +33,7 @@ class Chef
         @file_edited = false
 
         raise ArgumentError, "File doesn't exist" unless File.exist? @original_pathname
-        raise ArgumentError, "File is blank" unless (@contents = File.new(@original_pathname).readlines).length > 0
+        @contents = File.new(@original_pathname).readlines
       end
 
       #search the file line by line and match each line with the given regex
diff --git a/lib/chef/util/selinux.rb b/lib/chef/util/selinux.rb
new file mode 100644
index 0000000..1da3e88
--- /dev/null
+++ b/lib/chef/util/selinux.rb
@@ -0,0 +1,100 @@
+#
+# Author:: Sean O'Meara
+# Author:: Kevin Keane
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+#
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2013, North County Tech Center, LLC
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'chef/mixin/shell_out'
+
+class Chef
+  class Util
+    #
+    # IMPORTANT: We assume that selinux utilities are installed on an
+    # selinux enabled server. Provisioning an selinux enabled server
+    # without selinux utilities is not supported.
+    #
+    module Selinux
+
+      include Chef::Mixin::ShellOut
+
+      # We want to initialize below variables once during a
+      # chef-client run therefore they are class variables.
+      @@selinux_enabled = nil
+      @@restorecon_path = nil
+      @@selinuxenabled_path = nil
+
+      def selinux_enabled?
+        @@selinux_enabled = check_selinux_enabled? if @@selinux_enabled.nil?
+        @@selinux_enabled
+      end
+
+      def restore_security_context(file_path, recursive = false)
+        if restorecon_path
+          restorecon_command = recursive ? "#{restorecon_path} -R -r" : "#{restorecon_path} -R"
+          restorecon_command += " #{file_path}"
+          Chef::Log.debug("Restoring selinux security content with #{restorecon_command}")
+          shell_out!(restorecon_command)
+        else
+          Chef::Log.warn "Can not find 'restorecon' on the system. Skipping selinux security context restore."
+        end
+      end
+
+      private
+
+      def restorecon_path
+        @@restorecon_path = which("restorecon") if @@restorecon_path.nil?
+        @@restorecon_path
+      end
+
+      def selinuxenabled_path
+        @@selinuxenabled_path = which("selinuxenabled") if @@selinuxenabled_path.nil?
+        @@selinuxenabled_path
+      end
+
+      def which(cmd)
+        paths = ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/bin', '/usr/bin', '/sbin', '/usr/sbin' ]
+        paths.each do |path|
+          filename = File.join(path, cmd)
+          return filename if File.executable?(filename)
+        end
+        false
+      end
+
+      def check_selinux_enabled?
+        if selinuxenabled_path
+          cmd = shell_out!(selinuxenabled_path, :returns => [0,1])
+          case cmd.exitstatus
+          when 1
+            return false
+          when 0
+            return true
+          else
+            raise RuntimeError, "Unknown exit code from command #{selinuxenabled_path}: #{cmd.exitstatus}"
+          end
+        else
+          # We assume selinux is not enabled if selinux utils are not
+          # installed.
+          return false
+        end
+      end
+
+    end
+  end
+end
+
diff --git a/lib/chef/util/windows.rb b/lib/chef/util/windows.rb
index cba2c2a..777fe4a 100644
--- a/lib/chef/util/windows.rb
+++ b/lib/chef/util/windows.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb
index 9da0dc6..817e47e 100644
--- a/lib/chef/util/windows/net_group.rb
+++ b/lib/chef/util/windows/net_group.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb
index 1979e09..37efc02 100644
--- a/lib/chef/util/windows/net_use.rb
+++ b/lib/chef/util/windows/net_use.rb
@@ -6,9 +6,9 @@
 # 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.
diff --git a/lib/chef/util/windows/net_user.rb b/lib/chef/util/windows/net_user.rb
index 97d8f33..5cca348 100644
--- a/lib/chef/util/windows/net_user.rb
+++ b/lib/chef/util/windows/net_user.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -37,9 +37,14 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
   #[: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, nil],
+    [:password, ""],
     [:password_age, 0],
     [:priv, 0], #"The NetUserAdd and NetUserSetInfo functions ignore this member"
     [:home_dir, nil],
@@ -183,12 +188,20 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
   def disable_account
     user_modify do |user|
       user[:flags] |= 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
+      user[:password] = nil
     end
   end
 
   def enable_account
     user_modify do |user|
       user[:flags] &= ~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
+      user[:password] = nil
     end
   end
 
diff --git a/lib/chef/util/windows/volume.rb b/lib/chef/util/windows/volume.rb
index 11f8e08..08c3a53 100644
--- a/lib/chef/util/windows/volume.rb
+++ b/lib/chef/util/windows/volume.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -35,7 +35,7 @@ class Chef::Util::Windows::Volume < Chef::Util::Windows
     name += "\\" unless name =~ /\\$/ #trailing slash required
     @name = name
   end
-  
+
   def device
     buffer = 0.chr * 256
     if GetVolumeNameForVolumeMountPoint(@name, buffer, buffer.size)
@@ -51,8 +51,8 @@ class Chef::Util::Windows::Volume < Chef::Util::Windows
     end
   end
 
-  def add(device)
-    unless SetVolumeMountPoint(@name, device)
+  def add(args)
+    unless SetVolumeMountPoint(@name, args[:remote])
       raise ArgumentError, get_last_error
     end
   end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 00ddf8d..173b0cc 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -6,9 +6,9 @@
 # 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.
@@ -17,7 +17,7 @@
 
 class Chef
   CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
-  VERSION = '10.12.0'
+  VERSION = '11.8.2'
 end
 
 # NOTE: the Chef::Version class is defined in version_class.rb
diff --git a/lib/chef/version/platform.rb b/lib/chef/version/platform.rb
new file mode 100644
index 0000000..2921341
--- /dev/null
+++ b/lib/chef/version/platform.rb
@@ -0,0 +1,42 @@
+# Author:: Xabier de Zuazo (<xabier at onddo.com>)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'chef/version_class'
+
+class Chef
+  class Version
+    class Platform < Chef::Version
+
+      protected
+
+      def parse(str="")
+        @major, @minor, @patch =
+          case str.to_s
+          when /^(\d+)\.(\d+)\.(\d+)$/
+            [ $1.to_i, $2.to_i, $3.to_i ]
+          when /^(\d+)\.(\d+)$/
+            [ $1.to_i, $2.to_i, 0 ]
+          when /^(\d+)$/
+            [ $1.to_i, 0, 0 ]
+          else
+            msg = "'#{str.to_s}' does not match 'x.y.z', 'x.y' or 'x'"
+            raise Chef::Exceptions::InvalidPlatformVersion.new( msg )
+          end
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/version_class.rb b/lib/chef/version_class.rb
index e987927..01af6f1 100644
--- a/lib/chef/version_class.rb
+++ b/lib/chef/version_class.rb
@@ -51,7 +51,7 @@ class Chef
       other.is_a?(Version) && self == other
     end
 
-    private
+    protected
 
     def parse(str="")
       @major, @minor, @patch =
diff --git a/lib/chef/version_constraint.rb b/lib/chef/version_constraint.rb
index b6f1f08..cc213ab 100644
--- a/lib/chef/version_constraint.rb
+++ b/lib/chef/version_constraint.rb
@@ -22,6 +22,7 @@ class Chef
     STANDARD_OPS = %w(< > <= >=)
     OPS = %w(< > = <= >= ~>)
     PATTERN = /^(#{OPS.join('|')}) (.+)$/
+    VERSION_CLASS = Chef::Version
 
     attr_reader :op, :version
 
@@ -41,9 +42,9 @@ class Chef
 
     def include?(v)
       version = if v.respond_to? :version # a CookbookVersion-like object
-                  Chef::Version.new(v.version.to_s)
+                  self.class::VERSION_CLASS.new(v.version.to_s)
                 else
-                  Chef::Version.new(v.to_s)
+                  self.class::VERSION_CLASS.new(v.to_s)
                 end
      do_op(version)
     end
@@ -98,13 +99,13 @@ class Chef
       @missing_patch_level = false
       if str.index(" ").nil? && str =~ /^[0-9]/
         # try for lone version, implied '='
-        @version = Chef::Version.new(str)
+        @version = self.class::VERSION_CLASS.new(str)
         @op = "="
       elsif PATTERN.match str
         @op = $1
         raw_version = $2
-        @version = Chef::Version.new(raw_version)
-        if raw_version.split('.').size == 2
+        @version = self.class::VERSION_CLASS.new(raw_version)
+        if raw_version.split('.').size <= 2
           @missing_patch_level = true
         end
       else
diff --git a/lib/chef/version_constraint/platform.rb b/lib/chef/version_constraint/platform.rb
new file mode 100644
index 0000000..ada4f29
--- /dev/null
+++ b/lib/chef/version_constraint/platform.rb
@@ -0,0 +1,26 @@
+# Author:: Xabier de Zuazo (<xabier at onddo.com>)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+require 'chef/version_constraint'
+require 'chef/version/platform'
+
+class Chef
+  class VersionConstraint
+    class Platform < Chef::VersionConstraint
+      VERSION_CLASS = Chef::Version::Platform
+
+    end
+  end
+end
diff --git a/lib/chef/webui_user.rb b/lib/chef/webui_user.rb
deleted file mode 100644
index fda2883..0000000
--- a/lib/chef/webui_user.rb
+++ /dev/null
@@ -1,231 +0,0 @@
-#
-# Author:: Adam Jacob (<adam at opscode.com>)
-# Author:: Nuo Yan (<nuo at opscode.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 'chef/config'
-require 'chef/mixin/params_validate'
-require 'chef/couchdb'
-require 'chef/index_queue'
-require 'digest/sha1'
-require 'chef/json_compat'
-  
-
-class Chef
-  class WebUIUser
-    
-    attr_accessor :name, :validated, :admin, :openid, :couchdb
-    attr_reader   :password, :salt, :couchdb_id, :couchdb_rev
-    
-    include Chef::Mixin::ParamsValidate
-    
-    DESIGN_DOCUMENT = {
-      "version" => 3,
-      "language" => "javascript",
-      "views" => {
-        "all" => {
-          "map" => <<-EOJS
-            function(doc) {
-              if (doc.chef_type == "webui_user") {
-                emit(doc.name, doc);
-              }
-            }
-          EOJS
-        },
-        "all_id" => {
-          "map" => <<-EOJS
-          function(doc) {
-            if (doc.chef_type == "webui_user") {
-              emit(doc.name, doc.name);
-            }
-          }
-          EOJS
-        },
-      },
-    }
-    
-    # Create a new Chef::WebUIUser object.
-    def initialize(opts={})
-      @name, @salt, @password = opts['name'], opts['salt'], opts['password']
-      @openid, @couchdb_rev, @couchdb_id = opts['openid'], opts['_rev'], opts['_id']
-      @admin = false
-      @couchdb = Chef::CouchDB.new
-    end
-    
-    def name=(n)
-      @name = n.gsub(/\./, '_')
-    end
-    
-    def admin?
-      admin
-    end
-    
-    # Set the password for this object.
-    def set_password(password, confirm_password=password) 
-      raise ArgumentError, "Passwords do not match" unless password == confirm_password
-      raise ArgumentError, "Password cannot be blank" if (password.nil? || password.length==0)
-      raise ArgumentError, "Password must be a minimum of 6 characters" if password.length < 6
-      generate_salt
-      @password = encrypt_password(password)      
-    end
-    
-    def set_openid(given_openid)
-      @openid = given_openid
-    end 
-    
-    def verify_password(given_password)
-      encrypt_password(given_password) == @password
-    end 
-    
-    # Serialize this object as a hash 
-    def to_json(*a)
-      attributes = Hash.new
-      recipes = Array.new
-      result = {
-        'name' => name,
-        'json_class' => self.class.name,
-        'salt' => salt,
-        'password' => password,
-        'openid' => openid,
-        'admin' => admin,
-        'chef_type' => 'webui_user',
-      }
-      result["_id"]  = @couchdb_id if @couchdb_id  
-      result["_rev"] = @couchdb_rev if @couchdb_rev
-      result.to_json(*a)
-    end
-    
-    # Create a Chef::WebUIUser from JSON
-    def self.json_create(o)
-      me = new(o)
-      me.admin = o["admin"]
-      me
-    end
-    
-    # List all the Chef::WebUIUser objects in the CouchDB.  If inflate is set to true, you will get
-    # the full list of all registration objects.  Otherwise, you'll just get the IDs
-    def self.cdb_list(inflate=false)
-      rs = Chef::CouchDB.new.list("users", inflate)
-      if inflate
-        rs["rows"].collect { |r| r["value"] }
-      else
-        rs["rows"].collect { |r| r["key"] }
-      end
-    end
-    
-    def self.list(inflate=false)
-      r = Chef::REST.new(Chef::Config[:chef_server_url])
-      if inflate
-        response = Hash.new
-        Chef::Search::Query.new.search(:user) do |n|
-          response[n.name] = n unless n.nil?
-        end
-        response
-      else
-        r.get_rest("users")
-      end
-    end
-    
-    # Load an WebUIUser by name from CouchDB
-    def self.cdb_load(name)
-      Chef::CouchDB.new.load("webui_user", name)
-    end
-    
-    # Load a User by name
-    def self.load(name)
-      r = Chef::REST.new(Chef::Config[:chef_server_url])
-      r.get_rest("users/#{name}")
-    end
-    
-    
-    # Whether or not there is an WebUIUser with this key.
-    def self.has_key?(name)
-      Chef::CouchDB.new.has_key?("webui_user", name)
-    end
-    
-    # Remove this WebUIUser from the CouchDB
-    def cdb_destroy
-      couchdb.delete("webui_user", @name, @couchdb_rev)
-    end
-    
-    # Remove this WebUIUser via the REST API
-    def destroy
-      r = Chef::REST.new(Chef::Config[:chef_server_url])
-      r.delete_rest("users/#{@name}")
-    end
-    
-    # Save this WebUIUser to the CouchDB
-    def cdb_save
-      results = couchdb.store("webui_user", @name, self)
-      @couchdb_rev = results["rev"]
-    end
-    
-    # Save this WebUIUser via the REST API
-    def save
-      r = Chef::REST.new(Chef::Config[:chef_server_url])
-      begin
-        r.put_rest("users/#{@name}", self)
-      rescue Net::HTTPServerException => e
-        if e.response.code == "404"
-          r.post_rest("users", self)
-        else
-          raise e
-        end
-      end
-      self
-    end
-    
-    # Create the WebUIUser via the REST API
-    def create
-      r = Chef::REST.new(Chef::Config[:chef_server_url])
-      r.post_rest("users", self)
-      self
-    end
-    
-    # Set up our CouchDB design document
-    def self.create_design_document(couchdb=nil)
-      couchdb ||= Chef::CouchDB.new
-      couchdb.create_design_document("users", DESIGN_DOCUMENT)
-    end
-    
-    #return true if an admin user exists. this is pretty expensive (O(n)), should think of a better way (nuo)
-    def self.admin_exist
-      users = self.cdb_list
-      users.each do |u|
-        user = self.cdb_load(u)
-        if user.admin
-          return user.name
-        end
-      end
-      nil
-    end
-    
-    protected
-    
-      def generate_salt
-        @salt = Time.now.to_s
-        chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
-        1.upto(30) { |i| @salt << chars[rand(chars.size-1)] }
-        @salt
-      end
-    
-      def encrypt_password(password)
-        Digest::SHA1.hexdigest("--#{salt}--#{password}--")
-      end
-    
-  end
-end
diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb
index 41a128e..7a8dafd 100644
--- a/lib/chef/win32/api/file.rb
+++ b/lib/chef/win32/api/file.rb
@@ -459,8 +459,7 @@ BOOL WINAPI DeviceIoControl(
         # to be passed to the *W vesion of WinAPI File
         # functions
         def encode_path(path)
-          path.gsub!(::File::SEPARATOR, ::File::ALT_SEPARATOR)
-          (path_prepender << path).to_wstring
+          (path_prepender << path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)).to_wstring
         end
 
         def path_prepender
@@ -472,6 +471,13 @@ BOOL WINAPI DeviceIoControl(
         # ensures the handle is closed on exit of the block
         def file_search_handle(path, &block)
           begin
+            # Workaround for CHEF-4419:
+            # Make sure paths starting with "/" has a drive letter
+            # assigned from the current working diretory.
+            # Note: With CHEF-4427 this issue will be fixed with a
+            # broader fix to map all the paths starting with "/" to
+            # SYSTEM_DRIVE on windows.
+            path = ::File.expand_path(path) if path.start_with? "/"
             path = encode_path(path)
             find_data = WIN32_FIND_DATA.new
             handle = FindFirstFileW(path, find_data)
diff --git a/lib/chef/win32/api/process.rb b/lib/chef/win32/api/process.rb
index d18ad41..0aca992 100644
--- a/lib/chef/win32/api/process.rb
+++ b/lib/chef/win32/api/process.rb
@@ -33,6 +33,7 @@ class Chef
         safe_attach_function :GetCurrentProcess, [], :HANDLE
         safe_attach_function :GetProcessHandleCount, [ :HANDLE, :LPDWORD ], :BOOL
         safe_attach_function :GetProcessId, [ :HANDLE ], :DWORD
+        safe_attach_function :CloseHandle, [ :HANDLE ], :BOOL
 
       end
     end
diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb
index a096b40..7ca2d70 100644
--- a/lib/chef/win32/api/security.rb
+++ b/lib/chef/win32/api/security.rb
@@ -226,6 +226,50 @@ class Chef
              :SidTypeLabel
         ]
 
+        TOKEN_INFORMATION_CLASS = enum :TOKEN_INFORMATION_CLASS, [
+             :TokenUser, 1,
+             :TokenGroups,
+             :TokenPrivileges,
+             :TokenOwner,
+             :TokenPrimaryGroup,
+             :TokenDefaultDacl,
+             :TokenSource,
+             :TokenType,
+             :TokenImpersonationLevel,
+             :TokenStatistics,
+             :TokenRestrictedSids,
+             :TokenSessionId,
+             :TokenGroupsAndPrivileges,
+             :TokenSessionReference,
+             :TokenSandBoxInert,
+             :TokenAuditPolicy,
+             :TokenOrigin,
+             :TokenElevationType,
+             :TokenLinkedToken,
+             :TokenElevation,
+             :TokenHasRestrictions,
+             :TokenAccessInformation,
+             :TokenVirtualizationAllowed,
+             :TokenVirtualizationEnabled,
+             :TokenIntegrityLevel,
+             :TokenUIAccess,
+             :TokenMandatoryPolicy,
+             :TokenLogonSid,
+             :TokenIsAppContainer,
+             :TokenCapabilities,
+             :TokenAppContainerSid,
+             :TokenAppContainerNumber,
+             :TokenUserClaimAttributes,
+             :TokenDeviceClaimAttributes,
+             :TokenRestrictedUserClaimAttributes,
+             :TokenRestrictedDeviceClaimAttributes,
+             :TokenDeviceGroups,
+             :TokenRestrictedDeviceGroups,
+             :TokenSecurityAttributes,
+             :TokenIsRestricted,
+             :MaxTokenInfoClass
+        ]
+
         # SECURITY_DESCRIPTOR is an opaque structure whose contents can vary.  Pass the
         # pointer around and free it with LocalFree.
         # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379561(v=vs.85).aspx
@@ -334,7 +378,7 @@ class Chef
         safe_attach_function :SetSecurityDescriptorGroup, [ :pointer, :pointer, :BOOL ], :BOOL
         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
       end
     end
   end
diff --git a/lib/chef/win32/api/synchronization.rb b/lib/chef/win32/api/synchronization.rb
new file mode 100644
index 0000000..9c148d7
--- /dev/null
+++ b/lib/chef/win32/api/synchronization.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/api'
+
+class Chef
+  module ReservedNames::Win32
+    module API
+      module Synchronization
+        extend Chef::ReservedNames::Win32::API
+
+        ffi_lib 'kernel32'
+
+        # Constant synchronization functions use to indicate wait
+        # forever.
+        INFINITE = 0xFFFFFFFF
+
+        # Return codes
+        # http://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx
+        WAIT_FAILED = 0xFFFFFFFF
+        WAIT_TIMEOUT = 0x00000102
+        WAIT_OBJECT_0 = 0x00000000
+        WAIT_ABANDONED = 0x00000080
+
+        # Security and access rights for synchronization objects
+        # http://msdn.microsoft.com/en-us/library/windows/desktop/ms686670(v=vs.85).aspx
+        DELETE = 0x00010000
+        READ_CONTROL = 0x00020000
+        SYNCHRONIZE = 0x00100000
+        WRITE_DAC = 0x00040000
+        WRITE_OWNER = 0x00080000
+
+        # Mutex specific rights
+        MUTEX_ALL_ACCESS = 0x001F0001
+        MUTEX_MODIFY_STATE = 0x00000001
+
+=begin
+HANDLE WINAPI CreateMutex(
+  _In_opt_  LPSECURITY_ATTRIBUTES lpMutexAttributes,
+  _In_      BOOL bInitialOwner,
+  _In_opt_  LPCTSTR lpName
+);
+=end
+        safe_attach_function :CreateMutexW, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE
+        safe_attach_function :CreateMutexA, [ :LPSECURITY_ATTRIBUTES, :BOOL, :LPCTSTR ], :HANDLE
+
+=begin
+DWORD WINAPI WaitForSingleObject(
+  _In_  HANDLE hHandle,
+  _In_  DWORD dwMilliseconds
+);
+=end
+        safe_attach_function :WaitForSingleObject, [ :HANDLE, :DWORD ], :DWORD
+
+=begin
+BOOL WINAPI ReleaseMutex(
+  _In_  HANDLE hMutex
+);
+=end
+        safe_attach_function :ReleaseMutex, [ :HANDLE ], :BOOL
+
+=begin
+HANDLE WINAPI OpenMutex(
+  _In_  DWORD dwDesiredAccess,
+  _In_  BOOL bInheritHandle,
+  _In_  LPCTSTR lpName
+);
+=end
+        safe_attach_function :OpenMutexW, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE
+        safe_attach_function :OpenMutexA, [ :DWORD, :BOOL, :LPCTSTR ], :HANDLE
+      end
+    end
+  end
+end
diff --git a/lib/chef/win32/handle.rb b/lib/chef/win32/handle.rb
index 60e3591..21a8fdf 100644
--- a/lib/chef/win32/handle.rb
+++ b/lib/chef/win32/handle.rb
@@ -26,6 +26,10 @@ class Chef
     class Handle
       extend Chef::ReservedNames::Win32::API::Process
 
+      # See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx
+      # The handle value returned by the GetCurrentProcess function is the pseudo handle (HANDLE)-1 (which is 0xFFFFFFFF)
+      CURRENT_PROCESS_HANDLE = 4294967295
+
       def initialize(handle)
         @handle = handle
         ObjectSpace.define_finalizer(self, Handle.close_handle_finalizer(handle))
@@ -34,7 +38,10 @@ class Chef
       attr_reader :handle
 
       def self.close_handle_finalizer(handle)
-        proc { close_handle(handle) }
+        # According to http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx, it is not necessary
+        # to close the pseudo handle returned by the GetCurrentProcess function.  The docs also say that it doesn't hurt to call
+        # CloseHandle on it. However, doing so from inside of Ruby always seems to produce an invalid handle error.
+        proc { close_handle(handle) unless handle == CURRENT_PROCESS_HANDLE }
       end
 
       def self.close_handle(handle)
diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb
new file mode 100644
index 0000000..0b7d99f
--- /dev/null
+++ b/lib/chef/win32/mutex.rb
@@ -0,0 +1,117 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/api/synchronization'
+
+class Chef
+  module ReservedNames::Win32
+    class Mutex
+      include Chef::ReservedNames::Win32::API::Synchronization
+
+      def initialize(name)
+        @name = name
+        create_system_mutex
+      end
+
+      attr_reader :handle
+      attr_reader :name
+
+      #####################################################
+      # Attempts to grab the mutex.
+      # Returns true if the mutex is grabbed or if it's already
+      # owned; false otherwise.
+      def test
+        WaitForSingleObject(handle, 0) == WAIT_OBJECT_0
+      end
+
+      #####################################################
+      # Attempts to grab the mutex and waits until it is acquired.
+      def wait
+        loop do
+          wait_result = WaitForSingleObject(handle, 1000)
+          case wait_result
+          when WAIT_TIMEOUT
+            # We are periodically waking up in order to give ruby a
+            # chance to process any signal it got while we were
+            # sleeping. This condition shouldn't contain any logic
+            # other than sleeping.
+            sleep 0.1
+          when WAIT_ABANDONED
+            # Previous owner of the mutex died before it can release the
+            # mutex. Log a warning and continue.
+            Chef::Log.debug "Existing owner of the mutex exited prematurely."
+            break
+          when WAIT_OBJECT_0
+            # Mutex is successfully acquired.
+            break
+          else
+            Chef::Log.error("Failed to acquire system mutex '#{name}'. Return code: #{wait_result}")
+            Chef::ReservedNames::Win32::Error.raise!
+          end
+        end
+      end
+
+      #####################################################
+      # Releaes the mutex
+      def release
+        # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685066(v=vs.85).aspx
+        # Note that release method needs to be called more than once
+        # if mutex is acquired more than once.
+        unless ReleaseMutex(handle)
+          # Don't fail things in here if we can't release the mutex.
+          # Because it will be automatically released when the owner
+          # of the process goes away and this class is only being used
+          # to synchronize chef-clients runs on a node.
+          Chef::Log.error("Can not release mutex '#{name}'. This might cause issues \
+if the mutex is attempted to be acquired by other threads.")
+          Chef::ReservedNames::Win32::Error.raise!
+        end
+      end
+
+      private
+
+      def create_system_mutex
+        # First check if there exists a mutex in the system with the
+        # given name. We need only synchronize rights if a mutex is
+        # already created.
+        # InheritHandle is set to true so that subprocesses can
+        # inherit the ownership of the mutex.
+        @handle = OpenMutexW(SYNCHRONIZE, true, name.to_wstring)
+
+        if @handle == 0
+          # Mutext doesn't exist so create one.
+          # In the initial creation of the mutex initial_owner is set to
+          # false so that mutex will not be acquired until someone calls
+          # acquire.
+          # In order to call "*W" windows apis, strings needs to be
+          # encoded as wide strings.
+          @handle = CreateMutexW(nil, false, name.to_wstring)
+
+          # Looks like we can't create the mutex for some reason.
+          # Fail early.
+          if @handle == 0
+            Chef::Log.error("Failed to create system mutex with name'#{name}'")
+            Chef::ReservedNames::Win32::Error.raise!
+          end
+        end
+      end
+    end
+  end
+end
+
+
diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb
new file mode 100644
index 0000000..1e8f53b
--- /dev/null
+++ b/lib/chef/win32/registry.rb
@@ -0,0 +1,382 @@
+#
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+#
+# Copyright:: 2012, Opscode, 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/reserved_names'
+
+if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+  require 'win32/registry'
+  require 'ruby-wmi'
+  require 'win32/api'
+end
+
+class Chef
+  class Win32
+    class Registry
+
+      attr_accessor :run_context
+      attr_accessor :architecture
+
+      def initialize(run_context=nil, user_architecture=:machine)
+        @run_context = run_context
+        self.architecture = user_architecture
+      end
+
+      def architecture=(user_architecture)
+        @architecture = user_architecture.to_sym
+        assert_architecture!
+      end
+
+      def get_values(key_path)
+        hive, key = get_hive_and_key(key_path)
+        key_exists!(key_path)
+        values = hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
+          reg.map { |name, type, data| {:name=>name, :type=>get_name_from_type(type), :data=>data} }
+        end
+      end
+
+      def set_value(key_path, value)
+        Chef::Log.debug("Updating value #{value[:name]} in registry key #{key_path} with type #{value[:type]} and data #{value[:data]}")
+        key_exists!(key_path)
+        hive, key = get_hive_and_key(key_path)
+        if value_exists?(key_path, value)
+          if data_exists?(key_path, value)
+            Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} already had those values, not updated")
+            return false
+          else
+            hive.open(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry_system_architecture) do |reg|
+              reg.write(value[:name], get_type_from_name(value[:type]), value[:data])
+            end
+            Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} updated")
+          end
+        else
+          hive.open(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | registry_system_architecture) do |reg|
+            reg.write(value[:name], get_type_from_name(value[:type]), value[:data])
+          end
+          Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} created")
+        end
+        true
+      end
+
+      def delete_value(key_path, value)
+        Chef::Log.debug("Deleting value #{value[:name]} from registry key #{key_path}")
+        if value_exists?(key_path, value)
+          begin
+            hive, key = get_hive_and_key(key_path)
+          rescue Chef::Exceptions::Win32RegKeyMissing
+            return true
+          end
+          hive.open(key, ::Win32::Registry::KEY_SET_VALUE | registry_system_architecture) do |reg|
+            reg.delete_value(value[:name])
+            Chef::Log.debug("Deleted value #{value[:name]} from registry key #{key_path}")
+          end
+        else
+          Chef::Log.debug("Value #{value[:name]} in registry key #{key_path} does not exist, not updated")
+        end
+        true
+      end
+
+      def create_key(key_path, recursive)
+        Chef::Log.debug("Creating registry key #{key_path}")
+        if keys_missing?(key_path)
+          if recursive == true
+            Chef::Log.debug("Registry key #{key_path} has missing subkeys, and recursive specified, creating them....")
+            create_missing(key_path)
+          else
+            raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has missing subkeys, and recursive not specified"
+          end
+        end
+        if key_exists?(key_path)
+          Chef::Log.debug("Registry key #{key_path} already exists, doing nothing")
+        else
+          hive, key = get_hive_and_key(key_path)
+          hive.create(key, ::Win32::Registry::KEY_WRITE | registry_system_architecture)
+          Chef::Log.debug("Registry key #{key_path} created")
+        end
+        true
+      end
+
+      def delete_key(key_path, recursive)
+        Chef::Log.debug("Deleting registry key #{key_path}")
+        unless key_exists?(key_path)
+          Chef::Log.debug("Registry key #{key_path}, does not exist, not deleting")
+          return true
+        end
+        #key_path is in the form "HKLM\Software\Opscode" for example, extracting
+        #hive = HKLM,
+        #hive_namespace = ::Win32::Registry::HKEY_LOCAL_MACHINE
+        hive = key_path.split("\\").shift
+        hive_namespace, key_including_parent = get_hive_and_key(key_path)
+        if has_subkeys?(key_path)
+          if recursive == true
+            subkeys = get_subkeys(key_path)
+            subkeys.each do |key|
+              keypath_to_check = hive+"\\"+key_including_parent+"\\"+key
+              Chef::Log.debug("Deleting registry key #{key_path} recursively")
+              delete_key(keypath_to_check, true)
+            end
+            delete_key_ex(hive_namespace, key_including_parent)
+          else
+            raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified"
+          end
+        else
+          delete_key_ex(hive_namespace, key_including_parent)
+          return true
+        end
+        true
+      end
+
+      #Using the 'RegDeleteKeyEx' Windows API that correctly supports WOW64 systems (Win2003)
+      #instead of the 'RegDeleteKey'
+      def delete_key_ex(hive, key)
+        regDeleteKeyEx = ::Win32::API.new('RegDeleteKeyEx', 'LPLL', 'L', 'advapi32')
+        hive_num = hive.hkey - (1 << 32)
+        regDeleteKeyEx.call(hive_num, key, ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0)
+      end
+
+      def key_exists?(key_path)
+        hive, key = get_hive_and_key(key_path)
+        begin
+          hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |current_key|
+            return true
+          end
+        rescue ::Win32::Registry::Error => e
+          return false
+        end
+      end
+
+      def key_exists!(key_path)
+        unless key_exists?(key_path)
+          raise Chef::Exceptions::Win32RegKeyMissing, "Registry key #{key_path} does not exist"
+        end
+        true
+      end
+
+      def hive_exists?(key_path)
+        begin
+          hive, key = get_hive_and_key(key_path)
+        rescue Chef::Exceptions::Win32RegHiveMissing => e
+          return false
+        end
+        return true
+      end
+
+      def has_subkeys?(key_path)
+        key_exists!(key_path)
+        hive, key = get_hive_and_key(key_path)
+        hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
+          reg.each_key{ |key| return true }
+        end
+        return false
+      end
+
+      def get_subkeys(key_path)
+        subkeys = []
+        key_exists!(key_path)
+        hive, key = get_hive_and_key(key_path)
+        hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
+          reg.each_key{ |current_key| subkeys << current_key }
+        end
+        return subkeys
+      end
+
+      # 32-bit chef clients running on 64-bit machines will default to reading the 64-bit registry
+      def registry_system_architecture
+        applied_arch = ( architecture == :machine ) ? machine_architecture : architecture
+        ( applied_arch == :x86_64 ) ? 0x0100 : 0x0200
+      end
+
+      def value_exists?(key_path, value)
+        key_exists!(key_path)
+        hive, key = get_hive_and_key(key_path)
+        hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
+          return true if reg.any? {|val| val == value[:name] }
+        end
+        return false
+      end
+
+      def data_exists?(key_path, value)
+        key_exists!(key_path)
+        hive, key = get_hive_and_key(key_path)
+        hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
+          reg.each do |val_name, val_type, val_data|
+            if val_name == value[:name] &&
+              val_type == get_type_from_name(value[:type]) &&
+              val_data == value[:data]
+              return true
+            end
+          end
+        end
+        return false
+      end
+
+      def value_exists!(key_path, value)
+        unless value_exists?(key_path, value)
+          raise Chef::Exceptions::Win32RegValueMissing, "Registry key #{key_path} has no value named #{value[:name]}"
+        end
+        true
+      end
+
+      def data_exists!(key_path, value)
+        unless data_exists?(key_path, value)
+          raise Chef::Exceptions::Win32RegDataMissing, "Registry key #{key_path} has no value named #{value[:name]}, containing type #{value[:type]} and data #{value[:data]}"
+        end
+        true
+      end
+
+      def type_matches?(key_path, value)
+        value_exists!(key_path, value)
+        hive, key = get_hive_and_key(key_path)
+        hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
+          reg.each do |val_name, val_type|
+            if val_name == value[:name]
+              type_new = get_type_from_name(value[:type])
+              if val_type == type_new
+                return true
+              end
+            end
+          end
+        end
+        return false
+      end
+
+      def type_matches!(key_path, value)
+        unless type_matches?(key_path, value)
+          raise Chef::Exceptions::Win32RegTypesMismatch, "Registry key #{key_path} has a value #{value[:name]} with a type that is not #{value[:type]}"
+        end
+      end
+
+      def get_type_from_name(val_type)
+        value = {
+          :binary => ::Win32::Registry::REG_BINARY,
+          :string => ::Win32::Registry::REG_SZ,
+          :multi_string => ::Win32::Registry::REG_MULTI_SZ,
+          :expand_string => ::Win32::Registry::REG_EXPAND_SZ,
+          :dword => ::Win32::Registry::REG_DWORD,
+          :dword_big_endian => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
+          :qword => ::Win32::Registry::REG_QWORD
+        }[val_type]
+        return value
+      end
+
+      def keys_missing?(key_path)
+        missing_key_arr = key_path.split("\\")
+        missing_key_arr.pop
+        key = missing_key_arr.join("\\")
+        !key_exists?(key)
+      end
+
+      def get_type_from_name(val_type)
+        _type_name_map[val_type]
+      end
+
+      def get_name_from_type(val_class)
+        _name_type_map[val_class]
+      end
+
+      private
+
+      def node
+        run_context && run_context.node
+      end
+
+      def machine_architecture
+        node[:kernel][:machine].to_sym
+      end
+
+      def assert_architecture!
+        if machine_architecture == :i386 && architecture == :x86_64
+          raise Chef::Exceptions::Win32RegArchitectureIncorrect, "cannot access 64-bit registry on a 32-bit windows instance"
+        end
+      end
+
+      def get_hive_and_key(path)
+        reg_path = path.split("\\")
+        hive_name = reg_path.shift
+        key = reg_path.join("\\")
+
+        hive = {
+          "HKLM" => ::Win32::Registry::HKEY_LOCAL_MACHINE,
+          "HKEY_LOCAL_MACHINE" => ::Win32::Registry::HKEY_LOCAL_MACHINE,
+          "HKU" => ::Win32::Registry::HKEY_USERS,
+          "HKEY_USERS" => ::Win32::Registry::HKEY_USERS,
+          "HKCU" => ::Win32::Registry::HKEY_CURRENT_USER,
+          "HKEY_CURRENT_USER" => ::Win32::Registry::HKEY_CURRENT_USER,
+          "HKCR" => ::Win32::Registry::HKEY_CLASSES_ROOT,
+          "HKEY_CLASSES_ROOT" => ::Win32::Registry::HKEY_CLASSES_ROOT,
+          "HKCC" => ::Win32::Registry::HKEY_CURRENT_CONFIG,
+          "HKEY_CURRENT_CONFIG" => ::Win32::Registry::HKEY_CURRENT_CONFIG,
+        }[hive_name]
+
+        raise Chef::Exceptions::Win32RegHiveMissing, "Registry Hive #{hive_name} does not exist" unless hive
+
+        return hive, key
+      end
+
+      def _type_name_map
+        {
+          :binary => ::Win32::Registry::REG_BINARY,
+          :string => ::Win32::Registry::REG_SZ,
+          :multi_string => ::Win32::Registry::REG_MULTI_SZ,
+          :expand_string => ::Win32::Registry::REG_EXPAND_SZ,
+          :dword => ::Win32::Registry::REG_DWORD,
+          :dword_big_endian => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
+          :qword => ::Win32::Registry::REG_QWORD
+        }
+      end
+
+      def _name_type_map
+        @_name_type_map ||= _type_name_map.invert
+      end
+
+      def get_type_from_num(val_type)
+        value = {
+          3 => ::Win32::Registry::REG_BINARY,
+          1 => ::Win32::Registry::REG_SZ,
+          7 => ::Win32::Registry::REG_MULTI_SZ,
+          2 => ::Win32::Registry::REG_EXPAND_SZ,
+          4 => ::Win32::Registry::REG_DWORD,
+          5 => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
+          11 => ::Win32::Registry::REG_QWORD
+        }[val_type]
+        return value
+      end
+
+      def create_missing(key_path)
+        missing_key_arr = key_path.split("\\")
+        hivename = missing_key_arr.shift
+        missing_key_arr.pop
+        existing_key_path = hivename
+        hive, key = get_hive_and_key(key_path)
+        missing_key_arr.each do |intermediate_key|
+          existing_key_path = existing_key_path << "\\" << intermediate_key
+          if !key_exists?(existing_key_path)
+            Chef::Log.debug("Recursively creating registry key #{existing_key_path}")
+            hive.create(get_key(existing_key_path), ::Win32::Registry::KEY_ALL_ACCESS | registry_system_architecture)
+          end
+        end
+      end
+
+      def get_key(path)
+        reg_path = path.split("\\")
+        hive_name = reg_path.shift
+        key = reg_path.join("\\")
+      end
+
+    end
+  end
+end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index b7b14c5..ff97c46 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -478,6 +478,25 @@ class Chef
           token.adjust_privileges(old_privileges)
         end
       end
+
+      # Checks if the caller has the admin privileges in their
+      # security token
+      def self.has_admin_privileges?
+        if Chef::Platform.windows_server_2003?
+          # Admin privileges do not exist on Windows Server 2003
+
+          true
+        else
+          process_token = open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, 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)
+
+          # Assume process is not elevated if the call fails.
+          # Process is elevated if the result is different than 0.
+          success && (elevation_result.read_ulong != 0)
+        end
+      end
     end
   end
 end
diff --git a/lib/chef/win32/security/ace.rb b/lib/chef/win32/security/ace.rb
index efd44b1..3aeae35 100644
--- a/lib/chef/win32/security/ace.rb
+++ b/lib/chef/win32/security/ace.rb
@@ -122,4 +122,4 @@ class Chef
       end
     end
   end
-end
\ No newline at end of file
+end
diff --git a/lib/chef/win32/security/sid.rb b/lib/chef/win32/security/sid.rb
index 7ca21ee..e1b2022 100644
--- a/lib/chef/win32/security/sid.rb
+++ b/lib/chef/win32/security/sid.rb
@@ -196,4 +196,4 @@ class Chef
       end
     end
   end
-end
\ No newline at end of file
+end
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index 004fcad..62f8175 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -29,7 +29,19 @@ class Chef
       # http://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx
       # http://msdn.microsoft.com/en-us/library/ms724358(v=vs.85).aspx
 
+      private
+
+      def self.get_system_metrics(n_index)
+        Win32API.new('user32', 'GetSystemMetrics', 'I', 'I').call(n_index)
+      end
+
+      public
+
       WIN_VERSIONS = {
+        "Windows 8.1" => {:major => 6, :minor => 3, :callable => lambda{ @product_type == VER_NT_WORKSTATION }},
+        "Windows Server 2012 R2" => {:major => 6, :minor => 3, :callable => lambda{ @product_type != VER_NT_WORKSTATION }},
+        "Windows 8" => {:major => 6, :minor => 2, :callable => lambda{ @product_type == VER_NT_WORKSTATION }},
+        "Windows Server 2012" => {:major => 6, :minor => 2, :callable => lambda{ @product_type != VER_NT_WORKSTATION }},
         "Windows 7" => {:major => 6, :minor => 1, :callable => lambda{ @product_type == VER_NT_WORKSTATION }},
         "Windows Server 2008 R2" => {:major => 6, :minor => 1, :callable => lambda{ @product_type != VER_NT_WORKSTATION }},
         "Windows Server 2008" => {:major => 6, :minor => 0, :callable => lambda{ @product_type != VER_NT_WORKSTATION }},
@@ -48,7 +60,17 @@ class Chef
         @suite_mask = ver_info[:w_suite_mask]
         @sp_major_version = ver_info[:w_service_pack_major]
         @sp_minor_version = ver_info[:w_service_pack_minor]
-        @sku = get_product_info(@major_version, @minor_version, @sp_major_version, @sp_minor_version)
+
+        # Obtain sku information for the purpose of identifying
+        # datacenter, cluster, and core skus, the latter 2 only
+        # exist in releases after Windows Server 2003
+        if ! Chef::Platform::windows_server_2003?
+          @sku = get_product_info(@major_version, @minor_version, @sp_major_version, @sp_minor_version)
+        else
+          # The get_product_info API is not supported on Win2k3,
+          # use an alternative to identify datacenter skus
+          @sku = get_datacenter_product_info_windows_server_2003(ver_info)
+        end
       end
 
       marketing_names = Array.new
@@ -77,11 +99,6 @@ class Chef
             (self.class.const_get(c) == @sku) &&
               (c.to_s =~ /#{m}/i )
           end
-          # if @sku
-          #   !(PRODUCT_TYPE[@sku][:name] =~ /#{m}/i).nil?
-          # else
-          #   false
-          # end
         end
       end
 
@@ -110,8 +127,10 @@ class Chef
         out.get_uint(0)
       end
 
-      def get_system_metrics(n_index)
-        GetSystemMetrics(n_index)
+      def get_datacenter_product_info_windows_server_2003(ver_info)
+        # The intent is not to get the actual sku, just identify
+        # Windows Server 2003 datacenter
+        sku = (ver_info[:w_suite_mask] & VER_SUITE_DATACENTER) ? PRODUCT_DATACENTER_SERVER : 0
       end
 
     end
diff --git a/metadata.yml b/metadata.yml
index 2393cf4..aebc091 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,965 +1,1833 @@
---- !ruby/object:Gem::Specification 
+--- !ruby/object:Gem::Specification
 name: chef
-version: !ruby/object:Gem::Version 
-  hash: 127
+version: !ruby/object:Gem::Version
+  version: 11.8.2
   prerelease: 
-  segments: 
-  - 10
-  - 12
-  - 0
-  version: 10.12.0
 platform: ruby
-authors: 
+authors:
 - Adam Jacob
 autorequire: 
 bindir: bin
 cert_chain: []
-
-date: 2012-06-18 00:00:00 Z
-dependencies: 
-- !ruby/object:Gem::Dependency 
+date: 2013-12-03 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
   name: mixlib-config
-  prerelease: false
-  requirement: &id001 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 23
-        segments: 
-        - 1
-        - 1
-        - 2
-        version: 1.1.2
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '2.0'
   type: :runtime
-  version_requirements: *id001
-- !ruby/object:Gem::Dependency 
-  name: mixlib-cli
   prerelease: false
-  requirement: &id002 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 19
-        segments: 
-        - 1
-        - 1
-        - 0
-        version: 1.1.0
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '2.0'
+- !ruby/object:Gem::Dependency
+  name: mixlib-cli
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.3'
   type: :runtime
-  version_requirements: *id002
-- !ruby/object:Gem::Dependency 
-  name: mixlib-log
   prerelease: false
-  requirement: &id003 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 27
-        segments: 
-        - 1
-        - 3
-        - 0
-        version: 1.3.0
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.3'
+- !ruby/object:Gem::Dependency
+  name: mixlib-log
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.3'
   type: :runtime
-  version_requirements: *id003
-- !ruby/object:Gem::Dependency 
-  name: mixlib-authentication
   prerelease: false
-  requirement: &id004 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 19
-        segments: 
-        - 1
-        - 1
-        - 0
-        version: 1.1.0
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.3'
+- !ruby/object:Gem::Dependency
+  name: mixlib-authentication
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.3'
   type: :runtime
-  version_requirements: *id004
-- !ruby/object:Gem::Dependency 
-  name: mixlib-shellout
   prerelease: false
-  requirement: &id005 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.3'
+- !ruby/object:Gem::Dependency
+  name: mixlib-shellout
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.2'
   type: :runtime
-  version_requirements: *id005
-- !ruby/object:Gem::Dependency 
-  name: ohai
   prerelease: false
-  requirement: &id006 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 7
-        segments: 
-        - 0
-        - 6
-        - 0
-        version: 0.6.0
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.2'
+- !ruby/object:Gem::Dependency
+  name: ohai
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '6.0'
   type: :runtime
-  version_requirements: *id006
-- !ruby/object:Gem::Dependency 
-  name: rest-client
   prerelease: false
-  requirement: &id007 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 31
-        segments: 
-        - 1
-        - 0
-        - 4
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '6.0'
+- !ruby/object:Gem::Dependency
+  name: rest-client
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
         version: 1.0.4
     - - <
-      - !ruby/object:Gem::Version 
-        hash: 11
-        segments: 
-        - 1
-        - 7
-        - 0
+      - !ruby/object:Gem::Version
         version: 1.7.0
   type: :runtime
-  version_requirements: *id007
-- !ruby/object:Gem::Dependency 
-  name: bunny
   prerelease: false
-  requirement: &id008 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 7
-        segments: 
-        - 0
-        - 6
-        - 0
-        version: 0.6.0
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 1.0.4
+    - - <
+      - !ruby/object:Gem::Version
+        version: 1.7.0
+- !ruby/object:Gem::Dependency
+  name: mime-types
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.16'
   type: :runtime
-  version_requirements: *id008
-- !ruby/object:Gem::Dependency 
-  name: json
   prerelease: false
-  requirement: &id009 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 15
-        segments: 
-        - 1
-        - 4
-        - 4
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.16'
+- !ruby/object:Gem::Dependency
+  name: json
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
         version: 1.4.4
     - - <=
-      - !ruby/object:Gem::Version 
-        hash: 13
-        segments: 
-        - 1
-        - 6
-        - 1
-        version: 1.6.1
+      - !ruby/object:Gem::Version
+        version: 1.7.7
   type: :runtime
-  version_requirements: *id009
-- !ruby/object:Gem::Dependency 
-  name: yajl-ruby
   prerelease: false
-  requirement: &id010 !ruby/object:Gem::Requirement 
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 1.4.4
+    - - <=
+      - !ruby/object:Gem::Version
+        version: 1.7.7
+- !ruby/object:Gem::Dependency
+  name: yajl-ruby
+  requirement: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
+    requirements:
     - - ~>
-      - !ruby/object:Gem::Version 
-        hash: 13
-        segments: 
-        - 1
-        - 1
-        version: "1.1"
+      - !ruby/object:Gem::Version
+        version: '1.1'
   type: :runtime
-  version_requirements: *id010
-- !ruby/object:Gem::Dependency 
-  name: treetop
   prerelease: false
-  requirement: &id011 !ruby/object:Gem::Requirement 
+  version_requirements: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
+    requirements:
     - - ~>
-      - !ruby/object:Gem::Version 
-        hash: 21
-        segments: 
-        - 1
-        - 4
-        - 9
-        version: 1.4.9
-  type: :runtime
-  version_requirements: *id011
-- !ruby/object:Gem::Dependency 
+      - !ruby/object:Gem::Version
+        version: '1.1'
+- !ruby/object:Gem::Dependency
   name: net-ssh
-  prerelease: false
-  requirement: &id012 !ruby/object:Gem::Requirement 
+  requirement: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
+    requirements:
     - - ~>
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 2
-        - 2
-        - 2
-        version: 2.2.2
+      - !ruby/object:Gem::Version
+        version: '2.6'
   type: :runtime
-  version_requirements: *id012
-- !ruby/object:Gem::Dependency 
-  name: net-ssh-multi
   prerelease: false
-  requirement: &id013 !ruby/object:Gem::Requirement 
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '2.6'
+- !ruby/object:Gem::Dependency
+  name: net-ssh-multi
+  requirement: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
+    requirements:
     - - ~>
-      - !ruby/object:Gem::Version 
-        hash: 19
-        segments: 
-        - 1
-        - 1
-        - 0
+      - !ruby/object:Gem::Version
         version: 1.1.0
   type: :runtime
-  version_requirements: *id013
-- !ruby/object:Gem::Dependency 
-  name: highline
   prerelease: false
-  requirement: &id014 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 29
-        segments: 
-        - 1
-        - 6
-        - 9
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: 1.1.0
+- !ruby/object:Gem::Dependency
+  name: highline
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.6'
+    - - ! '>='
+      - !ruby/object:Gem::Version
         version: 1.6.9
   type: :runtime
-  version_requirements: *id014
-- !ruby/object:Gem::Dependency 
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.6'
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 1.6.9
+- !ruby/object:Gem::Dependency
   name: erubis
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '2.7'
+  type: :runtime
   prerelease: false
-  requirement: &id015 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '2.7'
+- !ruby/object:Gem::Dependency
+  name: diff-lcs
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.2'
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 1.2.4
   type: :runtime
-  version_requirements: *id015
-- !ruby/object:Gem::Dependency 
-  name: moneta
   prerelease: false
-  requirement: &id016 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.2'
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 1.2.4
+- !ruby/object:Gem::Dependency
+  name: chef-zero
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.6'
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 1.6.2
   type: :runtime
-  version_requirements: *id016
-- !ruby/object:Gem::Dependency 
-  name: uuidtools
   prerelease: false
-  requirement: &id017 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.6'
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: 1.6.2
+- !ruby/object:Gem::Dependency
+  name: puma
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.6'
   type: :runtime
-  version_requirements: *id017
-- !ruby/object:Gem::Dependency 
-  name: rdoc
   prerelease: false
-  requirement: &id018 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
-  type: :development
-  version_requirements: *id018
-- !ruby/object:Gem::Dependency 
-  name: sdoc
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '1.6'
+- !ruby/object:Gem::Dependency
+  name: pry
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '0.9'
+  type: :runtime
   prerelease: false
-  requirement: &id019 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: '0.9'
+- !ruby/object:Gem::Dependency
+  name: rdoc
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
   type: :development
-  version_requirements: *id019
-- !ruby/object:Gem::Dependency 
-  name: ronn
   prerelease: false
-  requirement: &id020 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: sdoc
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
   type: :development
-  version_requirements: *id020
-- !ruby/object:Gem::Dependency 
-  name: rake
   prerelease: false
-  requirement: &id021 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: rake
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
   type: :development
-  version_requirements: *id021
-- !ruby/object:Gem::Dependency 
-  name: rack
   prerelease: false
-  requirement: &id022 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: rack
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
   type: :development
-  version_requirements: *id022
-- !ruby/object:Gem::Dependency 
-  name: rspec_junit_formatter
   prerelease: false
-  requirement: &id023 !ruby/object:Gem::Requirement 
-    none: false
-    requirements: 
-    - - ">="
-      - !ruby/object:Gem::Version 
-        hash: 3
-        segments: 
-        - 0
-        version: "0"
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: rspec_junit_formatter
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
   type: :development
-  version_requirements: *id023
-- !ruby/object:Gem::Dependency 
-  name: rspec-core
   prerelease: false
-  requirement: &id024 !ruby/object:Gem::Requirement 
+  version_requirements: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+- !ruby/object:Gem::Dependency
+  name: rspec-core
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
     - - ~>
-      - !ruby/object:Gem::Version 
-        hash: 47
-        segments: 
-        - 2
-        - 8
-        - 0
-        version: 2.8.0
+      - !ruby/object:Gem::Version
+        version: 2.13.0
   type: :development
-  version_requirements: *id024
-- !ruby/object:Gem::Dependency 
-  name: rspec-expectations
   prerelease: false
-  requirement: &id025 !ruby/object:Gem::Requirement 
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: 2.13.0
+- !ruby/object:Gem::Dependency
+  name: rspec-expectations
+  requirement: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
+    requirements:
     - - ~>
-      - !ruby/object:Gem::Version 
-        hash: 47
-        segments: 
-        - 2
-        - 8
-        - 0
-        version: 2.8.0
+      - !ruby/object:Gem::Version
+        version: 2.13.0
   type: :development
-  version_requirements: *id025
-- !ruby/object:Gem::Dependency 
-  name: rspec-mocks
   prerelease: false
-  requirement: &id026 !ruby/object:Gem::Requirement 
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: 2.13.0
+- !ruby/object:Gem::Dependency
+  name: rspec-mocks
+  requirement: !ruby/object:Gem::Requirement
     none: false
-    requirements: 
+    requirements:
     - - ~>
-      - !ruby/object:Gem::Version 
-        hash: 47
-        segments: 
-        - 2
-        - 8
-        - 0
-        version: 2.8.0
+      - !ruby/object:Gem::Version
+        version: 2.13.0
   type: :development
-  version_requirements: *id026
-description: A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure.
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ~>
+      - !ruby/object:Gem::Version
+        version: 2.13.0
+description: A systems integration framework, built to bring the benefits of configuration
+  management to your entire infrastructure.
 email: adam at opscode.com
-executables: 
+executables:
 - chef-client
 - chef-solo
 - knife
+- chef-shell
 - shef
+- chef-apply
+- chef-service-manager
 extensions: []
-
-extra_rdoc_files: 
-- README.rdoc
+extra_rdoc_files:
+- README.md
+- CONTRIBUTING.md
 - LICENSE
-files: 
+files:
+- Rakefile
 - LICENSE
-- README.rdoc
-- distro/common/html/knife-search.1.html
-- distro/common/html/knife-node.1.html
-- distro/common/html/knife-tag.1.html
+- README.md
+- CONTRIBUTING.md
+- distro/arch/etc/conf.d/chef-client.conf
+- distro/arch/etc/conf.d/chef-expander.conf
+- distro/arch/etc/conf.d/chef-server-webui.conf
+- distro/arch/etc/conf.d/chef-server.conf
+- distro/arch/etc/conf.d/chef-solr.conf
+- distro/arch/etc/rc.d/chef-client
+- distro/arch/etc/rc.d/chef-expander
+- distro/arch/etc/rc.d/chef-server
+- distro/arch/etc/rc.d/chef-server-webui
+- distro/arch/etc/rc.d/chef-solr
 - distro/common/html/chef-client.8.html
-- distro/common/html/chef-solo.8.html
-- distro/common/html/knife-role.1.html
+- distro/common/html/chef-expander.8.html
+- distro/common/html/chef-expanderctl.8.html
 - distro/common/html/chef-server-webui.8.html
-- distro/common/html/knife-index.1.html
-- distro/common/html/knife-status.1.html
-- distro/common/html/knife-environment.1.html
-- distro/common/html/knife-recipe.1.html
-- distro/common/html/knife-data-bag.1.html
-- distro/common/html/knife-exec.1.html
+- distro/common/html/chef-server.8.html
+- distro/common/html/chef-shell.1.html
+- distro/common/html/chef-solo.8.html
 - distro/common/html/chef-solr.8.html
-- distro/common/html/knife-configure.1.html
+- distro/common/html/knife-bootstrap.1.html
 - distro/common/html/knife-client.1.html
-- distro/common/html/chef-expanderctl.8.html
-- distro/common/html/knife.1.html
-- distro/common/html/shef.1.html
+- distro/common/html/knife-configure.1.html
+- distro/common/html/knife-cookbook-site.1.html
 - distro/common/html/knife-cookbook.1.html
-- distro/common/html/chef-server.8.html
+- distro/common/html/knife-data-bag.1.html
+- distro/common/html/knife-environment.1.html
+- distro/common/html/knife-exec.1.html
+- distro/common/html/knife-index.1.html
+- distro/common/html/knife-node.1.html
+- distro/common/html/knife-role.1.html
+- distro/common/html/knife-search.1.html
 - distro/common/html/knife-ssh.1.html
-- distro/common/html/chef-expander.8.html
-- distro/common/html/knife-bootstrap.1.html
-- distro/common/html/knife-cookbook-site.1.html
-- distro/common/man/man1/knife-ssh.1
-- distro/common/man/man1/knife-environment.1
+- distro/common/html/knife-status.1.html
+- distro/common/html/knife-tag.1.html
+- distro/common/html/knife.1.html
+- distro/common/man/man1/chef-shell.1
 - distro/common/man/man1/knife-bootstrap.1
+- distro/common/man/man1/knife-client.1
+- distro/common/man/man1/knife-configure.1
+- distro/common/man/man1/knife-cookbook-site.1
 - distro/common/man/man1/knife-cookbook.1
-- distro/common/man/man1/knife-exec.1
-- distro/common/man/man1/knife-tag.1
-- distro/common/man/man1/knife-status.1
-- distro/common/man/man1/knife.1
 - distro/common/man/man1/knife-data-bag.1
-- distro/common/man/man1/knife-index.1
-- distro/common/man/man1/knife-configure.1
-- distro/common/man/man1/knife-search.1
+- distro/common/man/man1/knife-delete.1
+- distro/common/man/man1/knife-deps.1
+- distro/common/man/man1/knife-diff.1
+- distro/common/man/man1/knife-download.1
+- distro/common/man/man1/knife-edit.1
+- distro/common/man/man1/knife-environment.1
+- distro/common/man/man1/knife-exec.1
+- distro/common/man/man1/knife-index-rebuild.1
+- distro/common/man/man1/knife-list.1
 - distro/common/man/man1/knife-node.1
-- distro/common/man/man1/knife-client.1
-- distro/common/man/man1/shef.1
+- distro/common/man/man1/knife-raw.1
+- distro/common/man/man1/knife-recipe-list.1
 - distro/common/man/man1/knife-role.1
-- distro/common/man/man1/knife-cookbook-site.1
-- distro/common/man/man8/chef-server.8
+- distro/common/man/man1/knife-search.1
+- distro/common/man/man1/knife-show.1
+- distro/common/man/man1/knife-ssh.1
+- distro/common/man/man1/knife-status.1
+- distro/common/man/man1/knife-tag.1
+- distro/common/man/man1/knife-upload.1
+- distro/common/man/man1/knife-user.1
+- distro/common/man/man1/knife-xargs.1
+- distro/common/man/man1/knife.1
+- distro/common/man/man1/README.md
 - distro/common/man/man8/chef-client.8
-- distro/common/man/man8/chef-server-webui.8
-- distro/common/man/man8/chef-expander.8
 - distro/common/man/man8/chef-solo.8
-- distro/common/man/man8/chef-expanderctl.8
-- distro/common/man/man8/chef-solr.8
-- distro/common/markdown/man1/knife-ssh.mkd
-- distro/common/markdown/man1/knife-role.mkd
-- distro/common/markdown/man1/knife-cookbook.mkd
-- distro/common/markdown/man1/shef.mkd
-- distro/common/markdown/man1/knife-node.mkd
+- distro/common/markdown/man1/chef-shell.mkd
 - distro/common/markdown/man1/knife-bootstrap.mkd
-- distro/common/markdown/man1/knife-cookbook-site.mkd
 - distro/common/markdown/man1/knife-client.mkd
-- distro/common/markdown/man1/knife.mkd
-- distro/common/markdown/man1/knife-status.mkd
-- distro/common/markdown/man1/knife-data-bag.mkd
 - distro/common/markdown/man1/knife-configure.mkd
-- distro/common/markdown/man1/knife-search.mkd
+- distro/common/markdown/man1/knife-cookbook-site.mkd
+- distro/common/markdown/man1/knife-cookbook.mkd
+- distro/common/markdown/man1/knife-data-bag.mkd
 - distro/common/markdown/man1/knife-environment.mkd
-- distro/common/markdown/man1/knife-index.mkd
 - distro/common/markdown/man1/knife-exec.mkd
+- distro/common/markdown/man1/knife-index.mkd
+- distro/common/markdown/man1/knife-node.mkd
+- distro/common/markdown/man1/knife-role.mkd
+- distro/common/markdown/man1/knife-search.mkd
+- distro/common/markdown/man1/knife-ssh.mkd
+- distro/common/markdown/man1/knife-status.mkd
 - distro/common/markdown/man1/knife-tag.mkd
-- distro/common/markdown/man8/chef-server-webui.mkd
-- distro/common/markdown/man8/chef-solo.mkd
-- distro/common/markdown/man8/chef-solr.mkd
+- distro/common/markdown/man1/knife.mkd
 - distro/common/markdown/man8/chef-client.mkd
 - distro/common/markdown/man8/chef-expander.mkd
-- distro/common/markdown/man8/chef-server.mkd
 - distro/common/markdown/man8/chef-expanderctl.mkd
+- distro/common/markdown/man8/chef-server-webui.mkd
+- distro/common/markdown/man8/chef-server.mkd
+- distro/common/markdown/man8/chef-solo.mkd
+- distro/common/markdown/man8/chef-solr.mkd
 - distro/common/markdown/README
-- distro/redhat/etc/sysconfig/chef-server-webui
-- distro/redhat/etc/sysconfig/chef-solr
-- distro/redhat/etc/sysconfig/chef-expander
-- distro/redhat/etc/sysconfig/chef-client
-- distro/redhat/etc/sysconfig/chef-server
-- distro/redhat/etc/init.d/chef-server-webui
-- distro/redhat/etc/init.d/chef-solr
-- distro/redhat/etc/init.d/chef-expander
+- distro/debian/etc/default/chef-client
+- distro/debian/etc/default/chef-expander
+- distro/debian/etc/default/chef-server
+- distro/debian/etc/default/chef-server-webui
+- distro/debian/etc/default/chef-solr
+- distro/debian/etc/init/chef-client.conf
+- distro/debian/etc/init/chef-expander.conf
+- distro/debian/etc/init/chef-server-webui.conf
+- distro/debian/etc/init/chef-server.conf
+- distro/debian/etc/init/chef-solr.conf
+- distro/debian/etc/init.d/chef-client
+- distro/debian/etc/init.d/chef-expander
+- distro/debian/etc/init.d/chef-server
+- distro/debian/etc/init.d/chef-server-webui
+- distro/debian/etc/init.d/chef-solr
+- distro/README
 - distro/redhat/etc/init.d/chef-client
+- distro/redhat/etc/init.d/chef-expander
 - distro/redhat/etc/init.d/chef-server
-- distro/redhat/etc/logrotate.d/chef-server-webui
-- distro/redhat/etc/logrotate.d/chef-solr
-- distro/redhat/etc/logrotate.d/chef-expander
+- distro/redhat/etc/init.d/chef-server-webui
+- distro/redhat/etc/init.d/chef-solr
 - distro/redhat/etc/logrotate.d/chef-client
+- distro/redhat/etc/logrotate.d/chef-expander
 - distro/redhat/etc/logrotate.d/chef-server
-- distro/arch/etc/conf.d/chef-server-webui.conf
-- distro/arch/etc/conf.d/chef-client.conf
-- distro/arch/etc/conf.d/chef-solr.conf
-- distro/arch/etc/conf.d/chef-expander.conf
-- distro/arch/etc/conf.d/chef-server.conf
-- distro/arch/etc/rc.d/chef-server-webui
-- distro/arch/etc/rc.d/chef-solr
-- distro/arch/etc/rc.d/chef-expander
-- distro/arch/etc/rc.d/chef-client
-- distro/arch/etc/rc.d/chef-server
-- distro/debian/etc/default/chef-server-webui
-- distro/debian/etc/default/chef-solr
-- distro/debian/etc/default/chef-expander
-- distro/debian/etc/default/chef-client
-- distro/debian/etc/default/chef-server
-- distro/debian/etc/init.d/chef-server-webui
-- distro/debian/etc/init.d/chef-solr
-- distro/debian/etc/init.d/chef-expander
-- distro/debian/etc/init.d/chef-client
-- distro/debian/etc/init.d/chef-server
-- distro/debian/etc/init/chef-server-webui.conf
-- distro/debian/etc/init/chef-client.conf
-- distro/debian/etc/init/chef-solr.conf
-- distro/debian/etc/init/chef-expander.conf
-- distro/debian/etc/init/chef-server.conf
+- distro/redhat/etc/logrotate.d/chef-server-webui
+- distro/redhat/etc/logrotate.d/chef-solr
+- distro/redhat/etc/sysconfig/chef-client
+- distro/redhat/etc/sysconfig/chef-expander
+- distro/redhat/etc/sysconfig/chef-server
+- distro/redhat/etc/sysconfig/chef-server-webui
+- distro/redhat/etc/sysconfig/chef-solr
 - distro/windows/service_manager.rb
-- distro/README
-- lib/chef/handler.rb
-- lib/chef/solr_query.rb
-- lib/chef/runner.rb
-- lib/chef/file_access_control/windows.rb
-- lib/chef/file_access_control/unix.rb
-- lib/chef/providers.rb
-- lib/chef/exceptions.rb
-- lib/chef/file_cache.rb
-- lib/chef/solr_query/lucene_nodes.rb
-- lib/chef/solr_query/lucene.treetop
-- lib/chef/solr_query/query_transform.rb
-- lib/chef/solr_query/solr_http_request.rb
-- lib/chef/resource_collection/stepable_iterator.rb
-- lib/chef/webui_user.rb
-- lib/chef/node.rb
+- lib/chef/api_client/registration.rb
+- lib/chef/api_client.rb
+- lib/chef/application/agent.rb
+- lib/chef/application/apply.rb
+- lib/chef/application/client.rb
+- lib/chef/application/knife.rb
+- lib/chef/application/solo.rb
+- lib/chef/application/windows_service.rb
+- lib/chef/application/windows_service_manager.rb
 - lib/chef/application.rb
+- lib/chef/applications.rb
+- lib/chef/checksum/storage/filesystem.rb
+- lib/chef/checksum/storage.rb
+- lib/chef/chef_fs/chef_fs_data_store.rb
+- lib/chef/chef_fs/command_line.rb
+- lib/chef/chef_fs/config.rb
+- lib/chef/chef_fs/data_handler/acl_data_handler.rb
+- lib/chef/chef_fs/data_handler/client_data_handler.rb
+- lib/chef/chef_fs/data_handler/container_data_handler.rb
+- lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
+- lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
+- lib/chef/chef_fs/data_handler/data_handler_base.rb
+- lib/chef/chef_fs/data_handler/environment_data_handler.rb
+- lib/chef/chef_fs/data_handler/group_data_handler.rb
+- lib/chef/chef_fs/data_handler/node_data_handler.rb
+- lib/chef/chef_fs/data_handler/role_data_handler.rb
+- lib/chef/chef_fs/data_handler/user_data_handler.rb
+- lib/chef/chef_fs/file_pattern.rb
+- lib/chef/chef_fs/file_system/acl_dir.rb
+- lib/chef/chef_fs/file_system/acl_entry.rb
+- lib/chef/chef_fs/file_system/acls_dir.rb
+- lib/chef/chef_fs/file_system/already_exists_error.rb
+- lib/chef/chef_fs/file_system/base_fs_dir.rb
+- lib/chef/chef_fs/file_system/base_fs_object.rb
+- lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb
+- lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
+- lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
+- lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
+- lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb
+- lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
+- lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
+- lib/chef/chef_fs/file_system/chef_server_root_dir.rb
+- lib/chef/chef_fs/file_system/cookbook_dir.rb
+- lib/chef/chef_fs/file_system/cookbook_file.rb
+- lib/chef/chef_fs/file_system/cookbook_frozen_error.rb
+- lib/chef/chef_fs/file_system/cookbook_subdir.rb
+- lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
+- lib/chef/chef_fs/file_system/cookbooks_dir.rb
+- lib/chef/chef_fs/file_system/data_bag_dir.rb
+- lib/chef/chef_fs/file_system/data_bags_dir.rb
+- lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb
+- lib/chef/chef_fs/file_system/environments_dir.rb
+- lib/chef/chef_fs/file_system/file_system_entry.rb
+- lib/chef/chef_fs/file_system/file_system_error.rb
+- lib/chef/chef_fs/file_system/file_system_root_dir.rb
+- lib/chef/chef_fs/file_system/memory_dir.rb
+- lib/chef/chef_fs/file_system/memory_file.rb
+- lib/chef/chef_fs/file_system/memory_root.rb
+- lib/chef/chef_fs/file_system/multiplexed_dir.rb
+- lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
+- lib/chef/chef_fs/file_system/nodes_dir.rb
+- lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
+- lib/chef/chef_fs/file_system/not_found_error.rb
+- lib/chef/chef_fs/file_system/operation_failed_error.rb
+- lib/chef/chef_fs/file_system/operation_not_allowed_error.rb
+- lib/chef/chef_fs/file_system/rest_list_dir.rb
+- lib/chef/chef_fs/file_system/rest_list_entry.rb
+- lib/chef/chef_fs/file_system.rb
+- lib/chef/chef_fs/knife.rb
+- lib/chef/chef_fs/parallelizer.rb
+- lib/chef/chef_fs/path_utils.rb
+- lib/chef/chef_fs.rb
+- lib/chef/client.rb
+- lib/chef/config.rb
+- lib/chef/config_fetcher.rb
+- lib/chef/cookbook/chefignore.rb
+- lib/chef/cookbook/cookbook_collection.rb
+- lib/chef/cookbook/cookbook_version_loader.rb
+- lib/chef/cookbook/file_system_file_vendor.rb
+- lib/chef/cookbook/file_vendor.rb
+- lib/chef/cookbook/metadata.rb
+- lib/chef/cookbook/remote_file_vendor.rb
+- lib/chef/cookbook/synchronizer.rb
+- lib/chef/cookbook/syntax_check.rb
+- lib/chef/cookbook_loader.rb
+- lib/chef/cookbook_site_streaming_uploader.rb
+- lib/chef/cookbook_uploader.rb
+- lib/chef/cookbook_version.rb
+- lib/chef/daemon.rb
+- lib/chef/data_bag.rb
+- lib/chef/data_bag_item.rb
+- lib/chef/deprecation/mixin/template.rb
+- lib/chef/deprecation/provider/cookbook_file.rb
+- lib/chef/deprecation/provider/file.rb
+- lib/chef/deprecation/provider/remote_file.rb
+- lib/chef/deprecation/provider/template.rb
+- lib/chef/deprecation/warnings.rb
+- lib/chef/digester.rb
+- lib/chef/dsl/data_query.rb
+- lib/chef/dsl/include_attribute.rb
+- lib/chef/dsl/include_recipe.rb
+- lib/chef/dsl/platform_introspection.rb
+- lib/chef/dsl/recipe.rb
+- lib/chef/dsl/registry_helper.rb
+- lib/chef/dsl.rb
 - lib/chef/encrypted_data_bag_item.rb
+- lib/chef/environment.rb
+- lib/chef/event_dispatch/base.rb
+- lib/chef/event_dispatch/dispatcher.rb
+- lib/chef/exceptions.rb
+- lib/chef/file_access_control/unix.rb
+- lib/chef/file_access_control/windows.rb
 - lib/chef/file_access_control.rb
-- lib/chef/resource.rb
-- lib/chef/streaming_cookbook_uploader.rb
-- lib/chef/util/windows.rb
-- lib/chef/util/file_edit.rb
-- lib/chef/util/windows/net_use.rb
-- lib/chef/util/windows/volume.rb
-- lib/chef/util/windows/net_group.rb
-- lib/chef/util/windows/net_user.rb
-- lib/chef/knife/tag_delete.rb
-- lib/chef/knife/cookbook_metadata_from_file.rb
-- lib/chef/knife/role_bulk_delete.rb
-- lib/chef/knife/data_bag_from_file.rb
-- lib/chef/knife/configure_client.rb
-- lib/chef/knife/search.rb
-- lib/chef/knife/data_bag_show.rb
+- lib/chef/file_cache.rb
+- lib/chef/file_content_management/content_base.rb
+- lib/chef/file_content_management/deploy/cp.rb
+- lib/chef/file_content_management/deploy/mv_unix.rb
+- lib/chef/file_content_management/deploy/mv_windows.rb
+- lib/chef/file_content_management/deploy.rb
+- lib/chef/file_content_management/tempfile.rb
+- lib/chef/formatters/base.rb
+- lib/chef/formatters/doc.rb
+- lib/chef/formatters/error_descriptor.rb
+- lib/chef/formatters/error_inspectors/api_error_formatting.rb
+- lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+- lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
+- lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
+- lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
+- lib/chef/formatters/error_inspectors/registration_error_inspector.rb
+- lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
+- lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
+- lib/chef/formatters/error_inspectors.rb
+- lib/chef/formatters/error_mapper.rb
+- lib/chef/formatters/minimal.rb
+- lib/chef/handler/error_report.rb
+- lib/chef/handler/json_file.rb
+- lib/chef/handler.rb
+- lib/chef/http/auth_credentials.rb
+- lib/chef/http/authenticator.rb
+- lib/chef/http/basic_client.rb
+- lib/chef/http/cookie_jar.rb
+- lib/chef/http/cookie_manager.rb
+- lib/chef/http/decompressor.rb
+- lib/chef/http/http_request.rb
+- lib/chef/http/json_input.rb
+- lib/chef/http/json_output.rb
+- lib/chef/http/json_to_model_output.rb
+- lib/chef/http/simple.rb
+- lib/chef/http/ssl_policies.rb
+- lib/chef/http.rb
+- lib/chef/json_compat.rb
+- lib/chef/knife/bootstrap/archlinux-gems.erb
+- lib/chef/knife/bootstrap/centos5-gems.erb
+- lib/chef/knife/bootstrap/chef-full.erb
+- lib/chef/knife/bootstrap/fedora13-gems.erb
+- lib/chef/knife/bootstrap/ubuntu10.04-apt.erb
+- lib/chef/knife/bootstrap/ubuntu10.04-gems.erb
+- lib/chef/knife/bootstrap/ubuntu12.04-gems.erb
+- lib/chef/knife/bootstrap.rb
+- lib/chef/knife/client_bulk_delete.rb
+- lib/chef/knife/client_create.rb
+- lib/chef/knife/client_delete.rb
 - lib/chef/knife/client_edit.rb
-- lib/chef/knife/data_bag_list.rb
-- lib/chef/knife/cookbook_delete.rb
+- lib/chef/knife/client_list.rb
+- lib/chef/knife/client_reregister.rb
+- lib/chef/knife/client_show.rb
+- lib/chef/knife/configure.rb
+- lib/chef/knife/configure_client.rb
 - lib/chef/knife/cookbook_bulk_delete.rb
-- lib/chef/knife/node_from_file.rb
-- lib/chef/knife/cookbook_upload.rb
+- lib/chef/knife/cookbook_create.rb
+- lib/chef/knife/cookbook_delete.rb
+- lib/chef/knife/cookbook_download.rb
+- lib/chef/knife/cookbook_list.rb
+- lib/chef/knife/cookbook_metadata.rb
+- lib/chef/knife/cookbook_metadata_from_file.rb
+- lib/chef/knife/cookbook_show.rb
+- lib/chef/knife/cookbook_site_download.rb
+- lib/chef/knife/cookbook_site_install.rb
+- lib/chef/knife/cookbook_site_list.rb
+- lib/chef/knife/cookbook_site_search.rb
+- lib/chef/knife/cookbook_site_share.rb
+- lib/chef/knife/cookbook_site_show.rb
 - lib/chef/knife/cookbook_site_unshare.rb
-- lib/chef/knife/core/object_loader.rb
-- lib/chef/knife/core/subcommand_loader.rb
-- lib/chef/knife/core/cookbook_scm_repo.rb
-- lib/chef/knife/core/node_presenter.rb
+- lib/chef/knife/cookbook_site_vendor.rb
+- lib/chef/knife/cookbook_test.rb
+- lib/chef/knife/cookbook_upload.rb
 - lib/chef/knife/core/bootstrap_context.rb
-- lib/chef/knife/core/ui.rb
-- lib/chef/knife/core/text_formatter.rb
+- lib/chef/knife/core/cookbook_scm_repo.rb
 - lib/chef/knife/core/generic_presenter.rb
 - lib/chef/knife/core/node_editor.rb
-- lib/chef/knife/bootstrap.rb
-- lib/chef/knife/cookbook_metadata.rb
-- lib/chef/knife/node_show.rb
+- lib/chef/knife/core/node_presenter.rb
+- lib/chef/knife/core/object_loader.rb
+- lib/chef/knife/core/subcommand_loader.rb
+- lib/chef/knife/core/text_formatter.rb
+- lib/chef/knife/core/ui.rb
 - lib/chef/knife/data_bag_create.rb
-- lib/chef/knife/recipe_list.rb
-- lib/chef/knife/client_show.rb
-- lib/chef/knife/role_from_file.rb
-- lib/chef/knife/node_list.rb
-- lib/chef/knife/role_show.rb
-- lib/chef/knife/environment_show.rb
-- lib/chef/knife/node_create.rb
-- lib/chef/knife/cookbook_download.rb
-- lib/chef/knife/role_edit.rb
+- lib/chef/knife/data_bag_delete.rb
+- lib/chef/knife/data_bag_edit.rb
+- lib/chef/knife/data_bag_from_file.rb
+- lib/chef/knife/data_bag_list.rb
+- lib/chef/knife/data_bag_show.rb
+- lib/chef/knife/delete.rb
+- lib/chef/knife/deps.rb
+- lib/chef/knife/diff.rb
+- lib/chef/knife/download.rb
+- lib/chef/knife/edit.rb
 - lib/chef/knife/environment_create.rb
-- lib/chef/knife/configure.rb
-- lib/chef/knife/status.rb
-- lib/chef/knife/cookbook_test.rb
-- lib/chef/knife/cookbook_show.rb
-- lib/chef/knife/client_delete.rb
+- lib/chef/knife/environment_delete.rb
+- lib/chef/knife/environment_edit.rb
+- lib/chef/knife/environment_from_file.rb
 - lib/chef/knife/environment_list.rb
+- lib/chef/knife/environment_show.rb
+- lib/chef/knife/exec.rb
+- lib/chef/knife/help.rb
+- lib/chef/knife/help_topics.rb
 - lib/chef/knife/index_rebuild.rb
+- lib/chef/knife/list.rb
+- lib/chef/knife/node_bulk_delete.rb
+- lib/chef/knife/node_create.rb
 - lib/chef/knife/node_delete.rb
-- lib/chef/knife/environment_from_file.rb
+- lib/chef/knife/node_edit.rb
+- lib/chef/knife/node_from_file.rb
+- lib/chef/knife/node_list.rb
 - lib/chef/knife/node_run_list_add.rb
-- lib/chef/knife/cookbook_site_install.rb
-- lib/chef/knife/help_topics.rb
-- lib/chef/knife/data_bag_delete.rb
-- lib/chef/knife/role_create.rb
-- lib/chef/knife/cookbook_site_list.rb
-- lib/chef/knife/client_bulk_delete.rb
-- lib/chef/knife/data_bag_edit.rb
-- lib/chef/knife/client_create.rb
 - lib/chef/knife/node_run_list_remove.rb
-- lib/chef/knife/cookbook_create.rb
-- lib/chef/knife/node_edit.rb
-- lib/chef/knife/exec.rb
-- lib/chef/knife/cookbook_list.rb
-- lib/chef/knife/client_list.rb
-- lib/chef/knife/bootstrap/ubuntu10.04-gems.erb
-- lib/chef/knife/bootstrap/chef-full.erb
-- lib/chef/knife/bootstrap/ubuntu12.04-gems.erb
-- lib/chef/knife/bootstrap/archlinux-gems.erb
-- lib/chef/knife/bootstrap/fedora13-gems.erb
-- lib/chef/knife/bootstrap/ubuntu10.04-apt.erb
-- lib/chef/knife/bootstrap/centos5-gems.erb
+- lib/chef/knife/node_run_list_set.rb
+- lib/chef/knife/node_show.rb
+- lib/chef/knife/raw.rb
+- lib/chef/knife/recipe_list.rb
+- lib/chef/knife/role_bulk_delete.rb
+- lib/chef/knife/role_create.rb
+- lib/chef/knife/role_delete.rb
+- lib/chef/knife/role_edit.rb
+- lib/chef/knife/role_from_file.rb
+- lib/chef/knife/role_list.rb
+- lib/chef/knife/role_show.rb
+- lib/chef/knife/search.rb
+- lib/chef/knife/show.rb
 - lib/chef/knife/ssh.rb
-- lib/chef/knife/help.rb
-- lib/chef/knife/environment_edit.rb
-- lib/chef/knife/environment_delete.rb
-- lib/chef/knife/cookbook_site_show.rb
-- lib/chef/knife/cookbook_site_download.rb
-- lib/chef/knife/cookbook_site_share.rb
-- lib/chef/knife/cookbook_site_vendor.rb
-- lib/chef/knife/cookbook_site_search.rb
-- lib/chef/knife/tag_list.rb
-- lib/chef/knife/node_bulk_delete.rb
+- lib/chef/knife/status.rb
 - lib/chef/knife/tag_create.rb
-- lib/chef/knife/client_reregister.rb
-- lib/chef/knife/role_list.rb
-- lib/chef/knife/role_delete.rb
-- lib/chef/rest.rb
-- lib/chef/cookbook_version_selector.rb
-- lib/chef/monkey_patches/dir.rb
-- lib/chef/monkey_patches/string.rb
+- lib/chef/knife/tag_delete.rb
+- lib/chef/knife/tag_list.rb
+- lib/chef/knife/upload.rb
+- lib/chef/knife/user_create.rb
+- lib/chef/knife/user_delete.rb
+- lib/chef/knife/user_edit.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/knife.rb
+- lib/chef/log.rb
+- lib/chef/mash.rb
+- lib/chef/mixin/checksum.rb
+- lib/chef/mixin/command/unix.rb
+- lib/chef/mixin/command/windows.rb
+- lib/chef/mixin/command.rb
+- lib/chef/mixin/convert_to_class_name.rb
+- lib/chef/mixin/create_path.rb
+- lib/chef/mixin/deep_merge.rb
+- lib/chef/mixin/deprecation.rb
+- lib/chef/mixin/enforce_ownership_and_permissions.rb
+- lib/chef/mixin/file_class.rb
+- lib/chef/mixin/from_file.rb
+- lib/chef/mixin/get_source_from_package.rb
+- lib/chef/mixin/language.rb
+- lib/chef/mixin/language_include_attribute.rb
+- lib/chef/mixin/language_include_recipe.rb
+- lib/chef/mixin/params_validate.rb
+- lib/chef/mixin/path_sanity.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/why_run.rb
+- lib/chef/mixin/windows_architecture_helper.rb
+- lib/chef/mixin/xml_escape.rb
+- lib/chef/mixins.rb
+- lib/chef/monkey_patches/file.rb
+- lib/chef/monkey_patches/fileutils.rb
+- lib/chef/monkey_patches/net-ssh-multi.rb
+- lib/chef/monkey_patches/net_http.rb
 - lib/chef/monkey_patches/numeric.rb
 - lib/chef/monkey_patches/object.rb
 - lib/chef/monkey_patches/regexp.rb
-- lib/chef/monkey_patches/moneta.rb
+- lib/chef/monkey_patches/securerandom.rb
+- lib/chef/monkey_patches/string.rb
 - lib/chef/monkey_patches/tempfile.rb
-- lib/chef/shef.rb
-- lib/chef/handler/json_file.rb
-- lib/chef/handler/error_report.rb
+- lib/chef/monologger.rb
+- lib/chef/nil_argument.rb
 - lib/chef/node/attribute.rb
-- lib/chef/rest/rest_request.rb
-- lib/chef/rest/cookie_jar.rb
-- lib/chef/rest/auth_credentials.rb
-- lib/chef/tasks/chef_repo.rake
-- lib/chef/resource_definition.rb
-- lib/chef/shell_out.rb
-- lib/chef/index_queue.rb
-- lib/chef/version.rb
-- lib/chef/provider/subversion.rb
-- lib/chef/provider/package/easy_install.rb
-- lib/chef/provider/package/solaris.rb
-- lib/chef/provider/package/smartos.rb
-- lib/chef/provider/package/rubygems.rb
-- lib/chef/provider/package/macports.rb
-- lib/chef/provider/package/yum-dump.py
-- lib/chef/provider/package/dpkg.rb
-- lib/chef/provider/package/zypper.rb
-- lib/chef/provider/package/yum.rb
-- lib/chef/provider/package/apt.rb
-- lib/chef/provider/package/pacman.rb
-- lib/chef/provider/package/portage.rb
-- lib/chef/provider/package/freebsd.rb
-- lib/chef/provider/package/rpm.rb
+- lib/chef/node/attribute_collections.rb
+- lib/chef/node/immutable_collections.rb
+- lib/chef/node.rb
+- lib/chef/platform/provider_mapping.rb
+- lib/chef/platform/query_helpers.rb
+- lib/chef/platform.rb
+- lib/chef/provider/batch.rb
 - lib/chef/provider/breakpoint.rb
-- lib/chef/provider/execute.rb
-- lib/chef/provider/ohai.rb
-- lib/chef/provider/service/arch.rb
-- lib/chef/provider/service/solaris.rb
-- lib/chef/provider/service/windows.rb
-- lib/chef/provider/service/systemd.rb
-- lib/chef/provider/service/upstart.rb
-- lib/chef/provider/service/invokercd.rb
-- lib/chef/provider/service/redhat.rb
-- lib/chef/provider/service/simple.rb
-- lib/chef/provider/service/macosx.rb
-- lib/chef/provider/service/debian.rb
-- lib/chef/provider/service/gentoo.rb
-- lib/chef/provider/service/insserv.rb
-- lib/chef/provider/service/freebsd.rb
-- lib/chef/provider/service/init.rb
-- lib/chef/provider/mount/windows.rb
-- lib/chef/provider/mount/mount.rb
+- lib/chef/provider/cookbook_file/content.rb
+- lib/chef/provider/cookbook_file.rb
+- lib/chef/provider/cron/aix.rb
+- lib/chef/provider/cron/solaris.rb
+- lib/chef/provider/cron/unix.rb
+- lib/chef/provider/cron.rb
 - lib/chef/provider/deploy/revision.rb
 - lib/chef/provider/deploy/timestamped.rb
-- lib/chef/provider/template.rb
-- lib/chef/provider/git.rb
-- lib/chef/provider/link.rb
-- lib/chef/provider/service.rb
-- lib/chef/provider/remote_directory.rb
-- lib/chef/provider/remote_file.rb
-- lib/chef/provider/user.rb
+- lib/chef/provider/deploy.rb
 - lib/chef/provider/directory.rb
-- lib/chef/provider/cookbook_file.rb
-- lib/chef/provider/ruby_block.rb
 - lib/chef/provider/env/windows.rb
-- lib/chef/provider/script.rb
 - lib/chef/provider/env.rb
-- lib/chef/provider/http_request.rb
-- lib/chef/provider/group/gpasswd.rb
-- lib/chef/provider/group/windows.rb
-- lib/chef/provider/group/dscl.rb
-- lib/chef/provider/group/suse.rb
-- lib/chef/provider/group/usermod.rb
+- lib/chef/provider/erl_call.rb
+- lib/chef/provider/execute.rb
+- lib/chef/provider/file/content.rb
+- lib/chef/provider/file.rb
+- lib/chef/provider/git.rb
 - lib/chef/provider/group/aix.rb
+- lib/chef/provider/group/dscl.rb
+- lib/chef/provider/group/gpasswd.rb
 - lib/chef/provider/group/groupadd.rb
+- lib/chef/provider/group/groupmod.rb
 - lib/chef/provider/group/pw.rb
-- lib/chef/provider/erl_call.rb
-- lib/chef/provider/mdadm.rb
+- lib/chef/provider/group/suse.rb
+- lib/chef/provider/group/usermod.rb
+- lib/chef/provider/group/windows.rb
+- lib/chef/provider/group.rb
+- lib/chef/provider/http_request.rb
+- lib/chef/provider/ifconfig/aix.rb
+- lib/chef/provider/ifconfig/debian.rb
+- lib/chef/provider/ifconfig/redhat.rb
+- lib/chef/provider/ifconfig.rb
+- lib/chef/provider/link.rb
 - lib/chef/provider/log.rb
-- lib/chef/provider/deploy.rb
-- lib/chef/provider/file.rb
-- lib/chef/provider/cron.rb
+- lib/chef/provider/lwrp_base.rb
+- lib/chef/provider/mdadm.rb
+- lib/chef/provider/mount/aix.rb
+- lib/chef/provider/mount/mount.rb
+- lib/chef/provider/mount/windows.rb
 - lib/chef/provider/mount.rb
-- lib/chef/provider/user/useradd.rb
-- lib/chef/provider/user/windows.rb
-- lib/chef/provider/user/dscl.rb
-- lib/chef/provider/user/pw.rb
-- lib/chef/provider/cron/solaris.rb
+- lib/chef/provider/ohai.rb
+- lib/chef/provider/package/aix.rb
+- lib/chef/provider/package/apt.rb
+- lib/chef/provider/package/dpkg.rb
+- lib/chef/provider/package/easy_install.rb
+- lib/chef/provider/package/freebsd.rb
+- lib/chef/provider/package/ips.rb
+- lib/chef/provider/package/macports.rb
+- lib/chef/provider/package/pacman.rb
+- lib/chef/provider/package/portage.rb
+- lib/chef/provider/package/rpm.rb
+- lib/chef/provider/package/rubygems.rb
+- lib/chef/provider/package/smartos.rb
+- lib/chef/provider/package/solaris.rb
+- lib/chef/provider/package/yum-dump.py
+- lib/chef/provider/package/yum.rb
+- lib/chef/provider/package/zypper.rb
 - lib/chef/provider/package.rb
+- lib/chef/provider/powershell_script.rb
+- lib/chef/provider/registry_key.rb
+- lib/chef/provider/remote_directory.rb
+- lib/chef/provider/remote_file/cache_control_data.rb
+- lib/chef/provider/remote_file/content.rb
+- lib/chef/provider/remote_file/fetcher.rb
+- 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.rb
+- lib/chef/provider/resource_update.rb
 - lib/chef/provider/route.rb
-- lib/chef/provider/ifconfig.rb
-- lib/chef/provider/group.rb
-- lib/chef/recipe.rb
-- lib/chef/client.rb
-- lib/chef/mash.rb
-- lib/chef/checksum.rb
-- lib/chef/resources.rb
-- lib/chef/shef/shef_session.rb
-- lib/chef/shef/shef_rest.rb
-- lib/chef/shef/model_wrapper.rb
-- lib/chef/shef/ext.rb
-- lib/chef/data_bag.rb
-- lib/chef/version_constraint.rb
-- lib/chef/role.rb
-- lib/chef/openid_registration.rb
-- lib/chef/application/agent.rb
-- lib/chef/application/windows_service.rb
-- lib/chef/application/client.rb
-- lib/chef/application/solo.rb
-- lib/chef/application/knife.rb
-- lib/chef/run_status.rb
-- lib/chef/couchdb.rb
-- lib/chef/checksum_cache.rb
-- lib/chef/win32/memory.rb
-- lib/chef/win32/version.rb
-- lib/chef/win32/security/token.rb
-- lib/chef/win32/security/sid.rb
-- lib/chef/win32/security/ace.rb
-- lib/chef/win32/security/security_descriptor.rb
-- lib/chef/win32/security/securable_object.rb
-- lib/chef/win32/security/acl.rb
-- lib/chef/win32/api.rb
-- lib/chef/win32/handle.rb
-- lib/chef/win32/process.rb
-- lib/chef/win32/file.rb
-- lib/chef/win32/security.rb
-- lib/chef/win32/api/memory.rb
-- lib/chef/win32/api/psapi.rb
-- lib/chef/win32/api/process.rb
-- lib/chef/win32/api/file.rb
-- lib/chef/win32/api/system.rb
-- lib/chef/win32/api/security.rb
-- lib/chef/win32/api/error.rb
-- lib/chef/win32/api/unicode.rb
-- lib/chef/win32/file/info.rb
-- lib/chef/win32/error.rb
-- lib/chef/win32/unicode.rb
-- lib/chef/index_queue/amqp_client.rb
-- lib/chef/index_queue/consumer.rb
-- lib/chef/index_queue/indexable.rb
-- lib/chef/data_bag_item.rb
-- lib/chef/applications.rb
-- lib/chef/sandbox.rb
-- lib/chef/cookbook_site_streaming_uploader.rb
-- lib/chef/checksum/storage.rb
-- lib/chef/checksum/storage/filesystem.rb
-- lib/chef/daemon.rb
-- lib/chef/config.rb
-- lib/chef/cookbook_uploader.rb
-- lib/chef/cookbook_loader.rb
-- lib/chef/api_client.rb
-- lib/chef/mixins.rb
-- lib/chef/log.rb
-- lib/chef/resource_definition_list.rb
-- lib/chef/resource_collection.rb
-- lib/chef/nil_argument.rb
-- lib/chef/run_context.rb
-- lib/chef/environment.rb
-- lib/chef/platform.rb
-- lib/chef/run_list/versioned_recipe_list.rb
-- lib/chef/run_list/run_list_expansion.rb
-- lib/chef/run_list/run_list_item.rb
-- lib/chef/cookbook/file_vendor.rb
-- lib/chef/cookbook/cookbook_version_loader.rb
-- lib/chef/cookbook/metadata.rb
-- lib/chef/cookbook/syntax_check.rb
-- lib/chef/cookbook/remote_file_vendor.rb
-- lib/chef/cookbook/cookbook_collection.rb
-- lib/chef/cookbook/file_system_file_vendor.rb
-- lib/chef/cookbook/chefignore.rb
-- lib/chef/cookbook_version.rb
-- lib/chef/run_list.rb
+- lib/chef/provider/ruby_block.rb
+- lib/chef/provider/script.rb
+- lib/chef/provider/service/arch.rb
+- lib/chef/provider/service/debian.rb
+- lib/chef/provider/service/freebsd.rb
+- lib/chef/provider/service/gentoo.rb
+- lib/chef/provider/service/init.rb
+- lib/chef/provider/service/insserv.rb
+- lib/chef/provider/service/invokercd.rb
+- lib/chef/provider/service/macosx.rb
+- lib/chef/provider/service/redhat.rb
+- lib/chef/provider/service/simple.rb
+- lib/chef/provider/service/solaris.rb
+- lib/chef/provider/service/systemd.rb
+- lib/chef/provider/service/upstart.rb
+- lib/chef/provider/service/windows.rb
+- lib/chef/provider/service.rb
+- lib/chef/provider/subversion.rb
+- lib/chef/provider/template/content.rb
+- lib/chef/provider/template.rb
+- lib/chef/provider/template_finder.rb
+- lib/chef/provider/user/dscl.rb
+- lib/chef/provider/user/pw.rb
+- lib/chef/provider/user/solaris.rb
+- lib/chef/provider/user/useradd.rb
+- lib/chef/provider/user/windows.rb
+- lib/chef/provider/user.rb
+- lib/chef/provider/windows_script.rb
 - lib/chef/provider.rb
-- lib/chef/resource/subversion.rb
-- lib/chef/resource/rpm_package.rb
-- lib/chef/resource/gem_package.rb
-- lib/chef/resource/easy_install_package.rb
+- lib/chef/providers.rb
+- lib/chef/recipe.rb
+- lib/chef/reserved_names.rb
+- lib/chef/resource/apt_package.rb
+- lib/chef/resource/bash.rb
+- lib/chef/resource/batch.rb
+- lib/chef/resource/bff_package.rb
 - lib/chef/resource/breakpoint.rb
+- lib/chef/resource/chef_gem.rb
 - lib/chef/resource/conditional.rb
+- lib/chef/resource/conditional_action_not_nothing.rb
+- lib/chef/resource/cookbook_file.rb
+- lib/chef/resource/cron.rb
+- lib/chef/resource/csh.rb
+- lib/chef/resource/deploy.rb
+- lib/chef/resource/deploy_revision.rb
+- lib/chef/resource/directory.rb
+- lib/chef/resource/dpkg_package.rb
+- lib/chef/resource/easy_install_package.rb
+- lib/chef/resource/env.rb
+- lib/chef/resource/erl_call.rb
 - lib/chef/resource/execute.rb
-- lib/chef/resource/ohai.rb
-- lib/chef/resource/timestamped_deploy.rb
-- lib/chef/resource/apt_package.rb
-- lib/chef/resource/chef_gem.rb
-- lib/chef/resource/template.rb
+- lib/chef/resource/file.rb
+- lib/chef/resource/freebsd_package.rb
+- lib/chef/resource/gem_package.rb
 - lib/chef/resource/git.rb
+- lib/chef/resource/group.rb
+- lib/chef/resource/http_request.rb
+- lib/chef/resource/ifconfig.rb
+- lib/chef/resource/ips_package.rb
 - lib/chef/resource/link.rb
-- lib/chef/resource/perl.rb
-- lib/chef/resource/pacman_package.rb
-- lib/chef/resource/yum_package.rb
-- lib/chef/resource/smartos_package.rb
-- lib/chef/resource/solaris_package.rb
-- lib/chef/resource/service.rb
-- lib/chef/resource/dpkg_package.rb
-- lib/chef/resource/remote_directory.rb
-- lib/chef/resource/remote_file.rb
-- lib/chef/resource/user.rb
-- lib/chef/resource/directory.rb
-- lib/chef/resource/cookbook_file.rb
-- lib/chef/resource/ruby_block.rb
+- lib/chef/resource/log.rb
+- lib/chef/resource/lwrp_base.rb
 - lib/chef/resource/macports_package.rb
-- lib/chef/resource/python.rb
-- lib/chef/resource/freebsd_package.rb
-- lib/chef/resource/script.rb
-- lib/chef/resource/env.rb
-- lib/chef/resource/scm.rb
-- lib/chef/resource/http_request.rb
-- lib/chef/resource/erl_call.rb
 - lib/chef/resource/mdadm.rb
-- lib/chef/resource/log.rb
-- lib/chef/resource/deploy.rb
-- lib/chef/resource/file.rb
-- lib/chef/resource/cron.rb
-- lib/chef/resource/portage_package.rb
-- lib/chef/resource/ruby.rb
 - lib/chef/resource/mount.rb
-- lib/chef/resource/deploy_revision.rb
-- lib/chef/resource/bash.rb
+- lib/chef/resource/ohai.rb
 - lib/chef/resource/package.rb
+- lib/chef/resource/pacman_package.rb
+- lib/chef/resource/perl.rb
+- lib/chef/resource/portage_package.rb
+- lib/chef/resource/powershell_script.rb
+- lib/chef/resource/python.rb
+- lib/chef/resource/registry_key.rb
+- lib/chef/resource/remote_directory.rb
+- lib/chef/resource/remote_file.rb
 - lib/chef/resource/route.rb
-- lib/chef/resource/ifconfig.rb
-- lib/chef/resource/csh.rb
-- lib/chef/resource/group.rb
-- lib/chef/certificate.rb
-- lib/chef/search/query.rb
-- lib/chef/reserved_names.rb
-- lib/chef/mixin/convert_to_class_name.rb
-- lib/chef/mixin/deep_merge.rb
-- lib/chef/mixin/shell_out.rb
-- lib/chef/mixin/checksum.rb
-- lib/chef/mixin/create_path.rb
-- lib/chef/mixin/template.rb
-- lib/chef/mixin/command/windows.rb
-- lib/chef/mixin/command/unix.rb
-- lib/chef/mixin/enforce_ownership_and_permissions.rb
-- lib/chef/mixin/from_file.rb
-- lib/chef/mixin/recipe_definition_dsl_core.rb
-- lib/chef/mixin/get_source_from_package.rb
-- lib/chef/mixin/deprecation.rb
-- lib/chef/mixin/language.rb
-- lib/chef/mixin/xml_escape.rb
-- lib/chef/mixin/language_include_recipe.rb
-- lib/chef/mixin/check_helper.rb
-- lib/chef/mixin/path_sanity.rb
-- lib/chef/mixin/command.rb
-- lib/chef/mixin/params_validate.rb
-- lib/chef/mixin/securable.rb
-- lib/chef/mixin/language_include_attribute.rb
-- lib/chef/json_compat.rb
+- lib/chef/resource/rpm_package.rb
+- lib/chef/resource/ruby.rb
+- lib/chef/resource/ruby_block.rb
+- lib/chef/resource/scm.rb
+- lib/chef/resource/script.rb
+- lib/chef/resource/service.rb
+- lib/chef/resource/smartos_package.rb
+- lib/chef/resource/solaris_package.rb
+- lib/chef/resource/subversion.rb
+- lib/chef/resource/template.rb
+- lib/chef/resource/timestamped_deploy.rb
+- lib/chef/resource/user.rb
+- lib/chef/resource/windows_script.rb
+- lib/chef/resource/yum_package.rb
+- lib/chef/resource.rb
+- lib/chef/resource_collection/stepable_iterator.rb
+- lib/chef/resource_collection.rb
+- lib/chef/resource_definition.rb
+- lib/chef/resource_definition_list.rb
 - lib/chef/resource_platform_map.rb
-- lib/chef/knife.rb
+- lib/chef/resource_reporter.rb
+- lib/chef/resources.rb
+- lib/chef/rest.rb
+- lib/chef/role.rb
+- lib/chef/run_context/cookbook_compiler.rb
+- lib/chef/run_context.rb
+- lib/chef/run_list/run_list_expansion.rb
+- lib/chef/run_list/run_list_item.rb
+- lib/chef/run_list/versioned_recipe_list.rb
+- lib/chef/run_list.rb
+- lib/chef/run_lock.rb
+- lib/chef/run_status.rb
+- lib/chef/runner.rb
+- lib/chef/sandbox.rb
+- lib/chef/scan_access_control.rb
+- lib/chef/search/query.rb
+- lib/chef/server_api.rb
+- lib/chef/shef/ext.rb
+- lib/chef/shell/ext.rb
+- lib/chef/shell/model_wrapper.rb
+- lib/chef/shell/shell_rest.rb
+- lib/chef/shell/shell_session.rb
+- lib/chef/shell.rb
+- lib/chef/shell_out.rb
+- lib/chef/streaming_cookbook_uploader.rb
+- lib/chef/tasks/chef_repo.rake
+- lib/chef/user.rb
+- lib/chef/util/backup.rb
+- lib/chef/util/diff.rb
+- lib/chef/util/file_edit.rb
+- lib/chef/util/selinux.rb
+- lib/chef/util/windows/net_group.rb
+- lib/chef/util/windows/net_use.rb
+- lib/chef/util/windows/net_user.rb
+- lib/chef/util/windows/volume.rb
+- lib/chef/util/windows.rb
+- lib/chef/version/platform.rb
+- lib/chef/version.rb
 - lib/chef/version_class.rb
+- lib/chef/version_constraint/platform.rb
+- lib/chef/version_constraint.rb
+- lib/chef/win32/api/error.rb
+- lib/chef/win32/api/file.rb
+- lib/chef/win32/api/memory.rb
+- lib/chef/win32/api/process.rb
+- lib/chef/win32/api/psapi.rb
+- lib/chef/win32/api/security.rb
+- lib/chef/win32/api/synchronization.rb
+- lib/chef/win32/api/system.rb
+- lib/chef/win32/api/unicode.rb
+- lib/chef/win32/api.rb
+- lib/chef/win32/error.rb
+- lib/chef/win32/file/info.rb
+- lib/chef/win32/file.rb
+- lib/chef/win32/handle.rb
+- lib/chef/win32/memory.rb
+- lib/chef/win32/mutex.rb
+- lib/chef/win32/process.rb
+- lib/chef/win32/registry.rb
+- lib/chef/win32/security/ace.rb
+- lib/chef/win32/security/acl.rb
+- lib/chef/win32/security/securable_object.rb
+- lib/chef/win32/security/security_descriptor.rb
+- lib/chef/win32/security/sid.rb
+- lib/chef/win32/security/token.rb
+- lib/chef/win32/security.rb
+- lib/chef/win32/unicode.rb
+- lib/chef/win32/version.rb
 - lib/chef.rb
+- tasks/rspec.rb
+- spec/data/apt/chef-integration-test-1.0/debian/changelog
+- spec/data/apt/chef-integration-test-1.0/debian/compat
+- spec/data/apt/chef-integration-test-1.0/debian/control
+- spec/data/apt/chef-integration-test-1.0/debian/copyright
+- spec/data/apt/chef-integration-test-1.0/debian/files
+- spec/data/apt/chef-integration-test-1.0/debian/rules
+- spec/data/apt/chef-integration-test-1.0/debian/source/format
+- spec/data/apt/chef-integration-test-1.1/debian/changelog
+- spec/data/apt/chef-integration-test-1.1/debian/compat
+- spec/data/apt/chef-integration-test-1.1/debian/control
+- spec/data/apt/chef-integration-test-1.1/debian/copyright
+- spec/data/apt/chef-integration-test-1.1/debian/files
+- spec/data/apt/chef-integration-test-1.1/debian/rules
+- spec/data/apt/chef-integration-test-1.1/debian/source/format
+- spec/data/apt/chef-integration-test_1.0-1_amd64.changes
+- spec/data/apt/chef-integration-test_1.0-1_amd64.deb
+- spec/data/apt/chef-integration-test_1.0.orig.tar.gz
+- spec/data/apt/chef-integration-test_1.1-1_amd64.changes
+- spec/data/apt/chef-integration-test_1.1-1_amd64.deb
+- spec/data/apt/chef-integration-test_1.1.orig.tar.gz
+- spec/data/apt/var/www/apt/conf/distributions
+- spec/data/apt/var/www/apt/conf/incoming
+- spec/data/apt/var/www/apt/conf/pulls
+- spec/data/apt/var/www/apt/db/checksums.db
+- spec/data/apt/var/www/apt/db/contents.cache.db
+- spec/data/apt/var/www/apt/db/packages.db
+- spec/data/apt/var/www/apt/db/references.db
+- spec/data/apt/var/www/apt/db/release.caches.db
+- spec/data/apt/var/www/apt/db/version
+- spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages
+- spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz
+- spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release
+- spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages
+- spec/data/apt/var/www/apt/dists/sid/Release
+- spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb
+- spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb
+- spec/data/bad-config.rb
+- spec/data/big_json.json
+- spec/data/big_json_plus_one.json
+- spec/data/bootstrap/encrypted_data_bag_secret
+- spec/data/bootstrap/no_proxy.erb
+- spec/data/bootstrap/secret.erb
+- spec/data/bootstrap/test-hints.erb
+- spec/data/bootstrap/test.erb
+- spec/data/cb_version_cookbooks/tatft/attributes/default.rb
+- spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb
+- spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz
+- spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb
+- spec/data/cb_version_cookbooks/tatft/providers/lwp.rb
+- spec/data/cb_version_cookbooks/tatft/README.rdoc
+- spec/data/cb_version_cookbooks/tatft/recipes/default.rb
+- spec/data/cb_version_cookbooks/tatft/resources/lwr.rb
+- spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb
+- spec/data/checksum/random.txt
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0
+- spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0
+- spec/data/config.rb
+- spec/data/cookbooks/angrybash/recipes/default.rb
+- spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl
+- spec/data/cookbooks/apache2/recipes/default.rb
+- spec/data/cookbooks/borken/recipes/default.rb
+- spec/data/cookbooks/borken/templates/default/borken.erb
+- spec/data/cookbooks/chefignore
+- spec/data/cookbooks/ignorken/recipes/default.rb
+- spec/data/cookbooks/ignorken/recipes/ignoreme.rb
+- spec/data/cookbooks/java/files/default/java.response
+- spec/data/cookbooks/openldap/attributes/default.rb
+- spec/data/cookbooks/openldap/attributes/smokey.rb
+- spec/data/cookbooks/openldap/definitions/client.rb
+- spec/data/cookbooks/openldap/definitions/server.rb
+- spec/data/cookbooks/openldap/files/default/.dotfile
+- spec/data/cookbooks/openldap/files/default/.ssh/id_rsa
+- spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir
+- spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt
+- spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt
+- spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile
+- spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt
+- spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt
+- spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt
+- spec/data/cookbooks/openldap/metadata.rb
+- spec/data/cookbooks/openldap/recipes/default.rb
+- spec/data/cookbooks/openldap/recipes/gigantor.rb
+- spec/data/cookbooks/openldap/recipes/one.rb
+- spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb
+- spec/data/cookbooks/openldap/templates/default/helper_test.erb
+- spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb
+- spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb
+- spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb
+- spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb
+- spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb
+- spec/data/cookbooks/openldap/templates/default/test.erb
+- spec/data/cookbooks/preseed/files/default/preseed-file.seed
+- spec/data/cookbooks/preseed/files/default/preseed-template.seed
+- spec/data/cookbooks/preseed/templates/default/preseed-template.seed
+- spec/data/definitions/test.rb
+- spec/data/environment-config.rb
+- spec/data/file-providers-method-snapshot-chef-11-4.json
+- spec/data/fileedit/blank
+- spec/data/fileedit/hosts
+- spec/data/gems/chef-integration-test-0.1.0.gem
+- spec/data/git_bundles/example-repo.gitbundle
+- spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle
+- spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle
+- spec/data/git_bundles/sinatra-test-app.gitbundle
+- spec/data/kitchen/chefignore
+- spec/data/kitchen/openldap/attributes/default.rb
+- spec/data/kitchen/openldap/attributes/robinson.rb
+- spec/data/kitchen/openldap/definitions/client.rb
+- spec/data/kitchen/openldap/definitions/drewbarrymore.rb
+- spec/data/kitchen/openldap/recipes/gigantor.rb
+- spec/data/kitchen/openldap/recipes/ignoreme.rb
+- spec/data/kitchen/openldap/recipes/woot.rb
+- spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb
+- spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb
+- spec/data/knife_subcommand/test_explicit_category.rb
+- spec/data/knife_subcommand/test_name_mapping.rb
+- spec/data/knife_subcommand/test_yourself.rb
+- spec/data/lwrp/providers/buck_passer.rb
+- spec/data/lwrp/providers/buck_passer_2.rb
+- spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
+- spec/data/lwrp/providers/inline_compiler.rb
+- spec/data/lwrp/providers/monkey_name_printer.rb
+- spec/data/lwrp/providers/paint_drying_watcher.rb
+- spec/data/lwrp/providers/thumb_twiddler.rb
+- spec/data/lwrp/resources/bar.rb
+- spec/data/lwrp/resources/foo.rb
+- spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
+- spec/data/lwrp_const_scoping/resources/conflict.rb
+- spec/data/lwrp_override/providers/buck_passer.rb
+- spec/data/lwrp_override/resources/foo.rb
+- spec/data/metadata/quick_start/metadata.rb
+- spec/data/nodes/default.rb
+- spec/data/nodes/test.example.com.rb
+- spec/data/nodes/test.rb
+- spec/data/null_config.rb
+- spec/data/object_loader/environments/test.json
+- spec/data/object_loader/environments/test.rb
+- spec/data/object_loader/environments/test_json_class.json
+- spec/data/object_loader/nodes/test.json
+- spec/data/object_loader/nodes/test.rb
+- spec/data/object_loader/nodes/test_json_class.json
+- spec/data/object_loader/roles/test.json
+- spec/data/object_loader/roles/test.rb
+- spec/data/object_loader/roles/test_json_class.json
+- spec/data/old_home_dir/my-dot-emacs
+- spec/data/old_home_dir/my-dot-vim
+- spec/data/partial_one.erb
+- spec/data/recipes/test.rb
+- spec/data/remote_directory_data/remote_dir_file.txt
+- spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt
+- spec/data/remote_file/nyan_cat.png
+- spec/data/remote_file/nyan_cat.png.gz
+- spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb
+- spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb
+- spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb
+- spec/data/run_context/cookbooks/circular-dep1/metadata.rb
+- spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb
+- spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb
+- spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
+- spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb
+- spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb
+- spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb
+- spec/data/run_context/cookbooks/circular-dep2/metadata.rb
+- spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb
+- spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb
+- spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
+- spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb
+- spec/data/run_context/cookbooks/dependency1/attributes/default.rb
+- spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb
+- spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb
+- spec/data/run_context/cookbooks/dependency1/libraries/lib.rb
+- spec/data/run_context/cookbooks/dependency1/providers/provider.rb
+- spec/data/run_context/cookbooks/dependency1/recipes/default.rb
+- spec/data/run_context/cookbooks/dependency1/resources/resource.rb
+- spec/data/run_context/cookbooks/dependency2/attributes/default.rb
+- spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb
+- spec/data/run_context/cookbooks/dependency2/libraries/lib.rb
+- spec/data/run_context/cookbooks/dependency2/providers/provider.rb
+- spec/data/run_context/cookbooks/dependency2/recipes/default.rb
+- spec/data/run_context/cookbooks/dependency2/resources/resource.rb
+- spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb
+- spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb
+- spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb
+- spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb
+- spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
+- spec/data/run_context/cookbooks/test/attributes/default.rb
+- spec/data/run_context/cookbooks/test/attributes/george.rb
+- spec/data/run_context/cookbooks/test/definitions/new_animals.rb
+- spec/data/run_context/cookbooks/test/definitions/new_cat.rb
+- spec/data/run_context/cookbooks/test/definitions/test_res.rb
+- spec/data/run_context/cookbooks/test/providers/provider.rb
+- spec/data/run_context/cookbooks/test/recipes/default.rb
+- spec/data/run_context/cookbooks/test/recipes/one.rb
+- spec/data/run_context/cookbooks/test/recipes/two.rb
+- spec/data/run_context/cookbooks/test/resources/resource.rb
+- spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb
+- spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb
+- spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb
+- spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb
+- spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb
+- spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb
+- spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
+- spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb
+- spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb
+- spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb
+- spec/data/run_context/cookbooks/test-with-deps/metadata.rb
+- spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb
+- spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb
+- spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb
+- spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
+- spec/data/run_context/nodes/run_context.rb
+- spec/data/search_queries_to_transform.txt
+- spec/data/shef-config.rb
+- spec/data/ssl/5e707473.0
+- spec/data/ssl/chef-rspec.cert
+- spec/data/ssl/chef-rspec.key
+- spec/data/ssl/key.pem
+- spec/data/ssl/private_key.pem
+- spec/data/ssl/private_key_with_whitespace.pem
+- spec/data/templates/seattle.txt
+- spec/data/trusted_certs/example.crt
+- spec/data/trusted_certs/intermediate.pem
+- spec/data/trusted_certs/opscode.pem
+- spec/data/trusted_certs/root.pem
+- spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm
+- spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm
+- spec/functional/assets/mytest-1.0-1.noarch.rpm
+- spec/functional/assets/mytest-2.0-1.noarch.rpm
+- spec/functional/assets/PkgA.1.0.0.0.bff
+- spec/functional/assets/PkgA.2.0.0.0.bff
+- spec/functional/dsl/registry_helper_spec.rb
+- spec/functional/file_content_management/deploy_strategies_spec.rb
+- spec/functional/knife/cookbook_delete_spec.rb
+- spec/functional/knife/exec_spec.rb
+- spec/functional/knife/smoke_test.rb
+- spec/functional/knife/ssh_spec.rb
+- spec/functional/provider/remote_file/cache_control_data_spec.rb
+- spec/functional/resource/base.rb
+- spec/functional/resource/batch_spec.rb
+- spec/functional/resource/bff_spec.rb
+- spec/functional/resource/cookbook_file_spec.rb
+- spec/functional/resource/cron_spec.rb
+- spec/functional/resource/deploy_revision_spec.rb
+- spec/functional/resource/directory_spec.rb
+- spec/functional/resource/file_spec.rb
+- spec/functional/resource/git_spec.rb
+- spec/functional/resource/group_spec.rb
+- spec/functional/resource/ifconfig_spec.rb
+- spec/functional/resource/link_spec.rb
+- spec/functional/resource/mount_spec.rb
+- spec/functional/resource/package_spec.rb
+- spec/functional/resource/powershell_spec.rb
+- spec/functional/resource/registry_spec.rb
+- spec/functional/resource/remote_directory_spec.rb
+- spec/functional/resource/remote_file_spec.rb
+- spec/functional/resource/rpm_spec.rb
+- spec/functional/resource/template_spec.rb
+- spec/functional/resource/user_spec.rb
+- spec/functional/run_lock_spec.rb
+- spec/functional/shell_spec.rb
+- spec/functional/tiny_server_spec.rb
+- spec/functional/version_spec.rb
+- spec/functional/win32/registry_helper_spec.rb
+- spec/functional/win32/security_spec.rb
+- spec/functional/win32/service_manager_spec.rb
+- spec/functional/win32/versions_spec.rb
+- spec/integration/client/client_spec.rb
+- spec/integration/knife/chef_fs_data_store_spec.rb
+- spec/integration/knife/chef_repo_path_spec.rb
+- spec/integration/knife/chef_repository_file_system_spec.rb
+- spec/integration/knife/chefignore_spec.rb
+- spec/integration/knife/common_options_spec.rb
+- spec/integration/knife/delete_spec.rb
+- spec/integration/knife/deps_spec.rb
+- spec/integration/knife/diff_spec.rb
+- spec/integration/knife/download_spec.rb
+- spec/integration/knife/list_spec.rb
+- spec/integration/knife/raw_spec.rb
+- spec/integration/knife/redirection_spec.rb
+- spec/integration/knife/show_spec.rb
+- spec/integration/knife/upload_spec.rb
+- spec/integration/solo/solo_spec.rb
+- spec/rcov.opts
+- spec/spec_helper.rb
+- spec/stress/win32/file_spec.rb
+- spec/stress/win32/memory_spec.rb
+- spec/stress/win32/security_spec.rb
+- spec/support/chef_helpers.rb
+- spec/support/lib/chef/provider/easy.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/with_state.rb
+- spec/support/lib/chef/resource/zen_master.rb
+- spec/support/lib/library_load_order.rb
+- spec/support/matchers/leak.rb
+- spec/support/mock/constant.rb
+- spec/support/mock/platform.rb
+- spec/support/platform_helpers.rb
+- spec/support/platforms/prof/gc.rb
+- spec/support/platforms/prof/win32.rb
+- spec/support/platforms/win32/spec_service.rb
+- spec/support/shared/functional/diff_disabled.rb
+- spec/support/shared/functional/directory_resource.rb
+- spec/support/shared/functional/file_resource.rb
+- spec/support/shared/functional/knife.rb
+- spec/support/shared/functional/securable_resource.rb
+- spec/support/shared/functional/securable_resource_with_reporting.rb
+- spec/support/shared/functional/windows_script.rb
+- spec/support/shared/integration/integration_helper.rb
+- spec/support/shared/integration/knife_support.rb
+- spec/support/shared/unit/api_error_inspector.rb
+- spec/support/shared/unit/execute_resource.rb
+- spec/support/shared/unit/file_system_support.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/script_resource.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/application/agent_spec.rb
+- spec/unit/application/apply.rb
+- spec/unit/application/client_spec.rb
+- spec/unit/application/knife_spec.rb
+- spec/unit/application/server_spec.rb
+- spec/unit/application/solo_spec.rb
+- spec/unit/application_spec.rb
+- spec/unit/checksum/storage/filesystem_spec.rb
+- spec/unit/chef_fs/diff_spec.rb
+- spec/unit/chef_fs/file_pattern_spec.rb
+- spec/unit/chef_fs/file_system/operation_failed_error_spec.rb
+- spec/unit/chef_fs/file_system_spec.rb
+- 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/metadata_spec.rb
+- spec/unit/cookbook/synchronizer_spec.rb
+- spec/unit/cookbook/syntax_check_spec.rb
+- spec/unit/cookbook_loader_spec.rb
+- spec/unit/cookbook_manifest_spec.rb
+- spec/unit/cookbook_site_streaming_uploader.rb
+- spec/unit/cookbook_spec.rb
+- spec/unit/cookbook_version_spec.rb
+- spec/unit/daemon_spec.rb
+- spec/unit/data_bag_item_spec.rb
+- spec/unit/data_bag_spec.rb
+- spec/unit/deprecation_spec.rb
+- spec/unit/digester_spec.rb
+- spec/unit/dsl/data_query_spec.rb
+- spec/unit/dsl/platform_introspection_spec.rb
+- spec/unit/dsl/regsitry_helper_spec.rb
+- spec/unit/encrypted_data_bag_item_spec.rb
+- spec/unit/environment_spec.rb
+- spec/unit/exceptions_spec.rb
+- spec/unit/file_access_control_spec.rb
+- spec/unit/file_cache_spec.rb
+- spec/unit/file_content_management/deploy/cp_spec.rb
+- spec/unit/file_content_management/deploy/mv_unix_spec.rb
+- spec/unit/file_content_management/deploy/mv_windows_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
+- spec/unit/formatters/error_inspectors/node_load_error_inspector_spec.rb
+- spec/unit/formatters/error_inspectors/registration_error_inspector_spec.rb
+- spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
+- spec/unit/formatters/error_inspectors/run_list_expansion_error_inspector_spec.rb
+- spec/unit/handler/json_file_spec.rb
+- spec/unit/handler_spec.rb
+- spec/unit/http/ssl_policies_spec.rb
+- spec/unit/json_compat_spec.rb
+- spec/unit/knife/bootstrap_spec.rb
+- spec/unit/knife/client_bulk_delete_spec.rb
+- spec/unit/knife/client_create_spec.rb
+- spec/unit/knife/client_delete_spec.rb
+- spec/unit/knife/client_edit_spec.rb
+- spec/unit/knife/client_list_spec.rb
+- spec/unit/knife/client_reregister_spec.rb
+- spec/unit/knife/client_show_spec.rb
+- spec/unit/knife/config_file_selection_spec.rb
+- spec/unit/knife/configure_client_spec.rb
+- spec/unit/knife/configure_spec.rb
+- spec/unit/knife/cookbook_bulk_delete_spec.rb
+- spec/unit/knife/cookbook_create_spec.rb
+- spec/unit/knife/cookbook_delete_spec.rb
+- spec/unit/knife/cookbook_download_spec.rb
+- spec/unit/knife/cookbook_list_spec.rb
+- spec/unit/knife/cookbook_metadata_from_file_spec.rb
+- spec/unit/knife/cookbook_metadata_spec.rb
+- spec/unit/knife/cookbook_show_spec.rb
+- spec/unit/knife/cookbook_site_download_spec.rb
+- spec/unit/knife/cookbook_site_install_spec.rb
+- spec/unit/knife/cookbook_site_share_spec.rb
+- spec/unit/knife/cookbook_site_unshare_spec.rb
+- spec/unit/knife/cookbook_test_spec.rb
+- spec/unit/knife/cookbook_upload_spec.rb
+- spec/unit/knife/core/bootstrap_context_spec.rb
+- spec/unit/knife/core/cookbook_scm_repo_spec.rb
+- spec/unit/knife/core/object_loader_spec.rb
+- spec/unit/knife/core/subcommand_loader_spec.rb
+- spec/unit/knife/core/ui_spec.rb
+- spec/unit/knife/data_bag_create_spec.rb
+- spec/unit/knife/data_bag_edit_spec.rb
+- spec/unit/knife/data_bag_from_file_spec.rb
+- spec/unit/knife/data_bag_show_spec.rb
+- spec/unit/knife/environment_create_spec.rb
+- spec/unit/knife/environment_delete_spec.rb
+- spec/unit/knife/environment_edit_spec.rb
+- spec/unit/knife/environment_from_file_spec.rb
+- spec/unit/knife/environment_list_spec.rb
+- spec/unit/knife/environment_show_spec.rb
+- spec/unit/knife/index_rebuild_spec.rb
+- spec/unit/knife/knife_help.rb
+- spec/unit/knife/node_bulk_delete_spec.rb
+- spec/unit/knife/node_delete_spec.rb
+- spec/unit/knife/node_edit_spec.rb
+- spec/unit/knife/node_from_file_spec.rb
+- spec/unit/knife/node_list_spec.rb
+- spec/unit/knife/node_run_list_add_spec.rb
+- 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/role_bulk_delete_spec.rb
+- spec/unit/knife/role_create_spec.rb
+- spec/unit/knife/role_delete_spec.rb
+- spec/unit/knife/role_edit_spec.rb
+- spec/unit/knife/role_from_file_spec.rb
+- spec/unit/knife/role_list_spec.rb
+- spec/unit/knife/ssh_spec.rb
+- spec/unit/knife/status_spec.rb
+- spec/unit/knife/tag_create_spec.rb
+- spec/unit/knife/tag_delete_spec.rb
+- spec/unit/knife/tag_list_spec.rb
+- spec/unit/knife/user_create_spec.rb
+- spec/unit/knife/user_delete_spec.rb
+- spec/unit/knife/user_edit_spec.rb
+- spec/unit/knife/user_list_spec.rb
+- spec/unit/knife/user_reregister_spec.rb
+- spec/unit/knife/user_show_spec.rb
+- spec/unit/knife_spec.rb
+- spec/unit/log_spec.rb
+- spec/unit/lwrp_spec.rb
+- spec/unit/mash_spec.rb
+- spec/unit/mixin/checksum_spec.rb
+- spec/unit/mixin/command_spec.rb
+- spec/unit/mixin/convert_to_class_name_spec.rb
+- spec/unit/mixin/deep_merge_spec.rb
+- spec/unit/mixin/deprecation_spec.rb
+- spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
+- spec/unit/mixin/params_validate_spec.rb
+- spec/unit/mixin/path_sanity_spec.rb
+- spec/unit/mixin/securable_spec.rb
+- spec/unit/mixin/shell_out_spec.rb
+- spec/unit/mixin/template_spec.rb
+- spec/unit/mixin/windows_architecture_helper_spec.rb
+- spec/unit/mixin/xml_escape_spec.rb
+- spec/unit/monkey_patches/string_spec.rb
+- spec/unit/node/attribute_spec.rb
+- spec/unit/node/immutable_collections_spec.rb
+- spec/unit/node_spec.rb
+- spec/unit/platform_spec.rb
+- spec/unit/provider/breakpoint_spec.rb
+- spec/unit/provider/cookbook_file/content_spec.rb
+- spec/unit/provider/cookbook_file_spec.rb
+- spec/unit/provider/cron/unix_spec.rb
+- spec/unit/provider/cron_spec.rb
+- spec/unit/provider/deploy/revision_spec.rb
+- spec/unit/provider/deploy/timestamped_spec.rb
+- spec/unit/provider/deploy_spec.rb
+- spec/unit/provider/directory_spec.rb
+- spec/unit/provider/env_spec.rb
+- spec/unit/provider/erl_call_spec.rb
+- spec/unit/provider/execute_spec.rb
+- spec/unit/provider/file/content_spec.rb
+- spec/unit/provider/file_spec.rb
+- spec/unit/provider/git_spec.rb
+- spec/unit/provider/group/dscl_spec.rb
+- spec/unit/provider/group/gpasswd_spec.rb
+- spec/unit/provider/group/groupadd_spec.rb
+- spec/unit/provider/group/groupmod_spec.rb
+- spec/unit/provider/group/pw_spec.rb
+- spec/unit/provider/group/usermod_spec.rb
+- spec/unit/provider/group/windows_spec.rb
+- spec/unit/provider/group_spec.rb
+- spec/unit/provider/http_request_spec.rb
+- spec/unit/provider/ifconfig/aix_spec.rb
+- spec/unit/provider/ifconfig/debian_spec.rb
+- spec/unit/provider/ifconfig/redhat_spec.rb
+- spec/unit/provider/ifconfig_spec.rb
+- spec/unit/provider/link_spec.rb
+- spec/unit/provider/log_spec.rb
+- spec/unit/provider/mdadm_spec.rb
+- spec/unit/provider/mount/aix_spec.rb
+- spec/unit/provider/mount/mount_spec.rb
+- spec/unit/provider/mount/windows_spec.rb
+- spec/unit/provider/mount_spec.rb
+- spec/unit/provider/ohai_spec.rb
+- spec/unit/provider/package/aix_spec.rb
+- spec/unit/provider/package/apt_spec.rb
+- spec/unit/provider/package/dpkg_spec.rb
+- spec/unit/provider/package/easy_install_spec.rb
+- spec/unit/provider/package/freebsd_spec.rb
+- spec/unit/provider/package/ips_spec.rb
+- spec/unit/provider/package/macports_spec.rb
+- spec/unit/provider/package/pacman_spec.rb
+- spec/unit/provider/package/portage_spec.rb
+- spec/unit/provider/package/rpm_spec.rb
+- spec/unit/provider/package/rubygems_spec.rb
+- spec/unit/provider/package/smartos_spec.rb
+- spec/unit/provider/package/solaris_spec.rb
+- spec/unit/provider/package/yum_spec.rb
+- spec/unit/provider/package/zypper_spec.rb
+- spec/unit/provider/package_spec.rb
+- spec/unit/provider/powershell_spec.rb
+- spec/unit/provider/registry_key_spec.rb
+- spec/unit/provider/remote_directory_spec.rb
+- spec/unit/provider/remote_file/cache_control_data_spec.rb
+- spec/unit/provider/remote_file/content_spec.rb
+- spec/unit/provider/remote_file/fetcher_spec.rb
+- 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_spec.rb
+- spec/unit/provider/route_spec.rb
+- spec/unit/provider/ruby_block_spec.rb
+- spec/unit/provider/script_spec.rb
+- spec/unit/provider/service/arch_service_spec.rb
+- spec/unit/provider/service/debian_service_spec.rb
+- spec/unit/provider/service/freebsd_service_spec.rb
+- spec/unit/provider/service/gentoo_service_spec.rb
+- spec/unit/provider/service/init_service_spec.rb
+- spec/unit/provider/service/insserv_service_spec.rb
+- spec/unit/provider/service/invokercd_service_spec.rb
+- spec/unit/provider/service/macosx_spec.rb
+- spec/unit/provider/service/redhat_spec.rb
+- spec/unit/provider/service/simple_service_spec.rb
+- spec/unit/provider/service/solaris_smf_service_spec.rb
+- spec/unit/provider/service/systemd_service_spec.rb
+- spec/unit/provider/service/upstart_service_spec.rb
+- spec/unit/provider/service/windows_spec.rb
+- spec/unit/provider/service_spec.rb
+- spec/unit/provider/subversion_spec.rb
+- spec/unit/provider/template/content_spec.rb
+- spec/unit/provider/template_spec.rb
+- spec/unit/provider/user/dscl_spec.rb
+- spec/unit/provider/user/pw_spec.rb
+- spec/unit/provider/user/solaris_spec.rb
+- spec/unit/provider/user/useradd_spec.rb
+- spec/unit/provider/user/windows_spec.rb
+- spec/unit/provider/user_spec.rb
+- spec/unit/provider_spec.rb
+- spec/unit/recipe_spec.rb
+- spec/unit/registry_helper_spec.rb
+- spec/unit/resource/apt_package_spec.rb
+- spec/unit/resource/bash_spec.rb
+- spec/unit/resource/batch_spec.rb
+- spec/unit/resource/breakpoint_spec.rb
+- spec/unit/resource/chef_gem_spec.rb
+- spec/unit/resource/conditional_action_not_nothing_spec.rb
+- spec/unit/resource/conditional_spec.rb
+- spec/unit/resource/cookbook_file_spec.rb
+- spec/unit/resource/cron_spec.rb
+- spec/unit/resource/csh_spec.rb
+- spec/unit/resource/deploy_revision_spec.rb
+- spec/unit/resource/deploy_spec.rb
+- spec/unit/resource/directory_spec.rb
+- spec/unit/resource/dpkg_package_spec.rb
+- spec/unit/resource/easy_install_package_spec.rb
+- spec/unit/resource/env_spec.rb
+- spec/unit/resource/erl_call_spec.rb
+- spec/unit/resource/execute_spec.rb
+- spec/unit/resource/file_spec.rb
+- spec/unit/resource/freebsd_package_spec.rb
+- spec/unit/resource/gem_package_spec.rb
+- spec/unit/resource/git_spec.rb
+- spec/unit/resource/group_spec.rb
+- spec/unit/resource/http_request_spec.rb
+- spec/unit/resource/ifconfig_spec.rb
+- spec/unit/resource/ips_package_spec.rb
+- spec/unit/resource/link_spec.rb
+- spec/unit/resource/log_spec.rb
+- spec/unit/resource/macports_package_spec.rb
+- spec/unit/resource/mdadm_spec.rb
+- spec/unit/resource/mount_spec.rb
+- spec/unit/resource/ohai_spec.rb
+- spec/unit/resource/package_spec.rb
+- spec/unit/resource/pacman_package_spec.rb
+- spec/unit/resource/perl_spec.rb
+- spec/unit/resource/portage_package_spec.rb
+- spec/unit/resource/powershell_spec.rb
+- spec/unit/resource/python_spec.rb
+- spec/unit/resource/registry_key_spec.rb
+- spec/unit/resource/remote_directory_spec.rb
+- spec/unit/resource/remote_file_spec.rb
+- spec/unit/resource/route_spec.rb
+- spec/unit/resource/rpm_package_spec.rb
+- spec/unit/resource/ruby_block_spec.rb
+- spec/unit/resource/ruby_spec.rb
+- spec/unit/resource/scm_spec.rb
+- spec/unit/resource/script_spec.rb
+- spec/unit/resource/service_spec.rb
+- spec/unit/resource/smartos_package_spec.rb
+- spec/unit/resource/solaris_package_spec.rb
+- spec/unit/resource/subversion_spec.rb
+- spec/unit/resource/template_spec.rb
+- spec/unit/resource/timestamped_deploy_spec.rb
+- spec/unit/resource/user_spec.rb
+- spec/unit/resource/yum_package_spec.rb
+- spec/unit/resource_collection/stepable_iterator_spec.rb
+- spec/unit/resource_collection_spec.rb
+- spec/unit/resource_definition_spec.rb
+- spec/unit/resource_platform_map_spec.rb
+- spec/unit/resource_reporter_spec.rb
+- spec/unit/resource_spec.rb
+- spec/unit/rest/auth_credentials_spec.rb
+- spec/unit/rest_spec.rb
+- spec/unit/role_spec.rb
+- spec/unit/run_context/cookbook_compiler_spec.rb
+- spec/unit/run_context_spec.rb
+- spec/unit/run_list/run_list_expansion_spec.rb
+- spec/unit/run_list/run_list_item_spec.rb
+- spec/unit/run_list/versioned_recipe_list_spec.rb
+- spec/unit/run_list_spec.rb
+- spec/unit/run_lock_spec.rb
+- spec/unit/run_status_spec.rb
+- spec/unit/runner_spec.rb
+- spec/unit/scan_access_control_spec.rb
+- spec/unit/search/query_spec.rb
+- spec/unit/shell/model_wrapper_spec.rb
+- spec/unit/shell/shell_ext_spec.rb
+- spec/unit/shell/shell_session_spec.rb
+- spec/unit/shell_out_spec.rb
+- spec/unit/shell_spec.rb
+- spec/unit/user_spec.rb
+- spec/unit/util/backup_spec.rb
+- spec/unit/util/diff_spec.rb
+- spec/unit/util/file_edit_spec.rb
+- spec/unit/util/selinux_spec.rb
+- spec/unit/version/platform_spec.rb
+- spec/unit/version_class_spec.rb
+- spec/unit/version_constraint/platform_spec.rb
+- spec/unit/version_constraint_spec.rb
+- spec/unit/windows_service_spec.rb
 - bin/chef-client
 - bin/chef-solo
 - bin/knife
+- bin/chef-shell
 - bin/shef
+- bin/chef-apply
+- bin/chef-service-manager
 homepage: http://wiki.opscode.com/display/chef
 licenses: []
-
 post_install_message: 
 rdoc_options: []
-
-require_paths: 
+require_paths:
 - lib
-required_ruby_version: !ruby/object:Gem::Requirement 
+required_ruby_version: !ruby/object:Gem::Requirement
   none: false
-  requirements: 
-  - - ">="
-    - !ruby/object:Gem::Version 
-      hash: 3
-      segments: 
-      - 0
-      version: "0"
-required_rubygems_version: !ruby/object:Gem::Requirement 
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
   none: false
-  requirements: 
-  - - ">="
-    - !ruby/object:Gem::Version 
-      hash: 3
-      segments: 
-      - 0
-      version: "0"
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
 requirements: []
-
 rubyforge_project: 
-rubygems_version: 1.8.10
+rubygems_version: 1.8.23
 signing_key: 
 specification_version: 3
-summary: A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure.
+summary: A systems integration framework, built to bring the benefits of configuration
+  management to your entire infrastructure.
 test_files: []
-
diff --git a/spec/data/apt/chef-integration-test-1.0/debian/changelog b/spec/data/apt/chef-integration-test-1.0/debian/changelog
new file mode 100644
index 0000000..bb34505
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.0/debian/changelog
@@ -0,0 +1,5 @@
+chef-integration-test (1.0-1) unstable; urgency=low
+
+  * Initial release (Closes: #CHEF-1718)
+
+ -- Joshua Timberman <joshua at opscode.com>  Thu, 30 Sep 2010 09:53:45 -0600
diff --git a/spec/data/apt/chef-integration-test-1.0/debian/compat b/spec/data/apt/chef-integration-test-1.0/debian/compat
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.0/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/spec/data/apt/chef-integration-test-1.0/debian/control b/spec/data/apt/chef-integration-test-1.0/debian/control
new file mode 100644
index 0000000..e77b01b
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.0/debian/control
@@ -0,0 +1,13 @@
+Source: chef-integration-test
+Section: ruby
+Priority: extra
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Build-Depends: debhelper (>= 7.0.50~)
+Standards-Version: 3.8.4
+Homepage: http://tickets.opscode.com
+
+Package: chef-integration-test
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/spec/data/apt/chef-integration-test-1.0/debian/copyright b/spec/data/apt/chef-integration-test-1.0/debian/copyright
new file mode 100644
index 0000000..72b6c65
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.0/debian/copyright
@@ -0,0 +1,34 @@
+This work was packaged by:
+
+    Joshua Timberman <Joshua Timberman <joshua at opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600
+
+Upstream Author(s):
+
+    Opscode, Inc.
+
+Copyright:
+
+    Copyright (C) 2010 Opscode, Inc
+
+License:
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+The Debian packaging is:
+
+    Copyright (C) 2010 Opscode, Inc (<legal at opscode.com>)
+
+
+and is licensed under the Apache 2.0 license.
+
+See "/usr/share/common-licenses/Apache-2.0"
diff --git a/spec/data/apt/chef-integration-test-1.0/debian/files b/spec/data/apt/chef-integration-test-1.0/debian/files
new file mode 100644
index 0000000..536f4be
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.0/debian/files
@@ -0,0 +1 @@
+chef-integration-test_1.0-1_amd64.deb ruby extra
diff --git a/spec/data/apt/chef-integration-test-1.0/debian/rules b/spec/data/apt/chef-integration-test-1.0/debian/rules
new file mode 100755
index 0000000..b760bee
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.0/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+	dh $@ 
diff --git a/spec/data/apt/chef-integration-test-1.0/debian/source/format b/spec/data/apt/chef-integration-test-1.0/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.0/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/spec/data/apt/chef-integration-test-1.1/debian/changelog b/spec/data/apt/chef-integration-test-1.1/debian/changelog
new file mode 100644
index 0000000..fc693c1
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.1/debian/changelog
@@ -0,0 +1,11 @@
+chef-integration-test (1.1-1) unstable; urgency=low
+
+  * New upstream release (1.1)
+
+ -- Joshua Timberman <joshua at opscode.com>  Thu, 30 Sep 2010 10:09:34 -0600
+
+chef-integration-test (1.0-1) unstable; urgency=low
+
+  * Initial release (Closes: #CHEF-1718)
+
+ -- Joshua Timberman <joshua at opscode.com>  Thu, 30 Sep 2010 09:53:45 -0600
diff --git a/spec/data/apt/chef-integration-test-1.1/debian/compat b/spec/data/apt/chef-integration-test-1.1/debian/compat
new file mode 100644
index 0000000..7f8f011
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.1/debian/compat
@@ -0,0 +1 @@
+7
diff --git a/spec/data/apt/chef-integration-test-1.1/debian/control b/spec/data/apt/chef-integration-test-1.1/debian/control
new file mode 100644
index 0000000..e77b01b
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.1/debian/control
@@ -0,0 +1,13 @@
+Source: chef-integration-test
+Section: ruby
+Priority: extra
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Build-Depends: debhelper (>= 7.0.50~)
+Standards-Version: 3.8.4
+Homepage: http://tickets.opscode.com
+
+Package: chef-integration-test
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
diff --git a/spec/data/apt/chef-integration-test-1.1/debian/copyright b/spec/data/apt/chef-integration-test-1.1/debian/copyright
new file mode 100644
index 0000000..72b6c65
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.1/debian/copyright
@@ -0,0 +1,34 @@
+This work was packaged by:
+
+    Joshua Timberman <Joshua Timberman <joshua at opscode.com>> on Thu, 30 Sep 2010 09:53:45 -0600
+
+Upstream Author(s):
+
+    Opscode, Inc.
+
+Copyright:
+
+    Copyright (C) 2010 Opscode, Inc
+
+License:
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+The Debian packaging is:
+
+    Copyright (C) 2010 Opscode, Inc (<legal at opscode.com>)
+
+
+and is licensed under the Apache 2.0 license.
+
+See "/usr/share/common-licenses/Apache-2.0"
diff --git a/spec/data/apt/chef-integration-test-1.1/debian/files b/spec/data/apt/chef-integration-test-1.1/debian/files
new file mode 100644
index 0000000..d72553c
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.1/debian/files
@@ -0,0 +1 @@
+chef-integration-test_1.1-1_amd64.deb ruby extra
diff --git a/spec/data/apt/chef-integration-test-1.1/debian/rules b/spec/data/apt/chef-integration-test-1.1/debian/rules
new file mode 100755
index 0000000..b760bee
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.1/debian/rules
@@ -0,0 +1,13 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+	dh $@ 
diff --git a/spec/data/apt/chef-integration-test-1.1/debian/source/format b/spec/data/apt/chef-integration-test-1.1/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/spec/data/apt/chef-integration-test-1.1/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/spec/data/apt/chef-integration-test_1.0-1_amd64.changes b/spec/data/apt/chef-integration-test_1.0-1_amd64.changes
new file mode 100644
index 0000000..4746b83
--- /dev/null
+++ b/spec/data/apt/chef-integration-test_1.0-1_amd64.changes
@@ -0,0 +1,22 @@
+Format: 1.8
+Date: Thu, 30 Sep 2010 09:53:45 -0600
+Source: chef-integration-test
+Binary: chef-integration-test
+Architecture: amd64
+Version: 1.0-1
+Distribution: unstable
+Urgency: low
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Changed-By: Joshua Timberman <joshua at opscode.com>
+Description: 
+ chef-integration-test - Chef integration tests for APT in Cucumber
+Changes: 
+ chef-integration-test (1.0-1) unstable; urgency=low
+ .
+   * Initial release (Closes: #CHEF-1718)
+Checksums-Sha1: 
+ b44685ff59626bc94c67e60665f06c4643fe9767 1680 chef-integration-test_1.0-1_amd64.deb
+Checksums-Sha256: 
+ da176f4405fa21fd7207d4785680c6996d395a1ca132f2d5565a61c5479b1116 1680 chef-integration-test_1.0-1_amd64.deb
+Files: 
+ 713722480408ecc8e7220aea52bdd76e 1680 ruby extra chef-integration-test_1.0-1_amd64.deb
diff --git a/spec/data/apt/chef-integration-test_1.0-1_amd64.deb b/spec/data/apt/chef-integration-test_1.0-1_amd64.deb
new file mode 100644
index 0000000..458dd02
Binary files /dev/null and b/spec/data/apt/chef-integration-test_1.0-1_amd64.deb differ
diff --git a/spec/data/apt/chef-integration-test_1.0.orig.tar.gz b/spec/data/apt/chef-integration-test_1.0.orig.tar.gz
new file mode 100644
index 0000000..3de028d
Binary files /dev/null and b/spec/data/apt/chef-integration-test_1.0.orig.tar.gz differ
diff --git a/spec/data/apt/chef-integration-test_1.1-1_amd64.changes b/spec/data/apt/chef-integration-test_1.1-1_amd64.changes
new file mode 100644
index 0000000..f014de8
--- /dev/null
+++ b/spec/data/apt/chef-integration-test_1.1-1_amd64.changes
@@ -0,0 +1,22 @@
+Format: 1.8
+Date: Thu, 30 Sep 2010 10:09:34 -0600
+Source: chef-integration-test
+Binary: chef-integration-test
+Architecture: amd64
+Version: 1.1-1
+Distribution: unstable
+Urgency: low
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Changed-By: Joshua Timberman <joshua at opscode.com>
+Description: 
+ chef-integration-test - Chef integration tests for APT in Cucumber
+Changes: 
+ chef-integration-test (1.1-1) unstable; urgency=low
+ .
+   * New upstream release (1.1)
+Checksums-Sha1: 
+ 43c5653a9a5b9419849173a4ec3a9855cf0327a3 1722 chef-integration-test_1.1-1_amd64.deb
+Checksums-Sha256: 
+ 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1 1722 chef-integration-test_1.1-1_amd64.deb
+Files: 
+ 4b05bace483dbca54efc21f97ee47e1d 1722 ruby extra chef-integration-test_1.1-1_amd64.deb
diff --git a/spec/data/apt/chef-integration-test_1.1-1_amd64.deb b/spec/data/apt/chef-integration-test_1.1-1_amd64.deb
new file mode 100644
index 0000000..c4fac10
Binary files /dev/null and b/spec/data/apt/chef-integration-test_1.1-1_amd64.deb differ
diff --git a/spec/data/apt/chef-integration-test_1.1.orig.tar.gz b/spec/data/apt/chef-integration-test_1.1.orig.tar.gz
new file mode 100644
index 0000000..5fda119
Binary files /dev/null and b/spec/data/apt/chef-integration-test_1.1.orig.tar.gz differ
diff --git a/spec/data/apt/var/www/apt/conf/distributions b/spec/data/apt/var/www/apt/conf/distributions
new file mode 100644
index 0000000..285c1a8
--- /dev/null
+++ b/spec/data/apt/var/www/apt/conf/distributions
@@ -0,0 +1,7 @@
+Origin: localhost
+Label: apt repository
+Codename: sid
+Architectures: amd64
+Components: main
+Description: Apt repository
+Pull: sid
diff --git a/spec/data/apt/var/www/apt/conf/incoming b/spec/data/apt/var/www/apt/conf/incoming
new file mode 100644
index 0000000..d44e59c
--- /dev/null
+++ b/spec/data/apt/var/www/apt/conf/incoming
@@ -0,0 +1,4 @@
+Name: default
+IncomingDir: /tmp/incoming
+TempDir: /tmp
+Allow: sid unstable>sid
diff --git a/spec/data/apt/var/www/apt/conf/pulls b/spec/data/apt/var/www/apt/conf/pulls
new file mode 100644
index 0000000..0fc3358
--- /dev/null
+++ b/spec/data/apt/var/www/apt/conf/pulls
@@ -0,0 +1,3 @@
+Name: sid
+From: sid
+Components: main
diff --git a/spec/data/apt/var/www/apt/db/checksums.db b/spec/data/apt/var/www/apt/db/checksums.db
new file mode 100644
index 0000000..e36ade2
Binary files /dev/null and b/spec/data/apt/var/www/apt/db/checksums.db differ
diff --git a/spec/data/apt/var/www/apt/db/contents.cache.db b/spec/data/apt/var/www/apt/db/contents.cache.db
new file mode 100644
index 0000000..04a0c4a
Binary files /dev/null and b/spec/data/apt/var/www/apt/db/contents.cache.db differ
diff --git a/spec/data/apt/var/www/apt/db/packages.db b/spec/data/apt/var/www/apt/db/packages.db
new file mode 100644
index 0000000..43c70b0
Binary files /dev/null and b/spec/data/apt/var/www/apt/db/packages.db differ
diff --git a/spec/data/apt/var/www/apt/db/references.db b/spec/data/apt/var/www/apt/db/references.db
new file mode 100644
index 0000000..47c99fe
Binary files /dev/null and b/spec/data/apt/var/www/apt/db/references.db differ
diff --git a/spec/data/apt/var/www/apt/db/release.caches.db b/spec/data/apt/var/www/apt/db/release.caches.db
new file mode 100644
index 0000000..0e251c5
Binary files /dev/null and b/spec/data/apt/var/www/apt/db/release.caches.db differ
diff --git a/spec/data/apt/var/www/apt/db/version b/spec/data/apt/var/www/apt/db/version
new file mode 100644
index 0000000..a690869
--- /dev/null
+++ b/spec/data/apt/var/www/apt/db/version
@@ -0,0 +1,4 @@
+4.2.0
+3.3.0
+bdb4.8.30
+bdb4.8.0
diff --git a/spec/data/apt/var/www/apt/dists/sid/Release b/spec/data/apt/var/www/apt/dists/sid/Release
new file mode 100644
index 0000000..44ccd07
--- /dev/null
+++ b/spec/data/apt/var/www/apt/dists/sid/Release
@@ -0,0 +1,19 @@
+Origin: localhost
+Label: apt repository
+Codename: sid
+Date: Thu, 30 Sep 2010 16:33:01 UTC
+Architectures: amd64
+Components: main
+Description: Apt repository
+MD5Sum:
+ 92ed2cc14e37e9ab23466b27857d29ac 596 main/binary-amd64/Packages
+ c7726773341137b71cc971d44ddec4f5 394 main/binary-amd64/Packages.gz
+ 46cd71c965ce0813c94ef78c836cc7d3 104 main/binary-amd64/Release
+SHA1:
+ cde25071c5fcee59cee8dcd773ca419dcb40d946 596 main/binary-amd64/Packages
+ ce04daff75d4b27371d691d645282b198045544a 394 main/binary-amd64/Packages.gz
+ 91ca9531e3afa7a540cabdc6030c6f75d315fec7 104 main/binary-amd64/Release
+SHA256:
+ af601ce143f33405425746462973adc0fda3aceb381d1c739851b95ee0814ca3 596 main/binary-amd64/Packages
+ 15e98119705a08018d4583caabc91d36ba12e6f1c8af0f799a3ec8ca5bfaa80d 394 main/binary-amd64/Packages.gz
+ 098c599ac5b0a98785336afb2bc9c47002570ffa07dd62321c6f70b9fdb74325 104 main/binary-amd64/Release
diff --git a/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages b/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages
new file mode 100644
index 0000000..209c23c
--- /dev/null
+++ b/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages
@@ -0,0 +1,16 @@
+Package: chef-integration-test
+Version: 1.1-1
+Architecture: amd64
+Maintainer: Joshua Timberman <Joshua Timberman <joshua at opscode.com>>
+Installed-Size: 32
+Homepage: http://tickets.opscode.com
+Priority: extra
+Section: ruby
+Filename: pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb
+Size: 1722
+SHA256: 84e2f087f7e11d1b73743007ecfc6b8b34e03f6917c0993b35c0758ee59702c1
+SHA1: 43c5653a9a5b9419849173a4ec3a9855cf0327a3
+MD5sum: 4b05bace483dbca54efc21f97ee47e1d
+Description: Chef integration tests for APT in Cucumber
+ This package is used for cucumber integration testing in Chef.
+
diff --git a/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz b/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz
new file mode 100644
index 0000000..8a2c1e8
Binary files /dev/null and b/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Packages.gz differ
diff --git a/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release b/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release
new file mode 100644
index 0000000..e913d70
--- /dev/null
+++ b/spec/data/apt/var/www/apt/dists/sid/main/binary-amd64/Release
@@ -0,0 +1,5 @@
+Component: main
+Origin: localhost
+Label: apt repository
+Architecture: amd64
+Description: Apt repository
diff --git a/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages b/spec/data/apt/var/www/apt/dists/sid/main/binary-i386/Packages
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb b/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb
new file mode 100644
index 0000000..458dd02
Binary files /dev/null and b/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.0-1_amd64.deb differ
diff --git a/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb b/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb
new file mode 100644
index 0000000..c4fac10
Binary files /dev/null and b/spec/data/apt/var/www/apt/pool/main/c/chef-integration-test/chef-integration-test_1.1-1_amd64.deb differ
diff --git a/spec/data/bad-config.rb b/spec/data/bad-config.rb
new file mode 100644
index 0000000..5477a69
--- /dev/null
+++ b/spec/data/bad-config.rb
@@ -0,0 +1 @@
+monkey_soup("tastes nice")
\ No newline at end of file
diff --git a/spec/data/big_json.json b/spec/data/big_json.json
new file mode 100644
index 0000000..96c2818
--- /dev/null
+++ b/spec/data/big_json.json
@@ -0,0 +1,2 @@
+{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{" [...]
+}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
\ No newline at end of file
diff --git a/spec/data/big_json_plus_one.json b/spec/data/big_json_plus_one.json
new file mode 100644
index 0000000..8ea4b74
--- /dev/null
+++ b/spec/data/big_json_plus_one.json
@@ -0,0 +1,2 @@
+{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{" [...]
+}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
diff --git a/spec/data/bootstrap/encrypted_data_bag_secret b/spec/data/bootstrap/encrypted_data_bag_secret
new file mode 100644
index 0000000..ac88558
--- /dev/null
+++ b/spec/data/bootstrap/encrypted_data_bag_secret
@@ -0,0 +1 @@
+supersekret_from_file
diff --git a/spec/data/bootstrap/no_proxy.erb b/spec/data/bootstrap/no_proxy.erb
new file mode 100644
index 0000000..6945704
--- /dev/null
+++ b/spec/data/bootstrap/no_proxy.erb
@@ -0,0 +1,2 @@
+bash -c '
+<%= config_content %>'
diff --git a/spec/data/bootstrap/secret.erb b/spec/data/bootstrap/secret.erb
new file mode 100644
index 0000000..e0ad415
--- /dev/null
+++ b/spec/data/bootstrap/secret.erb
@@ -0,0 +1,9 @@
+bash -c '
+<% if encrypted_data_bag_secret -%>
+awk NF > /etc/chef/encrypted_data_bag_secret <<'EOP'
+<%= encrypted_data_bag_secret %>
+EOP
+chmod 0600 /etc/chef/encrypted_data_bag_secret
+<% end -%>
+
+<%= config_content %>'
diff --git a/spec/data/bootstrap/test-hints.erb b/spec/data/bootstrap/test-hints.erb
new file mode 100644
index 0000000..29ba710
--- /dev/null
+++ b/spec/data/bootstrap/test-hints.erb
@@ -0,0 +1,12 @@
+<%# Generate Ohai Hints -%>
+<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
+mkdir -p /etc/chef/ohai/hints
+<% end -%>
+
+<% @chef_config[:knife][:hints].each do |name, hash| -%>
+(
+cat <<'EOP'
+<%= hash.to_json %>
+EOP
+) > /etc/chef/ohai/hints/<%= name %>.json
+<% end -%>
diff --git a/spec/data/bootstrap/test.erb b/spec/data/bootstrap/test.erb
new file mode 100644
index 0000000..7cdc7df
--- /dev/null
+++ b/spec/data/bootstrap/test.erb
@@ -0,0 +1 @@
+<%= first_boot.to_json %>
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/README.rdoc b/spec/data/cb_version_cookbooks/tatft/README.rdoc
new file mode 100644
index 0000000..460d96b
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/README.rdoc
@@ -0,0 +1,3 @@
+= THIS RECIPE
+* is for testing CookbookLoader/CookbookVersion
+* has at least one of every kind of file that cookbooks can have
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/attributes/default.rb b/spec/data/cb_version_cookbooks/tatft/attributes/default.rb
new file mode 100644
index 0000000..4777445
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/attributes/default.rb
@@ -0,0 +1 @@
+#one_of_each default attributes
diff --git a/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb b/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb
new file mode 100644
index 0000000..3912b37
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/definitions/runit_service.rb
@@ -0,0 +1 @@
+# IRL the runit_service is a definition to set up runit services
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz b/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz
new file mode 100644
index 0000000..3e74966
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/files/default/giant_blob.tgz
@@ -0,0 +1 @@
+# not really a giant blob #
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb b/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb
new file mode 100644
index 0000000..fea05ba
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/libraries/ownage.rb
@@ -0,0 +1 @@
+# 0wnage
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb b/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb
new file mode 100644
index 0000000..977ad19
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/providers/lwp.rb
@@ -0,0 +1 @@
+# a LWP
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/recipes/default.rb b/spec/data/cb_version_cookbooks/tatft/recipes/default.rb
new file mode 100644
index 0000000..48eacf8
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/recipes/default.rb
@@ -0,0 +1 @@
+# the default recipe
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb b/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb
new file mode 100644
index 0000000..987114f
--- /dev/null
+++ b/spec/data/cb_version_cookbooks/tatft/resources/lwr.rb
@@ -0,0 +1 @@
+# a LWR #
\ No newline at end of file
diff --git a/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb b/spec/data/cb_version_cookbooks/tatft/templates/default/configuration.erb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/checksum/random.txt b/spec/data/checksum/random.txt
new file mode 100644
index 0000000..9570fff
--- /dev/null
+++ b/spec/data/checksum/random.txt
@@ -0,0 +1 @@
+e4010abcac515ef3b78e92ee1f848a0d3bc3a526fcb826e7b4d39a6d516aa0487085c9b1be35e8d909617b250dca36dd4a55f01b7cdd310826bfd748cb27e0e43dd52b22968383c8086b06ee2d16e13574f98c058ce2bc3475b92ecf9c16e504022d60b132643986a8e7908d067526e20b4bafe1eb75349f27a4d3de02b077e76a2f59b73c14413f11e7208ae0bf6a408d51a97d490530e23476960ab8780ad86349947d82f1c9e57c85f86d71f80a6709b58be5f993a6a6df80c5a0857627d4a01e71484f6a6e983985089c00fe538e947230813c3a3e19baf6dae6db7082d07392a239ec1be385646356db3e3d76571488a6c72f0b [...]
\ No newline at end of file
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-600hhz-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-6m8zdk-0
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ahd2gq-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-api8ux-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-b0r1m1-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-bfygsi-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-el14l6-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ivrl3y-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-kkbs85-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ory1ux-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-pgsq76-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ra8uim-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t7k1g-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-t8g0sv-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-ufy6g3-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-x2d6j9-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0 b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0
new file mode 100644
index 0000000..8183658
--- /dev/null
+++ b/spec/data/checksum_cache/chef-file--tmp-chef-rendered-template20100929-10863-xi0l6h-0
@@ -0,0 +1 @@
+checksum data here
diff --git a/spec/data/config.rb b/spec/data/config.rb
new file mode 100644
index 0000000..0b3340c
--- /dev/null
+++ b/spec/data/config.rb
@@ -0,0 +1,6 @@
+#
+# Sample Chef Config File
+# 
+
+cookbook_path "/etc/chef/cookbook", "/etc/chef/site-cookbook"
+
diff --git a/spec/data/cookbooks/angrybash/recipes/default.rb b/spec/data/cookbooks/angrybash/recipes/default.rb
new file mode 100644
index 0000000..458a291
--- /dev/null
+++ b/spec/data/cookbooks/angrybash/recipes/default.rb
@@ -0,0 +1,8 @@
+bash "go off the rails" do
+  code <<-END
+    for i in localhost 127.0.0.1 #{Socket.gethostname()}
+    do
+      echo "grant all on *.* to root@'$i' identified by 'a_password'; flush privileges;" | mysql -u root -h 127.0.0.1
+    done
+   END
+end
diff --git a/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl b/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl
new file mode 100644
index 0000000..6cce622
--- /dev/null
+++ b/spec/data/cookbooks/apache2/files/default/apache2_module_conf_generate.pl
@@ -0,0 +1,2 @@
+# apache2_module_conf_generate.pl
+# this is just here for show.
diff --git a/spec/data/cookbooks/apache2/recipes/default.rb b/spec/data/cookbooks/apache2/recipes/default.rb
new file mode 100644
index 0000000..c2fa53b
--- /dev/null
+++ b/spec/data/cookbooks/apache2/recipes/default.rb
@@ -0,0 +1,3 @@
+#
+# Nothing ot see here
+# 
\ No newline at end of file
diff --git a/spec/data/cookbooks/borken/recipes/default.rb b/spec/data/cookbooks/borken/recipes/default.rb
new file mode 100644
index 0000000..caf40b3
--- /dev/null
+++ b/spec/data/cookbooks/borken/recipes/default.rb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))
\ No newline at end of file
diff --git a/spec/data/cookbooks/borken/templates/default/borken.erb b/spec/data/cookbooks/borken/templates/default/borken.erb
new file mode 100644
index 0000000..cbb32c1
--- /dev/null
+++ b/spec/data/cookbooks/borken/templates/default/borken.erb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+<%= (*&)(*^^^^*******++_+_--- }}}}]]]end)%>
\ No newline at end of file
diff --git a/spec/data/cookbooks/chefignore b/spec/data/cookbooks/chefignore
new file mode 100644
index 0000000..84b4f1e
--- /dev/null
+++ b/spec/data/cookbooks/chefignore
@@ -0,0 +1,8 @@
+#
+# The ignore file allows you to skip files in cookbooks with the same name that appear
+# later in the search path.
+#
+
+recipes/ignoreme.rb
+  # comments can be indented
+ignored
diff --git a/spec/data/cookbooks/ignorken/recipes/default.rb b/spec/data/cookbooks/ignorken/recipes/default.rb
new file mode 100644
index 0000000..0f3b3a5
--- /dev/null
+++ b/spec/data/cookbooks/ignorken/recipes/default.rb
@@ -0,0 +1 @@
+# This is fine!
\ No newline at end of file
diff --git a/spec/data/cookbooks/ignorken/recipes/ignoreme.rb b/spec/data/cookbooks/ignorken/recipes/ignoreme.rb
new file mode 100644
index 0000000..caf40b3
--- /dev/null
+++ b/spec/data/cookbooks/ignorken/recipes/ignoreme.rb
@@ -0,0 +1,2 @@
+a cat walked on the keyboard one day...
+(*&(*&(*&(*&(*^%$%^%#^^&(*)(*{}}}}}}}}+++++===))))))
\ No newline at end of file
diff --git a/spec/data/cookbooks/java/files/default/java.response b/spec/data/cookbooks/java/files/default/java.response
new file mode 100644
index 0000000..eb4aa3c
--- /dev/null
+++ b/spec/data/cookbooks/java/files/default/java.response
@@ -0,0 +1,2 @@
+# Hi, I'm pretending to be the preseed file for installing the Sun JDK on debian
+# or Ubuntu
\ No newline at end of file
diff --git a/spec/data/cookbooks/openldap/attributes/default.rb b/spec/data/cookbooks/openldap/attributes/default.rb
new file mode 100644
index 0000000..d0756f1
--- /dev/null
+++ b/spec/data/cookbooks/openldap/attributes/default.rb
@@ -0,0 +1,16 @@
+chef_env ||= nil
+case chef_env
+when "prod"
+  default[:ldap_server] = "ops1prod"
+  default[:ldap_basedn] = "dc=hjksolutions,dc=com"
+  default[:ldap_replication_password] = "yes"
+when "corp"
+  default[:ldap_server] = "ops1prod"
+  default[:ldap_basedn] = "dc=hjksolutions,dc=com"
+  default[:ldap_replication_password] = "yougotit"
+else
+
+  default[:ldap_server] = "ops1prod"
+  default[:ldap_basedn] = "dc=hjksolutions,dc=com"
+  default[:ldap_replication_password] = "forsure" 
+end
diff --git a/spec/data/cookbooks/openldap/attributes/smokey.rb b/spec/data/cookbooks/openldap/attributes/smokey.rb
new file mode 100644
index 0000000..4489c6a
--- /dev/null
+++ b/spec/data/cookbooks/openldap/attributes/smokey.rb
@@ -0,0 +1 @@
+default[:smokey] = "robinson"
diff --git a/spec/data/cookbooks/openldap/definitions/client.rb b/spec/data/cookbooks/openldap/definitions/client.rb
new file mode 100644
index 0000000..ac81831
--- /dev/null
+++ b/spec/data/cookbooks/openldap/definitions/client.rb
@@ -0,0 +1,5 @@
+define :openldap_client, :mothra => "a big monster" do
+  cat "#{params[:name]}" do
+     pretty_kitty true
+  end
+end
diff --git a/spec/data/cookbooks/openldap/definitions/server.rb b/spec/data/cookbooks/openldap/definitions/server.rb
new file mode 100644
index 0000000..2df437a
--- /dev/null
+++ b/spec/data/cookbooks/openldap/definitions/server.rb
@@ -0,0 +1,5 @@
+define :openldap_server, :mothra => "a big monster" do
+  cat "#{params[:name]}" do
+     pretty_kitty true
+  end
+end
diff --git a/spec/data/cookbooks/openldap/files/default/.dotfile b/spec/data/cookbooks/openldap/files/default/.dotfile
new file mode 100644
index 0000000..35ae928
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/.dotfile
@@ -0,0 +1 @@
+I am here to test .dotfiles work in file directories.
diff --git a/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa b/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa
new file mode 100644
index 0000000..20a3ea4
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/.ssh/id_rsa
@@ -0,0 +1 @@
+FAKE KEY
\ No newline at end of file
diff --git a/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir b/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir
new file mode 100644
index 0000000..f44a956
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/remotedir/.a_dotdir/.a_dotfile_in_a_dotdir
@@ -0,0 +1 @@
+this is a dotfile in a dotdir
diff --git a/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt b/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt
new file mode 100644
index 0000000..7632730
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file1.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remote_dir_file1.txt
\ No newline at end of file
diff --git a/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt b/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt
new file mode 100644
index 0000000..fab0433
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/remotedir/remote_dir_file2.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remote_dir_file2.txt
\ No newline at end of file
diff --git a/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile b/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile
new file mode 100644
index 0000000..9a2b2a4
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/.a_dotfile
@@ -0,0 +1 @@
+this is a file with a name beginning with a . dot
diff --git a/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt b/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt
new file mode 100644
index 0000000..611294c
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file1.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remotesubdir/remote_dir_file1.txt
\ No newline at end of file
diff --git a/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt b/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt
new file mode 100644
index 0000000..e039654
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/remotedir/remotesubdir/remote_subdir_file2.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/remotesubdir/remote_dir_file2.txt
\ No newline at end of file
diff --git a/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt b/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt
new file mode 100644
index 0000000..bc47369
--- /dev/null
+++ b/spec/data/cookbooks/openldap/files/default/remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt
@@ -0,0 +1,3 @@
+# remote directory
+# file specificity: default
+# relpath: remotedir/subdir_with_no_file_just_a_subsubdir/the_subsubdir/some_file.txt
diff --git a/spec/data/cookbooks/openldap/metadata.rb b/spec/data/cookbooks/openldap/metadata.rb
new file mode 100644
index 0000000..ab0dfac
--- /dev/null
+++ b/spec/data/cookbooks/openldap/metadata.rb
@@ -0,0 +1,8 @@
+name              "openldap"
+maintainer        "Opscode, Inc."
+maintainer_email  "cookbooks at opscode.com"
+license           "Apache 2.0"
+description       "Installs and configures all aspects of openldap using Debian style symlinks with helper definitions"
+long_description  "The long description for the openldap cookbook from metadata.rb"
+version           "8.9.10"
+recipe            "openldap", "Main Open LDAP configuration"
diff --git a/spec/data/cookbooks/openldap/recipes/default.rb b/spec/data/cookbooks/openldap/recipes/default.rb
new file mode 100644
index 0000000..0ac8a9b
--- /dev/null
+++ b/spec/data/cookbooks/openldap/recipes/default.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+  pretty_kitty true
+end
diff --git a/spec/data/cookbooks/openldap/recipes/gigantor.rb b/spec/data/cookbooks/openldap/recipes/gigantor.rb
new file mode 100644
index 0000000..b450eca
--- /dev/null
+++ b/spec/data/cookbooks/openldap/recipes/gigantor.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+  pretty_kitty false
+end
diff --git a/spec/data/cookbooks/openldap/recipes/one.rb b/spec/data/cookbooks/openldap/recipes/one.rb
new file mode 100644
index 0000000..d2d3cfd
--- /dev/null
+++ b/spec/data/cookbooks/openldap/recipes/one.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com-default"
+
+##
+# Nodes can set arbitrary arguments
+##
+sunshine "in"
+something "else"
+
+##
+# Nodes should have recipes
+##
+recipes "operations-master", "operations-monitoring"
diff --git a/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb b/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb
new file mode 100644
index 0000000..727db12
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/all_windows_line_endings.erb
@@ -0,0 +1,4 @@
+Template rendering libraries
+should support
+different line endings
+
diff --git a/spec/data/cookbooks/openldap/templates/default/helper_test.erb b/spec/data/cookbooks/openldap/templates/default/helper_test.erb
new file mode 100644
index 0000000..92e6fe0
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/helper_test.erb
@@ -0,0 +1 @@
+<%= helper_method %>
diff --git a/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb b/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb
new file mode 100644
index 0000000..f94b6b5
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/helpers_via_partial_test.erb
@@ -0,0 +1 @@
+<%= render("helper_test.erb").strip %>
diff --git a/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb b/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb
new file mode 100644
index 0000000..e83c03b
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/no_windows_line_endings.erb
@@ -0,0 +1,4 @@
+Template rendering libraries
+should support
+different line endings
+
diff --git a/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb b/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb
new file mode 100644
index 0000000..af82f1d
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/openldap_stuff.conf.erb
@@ -0,0 +1 @@
+slappiness is <%= node[:slappiness] -%>
\ No newline at end of file
diff --git a/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb b/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb
new file mode 100644
index 0000000..e0041c9
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/openldap_variable_stuff.conf.erb
@@ -0,0 +1 @@
+super secret is <%= @secret -%>
diff --git a/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb b/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb
new file mode 100644
index 0000000..fa25999
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/some_windows_line_endings.erb
@@ -0,0 +1,4 @@
+Template rendering libraries
+should support
+different line endings
+
diff --git a/spec/data/cookbooks/openldap/templates/default/test.erb b/spec/data/cookbooks/openldap/templates/default/test.erb
new file mode 100644
index 0000000..f39fa7d
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/test.erb
@@ -0,0 +1 @@
+We could be diving for pearls!
diff --git a/spec/data/cookbooks/preseed/files/default/preseed-file.seed b/spec/data/cookbooks/preseed/files/default/preseed-file.seed
new file mode 100644
index 0000000..164da34
--- /dev/null
+++ b/spec/data/cookbooks/preseed/files/default/preseed-file.seed
@@ -0,0 +1 @@
+chef-integration-test chef-integration-test/sample-var string "hello world"
diff --git a/spec/data/cookbooks/preseed/files/default/preseed-template.seed b/spec/data/cookbooks/preseed/files/default/preseed-template.seed
new file mode 100644
index 0000000..011bc40
--- /dev/null
+++ b/spec/data/cookbooks/preseed/files/default/preseed-template.seed
@@ -0,0 +1,4 @@
+# This file should never be executed by the preseeding tests
+# This is here to verify that templates are preferred over cookbook_files when
+# preseeding packages.
+chef-integration-test chef-integration-test/sample-var string "WRONG-cookbook file used instead of template!"
diff --git a/spec/data/cookbooks/preseed/templates/default/preseed-template.seed b/spec/data/cookbooks/preseed/templates/default/preseed-template.seed
new file mode 100644
index 0000000..6229ac8
--- /dev/null
+++ b/spec/data/cookbooks/preseed/templates/default/preseed-template.seed
@@ -0,0 +1 @@
+chef-integration-test chef-integration-test/sample-var string "<%= node[:preseed_value] -%>"
diff --git a/spec/data/definitions/test.rb b/spec/data/definitions/test.rb
new file mode 100644
index 0000000..b0d0eff
--- /dev/null
+++ b/spec/data/definitions/test.rb
@@ -0,0 +1,5 @@
+define :rico_suave, :rich => "smooth" do
+  zen_master "test" do
+    something "#{params[:rich]}"
+  end
+end
\ No newline at end of file
diff --git a/spec/data/environment-config.rb b/spec/data/environment-config.rb
new file mode 100644
index 0000000..a157ecf
--- /dev/null
+++ b/spec/data/environment-config.rb
@@ -0,0 +1,5 @@
+#
+# Sample Chef Config File
+#
+
+environment "production"
\ No newline at end of file
diff --git a/spec/data/file-providers-method-snapshot-chef-11-4.json b/spec/data/file-providers-method-snapshot-chef-11-4.json
new file mode 100644
index 0000000..f6695aa
--- /dev/null
+++ b/spec/data/file-providers-method-snapshot-chef-11-4.json
@@ -0,0 +1,127 @@
+{
+  "Chef::Provider::CookbookFile": [
+    "action_create",
+    "file_cache_location",
+    "resource_cookbook",
+    "backup_new_resource",
+    "content_stale?",
+    "diff_current_from_content",
+    "is_binary?",
+    "diff_current",
+    "setup_acl",
+    "compare_content",
+    "set_content",
+    "update_new_file_state",
+    "set_all_access_controls",
+    "action_create_if_missing",
+    "action_delete",
+    "action_touch",
+    "backup",
+    "deploy_tempfile",
+    "shell_out",
+    "shell_out!",
+    "run_command_compatible_options",
+    "checksum",
+    "access_controls",
+    "enforce_ownership_and_permissions"
+  ],
+  "Chef::Provider::RemoteFile": [
+    "action_create",
+    "current_resource_matches_target_checksum?",
+    "matches_current_checksum?",
+    "backup_new_resource",
+    "source_file",
+    "http_client_opts",
+    "diff_current_from_content",
+    "is_binary?",
+    "diff_current",
+    "setup_acl",
+    "compare_content",
+    "set_content",
+    "update_new_file_state",
+    "set_all_access_controls",
+    "action_create_if_missing",
+    "action_delete",
+    "action_touch",
+    "backup",
+    "deploy_tempfile",
+    "shell_out",
+    "shell_out!",
+    "run_command_compatible_options",
+    "checksum",
+    "access_controls",
+    "enforce_ownership_and_permissions"
+  ],
+  "Chef::Provider::Template": [
+    "action_create",
+    "template_finder",
+    "template_location",
+    "resource_cookbook",
+    "rendered",
+    "content_matches?",
+    "render_template",
+    "diff_current_from_content",
+    "is_binary?",
+    "diff_current",
+    "setup_acl",
+    "compare_content",
+    "set_content",
+    "update_new_file_state",
+    "set_all_access_controls",
+    "action_create_if_missing",
+    "action_delete",
+    "action_touch",
+    "backup",
+    "deploy_tempfile",
+    "shell_out",
+    "shell_out!",
+    "run_command_compatible_options",
+    "checksum",
+    "access_controls",
+    "enforce_ownership_and_permissions"
+  ],
+  "Chef::Provider::Directory": [
+    "action_create",
+    "action_delete",
+    "diff_current_from_content",
+    "is_binary?",
+    "diff_current",
+    "setup_acl",
+    "compare_content",
+    "set_content",
+    "update_new_file_state",
+    "set_all_access_controls",
+    "action_create_if_missing",
+    "action_touch",
+    "backup",
+    "deploy_tempfile",
+    "shell_out",
+    "shell_out!",
+    "run_command_compatible_options",
+    "checksum",
+    "access_controls",
+    "enforce_ownership_and_permissions"
+  ],
+  "Chef::Provider::File": [
+    "diff_current_from_content",
+    "is_binary?",
+    "diff_current",
+    "setup_acl",
+    "compare_content",
+    "set_content",
+    "update_new_file_state",
+    "action_create",
+    "set_all_access_controls",
+    "action_create_if_missing",
+    "action_delete",
+    "action_touch",
+    "backup",
+    "deploy_tempfile",
+    "shell_out",
+    "shell_out!",
+    "run_command_compatible_options",
+    "checksum",
+    "access_controls",
+    "enforce_ownership_and_permissions"
+  ]
+}
diff --git a/spec/data/fileedit/blank b/spec/data/fileedit/blank
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/fileedit/hosts b/spec/data/fileedit/hosts
new file mode 100644
index 0000000..6fbdc0f
--- /dev/null
+++ b/spec/data/fileedit/hosts
@@ -0,0 +1,4 @@
+127.0.0.1       localhost
+255.255.255.255 broadcasthost
+::1             localhost
+fe80::1%lo0     localhost
diff --git a/spec/data/gems/chef-integration-test-0.1.0.gem b/spec/data/gems/chef-integration-test-0.1.0.gem
new file mode 100644
index 0000000..bcf1c77
Binary files /dev/null and b/spec/data/gems/chef-integration-test-0.1.0.gem differ
diff --git a/spec/data/git_bundles/example-repo.gitbundle b/spec/data/git_bundles/example-repo.gitbundle
new file mode 100644
index 0000000..de08296
Binary files /dev/null and b/spec/data/git_bundles/example-repo.gitbundle differ
diff --git a/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle b/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle
new file mode 100644
index 0000000..24e36f7
Binary files /dev/null and b/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle differ
diff --git a/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle b/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle
new file mode 100644
index 0000000..0a96fbb
Binary files /dev/null and b/spec/data/git_bundles/sinatra-test-app-with-symlinks.gitbundle differ
diff --git a/spec/data/git_bundles/sinatra-test-app.gitbundle b/spec/data/git_bundles/sinatra-test-app.gitbundle
new file mode 100644
index 0000000..43c54ea
Binary files /dev/null and b/spec/data/git_bundles/sinatra-test-app.gitbundle differ
diff --git a/spec/data/kitchen/chefignore b/spec/data/kitchen/chefignore
new file mode 100644
index 0000000..a90dc15
--- /dev/null
+++ b/spec/data/kitchen/chefignore
@@ -0,0 +1,6 @@
+#
+# The ignore file allows you to skip files in cookbooks with the same name that appear
+# later in the search path.
+#
+
+recipes/ignoreme\.rb
diff --git a/spec/data/kitchen/openldap/attributes/default.rb b/spec/data/kitchen/openldap/attributes/default.rb
new file mode 100644
index 0000000..d208959
--- /dev/null
+++ b/spec/data/kitchen/openldap/attributes/default.rb
@@ -0,0 +1,3 @@
+#
+# Nothing to see here, move along
+#
diff --git a/spec/data/kitchen/openldap/attributes/robinson.rb b/spec/data/kitchen/openldap/attributes/robinson.rb
new file mode 100644
index 0000000..9d6b44d
--- /dev/null
+++ b/spec/data/kitchen/openldap/attributes/robinson.rb
@@ -0,0 +1,3 @@
+#
+# Smokey lives here
+#
\ No newline at end of file
diff --git a/spec/data/kitchen/openldap/definitions/client.rb b/spec/data/kitchen/openldap/definitions/client.rb
new file mode 100644
index 0000000..d4c2263
--- /dev/null
+++ b/spec/data/kitchen/openldap/definitions/client.rb
@@ -0,0 +1,3 @@
+#
+# A sad client
+#
diff --git a/spec/data/kitchen/openldap/definitions/drewbarrymore.rb b/spec/data/kitchen/openldap/definitions/drewbarrymore.rb
new file mode 100644
index 0000000..510f0c3
--- /dev/null
+++ b/spec/data/kitchen/openldap/definitions/drewbarrymore.rb
@@ -0,0 +1,3 @@
+#
+# Was in people magazine this month... 
+#
\ No newline at end of file
diff --git a/spec/data/kitchen/openldap/recipes/gigantor.rb b/spec/data/kitchen/openldap/recipes/gigantor.rb
new file mode 100644
index 0000000..70a4196
--- /dev/null
+++ b/spec/data/kitchen/openldap/recipes/gigantor.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+  pretty_kitty true
+end
\ No newline at end of file
diff --git a/spec/data/kitchen/openldap/recipes/ignoreme.rb b/spec/data/kitchen/openldap/recipes/ignoreme.rb
new file mode 100644
index 0000000..1509598
--- /dev/null
+++ b/spec/data/kitchen/openldap/recipes/ignoreme.rb
@@ -0,0 +1,3 @@
+#
+# this file will never be seen
+#
\ No newline at end of file
diff --git a/spec/data/kitchen/openldap/recipes/woot.rb b/spec/data/kitchen/openldap/recipes/woot.rb
new file mode 100644
index 0000000..44893da
--- /dev/null
+++ b/spec/data/kitchen/openldap/recipes/woot.rb
@@ -0,0 +1,3 @@
+#
+# Such a funny word..
+#
diff --git a/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb b/spec/data/knife-home/.chef/plugins/knife/example_home_subcommand.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb b/spec/data/knife-site-subcommands/plugins/knife/example_subcommand.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/knife_subcommand/test_explicit_category.rb b/spec/data/knife_subcommand/test_explicit_category.rb
new file mode 100644
index 0000000..96d5069
--- /dev/null
+++ b/spec/data/knife_subcommand/test_explicit_category.rb
@@ -0,0 +1,7 @@
+module KnifeSpecs
+  class TestExplicitCategory < Chef::Knife
+    # i.e., the cookbook site commands should be in the cookbook site
+    # category instead of cookbook (which is what would be assumed)
+    category "cookbook site"
+  end
+end
\ No newline at end of file
diff --git a/spec/data/knife_subcommand/test_name_mapping.rb b/spec/data/knife_subcommand/test_name_mapping.rb
new file mode 100644
index 0000000..25c49b0
--- /dev/null
+++ b/spec/data/knife_subcommand/test_name_mapping.rb
@@ -0,0 +1,4 @@
+module KnifeSpecs
+  class TestNameMapping < Chef::Knife
+  end  
+end
diff --git a/spec/data/knife_subcommand/test_yourself.rb b/spec/data/knife_subcommand/test_yourself.rb
new file mode 100644
index 0000000..f18f565
--- /dev/null
+++ b/spec/data/knife_subcommand/test_yourself.rb
@@ -0,0 +1,21 @@
+module KnifeSpecs
+  class TestYourself < Chef::Knife
+
+    class << self
+      attr_reader :test_deps_loaded
+    end
+
+    deps do
+      @test_deps_loaded = true
+    end
+
+    option :scro, :short => '-s SCRO', :long => '--scro SCRO', :description => 'a configurable setting'
+
+    attr_reader :ran
+
+    def run
+      @ran = true
+      self # return self so tests can poke at me
+    end
+  end
+end
diff --git a/spec/data/lwrp/providers/buck_passer.rb b/spec/data/lwrp/providers/buck_passer.rb
new file mode 100644
index 0000000..1f83e50
--- /dev/null
+++ b/spec/data/lwrp/providers/buck_passer.rb
@@ -0,0 +1,3 @@
+action :buck_stops_here do
+  log "This should be overwritten by ../lwrp_override/buck_passer.rb"
+end
diff --git a/spec/data/lwrp/providers/buck_passer_2.rb b/spec/data/lwrp/providers/buck_passer_2.rb
new file mode 100644
index 0000000..d34da3c
--- /dev/null
+++ b/spec/data/lwrp/providers/buck_passer_2.rb
@@ -0,0 +1,10 @@
+action :pass_buck do
+  lwrp_bar :prepared_eyes do
+    action :prepare_eyes
+    provider :lwrp_paint_drying_watcher
+  end
+  lwrp_bar :dried_paint_watched do
+    action :watch_paint_dry
+    provider :lwrp_paint_drying_watcher
+  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
new file mode 100644
index 0000000..f5841fb
--- /dev/null
+++ b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
@@ -0,0 +1,16 @@
+# This action tests that embedded Resources have access to the enclosing Provider's
+# lexical scope (as demonstrated by the call to new_resource) and that all parameters
+# are passed properly (as demonstrated by the call to generate_new_name).
+attr_reader :enclosed_resource
+
+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
+  end
+end
+
+def generate_new_name(str, &block)
+  "#{str}, #{block.call}"
+end
diff --git a/spec/data/lwrp/providers/inline_compiler.rb b/spec/data/lwrp/providers/inline_compiler.rb
new file mode 100644
index 0000000..2535276
--- /dev/null
+++ b/spec/data/lwrp/providers/inline_compiler.rb
@@ -0,0 +1,26 @@
+
+use_inline_resources
+
+action :test do
+
+  ruby_block "interior-ruby-block-1" do
+    block do
+      # doesn't need to do anything
+    end
+    notifies :run, "ruby_block[interior-ruby-block-2]", :immediately
+  end
+
+  ruby_block "interior-ruby-block-2" do
+    block do
+      $interior_ruby_block_2 = "executed"
+    end
+    action :nothing
+  end
+end
+
+action :no_updates do
+  ruby_block "no-action" do
+    block {}
+    action :nothing
+  end
+end
diff --git a/spec/data/lwrp/providers/monkey_name_printer.rb b/spec/data/lwrp/providers/monkey_name_printer.rb
new file mode 100644
index 0000000..97ca66c
--- /dev/null
+++ b/spec/data/lwrp/providers/monkey_name_printer.rb
@@ -0,0 +1,5 @@
+attr_reader :monkey_name
+
+action :twiddle_thumbs do
+  @monkey_name = "my monkey's name is '#{new_resource.monkey}'"
+end
diff --git a/spec/data/lwrp/providers/paint_drying_watcher.rb b/spec/data/lwrp/providers/paint_drying_watcher.rb
new file mode 100644
index 0000000..04b4732
--- /dev/null
+++ b/spec/data/lwrp/providers/paint_drying_watcher.rb
@@ -0,0 +1,7 @@
+action :prepare_eyes do
+  "Prepared"
+end
+
+action :watch_paint_dry do
+  "This isn't very interesting"
+end
diff --git a/spec/data/lwrp/providers/thumb_twiddler.rb b/spec/data/lwrp/providers/thumb_twiddler.rb
new file mode 100644
index 0000000..7f01461
--- /dev/null
+++ b/spec/data/lwrp/providers/thumb_twiddler.rb
@@ -0,0 +1,7 @@
+action :prepare_thumbs do
+  "Prepared"
+end
+
+action :twiddle_thumbs do
+  "Twiddle twiddle"
+end
diff --git a/spec/data/lwrp/resources/bar.rb b/spec/data/lwrp/resources/bar.rb
new file mode 100644
index 0000000..bded6ee
--- /dev/null
+++ b/spec/data/lwrp/resources/bar.rb
@@ -0,0 +1 @@
+actions :pass_buck, :prepare_eyes, :watch_paint_dry
diff --git a/spec/data/lwrp/resources/foo.rb b/spec/data/lwrp/resources/foo.rb
new file mode 100644
index 0000000..c881c80
--- /dev/null
+++ b/spec/data/lwrp/resources/foo.rb
@@ -0,0 +1,3 @@
+actions :never_execute
+
+attribute :ever, :kind_of => String
diff --git a/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb b/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
new file mode 100644
index 0000000..dece7c4
--- /dev/null
+++ b/spec/data/lwrp/resources_with_default_attributes/nodeattr.rb
@@ -0,0 +1 @@
+attribute :penguin, :kind_of => String, :default => node[:penguin_name]
diff --git a/spec/data/lwrp_const_scoping/resources/conflict.rb b/spec/data/lwrp_const_scoping/resources/conflict.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/lwrp_override/providers/buck_passer.rb b/spec/data/lwrp_override/providers/buck_passer.rb
new file mode 100644
index 0000000..75917a5
--- /dev/null
+++ b/spec/data/lwrp_override/providers/buck_passer.rb
@@ -0,0 +1,10 @@
+action :pass_buck do
+  lwrp_foo :prepared_thumbs do
+    action :prepare_thumbs
+    provider :lwrp_thumb_twiddler
+  end
+  lwrp_foo :twiddled_thumbs do
+    action :twiddle_thumbs
+    provider :lwrp_thumb_twiddler
+  end
+end
\ No newline at end of file
diff --git a/spec/data/lwrp_override/resources/foo.rb b/spec/data/lwrp_override/resources/foo.rb
new file mode 100644
index 0000000..0ee83f0
--- /dev/null
+++ b/spec/data/lwrp_override/resources/foo.rb
@@ -0,0 +1,4 @@
+actions :prepare_thumbs, :twiddle_thumbs
+default_action :pass_buck
+
+attribute :monkey, :kind_of => String
diff --git a/spec/data/metadata/quick_start/metadata.rb b/spec/data/metadata/quick_start/metadata.rb
new file mode 100644
index 0000000..e74eedb
--- /dev/null
+++ b/spec/data/metadata/quick_start/metadata.rb
@@ -0,0 +1,19 @@
+maintainer        "Opscode, Inc."
+maintainer_email  "cookbooks at opscode.com"
+license           "Apache 2.0"
+description       "Example cookbook for quick_start wiki document"
+version           "0.7"
+
+%w{
+  redhat fedora centos
+  ubuntu debian
+  macosx freebsd openbsd
+  solaris
+}.each do |os|
+  supports os
+end
+
+attribute "quick_start/deep_thought",
+  :display_name => "Quick Start Deep Thought",
+  :description => "A deep thought",
+  :default => "If a tree falls in the forest..."
diff --git a/spec/data/nodes/default.rb b/spec/data/nodes/default.rb
new file mode 100644
index 0000000..1d6291f
--- /dev/null
+++ b/spec/data/nodes/default.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com-default"
+
+##
+# Nodes can set arbitrary arguments
+##
+default[:sunshine] = "in"
+default[:something] = "else"
+
+##
+# Nodes should have recipes
+##
+run_list "operations-master", "operations-monitoring"
diff --git a/spec/data/nodes/test.example.com.rb b/spec/data/nodes/test.example.com.rb
new file mode 100644
index 0000000..b30e848
--- /dev/null
+++ b/spec/data/nodes/test.example.com.rb
@@ -0,0 +1,17 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com"
+
+##
+# Nodes can set arbitrary arguments
+##
+normal[:sunshine] = "in"
+normal[:something] = "else"
+
+##
+# Nodes should have recipes
+##
+run_list "operations-master", "operations-monitoring"
+
+chef_environment "dev"
diff --git a/spec/data/nodes/test.rb b/spec/data/nodes/test.rb
new file mode 100644
index 0000000..e130113
--- /dev/null
+++ b/spec/data/nodes/test.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com-short"
+
+##
+# Nodes can set arbitrary arguments
+##
+default[:sunshine] = "in"
+default[:something] = "else"
+
+##
+# Nodes should have recipes
+##
+run_list "operations-master", "operations-monitoring"
diff --git a/spec/data/null_config.rb b/spec/data/null_config.rb
new file mode 100644
index 0000000..8865745
--- /dev/null
+++ b/spec/data/null_config.rb
@@ -0,0 +1 @@
+$__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole"
diff --git a/spec/data/object_loader/environments/test.json b/spec/data/object_loader/environments/test.json
new file mode 100644
index 0000000..cb4c790
--- /dev/null
+++ b/spec/data/object_loader/environments/test.json
@@ -0,0 +1,5 @@
+{
+  "name": "test",
+  "description": "prod",
+  "run_list": []
+}
diff --git a/spec/data/object_loader/environments/test.rb b/spec/data/object_loader/environments/test.rb
new file mode 100644
index 0000000..8bf4ee5
--- /dev/null
+++ b/spec/data/object_loader/environments/test.rb
@@ -0,0 +1,2 @@
+name "test"
+description "prod"
diff --git a/spec/data/object_loader/environments/test_json_class.json b/spec/data/object_loader/environments/test_json_class.json
new file mode 100644
index 0000000..2834519
--- /dev/null
+++ b/spec/data/object_loader/environments/test_json_class.json
@@ -0,0 +1,6 @@
+{
+  "name": "test",
+  "json_class": "Chef::Environment",
+  "description": "prod",
+  "run_list": []
+}
diff --git a/spec/data/object_loader/nodes/test.json b/spec/data/object_loader/nodes/test.json
new file mode 100644
index 0000000..49f297d
--- /dev/null
+++ b/spec/data/object_loader/nodes/test.json
@@ -0,0 +1,5 @@
+{
+  "name": "test",
+  "environment": "prod",
+  "run_list": []
+}
diff --git a/spec/data/object_loader/nodes/test.rb b/spec/data/object_loader/nodes/test.rb
new file mode 100644
index 0000000..9629539
--- /dev/null
+++ b/spec/data/object_loader/nodes/test.rb
@@ -0,0 +1,2 @@
+name "test"
+environment "prod"
diff --git a/spec/data/object_loader/nodes/test_json_class.json b/spec/data/object_loader/nodes/test_json_class.json
new file mode 100644
index 0000000..f485ae8
--- /dev/null
+++ b/spec/data/object_loader/nodes/test_json_class.json
@@ -0,0 +1,6 @@
+{
+  "name": "test",
+  "json_class": "Chef::Node",
+  "environment": "prod",
+  "run_list": []
+}
diff --git a/spec/data/object_loader/roles/test.json b/spec/data/object_loader/roles/test.json
new file mode 100644
index 0000000..cb4c790
--- /dev/null
+++ b/spec/data/object_loader/roles/test.json
@@ -0,0 +1,5 @@
+{
+  "name": "test",
+  "description": "prod",
+  "run_list": []
+}
diff --git a/spec/data/object_loader/roles/test.rb b/spec/data/object_loader/roles/test.rb
new file mode 100644
index 0000000..8bf4ee5
--- /dev/null
+++ b/spec/data/object_loader/roles/test.rb
@@ -0,0 +1,2 @@
+name "test"
+description "prod"
diff --git a/spec/data/object_loader/roles/test_json_class.json b/spec/data/object_loader/roles/test_json_class.json
new file mode 100644
index 0000000..ce922f5
--- /dev/null
+++ b/spec/data/object_loader/roles/test_json_class.json
@@ -0,0 +1,6 @@
+{
+  "name": "test",
+  "json_class": "Chef::Role",
+  "description": "prod",
+  "run_list": []
+}
diff --git a/spec/data/old_home_dir/my-dot-emacs b/spec/data/old_home_dir/my-dot-emacs
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/old_home_dir/my-dot-vim b/spec/data/old_home_dir/my-dot-vim
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/partial_one.erb b/spec/data/partial_one.erb
new file mode 100644
index 0000000..9fd79a0
--- /dev/null
+++ b/spec/data/partial_one.erb
@@ -0,0 +1 @@
+partial one <%= render('test.erb', :cookbook => 'openldap').strip %> calling home
diff --git a/spec/data/recipes/test.rb b/spec/data/recipes/test.rb
new file mode 100644
index 0000000..c33d714
--- /dev/null
+++ b/spec/data/recipes/test.rb
@@ -0,0 +1,7 @@
+
+file "/etc/nsswitch.conf" do 
+  action :create
+  owner  "root"
+  group  "root" 
+  mode   0644
+end
diff --git a/spec/data/remote_directory_data/remote_dir_file.txt b/spec/data/remote_directory_data/remote_dir_file.txt
new file mode 100644
index 0000000..17aa3a6
--- /dev/null
+++ b/spec/data/remote_directory_data/remote_dir_file.txt
@@ -0,0 +1 @@
+I'm a file inside a the root remote_directory source dir.
\ No newline at end of file
diff --git a/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt b/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt
new file mode 100644
index 0000000..a73728a
--- /dev/null
+++ b/spec/data/remote_directory_data/remote_subdirectory/remote_subdir_file.txt
@@ -0,0 +1 @@
+I'm a file in a subdirectory inside a remote_directory source
\ No newline at end of file
diff --git a/spec/data/remote_file/nyan_cat.png b/spec/data/remote_file/nyan_cat.png
new file mode 100644
index 0000000..14cd6ac
Binary files /dev/null and b/spec/data/remote_file/nyan_cat.png differ
diff --git a/spec/data/remote_file/nyan_cat.png.gz b/spec/data/remote_file/nyan_cat.png.gz
new file mode 100644
index 0000000..efa9d44
Binary files /dev/null and b/spec/data/remote_file/nyan_cat.png.gz differ
diff --git a/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb b/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb
new file mode 100644
index 0000000..ef0967a
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep1/attributes/default.rb
@@ -0,0 +1,4 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "circular-dep1::default"
+
+
diff --git a/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb b/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb
new file mode 100644
index 0000000..fa1770b
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep1/definitions/circular_dep1_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep1-definition')
diff --git a/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb b/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb
new file mode 100644
index 0000000..b20b648
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep1/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("circular-dep1")
+
diff --git a/spec/data/run_context/cookbooks/circular-dep1/metadata.rb b/spec/data/run_context/cookbooks/circular-dep1/metadata.rb
new file mode 100644
index 0000000..e990004
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep1/metadata.rb
@@ -0,0 +1,2 @@
+name "circular-dep1"
+depends "circular-dep2"
diff --git a/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb b/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb
new file mode 100644
index 0000000..ee66286
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep1/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep1-provider')
diff --git a/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb b/spec/data/run_context/cookbooks/circular-dep1/recipes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb b/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
new file mode 100644
index 0000000..aee6f4a
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep1/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep1-resource')
diff --git a/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb b/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb
new file mode 100644
index 0000000..f2ef012
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep2/attributes/default.rb
@@ -0,0 +1,3 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "circular-dep2::default"
+
diff --git a/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb b/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb
new file mode 100644
index 0000000..a23bf8f
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep2/definitions/circular_dep2_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep2-definition')
diff --git a/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb b/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb
new file mode 100644
index 0000000..ce0acc1
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep2/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("circular-dep2")
+
diff --git a/spec/data/run_context/cookbooks/circular-dep2/metadata.rb b/spec/data/run_context/cookbooks/circular-dep2/metadata.rb
new file mode 100644
index 0000000..3df19e0
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep2/metadata.rb
@@ -0,0 +1,2 @@
+name "circular-dep2"
+depends "circular-dep1"
diff --git a/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb b/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb
new file mode 100644
index 0000000..139ed59
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep2/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep2-provider')
diff --git a/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb b/spec/data/run_context/cookbooks/circular-dep2/recipes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb b/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
new file mode 100644
index 0000000..efd4ef3
--- /dev/null
+++ b/spec/data/run_context/cookbooks/circular-dep2/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('circular-dep2-resource')
diff --git a/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb b/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb
new file mode 100644
index 0000000..e818d36
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency1/attributes/aa_first.rb
@@ -0,0 +1,2 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "dependency1::aa_first"
diff --git a/spec/data/run_context/cookbooks/dependency1/attributes/default.rb b/spec/data/run_context/cookbooks/dependency1/attributes/default.rb
new file mode 100644
index 0000000..6875274
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency1/attributes/default.rb
@@ -0,0 +1,2 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "dependency1::default"
diff --git a/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb b/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb
new file mode 100644
index 0000000..1a513b0
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency1/attributes/zz_last.rb
@@ -0,0 +1,3 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "dependency1::zz_last"
+
diff --git a/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb b/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb
new file mode 100644
index 0000000..4e4344e
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency1/definitions/dependency1_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency1-definition')
diff --git a/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb b/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb
new file mode 100644
index 0000000..10dbb37
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency1/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("dependency1")
+
diff --git a/spec/data/run_context/cookbooks/dependency1/providers/provider.rb b/spec/data/run_context/cookbooks/dependency1/providers/provider.rb
new file mode 100644
index 0000000..f0ca000
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency1/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency1-provider')
diff --git a/spec/data/run_context/cookbooks/dependency1/recipes/default.rb b/spec/data/run_context/cookbooks/dependency1/recipes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/dependency1/resources/resource.rb b/spec/data/run_context/cookbooks/dependency1/resources/resource.rb
new file mode 100644
index 0000000..3bd0f17
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency1/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency1-resource')
diff --git a/spec/data/run_context/cookbooks/dependency2/attributes/default.rb b/spec/data/run_context/cookbooks/dependency2/attributes/default.rb
new file mode 100644
index 0000000..526751f
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency2/attributes/default.rb
@@ -0,0 +1,3 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "dependency2::default"
+
diff --git a/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb b/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb
new file mode 100644
index 0000000..7839278
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency2/definitions/dependency2_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency2-definition')
diff --git a/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb b/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb
new file mode 100644
index 0000000..27b3be1
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency2/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("dependency2")
+
diff --git a/spec/data/run_context/cookbooks/dependency2/providers/provider.rb b/spec/data/run_context/cookbooks/dependency2/providers/provider.rb
new file mode 100644
index 0000000..6cc6310
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency2/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency2-provider')
diff --git a/spec/data/run_context/cookbooks/dependency2/recipes/default.rb b/spec/data/run_context/cookbooks/dependency2/recipes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/dependency2/resources/resource.rb b/spec/data/run_context/cookbooks/dependency2/resources/resource.rb
new file mode 100644
index 0000000..73a0a34
--- /dev/null
+++ b/spec/data/run_context/cookbooks/dependency2/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('dependency2-resource')
diff --git a/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb b/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb
new file mode 100644
index 0000000..3ad2b92
--- /dev/null
+++ b/spec/data/run_context/cookbooks/no-default-attr/attributes/server.rb
@@ -0,0 +1,3 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "no-default-attr::server"
+
diff --git a/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb b/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb
new file mode 100644
index 0000000..cee4344
--- /dev/null
+++ b/spec/data/run_context/cookbooks/no-default-attr/definitions/no_default-attr_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('no-default-attr-definition')
diff --git a/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb b/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb
new file mode 100644
index 0000000..53b8adc
--- /dev/null
+++ b/spec/data/run_context/cookbooks/no-default-attr/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('no-default-attr-provider')
diff --git a/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb b/spec/data/run_context/cookbooks/no-default-attr/recipes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb b/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
new file mode 100644
index 0000000..067835e
--- /dev/null
+++ b/spec/data/run_context/cookbooks/no-default-attr/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('no-default-attr-resource')
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb
new file mode 100644
index 0000000..cca56bc
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-circular-deps/attributes/default.rb
@@ -0,0 +1,3 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "test-with-circular-deps::default"
+
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb
new file mode 100644
index 0000000..f084004
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-circular-deps/definitions/test_with-circular-deps_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-circular-deps-definition')
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb
new file mode 100644
index 0000000..76108f0
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-circular-deps/libraries/lib.rb
@@ -0,0 +1,2 @@
+LibraryLoadOrder.record("test-with-circular-deps")
+
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb
new file mode 100644
index 0000000..280c5ae
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-circular-deps/metadata.rb
@@ -0,0 +1,2 @@
+name "test-with-circular-deps"
+depends "circular-dep1"
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb
new file mode 100644
index 0000000..c25da70
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-circular-deps/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-circular-deps-provider')
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/recipes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb b/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
new file mode 100644
index 0000000..486e81d
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-circular-deps/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-circular-deps-resource')
diff --git a/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb b/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb
new file mode 100644
index 0000000..4d71cc3
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-deps/attributes/default.rb
@@ -0,0 +1,3 @@
+set_unless[:attr_load_order] = []
+set[:attr_load_order] << "test-with-deps::default"
+
diff --git a/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb b/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb
new file mode 100644
index 0000000..c481734
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-deps/definitions/test_with-deps_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-deps-definition')
diff --git a/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb b/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb
new file mode 100644
index 0000000..7dd942f
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-deps/libraries/lib.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record("test-with-deps")
diff --git a/spec/data/run_context/cookbooks/test-with-deps/metadata.rb b/spec/data/run_context/cookbooks/test-with-deps/metadata.rb
new file mode 100644
index 0000000..8909f11
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-deps/metadata.rb
@@ -0,0 +1,3 @@
+name "test-with-deps"
+depends "dependency1"
+depends "dependency2"
diff --git a/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb b/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb
new file mode 100644
index 0000000..96146c6
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-deps/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-deps-provider')
diff --git a/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb b/spec/data/run_context/cookbooks/test-with-deps/recipes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb b/spec/data/run_context/cookbooks/test-with-deps/recipes/server.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb b/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
new file mode 100644
index 0000000..54b152d
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test-with-deps/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-with-deps-resource')
diff --git a/spec/data/run_context/cookbooks/test/attributes/default.rb b/spec/data/run_context/cookbooks/test/attributes/default.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/data/run_context/cookbooks/test/attributes/george.rb b/spec/data/run_context/cookbooks/test/attributes/george.rb
new file mode 100644
index 0000000..8ea4454
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/attributes/george.rb
@@ -0,0 +1 @@
+default[:george] = "washington"
diff --git a/spec/data/run_context/cookbooks/test/definitions/new_animals.rb b/spec/data/run_context/cookbooks/test/definitions/new_animals.rb
new file mode 100644
index 0000000..5b00553
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/definitions/new_animals.rb
@@ -0,0 +1,9 @@
+define :new_dog, :is_cute => true do
+  dog "#{params[:name]}" do
+    cute params[:is_cute]
+  end
+end
+
+define :new_badger do
+  badger "#{params[:name]}"
+end
diff --git a/spec/data/run_context/cookbooks/test/definitions/new_cat.rb b/spec/data/run_context/cookbooks/test/definitions/new_cat.rb
new file mode 100644
index 0000000..a49b533
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/definitions/new_cat.rb
@@ -0,0 +1,5 @@
+define :new_cat, :is_pretty => true do
+  cat "#{params[:name]}" do
+     pretty_kitty params[:is_pretty]
+  end
+end
diff --git a/spec/data/run_context/cookbooks/test/definitions/test_res.rb b/spec/data/run_context/cookbooks/test/definitions/test_res.rb
new file mode 100644
index 0000000..b6a2e53
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/definitions/test_res.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-definition')
diff --git a/spec/data/run_context/cookbooks/test/providers/provider.rb b/spec/data/run_context/cookbooks/test/providers/provider.rb
new file mode 100644
index 0000000..abf0bd2
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/providers/provider.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-provider')
diff --git a/spec/data/run_context/cookbooks/test/recipes/default.rb b/spec/data/run_context/cookbooks/test/recipes/default.rb
new file mode 100644
index 0000000..d769dc8
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/recipes/default.rb
@@ -0,0 +1,5 @@
+
+cat "einstein" do
+  pretty_kitty true
+end
+
diff --git a/spec/data/run_context/cookbooks/test/recipes/one.rb b/spec/data/run_context/cookbooks/test/recipes/one.rb
new file mode 100644
index 0000000..7795cc1
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/recipes/one.rb
@@ -0,0 +1,7 @@
+cat "loulou" do
+  pretty_kitty true
+end
+
+new_cat "birthday" do
+  pretty_kitty false
+end
diff --git a/spec/data/run_context/cookbooks/test/recipes/two.rb b/spec/data/run_context/cookbooks/test/recipes/two.rb
new file mode 100644
index 0000000..01def1b
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/recipes/two.rb
@@ -0,0 +1,7 @@
+cat "peanut" do
+  pretty_kitty true
+end
+
+new_cat "fat peanut" do
+  pretty_kitty false
+end
diff --git a/spec/data/run_context/cookbooks/test/resources/resource.rb b/spec/data/run_context/cookbooks/test/resources/resource.rb
new file mode 100644
index 0000000..6775921
--- /dev/null
+++ b/spec/data/run_context/cookbooks/test/resources/resource.rb
@@ -0,0 +1 @@
+LibraryLoadOrder.record('test-resource')
diff --git a/spec/data/run_context/nodes/run_context.rb b/spec/data/run_context/nodes/run_context.rb
new file mode 100644
index 0000000..076d21a
--- /dev/null
+++ b/spec/data/run_context/nodes/run_context.rb
@@ -0,0 +1,5 @@
+##
+# Nodes should have a unique name
+##
+name "compile"
+run_list "test", "test::one", "test::two"
diff --git a/spec/data/search_queries_to_transform.txt b/spec/data/search_queries_to_transform.txt
new file mode 100644
index 0000000..4a05d54
--- /dev/null
+++ b/spec/data/search_queries_to_transform.txt
@@ -0,0 +1,98 @@
+afield:[* TO *]
+content:afield__=__*
+
+afield:[a TO *]
+content:[afield__=__a TO afield__=__\ufff0]
+
+afield:[* TO b]
+content:[afield__=__ TO afield__=__b]
+
+*:*
+*:*
+
+role:mon
+content:role__=__mon
+
+role:mon AND role:prod
+content:role__=__mon AND content:role__=__prod
+
+run_list:role\[rubberband\] AND run_list:role\[whale\]
+content:run_list__=__role\[rubberband\] AND content:run_list__=__role\[whale\]
+
+sharable_server:[* TO *]
+content:sharable_server__=__*
+
+run_list:role\[nfs_server\] AND sharable_server:[* TO *]
+content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__*
+
+run_list:role\[nfs_server\] AND sharable_server:[* TO *]
+content:run_list__=__role\[nfs_server\] AND content:sharable_server__=__*
+
+(role:prod AND x_y:true)
+(content:role__=__prod AND content:x_y__=__true)
+
+hostname:[* TO *] AND role:prod
+content:hostname__=__* AND content:role__=__prod
+
+role:t_mem AND role:prod NOT hostname:ip-1-2-3-4
+content:role__=__t_mem AND content:role__=__prod NOT content:hostname__=__ip-1-2-3-4
+
+ohai_time:[1234.567 TO *]
+content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0]
+
+ohai_time:{1234.567 TO *}
+content:{ohai_time__=__1234.567 TO ohai_time__=__\ufff0}
+
+ohai_time:[* TO baz]
+content:[ohai_time__=__ TO ohai_time__=__baz]
+
+ohai_time:{* TO baz}
+content:{ohai_time__=__ TO ohai_time__=__baz}
+
+tags:apples*.for.eating.com
+content:tags__=__apples*.for.eating.com
+
+role:safe AND ohai_time:[1234.567 TO *] AND whiz_bang:x5
+content:role__=__safe AND content:[ohai_time__=__1234.567 TO ohai_time__=__\ufff0] AND content:whiz_bang__=__x5
+
+role:safe AND ohai_time:[* TO 1234.567] AND whiz_bang:x5
+content:role__=__safe AND content:[ohai_time__=__ TO ohai_time__=__1234.567] AND content:whiz_bang__=__x5
+
+animal:[ape TO zebra]
+content:[animal__=__ape TO animal__=__zebra]
+
+animal:{ape TO zebra}
+content:{animal__=__ape TO animal__=__zebra}
+
+((value:[1 TO 3] OR nested_b1_a2_a3:B1_A2_A3-c) OR value:[5 TO *])
+((content:[value__=__1 TO value__=__3] OR content:nested_b1_a2_a3__=__B1_A2_A3-c) OR content:[value__=__5 TO value__=__\ufff0])
+
+((value:{1 TO 3} OR value:{1 TO 3}) OR run_list:recipe\[alpha\])
+((content:{value__=__1 TO value__=__3} OR content:{value__=__1 TO value__=__3}) OR content:run_list__=__recipe\[alpha\])
+
+words:"one two three"
+content:"words__=__one two three"
+
+words:"one \"two\" three"
+content:"words__=__one \"two\" three"
+
+words:"\"one two\" three"
+content:"words__=__\"one two\" three"
+
+words:"one two \"three\""
+content:"words__=__one two \"three\""
+
+words:"one two \"three\"" OR words:"\"one two\" three" AND words:"one \"two\" three"
+content:"words__=__one two \"three\"" OR content:"words__=__\"one two\" three" AND content:"words__=__one \"two\" three"
+
+words:\"*
+content:words__=__\"*
+
+-version:0.9.12
+-content:version__=__0.9.12
+
+!version:0.9.12
+NOT content:version__=__0.9.12
+
+ec2:*
+content:ec2__=__*
diff --git a/spec/data/shef-config.rb b/spec/data/shef-config.rb
new file mode 100644
index 0000000..3c3ae90
--- /dev/null
+++ b/spec/data/shef-config.rb
@@ -0,0 +1,10 @@
+Ohai::Config[:disabled_plugins] << 'darwin::system_profiler' << 'darwin::kernel' << 'darwin::ssh_host_key' << 'network_listeners' 
+Ohai::Config[:disabled_plugins] <<  "virtualization" << "darwin::virtualization"
+Ohai::Config[:disabled_plugins] << 'darwin::uptime' << 'darwin::filesystem' << 'dmi' << 'lanuages' << 'perl' << 'python' << 'java' 
+Ohai::Config[:disabled_plugins] << "linux::block_device" << "linux::kernel" << "linux::ssh_host_key" << "linux::virtualization"
+Ohai::Config[:disabled_plugins] << "linux::cpu" << "linux::memory" << "ec2" << "rackspace" << "eucalyptus" << "ip_scopes"
+Ohai::Config[:disabled_plugins] << "solaris2::cpu" << "solaris2::dmi" << "solaris2::filesystem" << "solaris2::kernel"
+Ohai::Config[:disabled_plugins] << "solaris2::virtualization" << "solaris2::zpools"
+Ohai::Config[:disabled_plugins] << 'c' << 'php' << 'mono' << 'groovy' << 'lua' << 'erlang'
+Ohai::Config[:disabled_plugins] << "kernel" << "linux::filesystem" << "ruby"
+
diff --git a/spec/data/ssl/5e707473.0 b/spec/data/ssl/5e707473.0
new file mode 100644
index 0000000..5c5cf87
--- /dev/null
+++ b/spec/data/ssl/5e707473.0
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6DCCAlGgAwIBAgIJANlevg7kzqvpMA0GCSqGSIb3DQEBBQUAMFcxITAfBgNV
+BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEeMBwGA1UECxMVU25ha2VvaWwg
+Q2VydGlmaWNhdGVzMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMDkxMjE5MTkxODUy
+WhcNMTAwMTE4MTkxODUyWjBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
+eSBMdGQxHjAcBgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJ
+bG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDhm9En1DL3aC4H
+j5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JEhQhLXStjhDr4p/IrARnZ8shy0MA4
+wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAoM9b/4pd0FqgRW1UbhvqQDzkWmVyK
+Tz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQABo4G7MIG4MB0GA1UdDgQWBBTmAcyA
+CqQblJ1L4sOIzmkdIAtY6jCBiAYDVR0jBIGAMH6AFOYBzIAKpBuUnUviw4jOaR0g
+C1jqoVukWTBXMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxHjAc
+BgNVBAsTFVNuYWtlb2lsIENlcnRpZmljYXRlczESMBAGA1UEAxMJbG9jYWxob3N0
+ggkA2V6+DuTOq+kwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBe4f9R
+s0g5GCFekabzl9AHvIn4ITxenvuyaNX9f2BJbdgoD03wlGycBxjbC57RjFVfetu7
+mtUYuJSx7iojBSC+LzotGptrG9d2BxrWOKBfF2K+dyoIG8kZL5aLfS0be6Cc5O3c
+L/IPadJhBu/EfyGI2vL1l8GspXdOxaFzHprpgA==
+-----END CERTIFICATE-----
diff --git a/spec/data/ssl/chef-rspec.cert b/spec/data/ssl/chef-rspec.cert
new file mode 100644
index 0000000..08ec684
--- /dev/null
+++ b/spec/data/ssl/chef-rspec.cert
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEkjCCA3qgAwIBAgIJAKBJr4wSRUVvMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD
+VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEN
+MAsGA1UEChMEQ2hlZjETMBEGA1UECxMKZGV2ZWxvcGVyczESMBAGA1UEAxMJa2Fs
+bGlzdGVjMR4wHAYJKoZIhvcNAQkBFg9kYW5Ab3BzY29kZS5jb20wHhcNMTAwNDEw
+MTkxMTMxWhcNMjAwNDA3MTkxMTMxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+Cldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzAR
+BgNVBAsTCmRldmVsb3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3
+DQEJARYPZGFuQG9wc2NvZGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdTnEdjlGGG
+SxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bLAHEHS2if
+1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/rMFHf+yoY
+guuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8nPMiiHpoO
+pgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xFSFwGUUA9
+IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABo4H0MIHxMB0GA1UdDgQWBBS88Zxt
+vG+FTu1U+VFA47ffzwStbjCBwQYDVR0jBIG5MIG2gBS88ZxtvG+FTu1U+VFA47ff
+zwStbqGBkqSBjzCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
+EDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzARBgNVBAsTCmRldmVs
+b3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3DQEJARYPZGFuQG9w
+c2NvZGUuY29tggkAoEmvjBJFRW8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUF
+AAOCAQEAwwMrbuJAhP5uawJi5OYEaJKSbJGyahCcOAl4+ONgsdDoCy/9AZKzuFNc
+C8vM/Ee6jyugrKMdckvZ883kJ4770HU6nbomCUVKMHMzJBE1Guvsn8wZP3nKyeSZ
+eXXbH1b/NfstNyo6XLucaBRQvyvQYDUnk6osrBh+Gekvqsahr0wkVa8VUY2UySyY
+60lYt4O92XJ1jWtYoFjRxeeUgo5E0TfIWj74kXhdMqwMf4Iv9VatfYR87ps5VMdf
+Hp+nrCRaquDAs87LdO9e7M8l+W1ryPfP2inuGjIozsN5lLmwBdT+O6NkpmuxGPEG
+ArIbYatR7+4MsDn+CjfkYblnmGLuug==
+-----END CERTIFICATE-----
diff --git a/spec/data/ssl/chef-rspec.key b/spec/data/ssl/chef-rspec.key
new file mode 100644
index 0000000..29aaecf
--- /dev/null
+++ b/spec/data/ssl/chef-rspec.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdT
+nEdjlGGGSxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bL
+AHEHS2if1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/r
+MFHf+yoYguuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8n
+PMiiHpoOpgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xF
+SFwGUUA9IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABAoIBAQC+hddKaA4se+sL
+4QaSoj+mwtypXjZHnv/+sJj8IjY+IMGbzmJmqzLX6VbB+gCMoMTySwmS54NxFTHp
+LPwUz0vFTUdzecHpzg9mDAU5HUYYA1ZNbhq2R2JvlW16j1b9NnOpse77fLbFCPgK
+b8TOqnmheot2hkjEipGN2Z7o5gYaz1/3PtolkP1ypCTG6Bh7V3ohBLBIEdjA552o
+HNGe3t6PpvoNtBqaeb/j/SAOvg+8DGF1WQtE+5Y1koSlhABYWkHzHC1fHAzRMSHH
+ZMfKOQNusRgBRNJabdVqkuTbvyRCQEb2YGQxPPYV2C+AxAlh3APeYTg90vUqAq/3
+ivNdilcBAoGBAOLELc0mcTftDbIMWVnrzAGAJOCMz3FkwGcV8nqNeA3R77e3pWL2
+5+bKadWQGjjpR3ZEYt/RxHsoGCW3NtM44icxqVCTPW/unp2xqadjuvcsKrxk+1wD
+OdvVrwcd/N+KzgXO+Hm7xbV/loFms3ueGfCRbOueQyP4dj9MyOBGlO2hAoGBANzQ
+u8IrZBG0DL8YFdmjw4YWUENIOtABPU1qHo/sugTQjI9K3/E3LA7aaGnl2P//1tao
+SR/aP/To90H6D989/JomhkEKKA+DyL1sRL1NMdtWwrKdEq32W8fUN0JEA+Q1FMsd
+Hk6Ix+KrZVg9cTb9HoGikDxeHW3pPKDWaEkWIQLLAoGAD13N4L3/JBQLPop5r487
+9soRNao1EHEMXK/vC4D0prMYNHHcYjVrB4el3lPygvLD5e7CaHpVfyb7Y+rjazLK
+mG9UEuK3YhNgaj00yuQGMmOqzbNmGRka3ZvATZIppZhJV7lruwwPXLo1n7Uu6myP
+Q28HW3wQ/qoCkU2JuzDtPKECgYBUrYcTEuixEUbCEU5vw6k7RltJMe27zn3frg5C
+Sxmatw7v9Fqkee/fUkowMgBhS47rimVgXaWhGaWYG3jytyajRpq9XlO2f2b/nQFP
+RscTwdWwASQkqhDQNMVsGAEWBnUO3v+8Rh/BANFAYW+FEtQcCmcdf0nx2DtzwkUD
+ogTOuQKBgCbEg+/ND/p8xKwY9LtjLKnrQSL5tSH/7prhLJvVVdW7FMRfKSp1t2xc
+kfJFqO1Lcf2j7hiclval3xDoWUretNQ5379T0Ob30WuIomSfeqcxJjCUtyN3fUqr
+z/QG9dk/23OOYJhRgAmttBDqpk5uB5mOQgSftdELNyw0EOyNIBfZ
+-----END RSA PRIVATE KEY-----
diff --git a/spec/data/ssl/key.pem b/spec/data/ssl/key.pem
new file mode 100644
index 0000000..50b8fd8
--- /dev/null
+++ b/spec/data/ssl/key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDhm9En1DL3aC4Hj5/SA6FXm6B/0AoVzoPfWX2rpkRcz/XX24JE
+hQhLXStjhDr4p/IrARnZ8shy0MA4wNpNPEn5c0RvqKypHzX+AeQkBx8J1/8vnMAo
+M9b/4pd0FqgRW1UbhvqQDzkWmVyKTz5yCiTntxDzudAtHlTo8V6E7UEDkwIDAQAB
+AoGAGicC1tgdVFqqQ0wd3a14DXzH3SkTkjWPSdvI2pX6hLvCptQWRLUbIglZ1z5j
+y6FETEHjakVfgRe7wJhyddOQS3eeVt/aK0xBHz/JiJuIF+NzbJT9t01nPV21abYU
+lWIhWV8Ja39a5LKV6hee0TTYdAub7BVQ95kwrqMqRcDoXHECQQDxpAgq925Cmlz1
+0Q1WZq2A/o8oqPvPS1FulPK2OgyOyQSK+DdcK2xUKGWMn0m9fDLLzj/pe/H3dN1I
+b8Z/iiWrAkEA7wPlesZX3GzfqQLd6GYGBa4IdrV5dHdeoCCVRnkFr06KjcqpAhg1
+7i0T9frSC5EfRCfbGNgo4eutT9+D7HJhuQJAZeDBrNPbQetxDBbSp73sovkwhHUS
+jah0scnMtvWse7rW1nymYo7QQn8xqWMzJNerVvAjVB50ut8juLmfmAA3twJAQy9/
+NBHI5Mcd365Unlz/WF1hN60vZNOhH7XJADRIqsyTGeRbuaEAl+DH+Z71qBa1CT2C
+0usAIvFSmF8mADLu0QJAHSSh6zLNInvkhDjYAmEu3oeFQgQ4Rp7oiMaBZ6VVuOMo
+4GU9CA18iI75NaO7FOfquJPkIJ0li0xadVofUpaJcg==
+-----END RSA PRIVATE KEY-----
diff --git a/spec/data/ssl/private_key.pem b/spec/data/ssl/private_key.pem
new file mode 100644
index 0000000..b663660
--- /dev/null
+++ b/spec/data/ssl/private_key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh
+8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy
+YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei
+PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A
+O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x
+PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD
+2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk
+WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP
+g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa
+Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ
+I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/
+/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR
+xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO
+ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy
+bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A
+s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4
+DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz
+dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv
+GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq
+qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8
+OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R
+b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I
+YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12
+2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo
+Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
+-----END RSA PRIVATE KEY-----
diff --git a/spec/data/ssl/private_key_with_whitespace.pem b/spec/data/ssl/private_key_with_whitespace.pem
new file mode 100644
index 0000000..b393a3f
--- /dev/null
+++ b/spec/data/ssl/private_key_with_whitespace.pem
@@ -0,0 +1,32 @@
+
+
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh
+8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy
+YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei
+PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A
+O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x
+PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD
+2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk
+WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP
+g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa
+Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ
+I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/
+/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR
+xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO
+ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy
+bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A
+s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4
+DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz
+dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv
+GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq
+qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8
+OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R
+b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I
+YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12
+2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo
+Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
+-----END RSA PRIVATE KEY-----
+
+
+
diff --git a/spec/data/templates/seattle.txt b/spec/data/templates/seattle.txt
new file mode 100644
index 0000000..19f6290
--- /dev/null
+++ b/spec/data/templates/seattle.txt
@@ -0,0 +1 @@
+Seattle is a great town.  Next time you visit, you should buy me a beer.
\ No newline at end of file
diff --git a/spec/data/trusted_certs/example.crt b/spec/data/trusted_certs/example.crt
new file mode 100644
index 0000000..832aebe
--- /dev/null
+++ b/spec/data/trusted_certs/example.crt
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDkjCCAnoCCQDihI8kxGYTFTANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMRAwDgYDVQQKEwdZb3VD
+b3JwMRMwEQYDVQQLEwpPcGVyYXRpb25zMRYwFAYDVQQDEw1leGFtcGxlLmxvY2Fs
+MR0wGwYJKoZIhvcNAQkBFg5tZUBleGFtcGxlLmNvbTAeFw0xMzEwMTcxODAxMzVa
+Fw0yMzEwMTUxODAxMzVaMIGKMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAO
+BgNVBAcTB1NlYXR0bGUxEDAOBgNVBAoTB1lvdUNvcnAxEzARBgNVBAsTCk9wZXJh
+dGlvbnMxFjAUBgNVBAMTDWV4YW1wbGUubG9jYWwxHTAbBgkqhkiG9w0BCQEWDm1l
+QGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyKBo
+U+Bdni0xZK/NCzdLdi2X+TyW5eahbYMx+r1GDcVqCICvrthBCVLVFsQ8rvOHwTPi
+AxQJGxb9TLSXRgXQSlH6FLjIUceuOtpan3qYVJ1v7AxY4DgNvYBpbtJz5MQedJnT
+g2F+rXzkwaD6CWBqWHeGU0oP3r7bq1AMD6XEsK2w2/zHtG7TEnL45ARv1PsyrU5M
+ZAW/XyoMyq1k2Lpv7YR5kAvTq1+4RSt/it2RFE7R0AVbaQ0MeAnllfySiHHHlaOT
+FVd/qPSiGISxsUmmzA3Z08+0sfJwkrnJXbLscCBYndd7gMGgtczGjJtul0Ch3GFa
+/Pn5McjwF272+usJ1wIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQCzPePWifWNECsG
+nL8on1AtFMkczE1/pdRS4YUl/Tc926MpezptSja8rL31+4Bom37/wYPG7HygtAQl
+R4FHpAtuqJKPOfjUmDNsIXRFnytrnflTpctDu/Nbj4PDCy01k/sTDUQt+s+lEBL8
+M8ArmfLZ8PCfAwnXmJQ5rggDFKqegjt6z1RsSglbMiASE7+KkpBnzaqH6fET6IQz
+WgAjv6WdRfwgfJjOTSX4XMpCSet9KaWmXExKrxiVng2Uu6E+ShVAyKaGMuc1B7VA
+oxnnVaVapFv5lOWucQr4KkC7EgaUZnyt8duOc8+Yvd+y3Xd2dcHUnmegRxly4jRV
+/lXbFAUb
+-----END CERTIFICATE-----
diff --git a/spec/data/trusted_certs/intermediate.pem b/spec/data/trusted_certs/intermediate.pem
new file mode 100644
index 0000000..78148b0
--- /dev/null
+++ b/spec/data/trusted_certs/intermediate.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIEjzCCA3egAwIBAgIQBp4dt3/PHfupevXlyaJANzANBgkqhkiG9w0BAQUFADBh
+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==
+-----END CERTIFICATE-----
diff --git a/spec/data/trusted_certs/opscode.pem b/spec/data/trusted_certs/opscode.pem
new file mode 100644
index 0000000..d7d211c
--- /dev/null
+++ b/spec/data/trusted_certs/opscode.pem
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+MIIGqjCCBZKgAwIBAgIQCJlQhNSTz1z3zHZb972KvDANBgkqhkiG9w0BAQUFADBI
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSIwIAYDVQQDExlE
+aWdpQ2VydCBTZWN1cmUgU2VydmVyIENBMB4XDTEzMDQxMjAwMDAwMFoXDTE0MDYx
+NjEyMDAwMFowYzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
+BgNVBAcTB1NlYXR0bGUxFTATBgNVBAoTDE9wc2NvZGUsIEluYzEWMBQGA1UEAwwN
+Ki5vcHNjb2RlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+U
+HLAzObPRlmchlkJ2JFeReJRPXj5F27HuX8SXT+5WhVGunQf1swjASJ0utk1x9wGT
+f9tnF8fYiwJIqWJopaPiwzNw1cD6CnIfhM3z4T3EzLAWWu2ZhfuaQk9Z6jhItkm7
+upO4CsFq1xw7IjqOq09PCAklYC/Y/8Qq5Qj8VoTp0ldVv6hbqTNkezhWcKU/07si
+jAX1O+DYN6dlVNezfl4Xt5ccsu8Mp0s92IMVYLgY6bpb1b91ez9+XBE1v7zjaR0V
+EP7Ix9av/pXjqMqHgjlsg46UpLa30f4FEi2xmXpCBpOP94rCrT7g+u8UlIrJ/QK/
+/lHyKBpCm0R9ftDbppsCAwEAAaOCA3MwggNvMB8GA1UdIwQYMBaAFJBx2zfrc8jv
+3NUeErY0uitaoKaSMB0GA1UdDgQWBBTdhCU7MvQblxtWHlfHG4jPUTuh5DBLBgNV
+HREERDBCgg0qLm9wc2NvZGUuY29tggtvcHNjb2RlLmNvbYIQY29ycC5vcHNjb2Rl
+LmNvbYISKi5jb3JwLm9wc2NvZGUuY29tMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUE
+FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwYQYDVR0fBFowWDAqoCigJoYkaHR0cDov
+L2NybDMuZGlnaWNlcnQuY29tL3NzY2EtZzEuY3JsMCqgKKAmhiRodHRwOi8vY3Js
+NC5kaWdpY2VydC5jb20vc3NjYS1nMS5jcmwwggHEBgNVHSAEggG7MIIBtzCCAbMG
+CWCGSAGG/WwBATCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQu
+Y29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCCAVYeggFS
+AEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBj
+AGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBu
+AGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQ
+AFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAg
+AEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABp
+AGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwBy
+AGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBl
+AC4weAYIKwYBBQUHAQEEbDBqMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
+Y2VydC5jb20wQgYIKwYBBQUHMAKGNmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv
+bS9EaWdpQ2VydFNlY3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqG
+SIb3DQEBBQUAA4IBAQCNk1+7l+VlAZrKov7ugP7WuKS7IEUZRk8CVAFPtIrp+jFB
+6W0ta1qMpYyItp5enTBCGOkTfPly06hZnFRQw3ZnkSsWDKIvCRks4kZt3oHLd3nO
+G671JGRJI/qbs6F5l6c96kotlZkolYIPMhyK8Ex4LjMW6UrPWdpJrXTWPvLq4c85
+ZaN52yKu6tsLrBTPwPmK9t+zQ2drb1g8Eq9B+cuwD3Row6njsDQ1Ltry+KCnivki
+E/ptgwyCkS4brkhjHMz5l5Co0KMsHylAb2XcBxFVFSl0aJIqK5Gr0nTlg26pNG7O
+qxv6ncOHl3tmArETi36TQbTYvFc+6cNb8CqdWe95
+-----END CERTIFICATE-----
diff --git a/spec/data/trusted_certs/root.pem b/spec/data/trusted_certs/root.pem
new file mode 100644
index 0000000..fd4341d
--- /dev/null
+++ b/spec/data/trusted_certs/root.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
diff --git a/spec/functional/assets/PkgA.1.0.0.0.bff b/spec/functional/assets/PkgA.1.0.0.0.bff
new file mode 100644
index 0000000..b157d08
Binary files /dev/null and b/spec/functional/assets/PkgA.1.0.0.0.bff differ
diff --git a/spec/functional/assets/PkgA.2.0.0.0.bff b/spec/functional/assets/PkgA.2.0.0.0.bff
new file mode 100644
index 0000000..0afe7b7
Binary files /dev/null and b/spec/functional/assets/PkgA.2.0.0.0.bff differ
diff --git a/spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm b/spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm
new file mode 100644
index 0000000..1c6f129
Binary files /dev/null and b/spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm differ
diff --git a/spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm b/spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm
new file mode 100644
index 0000000..650b7dd
Binary files /dev/null and b/spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm differ
diff --git a/spec/functional/assets/mytest-1.0-1.noarch.rpm b/spec/functional/assets/mytest-1.0-1.noarch.rpm
new file mode 100644
index 0000000..07334a0
Binary files /dev/null and b/spec/functional/assets/mytest-1.0-1.noarch.rpm differ
diff --git a/spec/functional/assets/mytest-2.0-1.noarch.rpm b/spec/functional/assets/mytest-2.0-1.noarch.rpm
new file mode 100644
index 0000000..7e7ba17
Binary files /dev/null and b/spec/functional/assets/mytest-2.0-1.noarch.rpm differ
diff --git a/spec/functional/dsl/registry_helper_spec.rb b/spec/functional/dsl/registry_helper_spec.rb
new file mode 100644
index 0000000..452c4c2
--- /dev/null
+++ b/spec/functional/dsl/registry_helper_spec.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Copyright:: Copyright (c) 2011 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/dsl/registry_helper"
+require "spec_helper"
+
+describe Chef::Resource::RegistryKey, :windows_only do
+
+  before (:all) do
+    ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root"
+    ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch"
+    ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root', Win32::Registry::KEY_ALL_ACCESS) do |reg|
+      reg['RootType1', Win32::Registry::REG_SZ] = 'fibrous'
+      reg.write('Roots', Win32::Registry::REG_MULTI_SZ, ["strong roots", "healthy tree"])
+    end
+
+    events = Chef::EventDispatch::Dispatcher.new
+    node = Chef::Node.new
+    ohai = Ohai::System.new
+    ohai.all_plugins
+    node.consume_external_attrs(ohai.data,{})
+    run_context = Chef::RunContext.new(node, {}, events)
+    @resource = Chef::Resource.new("foo", run_context)
+  end
+
+  context "tests registry dsl" do
+    it "returns true if registry_key_exists" do
+      @resource.registry_key_exists?("HKCU\\Software\\Root").should == true
+    end
+    it "returns true if registry has specified value" do
+      values = @resource.registry_get_values("HKCU\\Software\\Root")
+      values.include?({:name=>"RootType1",:type=>:string,:data=>"fibrous"}).should == true
+    end
+    it "returns true if specified registry_has_subkey" do
+      @resource.registry_has_subkeys?("HKCU\\Software\\Root").should == true
+    end
+    it "returns true if specified key has specified subkey" do
+      subkeys = @resource.registry_get_subkeys("HKCU\\Software\\Root")
+      subkeys.include?("Branch").should == true
+    end
+    it "returns true if registry_value_exists" do
+      @resource.registry_value_exists?("HKCU\\Software\\Root", {:name=>"RootType1", :type=>:string, :data=>"fibrous"}).should == true
+    end
+    it "returns true if data_value_exists" do
+      @resource.registry_data_exists?("HKCU\\Software\\Root", {:name=>"RootType1", :type=>:string, :data=>"fibrous"}).should == true
+    end
+  end
+end
diff --git a/spec/functional/file_content_management/deploy_strategies_spec.rb b/spec/functional/file_content_management/deploy_strategies_spec.rb
new file mode 100644
index 0000000..3a9c011
--- /dev/null
+++ b/spec/functional/file_content_management/deploy_strategies_spec.rb
@@ -0,0 +1,238 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+shared_examples_for "a content deploy strategy" do
+
+  # Ruby 1.8 has no binread
+  def binread(file)
+    if IO.respond_to?(:binread)
+      IO.binread(file)
+    else
+      IO.read(file)
+    end
+  end
+
+  def normalize_mode(mode_int)
+    ( mode_int & 07777).to_s(8)
+  end
+
+  let(:sandbox_dir) do
+    basename = make_tmpname("content-deploy-tests")
+    full_path = File.join(CHEF_SPEC_DATA, basename)
+    FileUtils.mkdir_p(full_path)
+    full_path
+  end
+
+  after do
+    FileUtils.rm_rf(sandbox_dir) if File.exist?(sandbox_dir)
+  end
+
+  let(:content_deployer) { described_class.new }
+  let(:target_file_path) { File.join(sandbox_dir, "cp-deploy-strategy-target-file.txt") }
+
+
+  describe "creating the file" do
+
+    ##
+    # UNIX Context
+    let(:default_mode) { normalize_mode(0100666 - File.umask) }
+
+    it "touches the file to create it (UNIX)", :unix_only do
+      content_deployer.create(target_file_path)
+      File.should exist(target_file_path)
+      file_info = File.stat(target_file_path)
+      file_info.should be_owned
+      file_info.should be_file
+      normalize_mode(file_info.mode).should == default_mode
+    end
+
+    ##
+    # Window Context
+    let(:parent_dir) { File.dirname(target_file_path) }
+
+    let(:parent_security_descriptor) do
+      security_obj = Chef::ReservedNames::Win32::Security::SecurableObject.new(parent_dir)
+      security_obj.security_descriptor(true)
+    end
+
+    let(:masks) do
+      Chef::ReservedNames::Win32::API::Security
+    end
+
+    def ace_inherits?(ace)
+      flags = ace.flags
+      (flags & masks::OBJECT_INHERIT_ACE) !=0
+    end
+
+    let(:parent_inheritable_aces) do
+      inheritable_aces = parent_security_descriptor.dacl.select do |ace|
+        ace_inherits?(ace)
+      end
+    end
+
+    # Win2003 has annoying differences in ACL inheritance behavior that make
+    # the default ACLs substantially different from those created on subsequent
+    # windows versions. The behaviors here are also covered by resource-level
+    # tests so we'll skip win2k3 here to keep the tests simple.
+    it "touches the file to create it (Windows)", :windows_only, :not_supported_on_win2k3 do
+      content_deployer.create(target_file_path)
+      File.should exist(target_file_path)
+      file_info = File.stat(target_file_path)
+      file_info.should be_owned
+      file_info.should be_file
+
+      parent_aces = parent_inheritable_aces
+      security_obj = Chef::ReservedNames::Win32::Security::SecurableObject.new(target_file_path)
+
+      security_descriptor = security_obj.security_descriptor(true)
+      security_descriptor.dacl.each_with_index do |ace, index|
+        ace.inherited?.should be_true
+        ace.mask.should == parent_aces[index].mask
+      end
+
+    end
+  end
+
+  describe "updating the file" do
+
+    let(:staging_dir) { Dir.mktmpdir }
+
+    let(:staging_file_content) { "this is the expected content" }
+
+    let(:staging_file_path) do
+      path = File.join(staging_dir, "cp-deploy-strategy-staging-file.txt")
+      File.open(path, "w+", 0600) { |f| f.print(staging_file_content) }
+      path
+    end
+
+    def unix_invariant_properies(stat_struct)
+      unix_invariants.inject({}) do |property_map, property|
+        property_map[property] = stat_struct.send(property)
+        property_map
+      end
+    end
+
+    def win_invariant_properties(sec_obj)
+      descriptor = sec_obj.security_descriptor(true)
+      security_descriptor_invariants.inject({}) do |prop_map, property|
+        prop_map[property] = descriptor.send(property)
+        prop_map
+       end
+    end
+
+    before do
+      content_deployer.create(target_file_path)
+    end
+
+    it "maintains invariant properties on UNIX", :unix_only do
+      original_info = File.stat(target_file_path)
+      content_deployer.deploy(staging_file_path, target_file_path)
+      updated_info = File.stat(target_file_path)
+
+      unix_invariant_properies(original_info).should == unix_invariant_properies(updated_info)
+    end
+
+    it "maintains invariant properties on Windows", :windows_only do
+      original_info = Chef::ReservedNames::Win32::Security::SecurableObject.new(target_file_path)
+      content_deployer.deploy(staging_file_path, target_file_path)
+      updated_info = Chef::ReservedNames::Win32::Security::SecurableObject.new(target_file_path)
+
+      win_invariant_properties(original_info).should == win_invariant_properties(updated_info)
+    end
+
+    it "updates the target with content from staged" do
+      content_deployer.deploy(staging_file_path, target_file_path)
+      binread(target_file_path).should == staging_file_content
+    end
+
+    context "when the owner of the target file is not the owner of the staging file", :requires_root do
+
+      before do
+        File.chown(1337, 1337, target_file_path)
+      end
+
+      it "copies the staging file's content" do
+        original_info = File.stat(target_file_path)
+        content_deployer.deploy(staging_file_path, target_file_path)
+        updated_info = File.stat(target_file_path)
+
+        unix_invariant_properies(original_info).should == unix_invariant_properies(updated_info)
+      end
+
+    end
+
+  end
+end
+
+describe Chef::FileContentManagement::Deploy::Cp do
+
+  let(:unix_invariants) do
+    [
+      :uid,
+      :gid,
+      :mode,
+      :ino
+    ]
+  end
+
+  let(:security_descriptor_invariants) do
+    [
+      :owner,
+      :group,
+      :dacl
+    ]
+  end
+
+  it_should_behave_like "a content deploy strategy"
+
+end
+
+describe Chef::FileContentManagement::Deploy::MvUnix, :unix_only do
+
+  let(:unix_invariants) do
+    [
+      :uid,
+      :gid,
+      :mode
+    ]
+  end
+
+  it_should_behave_like "a content deploy strategy"
+end
+
+# On Unix we won't have loaded the file, avoid undefined constant errors:
+class Chef::FileContentManagement::Deploy::MvWindows ; end
+
+describe Chef::FileContentManagement::Deploy::MvWindows, :windows_only do
+
+  context "when a file has no sacl" do
+
+    let(:security_descriptor_invariants) do
+      [
+       :owner,
+       :group,
+       :dacl
+      ]
+    end
+
+    it_should_behave_like "a content deploy strategy"
+  end
+
+end
diff --git a/spec/functional/knife/cookbook_delete_spec.rb b/spec/functional/knife/cookbook_delete_spec.rb
new file mode 100644
index 0000000..2d52fb9
--- /dev/null
+++ b/spec/functional/knife/cookbook_delete_spec.rb
@@ -0,0 +1,157 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tiny_server'
+
+describe Chef::Knife::CookbookDelete do
+  before(:all) do
+    @server = TinyServer::Manager.new
+    @server.start
+  end
+
+  before(:each) do
+    @knife = Chef::Knife::CookbookDelete.new
+    @api = TinyServer::API.instance
+    @api.clear
+
+    Chef::Config[:node_name] = nil
+    Chef::Config[:client_key] = nil
+    Chef::Config[:chef_server_url] = 'http://localhost:9000'
+  end
+
+  after(:all) do
+    @server.stop
+  end
+
+  context "when the cookbook doesn't exist" do
+    before do
+      @log_output = StringIO.new
+
+      Chef::Log.logger = Logger.new(@log_output)
+      Chef::Log.level = :debug
+
+      @knife.name_args = %w{no-such-cookbook}
+      @api.get("/cookbooks/no-such-cookbook", 404, {'error'=>'dear Tim, no. -Sent from my iPad'}.to_json)
+    end
+
+    it "logs an error and exits" do
+      @knife.ui.stub!(:stderr).and_return(@log_output)
+      lambda {@knife.run}.should raise_error(SystemExit)
+      @log_output.string.should match(/Cannot find a cookbook named no-such-cookbook to delete/)
+    end
+
+  end
+
+  context "when there is only one version of a cookbook" do
+    before do
+      @knife.name_args = %w{obsolete-cookbook}
+      @cookbook_list = {'obsolete-cookbook' => { 'versions' => ['version' => '1.0.0']} }
+      @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json)
+    end
+
+    it "asks for confirmation, then deletes the cookbook" do
+      stdin, stdout = StringIO.new("y\n"), StringIO.new
+      @knife.ui.stub!(:stdin).and_return(stdin)
+      @knife.ui.stub!(:stdout).and_return(stdout)
+
+      cb100_deleted = false
+      @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+
+      @knife.run
+
+      stdout.string.should match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
+      cb100_deleted.should be_true
+    end
+
+    it "asks for confirmation before purging" do
+      @knife.config[:purge] = true
+
+      stdin, stdout = StringIO.new("y\ny\n"), StringIO.new
+      @knife.ui.stub!(:stdin).and_return(stdin)
+      @knife.ui.stub!(:stdout).and_return(stdout)
+
+      cb100_deleted = false
+      @api.delete("/cookbooks/obsolete-cookbook/1.0.0?purge=true", 200) { cb100_deleted = true; "[\"true\"]" }
+
+      @knife.run
+
+      stdout.string.should match(/#{Regexp.escape('Are you sure you want to purge files')}/)
+      stdout.string.should match(/#{Regexp.escape('Do you really want to delete obsolete-cookbook version 1.0.0? (Y/N)')}/)
+      cb100_deleted.should be_true
+
+    end
+
+  end
+
+  context "when there are several versions of a cookbook" do
+    before do
+      @knife.name_args = %w{obsolete-cookbook}
+      versions = ['1.0.0', '1.1.0', '1.2.0']
+      with_version = lambda { |version| { 'version' => version } }
+      @cookbook_list = {'obsolete-cookbook' => { 'versions' => versions.map(&with_version) } }
+      @api.get("/cookbooks/obsolete-cookbook", 200, @cookbook_list.to_json)
+    end
+
+    it "deletes all versions of a cookbook when given the '-a' flag" do
+      @knife.config[:all] = true
+      @knife.config[:yes] = true
+      cb100_deleted = cb110_deleted = cb120_deleted = nil
+      @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+      @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
+      @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
+      @knife.run
+
+      cb100_deleted.should be_true
+      cb110_deleted.should be_true
+      cb120_deleted.should be_true
+    end
+
+    it "asks which version to delete and deletes that when not given the -a flag" do
+      cb100_deleted = cb110_deleted = cb120_deleted = nil
+      @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+      stdin, stdout = StringIO.new, StringIO.new
+      @knife.ui.stub!(:stdin).and_return(stdin)
+      @knife.ui.stub!(:stdout).and_return(stdout)
+      stdin << "1\n"
+      stdin.rewind
+      @knife.run
+      cb100_deleted.should be_true
+      stdout.string.should match(/Which version\(s\) do you want to delete\?/)
+    end
+
+    it "deletes all versions of the cookbook when not given the -a flag and the user chooses to delete all" do
+      cb100_deleted = cb110_deleted = cb120_deleted = nil
+      @api.delete("/cookbooks/obsolete-cookbook/1.0.0", 200) { cb100_deleted = true; "[\"true\"]" }
+      @api.delete("/cookbooks/obsolete-cookbook/1.1.0", 200) { cb110_deleted = true; "[\"true\"]" }
+      @api.delete("/cookbooks/obsolete-cookbook/1.2.0", 200) { cb120_deleted = true; "[\"true\"]" }
+
+      stdin, stdout = StringIO.new("4\n"), StringIO.new
+      @knife.ui.stub!(:stdin).and_return(stdin)
+      @knife.ui.stub!(:stdout).and_return(stdout)
+
+      @knife.run
+
+      cb100_deleted.should be_true
+      cb110_deleted.should be_true
+      cb120_deleted.should be_true
+    end
+
+  end
+
+end
diff --git a/spec/functional/knife/exec_spec.rb b/spec/functional/knife/exec_spec.rb
new file mode 100644
index 0000000..455160f
--- /dev/null
+++ b/spec/functional/knife/exec_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tiny_server'
+
+describe Chef::Knife::Exec do
+  before(:all) do
+    @server = TinyServer::Manager.new#(:debug => true)
+    @server.start
+  end
+
+  before(:each) do
+    @knife = Chef::Knife::Exec.new
+    @api = TinyServer::API.instance
+    @api.clear
+
+    Chef::Config[:node_name] = nil
+    Chef::Config[:client_key] = nil
+    Chef::Config[:chef_server_url] = 'http://localhost:9000'
+
+    $output = StringIO.new
+  end
+
+  after(:all) do
+    @server.stop
+  end
+
+  pending "executes a script in the context of the chef-shell main context", :ruby_18_only
+
+  it "executes a script in the context of the chef-shell main context", :ruby_gte_19_only do
+    @node = Chef::Node.new
+    @node.name("ohai-world")
+    response = {"rows" => [@node],"start" => 0,"total" => 1}
+    @api.get(%r{^/search/node}, 200, response.to_json)
+    code = "$output.puts nodes.all"
+    @knife.config[:exec] = code
+    @knife.run
+    $output.string.should match(%r{node\[ohai-world\]})
+  end
+
+end
diff --git a/spec/functional/knife/smoke_test.rb b/spec/functional/knife/smoke_test.rb
new file mode 100644
index 0000000..6ccd462
--- /dev/null
+++ b/spec/functional/knife/smoke_test.rb
@@ -0,0 +1,34 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe "knife smoke tests" do
+
+  # Since our specs load all code, there could be a case where knife does not
+  # run correctly b/c of a missing require, but is not caught by other tests.
+  #
+  # We run `knife -v` to verify that knife at least loads all its code.
+  it "can run and print its version" do
+    knife_path = File.expand_path("../../bin/knife", CHEF_SPEC_DATA)
+    knife_cmd = Mixlib::ShellOut.new("#{knife_path} -v")
+    knife_cmd.run_command
+    knife_cmd.error!
+    knife_cmd.stdout.should include(Chef::VERSION)
+  end
+end
diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb
new file mode 100644
index 0000000..fd8cfe0
--- /dev/null
+++ b/spec/functional/knife/ssh_spec.rb
@@ -0,0 +1,268 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tiny_server'
+
+describe Chef::Knife::Ssh do
+
+  before(:all) do
+    Chef::Knife::Ssh.load_deps
+    @server = TinyServer::Manager.new
+    @server.start
+  end
+
+  after(:all) do
+    @server.stop
+  end
+
+  describe "identity file" do
+    context "when knife[:ssh_identity_file] is set" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa"
+      end
+
+      it "uses the ssh_identity_file" do
+        @knife.run
+        @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+      end
+    end
+
+    context "when knife[:ssh_identity_file] is set and frozen" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/aws.rsa".freeze
+      end
+
+      it "uses the ssh_identity_file" do
+        @knife.run
+        @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+      end
+    end
+
+    context "when -i is provided" do
+      before do
+        setup_knife(['-i ~/.ssh/aws.rsa', '*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_identity_file] = nil
+      end
+
+      it "should use the value on the command line" do
+        @knife.run
+        @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+      end
+
+      it "should override what is set in knife.rb" do
+        Chef::Config[:knife][:ssh_identity_file] = "~/.ssh/other.rsa"
+        @knife.run
+        @knife.config[:identity_file].should == "~/.ssh/aws.rsa"
+      end
+    end
+
+    context "when knife[:ssh_identity_file] is not provided]" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_identity_file] = nil
+      end
+
+      it "uses the default" do
+        @knife.run
+        @knife.config[:identity_file].should == nil
+      end
+    end
+  end
+
+  describe "port" do
+    context "when -p 31337 is provided" do
+      before do
+        setup_knife(['-p 31337', '*:*', 'uptime'])
+      end
+
+      it "uses the ssh_port" do
+        @knife.run
+        @knife.config[:ssh_port].should == "31337"
+      end
+    end
+  end
+
+  describe "user" do
+    context "when knife[:ssh_user] is set" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_user] = "ubuntu"
+      end
+
+      it "uses the ssh_user" do
+        @knife.run
+        @knife.config[:ssh_user].should == "ubuntu"
+      end
+    end
+
+    context "when knife[:ssh_user] is set and frozen" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_user] = "ubuntu".freeze
+      end
+
+      it "uses the ssh_user" do
+        @knife.run
+        @knife.config[:ssh_user].should == "ubuntu"
+      end
+    end
+
+    context "when -x is provided" do
+      before do
+        setup_knife(['-x ubuntu', '*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_user] = nil
+      end
+
+      it "should use the value on the command line" do
+        @knife.run
+        @knife.config[:ssh_user].should == "ubuntu"
+      end
+
+      it "should override what is set in knife.rb" do
+        Chef::Config[:knife][:ssh_user] = "root"
+        @knife.run
+        @knife.config[:ssh_user].should == "ubuntu"
+      end
+    end
+
+    context "when knife[:ssh_user] is not provided]" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_user] = nil
+      end
+
+      it "uses the default (current user)" do
+        @knife.run
+        @knife.config[:ssh_user].should == nil
+      end
+    end
+  end
+
+  describe "attribute" do
+    context "when knife[:ssh_attribute] is set" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_attribute] = "ec2.public_hostname"
+      end
+
+      it "uses the ssh_attribute" do
+        @knife.run
+        @knife.config[:attribute].should == "ec2.public_hostname"
+      end
+    end
+
+    context "when knife[:ssh_attribute] is not provided]" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_attribute] = nil
+      end
+
+      it "uses the default" do
+        @knife.run
+        @knife.config[:attribute].should == "fqdn"
+      end
+    end
+
+    context "when -a ec2.public_ipv4 is provided" do
+      before do
+        setup_knife(['-a ec2.public_hostname', '*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_attribute] = nil
+      end
+
+      it "should use the value on the command line" do
+        @knife.run
+        @knife.config[:attribute].should == "ec2.public_hostname"
+      end
+
+      it "should override what is set in knife.rb" do
+        # This is the setting imported from knife.rb
+        Chef::Config[:knife][:ssh_attribute] = "fqdn"
+        # Then we run knife with the -a flag, which sets the above variable
+        setup_knife(['-a ec2.public_hostname', '*:*', 'uptime'])
+        @knife.run
+        @knife.config[:attribute].should == "ec2.public_hostname"
+      end
+    end
+  end
+
+  describe "gateway" do
+    context "when knife[:ssh_gateway] is set" do
+      before do
+        setup_knife(['*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_gateway] = "user at ec2.public_hostname"
+      end
+
+      it "uses the ssh_gateway" do
+        @knife.session.should_receive(:via).with("ec2.public_hostname", "user", {})
+        @knife.run
+        @knife.config[:ssh_gateway].should == "user at ec2.public_hostname"
+      end
+    end
+
+    context "when -G user at ec2.public_hostname is provided" do
+      before do
+        setup_knife(['-G user at ec2.public_hostname', '*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_gateway] = nil
+      end
+
+      it "uses the ssh_gateway" do
+        @knife.session.should_receive(:via).with("ec2.public_hostname", "user", {})
+        @knife.run
+        @knife.config[:ssh_gateway].should == "user at ec2.public_hostname"
+      end
+    end
+
+    context "when the gateway requires a password" do
+      before do
+        setup_knife(['-G user at ec2.public_hostname', '*:*', 'uptime'])
+        Chef::Config[:knife][:ssh_gateway] = nil
+        @knife.session.stub(:via) do |host, user, options|
+          raise Net::SSH::AuthenticationFailed unless options[:password]
+        end
+      end
+
+      it "should prompt the user for a password" do
+        @knife.ui.should_receive(:ask).with("Enter the password for user at ec2.public_hostname: ").and_return("password")
+        @knife.run
+      end
+    end
+  end
+
+  def setup_knife(params=[])
+    @knife = Chef::Knife::Ssh.new(params)
+    # We explicitly avoid running #configure_chef, which would read a knife.rb
+    # if available, but #merge_configs (which is called by #configure_chef) is
+    # necessary to have default options merged in.
+    @knife.merge_configs
+    @knife.stub!(:ssh_command).and_return { 0 }
+    @api = TinyServer::API.instance
+    @api.clear
+
+    Chef::Config[:node_name] = nil
+    Chef::Config[:client_key] = nil
+    Chef::Config[:chef_server_url] = 'http://localhost:9000'
+
+    @api.get("/search/node?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000", 200) {
+      %({"total":1, "start":0, "rows":[{"name":"i-xxxxxxxx", "json_class":"Chef::Node", "automatic":{"fqdn":"the.fqdn", "ec2":{"public_hostname":"the_public_hostname"}},"recipes":[]}]})
+    }
+  end
+
+end
diff --git a/spec/functional/provider/remote_file/cache_control_data_spec.rb b/spec/functional/provider/remote_file/cache_control_data_spec.rb
new file mode 100755
index 0000000..63a4578
--- /dev/null
+++ b/spec/functional/provider/remote_file/cache_control_data_spec.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'uri'
+
+describe Chef::Provider::RemoteFile::CacheControlData do
+
+  before do
+    @original_config = Chef::Config.hash_dup    
+  end
+
+  after do
+    Chef::Config.configuration = @original_config if @original_config    
+  end
+  
+  before(:each) do
+    Chef::Config[:file_cache_path] = Dir.mktmpdir
+  end
+
+  after(:each) do
+    FileUtils.rm_rf(Chef::Config[:file_cache_path])
+  end
+
+  let(:uri) { URI.parse("http://www.bing.com/robots.txt") }
+  
+  describe "when the cache control data save method is invoked" do
+
+    subject(:cache_control_data) do
+      Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum)
+    end
+
+    # the checksum of the file last we fetched it.
+    let(:file_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }
+
+    let(:etag) { "\"a-strong-identifier\"" }
+    let(:mtime) { "Thu, 01 Aug 2013 08:16:32 GMT" }
+
+    before do
+      cache_control_data.etag = etag
+      cache_control_data.mtime = mtime
+      cache_control_data.checksum = file_checksum
+    end
+
+    it "writes data to the cache" do
+      cache_control_data.save
+    end
+
+    it "writes the data to the cache and the same data can be read back" do
+      cache_control_data.save
+      saved_cache_control_data = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum)
+      saved_cache_control_data.etag.should == cache_control_data.etag
+      saved_cache_control_data.mtime.should == cache_control_data.mtime
+      saved_cache_control_data.checksum.should == cache_control_data.checksum  
+    end
+
+    # Cover the very long remote file path case -- see CHEF-4422 where
+    # local cache file names generated from the long uri exceeded
+    # local file system path limits resulting in exceptions from
+    # file system API's on both Windows and Unix systems.
+    context "when the length of the uri exceeds the path length limits for the local file system" do
+      let(:uri_exceeds_file_system_limit) do
+        URI.parse("http://www.bing.com/" + ('0' * 1024))
+      end
+
+      let(:uri) { uri_exceeds_file_system_limit }
+
+      it "writes data to the cache" do
+        lambda do
+          cache_control_data.save
+        end.should_not raise_error
+      end
+
+      it "writes the data to the cache and the same data can be read back" do
+        cache_control_data.save
+        saved_cache_control_data = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum)
+        saved_cache_control_data.etag.should == cache_control_data.etag
+        saved_cache_control_data.mtime.should == cache_control_data.mtime
+        saved_cache_control_data.checksum.should == cache_control_data.checksum  
+      end
+
+    end
+  end
+
+end
+
diff --git a/spec/functional/resource/base.rb b/spec/functional/resource/base.rb
new file mode 100644
index 0000000..13438c1
--- /dev/null
+++ b/spec/functional/resource/base.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+def ohai
+  # provider is platform-dependent, we need platform ohai data:
+  @OHAI_SYSTEM ||= begin
+    ohai = Ohai::System.new
+    ohai.require_plugin("os")
+    ohai.require_plugin("platform")
+    ohai.require_plugin("passwd")
+    ohai
+  end
+end
+
+def run_context
+  @run_context ||= begin
+    node = Chef::Node.new
+    node.default[:platform] = ohai[:platform]
+    node.default[:platform_version] = ohai[:platform_version]
+    events = Chef::EventDispatch::Dispatcher.new
+    Chef::RunContext.new(node, {}, events)
+  end
+end
+
diff --git a/spec/functional/resource/batch_spec.rb b/spec/functional/resource/batch_spec.rb
new file mode 100644
index 0000000..baa01ee
--- /dev/null
+++ b/spec/functional/resource/batch_spec.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::WindowsScript::Batch, :windows_only do
+  include_context Chef::Resource::WindowsScript
+
+  let(:script_content) { "whoami" }
+
+  let!(:resource) do
+    Chef::Resource::WindowsScript::Batch.new("Batch resource functional test", @run_context)
+  end
+
+  describe "when the run action is invoked on Windows" do
+    it "executes the script code" do
+      resource.code(script_content + " > #{script_output_path}")
+      resource.returns(0)
+      resource.run_action(:run)
+    end
+  end
+end
diff --git a/spec/functional/resource/bff_spec.rb b/spec/functional/resource/bff_spec.rb
new file mode 100644
index 0000000..5e7cb8a
--- /dev/null
+++ b/spec/functional/resource/bff_spec.rb
@@ -0,0 +1,122 @@
+#
+# Author:: Prabhu Das (<prabhu.das at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'functional/resource/base'
+require 'chef/mixin/shell_out'
+
+# Run the test only for AIX platform.
+describe Chef::Resource::BffPackage, :external => ohai[:platform] != 'aix' do
+  include Chef::Mixin::ShellOut
+
+  let(:new_resource) do
+    new_resource = Chef::Resource::BffPackage.new(@pkg_name, run_context)
+    new_resource.source @pkg_path
+    new_resource
+  end
+
+  def bff_pkg_should_be_installed(resource)
+    expect(shell_out("lslpp -L #{resource.name}").exitstatus).to eq(0)
+    ::File.exists?("/usr/PkgA/bin/acommand")
+  end
+
+  def bff_pkg_should_be_removed(resource)
+    expect(shell_out("lslpp -L #{resource.name}").exitstatus).to eq(1)
+    !::File.exists?("/usr/PkgA/bin/acommand")
+  end
+
+
+  before(:all) do
+    @pkg_name = "PkgA.rte"
+    @pkg_path = "/tmp/PkgA.1.0.0.0.bff"
+    FileUtils.cp 'spec/functional/assets/PkgA.1.0.0.0.bff' , @pkg_path
+  end
+
+  after(:all) do
+    FileUtils.rm @pkg_path
+  end
+
+  context "package install action" do
+    it "should install a package" do
+      new_resource.run_action(:install)
+      bff_pkg_should_be_installed(new_resource)
+    end
+
+    after(:each) do
+      shell_out("installp -u #{@pkg_name}")
+    end
+  end
+
+  context "package install action with options" do
+    it "should install a package" do
+      new_resource.options("-e/tmp/installp.log")
+      new_resource.run_action(:install)
+      bff_pkg_should_be_installed(new_resource)
+    end
+
+    after(:each) do
+      shell_out("installp -u #{@pkg_name}")
+      FileUtils.rm "/tmp/installp.log"
+    end
+  end
+
+  context "package upgrade action" do
+    before(:each) do
+      shell_out("installp -aYF -d #{@pkg_path} #{@pkg_name}")
+      @pkg_path = "/tmp/PkgA.2.0.0.0.bff"
+      FileUtils.cp 'spec/functional/assets/PkgA.2.0.0.0.bff' , @pkg_path
+    end
+
+    it "should upgrade package" do
+      new_resource.run_action(:install)
+      bff_pkg_should_be_installed(new_resource)
+    end
+
+    after(:each) do
+      shell_out("installp -u #{@pkg_name}")
+      FileUtils.rm @pkg_path
+    end
+  end
+
+  context "package remove action" do
+    before(:each) do
+      shell_out("installp -aYF -d #{@pkg_path} #{@pkg_name}")
+    end
+
+    it "should remove an installed package" do
+      new_resource.run_action(:remove)
+      bff_pkg_should_be_removed(new_resource)
+    end
+  end
+
+  context "package remove action with options" do
+    before(:each) do
+      shell_out("installp -aYF -d #{@pkg_path} #{@pkg_name}")
+    end
+
+    it "should remove an installed package" do
+      new_resource.options("-e/tmp/installp.log")
+      new_resource.run_action(:remove)
+      bff_pkg_should_be_removed(new_resource)
+    end
+
+    after(:each) do
+      FileUtils.rm "/tmp/installp.log"
+    end
+  end
+end
+
diff --git a/spec/functional/resource/cookbook_file_spec.rb b/spec/functional/resource/cookbook_file_spec.rb
new file mode 100644
index 0000000..173dac8
--- /dev/null
+++ b/spec/functional/resource/cookbook_file_spec.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::CookbookFile do
+  include_context Chef::Resource::File
+
+  let(:file_base) { 'cookbook_file_spec' }
+  let(:source) { 'java.response' }
+  let(:cookbook_name) { 'java' }
+  let(:expected_content) do
+    content = File.open(File.join(CHEF_SPEC_DATA, 'cookbooks', 'java', 'files', 'default', 'java.response'), "rb") do |f|
+      f.read
+    end
+    content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding)
+    content
+  end
+
+  let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+
+  it_behaves_like "a securable resource with reporting"
+
+  def create_resource
+    # set up cookbook collection for this run to use, based on our
+    # spec data.
+    cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, 'cookbooks'))
+    Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_repo) }
+    loader = Chef::CookbookLoader.new(cookbook_repo)
+    loader.load_cookbooks
+    cookbook_collection = Chef::CookbookCollection.new(loader)
+
+    node = Chef::Node.new
+    events = Chef::EventDispatch::Dispatcher.new
+    run_context = Chef::RunContext.new(node, cookbook_collection, events)
+    resource = Chef::Resource::CookbookFile.new(path, run_context)
+    resource.cookbook(cookbook_name)
+    resource.source(source)
+
+    resource
+  end
+
+  let(:resource) do
+    create_resource
+  end
+
+  it_behaves_like "a file resource"
+
+  # These examples cover CHEF-3467 where unexpected and incorrect
+  # permissions can result on Windows because CookbookFile's
+  # implementation
+  # stages files in temp.
+  context "targets a file outside of the system temp directory" do
+    let(:windows_non_temp_dir) { File.join(ENV['systemdrive'], make_tmpname(file_base, "non-temp")) }
+    let(:path) { File.join(windows_non_temp_dir, make_tmpname(file_base)) }
+
+    before do
+      FileUtils::mkdir_p(windows_non_temp_dir) if Chef::Platform.windows?
+    end
+
+    after do
+      FileUtils.rm_r(windows_non_temp_dir) if Chef::Platform.windows? && File.exists?(windows_non_temp_dir)
+    end
+
+  end
+end
diff --git a/spec/functional/resource/cron_spec.rb b/spec/functional/resource/cron_spec.rb
new file mode 100644
index 0000000..4c20023
--- /dev/null
+++ b/spec/functional/resource/cron_spec.rb
@@ -0,0 +1,147 @@
+# encoding: UTF-8
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'functional/resource/base'
+require 'chef/mixin/shell_out'
+
+describe Chef::Resource::Cron, :requires_root, :unix_only do
+
+  include Chef::Mixin::ShellOut
+
+  # Platform specific validation routines.
+  def cron_should_exists(cron_name, command)
+    case ohai[:platform]
+    when "aix", "solaris", "opensolaris", "solaris2", "omnios"
+      expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(0)
+      expect(shell_out("crontab -l #{new_resource.user} | grep \"#{command}\"").exitstatus).to eq(0)
+    else
+      expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(0)
+      expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{command}\"").exitstatus).to eq(0)
+    end
+  end
+
+  def cron_should_not_exists(cron_name)
+    case ohai[:platform]
+    when "aix", "solaris", "opensolaris", "solaris2", "omnios"
+      expect(shell_out("crontab -l #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(1)
+    else
+      expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{cron_name}\"").exitstatus).to eq(1)
+    end
+  end
+
+  # Actual tests
+  let(:new_resource) do
+    new_resource = Chef::Resource::Cron.new("Chef functional test cron", run_context)
+    new_resource.user  'root'
+    new_resource.minute "30"
+    new_resource.command "/bin/true"
+    new_resource
+  end
+
+  let(:provider) do
+    provider = new_resource.provider_for_action(new_resource.action)
+    provider
+  end
+
+  describe "create action" do
+    after do
+      new_resource.run_action(:delete)
+    end
+
+    it "should create a crontab entry" do
+      new_resource.run_action(:create)
+      cron_should_exists(new_resource.name, new_resource.command)
+    end
+  end
+
+  describe "delete action" do
+    before do
+      new_resource.run_action(:create)
+    end
+
+    it "should delete a crontab entry" do
+      # Note that test cron is created by previous test
+      new_resource.run_action(:delete)
+
+      cron_should_not_exists(new_resource.name)
+    end
+  end
+
+  exclude_solaris = ["solaris", "opensolaris", "solaris2", "omnios"].include?(ohai[:platform])
+  describe "create action with various attributes", :external => exclude_solaris do
+    def create_and_validate_with_attribute(resource, attribute, value)
+      if ohai[:platform] == 'aix'
+         expect {resource.run_action(:create)}.to raise_error(Chef::Exceptions::Cron, /Aix cron entry does not support environment variables. Please set them in script and use script in cron./)
+      else
+        resource.run_action(:create)
+        # Verify if the cron is created successfully
+        cron_attribute_should_exists(resource.name, attribute, value)
+      end
+    end
+
+    def cron_attribute_should_exists(cron_name, attribute, value)
+      return if ['aix', 'solaris'].include?(ohai[:platform])
+      # Test if the attribute exists on newly created cron
+      cron_should_exists(cron_name, "")
+      expect(shell_out("crontab -l -u #{new_resource.user} | grep \"#{attribute.upcase}=#{value}\"").exitstatus).to eq(0)
+    end
+
+    after do
+      new_resource.run_action(:delete)
+    end
+
+    it "should create a crontab entry for mailto attribute" do
+      new_resource.mailto "cheftest at example.com"
+      create_and_validate_with_attribute(new_resource, "mailto", "cheftest at example.com")
+    end
+
+    it "should create a crontab entry for path attribute" do
+      new_resource.path "/usr/local/bin"
+      create_and_validate_with_attribute(new_resource, "path", "/usr/local/bin")
+    end
+
+    it "should create a crontab entry for shell attribute" do
+      new_resource.shell "/bin/bash"
+      create_and_validate_with_attribute(new_resource, "shell", "/bin/bash")
+    end
+
+    it "should create a crontab entry for home attribute" do
+      new_resource.home "/home/opscode"
+      create_and_validate_with_attribute(new_resource, "home", "/home/opscode")
+    end
+  end
+
+  describe "negative tests for create action" do
+    def cron_create_should_raise_exception
+      expect { new_resource.run_action(:create) }.to raise_error(Chef::Exceptions::Cron, /Error updating state of #{new_resource.name}, exit: 1/)
+      cron_should_not_exists(new_resource.name)
+    end
+
+    it "should not create cron with invalid minute" do
+      new_resource.minute "invalid"
+      cron_create_should_raise_exception
+    end
+
+    it "should not create cron with invalid user" do
+      new_resource.user "1-really-really-invalid-user-name"
+      cron_create_should_raise_exception
+    end
+
+  end
+end
diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb
new file mode 100644
index 0000000..14e6e69
--- /dev/null
+++ b/spec/functional/resource/deploy_revision_spec.rb
@@ -0,0 +1,695 @@
+#
+# Author:: Daniel DeLeo (<dan 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 'tmpdir'
+
+# Deploy relies heavily on symlinks, so it doesn't work on windows.
+describe Chef::Resource::DeployRevision, :unix_only => true do
+
+  let(:file_cache_path) { Dir.mktmpdir }
+  let(:deploy_directory) { Dir.mktmpdir }
+
+  # By making restart or other operations write to this file, we can externally
+  # track the order in which those operations happened.
+  let(:observe_order_file) { Tempfile.new("deploy-resource-observe-operations") }
+
+  before do
+    Chef::Log.level = :info
+    @old_file_cache_path = Chef::Config[:file_cache_path]
+    Chef::Config[:file_cache_path] = file_cache_path
+  end
+
+  after do
+    Chef::Config[:file_cache_path] = @old_file_cache_path
+    FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory)
+    FileUtils.remove_entry_secure file_cache_path
+    observe_order_file.close
+    FileUtils.remove_entry_secure observe_order_file.path
+  end
+
+  before(:all) do
+    @ohai = Ohai::System.new
+    @ohai.require_plugin("os")
+  end
+
+  let(:node) do
+
+    Chef::Node.new.tap do |n|
+      n.name "rspec-test"
+      n.consume_external_attrs(@ohai.data, {})
+    end
+  end
+
+  let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) }
+
+
+  # These tests use git's bundle feature, which is a way to export an entire
+  # git repo (or subset of commits) as a single file.
+  #
+  # Generally you can treat a git bundle as a regular git remote.
+  #
+  # See also: http://git-scm.com/2010/03/10/bundles.html
+  let(:git_bundle_repo) { File.expand_path("git_bundles/sinatra-test-app.gitbundle", CHEF_SPEC_DATA) }
+
+  let(:git_bundle_with_in_repo_callbacks) { File.expand_path("git_bundles/sinatra-test-app-with-callback-files.gitbundle", CHEF_SPEC_DATA) }
+
+  let(:git_bundle_with_in_repo_symlinks) { File.expand_path("git_bundles/sinatra-test-app-with-symlinks.gitbundle", CHEF_SPEC_DATA) }
+
+  # This is the fourth version
+  let(:latest_rev) { "3eb5ca6c353c83d9179dd3b29347539829b401f3" }
+
+  # This is the third version
+  let(:previous_rev) { "6d19a6dbecc8e37f5b2277345885c0c783eb8fb1" }
+
+  # This is the sixth version, it is on the "with-deploy-scripts" branch
+  let(:rev_with_in_repo_callbacks) { "2404d015882659754bdb93ad6e4b4d3d02691a82" }
+
+  # This is the fifth version in the "with-symlinks" branch
+  let(:rev_with_in_repo_symlinks) { "5a4748c52aaea8250b4346a9b8ede95ee3755e28" }
+
+  # Read values from the +observe_order_file+ and split each line. This way you
+  # can see in which order things really happened.
+  def actual_operations_order
+    IO.read(observe_order_file.path).split("\n").map(&:strip)
+  end
+
+  # 1. touch `restart.txt` in cwd so we know that the command is run with the
+  #    right cwd.
+  # 2. Append +tag+ to the `observe_order_file` so we can check the order in
+  #    which operations happen later in the test.
+  def shell_restart_command(tag)
+    "touch restart.txt && echo '#{tag}' >> #{observe_order_file.path}"
+  end
+
+  let(:basic_deploy_resource) do
+    Chef::Resource::DeployRevision.new(deploy_directory, run_context).tap do |r|
+      r.repo git_bundle_repo
+      r.symlink_before_migrate({})
+      r.symlinks({})
+    end
+  end
+
+  let(:deploy_to_latest_rev) do
+    basic_deploy_resource.dup.tap do |r|
+      r.revision(latest_rev)
+      r.restart_command shell_restart_command(:deploy_to_latest_rev)
+    end
+  end
+
+  let(:deploy_to_previous_rev) do
+    basic_deploy_resource.dup.tap do |r|
+      r.revision(previous_rev)
+      r.restart_command shell_restart_command(:deploy_to_previous_rev)
+    end
+  end
+
+  let(:deploy_to_latest_rev_again) do
+    basic_deploy_resource.dup.tap do |r|
+      r.revision(latest_rev)
+      r.restart_command shell_restart_command(:deploy_to_latest_rev_again)
+    end
+  end
+
+  # Computes the full path for +path+ relative to the deploy directory
+  def rel_path(path)
+    File.expand_path(path, deploy_directory)
+  end
+
+  def actual_current_rev
+    Dir.chdir(rel_path("current")) do
+      `git rev-parse HEAD`.strip
+    end
+  end
+
+  def self.the_app_is_deployed_at_revision(target_rev_spec)
+    it "deploys the app to the target revision (#{target_rev_spec})" do
+      target_rev = send(target_rev_spec)
+
+      File.should exist(rel_path("current"))
+
+      actual_current_rev.should == target_rev
+
+      # Is the app code actually there?
+      File.should exist(rel_path("current/app/app.rb"))
+    end
+  end
+
+  context "when deploying a simple app" do
+    describe "for the first time, with the required directory layout precreated" do
+      before do
+        FileUtils.mkdir_p(rel_path("releases"))
+        FileUtils.mkdir_p(rel_path("shared"))
+        deploy_to_latest_rev.run_action(:deploy)
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+      it "restarts the application" do
+        File.should exist(rel_path("current/restart.txt"))
+        actual_operations_order.should == %w[deploy_to_latest_rev]
+      end
+
+      it "is marked as updated" do
+        deploy_to_latest_rev.should be_updated_by_last_action
+      end
+    end
+
+    describe "back to a previously deployed revision, with the directory structure precreated" do
+      before do
+        FileUtils.mkdir_p(rel_path("releases"))
+        FileUtils.mkdir_p(rel_path("shared"))
+
+        deploy_to_latest_rev.run_action(:deploy)
+        deploy_to_previous_rev.run_action(:deploy)
+        deploy_to_latest_rev_again.run_action(:deploy)
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+      it "restarts the application after rolling back" do
+        actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again]
+      end
+
+      it "is marked updated" do
+        deploy_to_latest_rev_again.should be_updated_by_last_action
+      end
+
+      it "deploys the right code" do
+        IO.read(rel_path("current/app/app.rb")).should include("this is the fourth version of the app")
+      end
+    end
+
+    describe "for the first time, with no existing directory layout" do
+      before do
+        deploy_to_latest_rev.run_action(:deploy)
+      end
+
+      it "creates the required directory tree" do
+        File.should be_directory(rel_path("releases"))
+        File.should be_directory(rel_path("shared"))
+        File.should be_directory(rel_path("releases/#{latest_rev}"))
+
+        File.should be_directory(rel_path("current/tmp"))
+        File.should be_directory(rel_path("current/config"))
+        File.should be_directory(rel_path("current/public"))
+
+        File.should be_symlink(rel_path("current"))
+        File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}")
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+      it "restarts the application" do
+        File.should exist(rel_path("current/restart.txt"))
+        actual_operations_order.should == %w[deploy_to_latest_rev]
+      end
+
+      it "is marked as updated" do
+        deploy_to_latest_rev.should be_updated_by_last_action
+      end
+    end
+
+    describe "again to the current revision" do
+      before do
+        deploy_to_latest_rev.run_action(:deploy)
+        deploy_to_latest_rev.run_action(:deploy)
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+      it "does not restart the app" do
+        actual_operations_order.should == %w[deploy_to_latest_rev]
+      end
+
+      it "is not marked updated" do
+        deploy_to_latest_rev.should_not be_updated_by_last_action
+      end
+
+    end
+
+    describe "again with force_deploy" do
+      before do
+        deploy_to_latest_rev.run_action(:force_deploy)
+        deploy_to_latest_rev_again.run_action(:force_deploy)
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+      it "restarts the app" do
+        actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_latest_rev_again]
+      end
+
+      it "is marked updated" do
+        deploy_to_latest_rev.should be_updated_by_last_action
+      end
+
+    end
+
+    describe "again to a new revision" do
+      before do
+        deploy_to_previous_rev.run_action(:deploy)
+        deploy_to_latest_rev.run_action(:deploy)
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+      it "restarts the application after the new deploy" do
+        actual_operations_order.should == %w[deploy_to_previous_rev deploy_to_latest_rev]
+      end
+
+      it "is marked updated" do
+        deploy_to_previous_rev.should be_updated_by_last_action
+      end
+
+      it "leaves the old copy of the app around for rollback" do
+        File.should exist(File.join(deploy_directory, "releases", previous_rev))
+      end
+
+    end
+
+    describe "back to a previously deployed revision (implicit rollback)" do
+      before do
+        deploy_to_latest_rev.run_action(:deploy)
+        deploy_to_previous_rev.run_action(:deploy)
+        deploy_to_latest_rev_again.run_action(:deploy)
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+      it "restarts the application after rolling back" do
+        actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again]
+      end
+
+      it "is marked updated" do
+        deploy_to_latest_rev_again.should be_updated_by_last_action
+      end
+
+      it "deploys the right code" do
+        IO.read(rel_path("current/app/app.rb")).should include("this is the fourth version of the app")
+      end
+    end
+
+    # CHEF-3435
+    describe "to a deploy_to path that does not yet exist" do
+
+      let(:top_level_tmpdir) { Dir.mktmpdir }
+
+      # override top level deploy_directory let block with one that is two
+      # directories deeper
+      let(:deploy_directory) { File.expand_path("nested/deeper", top_level_tmpdir) }
+
+      after do
+        FileUtils.remove_entry_secure top_level_tmpdir
+      end
+
+      before do
+        File.should_not exist(deploy_directory)
+        deploy_to_latest_rev.run_action(:deploy)
+      end
+
+      it "creates the required directory tree" do
+        File.should be_directory(rel_path("releases"))
+        File.should be_directory(rel_path("shared"))
+        File.should be_directory(rel_path("releases/#{latest_rev}"))
+
+        File.should be_directory(rel_path("current/tmp"))
+        File.should be_directory(rel_path("current/config"))
+        File.should be_directory(rel_path("current/public"))
+
+        File.should be_symlink(rel_path("current"))
+        File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}")
+      end
+
+      the_app_is_deployed_at_revision(:latest_rev)
+
+    end
+  end
+
+  context "when deploying an app with inline recipe callbacks" do
+
+    # Use closures to capture and mutate this variable. This allows us to track
+    # ordering of operations.
+    callback_order = []
+
+    let(:deploy_to_latest_with_inline_recipes) do
+      deploy_to_latest_rev.dup.tap do |r|
+        r.symlink_before_migrate "config/config.ru" => "config.ru"
+        r.before_migrate do
+          callback_order << :before_migrate
+
+          file "#{release_path}/before_migrate.txt" do
+            # The content here isn't relevant, but it gets printed when running
+            # the tests. Could be handy for debugging.
+            content callback_order.inspect
+          end
+        end
+        r.before_symlink do
+          callback_order << :before_symlink
+
+          current_release_path = release_path
+          ruby_block "ensure before symlink" do
+            block do
+              if ::File.exist?(::File.join(current_release_path, "/tmp"))
+                raise "Ordering issue with provider, expected symlinks to not have been created"
+              end
+            end
+          end
+
+          file "#{release_path}/before_symlink.txt" do
+            content callback_order.inspect
+          end
+        end
+        r.before_restart do
+          callback_order << :before_restart
+
+          current_release_path = release_path
+          ruby_block "ensure after symlink" do
+            block do
+              unless ::File.exist?(::File.join(current_release_path, "/tmp"))
+                raise "Ordering issue with provider, expected symlinks to have been created"
+              end
+            end
+          end
+
+          file "#{release_path}/tmp/before_restart.txt" do
+            content callback_order.inspect
+          end
+        end
+        r.after_restart do
+          callback_order << :after_restart
+          file "#{release_path}/tmp/after_restart.txt" do
+            content callback_order.inspect
+          end
+        end
+      end
+    end
+
+    before do
+      callback_order.clear # callback_order variable is global for this context group
+      deploy_to_latest_with_inline_recipes.run_action(:deploy)
+    end
+
+    the_app_is_deployed_at_revision(:latest_rev)
+
+    it "is marked updated" do
+      deploy_to_latest_with_inline_recipes.should be_updated_by_last_action
+    end
+
+    it "calls the callbacks in order" do
+      callback_order.should == [:before_migrate, :before_symlink, :before_restart, :after_restart]
+    end
+
+    it "runs chef resources in the callbacks" do
+      File.should exist(rel_path("current/before_migrate.txt"))
+      File.should exist(rel_path("current/before_symlink.txt"))
+      File.should exist(rel_path("current/tmp/before_restart.txt"))
+      File.should exist(rel_path("current/tmp/after_restart.txt"))
+    end
+  end
+
+  context "when deploying an app with in-repo callback scripts" do
+    let(:deploy_with_in_repo_callbacks) do
+      basic_deploy_resource.dup.tap do |r|
+        r.repo git_bundle_with_in_repo_callbacks
+        r.revision rev_with_in_repo_callbacks
+      end
+    end
+
+    before do
+      deploy_with_in_repo_callbacks.run_action(:deploy)
+    end
+
+    the_app_is_deployed_at_revision(:rev_with_in_repo_callbacks)
+
+    it "runs chef resources in the callbacks" do
+      File.should exist(rel_path("current/before_migrate.txt"))
+      File.should exist(rel_path("current/before_symlink.txt"))
+      File.should exist(rel_path("current/tmp/before_restart.txt"))
+      File.should exist(rel_path("current/tmp/after_restart.txt"))
+    end
+
+  end
+
+  context "when deploying an app with migrations" do
+    let(:deploy_with_migration) do
+      basic_deploy_resource.dup.tap do |r|
+
+        # Need this so we can call methods from this test inside the inline
+        # recipe callbacks
+        spec_context = self
+
+        r.revision latest_rev
+
+        # enable migrations
+        r.migrate true
+        # abuse `shell_restart_command` so we can observe order of when the
+        # miration command gets run
+        r.migration_command shell_restart_command("migration")
+        r.before_migrate do
+
+          # inline recipe callbacks don't cwd, so you have to get the release
+          # directory as a local and "capture" it in the closure.
+          current_release = release_path
+          execute spec_context.shell_restart_command("before_migrate") do
+            cwd current_release
+          end
+        end
+        r.before_symlink do
+          current_release = release_path
+          execute spec_context.shell_restart_command("before_symlink") do
+            cwd current_release
+          end
+        end
+
+        r.before_restart do
+          current_release = release_path
+          execute spec_context.shell_restart_command("before_restart") do
+            cwd current_release
+          end
+        end
+
+        r.after_restart do
+          current_release = release_path
+          execute spec_context.shell_restart_command("after_restart") do
+            cwd current_release
+          end
+        end
+
+      end
+    end
+
+    before do
+      deploy_with_migration.run_action(:deploy)
+    end
+
+    it "runs migrations in between the before_migrate and before_symlink steps" do
+      actual_operations_order.should == %w[before_migrate migration before_symlink before_restart after_restart]
+    end
+  end
+
+  context "when deploying an app with in-repo symlinks" do
+    let(:deploy_with_in_repo_symlinks) do
+      basic_deploy_resource.dup.tap do |r|
+        r.repo git_bundle_with_in_repo_symlinks
+        r.revision rev_with_in_repo_symlinks
+      end
+    end
+
+    it "should not raise an exception calling File.utime on symlinks" do
+      lambda { deploy_with_in_repo_symlinks.run_action(:deploy) }.should_not raise_error
+    end
+  end
+
+  context "when a previously deployed application has been nuked" do
+
+    shared_examples_for "a redeployed application" do
+
+      it "should redeploy the application" do
+        File.should be_directory(rel_path("releases"))
+        File.should be_directory(rel_path("shared"))
+        File.should be_directory(rel_path("releases/#{latest_rev}"))
+
+        File.should be_directory(rel_path("current/tmp"))
+        File.should be_directory(rel_path("current/config"))
+        File.should be_directory(rel_path("current/public"))
+
+        File.should be_symlink(rel_path("current"))
+        File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}")
+      end
+    end
+
+    # background: If a deployment is hosed and the user decides to rm -rf the
+    # deployment dir, deploy resource should detect that and nullify its cache.
+
+    context "by removing the entire deploy directory" do
+
+      before do
+        deploy_to_latest_rev.dup.run_action(:deploy)
+        FileUtils.rm_rf(deploy_directory)
+        deploy_to_latest_rev.dup.run_action(:deploy)
+      end
+
+      include_examples "a redeployed application"
+
+    end
+
+    context "by removing the current/ directory" do
+
+      before do
+        deploy_to_latest_rev.dup.run_action(:deploy)
+        FileUtils.rm(rel_path("current"))
+        deploy_to_latest_rev.dup.run_action(:deploy)
+      end
+
+      include_examples "a redeployed application"
+
+    end
+  end
+
+  context "when a deployment fails" do
+
+    shared_examples_for "a recovered deployment" do
+
+      it "should redeploy the application" do
+        File.should be_directory(rel_path("releases"))
+        File.should be_directory(rel_path("shared"))
+        File.should be_directory(rel_path("releases/#{latest_rev}"))
+
+        File.should be_directory(rel_path("current/tmp"))
+        File.should be_directory(rel_path("current/config"))
+        File.should be_directory(rel_path("current/public"))
+
+        File.should be_symlink(rel_path("current"))
+        File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}")
+
+        # if callbacks ran, we know the app was deployed and not merely rolled
+        # back to a (busted) prior deployment.
+        callback_order.should == [:before_migrate,
+                                  :before_symlink,
+                                  :before_restart,
+                                  :after_restart ]
+      end
+    end
+
+    let!(:callback_order) { [] }
+
+    let(:deploy_to_latest_with_callback_tracking) do
+      resource = deploy_to_latest_rev.dup
+      tracker = callback_order
+      resource.before_migrate { tracker << :before_migrate }
+      resource.before_symlink { tracker << :before_symlink }
+      resource.before_restart { tracker << :before_restart }
+      resource.after_restart  { tracker << :after_restart }
+      resource
+    end
+
+    [:before_migrate, :before_symlink, :before_restart, :after_restart].each do |callback|
+
+      context "in the `#{callback}' callback" do
+        before do
+          lambda { deploy_that_fails.run_action(:deploy) }.should raise_error(Exception, %r{I am a failed deploy})
+          deploy_to_latest_with_callback_tracking.run_action(:deploy)
+        end
+
+
+        let(:deploy_that_fails) do
+          resource = deploy_to_latest_rev.dup
+          errant_callback = lambda {|x| raise Exception, "I am a failed deploy" }
+          resource.send(callback, &errant_callback)
+          resource
+        end
+
+        include_examples "a recovered deployment"
+
+      end
+
+    end
+
+    context "in the service restart step" do
+
+      let(:deploy_that_fails) do
+        resource = deploy_to_latest_rev.dup
+        resource.restart_command("RUBYOPT=\"\" ruby -e 'exit 1'")
+        resource
+      end
+
+      before do
+        lambda { deploy_that_fails.run_action(:deploy) }.should raise_error(Chef::Exceptions::Exec)
+        deploy_to_latest_with_callback_tracking.run_action(:deploy)
+      end
+
+      include_examples "a recovered deployment"
+    end
+
+    context "when cloning the app code" do
+
+      class BadTimeScmProvider
+        def initialize(new_resource, run_context)
+        end
+
+        def load_current_resource
+        end
+
+        def revision_slug
+          "5"
+        end
+
+        def run_action(action)
+          raise RuntimeError, "network error"
+        end
+      end
+
+      let(:deploy_that_fails) do
+        resource = deploy_to_latest_rev.dup
+        resource.scm_provider(BadTimeScmProvider)
+        resource
+      end
+
+      before do
+        lambda { deploy_that_fails.run_action(:deploy) }.should raise_error(RuntimeError, /network error/)
+        deploy_to_latest_with_callback_tracking.run_action(:deploy)
+      end
+
+      include_examples "a recovered deployment"
+    end
+
+    context "and then is deployed to a different revision" do
+
+      let(:deploy_that_fails) do
+        resource = deploy_to_previous_rev.dup
+        resource.after_restart {|x| raise Exception, "I am a failed deploy" }
+        resource
+      end
+
+      before do
+        lambda { deploy_that_fails.run_action(:deploy) }.should raise_error(Exception, %r{I am a failed deploy})
+        deploy_to_latest_rev.run_action(:deploy)
+      end
+
+      it "removes the unsuccessful deploy after a later successful deploy" do
+        ::File.should_not exist(File.join(deploy_directory, "releases", previous_rev))
+      end
+
+    end
+
+  end
+end
+
+
diff --git a/spec/functional/resource/directory_spec.rb b/spec/functional/resource/directory_spec.rb
new file mode 100644
index 0000000..2c4025f
--- /dev/null
+++ b/spec/functional/resource/directory_spec.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::Directory do
+  include_context Chef::Resource::Directory
+
+  let(:directory_base) { "directory_spec" }
+
+  let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) }
+
+  def create_resource
+    events = Chef::EventDispatch::Dispatcher.new
+    node = Chef::Node.new
+    run_context = Chef::RunContext.new(node, {}, events)
+    Chef::Resource::Directory.new(path, run_context)
+  end
+
+  let(:resource) do
+  	create_resource
+  end
+
+  it_behaves_like "a directory resource"
+
+  it_behaves_like "a securable resource with reporting"
+
+end
diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb
new file mode 100644
index 0000000..f688bae
--- /dev/null
+++ b/spec/functional/resource/file_spec.rb
@@ -0,0 +1,119 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::File do
+  include_context Chef::Resource::File
+
+  let(:file_base) { "file_spec" }
+  let(:expected_content) { "Don't fear the ruby." }
+
+  def create_resource
+    events = Chef::EventDispatch::Dispatcher.new
+    node = Chef::Node.new
+    run_context = Chef::RunContext.new(node, {}, events)
+    resource = Chef::Resource::File.new(path, run_context)
+    resource
+  end
+
+  let(:resource) do
+    r = create_resource
+    r.content(expected_content)
+    r
+  end
+
+  let(:resource_without_content) do
+    create_resource
+  end
+
+  let(:unmanaged_content) do
+    "This is file content that is not managed by chef"
+  end
+
+  let(:current_resource) do
+    provider = resource.provider_for_action(resource.action)
+    provider.load_current_resource
+    provider.current_resource
+  end
+
+  let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+
+  it_behaves_like "a file resource"
+
+  it_behaves_like "a securable resource with reporting"
+
+  describe "when running action :create without content" do
+    before do
+      resource_without_content.run_action(:create)
+    end
+
+    context "and the target file does not exist" do
+      it "creates the file" do
+        File.should exist(path)
+      end
+
+      it "is marked updated by last action" do
+        resource_without_content.should be_updated_by_last_action
+      end
+    end
+  end
+
+  describe "when running action :touch" do
+    context "and the target file does not exist" do
+      before do
+        resource.run_action(:touch)
+      end
+
+      it "it creates the file" do
+        File.should exist(path)
+      end
+
+      it "is marked updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+    end
+
+    context "and the target file exists and has the correct content" do
+      before(:each) do
+        File.open(path, "w") { |f| f.print expected_content }
+
+        @expected_checksum = sha256_checksum(path)
+
+        now = Time.now.to_i
+        File.utime(now - 9000, now - 9000, path)
+        @expected_mtime = File.stat(path).mtime
+
+        resource.run_action(:touch)
+      end
+
+      it "updates the mtime of the file" do
+        File.stat(path).mtime.should > @expected_mtime
+      end
+
+      it "does not change the content" do
+        sha256_checksum(path).should == @expected_checksum
+      end
+
+      it "is marked as updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+    end
+  end
+
+end
diff --git a/spec/functional/resource/git_spec.rb b/spec/functional/resource/git_spec.rb
new file mode 100644
index 0000000..7ade6ee
--- /dev/null
+++ b/spec/functional/resource/git_spec.rb
@@ -0,0 +1,259 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/shell_out'
+require 'tmpdir'
+require 'shellwords'
+
+# Deploy relies heavily on symlinks, so it doesn't work on windows.
+describe Chef::Resource::Git do
+  include Chef::Mixin::ShellOut
+  let(:file_cache_path) { Dir.mktmpdir }
+  # Some versions of git complains when the deploy directory is
+  # already created. Here we intentionally don't create the deploy
+  # directory beforehand.
+  let(:base_dir_path) { Dir.mktmpdir }
+  let(:deploy_directory) { File.join(base_dir_path, make_tmpname("git_base")) }
+
+  let(:node) do
+    Chef::Node.new.tap do |n|
+      n.name "rspec-test"
+      n.consume_external_attrs(@ohai.data, {})
+    end
+  end
+
+  let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new }
+  let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) }
+
+  # These tests use git's bundle feature, which is a way to export an entire
+  # git repo (or subset of commits) as a single file.
+  #
+  # Generally you can treat a git bundle as a regular git remote.
+  #
+  # See also: http://git-scm.com/2010/03/10/bundles.html
+  #
+  # Beware that git bundles don't behave exactly the same as real
+  # remotes. To get closer to real remotes, we'll create a local clone
+  # of the bundle to use as a remote for the tests. This at least
+  # gives the expected responses for ls-remote using git version
+  # 1.7.12.4
+  let(:git_bundle_repo) { File.expand_path("git_bundles/example-repo.gitbundle", CHEF_SPEC_DATA) }
+  let(:origin_repo_dir) { Dir.mktmpdir }
+  let(:origin_repo) { "#{origin_repo_dir}/example" }
+
+  # This is the fourth version
+  let(:v1_commit) { "bc5ec79931ae74089aeadca6edc173527613e6d9" }
+  let(:v1_tag) { "9b73fb5e316bfaff7b822b0ccb3e1e08f9885085" }
+  let(:rev_foo) { "ed181b3419b6f489bedab282348162a110d6d3a1" }
+  let(:rev_testing) { "972d153654503bccec29f630c5dd369854a561e8" }
+  let(:rev_head) { "d294fbfd05aa7709ad9a9b8ef6343b17d355bf5f"}
+
+  let(:git_user_config) do
+    <<-E
+[user]
+  name = frodoTbaggins
+  email = frodo at shire.org
+E
+  end
+
+  before(:each) do
+    Chef::Log.level = :warn # silence git command live streams
+    @old_file_cache_path = Chef::Config[:file_cache_path]
+    shell_out!("git clone \"#{git_bundle_repo}\" example", :cwd => origin_repo_dir)
+    File.open("#{origin_repo}/.git/config", "a+") {|f| f.print(git_user_config) }
+    Chef::Config[:file_cache_path] = file_cache_path
+  end
+
+  after(:each) do
+    Chef::Config[:file_cache_path] = @old_file_cache_path
+    FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory)
+    FileUtils.remove_entry_secure file_cache_path
+  end
+
+  after(:all) do
+    FileUtils.remove_entry_secure origin_repo_dir
+  end
+
+  before(:all) do
+    @ohai = Ohai::System.new
+    @ohai.require_plugin("os")
+  end
+
+  context "working with pathes with special characters" do
+    let(:path_with_spaces) { "#{origin_repo_dir}/path with spaces" }
+
+    before(:each) do
+      FileUtils.mkdir(path_with_spaces)
+      FileUtils.cp(git_bundle_repo, path_with_spaces)
+    end
+
+    it "clones a repository with a space in the path" do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository "#{path_with_spaces}/example-repo.gitbundle"
+      end.run_action(:sync)
+    end
+  end
+
+  context "when deploying from an annotated tag" do
+    let(:basic_git_resource) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository origin_repo
+        r.revision "v1.0.0"
+      end
+    end
+
+    # We create a copy of the basic_git_resource so that we can run
+    # the resource again and verify that it doesn't update.
+    let(:copy_git_resource) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository origin_repo
+        r.revision "v1.0.0"
+      end
+    end
+
+    it "checks out the revision pointed to by the tag commit, not the tag commit itself" do
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      head_rev.should == v1_commit
+      # also verify the tag commit itself is what we expect as an extra sanity check
+      rev = shell_out!('git rev-parse v1.0.0', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      rev.should == v1_tag
+    end
+
+    it "doesn't update if up-to-date" do
+      # this used to fail because we didn't resolve the annotated tag
+      # properly to the pointed to commit.
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      head_rev.should == v1_commit
+
+      copy_git_resource.run_action(:sync)
+      copy_git_resource.should_not be_updated
+    end
+  end
+
+  context "when deploying from a SHA revision" do
+    let(:basic_git_resource) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository git_bundle_repo
+      end
+    end
+
+    # We create a copy of the basic_git_resource so that we can run
+    # the resource again and verify that it doesn't update.
+    let(:copy_git_resource) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository origin_repo
+      end
+    end
+
+    it "checks out the expected revision ed18" do
+      basic_git_resource.revision rev_foo
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      head_rev.should == rev_foo
+    end
+
+    it "doesn't update if up-to-date" do
+      basic_git_resource.revision rev_foo
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      head_rev.should == rev_foo
+
+      copy_git_resource.revision rev_foo
+      copy_git_resource.run_action(:sync)
+      copy_git_resource.should_not be_updated
+    end
+
+    it "checks out the expected revision 972d" do
+      basic_git_resource.revision rev_testing
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      head_rev.should == rev_testing
+    end
+  end
+
+  context "when deploying from a revision named 'HEAD'" do
+    let(:basic_git_resource) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository origin_repo
+        r.revision 'HEAD'
+      end
+    end
+
+    it "checks out the expected revision" do
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      head_rev.should == rev_head
+    end
+  end
+
+  context "when deploying from the default revision" do
+    let(:basic_git_resource) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository origin_repo
+        # use default
+      end
+    end
+
+    it "checks out HEAD as the default revision" do
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+      head_rev.should == rev_head
+    end
+  end
+
+  context "when dealing with a repo with a degenerate tag named 'HEAD'" do
+    before do
+      shell_out!("git tag -m\"degenerate tag\" HEAD ed181b3419b6f489bedab282348162a110d6d3a1",
+                 :cwd => origin_repo)
+    end
+
+    let(:basic_git_resource) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository origin_repo
+        r.revision 'HEAD'
+      end
+    end
+
+    let(:git_resource_default_rev) do
+      Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+        r.repository origin_repo
+        # use default of revision
+      end
+    end
+
+    it "checks out the (master) HEAD revision and ignores the tag" do
+      basic_git_resource.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD',
+                            :cwd => deploy_directory,
+                            :returns => [0]).stdout.strip
+      head_rev.should == rev_head
+    end
+
+    it "checks out the (master) HEAD revision when no revision is specified (ignores tag)" do
+      git_resource_default_rev.run_action(:sync)
+      head_rev = shell_out!('git rev-parse HEAD',
+                            :cwd => deploy_directory,
+                            :returns => [0]).stdout.strip
+      head_rev.should == rev_head
+    end
+
+  end
+end
diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb
new file mode 100644
index 0000000..2c9a568
--- /dev/null
+++ b/spec/functional/resource/group_spec.rb
@@ -0,0 +1,204 @@
+#
+# Author:: Chirag Jog (<chirag at clogeny.com>)
+# Author:: Siddheshwar More (<siddheshwar.more at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'functional/resource/base'
+
+describe Chef::Resource::Group, :requires_root_or_running_windows do
+
+  def group_should_exist(resource)
+    case ohai[:platform_family]
+    when "debian", "fedora", "rhel", "suse", "gentoo", "slackware", "arch"
+      expect { Etc::getgrnam(resource.name) }.to_not raise_error(ArgumentError, "can't find group for #{resource.name}")
+      expect(resource.name).to eq(Etc::getgrnam(resource.name).name)
+    when "windows"
+      expect { Chef::Util::Windows::NetGroup.new(resource.group_name).local_get_members }.to_not raise_error(ArgumentError, "The group name could not be found.")
+    end
+  end
+
+  def user_exist_in_group?(resource, user)
+    case ohai[:platform_family]
+    when "debian", "fedora", "rhel", "suse", "gentoo", "slackware", "arch"
+      Etc::getgrnam(resource.name).mem.include?(user)
+    when "windows"
+      Chef::Util::Windows::NetGroup.new(resource.group_name).local_get_members.include?(user)
+    end
+  end
+
+  def group_should_not_exist(resource)
+    case ohai[:platform_family]
+    when "debian", "fedora", "rhel", "suse", "gentoo", "slackware", "arch"
+      expect { Etc::getgrnam(resource.name) }.to raise_error(ArgumentError, "can't find group for #{resource.name}")
+    when "windows"
+      expect { Chef::Util::Windows::NetGroup.new(resource.group_name).local_get_members }.to raise_error(ArgumentError, "The group name could not be found.")
+    end
+  end
+
+  def compare_gid(resource, gid)
+    return resource.gid == Etc::getgrnam(resource.name).gid if unix?
+  end
+
+  def get_user_resource(username)
+    usr = Chef::Resource::User.new("#{username}", run_context)
+    usr.password("Jetsream123!")
+    usr
+  end
+
+  def create_user(username)
+    get_user_resource(username).run_action(:create)
+  end
+
+  def remove_user(username)
+    get_user_resource(username).run_action(:remove)
+  end
+
+  before do
+    @grp_resource = Chef::Resource::Group.new("test-group-#{SecureRandom.random_number(9999)}", run_context)
+  end
+
+  context "group create action" do
+    after(:each) do
+      @grp_resource.run_action(:remove)
+    end
+
+    it "create a group" do
+      @grp_resource.run_action(:create)
+      group_should_exist(@grp_resource)
+    end
+
+    context "group name with 256 characters", :windows_only do
+      before(:each) do
+        grp_name = "theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestree"
+        @new_grp = Chef::Resource::Group.new(grp_name, run_context)
+      end
+      after do
+        @new_grp.run_action(:remove)
+      end
+      it " create a group" do
+        @new_grp.run_action(:create)
+        group_should_exist(@new_grp)
+      end
+    end
+    context "group name with more than 256 characters", :windows_only do
+      before(:each) do
+        grp_name = "theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQQQQQQQQQQQQ"
+        @new_grp = Chef::Resource::Group.new(grp_name, run_context)
+      end
+      it " not create a group" do
+        expect { @new_grp.run_action(:create) }.to raise_error
+        group_should_not_exist(@new_grp)
+      end
+    end
+  end
+
+  context "group remove action" do
+    before(:each) do
+      @grp_resource.run_action(:create)
+    end
+
+    it "remove a group" do
+      @grp_resource.run_action(:remove)
+      group_should_not_exist(@grp_resource)
+    end
+  end
+
+  context "group modify action", :unsupported_group_provider_platform do
+    before(:each) do
+      @grp_resource.run_action(:create)
+    end
+
+    after(:each) do
+      @grp_resource.run_action(:remove)
+    end
+
+    it "add user to group" do
+      user1 = "user1-#{SecureRandom.random_number(9999)}"
+      user2 = "user2-#{SecureRandom.random_number(9999)}"
+
+      create_user(user1)
+      @grp_resource.members(user1)
+      expect(user_exist_in_group?(@grp_resource, user1)).to be_false
+      @grp_resource.run_action(:modify)
+      group_should_exist(@grp_resource)
+      expect(user_exist_in_group?(@grp_resource, user1)).to be_true
+
+      create_user(user2)
+      expect(user_exist_in_group?(@grp_resource, user2)).to be_false
+      @grp_resource.members(user2)
+      @grp_resource.run_action(:modify)
+      group_should_exist(@grp_resource)
+
+      #default append is false, so modify action remove old member user1 from group and add new member user2
+      expect(user_exist_in_group?(@grp_resource, user1)).to be_false
+      expect(user_exist_in_group?(@grp_resource, user2)).to be_true
+      remove_user(user1)
+      remove_user(user2)
+    end
+
+
+    it "append user to a group" do
+      user1 = "user1-#{SecureRandom.random_number(9999)}"
+      user2 = "user2-#{SecureRandom.random_number(9999)}"
+      create_user(user1)
+      @grp_resource.members(user1)
+      expect(user_exist_in_group?(@grp_resource, user1)).to be_false
+      #default append attribute is false
+      @grp_resource.run_action(:modify)
+      group_should_exist(@grp_resource)
+      expect(user_exist_in_group?(@grp_resource, user1)).to be_true
+      #set append attribute to true
+      @grp_resource.append(true)
+      create_user(user2)
+      expect(user_exist_in_group?(@grp_resource, user2)).to be_false
+      @grp_resource.members(user2)
+      @grp_resource.run_action(:modify)
+      group_should_exist(@grp_resource)
+      expect(user_exist_in_group?(@grp_resource, user1)).to be_true
+      expect(user_exist_in_group?(@grp_resource, user2)).to be_true
+      remove_user(user1)
+      remove_user(user2)
+    end
+
+    it "raise error on add non-existent user to group" do
+      user1 = "user1-#{SecureRandom.random_number(9999)}"
+      @grp_resource.members(user1)
+      @grp_resource.append(true)
+      expect(user_exist_in_group?(@grp_resource, user1)).to be_false
+      expect { @grp_resource.run_action(:modify) }.to raise_error
+    end
+  end
+
+  context "group manage action", :unix_only, :unsupported_group_provider_platform do
+    before(:each) do
+      @grp_resource.run_action(:create)
+    end
+
+    after(:each) do
+      @grp_resource.run_action(:remove)
+    end
+
+    it "change gid of the group" do
+      grp_id = 1234567890
+      @grp_resource.gid(grp_id)
+      @grp_resource.run_action(:manage)
+      group_should_exist(@grp_resource)
+      expect(compare_gid(@grp_resource, grp_id)).to be_true
+    end
+  end
+end
diff --git a/spec/functional/resource/ifconfig_spec.rb b/spec/functional/resource/ifconfig_spec.rb
new file mode 100644
index 0000000..c362884
--- /dev/null
+++ b/spec/functional/resource/ifconfig_spec.rb
@@ -0,0 +1,163 @@
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'functional/resource/base'
+require 'chef/mixin/shell_out'
+
+# run this test only for following platforms.
+include_flag = !(['ubuntu', 'centos', 'aix'].include?(ohai[:platform]))
+
+describe Chef::Resource::Ifconfig, :requires_root, :external => include_flag do
+  include Chef::Mixin::ShellOut
+
+  let(:new_resource) do
+    new_resource = Chef::Resource::Ifconfig.new('10.10.0.1', run_context)
+    new_resource
+  end
+
+  let(:provider) do
+    provider = new_resource.provider_for_action(new_resource.action)
+    provider
+  end
+
+  let(:current_resource) do
+    provider.load_current_resource
+  end
+
+  def lo_interface_for_test
+    # use loopback interface for tests
+    case ohai[:platform]
+    when "aix"
+      'lo0'
+    else
+      'lo'
+    end
+  end
+
+  # **Caution: any updates to core interfaces can be risky.
+  def en0_interface_for_test
+    case ohai[:platform]
+    when "aix"
+      'en0'
+    else
+      'eth0'
+    end
+  end
+
+  def network_interface_alias(interface)
+    case ohai[:platform]
+    when "aix"
+      interface
+    else
+      interface + ":10"
+    end
+  end
+
+  # platform specific test setup and validation routines
+
+  def setup_add_interface(resource)
+    resource.device network_interface_alias(en0_interface_for_test)
+  end
+
+  def setup_enable_interface(resource)
+    resource.device network_interface_alias(en0_interface_for_test)
+  end
+
+  def interface_should_exists(interface)
+    expect(shell_out("ifconfig #{@interface} | grep 10.10.0.1").exitstatus).to eq(0)
+  end
+
+  def interface_should_not_exists(interface)
+    expect(shell_out("ifconfig #{@interface} | grep 10.10.0.1").exitstatus).to eq(1)
+  end
+
+  def interface_persistence_should_exists(interface)
+    case ohai[:platform]
+    when "aix"
+      expect(shell_out("lsattr -E -l #{@interface} | grep 10.10.0.1").exitstatus).to eq(0)
+    else
+    end
+  end
+
+  def interface_persistence_should_not_exists(interface)
+    case ohai[:platform]
+    when "aix"
+      expect(shell_out("lsattr -E -l #{@interface} | grep 10.10.0.1").exitstatus).to eq(1)
+    else
+    end
+  end
+
+  # Actual tests
+
+  describe "#load_current_resource" do
+    it 'should load given interface' do
+      new_resource.device lo_interface_for_test
+      expect(current_resource.device).to eql(lo_interface_for_test)
+      expect(current_resource.inet_addr).to match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)
+    end
+  end
+
+  exclude_test = ohai[:platform] != 'ubuntu'
+  describe "#action_add", :external => exclude_test do
+    after do
+      new_resource.run_action(:delete)
+    end
+    it "should add interface (vip)" do
+      setup_add_interface(new_resource)
+      new_resource.run_action(:add)
+      interface_should_exists(network_interface_alias(en0_interface_for_test))
+      interface_persistence_should_exists(network_interface_alias(en0_interface_for_test))
+    end
+  end
+
+  describe "#action_enable", :external => exclude_test do
+    after do
+      new_resource.run_action(:disable)
+    end
+    it "should enable interface (vip)" do
+      setup_enable_interface(new_resource)
+      new_resource.run_action(:enable)
+      interface_should_exists(network_interface_alias(en0_interface_for_test))
+    end
+  end
+
+  describe "#action_disable", :external => exclude_test do
+    before do
+      setup_enable_interface(new_resource)
+      new_resource.run_action(:enable)
+    end
+    it "should disable interface (vip)" do
+      new_resource.run_action(:disable)
+      new_resource.should be_updated_by_last_action
+      interface_should_not_exists(network_interface_alias(en0_interface_for_test))
+    end
+  end
+
+  describe "#action_delete", :external => exclude_test do
+    before do
+      setup_add_interface(new_resource)
+      new_resource.run_action(:add)
+    end
+    it "should delete interface (vip)" do
+      new_resource.run_action(:delete)
+      new_resource.should be_updated_by_last_action
+      interface_should_not_exists(network_interface_alias(en0_interface_for_test))
+      interface_persistence_should_not_exists(network_interface_alias(en0_interface_for_test))
+    end
+  end
+end
diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb
new file mode 100644
index 0000000..36bc858
--- /dev/null
+++ b/spec/functional/resource/link_spec.rb
@@ -0,0 +1,608 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2011 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'
+
+if windows?
+  require 'chef/win32/file' #probably need this in spec_helper
+end
+
+describe Chef::Resource::Link do
+  let(:file_base) { "file_spec" }
+
+  let(:expect_updated?) {true}
+
+  # We create the files in a different directory than tmp to exercise
+  # different file deployment strategies more completely.
+  let(:test_file_dir) do
+    if windows?
+      File.join(ENV['systemdrive'], "test-dir")
+    else
+      File.join(CHEF_SPEC_DATA, "test-dir")
+    end
+  end
+
+  before do
+    FileUtils::mkdir_p(test_file_dir)
+  end
+
+  after do
+    FileUtils::rm_rf(test_file_dir)
+  end
+
+  let(:to) do
+    File.join(test_file_dir, make_tmpname("to_spec"))
+  end
+  let(:target_file) do
+    File.join(test_file_dir, make_tmpname("from_spec"))
+  end
+
+  after(:each) do
+    begin
+      cleanup_link(to) if File.exists?(to)
+      cleanup_link(target_file) if File.exists?(target_file)
+      cleanup_link(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH)
+    rescue
+      puts "Could not remove a file: #{$!}"
+    end
+  end
+
+  def cleanup_link(path)
+    if windows? && File.directory?(path)
+      # If the link target is a directory rm_rf doesn't work all the
+      # time on windows.
+      system "rmdir '#{path}'"
+    else
+      FileUtils.rm_rf(path)
+    end
+  end
+
+  def canonicalize(path)
+    windows? ? path.gsub('/', '\\') : path
+  end
+
+  def symlink(a, b)
+    if windows?
+      Chef::ReservedNames::Win32::File.symlink(a, b)
+    else
+      File.symlink(a, b)
+    end
+  end
+  def symlink?(file)
+    if windows?
+      Chef::ReservedNames::Win32::File.symlink?(file)
+    else
+      File.symlink?(file)
+    end
+  end
+  def readlink(file)
+    if windows?
+      Chef::ReservedNames::Win32::File.readlink(file)
+    else
+      File.readlink(file)
+    end
+  end
+  def link(a, b)
+    if windows?
+      Chef::ReservedNames::Win32::File.link(a, b)
+    else
+      File.link(a, b)
+    end
+  end
+
+  def create_resource
+    node = Chef::Node.new
+    events = Chef::EventDispatch::Dispatcher.new
+    cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+    cookbook_collection = Chef::CookbookCollection.new(Chef::CookbookLoader.new(cookbook_repo))
+    run_context = Chef::RunContext.new(node, cookbook_collection, events)
+    resource = Chef::Resource::Link.new(target_file, run_context)
+    resource.to(to)
+    resource
+  end
+
+  let(:resource) do
+    create_resource
+  end
+
+  describe "when supported on platform", :not_supported_on_win2k3 do
+    shared_examples_for 'delete errors out' do
+      it 'delete errors out' do
+        lambda { resource.run_action(:delete) }.should raise_error(Chef::Exceptions::Link)
+        (File.exist?(target_file) || symlink?(target_file)).should be_true
+      end
+    end
+
+    shared_context 'delete is noop' do
+      describe 'the :delete action' do
+        before(:each) do
+          @info = []
+          Chef::Log.stub!(:info) { |msg| @info << msg }
+          resource.run_action(:delete)
+        end
+
+        it 'leaves the file deleted' do
+          File.exist?(target_file).should be_false
+          symlink?(target_file).should be_false
+        end
+        it 'does not mark the resource updated' do
+          resource.should_not be_updated
+        end
+        it 'does not log that it deleted' do
+          @info.include?("link[#{target_file}] deleted").should be_false
+        end
+      end
+    end
+
+    shared_context 'delete succeeds' do
+      describe 'the :delete action' do
+        before(:each) do
+          @info = []
+          Chef::Log.stub!(:info) { |msg| @info << msg }
+          resource.run_action(:delete)
+        end
+
+        it 'deletes the file' do
+          File.exist?(target_file).should be_false
+          symlink?(target_file).should be_false
+        end
+        it 'marks the resource updated' do
+          resource.should be_updated
+        end
+        it 'logs that it deleted' do
+          @info.include?("link[#{target_file}] deleted").should be_true
+        end
+      end
+    end
+
+    shared_context 'create symbolic link succeeds' do
+      describe 'the :create action' do
+        before(:each) do
+          @info = []
+          Chef::Log.stub!(:info) { |msg| @info << msg }
+          resource.run_action(:create)
+        end
+
+        it 'links to the target file' do
+          symlink?(target_file).should be_true
+          readlink(target_file).should == canonicalize(to)
+        end
+        it 'marks the resource updated' do
+          resource.should be_updated
+        end
+        it 'logs that it created' do
+          @info.include?("link[#{target_file}] created").should be_true
+        end
+      end
+    end
+
+    shared_context 'create symbolic link is noop' do
+      describe 'the :create action' do
+        before(:each) do
+          @info = []
+          Chef::Log.stub!(:info) { |msg| @info << msg }
+          resource.run_action(:create)
+        end
+
+        it 'leaves the file linked' do
+          symlink?(target_file).should be_true
+          readlink(target_file).should == canonicalize(to)
+        end
+        it 'does not mark the resource updated' do
+          resource.should_not be_updated
+        end
+        it 'does not log that it created' do
+          @info.include?("link[#{target_file}] created").should be_false
+        end
+      end
+    end
+
+    shared_context 'create hard link succeeds' do
+      describe 'the :create action' do
+        before(:each) do
+          @info = []
+          Chef::Log.stub!(:info) { |msg| @info << msg }
+          resource.run_action(:create)
+        end
+        it 'preserves the hard link' do
+          File.exists?(target_file).should be_true
+          symlink?(target_file).should be_false
+          # Writing to one hardlinked file should cause both
+          # to have the new value.
+          IO.read(to).should == IO.read(target_file)
+          File.open(to, "w") { |file| file.write('wowzers') }
+          IO.read(target_file).should == 'wowzers'
+        end
+        it 'marks the resource updated' do
+          resource.should be_updated
+        end
+        it 'logs that it created' do
+          @info.include?("link[#{target_file}] created").should be_true
+        end
+      end
+    end
+
+    shared_context 'create hard link is noop' do
+      describe 'the :create action' do
+        before(:each) do
+          @info = []
+          Chef::Log.stub!(:info) { |msg| @info << msg }
+          resource.run_action(:create)
+        end
+        it 'links to the target file' do
+          File.exists?(target_file).should be_true
+          symlink?(target_file).should be_false
+          # Writing to one hardlinked file should cause both
+          # to have the new value.
+          IO.read(to).should == IO.read(target_file)
+          File.open(to, "w") { |file| file.write('wowzers') }
+          IO.read(target_file).should == 'wowzers'
+        end
+        it 'does not mark the resource updated' do
+          resource.should_not be_updated
+        end
+        it 'does not log that it created' do
+          @info.include?("link[#{target_file}] created").should be_false
+        end
+      end
+    end
+
+    context "is symbolic" do
+
+      context 'when the link destination is a file' do
+        before(:each) do
+          File.open(to, "w") do |file|
+            file.write('woohoo')
+          end
+        end
+        context 'and the link does not yet exist' do
+          include_context 'create symbolic link succeeds'
+          include_context 'delete is noop'
+        end
+        context 'and the link already exists and is a symbolic link' do
+          context 'pointing at the target' do
+            before(:each) do
+              symlink(to, target_file)
+              symlink?(target_file).should be_true
+              readlink(target_file).should == canonicalize(to)
+            end
+            include_context 'create symbolic link is noop'
+            include_context 'delete succeeds'
+            it 'the :delete action does not delete the target file' do
+              resource.run_action(:delete)
+              File.exists?(to).should be_true
+            end
+          end
+          context 'pointing somewhere else' do
+            before(:each) do
+              @other_target = File.join(test_file_dir, make_tmpname('other_spec'))
+              File.open(@other_target, 'w') { |file| file.write('eek') }
+              symlink(@other_target, target_file)
+              symlink?(target_file).should be_true
+              readlink(target_file).should == canonicalize(@other_target)
+            end
+            after(:each) do
+              File.delete(@other_target)
+            end
+            include_context 'create symbolic link succeeds'
+            include_context 'delete succeeds'
+            it 'the :delete action does not delete the target file' do
+              resource.run_action(:delete)
+              File.exists?(to).should be_true
+            end
+          end
+          context 'pointing nowhere' do
+            before(:each) do
+              nonexistent = File.join(test_file_dir, make_tmpname('nonexistent_spec'))
+              symlink(nonexistent, target_file)
+              symlink?(target_file).should be_true
+              readlink(target_file).should == canonicalize(nonexistent)
+            end
+            include_context 'create symbolic link succeeds'
+            include_context 'delete succeeds'
+          end
+        end
+        context 'and the link already exists and is a hard link to the file' do
+          before(:each) do
+            link(to, target_file)
+            File.exists?(target_file).should be_true
+            symlink?(target_file).should be_false
+          end
+          include_context 'create symbolic link succeeds'
+          it_behaves_like 'delete errors out'
+        end
+        context 'and the link already exists and is a file' do
+          before(:each) do
+            File.open(target_file, 'w') { |file| file.write('eek') }
+          end
+          include_context 'create symbolic link succeeds'
+          it_behaves_like 'delete errors out'
+        end
+        context 'and the link already exists and is a directory' do
+          before(:each) do
+            Dir.mkdir(target_file)
+          end
+          it 'create errors out' do
+            if windows?
+              lambda { resource.run_action(:create) }.should raise_error(Errno::EACCES)
+            elsif os_x? or solaris? or freebsd? or aix?
+              lambda { resource.run_action(:create) }.should raise_error(Errno::EPERM)
+            else
+              lambda { resource.run_action(:create) }.should raise_error(Errno::EISDIR)
+            end
+          end
+          it_behaves_like 'delete errors out'
+        end
+        context 'and the link already exists and is not writeable to this user', :pending do
+        end
+        it_behaves_like 'a securable resource without existing target' do
+          let(:path) { target_file }
+          def allowed_acl(sid, expected_perms)
+            [ ACE.access_allowed(sid, expected_perms[:specific]) ]
+          end
+          def denied_acl(sid, expected_perms)
+            [ ACE.access_denied(sid, expected_perms[:specific]) ]
+          end
+          def parent_inheritable_acls
+            dummy_file_path = File.join(test_file_dir, "dummy_file")
+            dummy_file = FileUtils.touch(dummy_file_path)
+            dummy_desc = get_security_descriptor(dummy_file_path)
+            FileUtils.rm_rf(dummy_file_path)
+            dummy_desc
+          end
+        end
+      end
+      context 'when the link destination is a directory' do
+        before(:each) do
+          Dir.mkdir(to)
+        end
+        # On Windows, readlink fails to open the link.  FILE_FLAG_OPEN_REPARSE_POINT
+        # might help, from http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
+        context 'and the link does not yet exist' do
+          include_context 'create symbolic link succeeds'
+          include_context 'delete is noop'
+        end
+      end
+      context "when the link destination is a symbolic link" do
+        context 'to a file that exists' do
+          before(:each) do
+            @other_target = File.join(test_file_dir, make_tmpname("other_spec"))
+            File.open(@other_target, "w") { |file| file.write("eek") }
+            symlink(@other_target, to)
+            symlink?(to).should be_true
+            readlink(to).should == canonicalize(@other_target)
+          end
+          after(:each) do
+            File.delete(@other_target)
+          end
+          context 'and the link does not yet exist' do
+            include_context 'create symbolic link succeeds'
+            include_context 'delete is noop'
+          end
+        end
+        context 'to a file that does not exist' do
+          before(:each) do
+            @other_target = File.join(test_file_dir, make_tmpname("other_spec"))
+            symlink(@other_target, to)
+            symlink?(to).should be_true
+            readlink(to).should == canonicalize(@other_target)
+          end
+          context 'and the link does not yet exist' do
+            include_context 'create symbolic link succeeds'
+            include_context 'delete is noop'
+          end
+        end
+      end
+      context "when the link destination is not readable to this user", :pending do
+      end
+      context "when the link destination does not exist" do
+        include_context 'create symbolic link succeeds'
+        include_context 'delete is noop'
+      end
+
+      {
+        '../' => 'with a relative link destination',
+        '' => 'with a bare filename for the link destination'
+      }.each do |prefix, desc|
+        context desc do
+          let(:to) { "#{prefix}#{File.basename(absolute_to)}" }
+          let(:absolute_to) { File.join(test_file_dir, make_tmpname("to_spec")) }
+          before(:each) do
+            resource.to(to)
+          end
+          context 'when the link does not yet exist' do
+            include_context 'create symbolic link succeeds'
+            include_context 'delete is noop'
+          end
+          context 'when the link already exists and points at the target' do
+            before(:each) do
+              symlink(to, target_file)
+              symlink?(target_file).should be_true
+              readlink(target_file).should == canonicalize(to)
+            end
+            include_context 'create symbolic link is noop'
+            include_context 'delete succeeds'
+          end
+          context 'when the link already exists and points at the target with an absolute path' do
+            before(:each) do
+              symlink(absolute_to, target_file)
+              symlink?(target_file).should be_true
+              readlink(target_file).should == canonicalize(absolute_to)
+            end
+            include_context 'create symbolic link succeeds'
+            include_context 'delete succeeds'
+          end
+        end
+      end
+    end
+
+    context "is a hard link" do
+      before(:each) do
+        resource.link_type(:hard)
+      end
+
+      context "when the link destination is a file" do
+        before(:each) do
+          File.open(to, "w") do |file|
+            file.write('woohoo')
+          end
+        end
+        context "and the link does not yet exist" do
+          include_context 'create hard link succeeds'
+          include_context 'delete is noop'
+        end
+        context "and the link already exists and is a symbolic link pointing at the same file" do
+          before(:each) do
+            symlink(to, target_file)
+            symlink?(target_file).should be_true
+            readlink(target_file).should == canonicalize(to)
+          end
+          include_context 'create hard link succeeds'
+          it_behaves_like 'delete errors out'
+        end
+        context 'and the link already exists and is a hard link to the file' do
+          before(:each) do
+            link(to, target_file)
+            File.exists?(target_file).should be_true
+            symlink?(target_file).should be_false
+          end
+          include_context 'create hard link is noop'
+          include_context 'delete succeeds'
+          it 'the :delete action does not delete the target file' do
+            resource.run_action(:delete)
+            File.exists?(to).should be_true
+          end
+        end
+        context "and the link already exists and is a file" do
+          before(:each) do
+            File.open(target_file, 'w') { |file| file.write('tomfoolery') }
+          end
+          include_context 'create hard link succeeds'
+          it_behaves_like 'delete errors out'
+        end
+        context "and the link already exists and is a directory" do
+          before(:each) do
+            Dir.mkdir(target_file)
+          end
+          it 'errors out' do
+            if windows?
+              lambda { resource.run_action(:create) }.should raise_error(Errno::EACCES)
+            elsif os_x? or solaris? or freebsd? or aix?
+              lambda { resource.run_action(:create) }.should raise_error(Errno::EPERM)
+            else
+              lambda { resource.run_action(:create) }.should raise_error(Errno::EISDIR)
+            end
+          end
+          it_behaves_like 'delete errors out'
+        end
+        context "and the link already exists and is not writeable to this user", :pending do
+        end
+        context "and specifies security attributes" do
+          before(:each) do
+            resource.owner(windows? ? 'Guest' : 'nobody')
+          end
+          it 'ignores them' do
+            resource.run_action(:create)
+            if windows?
+              Chef::ReservedNames::Win32::Security.get_named_security_info(target_file).owner.should_not == SID.Guest
+            else
+              File.lstat(target_file).uid.should_not == Etc.getpwnam('nobody').uid
+            end
+          end
+        end
+      end
+      context "when the link destination is a directory" do
+        before(:each) do
+          Dir.mkdir(to)
+        end
+        context 'and the link does not yet exist' do
+          it 'create errors out' do
+            lambda { resource.run_action(:create) }.should raise_error(windows? ? Chef::Exceptions::Win32APIError : Errno::EPERM)
+          end
+          include_context 'delete is noop'
+        end
+      end
+      context "when the link destination is a symbolic link" do
+        context 'to a real file' do
+          before(:each) do
+            @other_target = File.join(test_file_dir, make_tmpname("other_spec"))
+            File.open(@other_target, "w") { |file| file.write("eek") }
+            symlink(@other_target, to)
+            symlink?(to).should be_true
+            readlink(to).should == canonicalize(@other_target)
+          end
+          after(:each) do
+            File.delete(@other_target)
+          end
+          context 'and the link does not yet exist' do
+            it 'links to the target file' do
+              resource.run_action(:create)
+              File.exists?(target_file).should be_true
+              # 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?)) do
+                symlink?(target_file).should be_true
+                readlink(target_file).should == canonicalize(@other_target)
+              end
+            end
+            include_context 'delete is noop'
+          end
+        end
+        context 'to a nonexistent file' do
+          before(:each) do
+            @other_target = File.join(test_file_dir, make_tmpname("other_spec"))
+            symlink(@other_target, to)
+            symlink?(to).should be_true
+            readlink(to).should == canonicalize(@other_target)
+          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?)) do
+                resource.run_action(:create)
+                # Windows and Unix have different definitions of exists? here, and that's OK.
+                if windows?
+                  File.exists?(target_file).should be_true
+                else
+                  File.exists?(target_file).should be_false
+                end
+                symlink?(target_file).should be_true
+                readlink(target_file).should == canonicalize(@other_target)
+              end
+            end
+            include_context 'delete is noop'
+          end
+        end
+      end
+      context "when the link destination is not readable to this user", :pending 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
+            lambda { resource.run_action(:create) }.should raise_error(Errno::ENOENT)
+          end
+          include_context 'delete is noop'
+        end
+      end
+    end
+  end
+
+  describe "when not supported on platform", :win2k3_only do
+    it "raises error" do
+      lambda {resource}.should raise_error(Chef::Exceptions::Win32APIFunctionNotImplemented)
+    end
+  end
+end
diff --git a/spec/functional/resource/mount_spec.rb b/spec/functional/resource/mount_spec.rb
new file mode 100644
index 0000000..199ccbd
--- /dev/null
+++ b/spec/functional/resource/mount_spec.rb
@@ -0,0 +1,207 @@
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'functional/resource/base'
+require 'chef/mixin/shell_out'
+require 'tmpdir'
+
+# run this test only for following platforms.
+include_flag = !(['ubuntu', 'centos', 'aix'].include?(ohai[:platform]))
+
+describe Chef::Resource::Mount, :requires_root, :external => include_flag do
+
+  include Chef::Mixin::ShellOut
+
+  # Platform specific setup, cleanup and validation helpers.
+
+  def setup_device_for_mount
+    # use ramdisk for creating a test device for mount.
+    # This can cleaner if we have chef resource/provider for ramdisk.
+    case ohai[:platform]
+    when "aix"
+      ramdisk = shell_out!("mkramdisk 16M").stdout
+
+      # identify device, for /dev/rramdisk0 it is /dev/ramdisk0
+      device = ramdisk.tr("\n","").gsub(/\/rramdisk/, '/ramdisk')
+
+      fstype = "jfs2"
+      shell_out!("mkfs  -V #{fstype} #{device}")
+    when "ubuntu", "centos"
+      device = "/dev/ram1"
+      shell_out("ls -1 /dev/ram*").stdout.each_line do |d|
+        if shell_out("mount | grep #{d}").exitstatus == "1"
+          # this device is not mounted, so use it.
+          device = d
+          break
+        end
+      end
+      fstype = "tmpfs"
+      shell_out!("mkfs -q #{device} 512")
+    else
+    end
+    [device, fstype]
+  end
+
+  def cleanup_device(device)
+    case ohai[:platform]
+    when "aix"
+      ramdisk = device.gsub(/\/ramdisk/, '/rramdisk')
+      shell_out("rmramdisk #{ramdisk}")
+    else
+    end
+  end
+
+  def cleanup_mount(mount_point)
+    shell_out("umount #{mount_point}")
+  end
+
+  # platform specific validations.
+  def mount_should_exists(mount_point, device, fstype = nil, options = nil)
+    validation_cmd = "mount | grep #{mount_point} | grep #{device} "
+    validation_cmd << " | grep #{fstype} " unless fstype.nil?
+    validation_cmd << " | grep #{options.join(',')} " unless options.nil? || options.empty?
+    puts "validation_cmd = #{validation_cmd}"
+    expect(shell_out(validation_cmd).exitstatus).to eq(0)
+  end
+
+  def mount_should_not_exists(mount_point)
+    shell_out("mount").stdout.should_not include(mount_point)
+  end
+
+  def unix_mount_config_file
+    case ohai[:platform]
+    when 'aix'
+      mount_config = "/etc/filesystems"
+    else
+      mount_config = "/etc/fstab"
+    end
+  end
+
+  def mount_should_be_enabled(mount_point, device)
+    case ohai[:platform]
+    when 'aix'
+      expect(shell_out("cat #{unix_mount_config_file} | grep \"#{mount_point}:\" ").exitstatus).to eq(0)
+    else
+      expect(shell_out("cat #{unix_mount_config_file} | grep \"#{mount_point}\" | grep \"#{device}\" ").exitstatus).to eq(0)
+    end
+  end
+
+  def mount_should_be_disabled(mount_point)
+    shell_out("cat #{unix_mount_config_file}").stdout.should_not include("#{mount_point}:")
+  end
+
+  let(:new_resource) do
+    new_resource = Chef::Resource::Mount.new(@mount_point, run_context)
+    new_resource.device      @device
+    new_resource.name        @mount_point
+    new_resource.fstype      @fstype
+    new_resource.options     "log=NULL" if ohai[:platform] == 'aix'
+    new_resource
+  end
+
+  let(:provider) do
+    provider = new_resource.provider_for_action(new_resource.action)
+    provider
+  end
+
+  def current_resource
+    provider.load_current_resource
+    provider.current_resource
+  end
+
+  # Actual tests begin here.
+  before(:all) do
+    @device, @fstype = setup_device_for_mount
+
+    @mount_point = Dir.mktmpdir("testmount")
+
+    # Make sure all the potentially leaked mounts are cleared up
+    shell_out("mount").stdout.each_line do |line|
+      if line.include? "testmount"
+        line.split(" ").each do |section|
+          cleanup_mount(section) if section.include? "testmount"
+        end
+      end
+    end
+
+  end
+
+  after(:all) do
+    Dir.rmdir(@mount_point)
+    cleanup_device(@device)
+  end
+
+  after(:each) do
+    cleanup_mount(new_resource.mount_point)
+  end
+
+  describe "when the target state is a mounted filesystem" do
+    it "should mount the filesystem if it isn't mounted" do
+      current_resource.enabled.should be_false
+      current_resource.mounted.should be_false
+      new_resource.run_action(:mount)
+      new_resource.should be_updated
+      mount_should_exists(new_resource.mount_point, new_resource.device)
+    end
+
+  end
+
+  describe "when the filesystem should be remounted and the resource supports remounting" do
+    it "should remount the filesystem if it is mounted" do
+      new_resource.run_action(:mount)
+      mount_should_exists(new_resource.mount_point, new_resource.device)
+
+      new_resource.supports[:remount] = true
+      new_resource.options "rw,log=NULL" if ohai[:platform] == 'aix'
+      new_resource.run_action(:remount)
+
+      mount_should_exists(new_resource.mount_point, new_resource.device, nil, (ohai[:platform] == 'aix') ? new_resource.options : nil)
+    end
+  end
+
+  describe "when the target state is a unmounted filesystem" do
+    it "should umount the filesystem if it is mounted" do
+      new_resource.run_action(:mount)
+      mount_should_exists(new_resource.mount_point, new_resource.device)
+
+      new_resource.run_action(:umount)
+      mount_should_not_exists(new_resource.mount_point)
+    end
+  end
+
+  describe "when enabling the filesystem to be mounted" do
+    after do
+      new_resource.run_action(:disable)
+    end
+
+    it "should enable the mount if it isn't enable" do
+      new_resource.run_action(:mount)
+      new_resource.run_action(:enable)
+      mount_should_be_enabled(new_resource.mount_point, new_resource.device)
+    end
+  end
+
+  describe "when the target state is to disable the mount" do
+    it "should disable the mount if it is enabled" do
+      new_resource.run_action(:mount)
+      new_resource.run_action(:enable)
+      new_resource.run_action(:disable)
+      mount_should_be_disabled(new_resource.mount_point)
+    end
+  end
+end
diff --git a/spec/functional/resource/package_spec.rb b/spec/functional/resource/package_spec.rb
new file mode 100644
index 0000000..5501127
--- /dev/null
+++ b/spec/functional/resource/package_spec.rb
@@ -0,0 +1,375 @@
+# encoding: UTF-8
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'webrick'
+
+module AptServer
+  def enable_testing_apt_source
+    File.open("/etc/apt/sources.list.d/chef-integration-test.list", "w+") do |f|
+      f.puts "deb http://localhost:9000/ sid main"
+    end
+    # Magic to update apt cache for only our repo
+    shell_out!("apt-get update " +
+               '-o Dir::Etc::sourcelist="sources.list.d/chef-integration-test.list" ' +
+               '-o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"')
+  end
+
+  def disable_testing_apt_source
+    FileUtils.rm("/etc/apt/sources.list.d/chef-integration-test.list")
+  rescue Errno::ENOENT
+    puts("Attempted to remove integration test from /etc/apt/sources.list.d but it didn't exist")
+  end
+
+  def tcp_test_port(hostname, port)
+    tcp_socket = TCPSocket.new(hostname, port)
+    true
+  rescue Errno::ETIMEDOUT
+    false
+  rescue Errno::ECONNREFUSED
+    false
+  ensure
+    tcp_socket && tcp_socket.close
+  end
+
+  def apt_server
+    @apt_server ||= WEBrick::HTTPServer.new(
+      :Port         => 9000,
+      :DocumentRoot => apt_data_dir + "/var/www/apt",
+      # Make WEBrick quiet, comment out for debug.
+      :Logger       => Logger.new(StringIO.new),
+      :AccessLog    => [ StringIO.new, WEBrick::AccessLog::COMMON_LOG_FORMAT ]
+    )
+  end
+
+  def run_apt_server
+    apt_server.start
+  end
+
+  def start_apt_server
+    @apt_server_thread = Thread.new do
+      run_apt_server
+    end
+    until tcp_test_port("localhost", 9000) do
+      if @apt_server_thread.alive?
+        sleep 1
+      else
+        @apt_server_thread.join
+        raise "apt server failed to start"
+      end
+    end
+  end
+
+  def stop_apt_server
+    apt_server.shutdown
+    @apt_server_thread.join
+  end
+
+  def apt_data_dir
+    File.join(CHEF_SPEC_DATA, "apt")
+  end
+end
+
+metadata = { :unix_only => true,
+  :requires_root => true,
+  :provider => {:package => Chef::Provider::Package::Apt},
+  :arch => "x86_64" # test packages are 64bit
+}
+
+describe Chef::Resource::Package, metadata do
+  include Chef::Mixin::ShellOut
+
+  context "with a remote package source" do
+
+    include AptServer
+
+    before(:all) do
+      # Disable mixlib-shellout live streams
+      Chef::Log.level = :warn
+      start_apt_server
+      enable_testing_apt_source
+    end
+
+    after(:all) do
+      stop_apt_server
+      disable_testing_apt_source
+      shell_out!("apt-get clean")
+    end
+
+
+    after do
+      shell_out!("dpkg -r chef-integration-test")
+      shell_out("dpkg --clear-avail")
+      shell_out!("apt-get clean")
+    end
+
+    let(:node) do
+      n = Chef::Node.new
+      n.consume_external_attrs(OHAI_SYSTEM.data.dup, {})
+      n
+    end
+
+    let(:events) do
+      Chef::EventDispatch::Dispatcher.new
+    end
+
+    # TODO: lots of duplication from client.rb;
+    # All of this must be setup for preseed files to get found
+    let(:cookbook_collection) do
+      cookbook_path = File.join(CHEF_SPEC_DATA, "cookbooks")
+      cl = Chef::CookbookLoader.new(cookbook_path)
+      cl.load_cookbooks
+      Chef::Cookbook::FileVendor.on_create do |manifest|
+        Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_path)
+      end
+      Chef::CookbookCollection.new(cl)
+    end
+
+    let(:run_context) do
+      Chef::RunContext.new(node, cookbook_collection, events)
+    end
+
+    def base_resource
+      r = Chef::Resource::Package.new("chef-integration-test", run_context)
+      # The apt repository in the spec data is not gpg signed, so we need to
+      # force apt to accept the package:
+      r.options("--force-yes")
+      r
+    end
+
+    let(:package_resource) do
+      base_resource
+    end
+
+    context "when the package is not yet installed" do
+      it "installs the package with action :install" do
+        package_resource.run_action(:install)
+        shell_out!("dpkg -l chef-integration-test")
+        package_resource.should be_updated_by_last_action
+      end
+
+      it "installs the package for action :upgrade" do
+        package_resource.run_action(:upgrade)
+        shell_out!("dpkg -l chef-integration-test")
+        package_resource.should be_updated_by_last_action
+      end
+
+      it "does nothing for action :remove" do
+        package_resource.run_action(:remove)
+        shell_out!("dpkg -l chef-integration-test", :returns => [1])
+        package_resource.should_not be_updated_by_last_action
+      end
+
+      it "does nothing for action :purge" do
+        package_resource.run_action(:purge)
+        shell_out!("dpkg -l chef-integration-test", :returns => [1])
+        package_resource.should_not be_updated_by_last_action
+      end
+
+      context "and a not-available package version is specified" do
+        let(:package_resource) do
+          r = base_resource
+          r.version("2.0")
+          r
+        end
+
+        it "raises a reasonable error for action :install" do
+          expect do
+            package_resource.run_action(:install)
+          end.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
+        end
+
+      end
+
+      describe "when preseeding the install" do
+
+        let(:file_cache_path) { Dir.mktmpdir }
+
+        before do
+          Chef::Config[:file_cache_path] = file_cache_path
+          debconf_reset = 'chef-integration-test chef-integration-test/sample-var string "INVALID"'
+          shell_out!("echo #{debconf_reset} |debconf-set-selections")
+        end
+
+        after do
+          FileUtils.rm_rf(file_cache_path)
+        end
+
+        context "with a preseed file" do
+
+          let(:package_resource) do
+            r = base_resource
+            r.cookbook_name = "preseed"
+            r.response_file("preseed-file.seed")
+            r
+          end
+
+          it "preseeds the package, then installs it" do
+            package_resource.run_action(:install)
+            cmd = shell_out!("debconf-show chef-integration-test")
+            cmd.stdout.should include('chef-integration-test/sample-var: "hello world"')
+            package_resource.should be_updated_by_last_action
+          end
+
+          context "and the preseed file exists and is up-to-date" do
+
+            before do
+              # Code here is duplicated from the implementation. Not great, but
+              # it should at least fail if the code gets out of sync.
+              source = File.join(CHEF_SPEC_DATA, "cookbooks/preseed/files/default/preseed-file.seed")
+              file_cache_dir = Chef::FileCache.create_cache_path("preseed/preseed")
+              dest = "#{file_cache_dir}/chef-integration-test-1.1-1.seed"
+              FileUtils.cp(source, dest)
+            end
+
+            it "does not update the package configuration" do
+              package_resource.run_action(:install)
+              cmd = shell_out!("debconf-show chef-integration-test")
+              cmd.stdout.should include('chef-integration-test/sample-var: INVALID')
+              package_resource.should be_updated_by_last_action
+            end
+
+          end
+
+        end
+
+        context "with a preseed template" do
+
+          # NOTE: in the fixtures, there is also a cookbook_file named
+          # "preseed-template.seed". This implicitly tests that templates are
+          # preferred over cookbook_files when both are present.
+
+          let(:package_resource) do
+            r = base_resource
+            r.cookbook_name = "preseed"
+            r.response_file("preseed-template.seed")
+            r
+          end
+
+          before do
+            node.set[:preseed_value] = "FROM TEMPLATE"
+          end
+
+          it "preseeds the package, then installs it" do
+            package_resource.run_action(:install)
+            cmd = shell_out!("debconf-show chef-integration-test")
+            cmd.stdout.should include('chef-integration-test/sample-var: "FROM TEMPLATE"')
+            package_resource.should be_updated_by_last_action
+          end
+
+        end
+      end # installing w/ preseed
+    end # when package not installed
+
+    context "and the desired version of the package is installed" do
+
+      before do
+        v_1_1_package = File.expand_path("apt/chef-integration-test_1.1-1_amd64.deb", CHEF_SPEC_DATA)
+        shell_out!("dpkg -i #{v_1_1_package}")
+      end
+
+      it "does nothing for action :install" do
+        package_resource.run_action(:install)
+        shell_out!("dpkg -l chef-integration-test", :returns => [0])
+        package_resource.should_not be_updated_by_last_action
+      end
+
+      it "does nothing for action :upgrade" do
+        package_resource.run_action(:upgrade)
+        shell_out!("dpkg -l chef-integration-test", :returns => [0])
+        package_resource.should_not be_updated_by_last_action
+      end
+
+      # Verify that the package is removed by running `dpkg -l PACKAGE`
+      # On Ubuntu 12.10 and newer, the command exits 1.
+      #
+      # On Ubuntu 12.04 and older, the `dpkg -l` command will exit 0 and
+      # display a package status message like this:
+      #
+      # Desired=Unknown/Install/Remove/Purge/Hold
+      # | Status=Not/Inst/Cfg-files/Unpacked/Failed-cfg/Half-inst/trig-aWait/Trig-pend
+      # |/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
+      # ||/ Name                              Version                                   Description
+      # +++-=================================-=========================================-============================================
+      # un  chef-integration-test             <none>                                    (no description available)
+      def pkg_should_be_removed
+        # will raise if exit code != 0,1
+        pkg_check = shell_out!("dpkg -l chef-integration-test", :returns => [0,1])
+
+        if pkg_check.exitstatus == 0
+          pkg_check.stdout.should =~ /un[\s]+chef-integration-test/
+        end
+      end
+
+
+      it "removes the package for action :remove" do
+        package_resource.run_action(:remove)
+        pkg_should_be_removed
+        package_resource.should be_updated_by_last_action
+      end
+
+      it "removes the package for action :purge" do
+        package_resource.run_action(:purge)
+        pkg_should_be_removed
+        package_resource.should be_updated_by_last_action
+      end
+
+    end
+
+    context "and an older version of the package is installed" do
+      before do
+        v_1_0_package = File.expand_path("apt/chef-integration-test_1.0-1_amd64.deb", CHEF_SPEC_DATA)
+        shell_out!("dpkg -i #{v_1_0_package}")
+      end
+
+      it "does nothing for action :install" do
+        package_resource.run_action(:install)
+        shell_out!("dpkg -l chef-integration-test", :returns => [0])
+        package_resource.should_not be_updated_by_last_action
+      end
+
+      it "upgrades the package for action :upgrade" do
+        package_resource.run_action(:upgrade)
+        dpkg_l = shell_out!("dpkg -l chef-integration-test", :returns => [0])
+        dpkg_l.stdout.should =~ /chef\-integration\-test[\s]+1\.1\-1/
+        package_resource.should be_updated_by_last_action
+      end
+
+      context "and the resource specifies the new version" do
+        let(:package_resource) do
+          r = base_resource
+          r.version("1.1-1")
+          r
+        end
+
+        it "upgrades the package for action :install" do
+          package_resource.run_action(:install)
+          dpkg_l = shell_out!("dpkg -l chef-integration-test", :returns => [0])
+          dpkg_l.stdout.should =~ /chef\-integration\-test[\s]+1\.1\-1/
+          package_resource.should be_updated_by_last_action
+        end
+      end
+
+    end
+
+  end
+
+end
+
+
diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_spec.rb
new file mode 100644
index 0000000..6bd3b3c
--- /dev/null
+++ b/spec/functional/resource/powershell_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
+
+  include_context Chef::Resource::WindowsScript
+
+  let(:successful_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" }
+  let(:failed_executable_script_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe /badargument" }
+  let(:processor_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTURE" }
+  let(:native_architecture_script_content) { "echo $env:PROCESSOR_ARCHITECTUREW6432" }
+  let(:cmdlet_exit_code_not_found_content) { "get-item '.\\thisdoesnotexist'" }
+  let(:cmdlet_exit_code_success_content) { "get-item ." }
+  let(:windows_process_exit_code_success_content) { "#{ENV['SystemRoot']}\\system32\\attrib.exe $env:systemroot" }
+  let(:windows_process_exit_code_not_found_content) { "findstr /notavalidswitch" }
+  # Note that process exit codes on 32-bit Win2k3 cannot
+  # exceed maximum value of signed integer
+  let(:arbitrary_nonzero_process_exit_code) { 4193 }
+  let(:arbitrary_nonzero_process_exit_code_content) { "exit #{arbitrary_nonzero_process_exit_code}" }
+  let(:invalid_powershell_interpreter_flag) { "/thisflagisinvalid" }
+  let(:valid_powershell_interpreter_flag) { "-Sta" }
+  let!(:resource) do
+    r = Chef::Resource::WindowsScript::PowershellScript.new("Powershell resource functional test", @run_context)
+    r.code(successful_executable_script_content)
+    r
+  end
+
+  describe "when the run action is invoked on Windows" do
+    it "successfully executes a non-cmdlet Windows binary as the last command of the script" do
+      resource.code(successful_executable_script_content + " | out-file -encoding ASCII #{script_output_path}")
+      resource.returns(0)
+      resource.run_action(:run)
+    end
+
+    it "returns the process exit code" do
+      resource.code(arbitrary_nonzero_process_exit_code_content)
+      resource.returns(arbitrary_nonzero_process_exit_code)
+      resource.run_action(:run)
+    end
+
+    it "returns 0 if the last command was a cmdlet that succeeded" do
+      resource.code(cmdlet_exit_code_success_content)
+      resource.returns(0)
+      resource.run_action(:run)
+    end
+
+    it "returns 0 if the last command was a cmdlet that succeeded and was preceded by a non-cmdlet Windows binary that failed" do
+      resource.code([windows_process_exit_code_not_found_content, cmdlet_exit_code_success_content].join(';'))
+      resource.returns(0)
+      resource.run_action(:run)
+    end
+
+    it "returns 1 if the last command was a cmdlet that failed" do
+      resource.code(cmdlet_exit_code_not_found_content)
+      resource.returns(1)
+      resource.run_action(:run)
+    end
+
+    it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do
+      resource.code([windows_process_exit_code_success_content, cmdlet_exit_code_not_found_content].join(';'))
+      resource.returns(1)
+      resource.run_action(:run)
+    end
+
+    # This somewhat ambiguous case, two failures of different types,
+    # seems to violate the principle of returning the status of the
+    # last line executed -- in this case, we return the status of the
+    # second to last line. This happens because Powershell gives no
+    # way for us to determine whether the last operation was a cmdlet
+    # or Windows process. Because the latter gives more specified
+    # errors than 0 or 1, we return that instead, which is acceptable
+    # since callers can test for nonzero rather than testing for 1.
+    it "returns 1 if the last command was a cmdlet that failed and was preceded by an unsuccessfully executed non-cmdlet Windows binary" do
+      resource.code([arbitrary_nonzero_process_exit_code_content,cmdlet_exit_code_not_found_content].join(';'))
+      resource.returns(arbitrary_nonzero_process_exit_code)
+      resource.run_action(:run)
+    end
+
+    it "returns 0 if the last command was a non-cmdlet Windows binary that succeeded and was preceded by a failed cmdlet" do
+      resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(';'))
+      resource.returns(arbitrary_nonzero_process_exit_code)
+      resource.run_action(:run)
+    end
+
+    it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that succeeded" do
+      resource.code([cmdlet_exit_code_success_content, arbitrary_nonzero_process_exit_code_content].join(';'))
+      resource.returns(arbitrary_nonzero_process_exit_code)
+      resource.run_action(:run)
+    end
+
+    it "returns a specific error code if the last command was a non-cmdlet Windows binary that failed and was preceded by cmdlet that failed" do
+      resource.code([cmdlet_exit_code_not_found_content, arbitrary_nonzero_process_exit_code_content].join(';'))
+      resource.returns(arbitrary_nonzero_process_exit_code)
+      resource.run_action(:run)
+    end
+
+    it "executes a script with a 64-bit process on a 64-bit OS, otherwise a 32-bit process" do
+      resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
+      resource.returns(0)
+      resource.run_action(:run)
+
+      is_64_bit = (ENV['PROCESSOR_ARCHITECTURE'] == 'AMD64') || (ENV['PROCESSOR_ARCHITEW6432'] == 'AMD64')
+
+      detected_64_bit = source_contains_case_insensitive_content?( get_script_output, 'AMD64' )
+
+      is_64_bit.should == detected_64_bit
+    end
+
+    it "returns 1 if an invalid flag is passed to the interpreter" do
+      resource.code(cmdlet_exit_code_success_content)
+      resource.flags(invalid_powershell_interpreter_flag)
+      resource.returns(1)
+      resource.run_action(:run)
+    end
+
+    it "returns 0 if a valid flag is passed to the interpreter" do
+      resource.code(cmdlet_exit_code_success_content)
+      resource.flags(valid_powershell_interpreter_flag)
+      resource.returns(0)
+      resource.run_action(:run)
+    end
+  end
+
+  context "when running on a 32-bit version of Windows", :windows32_only do
+
+    it "executes a script with a 32-bit process if process architecture :i386 is specified" do
+      resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
+      resource.architecture(:i386)
+      resource.returns(0)
+      resource.run_action(:run)
+
+      source_contains_case_insensitive_content?( get_script_output, 'x86' ).should == true
+    end
+
+    it "raises an exception if :x86_64 process architecture is specified" do
+      begin
+        resource.architecture(:x86_64).should raise_error Chef::Exceptions::Win32ArchitectureIncorrect
+      rescue Chef::Exceptions::Win32ArchitectureIncorrect
+      end
+    end
+  end
+
+  context "when running on a 64-bit version of Windows", :windows64_only do
+    it "executes a script with a 64-bit process if :x86_64 arch is specified" do
+      resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
+      resource.architecture(:x86_64)
+      resource.returns(0)
+      resource.run_action(:run)
+
+      source_contains_case_insensitive_content?( get_script_output, 'AMD64' ).should == true
+    end
+
+    it "executes a script with a 32-bit process if :i386 arch is specified" do
+      resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
+      resource.architecture(:i386)
+      resource.returns(0)
+      resource.run_action(:run)
+
+      source_contains_case_insensitive_content?( get_script_output, 'x86' ).should == true
+    end
+  end
+
+  def get_script_output
+    script_output = File.read(script_output_path)
+  end
+
+  def source_contains_case_insensitive_content?( source, content )
+    source.downcase.include?(content.downcase)
+  end
+end
diff --git a/spec/functional/resource/registry_spec.rb b/spec/functional/resource/registry_spec.rb
new file mode 100644
index 0000000..8f3cfa9
--- /dev/null
+++ b/spec/functional/resource/registry_spec.rb
@@ -0,0 +1,562 @@
+#
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/win32/registry"
+require "chef/resource_reporter"
+require "spec_helper"
+
+describe Chef::Resource::RegistryKey, :unix_only do
+  before(:all) do
+    events = Chef::EventDispatch::Dispatcher.new
+    node = Chef::Node.new
+    ohai = Ohai::System.new
+    ohai.all_plugins
+    node.consume_external_attrs(ohai.data,{})
+    run_context = Chef::RunContext.new(node, {}, events)
+    @resource = Chef::Resource::RegistryKey.new("HKCU\\Software", run_context)
+  end
+  context "when load_current_resource is run on a non-windows node" do
+    it "throws an exception because you don't have a windows registry (derp)" do
+      @resource.key("HKCU\\Software\\Opscode")
+      @resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}])
+      lambda{@resource.run_action(:create)}.should raise_error(Chef::Exceptions::Win32NotWindows)
+    end
+  end
+end
+
+describe Chef::Resource::RegistryKey, :windows_only do
+
+  # parent and key must be single keys, not paths
+  let(:parent) { 'Opscode' }
+  let(:child) { 'Whatever' }
+  let(:key_parent) { "SOFTWARE\\" + parent }
+  let(:key_child) { "SOFTWARE\\" + parent + "\\" + child }
+  # must be under HKLM\SOFTWARE for WOW64 redirection to work
+  let(:reg_parent) { "HKLM\\" + key_parent }
+  let(:reg_child) { "HKLM\\" + key_child }
+  let(:hive_class) { ::Win32::Registry::HKEY_LOCAL_MACHINE }
+  let(:resource_name) { "This is the name of my Resource" }
+
+  def clean_registry
+    if windows64?
+      # clean 64-bit space on WOW64
+      @registry.architecture = :x86_64
+      @registry.delete_key(reg_parent, true)
+      @registry.architecture = :machine
+    end
+    # clean 32-bit space on WOW64
+    @registry.architecture = :i386
+    @registry.delete_key(reg_parent, true)
+    @registry.architecture = :machine
+  end
+
+  def reset_registry
+    clean_registry
+    hive_class.create(key_parent, Win32::Registry::KEY_WRITE | 0x0100)
+    hive_class.create(key_parent, Win32::Registry::KEY_WRITE | 0x0200)
+  end
+
+  def create_deletable_keys
+    # create them both 32-bit and 64-bit
+    [ 0x0100, 0x0200 ].each do |flag|
+      hive_class.create(key_parent + '\Opscode', Win32::Registry::KEY_WRITE | flag)
+      hive_class.open(key_parent + '\Opscode', Win32::Registry::KEY_ALL_ACCESS | flag) do |reg|
+        reg["Color", Win32::Registry::REG_SZ] = "Orange"
+        reg.write("Opscode", Win32::Registry::REG_MULTI_SZ, ["Seattle", "Washington"])
+        reg["AKA", Win32::Registry::REG_SZ] = "OC"
+      end
+      hive_class.create(key_parent + '\ReportKey', Win32::Registry::KEY_WRITE | flag)
+      hive_class.open(key_parent + '\ReportKey', Win32::Registry::KEY_ALL_ACCESS | flag) do |reg|
+        reg["ReportVal4", Win32::Registry::REG_SZ] = "report4"
+        reg["ReportVal5", Win32::Registry::REG_SZ] = "report5"
+      end
+      hive_class.create(key_parent + '\OpscodeWhyRun', Win32::Registry::KEY_WRITE | flag)
+      hive_class.open(key_parent + '\OpscodeWhyRun', Win32::Registry::KEY_ALL_ACCESS | flag) do |reg|
+        reg["BriskWalk", Win32::Registry::REG_SZ] = "is good for health"
+      end
+    end
+  end
+
+  before(:all) do
+    @events = Chef::EventDispatch::Dispatcher.new
+    @node = Chef::Node.new
+    ohai = Ohai::System.new
+    ohai.all_plugins
+    @node.consume_external_attrs(ohai.data,{})
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::RegistryKey.new(resource_name, @run_context)
+    @registry = Chef::Win32::Registry.new(@run_context)
+
+    reset_registry
+  end
+
+  #Reporting setup
+  before do
+    @node.name("windowsbox")
+
+    @rest_client = mock("Chef::REST (mock)")
+    @rest_client.stub!(:create_url).and_return("reports/nodes/windowsbox/runs/#{@run_id}");
+    @rest_client.stub!(:raw_http_request).and_return({"result"=>"ok"});
+    @rest_client.stub!(:post_rest).and_return({"uri"=>"https://example.com/reports/nodes/windowsbox/runs/#{@run_id}"});
+
+    @resource_reporter = Chef::ResourceReporter.new(@rest_client)
+    @events.register(@resource_reporter)
+    @run_id = @resource_reporter.run_id
+    @run_status = Chef::RunStatus.new(@node, @events)
+
+    @resource_reporter.run_started(@run_status)
+
+    @new_resource.cookbook_name = "monkey"
+    @cookbook_version = mock("Cookbook::Version", :version => "1.2.3")
+    @new_resource.stub!(:cookbook_version).and_return(@cookbook_version)
+  end
+
+  after (:all) do
+    clean_registry
+  end
+
+  context "when action is create" do
+    before (:all) do
+      reset_registry
+    end
+    it "creates registry key, value if the key is missing" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}])
+      @new_resource.run_action(:create)
+
+      @registry.key_exists?(reg_child).should == true
+      @registry.data_exists?(reg_child, {:name=>"Color", :type=>:string, :data=>"Orange"}).should == true
+    end
+
+    it "does not create the key if it already exists with same value, type and data" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}])
+      @new_resource.run_action(:create)
+
+      @registry.key_exists?(reg_child).should == true
+      @registry.data_exists?(reg_child, {:name=>"Color", :type=>:string, :data=>"Orange"}).should == true
+    end
+
+    it "creates a value if it does not exist" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Mango", :type=>:string, :data=>"Yellow"}])
+      @new_resource.run_action(:create)
+
+      @registry.data_exists?(reg_child, {:name=>"Mango", :type=>:string, :data=>"Yellow"}).should == true
+    end
+
+    it "modifies the data if the key and value exist and type matches" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Color", :type=>:string, :data=>"Not just Orange - OpscodeOrange!"}])
+      @new_resource.run_action(:create)
+
+      @registry.data_exists?(reg_child, {:name=>"Color", :type=>:string, :data=>"Not just Orange - OpscodeOrange!"}).should == true
+    end
+
+    it "modifys the type if the key and value exist and the type does not match" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Color", :type=>:multi_string, :data=>["Not just Orange - OpscodeOrange!"]}])
+      @new_resource.run_action(:create)
+
+      @registry.data_exists?(reg_child, {:name=>"Color", :type=>:multi_string, :data=>["Not just Orange - OpscodeOrange!"]}).should == true
+    end
+
+    it "creates subkey if parent exists" do
+      @new_resource.key(reg_child + '\OpscodeTest')
+      @new_resource.values([{:name=>"Chef", :type=>:multi_string, :data=>["OpscodeOrange", "Rules"]}])
+      @new_resource.recursive(false)
+      @new_resource.run_action(:create)
+
+      @registry.key_exists?(reg_child + '\OpscodeTest').should == true
+      @registry.value_exists?(reg_child + '\OpscodeTest', {:name=>"Chef", :type=>:multi_string, :data=>["OpscodeOrange", "Rules"]}).should == true
+    end
+
+    it "gives error if action create and parent does not exist and recursive is set to false" do
+      @new_resource.key(reg_child + '\Missing1\Missing2')
+      @new_resource.values([{:name=>"OC", :type=>:string, :data=>"MissingData"}])
+      @new_resource.recursive(false)
+      lambda{@new_resource.run_action(:create)}.should raise_error(Chef::Exceptions::Win32RegNoRecursive)
+    end
+
+    it "creates missing keys if action create and parent does not exist and recursive is set to true" do
+      @new_resource.key(reg_child + '\Missing1\Missing2')
+      @new_resource.values([{:name=>"OC", :type=>:string, :data=>"MissingData"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:create)
+
+      @registry.key_exists?(reg_child + '\Missing1\Missing2').should == true
+      @registry.value_exists?(reg_child + '\Missing1\Missing2', {:name=>"OC", :type=>:string, :data=>"MissingData"}).should == true
+    end
+
+    it "creates key with multiple value as specified" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"one", :type=>:string, :data=>"1"},{:name=>"two", :type=>:string, :data=>"2"},{:name=>"three", :type=>:string, :data=>"3"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:create)
+
+      @new_resource.values.each do |value|
+        @registry.value_exists?(reg_child, value).should == true
+      end
+    end
+
+    context "when running on 64-bit server", :windows64_only do
+      before(:all) do
+        reset_registry
+      end
+      after(:all) do
+        @new_resource.architecture(:machine)
+        @registry.architecture = :machine
+      end
+      it "creates a key in a 32-bit registry that is not viewable in 64-bit" do
+        @new_resource.key(reg_child + '\Atraxi' )
+        @new_resource.values([{:name=>"OC", :type=>:string, :data=>"Data"}])
+        @new_resource.recursive(true)
+        @new_resource.architecture(:i386)
+        @new_resource.run_action(:create)
+        @registry.architecture = :i386
+        @registry.data_exists?(reg_child + '\Atraxi', {:name=>"OC", :type=>:string, :data=>"Data"}).should == true
+        @registry.architecture = :x86_64
+        @registry.key_exists?(reg_child + '\Atraxi').should == false
+      end
+    end
+
+    it "prepares the reporting data for action :create" do
+      @new_resource.key(reg_child + '\Ood')
+      @new_resource.values([{:name=>"ReportingVal1", :type=>:string, :data=>"report1"},{:name=>"ReportingVal2", :type=>:string, :data=>"report2"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:create)
+      @report = @resource_reporter.prepare_run_data
+
+      @report["action"].should == "end"
+      @report["resources"][0]["type"].should == "registry_key"
+      @report["resources"][0]["name"].should == resource_name
+      @report["resources"][0]["id"].should == reg_child + '\Ood'
+      @report["resources"][0]["after"][:values].should == [{:name=>"ReportingVal1", :type=>:string, :data=>"report1"},
+                                                           {:name=>"ReportingVal2", :type=>:string, :data=>"report2"}]
+      @report["resources"][0]["before"][:values].should == []
+      @report["resources"][0]["result"].should == "create"
+      @report["status"].should == "success"
+      @report["total_res_count"].should == "1"
+    end
+
+    context "while running in whyrun mode" do
+      before (:each) do
+        Chef::Config[:why_run] = true
+      end
+
+      it "does not throw an exception if the keys do not exist but recursive is set to false" do
+        @new_resource.key(reg_child + '\Slitheen\Raxicoricofallapatorius')
+        @new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
+        @new_resource.recursive(false)
+        lambda{@new_resource.run_action(:create)}.should_not raise_error
+        @registry.key_exists?(reg_child + '\Slitheen').should == false
+        @registry.key_exists?(reg_child + '\Slitheen\Raxicoricofallapatorius').should == false
+      end
+      it "does not create key if the action is create" do
+        @new_resource.key(reg_child + '\Slitheen')
+        @new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
+        @new_resource.recursive(false)
+        @new_resource.run_action(:create)
+        @registry.key_exists?(reg_child + '\Slitheen').should == false
+      end
+    end
+  end
+
+  context "when action is create_if_missing" do
+    before (:all) do
+      reset_registry
+    end
+
+    it "creates registry key, value if the key is missing" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}])
+      @new_resource.run_action(:create_if_missing)
+
+      @registry.key_exists?(reg_parent).should == true
+      @registry.key_exists?(reg_child).should == true
+      @registry.data_exists?(reg_child, {:name=>"Color", :type=>:string, :data=>"Orange"}).should == true
+    end
+
+    it "does not create the key if it already exists with same value, type and data" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}])
+      @new_resource.run_action(:create_if_missing)
+
+      @registry.key_exists?(reg_child).should == true
+      @registry.data_exists?(reg_child, {:name=>"Color", :type=>:string, :data=>"Orange"}).should == true
+    end
+
+    it "creates a value if it does not exist" do
+      @new_resource.key(reg_child)
+      @new_resource.values([{:name=>"Mango", :type=>:string, :data=>"Yellow"}])
+      @new_resource.run_action(:create_if_missing)
+
+      @registry.data_exists?(reg_child, {:name=>"Mango", :type=>:string, :data=>"Yellow"}).should == true
+    end
+
+    it "creates subkey if parent exists" do
+      @new_resource.key(reg_child + '\Pyrovile')
+      @new_resource.values([{:name=>"Chef", :type=>:multi_string, :data=>["OpscodeOrange", "Rules"]}])
+      @new_resource.recursive(false)
+      @new_resource.run_action(:create_if_missing)
+
+      @registry.key_exists?(reg_child + '\Pyrovile').should == true
+      @registry.value_exists?(reg_child + '\Pyrovile', {:name=>"Chef", :type=>:multi_string, :data=>["OpscodeOrange", "Rules"]}).should == true
+    end
+
+    it "gives error if action create and parent does not exist and recursive is set to false" do
+      @new_resource.key(reg_child + '\Sontaran\Sontar')
+      @new_resource.values([{:name=>"OC", :type=>:string, :data=>"MissingData"}])
+      @new_resource.recursive(false)
+      lambda{@new_resource.run_action(:create_if_missing)}.should raise_error(Chef::Exceptions::Win32RegNoRecursive)
+    end
+
+    it "creates missing keys if action create and parent does not exist and recursive is set to true" do
+      @new_resource.key(reg_child + '\Sontaran\Sontar')
+      @new_resource.values([{:name=>"OC", :type=>:string, :data=>"MissingData"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:create_if_missing)
+
+      @registry.key_exists?(reg_child + '\Sontaran\Sontar').should == true
+      @registry.value_exists?(reg_child + '\Sontaran\Sontar', {:name=>"OC", :type=>:string, :data=>"MissingData"}).should == true
+    end
+
+    it "creates key with multiple value as specified" do
+      @new_resource.key(reg_child + '\Adipose')
+      @new_resource.values([{:name=>"one", :type=>:string, :data=>"1"},{:name=>"two", :type=>:string, :data=>"2"},{:name=>"three", :type=>:string, :data=>"3"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:create_if_missing)
+
+      @new_resource.values.each do |value|
+        @registry.value_exists?(reg_child + '\Adipose', value).should == true
+      end
+    end
+
+    it "prepares the reporting data for :create_if_missing" do
+      @new_resource.key(reg_child + '\Judoon')
+      @new_resource.values([{:name=>"ReportingVal3", :type=>:string, :data=>"report3"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:create_if_missing)
+      @report = @resource_reporter.prepare_run_data
+
+      @report["action"].should == "end"
+      @report["resources"][0]["type"].should == "registry_key"
+      @report["resources"][0]["name"].should == resource_name
+      @report["resources"][0]["id"].should == reg_child + '\Judoon'
+      @report["resources"][0]["after"][:values].should == [{:name=>"ReportingVal3", :type=>:string, :data=>"report3"}]
+      @report["resources"][0]["before"][:values].should == []
+      @report["resources"][0]["result"].should == "create_if_missing"
+      @report["status"].should == "success"
+      @report["total_res_count"].should == "1"
+    end
+
+    context "while running in whyrun mode" do
+      before (:each) do
+        Chef::Config[:why_run] = true
+      end
+
+      it "does not throw an exception if the keys do not exist but recursive is set to false" do
+        @new_resource.key(reg_child + '\Zygons\Zygor')
+        @new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
+        @new_resource.recursive(false)
+        lambda{@new_resource.run_action(:create_if_missing)}.should_not raise_error
+        @registry.key_exists?(reg_child + '\Zygons').should == false
+        @registry.key_exists?(reg_child + '\Zygons\Zygor').should == false
+      end
+      it "does nothing if the action is create_if_missing" do
+        @new_resource.key(reg_child + '\Zygons')
+        @new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
+        @new_resource.recursive(false)
+        @new_resource.run_action(:create_if_missing)
+        @registry.key_exists?(reg_child + '\Zygons').should == false
+      end
+    end
+  end
+
+  context "when the action is delete" do
+    before(:all) do
+      reset_registry
+      create_deletable_keys
+    end
+
+    it "takes no action if the specified key path does not exist in the system" do
+      @registry.key_exists?(reg_parent + '\Osirian').should == false
+
+      @new_resource.key(reg_parent+ '\Osirian')
+      @new_resource.recursive(false)
+      @new_resource.run_action(:delete)
+
+      @registry.key_exists?(reg_parent + '\Osirian').should == false
+    end
+
+    it "takes no action if the key exists but the value does not" do
+      @registry.data_exists?(reg_parent + '\Opscode', {:name=>"Color", :type=>:string, :data=>"Orange"}).should == true
+
+      @new_resource.key(reg_parent + '\Opscode')
+      @new_resource.values([{:name=>"LooksLike", :type=>:multi_string, :data=>["SeattleGrey", "OCOrange"]}])
+      @new_resource.recursive(false)
+      @new_resource.run_action(:delete)
+
+      @registry.data_exists?(reg_parent + '\Opscode', {:name=>"Color", :type=>:string, :data=>"Orange"}).should == true
+    end
+
+    it "deletes only specified values under a key path" do
+      @new_resource.key(reg_parent + '\Opscode')
+      @new_resource.values([{:name=>"Opscode", :type=>:multi_string, :data=>["Seattle", "Washington"]}, {:name=>"AKA", :type=>:string, :data=>"OC"}])
+      @new_resource.recursive(false)
+      @new_resource.run_action(:delete)
+
+      @registry.data_exists?(reg_parent + '\Opscode', {:name=>"Color", :type=>:string, :data=>"Orange"}).should == true
+      @registry.value_exists?(reg_parent + '\Opscode', {:name=>"AKA", :type=>:string, :data=>"OC"}).should == false
+      @registry.value_exists?(reg_parent + '\Opscode', {:name=>"Opscode", :type=>:multi_string, :data=>["Seattle", "Washington"]}).should == false
+    end
+
+    it "it deletes the values with the same name irrespective of it type and data" do
+      @new_resource.key(reg_parent + '\Opscode')
+      @new_resource.values([{:name=>"Color", :type=>:multi_string, :data=>["Black", "Orange"]}])
+      @new_resource.recursive(false)
+      @new_resource.run_action(:delete)
+
+      @registry.value_exists?(reg_parent + '\Opscode', {:name=>"Color", :type=>:string, :data=>"Orange"}).should == false
+    end
+
+    it "prepares the reporting data for action :delete" do
+      @new_resource.key(reg_parent + '\ReportKey')
+      @new_resource.values([{:name=>"ReportVal4", :type=>:string, :data=>"report4"},{:name=>"ReportVal5", :type=>:string, :data=>"report5"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:delete)
+
+      @report = @resource_reporter.prepare_run_data
+
+      @registry.value_exists?(reg_parent + '\ReportKey', [{:name=>"ReportVal4", :type=>:string, :data=>"report4"},{:name=>"ReportVal5", :type=>:string, :data=>"report5"}]).should == false
+
+      @report["action"].should == "end"
+      @report["resources"].count.should == 1
+      @report["resources"][0]["type"].should == "registry_key"
+      @report["resources"][0]["name"].should == resource_name
+      @report["resources"][0]["id"].should == reg_parent + '\ReportKey'
+      @report["resources"][0]["before"][:values].should == [{:name=>"ReportVal4", :type=>:string, :data=>"report4"},
+                                                            {:name=>"ReportVal5", :type=>:string, :data=>"report5"}]
+      #Not testing for after values to match since after -> new_resource values.
+      @report["resources"][0]["result"].should == "delete"
+      @report["status"].should == "success"
+      @report["total_res_count"].should == "1"
+    end
+
+    context "while running in whyrun mode" do
+      before (:each) do
+        Chef::Config[:why_run] = true
+      end
+      it "does nothing if the action is delete" do
+        @new_resource.key(reg_parent + '\OpscodeWhyRun')
+        @new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
+        @new_resource.recursive(false)
+        @new_resource.run_action(:delete)
+
+        @registry.key_exists?(reg_parent + '\OpscodeWhyRun').should == true
+      end
+    end
+  end
+
+  context "when the action is delete_key" do
+    before (:all) do
+      reset_registry
+      create_deletable_keys
+    end
+
+    it "takes no action if the specified key path does not exist in the system" do
+      @registry.key_exists?(reg_parent + '\Osirian').should == false
+
+      @new_resource.key(reg_parent + '\Osirian')
+      @new_resource.recursive(false)
+      @new_resource.run_action(:delete_key)
+
+      @registry.key_exists?(reg_parent + '\Osirian').should == false
+    end
+
+    it "deletes key if it has no subkeys and recursive == false" do
+      @new_resource.key(reg_parent + '\OpscodeTest')
+      @new_resource.recursive(false)
+      @new_resource.run_action(:delete_key)
+
+      @registry.key_exists?(reg_parent + '\OpscodeTest').should == false
+    end
+
+    it "raises an exception if the key has subkeys and recursive == false" do
+      @new_resource.key(reg_parent)
+      @new_resource.recursive(false)
+      lambda{@new_resource.run_action(:delete_key)}.should raise_error(Chef::Exceptions::Win32RegNoRecursive)
+    end
+
+    it "ignores the values under a key" do
+      @new_resource.key(reg_parent + '\OpscodeIgnoredValues')
+      #@new_resource.values([{:name=>"DontExist", :type=>:string, :data=>"These will be ignored anyways"}])
+      @new_resource.recursive(true)
+      @new_resource.run_action(:delete_key)
+    end
+
+    it "deletes the key if it has subkeys and recursive == true" do
+      @new_resource.key(reg_parent + '\Opscode')
+      @new_resource.recursive(true)
+      @new_resource.run_action(:delete_key)
+
+      @registry.key_exists?(reg_parent + '\Opscode').should == false
+    end
+
+    it "prepares the reporting data for action :delete_key" do
+      @new_resource.key(reg_parent + '\ReportKey')
+      @new_resource.recursive(true)
+      @new_resource.run_action(:delete_key)
+
+      @report = @resource_reporter.prepare_run_data
+      @report["action"].should == "end"
+      @report["resources"][0]["type"].should == "registry_key"
+      @report["resources"][0]["name"].should == resource_name
+      @report["resources"][0]["id"].should == reg_parent + '\ReportKey'
+      #Not testing for before or after values to match since
+      #after -> new_resource.values and
+      #before -> current_resource.values
+      @report["resources"][0]["result"].should == "delete_key"
+      @report["status"].should == "success"
+      @report["total_res_count"].should == "1"
+    end
+    context "while running in whyrun mode" do
+      before (:each) do
+        Chef::Config[:why_run] = true
+      end
+
+      it "does not throw an exception if the key has subkeys but recursive is set to false" do
+        @new_resource.key(reg_parent + '\OpscodeWhyRun')
+        @new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
+        @new_resource.recursive(false)
+        @new_resource.run_action(:delete_key)
+        @new_resource.should_not raise_error(ArgumentError)
+      end
+      it "does nothing if the action is delete_key" do
+        @new_resource.key(reg_parent + '\OpscodeWhyRun')
+        @new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
+        @new_resource.recursive(false)
+        @new_resource.run_action(:delete_key)
+
+        @registry.key_exists?(reg_parent + '\OpscodeWhyRun').should == true
+      end
+    end
+  end
+end
diff --git a/spec/functional/resource/remote_directory_spec.rb b/spec/functional/resource/remote_directory_spec.rb
new file mode 100644
index 0000000..70e575a
--- /dev/null
+++ b/spec/functional/resource/remote_directory_spec.rb
@@ -0,0 +1,220 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::RemoteDirectory do
+  include_context Chef::Resource::Directory
+
+  let(:directory_base) { "directory_spec" }
+  let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) }
+
+  def create_resource
+    cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+    Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_repo) }
+    node = Chef::Node.new
+    cl = Chef::CookbookLoader.new(cookbook_repo)
+    cl.load_cookbooks
+    cookbook_collection = Chef::CookbookCollection.new(cl)
+    events = Chef::EventDispatch::Dispatcher.new
+    run_context = Chef::RunContext.new(node, cookbook_collection, events)
+
+    resource = Chef::Resource::RemoteDirectory.new(path, run_context)
+    resource.source "remotedir"
+    resource.cookbook('openldap')
+    resource
+  end
+
+  def create_extraneous_files
+    FileUtils.mkdir_p(File.join(path, 'remotesubdir'))
+    @existing1 = File.join(path, 'marked_for_death.txt')
+    @existing2 = File.join(path, 'remotesubdir', 'marked_for_death_again.txt')
+    FileUtils.touch(@existing1)
+    FileUtils.touch(@existing2)
+  end
+
+  let(:resource) do
+    create_resource
+  end
+
+  let(:resource_second_pass) do
+    create_resource
+  end
+
+  # See spec/data/cookbooks/openldap/files/default
+  let(:expected_files) do
+    [
+      File.join(path, 'remote_dir_file1.txt'),
+      File.join(path, 'remote_dir_file2.txt'),
+      File.join(path, 'remotesubdir', 'remote_subdir_file1.txt'),
+      File.join(path, 'remotesubdir', 'remote_subdir_file2.txt'),
+      File.join(path, 'remotesubdir', '.a_dotfile'),
+      File.join(path, '.a_dotdir', '.a_dotfile_in_a_dotdir')
+    ]
+  end
+
+  it_behaves_like "a directory resource"
+
+  it_behaves_like "a securable resource with reporting"
+
+  context "when creating the remote directory with purging disabled" do
+
+    context "and the directory does not yet exist" do
+      before do
+        resource.run_action(:create)
+      end
+
+      it "transfers the directory with all contents" do
+        expected_files.each do |file_path|
+          File.should exist(file_path)
+        end
+      end
+
+      it "is marked as updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+    end
+
+    context "and there are extraneous files in the directory" do
+      before do
+        create_extraneous_files
+        resource.run_action(:create)
+      end
+
+      it "does not modify the expected state of the directory" do
+        expected_files.each do |file_path|
+          File.should exist(file_path)
+        end
+      end
+
+      it "does not remove unmanaged files" do
+        File.should exist(@existing1)
+        File.should exist(@existing2)
+      end
+    end
+
+    context "and the directory is in the desired state" do
+      before do
+        resource.run_action(:create)
+        resource_second_pass.run_action(:create)
+      end
+
+      it "does not modify the expected state of the directory" do
+        expected_files.each do |file_path|
+          File.should exist(file_path)
+        end
+      end
+
+      it "is not marked as updated by last action" do
+        resource_second_pass.should_not be_updated_by_last_action
+      end
+
+    end
+
+    describe "with overwrite disabled" do
+      before(:each) do
+        resource.purge(false)
+        resource.overwrite(false)
+      end
+
+      it "leaves modifications alone" do
+        FileUtils.mkdir_p(File.join(path, 'remotesubdir'))
+        modified_file = File.join(path, 'remote_dir_file1.txt')
+        modified_subdir_file = File.join(path, 'remotesubdir', 'remote_subdir_file1.txt')
+        File.open(modified_file, 'a') {|f| f.puts "santa is real"}
+        File.open(modified_subdir_file, 'a') {|f| f.puts "so is rudolph"}
+        modified_file_checksum = sha256_checksum(modified_file)
+        modified_subdir_file_checksum = sha256_checksum(modified_subdir_file)
+
+        resource.run_action(:create)
+        sha256_checksum(modified_file).should == modified_file_checksum
+        sha256_checksum(modified_subdir_file).should == modified_subdir_file_checksum
+      end
+    end
+  end
+
+  context "when creating the directory with purging enabled" do
+    before(:each) do
+      resource.purge(true)
+    end
+
+    context "and there are no extraneous files in the directory" do
+      before do
+        resource.run_action(:create)
+      end
+
+      it "creates the directory contents as normal" do
+        expected_files.each do |file_path|
+          File.should exist(file_path)
+        end
+      end
+
+    end
+
+    context "and there are extraneous files in the directory" do
+      before do
+        create_extraneous_files
+        resource.run_action(:create)
+      end
+
+      it "removes unmanaged files" do
+        File.should_not exist(@existing1)
+        File.should_not exist(@existing2)
+      end
+
+      it "does not modify managed files" do
+        expected_files.each do |file_path|
+          File.should exist(file_path)
+        end
+      end
+
+      it "is marked as updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+    end
+
+    context "and there are deeply nested extraneous files in the directory" do
+      before do
+        FileUtils.mkdir_p(File.join(path, 'a', 'multiply', 'nested', 'directory'))
+        @existing1 = File.join(path, 'a', 'foo.txt')
+        @existing2 = File.join(path, 'a', 'multiply', 'bar.txt')
+        @existing3 = File.join(path, 'a', 'multiply', 'nested', 'baz.txt')
+        @existing4 = File.join(path, 'a', 'multiply', 'nested', 'directory', 'qux.txt')
+        FileUtils.touch(@existing1)
+        FileUtils.touch(@existing2)
+        FileUtils.touch(@existing3)
+        FileUtils.touch(@existing4)
+
+        resource.run_action(:create)
+      end
+
+      it "removes files in subdirectories before files above" do
+        File.should_not exist(@existing1)
+        File.should_not exist(@existing2)
+        File.should_not exist(@existing3)
+        File.should_not exist(@existing4)
+      end
+
+      it "is marked as updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+
+    end
+  end
+
+end
diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb
new file mode 100644
index 0000000..bfc09dc
--- /dev/null
+++ b/spec/functional/resource/remote_file_spec.rb
@@ -0,0 +1,165 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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 'tiny_server'
+
+describe Chef::Resource::RemoteFile do
+
+  let(:file_cache_path) { Dir.mktmpdir }
+
+  before(:each) do
+    @old_file_cache = Chef::Config[:file_cache_path]
+    Chef::Config[:file_cache_path] = file_cache_path
+  end
+
+  after(:each) do
+    Chef::Config[:file_cache_path] = @old_file_cache
+    FileUtils.rm_rf(file_cache_path)
+  end
+
+  include_context Chef::Resource::File
+
+  let(:file_base) { "remote_file_spec" }
+
+  def create_resource
+    node = Chef::Node.new
+    events = Chef::EventDispatch::Dispatcher.new
+    run_context = Chef::RunContext.new(node, {}, events)
+    resource = Chef::Resource::RemoteFile.new(path, run_context)
+    resource.source(source)
+    resource
+  end
+
+  let(:resource) do
+    create_resource
+  end
+
+  let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+
+  def start_tiny_server(server_opts={})
+    @server = TinyServer::Manager.new(server_opts)
+    @server.start
+    @api = TinyServer::API.instance
+    @api.clear
+    @api.get("/nyan_cat.png", 200) {
+      File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png'), "rb") do |f|
+        f.read
+      end
+    }
+    @api.get("/nyan_cat.png.gz", 200, nil, { 'Content-Type' => 'application/gzip', 'Content-Encoding' => 'gzip' } ) {
+      File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png.gz'), "rb") do |f|
+        f.read
+      end
+    }
+  end
+
+  def stop_tiny_server
+    @server.stop
+    @server = @api = nil
+  end
+
+  context "when fetching files over HTTP" do
+    before(:all) do
+      start_tiny_server
+    end
+
+    after(:all) do
+      stop_tiny_server
+    end
+
+    describe "when redownload isn't necessary" do
+      let(:source) { 'http://localhost:9000/seattle_capo.png' }
+
+      before do
+        @api.get("/seattle_capo.png", 304, "", { 'Etag' => 'abcdef' } )
+      end
+
+      it "does not fetch the file" do
+        resource.run_action(:create)
+      end
+
+    end
+
+    context "when using normal encoding" do
+      let(:source) { 'http://localhost:9000/nyan_cat.png' }
+      let(:expected_content) do
+        content = File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png'), "rb") do |f|
+          f.read
+        end
+        content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding)
+        content
+      end
+
+      it_behaves_like "a file resource"
+
+      it_behaves_like "a securable resource with reporting"
+    end
+
+    context "when using gzip encoding" do
+      let(:source) { 'http://localhost:9000/nyan_cat.png.gz' }
+      let(:expected_content) do
+        content = File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png.gz'), "rb") do |f|
+          f.read
+        end
+        content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding)
+        content
+      end
+
+      it_behaves_like "a file resource"
+
+      it_behaves_like "a securable resource with reporting"
+    end
+
+  end
+
+  context "when fetching files over HTTPS" do
+
+    before(:all) do
+      cert_text = File.read(File.expand_path("ssl/chef-rspec.cert", CHEF_SPEC_DATA))
+      cert = OpenSSL::X509::Certificate.new(cert_text)
+      key_text = File.read(File.expand_path("ssl/chef-rspec.key", CHEF_SPEC_DATA))
+      key = OpenSSL::PKey::RSA.new(key_text)
+
+      server_opts = { :SSLEnable => true,
+                      :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
+                      :SSLCertificate => cert,
+                      :SSLPrivateKey => key }
+
+      start_tiny_server(server_opts)
+    end
+
+    after(:all) do
+      stop_tiny_server
+    end
+
+    let(:source) { 'https://localhost:9000/nyan_cat.png' }
+
+    let(:expected_content) do
+      content = File.open(File.join(CHEF_SPEC_DATA, 'remote_file', 'nyan_cat.png'), "rb") do |f|
+        f.read
+      end
+      content.force_encoding(Encoding::BINARY) if content.respond_to?(:force_encoding)
+      content
+    end
+
+    it_behaves_like "a file resource"
+
+  end
+
+end
diff --git a/spec/functional/resource/rpm_spec.rb b/spec/functional/resource/rpm_spec.rb
new file mode 100644
index 0000000..7825377
--- /dev/null
+++ b/spec/functional/resource/rpm_spec.rb
@@ -0,0 +1,122 @@
+#
+# Author:: Prabhu Das (<prabhu.das at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'functional/resource/base'
+require 'chef/mixin/shell_out'
+
+# run this test only for following platforms.
+exclude_test = !['aix', 'centos', 'redhat', 'suse'].include?(ohai[:platform])
+describe Chef::Resource::RpmPackage, :requires_root, :external => exclude_test do
+  include Chef::Mixin::ShellOut
+
+  let(:new_resource) do
+    new_resource = Chef::Resource::RpmPackage.new(@pkg_name, run_context)
+    new_resource.source @pkg_path
+    new_resource
+  end
+
+  def rpm_pkg_should_be_installed(resource)
+    case ohai[:platform]
+    # Due to dependency issues , different rpm pkgs are used in different platforms.
+    # dummy rpm package works in aix, without any dependency issues.
+    when "aix"
+      expect(shell_out("rpm -qa | grep dummy").exitstatus).to eq(0)
+    # mytest rpm package works in centos, redhat and in suse without any dependency issues.
+    when "centos", "redhat", "suse"
+      expect(shell_out("rpm -qa | grep mytest").exitstatus).to eq(0)
+      ::File.exists?("/opt/mytest/mytest.sh") # The mytest rpm package contains the mytest.sh file
+    end
+  end
+
+  def rpm_pkg_should_not_be_installed(resource)
+    case ohai[:platform]
+    when "aix"
+      expect(shell_out("rpm -qa | grep dummy").exitstatus).to eq(1)
+    when "centos", "redhat", "suse"
+      expect(shell_out("rpm -qa | grep mytest").exitstatus).to eq(1)
+      !::File.exists?("/opt/mytest/mytest.sh")
+    end
+  end
+
+  before(:all) do
+    case ohai[:platform]
+    # Due to dependency issues , different rpm pkgs are used in different platforms.
+    when "aix"
+      @pkg_name = "dummy"
+      @pkg_version = "1-0"
+      @pkg_path = "/tmp/dummy-1-0.aix6.1.noarch.rpm"
+      FileUtils.cp 'spec/functional/assets/dummy-1-0.aix6.1.noarch.rpm' , @pkg_path
+    when "centos", "redhat", "suse"
+      @pkg_name = "mytest"
+      @pkg_version = "1.0-1"
+      @pkg_path = "/tmp/mytest-1.0-1.noarch.rpm"
+      FileUtils.cp 'spec/functional/assets/mytest-1.0-1.noarch.rpm' , @pkg_path
+    end
+  end
+
+  after(:all) do
+    FileUtils.rm @pkg_path
+  end
+
+  context "package install action" do
+    it "should create a package" do
+      new_resource.run_action(:install)
+      rpm_pkg_should_be_installed(new_resource)
+    end
+
+    after(:each) do
+      shell_out("rpm -qa | grep #{@pkg_name}-#{@pkg_version} | xargs rpm -e")
+    end
+  end
+
+  context "package remove action" do
+    before(:each) do
+      shell_out("rpm -i #{@pkg_path}")
+    end
+
+    it "should remove an existing package" do
+      new_resource.run_action(:remove)
+      rpm_pkg_should_not_be_installed(new_resource)
+    end
+  end
+
+  context "package upgrade action" do
+    before(:each) do
+      shell_out("rpm -i #{@pkg_path}")
+      if ohai[:platform] == 'aix'
+        @pkg_version = "2-0"
+        @pkg_path = "/tmp/dummy-2-0.aix6.1.noarch.rpm"
+        FileUtils.cp 'spec/functional/assets/dummy-2-0.aix6.1.noarch.rpm' , @pkg_path
+      else
+        @pkg_version = "2.0-1"
+        @pkg_path = "/tmp/mytest-2.0-1.noarch.rpm"
+        FileUtils.cp 'spec/functional/assets/mytest-2.0-1.noarch.rpm' , @pkg_path
+      end
+    end
+
+    it "should upgrade a package" do
+      new_resource.run_action(:install)
+      rpm_pkg_should_be_installed(new_resource)
+    end
+
+    after(:each) do
+      shell_out("rpm -qa | grep #{@pkg_name}-#{@pkg_version} | xargs rpm -e")
+      FileUtils.rm @pkg_path
+    end
+  end
+end
diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb
new file mode 100644
index 0000000..7816d63
--- /dev/null
+++ b/spec/functional/resource/template_spec.rb
@@ -0,0 +1,212 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::Template do
+
+  def binread(file)
+    File.open(file,"rb") {|f| f.read }
+  end
+
+  include_context Chef::Resource::File
+
+  let(:file_base) { "template_spec" }
+  let(:expected_content) { "slappiness is a warm gun" }
+
+  let(:node) do
+    node = Chef::Node.new
+    node.normal[:slappiness] = "a warm gun"
+    node
+  end
+
+  def create_resource
+    cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+    Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_repo) }
+    cl = Chef::CookbookLoader.new(cookbook_repo)
+    cl.load_cookbooks
+    cookbook_collection = Chef::CookbookCollection.new(cl)
+    events = Chef::EventDispatch::Dispatcher.new
+    run_context = Chef::RunContext.new(node, cookbook_collection, events)
+    resource = Chef::Resource::Template.new(path, run_context)
+    resource.source('openldap_stuff.conf.erb')
+    resource.cookbook('openldap')
+
+    # NOTE: partials rely on `cookbook_name` getting set by chef internals and
+    # ignore the user-set `cookbook` attribute.
+    resource.cookbook_name = "openldap"
+
+    resource
+  end
+
+  let(:resource) do
+    create_resource
+  end
+
+  let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+
+  it_behaves_like "a file resource"
+
+  it_behaves_like "a securable resource with reporting"
+
+  context "when the target file does not exist" do
+    it "creates the template with the rendered content using the variable attribute when the :create action is run" do
+      resource.source('openldap_variable_stuff.conf.erb')
+      resource.variables(:secret => "nutella")
+      resource.run_action(:create)
+      IO.read(path).should == "super secret is nutella"
+    end
+
+    it "creates the template with the rendered content using a local erb file when the :create action is run" do
+      resource.source(File.expand_path(File.join(CHEF_SPEC_DATA,'cookbooks','openldap','templates','default','openldap_stuff.conf.erb')))
+      resource.cookbook(nil)
+      resource.local(true)
+      resource.run_action(:create)
+      IO.read(path).should == expected_content
+    end
+  end
+
+  describe "when the template resource defines helper methods" do
+
+    include_context "diff disabled"
+
+    let(:resource) do
+      r = create_resource
+      r.source "helper_test.erb"
+      r
+    end
+
+    let(:expected_content) { "value from helper method" }
+
+    shared_examples "a template with helpers" do
+      it "generates expected content by calling helper methods" do
+        resource.run_action(:create)
+        binread(path).strip.should == expected_content
+      end
+    end
+
+    context "using single helper syntax" do
+      before do
+        resource.helper(:helper_method) { "value from helper method" }
+      end
+
+      it_behaves_like "a template with helpers"
+    end
+
+    context "using single helper syntax referencing @node" do
+      before do
+        node.set[:helper_test_attr] = "value from helper method"
+        resource.helper(:helper_method) { "#{@node[:helper_test_attr]}" }
+      end
+
+      it_behaves_like "a template with helpers"
+    end
+
+    context "using an inline block to define helpers" do
+      before do
+        resource.helpers do
+          def helper_method
+            "value from helper method"
+          end
+        end
+      end
+
+      it_behaves_like "a template with helpers"
+    end
+
+    context "using an inline block referencing @node" do
+      before do
+        node.set[:helper_test_attr] = "value from helper method"
+
+        resource.helpers do
+          def helper_method
+            @node[:helper_test_attr]
+          end
+        end
+      end
+
+      it_behaves_like "a template with helpers"
+
+    end
+
+    context "using a module from a library" do
+
+      module ExampleModule
+        def helper_method
+          "value from helper method"
+        end
+      end
+
+      before do
+        resource.helpers(ExampleModule)
+      end
+
+      it_behaves_like "a template with helpers"
+
+    end
+    context "using a module from a library referencing @node" do
+
+      module ExampleModuleReferencingATNode
+        def helper_method
+          @node[:helper_test_attr]
+        end
+      end
+
+      before do
+        node.set[:helper_test_attr] = "value from helper method"
+
+        resource.helpers(ExampleModuleReferencingATNode)
+      end
+
+      it_behaves_like "a template with helpers"
+
+    end
+
+    context "using helpers with partial templates" do
+      before do
+        resource.source("helpers_via_partial_test.erb")
+        resource.helper(:helper_method) { "value from helper method" }
+      end
+
+      it_behaves_like "a template with helpers"
+
+    end
+  end
+
+  describe "when template source contains windows style line endings" do
+    include_context "diff disabled"
+
+    ["all", "some", "no"].each do |test_case|
+      context "for #{test_case} lines" do
+        let(:resource) do
+          r = create_resource
+          r.source "#{test_case}_windows_line_endings.erb"
+          r
+        end
+
+        it "output should contain platform's line endings" do
+          resource.run_action(:create)
+          binread(path).each_line do |line|
+            line.should end_with(Chef::Platform.windows? ? "\r\n" : "\n")
+          end
+        end
+      end
+    end
+  end
+
+end
diff --git a/spec/functional/resource/user_spec.rb b/spec/functional/resource/user_spec.rb
new file mode 100644
index 0000000..92248a9
--- /dev/null
+++ b/spec/functional/resource/user_spec.rb
@@ -0,0 +1,548 @@
+# encoding: UTF-8
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/shell_out'
+
+metadata = { :unix_only => true,
+  :requires_root => true,
+  :provider => {:user => Chef::Provider::User::Useradd}
+}
+
+describe Chef::Resource::User, metadata do
+
+  include Chef::Mixin::ShellOut
+
+
+  # Utility code for /etc/passwd interaction, avoid any caching of user records:
+  PwEntry = Struct.new(:name, :passwd, :uid, :gid, :gecos, :home, :shell)
+
+  class UserNotFound < StandardError; end
+
+  def pw_entry
+    passwd_file = File.open("/etc/passwd", "rb") {|f| f.read}
+    matcher = /^#{Regexp.escape(username)}.+$/
+    if passwd_entry = passwd_file.scan(matcher).first
+      PwEntry.new(*passwd_entry.split(':'))
+    else
+      raise UserNotFound, "no entry matching #{matcher.inspect} found in /etc/passwd"
+    end
+  end
+
+  def etc_shadow
+    File.open("/etc/shadow") {|f| f.read }
+  end
+
+  def supports_quote_in_username?
+    OHAI_SYSTEM["platform_family"] == "debian"
+  end
+
+  before do
+    pending "porting implementation for user provider in aix" if OHAI_SYSTEM[:platform] == 'aix'
+    # Silence shell_out live stream
+    Chef::Log.level = :warn
+  end
+
+  after do
+    begin
+      pw_entry # will raise if the user doesn't exist
+      shell_out!("userdel", "-f", "-r", username, :returns => [0,12])
+    rescue UserNotFound
+      # nothing to remove
+    end
+  end
+
+  let(:node) do
+    n = Chef::Node.new
+    n.consume_external_attrs(OHAI_SYSTEM.data.dup, {})
+    n
+  end
+
+  let(:events) do
+    Chef::EventDispatch::Dispatcher.new
+  end
+
+  let(:run_context) do
+    Chef::RunContext.new(node, {}, events)
+  end
+
+  let(:username) do
+    "chef-functional-test"
+  end
+
+  let(:uid) { nil }
+  let(:home) { nil }
+  let(:manage_home) { false }
+  let(:password) { nil }
+  let(:system) { false }
+  let(:comment) { nil }
+
+  let(:user_resource) do
+    r = Chef::Resource::User.new("TEST USER RESOURCE", run_context)
+    r.username(username)
+    r.uid(uid)
+    r.home(home)
+    r.comment(comment)
+    r.manage_home(manage_home)
+    r.password(password)
+    r.system(system)
+    r
+  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)
+        user_resource.should be_updated_by_last_action
+      end
+
+
+      it "ensures the user exists" do
+        pw_entry.name.should == username
+      end
+
+      #  On Debian, the only constraints are that usernames must neither start
+      #  with a dash ('-') nor plus ('+') nor tilde ('~') nor contain a colon
+      #  (':'), a comma (','), or a whitespace (space: ' ', end of line: '\n',
+      #  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
+
+        let(:username) { "t'bilisi" }
+
+        it "ensures the user exists" do
+          pw_entry.name.should == username
+        end
+      end
+
+
+      context "when uid is set" do
+        # Should verify uid not in use...
+        let(:uid) { 1999 }
+
+        it "ensures the user has the given uid" do
+          pw_entry.uid.should == "1999"
+        end
+      end
+
+      context "when comment is set" do
+        let(:comment) { "hello this is dog" }
+
+        it "ensures the comment is set" do
+          pw_entry.gecos.should == "hello this is dog"
+        end
+
+        context "in standard gecos format" do
+          let(:comment) { "Bobo T. Clown,some building,555-555-5555, at boboclown" }
+
+          it "ensures the comment is set" do
+            pw_entry.gecos.should == comment
+          end
+        end
+
+        context "to a string containing multibyte characters" do
+          let(:comment) { "(╯°□°)╯︵ ┻━┻" }
+
+          it "ensures the comment is set" do
+            actual = pw_entry.gecos
+            actual.force_encoding(Encoding::UTF_8) if "".respond_to?(:force_encoding)
+            actual.should == comment
+          end
+        end
+
+        context "to a string containing an apostrophe `'`" do
+          let(:comment) { "don't go" }
+
+          it "ensures the comment is set" do
+            pw_entry.gecos.should == comment
+          end
+        end
+      end
+
+      context "when home is set" do
+        let(:home) { "/home/#{username}" }
+
+        it "ensures the user's home is set to the given path" do
+          pw_entry.home.should == "/home/#{username}"
+        end
+
+        if OHAI_SYSTEM["platform_family"] == "rhel"
+          # Inconsistent behavior. See: CHEF-2205
+          it "creates the home dir when not explicitly asked to on RHEL (XXX)" do
+            File.should exist("/home/#{username}")
+          end
+        else
+          it "does not create the home dir without `manage_home'" do
+            File.should_not exist("/home/#{username}")
+          end
+        end
+
+        context "and manage_home is enabled" do
+          let(:manage_home) { true }
+
+          it "ensures the user's home directory exists" do
+            File.should exist("/home/#{username}")
+          end
+        end
+      end
+
+      context "when a password is specified" do
+        # openssl passwd -1 "secretpassword"
+        let(:password) { "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" }
+        it "sets the user's shadow password" do
+          pw_entry.passwd.should == "x"
+          expected_shadow = "chef-functional-test:$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/"
+          etc_shadow.should include(expected_shadow)
+        end
+      end
+
+      context "when a system user is specified" do
+        let(:system) { true }
+        let(:uid_min) do
+          # from `man useradd`, login user means uid will be between
+          # UID_SYS_MIN and UID_SYS_MAX defined in /etc/login.defs. On my
+          # Ubuntu 13.04 system, these are commented out, so we'll look at
+          # UID_MIN to find the lower limit of the non-system-user range, and
+          # use that value in our assertions.
+          login_defs = File.open("/etc/login.defs", "rb") {|f| f.read }
+          uid_min_scan = /^UID_MIN\s+(\d+)/
+          login_defs.match(uid_min_scan)[1]
+        end
+
+        it "ensures the user has the properties of a system user" do
+          pw_entry.uid.to_i.should be < uid_min.to_i
+        end
+      end
+    end # when the user does not exist beforehand
+
+    context "when the user already exists" do
+
+      let(:expect_updated?) { true }
+
+      let(:existing_uid) { nil }
+      let(:existing_home) { nil }
+      let(:existing_manage_home) { false }
+      let(:existing_password) { nil }
+      let(:existing_system) { false }
+      let(:existing_comment) { nil }
+
+      let(:existing_user) do
+        r = Chef::Resource::User.new("TEST USER RESOURCE", run_context)
+        # username is identity attr, must match.
+        r.username(username)
+        r.uid(existing_uid)
+        r.home(existing_home)
+        r.comment(existing_comment)
+        r.manage_home(existing_manage_home)
+        r.password(existing_password)
+        r.system(existing_system)
+        r
+      end
+
+      before do
+        if reason = skip
+          pending(reason)
+        end
+        existing_user.run_action(:create)
+        existing_user.should be_updated_by_last_action
+        user_resource.run_action(:create)
+        user_resource.updated_by_last_action?.should == expect_updated?
+      end
+
+      context "and all properties are in the desired state" do
+        let(:uid) { 1999 }
+        let(:home) { "/home/bobo" }
+        let(:manage_home) { true }
+        # openssl passwd -1 "secretpassword"
+        let(:password) { "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" }
+        let(:system) { false }
+        let(:comment) { "hello this is dog" }
+
+        let(:existing_uid) { uid }
+        let(:existing_home) { home }
+        let(:existing_manage_home) { manage_home }
+        let(:existing_password) { password }
+        let(:existing_system) { false }
+        let(:existing_comment) { comment }
+
+        let(:expect_updated?) { false }
+
+        it "does not update the user" do
+          user_resource.should_not be_updated
+        end
+      end
+
+      context "and the uid is updated" do
+        let(:uid) { 1999 }
+        let(:existing_uid) { 1998 }
+
+        it "ensures the uid is set to the desired value" do
+          pw_entry.uid.should == "1999"
+        end
+      end
+
+      context "and the comment is updated" do
+        let(:comment) { "hello this is dog" }
+        let(:existing_comment) { "woof" }
+
+        it "ensures the comment field is set to the desired value" do
+          pw_entry.gecos.should == "hello this is dog"
+        end
+      end
+
+      context "and home directory is updated" do
+        let(:existing_home) { "/home/foo" }
+        let(:home) { "/home/bar" }
+        it "ensures the home directory is set to the desired value" do
+          pw_entry.home.should == "/home/bar"
+        end
+
+        context "and manage_home is enabled" do
+          let(:existing_manage_home) { true }
+          let(:manage_home) { true }
+          it "moves the home directory to the new location" do
+            File.should_not exist("/home/foo")
+            File.should exist("/home/bar")
+          end
+        end
+
+        context "and manage_home wasn't enabled but is now" do
+          let(:existing_manage_home) { false }
+          let(:manage_home) { true }
+
+          if OHAI_SYSTEM["platform_family"] == "rhel"
+            # Inconsistent behavior. See: CHEF-2205
+            it "created the home dir b/c of CHEF-2205 so it still exists" do
+              # This behavior seems contrary to expectation and non-convergent.
+              File.should_not exist("/home/foo")
+              File.should exist("/home/bar")
+            end
+          else
+            it "does not create the home dir in the desired location (XXX)" do
+              # This behavior seems contrary to expectation and non-convergent.
+              File.should_not exist("/home/foo")
+              File.should_not exist("/home/bar")
+            end
+          end
+        end
+
+        context "and manage_home was enabled but is not now" do
+          let(:existing_manage_home) { true }
+          let(:manage_home) { false }
+
+          it "leaves the old home directory around (XXX)" do
+            # Would it be better to remove the old home?
+            File.should exist("/home/foo")
+            File.should_not exist("/home/bar")
+          end
+        end
+      end
+
+      context "and a password is added" do
+        # openssl passwd -1 "secretpassword"
+        let(:password) { "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" }
+
+        it "ensures the password is set" do
+          pw_entry.passwd.should == "x"
+          expected_shadow = "chef-functional-test:$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/"
+          etc_shadow.should include(expected_shadow)
+        end
+
+      end
+
+      context "and the password is updated" do
+        # openssl passwd -1 "OLDpassword"
+        let(:existing_password) { "$1$1dVmwm4z$CftsFn8eBDjDRUytYKkXB." }
+        # openssl passwd -1 "secretpassword"
+        let(:password) { "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" }
+
+        it "ensures the password is set to the desired value" do
+          pw_entry.passwd.should == "x"
+          expected_shadow = "chef-functional-test:$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/"
+          etc_shadow.should include(expected_shadow)
+        end
+      end
+
+      context "and the user is changed from not-system to system" do
+        let(:existing_system) { false }
+        let(:system) { true }
+
+        let(:expect_updated?) { false }
+
+        it "does not modify the user at all" do
+        end
+      end
+
+      context "and the user is changed from system to not-system" do
+        let(:existing_system) { true }
+        let(:system) { false }
+
+        let(:expect_updated?) { false }
+
+        it "does not modify the user at all" do
+        end
+      end
+
+    end # when the user already exists
+  end # action :create
+
+  shared_context "user exists for lock/unlock" do
+    let(:user_locked_context?) { false }
+
+    def shadow_entry
+      etc_shadow.lines.select {|l| l.include?(username) }.first
+    end
+
+    def shadow_password
+      shadow_entry.split(':')[1]
+    end
+
+    before do
+      # create user and setup locked/unlocked state
+      user_resource.dup.run_action(:create)
+
+      if user_locked_context?
+        shell_out!("usermod -L #{username}")
+        shadow_password.should include("!")
+      elsif password
+        shadow_password.should_not include("!")
+      end
+    end
+  end
+
+  describe "action :lock" do
+    context "when the user does not exist" do
+      it "raises a sensible error" do
+        expect { user_resource.run_action(:lock) }.to raise_error(Chef::Exceptions::User)
+      end
+    end
+
+    context "when the user exists" do
+
+      include_context "user exists for lock/unlock"
+
+      before do
+        user_resource.run_action(:lock)
+      end
+
+      context "and the user is not locked" do
+        # user will be locked if it has no password
+        let(:password) { "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" }
+
+        it "locks the user's password" do
+          shadow_password.should include("!")
+        end
+      end
+
+      context "and the user is locked" do
+        # user will be locked if it has no password
+        let(:password) { "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" }
+        let(:user_locked_context?) { true }
+        it "does not update the user" do
+          user_resource.should_not be_updated_by_last_action
+        end
+      end
+    end
+  end # action :lock
+
+  describe "action :unlock" do
+    context "when the user does not exist" do
+      it "raises a sensible error" do
+        expect { user_resource.run_action(:unlock) }.to raise_error(Chef::Exceptions::User)
+      end
+    end
+
+    context "when the user exists" do
+
+      include_context "user exists for lock/unlock"
+
+      before do
+        begin
+          user_resource.run_action(:unlock)
+          @error = nil
+        rescue Exception => e
+          @error = e
+        end
+      end
+
+      context "and has no password" do
+
+        # TODO: platform_family should be setup in spec_helper w/ tags
+        if %w[suse opensuse].include?(OHAI_SYSTEM["platform_family"])
+          # suse gets this right:
+          it "errors out trying to unlock the user" do
+            @error.should be_a(Mixlib::ShellOut::ShellCommandFailed)
+            @error.message.should include("Cannot unlock the password")
+          end
+        else
+
+          # borked on all other platforms:
+          it "is marked as updated but doesn't modify the user (XXX)" do
+            # This should be an error instead; note that usermod still exits 0
+            # (which is probably why this case silently fails):
+            #
+            # DEBUG: ---- Begin output of usermod -U chef-functional-test ----
+            # DEBUG: STDOUT:
+            # DEBUG: STDERR: usermod: unlocking the user's password would result in a passwordless account.
+            # You should set a password with usermod -p to unlock this user's password.
+            # DEBUG: ---- End output of usermod -U chef-functional-test ----
+            # DEBUG: Ran usermod -U chef-functional-test returned 0
+            @error.should be_nil
+            pw_entry.passwd.should == 'x'
+            shadow_password.should == "!"
+          end
+        end
+      end
+
+      context "and has a password" do
+        let(:password) { "$1$RRa/wMM/$XltKfoX5ffnexVF4dHZZf/" }
+        context "and the user is not locked" do
+          it "does not update the user" do
+            user_resource.should_not be_updated_by_last_action
+          end
+        end
+
+        context "and the user is locked" do
+          let(:user_locked_context?) { true }
+
+          it "unlocks the user's password" do
+            shadow_entry = etc_shadow.lines.select {|l| l.include?(username) }.first
+            shadow_password = shadow_entry.split(':')[1]
+            shadow_password.should_not include("!")
+          end
+        end
+      end
+    end
+  end # action :unlock
+
+end
diff --git a/spec/functional/run_lock_spec.rb b/spec/functional/run_lock_spec.rb
new file mode 100644
index 0000000..51645c0
--- /dev/null
+++ b/spec/functional/run_lock_spec.rb
@@ -0,0 +1,286 @@
+#
+# Author:: Daniel DeLeo (<dan 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 File.expand_path('../../spec_helper', __FILE__)
+require 'chef/client'
+
+describe Chef::RunLock do
+
+  # This behavior works on windows, but the tests use fork :(
+  describe "when locking the chef-client run", :unix_only => true do
+
+    ##
+    # Lockfile location and helpers
+
+    let(:random_temp_root) do
+      Kernel.srand(Time.now.to_i + Process.pid)
+      "/tmp/#{Kernel.rand(Time.now.to_i + Process.pid)}"
+    end
+
+    let(:lockfile){ "#{random_temp_root}/this/long/path/does/not/exist/chef-client-running.pid" }
+
+    # make sure to start with a clean slate.
+    before(:each){ FileUtils.rm_r(random_temp_root) if File.exist?(random_temp_root) }
+    after(:each){ FileUtils.rm_r(random_temp_root) }
+
+    def wait_on_lock
+      tries = 0
+      until File.exist?(lockfile)
+        raise "Lockfile never created, abandoning test" if tries > 10
+        tries += 1
+        sleep 0.1
+      end
+    end
+
+    ##
+    # Side channel via a pipe allows child processes to send errors to the parent
+
+    # Don't lazy create the pipe or else we might not share it with subprocesses
+    let!(:error_pipe) do
+      r,w = IO.pipe
+      w.sync = true
+      [r,w]
+    end
+
+    let(:error_read) { error_pipe[0] }
+    let(:error_write) { error_pipe[1] }
+
+    after do
+      error_read.close unless error_read.closed?
+      error_write.close unless error_write.closed?
+    end
+
+    # Send a RuntimeError from the child process to the parent process. Also
+    # prints error to $stdout, just in case something goes wrong with the error
+    # marshaling stuff.
+    def send_side_channel_error(message)
+      $stderr.puts(message)
+      $stderr.puts(caller)
+      e = RuntimeError.new(message)
+      error_write.print(Marshal.dump(e))
+    end
+
+    # Read the error (if any) from the error channel. If a marhaled error is
+    # present, it is unmarshaled and raised (which will fail the test)
+    def raise_side_channel_error!
+      error_write.close
+      err = error_read.read
+      error_read.close
+      begin
+        # ArgumentError from Marshal.load indicates no data, which we assume
+        # means no error in child process.
+        raise Marshal.load(err)
+      rescue ArgumentError
+        nil
+      end
+    end
+
+    ##
+    # Interprocess synchronization via a pipe. This allows us to control the
+    # state of the processes competing over the lock without relying on sleep.
+
+    let!(:sync_pipe) do
+      r,w = IO.pipe
+      w.sync = true
+      [r,w]
+    end
+    let(:sync_read) { sync_pipe[0] }
+    let(:sync_write) { sync_pipe[1] }
+
+    after do
+      sync_read.close unless sync_read.closed?
+      sync_write.close unless sync_write.closed?
+    end
+
+    # Wait on synchronization signal. If not received within the timeout, an
+    # error is sent via the error channel, and the process exits.
+    def sync_wait
+      if IO.select([sync_read], nil, nil, 20).nil?
+        # timeout reading from the sync pipe.
+        send_side_channel_error("Error syncing processes in run lock test (timeout)")
+        exit!(1)
+      else
+        sync_read.getc
+      end
+    end
+
+    # Sends a character in the sync pipe, which wakes ("unlocks") another
+    # process that is waiting on the sync signal
+    def sync_send
+      sync_write.putc("!")
+      sync_write.flush
+    end
+
+    ##
+    # IPC to record test results in a pipe. Tests can read pipe contents to
+    # check that operations occur in the expected order.
+
+    let!(:results_pipe) do
+      r,w = IO.pipe
+      w.sync = true
+      [r,w]
+    end
+    let(:results_read) { results_pipe[0] }
+    let(:results_write) { results_pipe[1] }
+
+    after do
+      results_read.close unless results_read.closed?
+      results_write.close unless results_write.closed?
+    end
+
+    # writes the message to the results pipe for later checking.
+    # note that nothing accounts for the pipe filling and waiting forever on a
+    # read or write call, so don't put too much data in.
+    def record(message)
+      results_write.puts(message)
+      results_write.flush
+    end
+
+    def results
+      results_write.flush
+      results_write.close
+      message = results_read.read
+      results_read.close
+      message
+    end
+
+    ##
+    # Run lock is the system under test
+    let!(:run_lock) { Chef::RunLock.new(lockfile) }
+
+    it "creates the full path to the lockfile" do
+      lambda { run_lock.acquire }.should_not raise_error(Errno::ENOENT)
+      File.should exist(lockfile)
+    end
+
+    it "sets FD_CLOEXEC on the lockfile", :supports_cloexec => true do
+      run_lock.acquire
+      (run_lock.runlock.fcntl(Fcntl::F_GETFD, 0) & Fcntl::FD_CLOEXEC).should == Fcntl::FD_CLOEXEC
+    end
+
+    it "allows only one chef client run per lockfile" do
+      # First process, gets the lock and keeps it.
+      p1 = fork do
+        run_lock.acquire
+        record "p1 has lock"
+        # Wait until the other process is trying to get the lock:
+        sync_wait
+        # sleep a little bit to make process p2 wait on the lock
+        sleep 2
+        record "p1 releasing lock"
+        run_lock.release
+        exit!(0)
+      end
+
+      # Wait until p1 creates the lockfile
+      wait_on_lock
+
+      p2 = fork do
+        # inform process p1 that we're trying to get the lock
+        sync_send
+        run_lock.acquire
+        record "p2 has lock"
+        run_lock.release
+        exit!(0)
+      end
+
+      Process.waitpid2(p1)
+      Process.waitpid2(p2)
+
+      raise_side_channel_error!
+
+      expected=<<-E
+p1 has lock
+p1 releasing lock
+p2 has lock
+E
+      results.should == expected
+    end
+
+    it "clears the lock if the process dies unexpectedly" do
+      p1 = fork do
+        run_lock.acquire
+        record "p1 has lock"
+        sleep 60
+        record "p1 still has lock"
+        exit! 1
+      end
+
+      wait_on_lock
+      Process.kill(:KILL, p1)
+      Process.waitpid2(p1)
+
+      p2 = fork do
+        run_lock.acquire
+        record "p2 has lock"
+        run_lock.release
+        exit! 0
+      end
+
+      Process.waitpid2(p2)
+
+      results.should =~ /p2 has lock\Z/
+    end
+
+    it "test returns true and acquires the lock" do
+      p1 = fork do
+        run_lock.test.should == true
+        sleep 2
+        exit! 1
+      end
+
+      wait_on_lock
+
+      p2 = fork do
+        run_lock.test.should == false
+        exit! 0
+      end
+
+      Process.waitpid2(p2)
+      Process.waitpid2(p1)
+    end
+
+    it "test returns without waiting when the lock is acquired" do
+      p1 = fork do
+        run_lock.acquire
+        sleep 2
+        exit! 1
+      end
+
+      wait_on_lock
+
+      run_lock.test.should == false
+      Process.waitpid2(p1)
+    end
+
+    it "doesn't truncate the lock file so that contents can be read" do
+      p1 = fork do
+        run_lock.acquire
+        run_lock.save_pid
+        sleep 2
+        exit! 1
+      end
+
+      wait_on_lock
+      File.read(lockfile).should == p1.to_s
+
+      Process.waitpid2(p1)
+    end
+
+  end
+end
+
diff --git a/spec/functional/shell_spec.rb b/spec/functional/shell_spec.rb
new file mode 100644
index 0000000..64bd28f
--- /dev/null
+++ b/spec/functional/shell_spec.rb
@@ -0,0 +1,122 @@
+#
+# Author:: Daniel DeLeo (<dan 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 'functional/resource/base'
+require 'chef/version'
+require 'chef/shell'
+require 'chef/mixin/command/unix'
+
+describe Shell do
+
+  # chef-shell's unit tests are by necessity very mock-heavy, and frequently do
+  # not catch cases where chef-shell fails to boot because of changes in
+  # chef/client.rb
+  describe "smoke tests", :unix_only => true do
+    include Chef::Mixin::Command::Unix
+
+    def read_until(io, expected_value)
+      start = Time.new
+      buffer = ""
+      until buffer.include?(expected_value)
+        begin
+          buffer << io.read_nonblock(1)
+        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
+        end
+      end
+      buffer
+    end
+
+    def wait_or_die(pid)
+      start = Time.new
+
+      until exitstatus = Process.waitpid2(pid, Process::WNOHANG)
+        if Time.new - start > 5
+          STDERR.puts("chef-shell tty did not exit cleanly, killing it")
+          Process.kill(:KILL, pid)
+        end
+        sleep 0.01
+      end
+      exitstatus[1]
+    end
+
+    def run_chef_shell_with(options)
+      case ohai[:platform]
+      when "aix"
+        config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA)
+        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 >")
+          yield stdout, stdin if block_given?
+          stdin.write("'done'\n")
+          output = read_until(stdout, '=> "done"')
+          stdin.print("exit\n")
+          read_until(stdout, "\n")
+        end
+
+        [output, status.exitstatus]
+      else
+        # Windows ruby installs don't (always?) have PTY,
+        # so hide the require here
+        begin
+          require 'pty'
+          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 >")
+          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")
+          writer.close
+
+          exitstatus = wait_or_die(pid)
+
+          [output, exitstatus]
+        rescue PTY::ChildExited => e
+          [output, e.status]
+        end
+      end
+    end
+
+    it "boots correctly with -lauto" do
+      output, exitstatus = run_chef_shell_with("-lauto")
+      output.should include("done")
+      expect(exitstatus).to eq(0)
+    end
+
+    it "sets the log_level from the command line" do
+      output, exitstatus = run_chef_shell_with("-lfatal") do |out, keyboard|
+        show_log_level_code = %q[puts "===#{Chef::Log.level}==="]
+        keyboard.puts(show_log_level_code)
+        read_until(out, show_log_level_code)
+      end
+      output.should include("===fatal===")
+      expect(exitstatus).to eq(0)
+    end
+  end
+end
diff --git a/spec/functional/tiny_server_spec.rb b/spec/functional/tiny_server_spec.rb
new file mode 100644
index 0000000..4262d9e
--- /dev/null
+++ b/spec/functional/tiny_server_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tiny_server'
+
+describe TinyServer::API do
+  before do
+    @api = TinyServer::API.instance
+    @api.clear
+  end
+
+  it "is a Singleton" do
+    lambda {TinyServer::API.new}.should raise_error
+  end
+
+  it "clears the router" do
+    @api.get('/blargh', 200, "blargh")
+    @api.clear
+    @api.routes["GET"].should be_empty
+  end
+
+  it "creates a route for a GET request" do
+    @api.get('/foo/bar', 200, 'hello foobar')
+    # WEBrick gives you the full URI with host, Thin only gave the part after scheme+host+port
+    response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => 'http://localhost:1974/foo/bar')
+    response.should == [200, {'Content-Type' => 'application/json'}, [ 'hello foobar' ]]
+  end
+
+  it "creates a route for a request with a block" do
+    block_called = false
+    @api.get('/bar/baz', 200) { block_called = true; 'hello barbaz' }
+    response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => 'http://localhost:1974/bar/baz')
+    response.should == [200, {'Content-Type' => 'application/json'}, [ 'hello barbaz' ]]
+    block_called.should be_true
+  end
+
+  it "returns debugging info for 404s" do
+    response = @api.call("REQUEST_METHOD" => "GET", "REQUEST_URI" => '/no_such_thing')
+    response[0].should == 404
+    response[1].should == {'Content-Type' => 'application/json'}
+    response[2].should be_a_kind_of(String)
+    response_obj = Chef::JSONCompat.from_json(response[2])
+    response_obj["message"].should == "no data matches the request for /no_such_thing"
+    response_obj["available_routes"].should == {"GET"=>[], "PUT"=>[], "POST"=>[], "DELETE"=>[]}
+    response_obj["request"].should == {"REQUEST_METHOD"=>"GET", "REQUEST_URI"=>"/no_such_thing"}
+  end
+
+end
+
+describe TinyServer::Manager do
+  it "runs the server" do
+    @server = TinyServer::Manager.new
+    @server.start
+
+    TinyServer::API.instance.get("/index", 200, "[\"hello\"]")
+
+    rest = Chef::REST.new('http://localhost:9000', false, false)
+    rest.get_rest("index").should == ["hello"]
+
+    @server.stop
+  end
+end
diff --git a/spec/functional/version_spec.rb b/spec/functional/version_spec.rb
new file mode 100644
index 0000000..a342206
--- /dev/null
+++ b/spec/functional/version_spec.rb
@@ -0,0 +1,35 @@
+#
+# Author:: Serdar Sutay (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require File.expand_path('../../spec_helper', __FILE__)
+require 'chef/mixin/shell_out'
+require 'chef/version'
+require 'ohai/version'
+
+describe "Chef Versions" do
+  include Chef::Mixin::ShellOut
+  let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..") }
+
+  binaries = [ "chef-client", "chef-shell", "chef-apply", "knife", "chef-solo" ]
+
+  binaries.each do |binary|
+    it "#{binary} version should be sane" do
+      shell_out!("ruby #{File.join("bin", binary)} -v", :cwd => chef_dir).stdout.chomp.should == "Chef: #{Chef::VERSION}"
+    end
+  end
+
+end
diff --git a/spec/functional/win32/registry_helper_spec.rb b/spec/functional/win32/registry_helper_spec.rb
new file mode 100644
index 0000000..830d6f4
--- /dev/null
+++ b/spec/functional/win32/registry_helper_spec.rb
@@ -0,0 +1,632 @@
+#
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Author:: Lamont Granquist (<lamont 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/win32/registry'
+
+describe Chef::Resource::RegistryKey, :unix_only do
+  before(:all) do
+    events = Chef::EventDispatch::Dispatcher.new
+    node = Chef::Node.new
+    ohai = Ohai::System.new
+    ohai.all_plugins
+    node.consume_external_attrs(ohai.data,{})
+    run_context = Chef::RunContext.new(node, {}, events)
+    @resource = Chef::Resource::RegistryKey.new("HKCU\\Software", run_context)
+  end
+  context "when load_current_resource is run on a non-windows node" do
+    it "throws an exception because you don't have a windows registry (derp)" do
+      @resource.key("HKCU\\Software\\Opscode")
+      @resource.values([{:name=>"Color", :type=>:string, :data=>"Orange"}])
+      lambda{@resource.run_action(:create)}.should raise_error(Chef::Exceptions::Win32NotWindows)
+    end
+  end
+end
+
+describe 'Chef::Win32::Registry', :windows_only do
+
+  before(:all) do
+    #Create a registry item
+    ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root"
+    ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch"
+    ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Flower"
+    ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root', Win32::Registry::KEY_ALL_ACCESS) do |reg|
+      reg['RootType1', Win32::Registry::REG_SZ] = 'fibrous'
+      reg.write('Roots', Win32::Registry::REG_MULTI_SZ, ["strong roots", "healthy tree"])
+    end
+    ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch', Win32::Registry::KEY_ALL_ACCESS) do |reg|
+      reg['Strong', Win32::Registry::REG_SZ] = 'bird nest'
+    end
+    ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Flower', Win32::Registry::KEY_ALL_ACCESS) do |reg|
+      reg['Petals', Win32::Registry::REG_MULTI_SZ] = ["Pink", "Delicate"]
+    end
+
+    #Create the node with ohai data
+    events = Chef::EventDispatch::Dispatcher.new
+    @node = Chef::Node.new
+    ohai = Ohai::System.new
+    ohai.all_plugins
+    @node.consume_external_attrs(ohai.data,{})
+    @run_context = Chef::RunContext.new(@node, {}, events)
+
+    #Create a registry object that has access ot the node previously created
+    @registry = Chef::Win32::Registry.new(@run_context)
+  end
+
+  #Delete what is left of the registry key-values previously created
+  after(:all) do
+    ::Win32::Registry::HKEY_CURRENT_USER.open("Software") do |reg|
+      reg.delete_key("Root", true)
+    end
+  end
+
+  # Server Versions
+  # it "succeeds if server versiion is 2003R2, 2008, 2008R2, 2012" do
+  # end
+  # it "falis if the server versions are anything else" do
+  # end
+
+  describe "hive_exists?" do
+    it "returns true if the hive exists" do
+      @registry.hive_exists?("HKCU\\Software\\Root").should == true
+    end
+
+    it "returns false if the hive does not exist" do
+      hive = @registry.hive_exists?("LYRU\\Software\\Root").should == false
+    end
+  end
+
+  describe "key_exists?" do
+    it "returns true if the key path exists" do
+      @registry.key_exists?("HKCU\\Software\\Root\\Branch\\Flower").should == true
+    end
+
+    it "returns false if the key path does not exist" do
+      @registry.key_exists?("HKCU\\Software\\Branch\\Flower").should == false
+    end
+
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.key_exists?("JKLM\\Software\\Branch\\Flower")}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+  end
+
+  describe "key_exists!" do
+    it "returns true if the key path exists" do
+      @registry.key_exists!("HKCU\\Software\\Root\\Branch\\Flower").should == true
+    end
+
+    it "throws an exception if the key path does not exist" do
+      lambda {@registry.key_exists!("HKCU\\Software\\Branch\\Flower")}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.key_exists!("JKLM\\Software\\Branch\\Flower")}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+  end
+
+  describe "value_exists?" do
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.value_exists?("JKLM\\Software\\Branch\\Flower", {:name=>"Petals"})}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+    it "throws an exception if the key does not exist" do
+      lambda {@registry.value_exists?("HKCU\\Software\\Branch\\Flower", {:name=>"Petals"})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+    it "returns true if the value exists" do
+      @registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"}).should == true
+    end
+    it "returns false if the value does not exist" do
+      @registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"}).should == false
+    end
+  end
+
+  describe "value_exists!" do
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.value_exists!("JKLM\\Software\\Branch\\Flower", {:name=>"Petals"})}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+    it "throws an exception if the key does not exist" do
+      lambda {@registry.value_exists!("HKCU\\Software\\Branch\\Flower", {:name=>"Petals"})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+    it "returns true if the value exists" do
+      @registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"}).should == true
+    end
+    it "throws an exception if the value does not exist" do
+      lambda {@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})}.should raise_error(Chef::Exceptions::Win32RegValueMissing)
+    end
+  end
+
+  describe "data_exists?" do
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.data_exists?("JKLM\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+    it "throws an exception if the key does not exist" do
+      lambda {@registry.data_exists?("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+    it "returns true if all the data matches" do
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]}).should == true
+    end
+    it "returns false if the name does not exist" do
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]}).should == false
+    end
+    it "returns false if the types do not match" do
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Pink"}).should == false
+    end
+    it "returns false if the data does not match" do
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Mauve", "Delicate"]}).should == false
+    end
+  end
+
+  describe "data_exists!" do
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.data_exists!("JKLM\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+    it "throws an exception if the key does not exist" do
+      lambda {@registry.data_exists!("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+    it "returns true if all the data matches" do
+      @registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]}).should == true
+    end
+    it "throws an exception if the name does not exist" do
+      lambda {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.should raise_error(Chef::Exceptions::Win32RegDataMissing)
+    end
+    it "throws an exception if the types do not match" do
+      lambda {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Pink"})}.should raise_error(Chef::Exceptions::Win32RegDataMissing)
+    end
+    it "throws an exception if the data does not match" do
+      lambda {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Mauve", "Delicate"]})}.should raise_error(Chef::Exceptions::Win32RegDataMissing)
+    end
+  end
+
+  describe "get_values" do
+    it "returns all values for a key if it exists" do
+      values = @registry.get_values("HKCU\\Software\\Root")
+      values.should be_an_instance_of Array
+      values.should == [{:name=>"RootType1", :type=>:string, :data=>"fibrous"},
+                        {:name=>"Roots", :type=>:multi_string, :data=>["strong roots", "healthy tree"]}]
+    end
+
+    it "throws an exception if the key does not exist" do
+      lambda {@registry.get_values("HKCU\\Software\\Branch\\Flower")}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.get_values("JKLM\\Software\\Branch\\Flower")}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+  end
+
+  describe "set_value" do
+    it "updates a value if the key, value exist and type matches and value different" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]}).should == true
+    end
+
+    it "updates a value if the type does match and the values are different" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Yellow"}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:string, :data=>"Yellow"}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]}).should == false
+    end
+
+    it "creates a value if key exists and value does not" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]}).should == true
+    end
+
+    it "does nothing if data,type and name parameters for the value are same" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]}).should == false
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Stamen", :type=>:multi_string, :data=>["Yellow", "Changed Color"]}).should == true
+    end
+
+    it "throws an exception if the key does not exist" do
+      lambda {@registry.set_value("HKCU\\Software\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.set_value("JKLM\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Yellow", "Changed Color"]})}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+
+    # we are validating that the data gets .to_i called on it when type is a :dword
+
+    it "casts an integer string given as a dword into an integer" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe32767", :type=>:dword, :data=>"32767"}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe32767", :type=>:dword, :data=>32767}).should == true
+    end
+
+    it "casts a nonsense string given as a dword into zero" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeZero", :type=>:dword, :data=>"whatdoesthisdo"}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeZero", :type=>:dword, :data=>0}).should == true
+    end
+
+    it "throws an exception when trying to cast an array to an int for a dword" do
+      lambda {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldThrow", :type=>:dword, :data=>["one","two"]})}.should raise_error
+    end
+
+    # we are validating that the data gets .to_s called on it when type is a :string
+
+    it "stores the string representation of an array into a string if you pass it an array" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBePainful", :type=>:string, :data=>["one","two"]}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBePainful", :type=>:string, :data=>'["one", "two"]'}).should == true
+    end
+
+    it "stores the string representation of a number into a string if you pass it an number" do
+      @registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe65535", :type=>:string, :data=>65535}).should == true
+      @registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBe65535", :type=>:string, :data=>"65535"}).should == true
+    end
+
+    # we are validating that the data gets .to_a called on it when type is a :multi_string
+
+    it "throws an exception when a multi-string is passed a number" do
+      lambda {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldThrow", :type=>:multi_string, :data=>65535})}.should raise_error
+    end
+
+    it "throws an exception when a multi-string is passed a string" do
+      lambda {@registry.set_value("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"ShouldBeWat", :type=>:multi_string, :data=>"foo"})}.should raise_error
+    end
+  end
+
+  describe "create_key" do
+    before(:all) do
+      ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root") do |reg|
+        begin
+          reg.delete_key("Trunk", true)
+        rescue
+        end
+      end
+    end
+
+    it "throws an exception if the path has missing keys but recursive set to false" do
+      lambda {@registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)}.should raise_error(Chef::Exceptions::Win32RegNoRecursive)
+      @registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker").should == false
+    end
+
+    it "creates the key_path if the keys were missing but recursive was set to true" do
+      @registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", true).should == true
+      @registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker").should == true
+    end
+
+    it "does nothing if the key already exists" do
+      @registry.create_key("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", false).should == true
+      @registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker").should == true
+    end
+
+    it "throws an exception of the hive does not exist" do
+      lambda {@registry.create_key("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", false)}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+  end
+
+  describe "delete_value" do
+    before(:all) do
+      ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker"
+      ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg|
+        reg['Peter', Win32::Registry::REG_SZ] = 'Tiny'
+      end
+    end
+
+    it "deletes values if the value exists" do
+      @registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"}).should == true
+      @registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"}).should == false
+    end
+
+    it "does nothing if value does not exist" do
+      @registry.delete_value("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"}).should == true
+      @registry.value_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"}).should == false
+    end
+
+    it "throws an exception if the key does not exist?" do
+      lambda {@registry.delete_value("HKCU\\Software\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.delete_value("JKLM\\Software\\Root\\Trunk\\Peck\\Woodpecker", {:name=>"Peter", :type=>:string, :data=>"Tiny"})}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+  end
+
+  describe "delete_key" do
+    before (:all) do
+      ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Branch\\Fruit"
+      ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Branch\\Fruit', Win32::Registry::KEY_ALL_ACCESS) do |reg|
+        reg['Apple', Win32::Registry::REG_MULTI_SZ] = ["Red", "Juicy"]
+      end
+      ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Peck\\Woodpecker"
+      ::Win32::Registry::HKEY_CURRENT_USER.open('Software\\Root\\Trunk\\Peck\\Woodpecker', Win32::Registry::KEY_ALL_ACCESS) do |reg|
+        reg['Peter', Win32::Registry::REG_SZ] = 'Tiny'
+      end
+    end
+
+    it "deletes a key if it has no subkeys" do
+      @registry.delete_key("HKCU\\Software\\Root\\Branch\\Fruit", false).should == true
+      @registry.key_exists?("HKCU\\Software\\Root\\Branch\\Fruit").should == false
+    end
+
+    it "throws an exception if key to delete has subkeys and recursive is false" do
+      lambda { @registry.delete_key("HKCU\\Software\\Root\\Trunk", false) }.should raise_error(Chef::Exceptions::Win32RegNoRecursive)
+      @registry.key_exists?("HKCU\\Software\\Root\\Trunk\\Peck\\Woodpecker").should == true
+    end
+
+    it "deletes a key if it has subkeys and recursive true" do
+      @registry.delete_key("HKCU\\Software\\Root\\Trunk", true).should == true
+      @registry.key_exists?("HKCU\\Software\\Root\\Trunk").should == false
+    end
+
+    it "does nothing if the key does not exist" do
+      @registry.delete_key("HKCU\\Software\\Root\\Trunk", true).should == true
+      @registry.key_exists?("HKCU\\Software\\Root\\Trunk").should == false
+    end
+
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.delete_key("JKLM\\Software\\Root\\Branch\\Flower", false)}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+  end
+
+  describe "has_subkeys?" do
+    before(:all) do
+      ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk"
+      ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root\\Trunk") do |reg|
+        begin
+          reg.delete_key("Red", true)
+        rescue
+        end
+      end
+    end
+
+    it "throws an exception if the hive was missing" do
+      lambda {@registry.has_subkeys?("LMNO\\Software\\Root")}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+
+    it "throws an exception if the key is missing" do
+      lambda {@registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red")}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "returns true if the key has subkeys" do
+      @registry.has_subkeys?("HKCU\\Software\\Root").should == true
+    end
+
+    it "returns false if the key has no subkeys" do
+      ::Win32::Registry::HKEY_CURRENT_USER.create "Software\\Root\\Trunk\\Red"
+      @registry.has_subkeys?("HKCU\\Software\\Root\\Trunk\\Red").should == false
+    end
+  end
+
+  describe "get_subkeys" do
+    it "throws an exception if the key is missing" do
+      lambda {@registry.get_subkeys("HKCU\\Software\\Trunk\\Red")}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+    it "throws an exception if the hive does not exist" do
+      lambda {@registry.get_subkeys("JKLM\\Software\\Root")}.should raise_error(Chef::Exceptions::Win32RegHiveMissing)
+    end
+    it "returns the array of subkeys for a given key" do
+      subkeys = @registry.get_subkeys("HKCU\\Software\\Root")
+      reg_subkeys = []
+      ::Win32::Registry::HKEY_CURRENT_USER.open("Software\\Root", Win32::Registry::KEY_ALL_ACCESS) do |reg|
+        reg.each_key{|name| reg_subkeys << name}
+      end
+      reg_subkeys.should == subkeys
+    end
+  end
+
+  describe "architecture" do
+    describe "on 32-bit" do
+      before(:all) do
+        @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine]
+        @node.automatic_attrs[:kernel][:machine] = :i386
+      end
+
+      after(:all) do
+        @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine
+      end
+
+      context "registry constructor" do
+        it "throws an exception if requested architecture is 64bit but running on 32bit" do
+          lambda {Chef::Win32::Registry.new(@run_context, :x86_64)}.should raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect)
+        end
+
+        it "can correctly set the requested architecture to 32-bit" do
+          @r = Chef::Win32::Registry.new(@run_context, :i386)
+          @r.architecture.should == :i386
+          @r.registry_system_architecture.should == 0x0200
+        end
+
+        it "can correctly set the requested architecture to :machine" do
+          @r = Chef::Win32::Registry.new(@run_context, :machine)
+          @r.architecture.should == :machine
+          @r.registry_system_architecture.should == 0x0200
+        end
+      end
+
+      context "architecture setter" do
+        it "throws an exception if requested architecture is 64bit but running on 32bit" do
+          lambda {@registry.architecture = :x86_64}.should raise_error(Chef::Exceptions::Win32RegArchitectureIncorrect)
+        end
+
+        it "sets the requested architecture to :machine if passed :machine" do
+          @registry.architecture = :machine
+          @registry.architecture.should == :machine
+          @registry.registry_system_architecture.should == 0x0200
+        end
+
+        it "sets the requested architecture to 32-bit if passed i386 as a string" do
+          @registry.architecture = :i386
+          @registry.architecture.should == :i386
+          @registry.registry_system_architecture.should == 0x0200
+        end
+      end
+    end
+
+    describe "on 64-bit" do
+      before(:all) do
+        @saved_kernel_machine = @node.automatic_attrs[:kernel][:machine]
+        @node.automatic_attrs[:kernel][:machine] = :x86_64
+      end
+
+      after(:all) do
+        @node.automatic_attrs[:kernel][:machine] = @saved_kernel_machine
+      end
+
+      context "registry constructor" do
+        it "can correctly set the requested architecture to 32-bit" do
+          @r = Chef::Win32::Registry.new(@run_context, :i386)
+          @r.architecture.should == :i386
+          @r.registry_system_architecture.should == 0x0200
+        end
+
+        it "can correctly set the requested architecture to 64-bit" do
+          @r = Chef::Win32::Registry.new(@run_context, :x86_64)
+          @r.architecture.should == :x86_64
+          @r.registry_system_architecture.should == 0x0100
+        end
+
+        it "can correctly set the requested architecture to :machine" do
+          @r = Chef::Win32::Registry.new(@run_context, :machine)
+          @r.architecture.should == :machine
+          @r.registry_system_architecture.should == 0x0100
+        end
+      end
+
+      context "architecture setter" do
+        it "sets the requested architecture to 64-bit if passed 64-bit" do
+          @registry.architecture = :x86_64
+          @registry.architecture.should == :x86_64
+          @registry.registry_system_architecture.should == 0x0100
+        end
+
+        it "sets the requested architecture to :machine if passed :machine" do
+          @registry.architecture = :machine
+          @registry.architecture.should == :machine
+          @registry.registry_system_architecture.should == 0x0100
+        end
+
+        it "sets the requested architecture to 32-bit if passed 32-bit" do
+          @registry.architecture = :i386
+          @registry.architecture.should == :i386
+          @registry.registry_system_architecture.should == 0x0200
+        end
+      end
+    end
+
+    describe "when running on an actual 64-bit server", :windows64_only do
+      before(:all) do
+        begin
+          ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg|
+            reg.delete_key("Trunk", true)
+          end
+        rescue
+        end
+        begin
+          ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg|
+            reg.delete_key("Trunk", true)
+          end
+        rescue
+        end
+        # 64-bit
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Mauve", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100)
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Mauve', Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg|
+          reg['Alert', Win32::Registry::REG_SZ] = 'Universal'
+        end
+        # 32-bit
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.create("Software\\Root\\Poosh", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200)
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.open('Software\\Root\\Poosh', Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg|
+          reg['Status', Win32::Registry::REG_SZ] = 'Lost'
+        end
+      end
+
+      after(:all) do
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0100) do |reg|
+          reg.delete_key("Trunk", true)
+        end
+        ::Win32::Registry::HKEY_LOCAL_MACHINE.open("Software\\Root", ::Win32::Registry::KEY_ALL_ACCESS | 0x0200) do |reg|
+          reg.delete_key("Trunk", true)
+        end
+      end
+
+      describe "key_exists?" do
+        it "does not find 64-bit keys in the 32-bit registry" do
+          @registry.architecture=:i386
+          @registry.key_exists?("HKLM\\Software\\Root\\Mauve").should == false
+        end
+        it "finds 32-bit keys in the 32-bit registry" do
+          @registry.architecture=:i386
+          @registry.key_exists?("HKLM\\Software\\Root\\Poosh").should == true
+        end
+        it "does not find 32-bit keys in the 64-bit registry" do
+          @registry.architecture=:x86_64
+          @registry.key_exists?("HKLM\\Software\\Root\\Mauve").should == true
+        end
+        it "finds 64-bit keys in the 64-bit registry" do
+          @registry.architecture=:x86_64
+          @registry.key_exists?("HKLM\\Software\\Root\\Poosh").should == false
+        end
+      end
+
+      describe "value_exists?" do
+        it "does not find 64-bit values in the 32-bit registry" do
+          @registry.architecture=:i386
+          lambda{@registry.value_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert"})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+        end
+        it "finds 32-bit values in the 32-bit registry" do
+          @registry.architecture=:i386
+          @registry.value_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status"}).should == true
+        end
+        it "does not find 32-bit values in the 64-bit registry" do
+          @registry.architecture=:x86_64
+          @registry.value_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert"}).should == true
+        end
+        it "finds 64-bit values in the 64-bit registry" do
+          @registry.architecture=:x86_64
+          lambda{@registry.value_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status"})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+        end
+      end
+
+      describe "data_exists?" do
+        it "does not find 64-bit keys in the 32-bit registry" do
+          @registry.architecture=:i386
+          lambda{@registry.data_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert", :type=>:string, :data=>"Universal"})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+        end
+        it "finds 32-bit keys in the 32-bit registry" do
+          @registry.architecture=:i386
+          @registry.data_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status", :type=>:string, :data=>"Lost"}).should == true
+        end
+        it "does not find 32-bit keys in the 64-bit registry" do
+          @registry.architecture=:x86_64
+          @registry.data_exists?("HKLM\\Software\\Root\\Mauve", {:name=>"Alert", :type=>:string, :data=>"Universal"}).should == true
+        end
+        it "finds 64-bit keys in the 64-bit registry" do
+          @registry.architecture=:x86_64
+          lambda{@registry.data_exists?("HKLM\\Software\\Root\\Poosh", {:name=>"Status", :type=>:string, :data=>"Lost"})}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+        end
+      end
+
+      describe "create_key" do
+        it "can create a 32-bit only registry key" do
+          @registry.architecture = :i386
+          @registry.create_key("HKLM\\Software\\Root\\Trunk\\Red", true).should == true
+          @registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red").should == true
+          @registry.architecture = :x86_64
+          @registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Red").should == false
+        end
+
+        it "can create a 64-bit only registry key" do
+          @registry.architecture = :x86_64
+          @registry.create_key("HKLM\\Software\\Root\\Trunk\\Blue", true).should == true
+          @registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue").should == true
+          @registry.architecture = :i386
+          @registry.key_exists?("HKLM\\Software\\Root\\Trunk\\Blue").should == false
+        end
+      end
+
+    end
+  end
+end
diff --git a/spec/functional/win32/security_spec.rb b/spec/functional/win32/security_spec.rb
new file mode 100644
index 0000000..4ad9b07
--- /dev/null
+++ b/spec/functional/win32/security_spec.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Serdar Sutay (<serdar 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'
+if Chef::Platform.windows?
+  require 'chef/win32/security'
+end
+
+describe 'Chef::Win32::Security', :windows_only do
+  it "has_admin_privileges? returns true when running as admin" do
+    Chef::ReservedNames::Win32::Security.has_admin_privileges?.should == true
+  end
+
+  # We've done some investigation adding a negative test and it turned
+  # out to be a lot of work since mixlib-shellout doesn't have user
+  # support for windows.
+  #
+  # TODO - Add negative tests once mixlib-shellout has user support
+  it "has_admin_privileges? returns false when running as non-admin" do
+    pending "requires user support in mixlib-shellout"
+  end
+end
diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb
new file mode 100644
index 0000000..569b013
--- /dev/null
+++ b/spec/functional/win32/service_manager_spec.rb
@@ -0,0 +1,269 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+if Chef::Platform.windows?
+  require 'chef/application/windows_service_manager'
+end
+
+#
+# ATTENTION:
+# This test creates a windows service for testing purposes and runs it
+# as Local System on windows boxes.
+# This test will fail if you run the tests inside a Windows VM by
+# sharing the code from your host since Local System account by
+# default can't see the mounted partitions.
+# Run this test by copying the code to a local VM directory or setup
+# Local System account to see the maunted partitions for the shared
+# directories.
+#
+
+describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only do
+
+  # Some helper methods.
+
+  def test_service_exists?
+    ::Win32::Service.exists?("spec-service")
+  end
+
+  def test_service_state
+    ::Win32::Service.status("spec-service").current_state
+  end
+
+  def service_manager
+    Chef::Application::WindowsServiceManager.new(test_service)
+  end
+
+  def cleanup
+    # Uninstall if the test service is installed.
+    if test_service_exists?
+
+      # We can only uninstall when the service is stopped.
+      if test_service_state != "stopped"
+        ::Win32::Service.send("stop", "spec-service")
+        while test_service_state != "stopped"
+          sleep 1
+        end
+      end
+
+      ::Win32::Service.delete("spec-service")
+    end
+
+    # Delete the test_service_file if it exists
+    if File.exists?(test_service_file)
+      File.delete(test_service_file)
+    end
+
+  end
+
+
+  # Definition for the test-service
+
+  let(:test_service) {
+    {
+      :service_name => "spec-service",
+      :service_display_name => "Spec Test Service",
+      :service_description => "Service for testing Chef::Application::WindowsServiceManager.",
+      :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../support/platforms/win32/spec_service.rb'))
+    }
+  }
+
+  # Test service creates a file for us to verify that it is running.
+  # Since our test service is running as Local System we should look
+  # for the file it creates under SYSTEM temp directory
+
+  let(:test_service_file) {
+    "#{ENV['SystemDrive']}\\windows\\temp\\spec_service_file"
+  }
+
+  context "with invalid service definition" do
+    it "throws an error when initialized with no service definition" do
+      lambda { Chef::Application::WindowsServiceManager.new(nil) }.should raise_error(ArgumentError)
+    end
+
+    it "throws an error with required missing options" do
+      test_service.each do |key,value|
+        service_def = test_service.dup
+        service_def.delete(key)
+
+        lambda { Chef::Application::WindowsServiceManager.new(service_def) }.should raise_error(ArgumentError)
+      end
+    end
+  end
+
+  context "with valid definition" do
+    before(:each) do
+      @service_manager_output = [ ]
+      # Uncomment below lines to debug this test
+      # original_puts = $stdout.method(:puts)
+      $stdout.stub(:puts) do |message|
+        @service_manager_output << message
+        # original_puts.call(message)
+      end
+    end
+
+    after(:each) do
+      cleanup
+    end
+
+    context "when service doesn't exist" do
+      it "default => should say service don't exist" do
+        service_manager.run
+
+        @service_manager_output.grep(/doesn't exist on the system/).length.should > 0
+      end
+
+      it "install => should install the service" do
+        service_manager.run(["-a", "install"])
+
+        test_service_exists?.should be_true
+      end
+
+      it "other actions => should say service doesn't exist" do
+        ["delete", "start", "stop", "pause", "resume", "uninstall"].each do |action|
+          service_manager.run(["-a", action])
+          @service_manager_output.grep(/doesn't exist on the system/).length.should > 0
+          @service_manager_output = [ ]
+        end
+      end
+    end
+
+    context "when service exists" do
+      before(:each) do
+        service_manager.run(["-a", "install"])
+      end
+
+      it "install => should say service already exists" do
+          service_manager.run(["-a", "install"])
+          @service_manager_output.grep(/already exists/).length.should > 0
+      end
+
+      context "and service is stopped" do
+        ["delete", "uninstall"].each do |action|
+          it "#{action} => should remove the service", :volatile do
+            service_manager.run(["-a", action])
+            test_service_exists?.should be_false
+          end
+        end
+
+        it "default, status => should say service is stopped" do
+          service_manager.run([ ])
+          @service_manager_output.grep(/stopped/).length.should > 0
+          @service_manager_output = [ ]
+
+          service_manager.run(["-a", "status"])
+          @service_manager_output.grep(/stopped/).length.should > 0
+        end
+
+        it "start should start the service", :volatile do
+          service_manager.run(["-a", "start"])
+          test_service_state.should == "running"
+          File.exists?(test_service_file).should be_true
+        end
+
+        it "stop should not affect the service" do
+          service_manager.run(["-a", "stop"])
+          test_service_state.should == "stopped"
+        end
+
+
+        ["pause", "resume"].each do |action|
+          it "#{action} => should raise error" do
+            lambda {service_manager.run(["-a", action])}.should raise_error(::Win32::Service::Error)
+          end
+        end
+
+        context "and service is started", :volatile do
+          before(:each) do
+            service_manager.run(["-a", "start"])
+          end
+
+          ["delete", "uninstall"].each do |action|
+            it "#{action} => should remove the service", :volatile do
+              service_manager.run(["-a", action])
+              test_service_exists?.should be_false
+            end
+          end
+
+          it "default, status => should say service is running" do
+            service_manager.run([ ])
+            @service_manager_output.grep(/running/).length.should > 0
+            @service_manager_output = [ ]
+
+            service_manager.run(["-a", "status"])
+            @service_manager_output.grep(/running/).length.should > 0
+          end
+
+          it "stop should stop the service" do
+            service_manager.run(["-a", "stop"])
+            test_service_state.should == "stopped"
+          end
+
+          it "pause should pause the service" do
+            service_manager.run(["-a", "pause"])
+            test_service_state.should == "paused"
+          end
+
+          it "resume should have no affect" do
+            service_manager.run(["-a", "resume"])
+            test_service_state.should == "running"
+          end
+        end
+
+        context "and service is paused", :volatile do
+          before(:each) do
+            service_manager.run(["-a", "start"])
+            service_manager.run(["-a", "pause"])
+          end
+
+          actions = ["delete", "uninstall"]
+          actions.each do |action|
+            it "#{action} => should remove the service" do
+              service_manager.run(["-a", action])
+              test_service_exists?.should be_false
+            end
+          end
+
+          it "default, status => should say service is paused" do
+            service_manager.run([ ])
+            @service_manager_output.grep(/paused/).length.should > 0
+            @service_manager_output = [ ]
+
+            service_manager.run(["-a", "status"])
+            @service_manager_output.grep(/paused/).length.should > 0
+          end
+
+          it "stop should stop the service" do
+            service_manager.run(["-a", "stop"])
+            test_service_state.should == "stopped"
+          end
+
+          it "pause should not affect the service" do
+            service_manager.run(["-a", "pause"])
+            test_service_state.should == "paused"
+          end
+
+          it "start should raise an error" do
+            lambda {service_manager.run(["-a", "start"])}.should raise_error(::Win32::Service::Error)
+          end
+
+        end
+      end
+    end
+  end
+end
diff --git a/spec/functional/win32/versions_spec.rb b/spec/functional/win32/versions_spec.rb
new file mode 100644
index 0000000..acb8bb1
--- /dev/null
+++ b/spec/functional/win32/versions_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Chirag Jog (<chirag at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+if Chef::Platform.windows?
+  require 'chef/win32/version'
+  require 'ruby-wmi'
+end
+
+describe "Chef::ReservedNames::Win32::Version", :windows_only do
+  before do
+
+    host = WMI::Win32_OperatingSystem.find(:first)
+
+    # Use WMI to determine current OS version.
+    # On Win2k8R2 and later, we can dynamically obtain marketing
+    # names for comparison from WMI so the test should not
+    # need to be modified when new Windows releases arise.
+    # For Win2k3 and Win2k8, we use static names in this test
+    # based on the version number information from WMI. The names
+    # from WMI contain extended characters such as registered
+    # trademark on Win2k8 and Win2k3 that we're not using in our
+    # library, so we have to set the expectation statically.
+    if Chef::Platform::windows_server_2003?
+      @current_os_version = 'Windows Server 2003 R2'
+    elsif is_windows_server_2008?(host)
+      @current_os_version = 'Windows Server 2008'
+    else
+      # The name from WMI is actually what we want in Win2k8R2+.
+      # So this expectation sould continue to hold without modification
+      # as new versions of Windows are released.
+      @current_os_version = host.caption
+    end
+
+    @version = Chef::ReservedNames::Win32::Version.new
+  end
+
+  context "Windows Operating System version" do
+    it "should match the version from WMI" do
+      @current_os_version.should include(@version.marketing_name)
+    end
+  end
+
+  def is_windows_server_2008?(wmi_host)
+    is_win2k8 = false
+
+    os_version = wmi_host.send('Version')
+
+    # The operating system version is a string in the following form
+    # that can be split into components based on the '.' delimiter:
+    # MajorVersionNumber.MinorVersionNumber.BuildNumber
+    os_version_components = os_version.split('.')
+
+    if os_version_components.length < 2
+      raise 'WMI returned a Windows version from Win32_OperatingSystem.Version ' +
+        'with an unexpected format. The Windows version could not be determined.'
+    end
+
+    # Windows 6.0 is Windows Server 2008, so test the major and
+    # minor version components
+    is_win2k8 = os_version_components[0] == '6' && os_version_components[1] == '0'
+  end
+end
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
new file mode 100644
index 0000000..839899d
--- /dev/null
+++ b/spec/integration/client/client_spec.rb
@@ -0,0 +1,146 @@
+require 'support/shared/integration/integration_helper'
+require 'chef/mixin/shell_out'
+
+describe "chef-client" do
+  extend IntegrationSupport
+  include Chef::Mixin::ShellOut
+
+  when_the_repository "has a cookbook with a no-op recipe" do
+    file 'cookbooks/x/recipes/default.rb', ''
+
+    it "should complete with success" do
+      file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+      chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..", "bin")
+      result = shell_out("chef-client -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+      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
+        chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin"))
+        result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => path_to(''))
+        result.error!
+      end
+
+      it 'should complete with success when cwd is below cookbooks and paths are not specified' do
+        chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin"))
+        result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => path_to('cookbooks/x'))
+        result.error!
+      end
+
+      it 'should fail when cwd is below high above and paths are not specified' do
+        chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin"))
+        result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => File.expand_path('..', path_to('')))
+        result.exitstatus.should == 1
+      end
+    end
+
+    context 'and a config file under .chef/knife.rb' do
+      file '.chef/knife.rb', 'xxx.xxx'
+
+      it 'should load .chef/knife.rb when -z is specified' do
+        chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin"))
+        result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('')}\"", :cwd => path_to(''))
+        result.exitstatus.should == 2
+      end
+
+      it 'fails to load .chef/knife.rb when -z is specified and --config-file-jail does not include the .chef/knife.rb' do
+        chef_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "bin"))
+        result = shell_out("#{chef_dir}/chef-client -z -o 'x::default' --config-file-jail \"#{path_to('roles')}\"", :cwd => path_to(''))
+        result.error!
+      end
+    end
+
+    it "should complete with success" do
+      file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+      chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..", "bin")
+      result = shell_out("chef-client -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+      result.error!
+    end
+
+    context 'and a private key' do
+      file 'mykey.pem', <<EOM
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf
+0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk
+NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn
+0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O
+AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP
+HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom
+8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB
+zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx
+k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb
+i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ
+G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV
+ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL
+awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK
+7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns
+g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr
+Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy
+HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2
+V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO
+fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN
+lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr
+c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo
+fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV
+YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL
+syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T
++vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA=
+-----END RSA PRIVATE KEY-----
+EOM
+
+      it "should complete with success even with a client key" do
+        file 'config/client.rb', <<EOM
+local_mode true
+client_key "#{path_to('mykey.pem')}"
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+        chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..", "bin")
+        result = shell_out("chef-client -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+        result.error!
+      end
+    end
+
+    it "should complete with success when passed the -z flag" do
+      file 'config/client.rb', <<EOM
+chef_server_url 'http://omg.com/blah'
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+      chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..", "bin")
+      result = shell_out("chef-client -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z", :cwd => chef_dir)
+      result.error!
+    end
+
+    it "should complete with success when passed the --local-mode flag" do
+      file 'config/client.rb', <<EOM
+chef_server_url 'http://omg.com/blah'
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+      chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..", "bin")
+      result = shell_out("chef-client -c \"#{path_to('config/client.rb')}\" -o 'x::default' --local-mode", :cwd => chef_dir)
+      result.error!
+    end
+
+    it "should complete with success when passed -z and --chef-zero-port" do
+      file 'config/client.rb', <<EOM
+chef_server_url 'http://omg.com/blah'
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+      chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..", "bin")
+      result = shell_out("chef-client -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z", :cwd => chef_dir)
+      result.error!
+    end
+  end
+end
diff --git a/spec/integration/knife/chef_fs_data_store_spec.rb b/spec/integration/knife/chef_fs_data_store_spec.rb
new file mode 100644
index 0000000..255a7b6
--- /dev/null
+++ b/spec/integration/knife/chef_fs_data_store_spec.rb
@@ -0,0 +1,353 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/list'
+require 'chef/knife/delete'
+require 'chef/knife/show'
+require 'chef/knife/raw'
+require 'chef/knife/cookbook_upload'
+
+describe 'knife raw -z' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  when_the_repository "has one of each thing" do
+    file 'clients/x.json', {}
+    file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+    file 'data_bags/x/y.json', {}
+    file 'environments/x.json', {}
+    file 'nodes/x.json', {}
+    file 'roles/x.json', {}
+    file 'users/x.json', {}
+
+    context 'GET /TYPE' do
+      it 'knife list -z -R returns everything' do
+        knife('list -z -Rfp /').should_succeed <<EOM
+/clients/
+/clients/x.json
+/cookbooks/
+/cookbooks/x/
+/cookbooks/x/metadata.rb
+/data_bags/
+/data_bags/x/
+/data_bags/x/y.json
+/environments/
+/environments/x.json
+/nodes/
+/nodes/x.json
+/roles/
+/roles/x.json
+/users/
+/users/x.json
+EOM
+      end
+    end
+
+    context 'DELETE /TYPE/NAME' do
+      it 'knife delete -z /clients/x.json works' do
+        knife('delete -z /clients/x.json').should_succeed "Deleted /clients/x.json\n"
+        knife('list -z -Rfp /clients').should_succeed ''
+      end
+
+      it 'knife delete -z -r /cookbooks/x works' do
+        knife('delete -z -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('list -z -Rfp /cookbooks').should_succeed ''
+      end
+
+      it 'knife delete -z -r /data_bags/x works' do
+        knife('delete -z -r /data_bags/x').should_succeed "Deleted /data_bags/x\n"
+        knife('list -z -Rfp /data_bags').should_succeed ''
+      end
+
+      it 'knife delete -z /data_bags/x/y.json works' do
+        knife('delete -z /data_bags/x/y.json').should_succeed "Deleted /data_bags/x/y.json\n"
+        knife('list -z -Rfp /data_bags').should_succeed "/data_bags/x/\n"
+      end
+
+      it 'knife delete -z /environments/x.json works' do
+        knife('delete -z /environments/x.json').should_succeed "Deleted /environments/x.json\n"
+        knife('list -z -Rfp /environments').should_succeed ''
+      end
+
+      it 'knife delete -z /nodes/x.json works' do
+        knife('delete -z /nodes/x.json').should_succeed "Deleted /nodes/x.json\n"
+        knife('list -z -Rfp /nodes').should_succeed ''
+      end
+
+      it 'knife delete -z /roles/x.json works' do
+        knife('delete -z /roles/x.json').should_succeed "Deleted /roles/x.json\n"
+        knife('list -z -Rfp /roles').should_succeed ''
+      end
+
+      it 'knife delete -z /users/x.json works' do
+        knife('delete -z /users/x.json').should_succeed "Deleted /users/x.json\n"
+        knife('list -z -Rfp /users').should_succeed ''
+      end
+    end
+
+    context 'GET /TYPE/NAME' do
+      it 'knife show -z /clients/x.json works' do
+        knife('show -z /clients/x.json').should_succeed /"x"/
+      end
+
+      it 'knife show -z /cookbooks/x/metadata.rb works' do
+        knife('show -z /cookbooks/x/metadata.rb').should_succeed "/cookbooks/x/metadata.rb:\nversion \"1.0.0\"\n"
+      end
+
+      it 'knife show -z /data_bags/x/y.json works' do
+        knife('show -z /data_bags/x/y.json').should_succeed /"y"/
+      end
+
+      it 'knife show -z /environments/x.json works' do
+        knife('show -z /environments/x.json').should_succeed /"x"/
+      end
+
+      it 'knife show -z /nodes/x.json works' do
+        knife('show -z /nodes/x.json').should_succeed /"x"/
+      end
+
+      it 'knife show -z /roles/x.json works' do
+        knife('show -z /roles/x.json').should_succeed /"x"/
+      end
+
+      it 'knife show -z /users/x.json works' do
+        knife('show -z /users/x.json').should_succeed /"x"/
+      end
+    end
+
+    context 'PUT /TYPE/NAME' do
+      file 'empty.json', {}
+      file 'rolestuff.json', '{"description":"hi there","name":"x"}'
+      file 'cookbooks_to_upload/x/metadata.rb', "version '1.0.0'\n\n"
+
+      it 'knife raw -z -i empty.json -m PUT /clients/x' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /clients/x").should_succeed /"x"/
+        knife('list --local /clients').should_succeed "/clients/x.json\n"
+      end
+
+      it 'knife cookbook upload works' do
+        knife("cookbook upload -z --cookbook-path #{path_to('cookbooks_to_upload')} x").should_succeed <<EOM
+Uploading x              [1.0.0]
+Uploaded 1 cookbook.
+EOM
+        knife('list --local -Rfp /cookbooks').should_succeed "/cookbooks/x/\n/cookbooks/x/metadata.rb\n"
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /data/x/y' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /data/x/y").should_succeed /"y"/
+        knife('list --local -Rfp /data_bags').should_succeed "/data_bags/x/\n/data_bags/x/y.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /environments/x' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /environments/x").should_succeed /"x"/
+        knife('list --local /environments').should_succeed "/environments/x.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /nodes/x' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /nodes/x").should_succeed /"x"/
+        knife('list --local /nodes').should_succeed "/nodes/x.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /roles/x' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /roles/x").should_succeed /"x"/
+        knife('list --local /roles').should_succeed "/roles/x.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /users/x' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /users/x").should_succeed /"x"/
+        knife('list --local /users').should_succeed "/users/x.json\n"
+      end
+
+      it 'After knife raw -z -i rolestuff.json -m PUT /roles/x, the output is pretty', :pending => (RUBY_VERSION < "1.9") do
+        knife("raw -z -i #{path_to('rolestuff.json')} -m PUT /roles/x").should_succeed /"x"/
+        IO.read(path_to('roles/x.json')).should == <<EOM.strip
+{
+  "name": "x",
+  "description": "hi there"
+}
+EOM
+      end
+    end
+  end
+
+  when_the_repository 'is empty' do
+    context 'POST /TYPE/NAME' do
+      file 'empty.json', { 'name' => 'z' }
+      file 'empty_id.json', { 'id' => 'z' }
+      file 'rolestuff.json', '{"description":"hi there","name":"x"}'
+      file 'cookbooks_to_upload/z/metadata.rb', "version '1.0.0'"
+
+      it 'knife raw -z -i empty.json -m POST /clients' do
+        knife("raw -z -i #{path_to('empty.json')} -m POST /clients").should_succeed /uri/
+        knife('list --local /clients').should_succeed "/clients/z.json\n"
+      end
+
+      it 'knife cookbook upload works' do
+        knife("cookbook upload -z --cookbook-path #{path_to('cookbooks_to_upload')} z").should_succeed <<EOM
+Uploading z            [1.0.0]
+Uploaded 1 cookbook.
+EOM
+        knife('list --local -Rfp /cookbooks').should_succeed "/cookbooks/z/\n/cookbooks/z/metadata.rb\n"
+      end
+
+      it 'knife raw -z -i empty.json -m POST /data' do
+        knife("raw -z -i #{path_to('empty.json')} -m POST /data").should_succeed /uri/
+        knife('list --local -Rfp /data_bags').should_succeed "/data_bags/z/\n"
+      end
+
+      it 'knife raw -z -i empty.json -m POST /data/x' do
+        knife("raw -z -i #{path_to('empty_id.json')} -m POST /data/x").should_succeed /"z"/
+        knife('list --local -Rfp /data_bags').should_succeed "/data_bags/x/\n/data_bags/x/z.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m POST /environments' do
+        knife("raw -z -i #{path_to('empty.json')} -m POST /environments").should_succeed /uri/
+        knife('list --local /environments').should_succeed "/environments/z.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m POST /nodes' do
+        knife("raw -z -i #{path_to('empty.json')} -m POST /nodes").should_succeed /uri/
+        knife('list --local /nodes').should_succeed "/nodes/z.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m POST /roles' do
+        knife("raw -z -i #{path_to('empty.json')} -m POST /roles").should_succeed /uri/
+        knife('list --local /roles').should_succeed "/roles/z.json\n"
+      end
+
+      it 'knife raw -z -i empty.json -m POST /users' do
+        knife("raw -z -i #{path_to('empty.json')} -m POST /users").should_succeed /uri/
+        knife('list --local /users').should_succeed "/users/z.json\n"
+      end
+
+      it 'After knife raw -z -i rolestuff.json -m POST /roles, the output is pretty', :pending => (RUBY_VERSION < "1.9") do
+        knife("raw -z -i #{path_to('rolestuff.json')} -m POST /roles").should_succeed /uri/
+        IO.read(path_to('roles/x.json')).should == <<EOM.strip
+{
+  "name": "x",
+  "description": "hi there"
+}
+EOM
+      end
+    end
+
+    it 'knife list -z -R returns nothing' do
+      knife('list -z -Rfp /').should_succeed <<EOM
+/clients/
+/cookbooks/
+/data_bags/
+/environments/
+/nodes/
+/roles/
+/users/
+EOM
+    end
+
+    context 'DELETE /TYPE/NAME' do
+      it 'knife delete -z /clients/x.json fails with an error' do
+        knife('delete -z /clients/x.json').should_fail "ERROR: /clients/x.json: No such file or directory\n"
+      end
+
+      it 'knife delete -z -r /cookbooks/x fails with an error' do
+        knife('delete -z -r /cookbooks/x').should_fail "ERROR: /cookbooks/x: No such file or directory\n"
+      end
+
+      it 'knife delete -z -r /data_bags/x fails with an error' do
+        knife('delete -z -r /data_bags/x').should_fail "ERROR: /data_bags/x: No such file or directory\n"
+      end
+
+      it 'knife delete -z /data_bags/x/y.json fails with an error' do
+        knife('delete -z /data_bags/x/y.json').should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n"
+      end
+
+      it 'knife delete -z /environments/x.json fails with an error' do
+        knife('delete -z /environments/x.json').should_fail "ERROR: /environments/x.json: No such file or directory\n"
+      end
+
+      it 'knife delete -z /nodes/x.json fails with an error' do
+        knife('delete -z /nodes/x.json').should_fail "ERROR: /nodes/x.json: No such file or directory\n"
+      end
+
+      it 'knife delete -z /roles/x.json fails with an error' do
+        knife('delete -z /roles/x.json').should_fail "ERROR: /roles/x.json: No such file or directory\n"
+      end
+
+      it 'knife delete -z /users/x.json fails with an error' do
+        knife('delete -z /users/x.json').should_fail "ERROR: /users/x.json: No such file or directory\n"
+      end
+    end
+
+    context 'GET /TYPE/NAME' do
+      it 'knife show -z /clients/x.json fails with an error' do
+        knife('show -z /clients/x.json').should_fail "ERROR: /clients/x.json: No such file or directory\n"
+      end
+
+      it 'knife show -z /cookbooks/x/metadata.rb fails with an error' do
+        knife('show -z /cookbooks/x/metadata.rb').should_fail "ERROR: /cookbooks/x/metadata.rb: No such file or directory\n"
+      end
+
+      it 'knife show -z /data_bags/x/y.json fails with an error' do
+        knife('show -z /data_bags/x/y.json').should_fail "ERROR: /data_bags/x/y.json: No such file or directory\n"
+      end
+
+      it 'knife show -z /environments/x.json fails with an error' do
+        knife('show -z /environments/x.json').should_fail "ERROR: /environments/x.json: No such file or directory\n"
+      end
+
+      it 'knife show -z /nodes/x.json fails with an error' do
+        knife('show -z /nodes/x.json').should_fail "ERROR: /nodes/x.json: No such file or directory\n"
+      end
+
+      it 'knife show -z /roles/x.json fails with an error' do
+        knife('show -z /roles/x.json').should_fail "ERROR: /roles/x.json: No such file or directory\n"
+      end
+
+      it 'knife show -z /users/x.json fails with an error' do
+        knife('show -z /users/x.json').should_fail "ERROR: /users/x.json: No such file or directory\n"
+      end
+    end
+
+    context 'PUT /TYPE/NAME' do
+      file 'empty.json', {}
+
+      it 'knife raw -z -i empty.json -m PUT /clients/x fails with 404' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /clients/x").should_fail /404/
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /data/x/y fails with 404' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /data/x/y").should_fail /404/
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /environments/x fails with 404' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /environments/x").should_fail /404/
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /nodes/x fails with 404' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /nodes/x").should_fail /404/
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /roles/x fails with 404' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /roles/x").should_fail /404/
+      end
+
+      it 'knife raw -z -i empty.json -m PUT /users/x fails with 404' do
+        knife("raw -z -i #{path_to('empty.json')} -m PUT /users/x").should_fail /404/
+      end
+    end
+  end
+end
diff --git a/spec/integration/knife/chef_repo_path_spec.rb b/spec/integration/knife/chef_repo_path_spec.rb
new file mode 100644
index 0000000..4ffb179
--- /dev/null
+++ b/spec/integration/knife/chef_repo_path_spec.rb
@@ -0,0 +1,858 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/list'
+require 'chef/knife/show'
+
+describe 'chef_repo_path tests' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  # TODO alternate repo_path / *_path
+  context 'alternate *_path' do
+    when_the_repository 'has clients and clients2, cookbooks and cookbooks2, etc.' do
+      file 'clients/client1.json', {}
+      file 'cookbooks/cookbook1/metadata.rb', ''
+      file 'data_bags/bag/item.json', {}
+      file 'environments/env1.json', {}
+      file 'nodes/node1.json', {}
+      file 'roles/role1.json', {}
+      file 'users/user1.json', {}
+
+      file 'clients2/client2.json', {}
+      file 'cookbooks2/cookbook2/metadata.rb', ''
+      file 'data_bags2/bag2/item2.json', {}
+      file 'environments2/env2.json', {}
+      file 'nodes2/node2.json', {}
+      file 'roles2/role2.json', {}
+      file 'users2/user2.json', {}
+
+      directory 'chef_repo2' do
+        file 'clients/client3.json', {}
+        file 'cookbooks/cookbook3/metadata.rb', ''
+        file 'data_bags/bag3/item3.json', {}
+        file 'environments/env3.json', {}
+        file 'nodes/node3.json', {}
+        file 'roles/role3.json', {}
+        file 'users/user3.json', {}
+      end
+
+      it 'knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff' do
+        Chef::Config.delete(:chef_repo_path)
+        knife("list --local -Rfp --chef-repo-path #{path_to('chef_repo2')} /").should_succeed <<EOM
+/clients/
+/clients/client3.json
+/cookbooks/
+/cookbooks/cookbook3/
+/cookbooks/cookbook3/metadata.rb
+/data_bags/
+/data_bags/bag3/
+/data_bags/bag3/item3.json
+/environments/
+/environments/env3.json
+/nodes/
+/nodes/node3.json
+/roles/
+/roles/role3.json
+/users/
+/users/user3.json
+EOM
+      end
+
+      context 'when all _paths are set to alternates' do
+        before :each do
+          %w(client cookbook data_bag environment node role user).each do |object_name|
+            Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
+          end
+          Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'chef_repo2')
+        end
+
+        it 'knife list --local -Rfp --chef-repo-path chef_repo2 / grabs chef_repo2 stuff' do
+          knife("list --local -Rfp --chef-repo-path #{path_to('chef_repo2')} /").should_succeed <<EOM
+/clients/
+/clients/client3.json
+/cookbooks/
+/cookbooks/cookbook3/
+/cookbooks/cookbook3/metadata.rb
+/data_bags/
+/data_bags/bag3/
+/data_bags/bag3/item3.json
+/environments/
+/environments/env3.json
+/nodes/
+/nodes/node3.json
+/roles/
+/roles/role3.json
+/users/
+/users/user3.json
+EOM
+        end        
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client2.json
+cookbooks/
+cookbooks/cookbook2/
+cookbooks/cookbook2/metadata.rb
+data_bags/
+data_bags/bag2/
+data_bags/bag2/item2.json
+environments/
+environments/env2.json
+nodes/
+nodes/node2.json
+roles/
+roles/role2.json
+users/
+users/user2.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside data_bags2' do
+          cwd 'data_bags2'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag2/
+bag2/item2.json
+EOM
+          end
+          it 'knife list --local -Rfp ../roles lists roles' do
+            knife('list --local -Rfp ../roles').should_succeed "/roles/role2.json\n"
+          end
+        end
+      end
+
+      context 'when all _paths except chef_repo_path are set to alternates' do
+        before :each do
+          %w(client cookbook data_bag environment node role user).each do |object_name|
+            Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
+          end
+        end
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client2.json
+cookbooks/
+cookbooks/cookbook2/
+cookbooks/cookbook2/metadata.rb
+data_bags/
+data_bags/bag2/
+data_bags/bag2/item2.json
+environments/
+environments/env2.json
+nodes/
+nodes/node2.json
+roles/
+roles/role2.json
+users/
+users/user2.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside data_bags2' do
+          cwd 'data_bags2'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag2/
+bag2/item2.json
+EOM
+          end
+        end
+      end
+
+      context 'when only chef_repo_path is set to its alternate' do
+        before :each do
+          %w(client cookbook data_bag environment node role user).each do |object_name|
+            Chef::Config.delete("#{object_name}_path".to_sym)
+          end
+          Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'chef_repo2')
+        end
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client3.json
+cookbooks/
+cookbooks/cookbook3/
+cookbooks/cookbook3/metadata.rb
+data_bags/
+data_bags/bag3/
+data_bags/bag3/item3.json
+environments/
+environments/env3.json
+nodes/
+nodes/node3.json
+roles/
+roles/role3.json
+users/
+users/user3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2/data_bags' do
+          cwd 'chef_repo2/data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag3/
+bag3/item3.json
+EOM
+          end
+        end
+      end
+
+      context 'when paths are set to point to both versions of each' do
+        before :each do
+          %w(client cookbook data_bag environment node role user).each do |object_name|
+            Chef::Config["#{object_name}_path".to_sym] = [
+              File.join(Chef::Config.chef_repo_path, "#{object_name}s"),
+              File.join(Chef::Config.chef_repo_path, "#{object_name}s2")
+            ]
+          end
+          Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'chef_repo2')
+        end
+
+        context 'when there is a directory in clients1 and file in clients2 with the same name' do
+          directory 'clients/blah.json'
+          file 'clients2/blah.json', {}
+          it 'knife show /clients/blah.json succeeds' do
+            knife('show --local /clients/blah.json').should_succeed <<EOM
+/clients/blah.json:
+{
+}
+EOM
+          end
+        end
+
+        context 'when there is a file in cookbooks1 and directory in cookbooks2 with the same name' do
+          file 'cookbooks/blah', ''
+          file 'cookbooks2/blah/metadata.rb', ''
+          it 'knife list -Rfp cookbooks shows files in blah' do
+            knife('list --local -Rfp /cookbooks').should_succeed <<EOM
+/cookbooks/blah/
+/cookbooks/blah/metadata.rb
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/metadata.rb
+EOM
+          end
+        end
+
+        context 'when there is an empty directory in cookbooks1 and a real cookbook in cookbooks2 with the same name' do
+          directory 'cookbooks/blah'
+          file 'cookbooks2/blah/metadata.rb', ''
+          it 'knife list -Rfp cookbooks shows files in blah' do
+            knife('list --local -Rfp /cookbooks').should_succeed(<<EOM, :stderr => "WARN: Cookbook 'blah' is empty or entirely chefignored at #{Chef::Config.cookbook_path[0]}/blah\n")
+/cookbooks/blah/
+/cookbooks/blah/metadata.rb
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/metadata.rb
+EOM
+          end
+        end
+
+        context 'when there is a cookbook in cookbooks1 and a cookbook in cookbooks2 with the same name' do
+          file 'cookbooks/blah/metadata.json', {}
+          file 'cookbooks2/blah/metadata.rb', ''
+          it 'knife list -Rfp cookbooks shows files in the first cookbook and not the second' do
+            knife('list --local -Rfp /cookbooks').should_succeed(<<EOM, :stderr => "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.cookbook_path[0]}/blah and #{Chef::Config.cookbook_path[1]}/blah\n")
+/cookbooks/blah/
+/cookbooks/blah/metadata.json
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/metadata.rb
+EOM
+          end
+        end
+
+        context 'when there is a file in data_bags1 and a directory in data_bags2 with the same name' do
+          file 'data_bags/blah', ''
+          file 'data_bags2/blah/item.json', ''
+          it 'knife list -Rfp data_bags shows files in blah' do
+            knife('list --local -Rfp /data_bags').should_succeed <<EOM
+/data_bags/bag/
+/data_bags/bag/item.json
+/data_bags/bag2/
+/data_bags/bag2/item2.json
+/data_bags/blah/
+/data_bags/blah/item.json
+EOM
+          end
+        end
+
+        context 'when there is a data bag in data_bags1 and a data bag in data_bags2 with the same name' do
+          file 'data_bags/blah/item1.json', ''
+          file 'data_bags2/blah/item2.json', ''
+          it 'knife list -Rfp data_bags shows only items in data_bags1' do
+            knife('list --local -Rfp /data_bags').should_succeed(<<EOM, :stderr => "WARN: Child with name 'blah' found in multiple directories: #{Chef::Config.data_bag_path[0]}/blah and #{Chef::Config.data_bag_path[1]}/blah\n")
+/data_bags/bag/
+/data_bags/bag/item.json
+/data_bags/bag2/
+/data_bags/bag2/item2.json
+/data_bags/blah/
+/data_bags/blah/item1.json
+EOM
+          end
+        end
+
+        context 'when there is a directory in environments1 and file in environments2 with the same name' do
+          directory 'environments/blah.json'
+          file 'environments2/blah.json', {}
+          it 'knife show /environments/blah.json succeeds' do
+            knife('show --local /environments/blah.json').should_succeed <<EOM
+/environments/blah.json:
+{
+}
+EOM
+          end
+        end
+
+        context 'when there is a directory in nodes1 and file in nodes2 with the same name' do
+          directory 'nodes/blah.json'
+          file 'nodes2/blah.json', {}
+          it 'knife show /nodes/blah.json succeeds' do
+            knife('show --local /nodes/blah.json').should_succeed <<EOM
+/nodes/blah.json:
+{
+}
+EOM
+          end
+        end
+
+        context 'when there is a directory in roles1 and file in roles2 with the same name' do
+          directory 'roles/blah.json'
+          file 'roles2/blah.json', {}
+          it 'knife show /roles/blah.json succeeds' do
+            knife('show --local /roles/blah.json').should_succeed <<EOM
+/roles/blah.json:
+{
+}
+EOM
+          end
+        end
+
+        context 'when there is a directory in users1 and file in users2 with the same name' do
+          directory 'users/blah.json'
+          file 'users2/blah.json', {}
+          it 'knife show /users/blah.json succeeds' do
+            knife('show --local /users/blah.json').should_succeed <<EOM
+/users/blah.json:
+{
+}
+EOM
+          end
+        end
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+bag2/
+bag2/item2.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client1.json
+clients/client2.json
+cookbooks/
+cookbooks/cookbook1/
+cookbooks/cookbook1/metadata.rb
+cookbooks/cookbook2/
+cookbooks/cookbook2/metadata.rb
+data_bags/
+data_bags/bag/
+data_bags/bag/item.json
+data_bags/bag2/
+data_bags/bag2/item2.json
+environments/
+environments/env1.json
+environments/env2.json
+nodes/
+nodes/node1.json
+nodes/node2.json
+roles/
+roles/role1.json
+roles/role2.json
+users/
+users/user1.json
+users/user2.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside data_bags2' do
+          cwd 'data_bags2'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+bag2/
+bag2/item2.json
+EOM
+          end
+        end
+      end
+
+      context 'when when chef_repo_path is set to both places and no other _path is set' do
+        before :each do
+          %w(client cookbook data_bag environment node role user).each do |object_name|
+            Chef::Config.delete("#{object_name}_path".to_sym)
+          end
+          Chef::Config.chef_repo_path = [
+            Chef::Config.chef_repo_path,
+            File.join(Chef::Config.chef_repo_path, 'chef_repo2')
+          ]
+        end
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client1.json
+clients/client3.json
+cookbooks/
+cookbooks/cookbook1/
+cookbooks/cookbook1/metadata.rb
+cookbooks/cookbook3/
+cookbooks/cookbook3/metadata.rb
+data_bags/
+data_bags/bag/
+data_bags/bag/item.json
+data_bags/bag3/
+data_bags/bag3/item3.json
+environments/
+environments/env1.json
+environments/env3.json
+nodes/
+nodes/node1.json
+nodes/node3.json
+roles/
+roles/role1.json
+roles/role3.json
+users/
+users/user1.json
+users/user3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+bag3/
+bag3/item3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client1.json
+clients/client3.json
+cookbooks/
+cookbooks/cookbook1/
+cookbooks/cookbook1/metadata.rb
+cookbooks/cookbook3/
+cookbooks/cookbook3/metadata.rb
+data_bags/
+data_bags/bag/
+data_bags/bag/item.json
+data_bags/bag3/
+data_bags/bag3/item3.json
+environments/
+environments/env1.json
+environments/env3.json
+nodes/
+nodes/node1.json
+nodes/node3.json
+roles/
+roles/role1.json
+roles/role3.json
+users/
+users/user1.json
+users/user3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2/data_bags' do
+          cwd 'chef_repo2/data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+bag3/
+bag3/item3.json
+EOM
+          end
+        end
+      end
+
+      context 'when cookbook_path is set and nothing else' do
+        before :each do
+          %w(client data_bag environment node role user).each do |object_name|
+            Chef::Config.delete("#{object_name}_path".to_sym)
+          end
+          Chef::Config.delete(:chef_repo_path)
+          Chef::Config.cookbook_path = File.join(@repository_dir, 'chef_repo2', 'cookbooks')
+        end
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client3.json
+cookbooks/
+cookbooks/cookbook3/
+cookbooks/cookbook3/metadata.rb
+data_bags/
+data_bags/bag3/
+data_bags/bag3/item3.json
+environments/
+environments/env3.json
+nodes/
+nodes/node3.json
+roles/
+roles/role3.json
+users/
+users/user3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2/data_bags' do
+          cwd 'chef_repo2/data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag3/
+bag3/item3.json
+EOM
+          end
+        end
+      end
+
+      context 'when cookbook_path is set to multiple places and nothing else is set' do
+        before :each do
+          %w(client data_bag environment node role user).each do |object_name|
+            Chef::Config.delete("#{object_name}_path".to_sym)
+          end
+          Chef::Config.delete(:chef_repo_path)
+          Chef::Config.cookbook_path = [
+            File.join(@repository_dir, 'cookbooks'),
+            File.join(@repository_dir, 'chef_repo2', 'cookbooks')
+          ]
+        end
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client1.json
+clients/client3.json
+cookbooks/
+cookbooks/cookbook1/
+cookbooks/cookbook1/metadata.rb
+cookbooks/cookbook3/
+cookbooks/cookbook3/metadata.rb
+data_bags/
+data_bags/bag/
+data_bags/bag/item.json
+data_bags/bag3/
+data_bags/bag3/item3.json
+environments/
+environments/env1.json
+environments/env3.json
+nodes/
+nodes/node1.json
+nodes/node3.json
+roles/
+roles/role1.json
+roles/role3.json
+users/
+users/user1.json
+users/user3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+bag3/
+bag3/item3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client1.json
+clients/client3.json
+cookbooks/
+cookbooks/cookbook1/
+cookbooks/cookbook1/metadata.rb
+cookbooks/cookbook3/
+cookbooks/cookbook3/metadata.rb
+data_bags/
+data_bags/bag/
+data_bags/bag/item.json
+data_bags/bag3/
+data_bags/bag3/item3.json
+environments/
+environments/env1.json
+environments/env3.json
+nodes/
+nodes/node1.json
+nodes/node3.json
+roles/
+roles/role1.json
+roles/role3.json
+users/
+users/user1.json
+users/user3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2/data_bags' do
+          cwd 'chef_repo2/data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+bag3/
+bag3/item3.json
+EOM
+          end
+        end
+      end
+
+      context 'when data_bag_path and chef_repo_path are set, and nothing else' do
+        before :each do
+          %w(client cookbook  environment node role user).each do |object_name|
+            Chef::Config.delete("#{object_name}_path".to_sym)
+          end
+          Chef::Config.data_bag_path = File.join(Chef::Config.chef_repo_path, 'data_bags')
+          Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'chef_repo2')
+        end
+
+        context 'when cwd is at the top level' do
+          cwd '.'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2' do
+          cwd 'chef_repo2'
+          it 'knife list --local -Rfp lists everything' do
+            knife('list --local -Rfp').should_succeed <<EOM
+clients/
+clients/client3.json
+cookbooks/
+cookbooks/cookbook3/
+cookbooks/cookbook3/metadata.rb
+data_bags/
+data_bags/bag/
+data_bags/bag/item.json
+environments/
+environments/env3.json
+nodes/
+nodes/node3.json
+roles/
+roles/role3.json
+users/
+users/user3.json
+EOM
+          end
+        end
+
+        context 'when cwd is inside chef_repo2/data_bags' do
+          cwd 'chef_repo2/data_bags'
+          it 'knife list --local -Rfp fails' do
+            knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+          end
+        end
+      end
+
+      context 'when data_bag_path is set and nothing else' do
+        before :each do
+          %w(client cookbook  environment node role user).each do |object_name|
+            Chef::Config.delete("#{object_name}_path".to_sym)
+          end
+          Chef::Config.delete(:chef_repo_path)
+          Chef::Config.data_bag_path = File.join(@repository_dir, 'data_bags')
+        end
+
+        it 'knife list --local -Rfp / lists data bags' do
+          knife('list --local -Rfp /').should_succeed <<EOM
+/data_bags/
+/data_bags/bag/
+/data_bags/bag/item.json
+EOM
+        end
+
+        it 'knife list --local -Rfp /data_bags lists data bags' do
+          knife('list --local -Rfp /data_bags').should_succeed <<EOM
+/data_bags/bag/
+/data_bags/bag/item.json
+EOM
+        end
+
+        context 'when cwd is inside the data_bags directory' do
+          cwd 'data_bags'
+          it 'knife list --local -Rfp lists data bags' do
+            knife('list --local -Rfp').should_succeed <<EOM
+bag/
+bag/item.json
+EOM
+          end
+        end
+      end
+    end
+
+    when_the_repository 'is empty' do
+      context 'when the repository _paths point to places that do not exist' do
+        before :each do
+          %w(client cookbook data_bag environment node role user).each do |object_name|
+            Chef::Config["#{object_name}_path".to_sym] = File.join(Chef::Config.chef_repo_path, 'nowhere', object_name)
+          end
+          Chef::Config.chef_repo_path = File.join(Chef::Config.chef_repo_path, 'nowhere')
+        end
+
+        it 'knife list --local -Rfp / fails' do
+          knife('list --local -Rfp /').should_succeed ''
+        end
+
+        it 'knife list --local -Rfp /data_bags fails' do
+          knife('list --local -Rfp /data_bags').should_fail("ERROR: /data_bags: No such file or directory\n")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/integration/knife/chef_repository_file_system_spec.rb b/spec/integration/knife/chef_repository_file_system_spec.rb
new file mode 100644
index 0000000..68ca5f8
--- /dev/null
+++ b/spec/integration/knife/chef_repository_file_system_spec.rb
@@ -0,0 +1,276 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/list'
+require 'chef/knife/show'
+
+describe 'General chef_repo file system checks' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  context 'directories and files that should/should not be ignored' do
+    when_the_repository "has empty roles, environments and data bag item directories" do
+      directory "roles"
+      directory "environments"
+      directory "data_bags/bag1"
+
+      it "knife list --local -Rfp / returns them" do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/data_bags/
+/data_bags/bag1/
+/environments/
+/roles/
+EOM
+      end
+    end
+
+    when_the_repository "has an empty data_bags directory" do
+      directory "data_bags"
+
+      it "knife list --local / returns it" do
+        knife('list --local /').should_succeed "/data_bags\n"
+      end
+    end
+
+    when_the_repository "has an empty cookbook directory" do
+      directory 'cookbooks/cookbook1'
+
+      it "knife list --local -Rfp / does not return it" do
+        knife('list --local -Rfp /').should_succeed(<<EOM, :stderr => "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+/cookbooks/
+EOM
+      end
+    end
+
+    when_the_repository "has only empty cookbook subdirectories" do
+      directory 'cookbooks/cookbook1/recipes'
+
+      it "knife list --local -Rfp / does not return it" do
+        knife('list --local -Rfp /').should_succeed(<<EOM, :stderr => "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+/cookbooks/
+EOM
+      end
+    end
+
+    when_the_repository "has empty and non-empty cookbook subdirectories" do
+      directory 'cookbooks/cookbook1/recipes'
+      file 'cookbooks/cookbook1/templates/default/x.txt', ''
+
+      it "knife list --local -Rfp / does not return the empty ones" do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/templates/
+/cookbooks/cookbook1/templates/default/
+/cookbooks/cookbook1/templates/default/x.txt
+EOM
+      end
+    end
+
+    when_the_repository "has only empty cookbook sub-sub-directories" do
+      directory 'cookbooks/cookbook1/templates/default'
+
+      it "knife list --local -Rfp / does not return it" do
+        knife('list --local -Rfp /').should_succeed(<<EOM, :stderr => "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+/cookbooks/
+EOM
+      end
+    end
+
+    when_the_repository "has empty cookbook sub-sub-directories alongside non-empty ones" do
+      file 'cookbooks/cookbook1/templates/default/x.txt', ''
+      directory 'cookbooks/cookbook1/templates/rhel'
+      directory 'cookbooks/cookbook1/files/default'
+
+      it "knife list --local -Rfp / does not return the empty ones" do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/templates/
+/cookbooks/cookbook1/templates/default/
+/cookbooks/cookbook1/templates/default/x.txt
+EOM
+      end
+    end
+
+    when_the_repository "has an extra schmenvironments directory" do
+      directory "schmenvironments" do
+        file "_default.json", {}
+      end
+
+      it "knife list --local -Rfp / should NOT return it" do
+        knife('list --local -Rfp /').should_succeed ""
+      end
+    end
+
+    when_the_repository "has extra subdirectories and files under data bag items, roles, and environments" do
+      directory "data_bags/bag1" do
+        file "item1.json", {}
+        file "item2.xml", ""
+        file "another_subdir/item.json", {}
+      end
+      directory "roles" do
+        file "role1.json", {}
+        file "role2.xml", ""
+        file "subdir/role.json", {}
+      end
+      directory "environments" do
+        file "environment1.json", {}
+        file "environment2.xml", ""
+        file "subdir/environment.json", {}
+      end
+
+      it "knife list --local -Rfp / should NOT return them" do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/data_bags/
+/data_bags/bag1/
+/data_bags/bag1/item1.json
+/environments/
+/environments/environment1.json
+/roles/
+/roles/role1.json
+EOM
+      end
+    end
+
+    when_the_repository "has extraneous subdirectories and files under a cookbook" do
+      directory 'cookbooks/cookbook1' do
+        file 'a.rb', ''
+        file 'blarghle/blah.rb', ''
+        directory 'attributes' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+        directory 'definitions' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+        directory 'recipes' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+        directory 'libraries' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+        directory 'templates' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+        directory 'files' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+        directory 'resources' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+        directory 'providers' do
+          file 'a.rb', ''
+          file 'b.json', {}
+          file 'c/d.rb', ''
+          file 'c/e.json', {}
+        end
+      end
+
+      it "knife list --local -Rfp / should NOT return them" do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/a.rb
+/cookbooks/cookbook1/attributes/
+/cookbooks/cookbook1/attributes/a.rb
+/cookbooks/cookbook1/definitions/
+/cookbooks/cookbook1/definitions/a.rb
+/cookbooks/cookbook1/files/
+/cookbooks/cookbook1/files/a.rb
+/cookbooks/cookbook1/files/b.json
+/cookbooks/cookbook1/files/c/
+/cookbooks/cookbook1/files/c/d.rb
+/cookbooks/cookbook1/files/c/e.json
+/cookbooks/cookbook1/libraries/
+/cookbooks/cookbook1/libraries/a.rb
+/cookbooks/cookbook1/providers/
+/cookbooks/cookbook1/providers/a.rb
+/cookbooks/cookbook1/providers/c/
+/cookbooks/cookbook1/providers/c/d.rb
+/cookbooks/cookbook1/recipes/
+/cookbooks/cookbook1/recipes/a.rb
+/cookbooks/cookbook1/resources/
+/cookbooks/cookbook1/resources/a.rb
+/cookbooks/cookbook1/resources/c/
+/cookbooks/cookbook1/resources/c/d.rb
+/cookbooks/cookbook1/templates/
+/cookbooks/cookbook1/templates/a.rb
+/cookbooks/cookbook1/templates/b.json
+/cookbooks/cookbook1/templates/c/
+/cookbooks/cookbook1/templates/c/d.rb
+/cookbooks/cookbook1/templates/c/e.json
+EOM
+      end
+    end
+
+    when_the_repository "has a file in cookbooks/" do
+      file 'cookbooks/file', ''
+      it 'does not show up in list -Rfp' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+EOM
+      end
+    end
+
+    when_the_repository "has a file in data_bags/" do
+      file 'data_bags/file', ''
+      it 'does not show up in list -Rfp' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/data_bags/
+EOM
+      end
+    end
+  end
+
+  when_the_repository 'has a cookbook starting with .' do
+    file 'cookbooks/.svn/metadata.rb', ''
+    file 'cookbooks/a.b/metadata.rb', ''
+    it 'knife list does not show it' do
+      knife('list --local -fp /cookbooks').should_succeed "/cookbooks/a.b/\n"
+    end
+  end
+
+  when_the_repository 'has a data bag starting with .' do
+    file 'data_bags/.svn/x.json', {}
+    file 'data_bags/a.b/x.json', {}
+    it 'knife list does not show it' do
+      knife('list --local -fp /data_bags').should_succeed "/data_bags/a.b/\n"
+    end
+  end
+end
diff --git a/spec/integration/knife/chefignore_spec.rb b/spec/integration/knife/chefignore_spec.rb
new file mode 100644
index 0000000..f2a8d9a
--- /dev/null
+++ b/spec/integration/knife/chefignore_spec.rb
@@ -0,0 +1,271 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/list'
+require 'chef/knife/show'
+
+describe 'chefignore tests' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  when_the_repository "has lots of stuff in it" do
+    file 'roles/x.json', {}
+    file 'environments/x.json', {}
+    file 'data_bags/bag1/x.json', {}
+    file 'cookbooks/cookbook1/x.json', {}
+
+    context "and has a chefignore everywhere except cookbooks" do
+      chefignore = "x.json\nroles/x.json\nenvironments/x.json\ndata_bags/bag1/x.json\nbag1/x.json\ncookbooks/cookbook1/x.json\ncookbook1/x.json\n"
+      file 'chefignore', chefignore
+      file 'roles/chefignore', chefignore
+      file 'environments/chefignore', chefignore
+      file 'data_bags/chefignore', chefignore
+      file 'data_bags/bag1/chefignore', chefignore
+      file 'cookbooks/cookbook1/chefignore', chefignore
+
+      it 'nothing is ignored' do
+        # NOTE: many of the "chefignore" files should probably not show up
+        # themselves, but we have other tests that talk about that
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/chefignore
+/cookbooks/cookbook1/x.json
+/data_bags/
+/data_bags/bag1/
+/data_bags/bag1/x.json
+/environments/
+/environments/x.json
+/roles/
+/roles/x.json
+EOM
+      end
+    end
+  end
+
+  when_the_repository 'has a cookbook with only chefignored files' do
+    file 'cookbooks/cookbook1/templates/default/x.rb', ''
+    file 'cookbooks/cookbook1/libraries/x.rb', ''
+    file 'cookbooks/chefignore', "libraries/x.rb\ntemplates/default/x.rb\n"
+
+    it 'the cookbook is not listed' do
+      knife('list --local -Rfp /').should_succeed(<<EOM, :stderr => "WARN: Cookbook 'cookbook1' is empty or entirely chefignored at #{Chef::Config.chef_repo_path}/cookbooks/cookbook1\n")
+/cookbooks/
+EOM
+    end
+  end
+
+  when_the_repository "has multiple cookbooks" do
+    file 'cookbooks/cookbook1/x.json', {}
+    file 'cookbooks/cookbook1/y.json', {}
+    file 'cookbooks/cookbook2/x.json', {}
+    file 'cookbooks/cookbook2/y.json', {}
+
+    context 'and has a chefignore with filenames' do
+      file 'cookbooks/chefignore', "x.json\n"
+
+      it 'matching files and directories get ignored in all cookbooks' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/y.json
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/y.json
+EOM
+      end
+    end
+
+    context "and has a chefignore with wildcards" do
+      file 'cookbooks/chefignore', "x.*\n"
+      file 'cookbooks/cookbook1/x.rb', ''
+
+      it 'matching files and directories get ignored in all cookbooks' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/y.json
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/y.json
+EOM
+      end
+    end
+
+    context "and has a chefignore with relative paths" do
+      file 'cookbooks/cookbook1/recipes/x.rb', ''
+      file 'cookbooks/cookbook2/recipes/y.rb', ''
+      file 'cookbooks/chefignore', "recipes/x.rb\n"
+
+      it 'matching directories get ignored' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/x.json
+/cookbooks/cookbook1/y.json
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/recipes/
+/cookbooks/cookbook2/recipes/y.rb
+/cookbooks/cookbook2/x.json
+/cookbooks/cookbook2/y.json
+EOM
+      end
+    end
+
+    context "and has a chefignore with subdirectories" do
+      file 'cookbooks/cookbook1/recipes/y.rb', ''
+      file 'cookbooks/chefignore', "recipes\nrecipes/\n"
+
+      it 'matching directories do NOT get ignored' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/recipes/
+/cookbooks/cookbook1/recipes/y.rb
+/cookbooks/cookbook1/x.json
+/cookbooks/cookbook1/y.json
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/x.json
+/cookbooks/cookbook2/y.json
+EOM
+      end
+    end
+
+    context "and has a chefignore that ignores all files in a subdirectory" do
+      file 'cookbooks/cookbook1/templates/default/x.rb', ''
+      file 'cookbooks/cookbook1/libraries/x.rb', ''
+      file 'cookbooks/chefignore', "libraries/x.rb\ntemplates/default/x.rb\n"
+
+      it 'ignores the subdirectory entirely' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/x.json
+/cookbooks/cookbook1/y.json
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/x.json
+/cookbooks/cookbook2/y.json
+EOM
+      end
+    end
+
+    context "and has an empty chefignore" do
+      file 'cookbooks/chefignore', "\n"
+
+      it 'nothing is ignored' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/x.json
+/cookbooks/cookbook1/y.json
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/x.json
+/cookbooks/cookbook2/y.json
+EOM
+      end
+    end
+
+    context "and has a chefignore with comments and empty lines" do
+      file 'cookbooks/chefignore', "\n\n # blah\n#\nx.json\n\n"
+
+      it 'matching files and directories get ignored in all cookbooks' do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/y.json
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/y.json
+EOM
+      end
+    end
+  end
+
+  when_the_repository "has multiple cookbook paths" do
+    before :each do
+      Chef::Config.cookbook_path = [
+        File.join(Chef::Config.chef_repo_path, 'cookbooks1'),
+        File.join(Chef::Config.chef_repo_path, 'cookbooks2')
+      ]
+    end
+
+    file 'cookbooks1/mycookbook/metadata.rb', ''
+    file 'cookbooks1/mycookbook/x.json', {}
+    file 'cookbooks2/yourcookbook/metadata.rb', ''
+    file 'cookbooks2/yourcookbook/x.json', ''
+
+    context "and multiple chefignores" do
+      file 'cookbooks1/chefignore', "metadata.rb\n"
+      file 'cookbooks2/chefignore', "x.json\n"
+      it "chefignores apply only to the directories they are in" do
+        knife('list --local -Rfp /').should_succeed <<EOM
+/cookbooks/
+/cookbooks/mycookbook/
+/cookbooks/mycookbook/x.json
+/cookbooks/yourcookbook/
+/cookbooks/yourcookbook/metadata.rb
+EOM
+      end
+
+      context "and conflicting cookbooks" do
+        file 'cookbooks1/yourcookbook/metadata.rb', ''
+        file 'cookbooks1/yourcookbook/x.json', ''
+        file 'cookbooks1/yourcookbook/onlyincookbooks1.rb', ''
+        file 'cookbooks2/yourcookbook/onlyincookbooks2.rb', ''
+
+        it "chefignores apply only to the winning cookbook" do
+          knife('list --local -Rfp /').should_succeed(<<EOM, :stderr => "WARN: Child with name 'yourcookbook' found in multiple directories: #{Chef::Config.chef_repo_path}/cookbooks1/yourcookbook and #{Chef::Config.chef_repo_path}/cookbooks2/yourcookbook\n")
+/cookbooks/
+/cookbooks/mycookbook/
+/cookbooks/mycookbook/x.json
+/cookbooks/yourcookbook/
+/cookbooks/yourcookbook/onlyincookbooks1.rb
+/cookbooks/yourcookbook/x.json
+EOM
+        end
+      end
+    end
+  end
+
+  when_the_repository 'has a cookbook named chefignore' do
+    file 'cookbooks/chefignore/metadata.rb', {}
+    it 'knife list -Rfp /cookbooks shows it' do
+      knife('list --local -Rfp /cookbooks').should_succeed <<EOM
+/cookbooks/chefignore/
+/cookbooks/chefignore/metadata.rb
+EOM
+    end
+  end
+
+  when_the_repository 'has multiple cookbook paths, one with a chefignore file and the other with a cookbook named chefignore' do
+    file 'cookbooks1/chefignore', ''
+    file 'cookbooks1/blah/metadata.rb', ''
+    file 'cookbooks2/chefignore/metadata.rb', ''
+    before :each do
+      Chef::Config.cookbook_path = [
+        File.join(Chef::Config.chef_repo_path, 'cookbooks1'),
+        File.join(Chef::Config.chef_repo_path, 'cookbooks2')
+      ]
+    end
+    it 'knife list -Rfp /cookbooks shows the chefignore cookbook' do
+      knife('list --local -Rfp /cookbooks').should_succeed <<EOM
+/cookbooks/blah/
+/cookbooks/blah/metadata.rb
+/cookbooks/chefignore/
+/cookbooks/chefignore/metadata.rb
+EOM
+    end
+  end
+end
diff --git a/spec/integration/knife/common_options_spec.rb b/spec/integration/knife/common_options_spec.rb
new file mode 100644
index 0000000..6de4d93
--- /dev/null
+++ b/spec/integration/knife/common_options_spec.rb
@@ -0,0 +1,103 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/raw'
+
+describe 'knife common options' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  when_the_repository "has a node" do
+    file 'nodes/x.json', {}
+
+    before(:each) do
+      if ChefZero::RSpec.server
+        ChefZero::RSpec.server.stop
+        ChefZero::RSpec.server = nil
+      end
+    end
+
+    context 'When chef_zero.enabled is true' do
+      before(:each) do
+        Chef::Config.chef_zero.enabled = true
+      end
+
+      it 'knife raw /nodes/x should retrieve the role' do
+        knife('raw /nodes/x').should_succeed /"name": "x"/
+      end
+
+      context 'And chef_zero.port is 9999' do
+        before(:each) { Chef::Config.chef_zero.port = 9999 }
+ 
+        it 'knife raw /nodes/x should retrieve the role' do
+          knife('raw /nodes/x').should_succeed /"name": "x"/
+          Chef::Config.chef_server_url.should == 'http://127.0.0.1:9999'
+        end
+      end
+
+      context 'and there is a private key' do
+        file 'mykey.pem', <<EOM
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEApubutqtYYQ5UiA9QhWP7UvSmsfHsAoPKEVVPdVW/e8Svwpyf
+0Xef6OFWVmBE+W442ZjLOe2y6p2nSnaq4y7dg99NFz6X+16mcKiCbj0RCiGqCvCk
+NftHhTgO9/RFvCbmKZ1RKNob1YzLrFpxBHaSh9po+DGWhApcd+I+op+ZzvDgXhNn
+0nauZu3rZmApI/r7EEAOjFedAXs7VPNXhhtZAiLSAVIrwU3ZajtSzgXOxbNzgj5O
+AAAMmThK+71qPdffAdO4J198H6/MY04qgtFo7vumzCq0UCaGZfmeI1UNE4+xQWwP
+HJ3pDAP61C6Ebx2snI2kAd9QMx9Y78nIedRHPwIDAQABAoIBAHssRtPM1GacWsom
+8zfeN6ZbI4KDlbetZz0vhnqDk9NVrpijWlcOP5dwZXVNitnB/HaqCqFvyPDY9JNB
+zI/pEFW4QH59FVDP42mVEt0keCTP/1wfiDDGh1vLqVBYl/ZphscDcNgDTzNkuxMx
+k+LFVxKnn3w7rGc59lALSkpeGvbbIDjp3LUMlUeCF8CIFyYZh9ZvXe4OCxYdyjxb
+i8tnMLKvJ4Psbh5jMapsu3rHQkfPdqzztQUz8vs0NYwP5vWge46FUyk+WNm/IhbJ
+G3YM22nwUS8Eu2bmTtADSJolATbCSkOwQ1D+Fybz/4obfYeGaCdOqB05ttubhenV
+ShsAb7ECgYEA20ecRVxw2S7qA7sqJ4NuYOg9TpfGooptYNA1IP971eB6SaGAelEL
+awYkGNuu2URmm5ElZpwJFFTDLGA7t2zB2xI1FeySPPIVPvJGSiZoFQOVlIg9WQzK
+7jTtFQ/tOMrF+bigEUJh5bP1/7HzqSpuOsPjEUb2aoCTp+tpiRGL7TUCgYEAwtns
+g3ysrSEcTzpSv7fQRJRk1lkBhatgNd0oc+ikzf74DaVLhBg1jvSThDhiDCdB59mr
+Jh41cnR1XqE8jmdQbCDRiFrI1Pq6TPaDZFcovDVE1gue9x86v3FOH2ukPG4d2/Xy
+HevXjThtpMMsWFi0JYXuzXuV5HOvLZiP8sN3lSMCgYANpdxdGM7RRbE9ADY0dWK2
+V14ReTLcxP7fyrWz0xLzEeCqmomzkz3BsIUoouu0DCTSw+rvAwExqcDoDylIVlWO
+fAifz7SeZHbcDxo+3TsXK7zwnLYsx7YNs2+aIv6hzUUbMNmNmXMcZ+IEwx+mRMTN
+lYmZdrA5mr0V83oDFPt/jQKBgC74RVE03pMlZiObFZNtheDiPKSG9Bz6wMh7NWMr
+c37MtZLkg52mEFMTlfPLe6ceV37CM8WOhqe+dwSGrYhOU06dYqUR7VOZ1Qr0aZvo
+fsNPu/Y0+u7rMkgv0fs1AXQnvz7kvKaF0YITVirfeXMafuKEtJoH7owRbur42cpV
+YCAtAoGAP1rHOc+w0RUcBK3sY7aErrih0OPh9U5bvJsrw1C0FIZhCEoDVA+fNIQL
+syHLXYFNy0OxMtH/bBAXBGNHd9gf5uOnqh0pYcbe/uRAxumC7Rl0cL509eURiA2T
++vFmf54y9YdnLXaqv+FhJT6B6V7WX7IpU9BMqJY1cJYXHuHG2KA=
+-----END RSA PRIVATE KEY-----
+EOM
+
+        it 'knife raw /nodes/x should retrieve the role' do
+          knife('raw /nodes/x').should_succeed /"name": "x"/
+        end
+      end
+    end
+
+    it 'knife raw -z /nodes/x retrieves the role' do
+      knife('raw -z /nodes/x').should_succeed /"name": "x"/
+    end
+
+    it 'knife raw --local-mode /nodes/x retrieves the role' do
+      knife('raw --local-mode /nodes/x').should_succeed /"name": "x"/
+    end
+
+    it 'knife raw -z --chef-zero-port=9999 /nodes/x retrieves the role' do
+      knife('raw -z --chef-zero-port=9999 /nodes/x').should_succeed /"name": "x"/
+      Chef::Config.chef_server_url.should == 'http://127.0.0.1:9999'
+    end
+  end
+end
diff --git a/spec/integration/knife/delete_spec.rb b/spec/integration/knife/delete_spec.rb
new file mode 100644
index 0000000..8d9b972
--- /dev/null
+++ b/spec/integration/knife/delete_spec.rb
@@ -0,0 +1,944 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/delete'
+require 'chef/knife/list'
+require 'chef/knife/raw'
+
+describe 'knife delete' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  let :everything do
+    <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+  end
+
+  let :server_everything do
+    <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+  end
+  let :server_nothing do
+    <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/cookbooks
+/data_bags
+/environments
+/environments/_default.json
+/nodes
+/roles
+/users
+/users/admin.json
+EOM
+  end
+
+  let :nothing do
+    <<EOM
+/clients
+/cookbooks
+/data_bags
+/environments
+/nodes
+/roles
+/users
+EOM
+  end
+
+  when_the_chef_server "has one of each thing" do
+    client 'x', '{}'
+    cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+    data_bag 'x', { 'y' => '{}' }
+    environment 'x', '{}'
+    node 'x', '{}'
+    role 'x', '{}'
+    user 'x', '{}'
+
+    when_the_repository 'also has one of each thing' do
+      file 'clients/x.json', {}
+      file 'cookbooks/x/metadata.rb', ''
+      file 'data_bags/x/y.json', {}
+      file 'environments/_default.json', {}
+      file 'environments/x.json', {}
+      file 'nodes/x.json', {}
+      file 'roles/x.json', {}
+      file 'users/x.json', {}
+
+      it 'knife delete --both /cookbooks/x fails' do
+        knife('delete --both /cookbooks/x').should_fail <<EOM
+ERROR: /cookbooks/x (remote) must be deleted recursively!  Pass -r to knife delete.
+ERROR: /cookbooks/x (local) must be deleted recursively!  Pass -r to knife delete.
+EOM
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both -r /cookbooks/x deletes x' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete -r --local /cookbooks/x deletes x locally but not remotely' do
+        knife('delete -r --local /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete -r /cookbooks/x deletes x remotely but not locally' do
+        knife('delete -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      # TODO delete empty data bag (particularly different on local side)
+      context 'with an empty data bag on both' do
+        data_bag 'empty', {}
+        directory 'data_bags/empty'
+        it 'knife delete --both /data_bags/empty fails but deletes local version' do
+          knife('delete --both /data_bags/empty').should_fail <<EOM
+ERROR: /data_bags/empty (remote) must be deleted recursively!  Pass -r to knife delete.
+ERROR: /data_bags/empty (local) must be deleted recursively!  Pass -r to knife delete.
+EOM
+          knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/empty
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+          knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/empty
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+        end
+      end
+
+      it 'knife delete --both /data_bags/x fails' do
+        knife('delete --both /data_bags/x').should_fail <<EOM
+ERROR: /data_bags/x (remote) must be deleted recursively!  Pass -r to knife delete.
+ERROR: /data_bags/x (local) must be deleted recursively!  Pass -r to knife delete.
+EOM
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both -r /data_bags/x deletes x' do
+        knife('delete --both -r /data_bags/x').should_succeed "Deleted /data_bags/x\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /environments/x.json deletes x' do
+        knife('delete --both /environments/x.json').should_succeed "Deleted /environments/x.json\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /roles/x.json deletes x' do
+        knife('delete --both /roles/x.json').should_succeed "Deleted /roles/x.json\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /environments/_default.json fails but still deletes the local copy' do
+        knife('delete --both /environments/_default.json').should_fail :stderr => "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", :stdout => "Deleted /environments/_default.json\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /environments/nonexistent.json fails' do
+        knife('delete --both /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both / fails' do
+        knife('delete --both /').should_fail <<EOM
+ERROR: / (remote) cannot be deleted.
+ERROR: / (local) cannot be deleted.
+EOM
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both -r /* fails' do
+        knife('delete --both -r /*').should_fail <<EOM
+ERROR: / (remote) cannot be deleted.
+ERROR: / (local) cannot be deleted.
+ERROR: /clients (remote) cannot be deleted.
+ERROR: /clients (local) cannot be deleted.
+ERROR: /cookbooks (remote) cannot be deleted.
+ERROR: /cookbooks (local) cannot be deleted.
+ERROR: /data_bags (remote) cannot be deleted.
+ERROR: /data_bags (local) cannot be deleted.
+ERROR: /environments (remote) cannot be deleted.
+ERROR: /environments (local) cannot be deleted.
+ERROR: /nodes (remote) cannot be deleted.
+ERROR: /nodes (local) cannot be deleted.
+ERROR: /roles (remote) cannot be deleted.
+ERROR: /roles (local) cannot be deleted.
+ERROR: /users (remote) cannot be deleted.
+ERROR: /users (local) cannot be deleted.
+EOM
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed everything
+      end
+    end
+
+    when_the_repository 'has only top-level directories' do
+      directory 'clients'
+      directory 'cookbooks'
+      directory 'data_bags'
+      directory 'environments'
+      directory 'nodes'
+      directory 'roles'
+      directory 'users'
+
+      it 'knife delete --both /cookbooks/x fails' do
+        knife('delete --both /cookbooks/x').should_fail "ERROR: /cookbooks/x (remote) must be deleted recursively!  Pass -r to knife delete.\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both -r /cookbooks/x deletes x' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both /data_bags/x fails' do
+        knife('delete --both /data_bags/x').should_fail "ERROR: /data_bags/x (remote) must be deleted recursively!  Pass -r to knife delete.\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both -r /data_bags/x deletes x' do
+        knife('delete --both -r /data_bags/x').should_succeed "Deleted /data_bags/x\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both /environments/x.json deletes x' do
+        knife('delete --both /environments/x.json').should_succeed "Deleted /environments/x.json\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both /roles/x.json deletes x' do
+        knife('delete --both /roles/x.json').should_succeed "Deleted /roles/x.json\n"
+        knife('list -Rf /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/users
+/users/admin.json
+/users/x.json
+EOM
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both /environments/_default.json fails' do
+        knife('delete --both /environments/_default.json').should_fail "", :stderr => "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both / fails' do
+        knife('delete --both /').should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both -r /* fails' do
+        knife('delete --both -r /*').should_fail <<EOM
+ERROR: / (remote) cannot be deleted.
+ERROR: / (local) cannot be deleted.
+ERROR: /clients (remote) cannot be deleted.
+ERROR: /clients (local) cannot be deleted.
+ERROR: /cookbooks (remote) cannot be deleted.
+ERROR: /cookbooks (local) cannot be deleted.
+ERROR: /data_bags (remote) cannot be deleted.
+ERROR: /data_bags (local) cannot be deleted.
+ERROR: /environments (remote) cannot be deleted.
+ERROR: /environments (local) cannot be deleted.
+ERROR: /nodes (remote) cannot be deleted.
+ERROR: /nodes (local) cannot be deleted.
+ERROR: /roles (remote) cannot be deleted.
+ERROR: /roles (local) cannot be deleted.
+ERROR: /users (remote) cannot be deleted.
+ERROR: /users (local) cannot be deleted.
+EOM
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      it 'knife delete --both /environments/nonexistent.json fails' do
+        knife('delete --both /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+        knife('list -Rf /').should_succeed server_everything
+        knife('list -Rf --local /').should_succeed nothing
+      end
+
+      context 'and cwd is at the top level' do
+        cwd '.'
+        it 'knife delete fails' do
+          knife('delete').should_fail "FATAL: Must specify at least one argument.  If you want to delete everything in this directory, type \"knife delete --recurse .\"\n", :stdout => /USAGE/
+          knife('list -Rf /').should_succeed <<EOM
+clients
+clients/chef-validator.json
+clients/chef-webui.json
+clients/x.json
+cookbooks
+cookbooks/x
+cookbooks/x/metadata.rb
+data_bags
+data_bags/x
+data_bags/x/y.json
+environments
+environments/_default.json
+environments/x.json
+nodes
+nodes/x.json
+roles
+roles/x.json
+users
+users/admin.json
+users/x.json
+EOM
+          knife('list -Rf --local /').should_succeed <<EOM
+clients
+cookbooks
+data_bags
+environments
+nodes
+roles
+users
+EOM
+        end
+      end
+    end
+  end
+
+  when_the_chef_server 'is empty' do
+    when_the_repository 'has one of each thing' do
+      file 'clients/x.json', {}
+      file 'cookbooks/x/metadata.rb', ''
+      file 'data_bags/x/y.json', {}
+      file 'environments/_default.json', {}
+      file 'environments/x.json', {}
+      file 'nodes/x.json', {}
+      file 'roles/x.json', {}
+      file 'users/x.json', {}
+
+      it 'knife delete --both /cookbooks/x fails' do
+        knife('delete --both /cookbooks/x').should_fail "ERROR: /cookbooks/x (local) must be deleted recursively!  Pass -r to knife delete.\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both -r /cookbooks/x deletes x' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /data_bags/x fails' do
+        knife('delete --both /data_bags/x').should_fail "ERROR: /data_bags/x (local) must be deleted recursively!  Pass -r to knife delete.\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both -r /data_bags/x deletes x' do
+        knife('delete --both -r /data_bags/x').should_succeed "Deleted /data_bags/x\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /environments/x.json deletes x' do
+        knife('delete --both /environments/x.json').should_succeed "Deleted /environments/x.json\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /roles/x.json deletes x' do
+        knife('delete --both /roles/x.json').should_succeed "Deleted /roles/x.json\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/_default.json
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both /environments/_default.json fails but still deletes the local copy' do
+        knife('delete --both /environments/_default.json').should_fail :stderr => "ERROR: /environments/_default.json (remote) cannot be deleted (default environment cannot be modified).\n", :stdout => "Deleted /environments/_default.json\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed <<EOM
+/clients
+/clients/x.json
+/cookbooks
+/cookbooks/x
+/cookbooks/x/metadata.rb
+/data_bags
+/data_bags/x
+/data_bags/x/y.json
+/environments
+/environments/x.json
+/nodes
+/nodes/x.json
+/roles
+/roles/x.json
+/users
+/users/x.json
+EOM
+      end
+
+      it 'knife delete --both / fails' do
+        knife('delete --both /').should_fail "ERROR: / (remote) cannot be deleted.\nERROR: / (local) cannot be deleted.\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both -r /* fails' do
+        knife('delete --both -r /*').should_fail <<EOM
+ERROR: / (remote) cannot be deleted.
+ERROR: / (local) cannot be deleted.
+ERROR: /clients (remote) cannot be deleted.
+ERROR: /clients (local) cannot be deleted.
+ERROR: /cookbooks (remote) cannot be deleted.
+ERROR: /cookbooks (local) cannot be deleted.
+ERROR: /data_bags (remote) cannot be deleted.
+ERROR: /data_bags (local) cannot be deleted.
+ERROR: /environments (remote) cannot be deleted.
+ERROR: /environments (local) cannot be deleted.
+ERROR: /nodes (remote) cannot be deleted.
+ERROR: /nodes (local) cannot be deleted.
+ERROR: /roles (remote) cannot be deleted.
+ERROR: /roles (local) cannot be deleted.
+ERROR: /users (remote) cannot be deleted.
+ERROR: /users (local) cannot be deleted.
+EOM
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      it 'knife delete --both /environments/nonexistent.json fails' do
+        knife('delete --both /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+        knife('list -Rf /').should_succeed server_nothing
+        knife('list -Rf --local /').should_succeed everything
+      end
+
+      context 'and cwd is at the top level' do
+        cwd '.'
+        it 'knife delete fails' do
+          knife('delete').should_fail "FATAL: Must specify at least one argument.  If you want to delete everything in this directory, type \"knife delete --recurse .\"\n", :stdout => /USAGE/
+          knife('list -Rf /').should_succeed <<EOM
+clients
+clients/chef-validator.json
+clients/chef-webui.json
+cookbooks
+data_bags
+environments
+environments/_default.json
+nodes
+roles
+users
+users/admin.json
+EOM
+          knife('list -Rf --local /').should_succeed <<EOM
+clients
+clients/x.json
+cookbooks
+cookbooks/x
+cookbooks/x/metadata.rb
+data_bags
+data_bags/x
+data_bags/x/y.json
+environments
+environments/_default.json
+environments/x.json
+nodes
+nodes/x.json
+roles
+roles/x.json
+users
+users/x.json
+EOM
+        end
+      end
+    end
+  end
+
+  when_the_repository 'has a cookbook' do
+    file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+    file 'cookbooks/x/onlyin1.0.0.rb', 'old_text'
+
+    when_the_chef_server 'has a later version for the cookbook' do
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => '' }
+      cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+      # TODO this seems wrong
+      it 'knife delete --both -r /cookbooks/x deletes the latest version on the server and the local version' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('raw /cookbooks/x').should_succeed(/1.0.0/)
+        knife('list --local /cookbooks').should_succeed ''
+      end
+    end
+
+    when_the_chef_server 'has an earlier version for the cookbook' do
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => ''}
+      cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+
+      it 'knife delete --both /cookbooks/x deletes the latest version on the server and the local version' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('raw /cookbooks/x').should_succeed(/0.9.9/)
+        knife('list --local /cookbooks').should_succeed ''
+      end
+    end
+
+    when_the_chef_server 'has a later version for the cookbook, and no current version' do
+      cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+      it 'knife delete --both /cookbooks/x deletes the server and client version of the cookbook' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('raw /cookbooks/x').should_fail(/404/)
+        knife('list --local /cookbooks').should_succeed ''
+      end
+    end
+
+    when_the_chef_server 'has an earlier version for the cookbook, and no current version' do
+      cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+
+      it 'knife delete --both /cookbooks/x deletes the server and client version of the cookbook' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('raw /cookbooks/x').should_fail(/404/)
+        knife('list --local /cookbooks').should_succeed ''
+      end
+    end
+  end
+
+  when_the_repository 'is empty' do
+    when_the_chef_server 'has two versions of a cookbook' do
+      cookbook 'x', '2.0.11', { 'metadata.rb' => 'version "2.0.11"' }
+      cookbook 'x', '11.0.0', { 'metadata.rb' => 'version "11.0.0"' }
+      it 'knife delete deletes the latest version' do
+        knife('delete --both -r /cookbooks/x').should_succeed "Deleted /cookbooks/x\n"
+        knife('raw /cookbooks/x').should_succeed /2.0.11/
+      end
+    end
+  end
+end
diff --git a/spec/integration/knife/deps_spec.rb b/spec/integration/knife/deps_spec.rb
new file mode 100644
index 0000000..5ede0ca
--- /dev/null
+++ b/spec/integration/knife/deps_spec.rb
@@ -0,0 +1,648 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/deps'
+
+describe 'knife deps' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  context 'local' do
+    when_the_repository 'has a role with no run_list' do
+      file 'roles/starring.json', {}
+      it 'knife deps reports no dependencies' do
+        knife('deps /roles/starring.json').should_succeed "/roles/starring.json\n"
+      end
+    end
+
+    when_the_repository 'has a role with a default run_list' do
+      file 'roles/starring.json', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
+      file 'roles/minor.json', {}
+      file 'cookbooks/quiche/metadata.rb', 'name "quiche"'
+      file 'cookbooks/quiche/recipes/default.rb', ''
+      file 'cookbooks/soup/metadata.rb', 'name "soup"'
+      file 'cookbooks/soup/recipes/chicken.rb', ''
+      it 'knife deps reports all dependencies' do
+        knife('deps /roles/starring.json').should_succeed <<EOM
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+EOM
+      end
+    end
+
+    when_the_repository 'has a role with an env_run_list' do
+      file 'roles/starring.json', { 'env_run_lists' => { 'desert' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) } }
+      file 'roles/minor.json', {}
+      file 'cookbooks/quiche/metadata.rb', 'name "quiche"'
+      file 'cookbooks/quiche/recipes/default.rb', ''
+      file 'cookbooks/soup/metadata.rb', 'name "soup"'
+      file 'cookbooks/soup/recipes/chicken.rb', ''
+      it 'knife deps reports all dependencies' do
+        knife('deps /roles/starring.json').should_succeed <<EOM
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+EOM
+      end
+    end
+
+    when_the_repository 'has a node with no environment or run_list' do
+      file 'nodes/mort.json', {}
+      it 'knife deps reports just the node' do
+        knife('deps /nodes/mort.json').should_succeed "/nodes/mort.json\n"
+      end
+    end
+    when_the_repository 'has a node with an environment' do
+      file 'environments/desert.json', {}
+      file 'nodes/mort.json', { 'chef_environment' => 'desert' }
+      it 'knife deps reports just the node' do
+        knife('deps /nodes/mort.json').should_succeed "/environments/desert.json\n/nodes/mort.json\n"
+      end
+    end
+    when_the_repository 'has a node with roles and recipes in its run_list' do
+      file 'roles/minor.json', {}
+      file 'cookbooks/quiche/metadata.rb', 'name "quiche"'
+      file 'cookbooks/quiche/recipes/default.rb', ''
+      file 'cookbooks/soup/metadata.rb', 'name "soup"'
+      file 'cookbooks/soup/recipes/chicken.rb', ''
+      file 'nodes/mort.json', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
+      it 'knife deps reports just the node' do
+        knife('deps /nodes/mort.json').should_succeed <<EOM
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/nodes/mort.json
+EOM
+      end
+    end
+    when_the_repository 'has a cookbook with no dependencies' do
+      file 'cookbooks/quiche/metadata.rb', 'name "quiche"'
+      file 'cookbooks/quiche/recipes/default.rb', ''
+      it 'knife deps reports just the cookbook' do
+        knife('deps /cookbooks/quiche').should_succeed "/cookbooks/quiche\n"
+      end
+    end
+    when_the_repository 'has a cookbook with dependencies' do
+      file 'cookbooks/kettle/metadata.rb', 'name "kettle"'
+      file 'cookbooks/quiche/metadata.rb', "name 'quiche'\ndepends 'kettle'\n"
+      file 'cookbooks/quiche/recipes/default.rb', ''
+      it 'knife deps reports just the cookbook' do
+        knife('deps /cookbooks/quiche').should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n"
+      end
+    end
+    when_the_repository 'has a data bag' do
+      file 'data_bags/bag/item.json', {}
+      it 'knife deps reports just the data bag' do
+        knife('deps /data_bags/bag/item.json').should_succeed "/data_bags/bag/item.json\n"
+      end
+    end
+    when_the_repository 'has an environment' do
+      file 'environments/desert.json', {}
+      it 'knife deps reports just the environment' do
+        knife('deps /environments/desert.json').should_succeed "/environments/desert.json\n"
+      end
+    end
+    when_the_repository 'has a deep dependency tree' do
+      file 'roles/starring.json', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
+      file 'roles/minor.json', {}
+      file 'cookbooks/quiche/metadata.rb', 'name "quiche"'
+      file 'cookbooks/quiche/recipes/default.rb', ''
+      file 'cookbooks/soup/metadata.rb', 'name "soup"'
+      file 'cookbooks/soup/recipes/chicken.rb', ''
+      file 'environments/desert.json', {}
+      file 'nodes/mort.json', { 'chef_environment' => 'desert', 'run_list' => [ 'role[starring]' ] }
+      file 'nodes/bart.json', { 'run_list' => [ 'role[minor]' ] }
+
+      it 'knife deps reports all dependencies' do
+        knife('deps /nodes/mort.json').should_succeed <<EOM
+/environments/desert.json
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+/nodes/mort.json
+EOM
+      end
+      it 'knife deps * reports all dependencies of all things' do
+        knife('deps /nodes/*').should_succeed <<EOM
+/roles/minor.json
+/nodes/bart.json
+/environments/desert.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+/nodes/mort.json
+EOM
+      end
+      it 'knife deps a b reports all dependencies of a and b' do
+        knife('deps /nodes/bart.json /nodes/mort.json').should_succeed <<EOM
+/roles/minor.json
+/nodes/bart.json
+/environments/desert.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+/nodes/mort.json
+EOM
+      end
+      it 'knife deps --tree /* shows dependencies in a tree' do
+        knife('deps --tree /nodes/*').should_succeed <<EOM
+/nodes/bart.json
+  /roles/minor.json
+/nodes/mort.json
+  /environments/desert.json
+  /roles/starring.json
+    /roles/minor.json
+    /cookbooks/quiche
+    /cookbooks/soup
+EOM
+      end
+      it 'knife deps --tree --no-recurse shows only the first level of dependencies' do
+        knife('deps --tree --no-recurse /nodes/*').should_succeed <<EOM
+/nodes/bart.json
+  /roles/minor.json
+/nodes/mort.json
+  /environments/desert.json
+  /roles/starring.json
+EOM
+      end
+    end
+
+    context 'circular dependencies' do
+      when_the_repository 'has cookbooks with circular dependencies' do
+        file 'cookbooks/foo/metadata.rb', "name 'foo'\ndepends 'bar'\n"
+        file 'cookbooks/bar/metadata.rb', "name 'bar'\ndepends 'baz'\n"
+        file 'cookbooks/baz/metadata.rb', "name 'baz'\ndepends 'foo'\n"
+        file 'cookbooks/self/metadata.rb', "name 'self'\ndepends 'self'\n"
+        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
+        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
+        end
+      end
+      when_the_repository 'has roles with circular dependencies' do
+        file 'roles/foo.json', { 'run_list' => [ 'role[bar]' ] }
+        file 'roles/bar.json', { 'run_list' => [ 'role[baz]' ] }
+        file 'roles/baz.json', { 'run_list' => [ 'role[foo]' ] }
+        file 'roles/self.json', { 'run_list' => [ 'role[self]' ] }
+        it 'knife deps prints each once' do
+          knife('deps /roles/foo.json /roles/self.json').should_succeed <<EOM
+/roles/baz.json
+/roles/bar.json
+/roles/foo.json
+/roles/self.json
+EOM
+        end
+        it 'knife deps --tree prints each once' do
+          knife('deps --tree /roles/foo.json /roles/self.json') do
+            stdout.should == "/roles/foo.json\n  /roles/bar.json\n    /roles/baz.json\n      /roles/foo.json\n/roles/self.json\n  /roles/self.json\n"
+            stderr.should == "WARNING: No knife configuration file found\n"
+          end
+        end
+      end
+    end
+
+    context 'missing objects' do
+      when_the_repository 'is empty' do
+        it 'knife deps /blah reports an error' do
+          knife('deps /blah').should_fail(
+            :exit_code => 2,
+            :stdout => "/blah\n",
+            :stderr => "ERROR: /blah: No such file or directory\n"
+          )
+        end
+        it 'knife deps /roles/x.json reports an error' do
+          knife('deps /roles/x.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/roles/x.json\n",
+            :stderr => "ERROR: /roles/x.json: No such file or directory\n"
+          )
+        end
+        it 'knife deps /nodes/x.json reports an error' do
+          knife('deps /nodes/x.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/nodes/x.json\n",
+            :stderr => "ERROR: /nodes/x.json: No such file or directory\n"
+          )
+        end
+        it 'knife deps /environments/x.json reports an error' do
+          knife('deps /environments/x.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/environments/x.json\n",
+            :stderr => "ERROR: /environments/x.json: No such file or directory\n"
+          )
+        end
+        it 'knife deps /cookbooks/x reports an error' do
+          knife('deps /cookbooks/x').should_fail(
+            :exit_code => 2,
+            :stdout => "/cookbooks/x\n",
+            :stderr => "ERROR: /cookbooks/x: No such file or directory\n"
+          )
+        end
+        it 'knife deps /data_bags/bag/item reports an error' do
+          knife('deps /data_bags/bag/item').should_fail(
+            :exit_code => 2,
+            :stdout => "/data_bags/bag/item\n",
+            :stderr => "ERROR: /data_bags/bag/item: No such file or directory\n"
+          )
+        end
+      end
+      when_the_repository 'is missing a dependent cookbook' do
+        file 'roles/starring.json', { 'run_list' => [ 'recipe[quiche]'] }
+        it 'knife deps reports the cookbook, along with an error' do
+          knife('deps /roles/starring.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/cookbooks/quiche\n/roles/starring.json\n",
+            :stderr => "ERROR: /cookbooks/quiche: No such file or directory\n"
+          )
+        end
+      end
+      when_the_repository 'is missing a dependent environment' do
+        file 'nodes/mort.json', { 'chef_environment' => 'desert' }
+        it 'knife deps reports the environment, along with an error' do
+          knife('deps /nodes/mort.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/environments/desert.json\n/nodes/mort.json\n",
+            :stderr => "ERROR: /environments/desert.json: No such file or directory\n"
+          )
+        end
+      end
+      when_the_repository 'is missing a dependent role' do
+        file 'roles/starring.json', { 'run_list' => [ 'role[minor]'] }
+        it 'knife deps reports the role, along with an error' do
+          knife('deps /roles/starring.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/roles/minor.json\n/roles/starring.json\n",
+            :stderr => "ERROR: /roles/minor.json: No such file or directory\n"
+          )
+        end
+      end
+    end
+    context 'invalid objects' do
+      when_the_repository 'is empty' do
+        it 'knife deps / reports itself only' do
+          knife('deps /').should_succeed("/\n")
+        end
+        it 'knife deps /roles reports an error' do
+          knife('deps /roles').should_fail(
+            :exit_code => 2,
+            :stderr => "ERROR: /roles: No such file or directory\n",
+            :stdout => "/roles\n"
+          )
+        end
+      end
+      when_the_repository 'has a data bag' do
+        file 'data_bags/bag/item.json', ''
+        it 'knife deps /data_bags/bag shows no dependencies' do
+          knife('deps /data_bags/bag').should_succeed("/data_bags/bag\n")
+        end
+      end
+      when_the_repository 'has a cookbook' do
+        file 'cookbooks/blah/metadata.rb', 'name "blah"'
+        it 'knife deps on a cookbook file shows no dependencies' do
+          knife('deps /cookbooks/blah/metadata.rb').should_succeed(
+            "/cookbooks/blah/metadata.rb\n"
+          )
+        end
+      end
+    end
+  end
+
+  context 'remote' do
+    when_the_chef_server 'has a role with no run_list' do
+      role 'starring', {}
+      it 'knife deps reports no dependencies' do
+        knife('deps --remote /roles/starring.json').should_succeed "/roles/starring.json\n"
+      end
+    end
+
+    when_the_chef_server 'has a role with a default run_list' do
+      role 'starring', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
+      role 'minor', {}
+      cookbook 'quiche', '1.0.0', { 'metadata.rb' => "name 'quiche'\nversion '1.0.0'\n", 'recipes' => { 'default.rb' => '' } }
+      cookbook 'soup', '1.0.0', { 'metadata.rb' => "name 'soup'\nversion '1.0.0'\n", 'recipes' => { 'chicken.rb' => '' } }
+      it 'knife deps reports all dependencies' do
+        knife('deps --remote /roles/starring.json').should_succeed <<EOM
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+EOM
+      end
+    end
+
+    when_the_chef_server 'has a role with an env_run_list' do
+      role 'starring', { 'env_run_lists' => { 'desert' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) } }
+      role 'minor', {}
+      cookbook 'quiche', '1.0.0', { 'metadata.rb' => "name 'quiche'\nversion '1.0.0'\n", 'recipes' => { 'default.rb' => '' } }
+      cookbook 'soup', '1.0.0', { 'metadata.rb' =>   "name 'soup'\nversion '1.0.0'\n", 'recipes' => { 'chicken.rb' => '' } }
+      it 'knife deps reports all dependencies' do
+        knife('deps --remote /roles/starring.json').should_succeed <<EOM
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+EOM
+      end
+    end
+
+    when_the_chef_server 'has a node with no environment or run_list' do
+      node 'mort', {}
+      it 'knife deps reports just the node' do
+        knife('deps --remote /nodes/mort.json').should_succeed "/nodes/mort.json\n"
+      end
+    end
+    when_the_chef_server 'has a node with an environment' do
+      environment 'desert', {}
+      node 'mort', { 'chef_environment' => 'desert' }
+      it 'knife deps reports just the node' do
+        knife('deps --remote /nodes/mort.json').should_succeed "/environments/desert.json\n/nodes/mort.json\n"
+      end
+    end
+    when_the_chef_server 'has a node with roles and recipes in its run_list' do
+      role 'minor', {}
+      cookbook 'quiche', '1.0.0', { 'metadata.rb' => "name 'quiche'\nversion '1.0.0'\n", 'recipes' => { 'default.rb' => '' } }
+      cookbook 'soup', '1.0.0', { 'metadata.rb' =>   "name 'soup'\nversion '1.0.0'\n", 'recipes' => { 'chicken.rb' => '' } }
+      node 'mort', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
+      it 'knife deps reports just the node' do
+        knife('deps --remote /nodes/mort.json').should_succeed <<EOM
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/nodes/mort.json
+EOM
+      end
+    end
+    when_the_chef_server 'has a cookbook with no dependencies' do
+      cookbook 'quiche', '1.0.0', { 'metadata.rb' => "name 'quiche'\nversion '1.0.0'\n", 'recipes' => { 'default.rb' => '' } }
+      it 'knife deps reports just the cookbook' do
+        knife('deps --remote /cookbooks/quiche').should_succeed "/cookbooks/quiche\n"
+      end
+    end
+    when_the_chef_server 'has a cookbook with dependencies' do
+      cookbook 'kettle', '1.0.0', { 'metadata.rb' => "name 'kettle'\nversion '1.0.0'\n" }
+      cookbook 'quiche', '1.0.0', { 'metadata.rb' => "name 'quiche'\ndepends 'kettle'\n", 'recipes' => { 'default.rb' => '' } }
+      it 'knife deps reports the cookbook and its dependencies' do
+        knife('deps --remote /cookbooks/quiche').should_succeed "/cookbooks/kettle\n/cookbooks/quiche\n"
+      end
+    end
+    when_the_chef_server 'has a data bag' do
+      data_bag 'bag', { 'item' => {} }
+      it 'knife deps reports just the data bag' do
+        knife('deps --remote /data_bags/bag/item.json').should_succeed "/data_bags/bag/item.json\n"
+      end
+    end
+    when_the_chef_server 'has an environment' do
+      environment 'desert', {}
+      it 'knife deps reports just the environment' do
+        knife('deps --remote /environments/desert.json').should_succeed "/environments/desert.json\n"
+      end
+    end
+    when_the_chef_server 'has a deep dependency tree' do
+      role 'starring', { 'run_list' => %w(role[minor] recipe[quiche] recipe[soup::chicken]) }
+      role 'minor', {}
+      cookbook 'quiche', '1.0.0', { 'metadata.rb' => "name 'quiche'\nversion '1.0.0'\n", 'recipes' => { 'default.rb' => '' } }
+      cookbook 'soup', '1.0.0', { 'metadata.rb' =>   "name 'soup'\nversion '1.0.0'\n", 'recipes' => { 'chicken.rb' => '' } }
+      environment 'desert', {}
+      node 'mort', { 'chef_environment' => 'desert', 'run_list' => [ 'role[starring]' ] }
+      node 'bart', { 'run_list' => [ 'role[minor]' ] }
+
+      it 'knife deps reports all dependencies' do
+        knife('deps --remote /nodes/mort.json').should_succeed <<EOM
+/environments/desert.json
+/roles/minor.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+/nodes/mort.json
+EOM
+      end
+      it 'knife deps * reports all dependencies of all things' do
+        knife('deps --remote /nodes/*').should_succeed <<EOM
+/roles/minor.json
+/nodes/bart.json
+/environments/desert.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+/nodes/mort.json
+EOM
+      end
+      it 'knife deps a b reports all dependencies of a and b' do
+        knife('deps --remote /nodes/bart.json /nodes/mort.json').should_succeed <<EOM
+/roles/minor.json
+/nodes/bart.json
+/environments/desert.json
+/cookbooks/quiche
+/cookbooks/soup
+/roles/starring.json
+/nodes/mort.json
+EOM
+      end
+      it 'knife deps --tree /* shows dependencies in a tree' do
+        knife('deps --remote --tree /nodes/*').should_succeed <<EOM
+/nodes/bart.json
+  /roles/minor.json
+/nodes/mort.json
+  /environments/desert.json
+  /roles/starring.json
+    /roles/minor.json
+    /cookbooks/quiche
+    /cookbooks/soup
+EOM
+      end
+      it 'knife deps --tree --no-recurse shows only the first level of dependencies' do
+        knife('deps --remote --tree --no-recurse /nodes/*').should_succeed <<EOM
+/nodes/bart.json
+  /roles/minor.json
+/nodes/mort.json
+  /environments/desert.json
+  /roles/starring.json
+EOM
+      end
+    end
+
+    context 'circular dependencies' do
+      when_the_chef_server 'has cookbooks with circular dependencies' do
+        cookbook 'foo', '1.0.0', { 'metadata.rb'  => "name 'foo'\ndepends 'bar'\n" }
+        cookbook 'bar', '1.0.0', { 'metadata.rb'  => "name 'bar'\ndepends 'baz'\n" }
+        cookbook 'baz', '1.0.0', { 'metadata.rb'  => "name 'baz'\ndepends 'foo'\n" }
+        cookbook 'self', '1.0.0', { 'metadata.rb' => "name 'self'\ndepends 'self'\n" }
+        it 'knife deps prints each once' do
+          knife('deps --remote /cookbooks/foo /cookbooks/self').should_succeed <<EOM
+/cookbooks/baz
+/cookbooks/bar
+/cookbooks/foo
+/cookbooks/self
+EOM
+        end
+        it 'knife deps --tree prints each once' do
+          knife('deps --remote --tree /cookbooks/foo /cookbooks/self').should_succeed <<EOM
+/cookbooks/foo
+  /cookbooks/bar
+    /cookbooks/baz
+      /cookbooks/foo
+/cookbooks/self
+  /cookbooks/self
+EOM
+        end
+      end
+      when_the_chef_server 'has roles with circular dependencies' do
+        role 'foo', { 'run_list' => [ 'role[bar]' ] }
+        role 'bar', { 'run_list' => [ 'role[baz]' ] }
+        role 'baz', { 'run_list' => [ 'role[foo]' ] }
+        role 'self', { 'run_list' => [ 'role[self]' ] }
+        it 'knife deps prints each once' do
+          knife('deps --remote /roles/foo.json /roles/self.json').should_succeed <<EOM
+/roles/baz.json
+/roles/bar.json
+/roles/foo.json
+/roles/self.json
+EOM
+        end
+        it 'knife deps --tree prints each once' do
+          knife('deps --remote --tree /roles/foo.json /roles/self.json') do
+            stdout.should == "/roles/foo.json\n  /roles/bar.json\n    /roles/baz.json\n      /roles/foo.json\n/roles/self.json\n  /roles/self.json\n"
+            stderr.should == "WARNING: No knife configuration file found\n"
+          end
+        end
+      end
+    end
+
+    context 'missing objects' do
+      when_the_chef_server 'is empty' do
+        it 'knife deps /blah reports an error' do
+          knife('deps --remote /blah').should_fail(
+            :exit_code => 2,
+            :stdout => "/blah\n",
+            :stderr => "ERROR: /blah: No such file or directory\n"
+          )
+        end
+        it 'knife deps /roles/x.json reports an error' do
+          knife('deps --remote /roles/x.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/roles/x.json\n",
+            :stderr => "ERROR: /roles/x.json: No such file or directory\n"
+          )
+        end
+        it 'knife deps /nodes/x.json reports an error' do
+          knife('deps --remote /nodes/x.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/nodes/x.json\n",
+            :stderr => "ERROR: /nodes/x.json: No such file or directory\n"
+          )
+        end
+        it 'knife deps /environments/x.json reports an error' do
+          knife('deps --remote /environments/x.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/environments/x.json\n",
+            :stderr => "ERROR: /environments/x.json: No such file or directory\n"
+          )
+        end
+        it 'knife deps /cookbooks/x reports an error' do
+          knife('deps --remote /cookbooks/x').should_fail(
+            :exit_code => 2,
+            :stdout => "/cookbooks/x\n",
+            :stderr => "ERROR: /cookbooks/x: No such file or directory\n"
+          )
+        end
+        it 'knife deps /data_bags/bag/item reports an error' do
+          knife('deps --remote /data_bags/bag/item').should_fail(
+            :exit_code => 2,
+            :stdout => "/data_bags/bag/item\n",
+            :stderr => "ERROR: /data_bags/bag/item: No such file or directory\n"
+          )
+        end
+      end
+      when_the_chef_server 'is missing a dependent cookbook' do
+        role 'starring', { 'run_list' => [ 'recipe[quiche]'] }
+        it 'knife deps reports the cookbook, along with an error' do
+          knife('deps --remote /roles/starring.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/cookbooks/quiche\n/roles/starring.json\n",
+            :stderr => "ERROR: /cookbooks/quiche: No such file or directory\n"
+          )
+        end
+      end
+      when_the_chef_server 'is missing a dependent environment' do
+        node 'mort', { 'chef_environment' => 'desert' }
+        it 'knife deps reports the environment, along with an error' do
+          knife('deps --remote /nodes/mort.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/environments/desert.json\n/nodes/mort.json\n",
+            :stderr => "ERROR: /environments/desert.json: No such file or directory\n"
+          )
+        end
+      end
+      when_the_chef_server 'is missing a dependent role' do
+        role 'starring', { 'run_list' => [ 'role[minor]'] }
+        it 'knife deps reports the role, along with an error' do
+          knife('deps --remote /roles/starring.json').should_fail(
+            :exit_code => 2,
+            :stdout => "/roles/minor.json\n/roles/starring.json\n",
+            :stderr => "ERROR: /roles/minor.json: No such file or directory\n"
+          )
+        end
+      end
+    end
+    context 'invalid objects' do
+      when_the_chef_server 'is empty' do
+        it 'knife deps / reports an error' do
+          knife('deps --remote /').should_succeed("/\n")
+        end
+        it 'knife deps /roles reports an error' do
+          knife('deps --remote /roles').should_succeed("/roles\n")
+        end
+      end
+      when_the_chef_server 'has a data bag' do
+        data_bag 'bag', { 'item' => {} }
+        it 'knife deps /data_bags/bag shows no dependencies' do
+          knife('deps --remote /data_bags/bag').should_succeed("/data_bags/bag\n")
+        end
+      end
+      when_the_chef_server 'has a cookbook' do
+        cookbook 'blah', '1.0.0', { 'metadata.rb' => 'name "blah"' }
+        it 'knife deps on a cookbook file shows no dependencies' do
+          knife('deps --remote /cookbooks/blah/metadata.rb').should_succeed(
+            "/cookbooks/blah/metadata.rb\n"
+          )
+        end
+      end
+    end
+  end
+
+  it 'knife deps --no-recurse reports an error' do
+    knife('deps --no-recurse /').should_fail("ERROR: --no-recurse requires --tree\n")
+  end
+end
diff --git a/spec/integration/knife/diff_spec.rb b/spec/integration/knife/diff_spec.rb
new file mode 100644
index 0000000..2e36f39
--- /dev/null
+++ b/spec/integration/knife/diff_spec.rb
@@ -0,0 +1,536 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/diff'
+
+describe 'knife diff' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  context 'without versioned cookbooks' do
+    when_the_chef_server "has one of each thing" do
+      client 'x', '{}'
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+      data_bag 'x', { 'y' => '{}' }
+      environment 'x', '{}'
+      node 'x', '{}'
+      role 'x', '{}'
+      user 'x', '{}'
+
+      when_the_repository 'has only top-level directories' do
+        directory 'clients'
+        directory 'cookbooks'
+        directory 'data_bags'
+        directory 'environments'
+        directory 'nodes'
+        directory 'roles'
+        directory 'users'
+
+        it 'knife diff reports everything as deleted' do
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients/chef-validator.json
+D\t/clients/chef-webui.json
+D\t/clients/x.json
+D\t/cookbooks/x
+D\t/data_bags/x
+D\t/environments/_default.json
+D\t/environments/x.json
+D\t/nodes/x.json
+D\t/roles/x.json
+D\t/users/admin.json
+D\t/users/x.json
+EOM
+      end
+    end
+
+      when_the_repository 'has an identical copy of each thing' do
+
+        file 'clients/chef-validator.json', { 'validator' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/chef-webui.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+        file 'data_bags/x/y.json', {}
+        file 'environments/_default.json', { "description" => "The default Chef environment" }
+        file 'environments/x.json', {}
+        file 'nodes/x.json', {}
+        file 'roles/x.json', {}
+        file 'users/admin.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'users/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+        it 'knife diff reports no differences' do
+          knife('diff /').should_succeed ''
+        end
+
+        it 'knife diff /environments/nonexistent.json reports an error' do
+          knife('diff /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
+        end
+
+        it 'knife diff /environments/*.txt reports an error' do
+          knife('diff /environments/*.txt').should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
+        end
+
+        context 'except the role file' do
+          file 'roles/x.json', <<EOM
+{
+  "foo": "bar"
+}
+EOM
+          it 'knife diff reports the role as different' do
+            knife('diff --name-status /').should_succeed <<EOM
+M\t/roles/x.json
+EOM
+          end
+        end
+
+        context 'as well as one extra copy of each thing' do
+          file 'clients/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+          file 'cookbooks/x/blah.rb', ''
+          file 'cookbooks/y/metadata.rb', 'version "1.0.0"'
+          file 'data_bags/x/z.json', {}
+          file 'data_bags/y/zz.json', {}
+          file 'environments/y.json', {}
+          file 'nodes/y.json', {}
+          file 'roles/y.json', {}
+          file 'users/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+          it 'knife diff reports the new files as added' do
+            knife('diff --name-status /').should_succeed <<EOM
+A\t/clients/y.json
+A\t/cookbooks/x/blah.rb
+A\t/cookbooks/y
+A\t/data_bags/x/z.json
+A\t/data_bags/y
+A\t/environments/y.json
+A\t/nodes/y.json
+A\t/roles/y.json
+A\t/users/y.json
+EOM
+          end
+
+          context 'when cwd is the data_bags directory' do
+            cwd 'data_bags'
+            it 'knife diff reports different data bags' do
+              knife('diff --name-status').should_succeed <<EOM
+A\tx/z.json
+A\ty
+EOM
+            end
+            it 'knife diff * reports different data bags' do
+              knife('diff --name-status *').should_succeed <<EOM
+A\tx/z.json
+A\ty
+EOM
+            end
+          end
+        end
+      end
+
+      when_the_repository 'is empty' do
+        it 'knife diff reports everything as deleted' do
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients
+D\t/cookbooks
+D\t/data_bags
+D\t/environments
+D\t/nodes
+D\t/roles
+D\t/users
+EOM
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x/onlyin1.0.0.rb', ''
+
+      when_the_chef_server 'has a later version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => ''}
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => '' }
+
+        it 'knife diff /cookbooks/x shows differences' do
+          knife('diff --name-status /cookbooks/x').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/onlyin1.0.1.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+        end
+
+        it 'knife diff --diff-filter=MAT does not show deleted files' do
+          knife('diff --diff-filter=MAT --name-status /cookbooks/x').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => '' }
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => '' }
+        it 'knife diff /cookbooks/x shows no differences' do
+          knife('diff --name-status /cookbooks/x').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has a later version for the cookbook, and no current version' do
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => '' }
+
+        it 'knife diff /cookbooks/x shows the differences' do
+          knife('diff --name-status /cookbooks/x').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/onlyin1.0.1.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook, and no current version' do
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => '' }
+
+        it 'knife diff /cookbooks/x shows the differences' do
+          knife('diff --name-status /cookbooks/x').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/onlyin0.9.9.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+        end
+      end
+    end
+
+    context 'json diff tests' do
+      when_the_repository 'has an empty environment file' do
+        file 'environments/x.json', {}
+        when_the_chef_server 'has an empty environment' do
+          environment 'x', {}
+          it 'knife diff returns no differences' do
+            knife('diff /environments/x.json').should_succeed ''
+          end
+        end
+        when_the_chef_server 'has an environment with a different value' do
+          environment 'x', { 'description' => 'hi' }
+          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
+            knife('diff /environments/x.json').should_succeed(/
+ {
+-  "name": "x",
+-  "description": "hi"
+\+  "name": "x"
+ }
+/)
+          end
+        end
+      end
+
+      when_the_repository 'has an environment file with a value in it' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        when_the_chef_server 'has an environment with the same value' do
+          environment 'x', { 'description' => 'hi' }
+          it 'knife diff returns no differences' do
+            knife('diff /environments/x.json').should_succeed ''
+          end
+        end
+        when_the_chef_server 'has an environment with no value' do
+          environment 'x', {}
+          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
+            knife('diff /environments/x.json').should_succeed(/
+ {
+-  "name": "x"
+\+  "name": "x",
+\+  "description": "hi"
+ }
+/)
+          end
+        end
+        when_the_chef_server 'has an environment with a different value' do
+          environment 'x', { 'description' => 'lo' }
+          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
+            knife('diff /environments/x.json').should_succeed(/
+ {
+   "name": "x",
+-  "description": "lo"
+\+  "description": "hi"
+ }
+/)
+          end
+        end
+      end
+    end
+
+    when_the_chef_server 'has an environment' do
+      environment 'x', {}
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife diff reports an error and does a textual diff' do
+          knife('diff /environments/x.json').should_succeed(/-  "name": "x"/, :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n")
+        end
+      end
+    end
+  end # without versioned cookbooks
+
+  with_versioned_cookbooks do
+    when_the_chef_server "has one of each thing" do
+      client 'x', '{}'
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+      data_bag 'x', { 'y' => '{}' }
+      environment 'x', '{}'
+      node 'x', '{}'
+      role 'x', '{}'
+      user 'x', '{}'
+
+      when_the_repository 'has only top-level directories' do
+        directory 'clients'
+        directory 'cookbooks'
+        directory 'data_bags'
+        directory 'environments'
+        directory 'nodes'
+        directory 'roles'
+        directory 'users'
+
+        it 'knife diff reports everything as deleted' do
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients/chef-validator.json
+D\t/clients/chef-webui.json
+D\t/clients/x.json
+D\t/cookbooks/x-1.0.0
+D\t/data_bags/x
+D\t/environments/_default.json
+D\t/environments/x.json
+D\t/nodes/x.json
+D\t/roles/x.json
+D\t/users/admin.json
+D\t/users/x.json
+EOM
+      end
+    end
+
+      when_the_repository 'has an identical copy of each thing' do
+        file 'clients/chef-validator.json', { 'validator' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/chef-webui.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+        file 'data_bags/x/y.json', {}
+        file 'environments/_default.json', { "description" => "The default Chef environment" }
+        file 'environments/x.json', {}
+        file 'nodes/x.json', {}
+        file 'roles/x.json', {}
+        file 'users/admin.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'users/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+        it 'knife diff reports no differences' do
+          knife('diff /').should_succeed ''
+        end
+
+        it 'knife diff /environments/nonexistent.json reports an error' do
+          knife('diff /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory on remote or local\n"
+        end
+
+        it 'knife diff /environments/*.txt reports an error' do
+          knife('diff /environments/*.txt').should_fail "ERROR: /environments/*.txt: No such file or directory on remote or local\n"
+        end
+
+        context 'except the role file' do
+          file 'roles/x.json', <<EOM
+{
+  "foo": "bar"
+}
+EOM
+          it 'knife diff reports the role as different' do
+            knife('diff --name-status /').should_succeed <<EOM
+M\t/roles/x.json
+EOM
+          end
+        end
+
+        context 'as well as one extra copy of each thing' do
+          file 'clients/y.json', {}
+          file 'cookbooks/x-1.0.0/blah.rb', ''
+          file 'cookbooks/x-2.0.0/metadata.rb', 'version "2.0.0"'
+          file 'cookbooks/y-1.0.0/metadata.rb', 'version "1.0.0"'
+          file 'data_bags/x/z.json', {}
+          file 'data_bags/y/zz.json', {}
+          file 'environments/y.json', {}
+          file 'nodes/y.json', {}
+          file 'roles/y.json', {}
+          file 'users/y.json', {}
+
+          it 'knife diff reports the new files as added' do
+            knife('diff --name-status /').should_succeed <<EOM
+A\t/clients/y.json
+A\t/cookbooks/x-1.0.0/blah.rb
+A\t/cookbooks/x-2.0.0
+A\t/cookbooks/y-1.0.0
+A\t/data_bags/x/z.json
+A\t/data_bags/y
+A\t/environments/y.json
+A\t/nodes/y.json
+A\t/roles/y.json
+A\t/users/y.json
+EOM
+          end
+
+          context 'when cwd is the data_bags directory' do
+            cwd 'data_bags'
+            it 'knife diff reports different data bags' do
+              knife('diff --name-status').should_succeed <<EOM
+A\tx/z.json
+A\ty
+EOM
+            end
+            it 'knife diff * reports different data bags' do
+              knife('diff --name-status *').should_succeed <<EOM
+A\tx/z.json
+A\ty
+EOM
+            end
+          end
+        end
+      end
+
+      when_the_repository 'is empty' do
+        it 'knife diff reports everything as deleted' do
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients
+D\t/cookbooks
+D\t/data_bags
+D\t/environments
+D\t/nodes
+D\t/roles
+D\t/users
+EOM
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x-1.0.0/onlyin1.0.0.rb', ''
+
+      when_the_chef_server 'has a later version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => ''}
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => '' }
+
+        it 'knife diff /cookbooks shows differences' do
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+D\t/cookbooks/x-1.0.1
+EOM
+        end
+
+        it 'knife diff --diff-filter=MAT does not show deleted files' do
+          knife('diff --diff-filter=MAT --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => '' }
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => '' }
+        it 'knife diff /cookbooks shows the differences' do
+          knife('diff --name-status /cookbooks').should_succeed "D\t/cookbooks/x-0.9.9\n"
+        end
+      end
+
+      when_the_chef_server 'has a later version for the cookbook, and no current version' do
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => '' }
+
+        it 'knife diff /cookbooks shows the differences' do
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+D\t/cookbooks/x-1.0.1
+A\t/cookbooks/x-1.0.0
+EOM
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook, and no current version' do
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => '' }
+
+        it 'knife diff /cookbooks shows the differences' do
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+D\t/cookbooks/x-0.9.9
+A\t/cookbooks/x-1.0.0
+EOM
+        end
+      end
+    end
+
+    context 'json diff tests' do
+      when_the_repository 'has an empty environment file' do
+        file 'environments/x.json', {}
+        when_the_chef_server 'has an empty environment' do
+          environment 'x', {}
+          it 'knife diff returns no differences' do
+            knife('diff /environments/x.json').should_succeed ''
+          end
+        end
+        when_the_chef_server 'has an environment with a different value' do
+          environment 'x', { 'description' => 'hi' }
+          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
+            knife('diff /environments/x.json').should_succeed(/
+ {
+-  "name": "x",
+-  "description": "hi"
+\+  "name": "x"
+ }
+/)
+          end
+        end
+      end
+
+      when_the_repository 'has an environment file with a value in it' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        when_the_chef_server 'has an environment with the same value' do
+          environment 'x', { 'description' => 'hi' }
+          it 'knife diff returns no differences' do
+            knife('diff /environments/x.json').should_succeed ''
+          end
+        end
+        when_the_chef_server 'has an environment with no value' do
+          environment 'x', {}
+          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
+            knife('diff /environments/x.json').should_succeed(/
+ {
+-  "name": "x"
+\+  "name": "x",
+\+  "description": "hi"
+ }
+/)
+          end
+        end
+        when_the_chef_server 'has an environment with a different value' do
+          environment 'x', { 'description' => 'lo' }
+          it 'knife diff reports the difference', :pending => (RUBY_VERSION < "1.9") do
+            knife('diff /environments/x.json').should_succeed(/
+ {
+   "name": "x",
+-  "description": "lo"
+\+  "description": "hi"
+ }
+/)
+          end
+        end
+      end
+    end
+
+    when_the_chef_server 'has an environment' do
+      environment 'x', {}
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife diff reports an error and does a textual diff' do
+          knife('diff /environments/x.json').should_succeed(/-  "name": "x"/, :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n")
+        end
+      end
+    end
+  end # without versioned cookbooks
+end
diff --git a/spec/integration/knife/download_spec.rb b/spec/integration/knife/download_spec.rb
new file mode 100644
index 0000000..ce8df38
--- /dev/null
+++ b/spec/integration/knife/download_spec.rb
@@ -0,0 +1,998 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/download'
+require 'chef/knife/diff'
+
+describe 'knife download' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  context 'without versioned cookbooks' do
+    when_the_chef_server "has one of each thing" do
+      client 'x', {}
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+      data_bag 'x', { 'y' => {} }
+      environment 'x', {}
+      node 'x', {}
+      role 'x', {}
+      user 'x', {}
+
+      when_the_repository 'has only top-level directories' do
+        directory 'clients'
+        directory 'cookbooks'
+        directory 'data_bags'
+        directory 'environments'
+        directory 'nodes'
+        directory 'roles'
+        directory 'users'
+
+        it 'knife download downloads everything' do
+          knife('download /').should_succeed <<EOM
+Created /clients/chef-validator.json
+Created /clients/chef-webui.json
+Created /clients/x.json
+Created /cookbooks/x
+Created /cookbooks/x/metadata.rb
+Created /data_bags/x
+Created /data_bags/x/y.json
+Created /environments/_default.json
+Created /environments/x.json
+Created /nodes/x.json
+Created /roles/x.json
+Created /users/admin.json
+Created /users/x.json
+EOM
+          knife('diff --name-status /').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has an identical copy of each thing' do
+        file 'clients/chef-validator.json', { 'validator' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/chef-webui.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+        file 'data_bags/x/y.json', {}
+        file 'environments/_default.json', { "description" => "The default Chef environment" }
+        file 'environments/x.json', {}
+        file 'nodes/x.json', {}
+        file 'roles/x.json', {}
+        file 'users/admin.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'users/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+        it 'knife download makes no changes' do
+          knife('download /').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        it 'knife download --purge makes no changes' do
+          knife('download --purge /').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        context 'except the role file' do
+          file 'roles/x.json', <<EOM
+{
+  "chef_type": "role",
+  "default_attributes": {
+  },
+  "description": "blarghle",
+  "env_run_lists": {
+  },
+  "json_class": "Chef::Role",
+  "name": "x",
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ]
+}
+EOM
+          it 'knife download changes the role' do
+            knife('download /').should_succeed "Updated /roles/x.json\n"
+            knife('diff --name-status /').should_succeed ''
+          end
+
+          it 'knife download --no-diff does not change the role' do
+            knife('download --no-diff /').should_succeed ''
+            knife('diff --name-status /').should_succeed "M\t/roles/x.json\n"
+          end
+        end
+
+        context 'except the role file is textually different, but not ACTUALLY different' do
+          file 'roles/x.json', <<EOM
+{
+  "chef_type": "role",
+  "default_attributes": {
+  },
+  "env_run_lists": {
+  },
+  "json_class": "Chef::Role",
+  "name": "x",
+  "description": "",
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ]
+}
+EOM
+          it 'knife download / does not change anything' do
+            knife('download /').should_succeed ''
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+
+        context 'as well as one extra copy of each thing' do
+          file 'clients/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+          file 'cookbooks/x/blah.rb', ''
+          file 'cookbooks/y/metadata.rb', 'version "1.0.0"'
+          file 'data_bags/x/z.json', {}
+          file 'data_bags/y/zz.json', {}
+          file 'environments/y.json', {}
+          file 'nodes/y.json', {}
+          file 'roles/y.json', {}
+          file 'users/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+          it 'knife download does nothing' do
+            knife('download /').should_succeed ''
+            knife('diff --name-status /').should_succeed <<EOM
+A\t/clients/y.json
+A\t/cookbooks/x/blah.rb
+A\t/cookbooks/y
+A\t/data_bags/x/z.json
+A\t/data_bags/y
+A\t/environments/y.json
+A\t/nodes/y.json
+A\t/roles/y.json
+A\t/users/y.json
+EOM
+          end
+
+          it 'knife download --purge deletes the extra files' do
+            knife('download --purge /').should_succeed <<EOM
+Deleted extra entry /clients/y.json (purge is on)
+Deleted extra entry /cookbooks/x/blah.rb (purge is on)
+Deleted extra entry /cookbooks/y (purge is on)
+Deleted extra entry /data_bags/x/z.json (purge is on)
+Deleted extra entry /data_bags/y (purge is on)
+Deleted extra entry /environments/y.json (purge is on)
+Deleted extra entry /nodes/y.json (purge is on)
+Deleted extra entry /roles/y.json (purge is on)
+Deleted extra entry /users/y.json (purge is on)
+EOM
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+      end
+
+      when_the_repository 'is empty' do
+        it 'knife download creates the extra files' do
+          knife('download /').should_succeed <<EOM
+Created /clients
+Created /clients/chef-validator.json
+Created /clients/chef-webui.json
+Created /clients/x.json
+Created /cookbooks
+Created /cookbooks/x
+Created /cookbooks/x/metadata.rb
+Created /data_bags
+Created /data_bags/x
+Created /data_bags/x/y.json
+Created /environments
+Created /environments/_default.json
+Created /environments/x.json
+Created /nodes
+Created /nodes/x.json
+Created /roles
+Created /roles/x.json
+Created /users
+Created /users/admin.json
+Created /users/x.json
+EOM
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        it 'knife download --no-diff creates the extra files' do
+          knife('download --no-diff /').should_succeed <<EOM
+Created /clients
+Created /clients/chef-validator.json
+Created /clients/chef-webui.json
+Created /clients/x.json
+Created /cookbooks
+Created /cookbooks/x
+Created /cookbooks/x/metadata.rb
+Created /data_bags
+Created /data_bags/x
+Created /data_bags/x/y.json
+Created /environments
+Created /environments/_default.json
+Created /environments/x.json
+Created /nodes
+Created /nodes/x.json
+Created /roles
+Created /roles/x.json
+Created /users
+Created /users/admin.json
+Created /users/x.json
+EOM
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        context 'when current directory is top level' do
+          cwd '.'
+          it 'knife download with no parameters reports an error' do
+            knife('download').should_fail "FATAL: Must specify at least one argument.  If you want to download everything in this directory, type \"knife download .\"\n", :stdout => /USAGE/
+          end
+        end
+      end
+    end
+
+    # Test download of an item when the other end doesn't even have the container
+    when_the_repository 'is empty' do
+      when_the_chef_server 'has two data bag items' do
+        data_bag 'x', { 'y' => {}, 'z' => {} }
+
+        it 'knife download of one data bag item itself succeeds' do
+          knife('download /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags
+Created /data_bags/x
+Created /data_bags/x/y.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/z.json
+EOM
+        end
+
+        it 'knife download /data_bags/x /data_bags/x/y.json downloads x once' do
+          knife('download /data_bags/x /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags
+Created /data_bags/x
+Created /data_bags/x/y.json
+Created /data_bags/x/z.json
+EOM
+        end
+      end
+    end
+
+    when_the_repository 'has three data bag items' do
+      file 'data_bags/x/deleted.json', <<EOM
+{
+  "id": "deleted"
+}
+EOM
+      file 'data_bags/x/modified.json', <<EOM
+{
+  "id": "modified"
+}
+EOM
+      file 'data_bags/x/unmodified.json', <<EOM
+{
+  "id": "unmodified"
+}
+EOM
+
+      when_the_chef_server 'has a modified, unmodified, added and deleted data bag item' do
+        data_bag 'x', {
+          'added' => {},
+          'modified' => { 'foo' => 'bar' },
+          'unmodified' => {}
+        }
+
+        it 'knife download of the modified file succeeds' do
+          knife('download /data_bags/x/modified.json').should_succeed <<EOM
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download of the unmodified file does nothing' do
+          knife('download /data_bags/x/unmodified.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download of the added file succeeds' do
+          knife('download /data_bags/x/added.json').should_succeed <<EOM
+Created /data_bags/x/added.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download of the deleted file does nothing' do
+          knife('download /data_bags/x/deleted.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download --purge of the deleted file deletes it' do
+          knife('download --purge /data_bags/x/deleted.json').should_succeed <<EOM
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+M\t/data_bags/x/modified.json
+EOM
+        end
+        it 'knife download of the entire data bag downloads everything' do
+          knife('download /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download --purge of the entire data bag downloads everything' do
+          knife('download --purge /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed ''
+        end
+        context 'when cwd is the /data_bags directory' do
+          cwd 'data_bags'
+          it 'knife download fails' do
+            knife('download').should_fail "FATAL: Must specify at least one argument.  If you want to download everything in this directory, type \"knife download .\"\n", :stdout => /USAGE/
+          end
+          it 'knife download --purge . downloads everything' do
+            knife('download --purge .').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+          it 'knife download --purge * downloads everything' do
+            knife('download --purge *').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x/z.rb', ''
+
+      when_the_chef_server 'has a modified, added and deleted file for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version  "1.0.0"', 'y.rb' => 'hi' }
+
+        it 'knife download of a modified file succeeds' do
+          knife('download /cookbooks/x/metadata.rb').should_succeed "Updated /cookbooks/x/metadata.rb\n"
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+D\t/cookbooks/x/y.rb
+A\t/cookbooks/x/z.rb
+EOM
+        end
+        it 'knife download of a deleted file does nothing' do
+          knife('download /cookbooks/x/z.rb').should_succeed ''
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/y.rb
+A\t/cookbooks/x/z.rb
+EOM
+        end
+        it 'knife download --purge of a deleted file succeeds' do
+          knife('download --purge /cookbooks/x/z.rb').should_succeed "Deleted extra entry /cookbooks/x/z.rb (purge is on)\n"
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/y.rb
+EOM
+        end
+        it 'knife download of an added file succeeds' do
+          knife('download /cookbooks/x/y.rb').should_succeed "Created /cookbooks/x/y.rb\n"
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+A\t/cookbooks/x/z.rb
+EOM
+        end
+        it 'knife download of the cookbook itself succeeds' do
+          knife('download /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x/metadata.rb
+Created /cookbooks/x/y.rb
+EOM
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+A\t/cookbooks/x/z.rb
+EOM
+        end
+        it 'knife download --purge of the cookbook itself succeeds' do
+          knife('download --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x/metadata.rb
+Created /cookbooks/x/y.rb
+Deleted extra entry /cookbooks/x/z.rb (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x/onlyin1.0.0.rb', 'old_text'
+
+      when_the_chef_server 'has a later version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => '' }
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife download /cookbooks/x downloads the latest version' do
+          knife('download --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x/metadata.rb
+Created /cookbooks/x/onlyin1.0.1.rb
+Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => ''}
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+        it 'knife download /cookbooks/x downloads the updated file' do
+          knife('download --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x/onlyin1.0.0.rb
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has a later version for the cookbook, and no current version' do
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife download /cookbooks/x downloads the latest version' do
+          knife('download --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x/metadata.rb
+Created /cookbooks/x/onlyin1.0.1.rb
+Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook, and no current version' do
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+
+        it 'knife download /cookbooks/x downloads the old version' do
+          knife('download --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x/metadata.rb
+Created /cookbooks/x/onlyin0.9.9.rb
+Deleted extra entry /cookbooks/x/onlyin1.0.0.rb (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
+
+    when_the_chef_server 'has an environment' do
+      environment 'x', {}
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife download succeeds' do
+          knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has the same environment with the wrong name in the file' do
+        file 'environments/x.json', { 'name' => 'y' }
+        it 'knife download succeeds' do
+          knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has the same environment with no name in the file' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        it 'knife download succeeds' do
+          knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+    end
+  end # without versioned cookbooks
+
+  with_versioned_cookbooks do
+    when_the_chef_server "has one of each thing" do
+      client 'x', {}
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+      data_bag 'x', { 'y' => {} }
+      environment 'x', {}
+      node 'x', {}
+      role 'x', {}
+      user 'x', {}
+
+      when_the_repository 'has only top-level directories' do
+        directory 'clients'
+        directory 'cookbooks'
+        directory 'data_bags'
+        directory 'environments'
+        directory 'nodes'
+        directory 'roles'
+        directory 'users'
+
+        it 'knife download downloads everything' do
+          knife('download /').should_succeed <<EOM
+Created /clients/chef-validator.json
+Created /clients/chef-webui.json
+Created /clients/x.json
+Created /cookbooks/x-1.0.0
+Created /cookbooks/x-1.0.0/metadata.rb
+Created /data_bags/x
+Created /data_bags/x/y.json
+Created /environments/_default.json
+Created /environments/x.json
+Created /nodes/x.json
+Created /roles/x.json
+Created /users/admin.json
+Created /users/x.json
+EOM
+          knife('diff --name-status /').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has an identical copy of each thing' do
+        file 'clients/chef-validator.json', { 'validator' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/chef-webui.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+        file 'data_bags/x/y.json', {}
+        file 'environments/_default.json', { "description" => "The default Chef environment" }
+        file 'environments/x.json', {}
+        file 'nodes/x.json', {}
+        file 'roles/x.json', {}
+        file 'users/admin.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'users/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+        it 'knife download makes no changes' do
+          knife('download /').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        it 'knife download --purge makes no changes' do
+          knife('download --purge /').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        context 'except the role file' do
+          file 'roles/x.json', { "description" => "blarghle" }
+
+          it 'knife download changes the role' do
+            knife('download /').should_succeed "Updated /roles/x.json\n"
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+
+        context 'except the role file is textually different, but not ACTUALLY different' do
+          file 'roles/x.json', <<EOM
+{
+  "chef_type": "role" ,
+  "default_attributes": {
+  },
+  "env_run_lists": {
+  },
+  "json_class": "Chef::Role",
+  "name": "x",
+  "description": "",
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ]
+}
+EOM
+          it 'knife download / does not change anything' do
+            knife('download /').should_succeed ''
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+
+        context 'as well as one extra copy of each thing' do
+          file 'clients/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+          file 'cookbooks/x-1.0.0/blah.rb', ''
+          file 'cookbooks/x-2.0.0/metadata.rb', 'version "2.0.0"'
+          file 'cookbooks/y-1.0.0/metadata.rb', 'version "1.0.0"'
+          file 'data_bags/x/z.json', {}
+          file 'data_bags/y/zz.json', {}
+          file 'environments/y.json', {}
+          file 'nodes/y.json', {}
+          file 'roles/y.json', {}
+          file 'users/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+          it 'knife download does nothing' do
+            knife('download /').should_succeed ''
+            knife('diff --name-status /').should_succeed <<EOM
+A\t/clients/y.json
+A\t/cookbooks/x-1.0.0/blah.rb
+A\t/cookbooks/x-2.0.0
+A\t/cookbooks/y-1.0.0
+A\t/data_bags/x/z.json
+A\t/data_bags/y
+A\t/environments/y.json
+A\t/nodes/y.json
+A\t/roles/y.json
+A\t/users/y.json
+EOM
+          end
+
+          it 'knife download --purge deletes the extra files' do
+            knife('download --purge /').should_succeed <<EOM
+Deleted extra entry /clients/y.json (purge is on)
+Deleted extra entry /cookbooks/x-1.0.0/blah.rb (purge is on)
+Deleted extra entry /cookbooks/x-2.0.0 (purge is on)
+Deleted extra entry /cookbooks/y-1.0.0 (purge is on)
+Deleted extra entry /data_bags/x/z.json (purge is on)
+Deleted extra entry /data_bags/y (purge is on)
+Deleted extra entry /environments/y.json (purge is on)
+Deleted extra entry /nodes/y.json (purge is on)
+Deleted extra entry /roles/y.json (purge is on)
+Deleted extra entry /users/y.json (purge is on)
+EOM
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+      end
+
+      when_the_repository 'is empty' do
+        it 'knife download creates the extra files' do
+          knife('download /').should_succeed <<EOM
+Created /clients
+Created /clients/chef-validator.json
+Created /clients/chef-webui.json
+Created /clients/x.json
+Created /cookbooks
+Created /cookbooks/x-1.0.0
+Created /cookbooks/x-1.0.0/metadata.rb
+Created /data_bags
+Created /data_bags/x
+Created /data_bags/x/y.json
+Created /environments
+Created /environments/_default.json
+Created /environments/x.json
+Created /nodes
+Created /nodes/x.json
+Created /roles
+Created /roles/x.json
+Created /users
+Created /users/admin.json
+Created /users/x.json
+EOM
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        context 'when current directory is top level' do
+          cwd '.'
+          it 'knife download with no parameters reports an error' do
+            knife('download').should_fail "FATAL: Must specify at least one argument.  If you want to download everything in this directory, type \"knife download .\"\n", :stdout => /USAGE/
+          end
+        end
+      end
+    end
+
+    # Test download of an item when the other end doesn't even have the container
+    when_the_repository 'is empty' do
+      when_the_chef_server 'has two data bag items' do
+        data_bag 'x', { 'y' => {}, 'z' => {} }
+
+        it 'knife download of one data bag item itself succeeds' do
+          knife('download /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags
+Created /data_bags/x
+Created /data_bags/x/y.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/z.json
+EOM
+        end
+      end
+    end
+
+    when_the_repository 'has three data bag items' do
+      file 'data_bags/x/deleted.json', <<EOM
+{
+  "id": "deleted"
+}
+EOM
+      file 'data_bags/x/modified.json', <<EOM
+{
+  "id": "modified"
+}
+EOM
+      file 'data_bags/x/unmodified.json', <<EOM
+{
+  "id": "unmodified"
+}
+EOM
+
+      when_the_chef_server 'has a modified, unmodified, added and deleted data bag item' do
+        data_bag 'x', {
+          'added' => {},
+          'modified' => { 'foo' => 'bar' },
+          'unmodified' => {}
+        }
+
+        it 'knife download of the modified file succeeds' do
+          knife('download /data_bags/x/modified.json').should_succeed <<EOM
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download of the unmodified file does nothing' do
+          knife('download /data_bags/x/unmodified.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download of the added file succeeds' do
+          knife('download /data_bags/x/added.json').should_succeed <<EOM
+Created /data_bags/x/added.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download of the deleted file does nothing' do
+          knife('download /data_bags/x/deleted.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download --purge of the deleted file deletes it' do
+          knife('download --purge /data_bags/x/deleted.json').should_succeed <<EOM
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/added.json
+M\t/data_bags/x/modified.json
+EOM
+        end
+        it 'knife download of the entire data bag downloads everything' do
+          knife('download /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+A\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife download --purge of the entire data bag downloads everything' do
+          knife('download --purge /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed ''
+        end
+        context 'when cwd is the /data_bags directory' do
+          cwd 'data_bags'
+          it 'knife download fails' do
+            knife('download').should_fail "FATAL: Must specify at least one argument.  If you want to download everything in this directory, type \"knife download .\"\n", :stdout => /USAGE/
+          end
+          it 'knife download --purge . downloads everything' do
+            knife('download --purge .').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+          it 'knife download --purge * downloads everything' do
+            knife('download --purge *').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x-1.0.0/z.rb', ''
+
+      when_the_chef_server 'has a modified, added and deleted file for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version  "1.0.0"', 'y.rb' => 'hi' }
+
+        it 'knife download of a modified file succeeds' do
+          knife('download /cookbooks/x-1.0.0/metadata.rb').should_succeed "Updated /cookbooks/x-1.0.0/metadata.rb\n"
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+D\t/cookbooks/x-1.0.0/y.rb
+A\t/cookbooks/x-1.0.0/z.rb
+EOM
+        end
+        it 'knife download of a deleted file does nothing' do
+          knife('download /cookbooks/x-1.0.0/z.rb').should_succeed ''
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x-1.0.0/metadata.rb
+D\t/cookbooks/x-1.0.0/y.rb
+A\t/cookbooks/x-1.0.0/z.rb
+EOM
+        end
+        it 'knife download --purge of a deleted file succeeds' do
+          knife('download --purge /cookbooks/x-1.0.0/z.rb').should_succeed "Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)\n"
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x-1.0.0/metadata.rb
+D\t/cookbooks/x-1.0.0/y.rb
+EOM
+        end
+        it 'knife download of an added file succeeds' do
+          knife('download /cookbooks/x-1.0.0/y.rb').should_succeed "Created /cookbooks/x-1.0.0/y.rb\n"
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x-1.0.0/metadata.rb
+A\t/cookbooks/x-1.0.0/z.rb
+EOM
+        end
+        it 'knife download of the cookbook itself succeeds' do
+          knife('download /cookbooks/x-1.0.0').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0/metadata.rb
+Created /cookbooks/x-1.0.0/y.rb
+EOM
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+A\t/cookbooks/x-1.0.0/z.rb
+EOM
+        end
+        it 'knife download --purge of the cookbook itself succeeds' do
+          knife('download --purge /cookbooks/x-1.0.0').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0/metadata.rb
+Created /cookbooks/x-1.0.0/y.rb
+Deleted extra entry /cookbooks/x-1.0.0/z.rb (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x-1.0.0/onlyin1.0.0.rb', 'old_text'
+
+      when_the_chef_server 'has a later version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => '' }
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife download /cookbooks/x downloads the latest version' do
+          knife('download --purge /cookbooks').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
+Created /cookbooks/x-1.0.1
+Created /cookbooks/x-1.0.1/metadata.rb
+Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => ''}
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+        it 'knife download /cookbooks downloads the updated file' do
+          knife('download --purge /cookbooks').should_succeed <<EOM
+Created /cookbooks/x-0.9.9
+Created /cookbooks/x-0.9.9/metadata.rb
+Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
+Updated /cookbooks/x-1.0.0/onlyin1.0.0.rb
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has a later version for the cookbook, and no current version' do
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife download /cookbooks/x downloads the latest version' do
+          knife('download --purge /cookbooks').should_succeed <<EOM
+Created /cookbooks/x-1.0.1
+Created /cookbooks/x-1.0.1/metadata.rb
+Created /cookbooks/x-1.0.1/onlyin1.0.1.rb
+Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook, and no current version' do
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+
+        it 'knife download --purge /cookbooks downloads the old version and deletes the new version' do
+          knife('download --purge /cookbooks').should_succeed <<EOM
+Created /cookbooks/x-0.9.9
+Created /cookbooks/x-0.9.9/metadata.rb
+Created /cookbooks/x-0.9.9/onlyin0.9.9.rb
+Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
+
+    when_the_chef_server 'has an environment' do
+      environment 'x', {}
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife download succeeds' do
+          knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has the same environment with the wrong name in the file' do
+        file 'environments/x.json', { 'name' => 'y' }
+        it 'knife download succeeds' do
+          knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has the same environment with no name in the file' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        it 'knife download succeeds' do
+          knife('download /environments/x.json').should_succeed "Updated /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+    end
+  end # with versioned cookbooks
+
+  when_the_chef_server 'has a cookbook' do
+    cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+
+    when_the_repository 'is empty' do
+      it 'knife download /cookbooks/x signs all requests' do
+
+        # Check that BasicClient.request() always gets called with X-OPS-USERID
+        original_new = Chef::HTTP::BasicClient.method(:new)
+        Chef::HTTP::BasicClient.should_receive(:new) do |args|
+          new_result = original_new.call(*args)
+          original_request = new_result.method(:request)
+          new_result.should_receive(:request) do |method, url, body, headers, &response_handler|
+            headers['X-OPS-USERID'].should_not be_nil
+            original_request.call(method, url, body, headers, &response_handler)
+          end.at_least(:once)
+          new_result
+        end.at_least(:once)
+
+        knife('download /cookbooks/x').should_succeed <<EOM
+Created /cookbooks
+Created /cookbooks/x
+Created /cookbooks/x/metadata.rb
+EOM
+      end
+    end
+  end
+end
diff --git a/spec/integration/knife/list_spec.rb b/spec/integration/knife/list_spec.rb
new file mode 100644
index 0000000..b9d75ce
--- /dev/null
+++ b/spec/integration/knife/list_spec.rb
@@ -0,0 +1,633 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/list'
+
+describe 'knife list' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  when_the_chef_server "is empty" do
+    it "knife list / returns all top level directories" do
+      knife('list /').should_succeed <<EOM
+/clients
+/cookbooks
+/data_bags
+/environments
+/nodes
+/roles
+/users
+EOM
+    end
+
+    it "knife list -R / returns everything" do
+      knife('list -R /').should_succeed <<EOM
+/:
+clients
+cookbooks
+data_bags
+environments
+nodes
+roles
+users
+
+/clients:
+chef-validator.json
+chef-webui.json
+
+/cookbooks:
+
+/data_bags:
+
+/environments:
+_default.json
+
+/nodes:
+
+/roles:
+
+/users:
+admin.json
+EOM
+    end
+  end
+
+  when_the_chef_server "has plenty of stuff in it" do
+    client 'client1', {}
+    client 'client2', {}
+    cookbook 'cookbook1', '1.0.0', { 'metadata.rb' => '' }
+    cookbook 'cookbook2', '1.0.1', { 'metadata.rb' => '', 'recipes' => { 'default.rb' => '' } }
+    data_bag 'bag1', { 'item1' => {}, 'item2' => {} }
+    data_bag 'bag2', { 'item1' => {}, 'item2' => {} }
+    environment 'environment1', {}
+    environment 'environment2', {}
+    node 'node1', {}
+    node 'node2', {}
+    role 'role1', {}
+    role 'role2', {}
+    user 'user1', {}
+    user 'user2', {}
+
+    it "knife list / returns all top level directories" do
+      knife('list /').should_succeed <<EOM
+/clients
+/cookbooks
+/data_bags
+/environments
+/nodes
+/roles
+/users
+EOM
+    end
+
+    it "knife list -R / returns everything" do
+      knife('list -R /').should_succeed <<EOM
+/:
+clients
+cookbooks
+data_bags
+environments
+nodes
+roles
+users
+
+/clients:
+chef-validator.json
+chef-webui.json
+client1.json
+client2.json
+
+/cookbooks:
+cookbook1
+cookbook2
+
+/cookbooks/cookbook1:
+metadata.rb
+
+/cookbooks/cookbook2:
+metadata.rb
+recipes
+
+/cookbooks/cookbook2/recipes:
+default.rb
+
+/data_bags:
+bag1
+bag2
+
+/data_bags/bag1:
+item1.json
+item2.json
+
+/data_bags/bag2:
+item1.json
+item2.json
+
+/environments:
+_default.json
+environment1.json
+environment2.json
+
+/nodes:
+node1.json
+node2.json
+
+/roles:
+role1.json
+role2.json
+
+/users:
+admin.json
+user1.json
+user2.json
+EOM
+    end
+
+    it "knife list -R --flat / returns everything" do
+      knife('list -R --flat /').should_succeed <<EOM
+/clients
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/client1.json
+/clients/client2.json
+/cookbooks
+/cookbooks/cookbook1
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2
+/cookbooks/cookbook2/metadata.rb
+/cookbooks/cookbook2/recipes
+/cookbooks/cookbook2/recipes/default.rb
+/data_bags
+/data_bags/bag1
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+/environments
+/environments/_default.json
+/environments/environment1.json
+/environments/environment2.json
+/nodes
+/nodes/node1.json
+/nodes/node2.json
+/roles
+/roles/role1.json
+/roles/role2.json
+/users
+/users/admin.json
+/users/user1.json
+/users/user2.json
+EOM
+    end
+
+    it "knife list -Rfp / returns everything" do
+      knife('list -Rfp /').should_succeed <<EOM
+/clients/
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/client1.json
+/clients/client2.json
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/metadata.rb
+/cookbooks/cookbook2/recipes/
+/cookbooks/cookbook2/recipes/default.rb
+/data_bags/
+/data_bags/bag1/
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2/
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+/environments/
+/environments/_default.json
+/environments/environment1.json
+/environments/environment2.json
+/nodes/
+/nodes/node1.json
+/nodes/node2.json
+/roles/
+/roles/role1.json
+/roles/role2.json
+/users/
+/users/admin.json
+/users/user1.json
+/users/user2.json
+EOM
+    end
+
+    it "knife list /cookbooks returns the list of cookbooks" do
+      knife('list /cookbooks').should_succeed <<EOM
+/cookbooks/cookbook1
+/cookbooks/cookbook2
+EOM
+    end
+
+    it "knife list /cookbooks/*2/*/*.rb returns the one file" do
+      knife('list /cookbooks/*2/*/*.rb').should_succeed "/cookbooks/cookbook2/recipes/default.rb\n"
+    end
+
+    it "knife list /**.rb returns all ruby files" do
+      knife('list /**.rb').should_succeed <<EOM
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/metadata.rb
+/cookbooks/cookbook2/recipes/default.rb
+EOM
+    end
+
+    it "knife list /cookbooks/**.rb returns all ruby files" do
+      knife('list /cookbooks/**.rb').should_succeed <<EOM
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/metadata.rb
+/cookbooks/cookbook2/recipes/default.rb
+EOM
+    end
+
+    it "knife list /**.json returns all json files" do
+      knife('list /**.json').should_succeed <<EOM
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/client1.json
+/clients/client2.json
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+/environments/_default.json
+/environments/environment1.json
+/environments/environment2.json
+/nodes/node1.json
+/nodes/node2.json
+/roles/role1.json
+/roles/role2.json
+/users/admin.json
+/users/user1.json
+/users/user2.json
+EOM
+    end
+
+    it "knife list /data**.json returns all data bag json files" do
+      knife('list /data**.json').should_succeed <<EOM
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+EOM
+    end
+
+    it "knife list /environments/missing_file.json reports missing file" do
+      knife('list /environments/missing_file.json').should_fail "ERROR: /environments/missing_file.json: No such file or directory\n"
+    end
+
+    context "missing file/directory exact match tests" do
+      it "knife list /blarghle reports missing directory" do
+        knife('list /blarghle').should_fail "ERROR: /blarghle: No such file or directory\n"
+      end
+
+      it "knife list /roles/blarghle reports missing directory" do
+        knife('list /roles/blarghle').should_fail "ERROR: /roles/blarghle: No such file or directory\n"
+      end
+
+      it "knife list /roles/blarghle/blorghle reports missing directory" do
+        knife('list /roles/blarghle/blorghle').should_fail "ERROR: /roles/blarghle/blorghle: No such file or directory\n"
+      end
+    end
+
+    context 'symlink tests' do
+      when_the_repository 'is empty' do
+        context 'when cwd is at the top of the repository' do
+          cwd '.'
+
+          it "knife list -Rfp returns everything" do
+            knife('list -Rfp').should_succeed <<EOM
+clients/
+clients/chef-validator.json
+clients/chef-webui.json
+clients/client1.json
+clients/client2.json
+cookbooks/
+cookbooks/cookbook1/
+cookbooks/cookbook1/metadata.rb
+cookbooks/cookbook2/
+cookbooks/cookbook2/metadata.rb
+cookbooks/cookbook2/recipes/
+cookbooks/cookbook2/recipes/default.rb
+data_bags/
+data_bags/bag1/
+data_bags/bag1/item1.json
+data_bags/bag1/item2.json
+data_bags/bag2/
+data_bags/bag2/item1.json
+data_bags/bag2/item2.json
+environments/
+environments/_default.json
+environments/environment1.json
+environments/environment2.json
+nodes/
+nodes/node1.json
+nodes/node2.json
+roles/
+roles/role1.json
+roles/role2.json
+users/
+users/admin.json
+users/user1.json
+users/user2.json
+EOM
+          end
+        end
+      end
+
+      when_the_repository 'has a cookbooks directory' do
+        directory 'cookbooks'
+        context 'when cwd is in cookbooks/' do
+          cwd 'cookbooks'
+
+          it "knife list -Rfp / returns everything" do
+            knife('list -Rfp /').should_succeed <<EOM
+/clients/
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/client1.json
+/clients/client2.json
+./
+cookbook1/
+cookbook1/metadata.rb
+cookbook2/
+cookbook2/metadata.rb
+cookbook2/recipes/
+cookbook2/recipes/default.rb
+/data_bags/
+/data_bags/bag1/
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2/
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+/environments/
+/environments/_default.json
+/environments/environment1.json
+/environments/environment2.json
+/nodes/
+/nodes/node1.json
+/nodes/node2.json
+/roles/
+/roles/role1.json
+/roles/role2.json
+/users/
+/users/admin.json
+/users/user1.json
+/users/user2.json
+EOM
+          end
+
+          it "knife list -Rfp .. returns everything" do
+            knife('list -Rfp ..').should_succeed <<EOM
+/clients/
+/clients/chef-validator.json
+/clients/chef-webui.json
+/clients/client1.json
+/clients/client2.json
+./
+cookbook1/
+cookbook1/metadata.rb
+cookbook2/
+cookbook2/metadata.rb
+cookbook2/recipes/
+cookbook2/recipes/default.rb
+/data_bags/
+/data_bags/bag1/
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2/
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+/environments/
+/environments/_default.json
+/environments/environment1.json
+/environments/environment2.json
+/nodes/
+/nodes/node1.json
+/nodes/node2.json
+/roles/
+/roles/role1.json
+/roles/role2.json
+/users/
+/users/admin.json
+/users/user1.json
+/users/user2.json
+EOM
+          end
+
+          it "knife list -Rfp returns cookbooks" do
+            knife('list -Rfp').should_succeed <<EOM
+cookbook1/
+cookbook1/metadata.rb
+cookbook2/
+cookbook2/metadata.rb
+cookbook2/recipes/
+cookbook2/recipes/default.rb
+EOM
+          end
+        end
+      end
+
+      when_the_repository 'has a cookbooks/cookbook2 directory' do
+        directory 'cookbooks/cookbook2'
+
+        context 'when cwd is in cookbooks/cookbook2' do
+          cwd 'cookbooks/cookbook2'
+
+          it "knife list -Rfp returns cookbooks" do
+            knife('list -Rfp').should_succeed <<EOM
+metadata.rb
+recipes/
+recipes/default.rb
+EOM
+          end
+        end
+      end
+
+      when_the_repository 'has a cookbooks directory and a symlinked cookbooks directory', :pending => (Chef::Platform.windows?) do
+        directory 'cookbooks'
+        symlink 'symlinked', 'cookbooks'
+
+        context 'when cwd is in cookbooks/' do
+          cwd 'cookbooks'
+
+          it "knife list -Rfp returns cookbooks" do
+            knife('list -Rfp').should_succeed <<EOM
+cookbook1/
+cookbook1/metadata.rb
+cookbook2/
+cookbook2/metadata.rb
+cookbook2/recipes/
+cookbook2/recipes/default.rb
+EOM
+          end
+        end
+
+        context 'when cwd is in symlinked/' do
+          cwd 'symlinked'
+
+          it "knife list -Rfp returns cookbooks" do
+            knife('list -Rfp').should_succeed <<EOM
+cookbook1/
+cookbook1/metadata.rb
+cookbook2/
+cookbook2/metadata.rb
+cookbook2/recipes/
+cookbook2/recipes/default.rb
+EOM
+          end
+        end
+      end
+
+      when_the_repository 'has a real_cookbooks directory and a cookbooks symlink to it', :pending => (Chef::Platform.windows?) do
+        directory 'real_cookbooks'
+        symlink 'cookbooks', 'real_cookbooks'
+
+        context 'when cwd is in real_cookbooks/' do
+          cwd 'real_cookbooks'
+
+          it "knife list -Rfp returns cookbooks" do
+            knife('list -Rfp').should_succeed <<EOM
+cookbook1/
+cookbook1/metadata.rb
+cookbook2/
+cookbook2/metadata.rb
+cookbook2/recipes/
+cookbook2/recipes/default.rb
+EOM
+          end
+        end
+
+        context 'when cwd is in cookbooks/' do
+          cwd 'cookbooks'
+
+          it "knife list -Rfp returns cookbooks" do
+            knife('list -Rfp').should_succeed <<EOM
+cookbook1/
+cookbook1/metadata.rb
+cookbook2/
+cookbook2/metadata.rb
+cookbook2/recipes/
+cookbook2/recipes/default.rb
+EOM
+          end
+        end
+      end
+    end
+  end
+
+  context "--local" do
+    when_the_repository "is empty" do
+      it "knife list --local / returns nothing" do
+        knife('list --local /').should_succeed ""
+      end
+
+      it "knife list /roles returns nothing" do
+        knife('list --local /roles').should_fail "ERROR: /roles: No such file or directory\n"
+      end
+    end
+
+    when_the_repository "has a bunch of stuff" do
+      file 'clients/client1.json', {}
+      file 'clients/client2.json', {}
+
+      directory 'cookbooks/cookbook1' do
+        file 'metadata.rb', ''
+      end
+      directory 'cookbooks/cookbook2' do
+        file 'metadata.rb', ''
+        file 'recipes/default.rb', ''
+      end
+
+      directory 'data_bags' do
+        directory 'bag1' do
+          file 'item1.json', {}
+          file 'item2.json', {}
+        end
+        directory 'bag2' do
+          file 'item1.json', {}
+          file 'item2.json', {}
+        end
+      end
+
+      file 'environments/environment1.json', {}
+      file 'environments/environment2.json', {}
+      file 'nodes/node1.json', {}
+      file 'nodes/node2.json', {}
+      file 'roles/role1.json', {}
+      file 'roles/role2.json', {}
+      file 'users/user1.json', {}
+      file 'users/user2.json', {}
+
+      it "knife list -Rfp / returns everything" do
+        knife('list -Rp --local --flat /').should_succeed <<EOM
+/clients/
+/clients/client1.json
+/clients/client2.json
+/cookbooks/
+/cookbooks/cookbook1/
+/cookbooks/cookbook1/metadata.rb
+/cookbooks/cookbook2/
+/cookbooks/cookbook2/metadata.rb
+/cookbooks/cookbook2/recipes/
+/cookbooks/cookbook2/recipes/default.rb
+/data_bags/
+/data_bags/bag1/
+/data_bags/bag1/item1.json
+/data_bags/bag1/item2.json
+/data_bags/bag2/
+/data_bags/bag2/item1.json
+/data_bags/bag2/item2.json
+/environments/
+/environments/environment1.json
+/environments/environment2.json
+/nodes/
+/nodes/node1.json
+/nodes/node2.json
+/roles/
+/roles/role1.json
+/roles/role2.json
+/users/
+/users/user1.json
+/users/user2.json
+EOM
+      end
+
+      context "missing file/directory tests" do
+        it "knife list --local /blarghle reports missing directory" do
+          knife('list --local /blarghle').should_fail "ERROR: /blarghle: No such file or directory\n"
+        end
+
+        it "knife list /roles/blarghle reports missing directory" do
+          knife('list --local /roles/blarghle').should_fail "ERROR: /roles/blarghle: No such file or directory\n"
+        end
+
+        it "knife list /roles/blarghle/blorghle reports missing directory" do
+          knife('list --local /roles/blarghle/blorghle').should_fail "ERROR: /roles/blarghle/blorghle: No such file or directory\n"
+        end
+      end
+    end
+  end
+end
diff --git a/spec/integration/knife/raw_spec.rb b/spec/integration/knife/raw_spec.rb
new file mode 100644
index 0000000..6963006
--- /dev/null
+++ b/spec/integration/knife/raw_spec.rb
@@ -0,0 +1,229 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/raw'
+require 'chef/knife/show'
+
+describe 'knife raw' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  when_the_chef_server "has one of each thing" do
+    client 'x', '{}'
+    cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+    data_bag 'x', { 'y' => '{}' }
+    environment 'x', '{}'
+    node 'x', '{}'
+    role 'x', '{}'
+    user 'x', '{}'
+
+    it 'knife raw /nodes/x returns the node', :pending => (RUBY_VERSION < "1.9") do
+      knife('raw /nodes/x').should_succeed <<EOM
+{
+  "name": "x",
+  "json_class": "Chef::Node",
+  "chef_type": "node",
+  "chef_environment": "_default",
+  "override": {
+  },
+  "normal": {
+  },
+  "default": {
+  },
+  "automatic": {
+  },
+  "run_list": [
+
+  ]
+}
+EOM
+    end
+
+    it 'knife raw /blarghle returns 404' do
+      knife('raw /blarghle').should_fail(/ERROR: Server responded with error 404 "Not Found"/)
+    end
+
+    it 'knife raw -m DELETE /roles/x succeeds', :pending => (RUBY_VERSION < "1.9") do
+      knife('raw -m DELETE /roles/x').should_succeed <<EOM
+{
+  "name": "x",
+  "description": "",
+  "json_class": "Chef::Role",
+  "chef_type": "role",
+  "default_attributes": {
+  },
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ],
+  "env_run_lists": {
+  }
+}
+EOM
+      knife('show /roles/x.json').should_fail "ERROR: /roles/x.json: No such file or directory\n"
+    end
+
+    it 'knife raw -m PUT -i blah.txt /roles/x succeeds', :pending => (RUBY_VERSION < "1.9") do
+      Tempfile.open('raw_put_input') do |file|
+        file.write <<EOM
+{
+  "name": "x",
+  "description": "eek",
+  "json_class": "Chef::Role",
+  "chef_type": "role",
+  "default_attributes": {
+  },
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ],
+  "env_run_lists": {
+  }
+}
+EOM
+        file.close
+
+        knife("raw -m PUT -i #{file.path} /roles/x").should_succeed <<EOM
+{
+  "name": "x",
+  "description": "eek",
+  "json_class": "Chef::Role",
+  "chef_type": "role",
+  "default_attributes": {
+  },
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ],
+  "env_run_lists": {
+  }
+}
+EOM
+        knife('show /roles/x.json').should_succeed <<EOM
+/roles/x.json:
+{
+  "name": "x",
+  "description": "eek"
+}
+EOM
+      end
+    end
+
+    it 'knife raw -m POST -i blah.txt /roles succeeds', :pending => (RUBY_VERSION < "1.9") do
+      Tempfile.open('raw_put_input') do |file|
+        file.write <<EOM
+{
+  "name": "y",
+  "description": "eek",
+  "json_class": "Chef::Role",
+  "chef_type": "role",
+  "default_attributes": {
+  },
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ],
+  "env_run_lists": {
+  }
+}
+EOM
+        file.close
+
+        knife("raw -m POST -i #{file.path} /roles").should_succeed <<EOM
+{
+  "uri": "#{ChefZero::RSpec.server.url}/roles/y"
+}
+EOM
+        knife('show /roles/y.json').should_succeed <<EOM
+/roles/y.json:
+{
+  "name": "y",
+  "description": "eek"
+}
+EOM
+      end
+    end
+
+    context 'When a server returns raw json' do
+      before :each do
+        @real_chef_server_url = Chef::Config.chef_server_url
+        Chef::Config.chef_server_url = "http://127.0.0.1:9018"
+        app = lambda do |env|
+          [200, {'Content-Type' => 'application/json' }, ['{ "x": "y", "a": "b" }'] ]
+        end
+        @raw_server = Puma::Server.new(app, Puma::Events.new(STDERR, STDOUT))
+        @raw_server.add_tcp_listener("127.0.0.1", 9018)
+        @raw_server.run
+      end
+
+      after :each do
+        Chef::Config.chef_server_url = @real_chef_server_url
+        @raw_server.stop(true)
+      end
+
+      it 'knife raw /blah returns the prettified json', :pending => (RUBY_VERSION < "1.9") do
+        knife('raw /blah').should_succeed <<EOM
+{
+  "x": "y",
+  "a": "b"
+}
+EOM
+      end
+
+      it 'knife raw --no-pretty /blah returns the raw json' do
+        knife('raw --no-pretty /blah').should_succeed <<EOM
+{ "x": "y", "a": "b" }
+EOM
+      end
+    end
+
+    context 'When a server returns text' do
+      before :each do
+        @real_chef_server_url = Chef::Config.chef_server_url
+        Chef::Config.chef_server_url = "http://127.0.0.1:9018"
+        app = lambda do |env|
+          [200, {'Content-Type' => 'text' }, ['{ "x": "y", "a": "b" }'] ]
+        end
+        @raw_server = Puma::Server.new(app, Puma::Events.new(STDERR, STDOUT))
+        @raw_server.add_tcp_listener("127.0.0.1", 9018)
+        @raw_server.run
+      end
+
+      after :each do
+        Chef::Config.chef_server_url = @real_chef_server_url
+        @raw_server.stop(true)
+      end
+
+      it 'knife raw /blah returns the raw text' do
+        knife('raw /blah').should_succeed(<<EOM)
+{ "x": "y", "a": "b" }
+EOM
+      end
+
+      it 'knife raw --no-pretty /blah returns the raw text' do
+        knife('raw --no-pretty /blah').should_succeed(<<EOM)
+{ "x": "y", "a": "b" }
+EOM
+      end
+    end
+  end
+end
diff --git a/spec/integration/knife/redirection_spec.rb b/spec/integration/knife/redirection_spec.rb
new file mode 100644
index 0000000..5af9fd3
--- /dev/null
+++ b/spec/integration/knife/redirection_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'puma'
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/list'
+
+describe 'redirection' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  when_the_chef_server 'has a role' do
+    role 'x', {}
+
+    context 'and another server redirects to it with 302' do
+      before :each do
+        @real_chef_server_url = Chef::Config.chef_server_url
+        Chef::Config.chef_server_url = "http://127.0.0.1:9018"
+        app = lambda do |env|
+          [302, {'Content-Type' => 'text','Location' => "#{@real_chef_server_url}#{env['PATH_INFO']}" }, ['302 found'] ]
+        end
+        @redirector_server = Puma::Server.new(app, Puma::Events.new(STDERR, STDOUT))
+        @redirector_server.add_tcp_listener("127.0.0.1", 9018)
+        @redirector_server.run
+        Timeout::timeout(5) do
+          until @redirector_server.running
+            sleep(0.01)
+          end
+          raise @server_error if @server_error
+        end
+      end
+
+      after :each do
+        Chef::Config.chef_server_url = @real_chef_server_url
+        @redirector_server.stop(true)
+      end
+
+      it 'knife list /roles returns the role' do
+        knife('list /roles').should_succeed "/roles/x.json\n"
+      end
+    end
+  end
+end
diff --git a/spec/integration/knife/show_spec.rb b/spec/integration/knife/show_spec.rb
new file mode 100644
index 0000000..a061fab
--- /dev/null
+++ b/spec/integration/knife/show_spec.rb
@@ -0,0 +1,158 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/show'
+
+describe 'knife show' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  when_the_chef_server "has one of each thing" do
+    client 'x', '{}'
+    cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+    data_bag 'x', { 'y' => '{}' }
+    environment 'x', '{}'
+    node 'x', '{}'
+    role 'x', '{}'
+    user 'x', '{}'
+
+    when_the_repository 'also has one of each thing' do
+      file 'clients/x.json', { 'foo' => 'bar' }
+      file 'cookbooks/x/metadata.rb', 'version "1.0.1"'
+      file 'data_bags/x/y.json', { 'foo' => 'bar' }
+      file 'environments/_default.json', { 'foo' => 'bar' }
+      file 'environments/x.json', { 'foo' => 'bar' }
+      file 'nodes/x.json', { 'foo' => 'bar' }
+      file 'roles/x.json', { 'foo' => 'bar' }
+      file 'users/x.json', { 'foo' => 'bar' }
+
+      it 'knife show /cookbooks/x/metadata.rb shows the remote version' do
+        knife('show /cookbooks/x/metadata.rb').should_succeed <<EOM
+/cookbooks/x/metadata.rb:
+version "1.0.0"
+EOM
+      end
+      it 'knife show --local /cookbooks/x/metadata.rb shows the local version' do
+        knife('show --local /cookbooks/x/metadata.rb').should_succeed <<EOM
+/cookbooks/x/metadata.rb:
+version "1.0.1"
+EOM
+      end
+      it 'knife show /data_bags/x/y.json shows the remote version' do
+        knife('show /data_bags/x/y.json').should_succeed <<EOM
+/data_bags/x/y.json:
+{
+  "id": "y"
+}
+EOM
+      end
+      it 'knife show --local /data_bags/x/y.json shows the local version' do
+        knife('show --local /data_bags/x/y.json').should_succeed <<EOM
+/data_bags/x/y.json:
+{
+  "foo": "bar"
+}
+EOM
+      end
+      it 'knife show /environments/x.json shows the remote version', :pending => (RUBY_VERSION < "1.9") do
+        knife('show /environments/x.json').should_succeed <<EOM
+/environments/x.json:
+{
+  "name": "x"
+}
+EOM
+      end
+      it 'knife show --local /environments/x.json shows the local version' do
+        knife('show --local /environments/x.json').should_succeed <<EOM
+/environments/x.json:
+{
+  "foo": "bar"
+}
+EOM
+      end
+      it 'knife show /roles/x.json shows the remote version', :pending => (RUBY_VERSION < "1.9") do
+        knife('show /roles/x.json').should_succeed <<EOM
+/roles/x.json:
+{
+  "name": "x"
+}
+EOM
+      end
+      it 'knife show --local /roles/x.json shows the local version' do
+        knife('show --local /roles/x.json').should_succeed <<EOM
+/roles/x.json:
+{
+  "foo": "bar"
+}
+EOM
+      end
+      # show directory
+      it 'knife show /data_bags/x fails' do
+        knife('show /data_bags/x').should_fail "ERROR: /data_bags/x: is a directory\n"
+      end
+      it 'knife show --local /data_bags/x fails' do
+        knife('show --local /data_bags/x').should_fail "ERROR: /data_bags/x: is a directory\n"
+      end
+      # show nonexistent file
+      it 'knife show /environments/nonexistent.json fails' do
+        knife('show /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+      end
+      it 'knife show --local /environments/nonexistent.json fails' do
+        knife('show --local /environments/nonexistent.json').should_fail "ERROR: /environments/nonexistent.json: No such file or directory\n"
+      end
+    end
+  end
+
+  when_the_chef_server 'has a hash with multiple keys' do
+    environment 'x', {
+      'default_attributes' => { 'foo' => 'bar' },
+      'cookbook_versions' => { 'blah' => '= 1.0.0'},
+      'override_attributes' => { 'x' => 'y' },
+      'description' => 'woo',
+      'name' => 'x'
+    }
+    it 'knife show shows the attributes in a predetermined order', :pending => (RUBY_VERSION < "1.9") do
+      knife('show /environments/x.json').should_succeed <<EOM
+/environments/x.json:
+{
+  "name": "x",
+  "description": "woo",
+  "cookbook_versions": {
+    "blah": "= 1.0.0"
+  },
+  "default_attributes": {
+    "foo": "bar"
+  },
+  "override_attributes": {
+    "x": "y"
+  }
+}
+EOM
+    end
+  end
+
+  when_the_repository 'has an environment with bad JSON' do
+    file 'environments/x.json', '{'
+    it 'knife show succeeds' do
+      knife('show --local /environments/x.json').should_succeed <<EOM
+/environments/x.json:
+{
+EOM
+    end
+  end
+end
diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb
new file mode 100644
index 0000000..46b8042
--- /dev/null
+++ b/spec/integration/knife/upload_spec.rb
@@ -0,0 +1,1076 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'support/shared/integration/integration_helper'
+require 'chef/knife/upload'
+require 'chef/knife/diff'
+require 'chef/knife/raw'
+
+describe 'knife upload' do
+  extend IntegrationSupport
+  include KnifeSupport
+
+  context 'without versioned cookbooks' do
+    when_the_chef_server "has one of each thing" do
+      client 'x', {}
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+      data_bag 'x', { 'y' => {} }
+      environment 'x', {}
+      node 'x', {}
+      role 'x', {}
+      user 'x', {}
+
+      when_the_repository 'has only top-level directories' do
+        directory 'clients'
+        directory 'cookbooks'
+        directory 'data_bags'
+        directory 'environments'
+        directory 'nodes'
+        directory 'roles'
+        directory 'users'
+
+        it 'knife upload does nothing' do
+          knife('upload /').should_succeed ''
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients/chef-validator.json
+D\t/clients/chef-webui.json
+D\t/clients/x.json
+D\t/cookbooks/x
+D\t/data_bags/x
+D\t/environments/_default.json
+D\t/environments/x.json
+D\t/nodes/x.json
+D\t/roles/x.json
+D\t/users/admin.json
+D\t/users/x.json
+EOM
+        end
+
+        it 'knife upload --purge deletes everything' do
+          knife('upload --purge /').should_succeed(<<EOM, :stderr => "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n")
+Deleted extra entry /clients/chef-validator.json (purge is on)
+Deleted extra entry /clients/chef-webui.json (purge is on)
+Deleted extra entry /clients/x.json (purge is on)
+Deleted extra entry /cookbooks/x (purge is on)
+Deleted extra entry /data_bags/x (purge is on)
+Deleted extra entry /environments/x.json (purge is on)
+Deleted extra entry /nodes/x.json (purge is on)
+Deleted extra entry /roles/x.json (purge is on)
+Deleted extra entry /users/admin.json (purge is on)
+Deleted extra entry /users/x.json (purge is on)
+EOM
+        knife('diff --name-status /').should_succeed <<EOM
+D\t/environments/_default.json
+EOM
+        end
+      end
+
+      when_the_repository 'has an identical copy of each thing' do
+        file 'clients/chef-validator.json', { 'validator' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/chef-webui.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+        file 'data_bags/x/y.json', {}
+        file 'environments/_default.json', { "description" => "The default Chef environment" }
+        file 'environments/x.json', {}
+        file 'nodes/x.json', {}
+        file 'roles/x.json', {}
+        file 'users/admin.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'users/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+        it 'knife upload makes no changes' do
+          knife('upload /cookbooks/x').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        it 'knife upload --purge makes no changes' do
+          knife('upload --purge /').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        context 'except the role file' do
+          file 'roles/x.json', { 'description' => 'blarghle' }
+          it 'knife upload changes the role' do
+            knife('upload /').should_succeed "Updated /roles/x.json\n"
+            knife('diff --name-status /').should_succeed ''
+          end
+          it 'knife upload --no-diff does not change the role' do
+            knife('upload --no-diff /').should_succeed ''
+            knife('diff --name-status /').should_succeed "M\t/roles/x.json\n"
+          end
+        end
+
+        context 'except the role file is textually different, but not ACTUALLY different' do
+          file 'roles/x.json', <<EOM
+{
+  "chef_type": "role",
+  "default_attributes":  {
+  },
+  "env_run_lists": {
+  },
+  "json_class": "Chef::Role",
+  "name": "x",
+  "description": "",
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ]
+}
+EOM
+          it 'knife upload / does not change anything' do
+            knife('upload /').should_succeed ''
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+
+        context 'as well as one extra copy of each thing' do
+          file 'clients/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+          file 'cookbooks/x/blah.rb', ''
+          file 'cookbooks/y/metadata.rb', 'version "1.0.0"'
+          file 'data_bags/x/z.json', {}
+          file 'data_bags/y/zz.json', {}
+          file 'environments/y.json', {}
+          file 'nodes/y.json', {}
+          file 'roles/y.json', {}
+          file 'users/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+          it 'knife upload adds the new files' do
+            knife('upload /').should_succeed <<EOM
+Created /clients/y.json
+Updated /cookbooks/x
+Created /cookbooks/y
+Created /data_bags/x/z.json
+Created /data_bags/y
+Created /data_bags/y/zz.json
+Created /environments/y.json
+Created /nodes/y.json
+Created /roles/y.json
+Created /users/y.json
+EOM
+            knife('diff --name-status /').should_succeed ''
+          end
+
+          it 'knife upload --no-diff adds the new files' do
+            knife('upload --no-diff /').should_succeed <<EOM
+Created /clients/y.json
+Updated /cookbooks/x
+Created /cookbooks/y
+Created /data_bags/x/z.json
+Created /data_bags/y
+Created /data_bags/y/zz.json
+Created /environments/y.json
+Created /nodes/y.json
+Created /roles/y.json
+Created /users/y.json
+EOM
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+      end
+
+      when_the_repository 'is empty' do
+        it 'knife upload does nothing' do
+          knife('upload /').should_succeed ''
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients
+D\t/cookbooks
+D\t/data_bags
+D\t/environments
+D\t/nodes
+D\t/roles
+D\t/users
+EOM
+        end
+
+        it 'knife upload --purge deletes nothing' do
+          knife('upload --purge /').should_fail <<EOM
+ERROR: /clients cannot be deleted.
+ERROR: /cookbooks cannot be deleted.
+ERROR: /data_bags cannot be deleted.
+ERROR: /environments cannot be deleted.
+ERROR: /nodes cannot be deleted.
+ERROR: /roles cannot be deleted.
+ERROR: /users cannot be deleted.
+EOM
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients
+D\t/cookbooks
+D\t/data_bags
+D\t/environments
+D\t/nodes
+D\t/roles
+D\t/users
+EOM
+        end
+
+        context 'when current directory is top level' do
+          cwd '.'
+          it 'knife upload with no parameters reports an error' do
+            knife('upload').should_fail "FATAL: Must specify at least one argument.  If you want to upload everything in this directory, type \"knife upload .\"\n", :stdout => /USAGE/
+          end
+        end
+      end
+    end
+
+    when_the_chef_server 'is empty' do
+      when_the_repository 'has a data bag item' do
+        file 'data_bags/x/y.json', { 'foo' => 'bar' }
+        it 'knife upload of the data bag uploads only the values in the data bag item and no other' do
+          knife('upload /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags/x
+Created /data_bags/x/y.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+EOM
+          JSON.parse(knife('raw /data/x/y').stdout, :create_additions => false).keys.sort.should == [ 'foo', 'id' ]
+        end
+
+        it 'knife upload /data_bags/x /data_bags/x/y.json uploads x once' do
+          knife('upload /data_bags/x /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags/x
+Created /data_bags/x/y.json
+EOM
+        end
+      end
+
+      when_the_repository 'has a data bag item with keys chef_type and data_bag' do
+        file 'data_bags/x/y.json', { 'chef_type' => 'aaa', 'data_bag' => 'bbb' }
+        it 'upload preserves chef_type and data_bag' do
+          knife('upload /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags/x
+Created /data_bags/x/y.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed ''
+          result = JSON.parse(knife('raw /data/x/y').stdout, :create_additions => false)
+          result.keys.sort.should == [ 'chef_type', 'data_bag', 'id' ]
+          result['chef_type'].should == 'aaa'
+          result['data_bag'].should == 'bbb'
+        end
+      end
+
+      # Test upload of an item when the other end doesn't even have the container
+      when_the_repository 'has two data bag items' do
+        file 'data_bags/x/y.json', {}
+        file 'data_bags/x/z.json', {}
+        it 'knife upload of one data bag item itself succeeds' do
+          knife('upload /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags/x
+Created /data_bags/x/y.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+A\t/data_bags/x/z.json
+EOM
+        end
+      end
+    end
+
+    when_the_chef_server 'has three data bag items' do
+      data_bag 'x', { 'deleted' => {}, 'modified' => {}, 'unmodified' => {} }
+
+      when_the_repository 'has a modified, unmodified, added and deleted data bag item' do
+        file 'data_bags/x/added.json', {}
+        file 'data_bags/x/modified.json', { 'foo' => 'bar' }
+        file 'data_bags/x/unmodified.json', {}
+
+        it 'knife upload of the modified file succeeds' do
+          knife('upload /data_bags/x/modified.json').should_succeed <<EOM
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload of the unmodified file does nothing' do
+          knife('upload /data_bags/x/unmodified.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload of the added file succeeds' do
+          knife('upload /data_bags/x/added.json').should_succeed <<EOM
+Created /data_bags/x/added.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+M\t/data_bags/x/modified.json
+EOM
+        end
+        it 'knife upload of the deleted file does nothing' do
+          knife('upload /data_bags/x/deleted.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload --purge of the deleted file deletes it' do
+          knife('upload --purge /data_bags/x/deleted.json').should_succeed <<EOM
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload of the entire data bag uploads everything' do
+          knife('upload /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife upload --purge of the entire data bag uploads everything' do
+          knife('upload --purge /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed ''
+        end
+        context 'when cwd is the /data_bags directory' do
+          cwd 'data_bags'
+          it 'knife upload fails' do
+            knife('upload').should_fail "FATAL: Must specify at least one argument.  If you want to upload everything in this directory, type \"knife upload .\"\n", :stdout => /USAGE/
+          end
+          it 'knife upload --purge . uploads everything' do
+            knife('upload --purge .').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+          it 'knife upload --purge * uploads everything' do
+            knife('upload --purge *').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+        end
+      end
+    end
+
+    # Cookbook upload is a funny thing ... direct cookbook upload works, but
+    # upload of a file is designed not to work at present.  Make sure that is the
+    # case.
+    when_the_chef_server 'has a cookbook' do
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'z.rb' => '' }
+      when_the_repository 'has a modified, extra and missing file for the cookbook' do
+        file 'cookbooks/x/metadata.rb', 'version  "1.0.0"'
+        file 'cookbooks/x/y.rb', 'hi'
+        it 'knife upload of any individual file fails' do
+          knife('upload /cookbooks/x/metadata.rb').should_fail "ERROR: /cookbooks/x/metadata.rb cannot be updated.\n"
+          knife('upload /cookbooks/x/y.rb').should_fail "ERROR: /cookbooks/x cannot have a child created under it.\n"
+          knife('upload --purge /cookbooks/x/z.rb').should_fail "ERROR: /cookbooks/x/z.rb cannot be deleted.\n"
+        end
+        # TODO this is a bit of an inconsistency: if we didn't specify --purge,
+        # technically we shouldn't have deleted missing files.  But ... cookbooks
+        # are a special case.
+        it 'knife upload of the cookbook itself succeeds' do
+          knife('upload /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+        it 'knife upload --purge of the cookbook itself succeeds' do
+          knife('upload /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+      when_the_repository 'has a missing file for the cookbook' do
+        file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+        it 'knife upload of the cookbook succeeds' do
+          knife('upload /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+      when_the_repository 'has an extra file for the cookbook' do
+        file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+        file 'cookbooks/x/z.rb', ''
+        file 'cookbooks/x/blah.rb', ''
+        it 'knife upload of the cookbook succeeds' do
+          knife('upload /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has a different file in the cookbook' do
+        file 'cookbooks/x/metadata.rb', 'version  "1.0.0"'
+
+        it 'knife upload --freeze freezes the cookbook' do
+          knife('upload --freeze /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          # Modify a file and attempt to upload
+          file 'cookbooks/x/metadata.rb', 'version "1.0.0" # This is different'
+          knife('upload /cookbooks/x').should_fail "ERROR: /cookbooks failed to write: Cookbook x is frozen\n"
+        end
+      end
+    end
+
+    when_the_chef_server 'has a frozen cookbook' do
+      cookbook 'frozencook', '1.0.0', {
+        'metadata.rb' => 'version "1.0.0"'
+      }, :frozen => true
+
+      when_the_repository 'has an update to said cookbook' do
+        file 'cookbooks/frozencook/metadata.rb', 'version "1.0.0" # This is different'
+
+        it 'knife upload fails to upload the frozen cookbook' do
+          knife('upload /cookbooks/frozencook').should_fail "ERROR: /cookbooks failed to write: Cookbook frozencook is frozen\n"
+        end
+        it 'knife upload --force uploads the frozen cookbook' do
+          knife('upload --force /cookbooks/frozencook').should_succeed <<EOM
+Updated /cookbooks/frozencook
+EOM
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x/onlyin1.0.0.rb', 'old_text'
+
+      when_the_chef_server 'has a later version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => '' }
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife upload /cookbooks/x uploads the local version' do
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/onlyin1.0.1.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+          knife('upload --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/onlyin1.0.1.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => ''}
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+        it 'knife upload /cookbooks/x uploads the local version' do
+          knife('upload --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has a later version for the cookbook, and no current version' do
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife upload /cookbooks/x uploads the local version' do
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/onlyin1.0.1.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+          knife('upload --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x/metadata.rb
+D\t/cookbooks/x/onlyin1.0.1.rb
+A\t/cookbooks/x/onlyin1.0.0.rb
+EOM
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook, and no current version' do
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+
+        it 'knife upload /cookbooks/x uploads the new version' do
+          knife('upload --purge /cookbooks/x').should_succeed <<EOM
+Updated /cookbooks/x
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
+
+    when_the_chef_server 'has an environment' do
+      environment 'x', {}
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife upload tries and fails' do
+          knife('upload /environments/x.json').should_fail "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\nERROR: /environments/x.json failed to write: Parse error reading JSON: A JSON text must at least contain two octets!\n"
+          knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n"
+        end
+      end
+
+      when_the_repository 'has the same environment with the wrong name in the file' do
+        file 'environments/x.json', { 'name' => 'y' }
+        it 'knife upload fails' do
+          knife('upload /environments/x.json').should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n"
+          knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n"
+        end
+      end
+
+      when_the_repository 'has the same environment with no name in the file' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        it 'knife upload succeeds' do
+          knife('upload /environments/x.json').should_succeed "Updated /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+    end
+
+    when_the_chef_server 'is empty' do
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife upload tries and fails' do
+          knife('upload /environments/x.json').should_fail "ERROR: /environments failed to create_child: Parse error reading JSON creating child 'x.json': A JSON text must at least contain two octets!\n"
+          knife('diff --name-status /environments/x.json').should_succeed "A\t/environments/x.json\n"
+        end
+      end
+
+      when_the_repository 'has an environment with the wrong name in the file' do
+        file 'environments/x.json', { 'name' => 'y' }
+        it 'knife upload fails' do
+          knife('upload /environments/x.json').should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n"
+          knife('diff --name-status /environments/x.json').should_succeed "A\t/environments/x.json\n"
+        end
+      end
+
+      when_the_repository 'has an environment with no name in the file' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        it 'knife upload succeeds' do
+          knife('upload /environments/x.json').should_succeed "Created /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has a data bag with no id in the file' do
+        file 'data_bags/bag/x.json', { 'foo' => 'bar' }
+        it 'knife upload succeeds' do
+          knife('upload /data_bags/bag/x.json').should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n"
+          knife('diff --name-status /data_bags/bag/x.json').should_succeed ''
+        end
+      end
+    end
+  end # without versioned cookbooks
+
+  with_versioned_cookbooks do
+    when_the_chef_server "has one of each thing" do
+      client 'x', {}
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"' }
+      data_bag 'x', { 'y' => {} }
+      environment 'x', {}
+      node 'x', {}
+      role 'x', {}
+      user 'x', {}
+
+      when_the_repository 'has only top-level directories' do
+        directory 'clients'
+        directory 'cookbooks'
+        directory 'data_bags'
+        directory 'environments'
+        directory 'nodes'
+        directory 'roles'
+        directory 'users'
+
+        it 'knife upload does nothing' do
+          knife('upload /').should_succeed ''
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients/chef-validator.json
+D\t/clients/chef-webui.json
+D\t/clients/x.json
+D\t/cookbooks/x-1.0.0
+D\t/data_bags/x
+D\t/environments/_default.json
+D\t/environments/x.json
+D\t/nodes/x.json
+D\t/roles/x.json
+D\t/users/admin.json
+D\t/users/x.json
+EOM
+        end
+
+        it 'knife upload --purge deletes everything' do
+          knife('upload --purge /').should_succeed(<<EOM, :stderr => "WARNING: /environments/_default.json cannot be deleted (default environment cannot be modified).\n")
+Deleted extra entry /clients/chef-validator.json (purge is on)
+Deleted extra entry /clients/chef-webui.json (purge is on)
+Deleted extra entry /clients/x.json (purge is on)
+Deleted extra entry /cookbooks/x-1.0.0 (purge is on)
+Deleted extra entry /data_bags/x (purge is on)
+Deleted extra entry /environments/x.json (purge is on)
+Deleted extra entry /nodes/x.json (purge is on)
+Deleted extra entry /roles/x.json (purge is on)
+Deleted extra entry /users/admin.json (purge is on)
+Deleted extra entry /users/x.json (purge is on)
+EOM
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/environments/_default.json
+EOM
+        end
+      end
+
+      when_the_repository 'has an identical copy of each thing' do
+        file 'clients/chef-validator.json', { 'validator' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/chef-webui.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'clients/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+        file 'data_bags/x/y.json', {}
+        file 'environments/_default.json', { 'description' => 'The default Chef environment' }
+        file 'environments/x.json', {}
+        file 'nodes/x.json', {}
+        file 'roles/x.json', {}
+        file 'users/admin.json', { 'admin' => true, 'public_key' => ChefZero::PUBLIC_KEY }
+        file 'users/x.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+        it 'knife upload makes no changes' do
+          knife('upload /cookbooks/x-1.0.0').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        it 'knife upload --purge makes no changes' do
+          knife('upload --purge /').should_succeed ''
+          knife('diff --name-status /').should_succeed ''
+        end
+
+        context 'except the role file' do
+          file 'roles/x.json', { 'description' => 'blarghle' }
+
+          it 'knife upload changes the role' do
+            knife('upload /').should_succeed "Updated /roles/x.json\n"
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+
+        context 'except the role file is textually different, but not ACTUALLY different' do
+          file 'roles/x.json', <<EOM
+{
+  "chef_type": "role",
+  "default_attributes":  {
+  },
+  "env_run_lists": {
+  },
+  "json_class": "Chef::Role",
+  "name": "x",
+  "description": "",
+  "override_attributes": {
+  },
+  "run_list": [
+
+  ]
+}
+EOM
+          it 'knife upload / does not change anything' do
+            knife('upload /').should_succeed ''
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+
+        context 'as well as one extra copy of each thing' do
+          file 'clients/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+          file 'cookbooks/x-1.0.0/blah.rb', ''
+          file 'cookbooks/x-2.0.0/metadata.rb', 'version "2.0.0"'
+          file 'cookbooks/y-1.0.0/metadata.rb', 'version "1.0.0"'
+          file 'data_bags/x/z.json', {}
+          file 'data_bags/y/zz.json', {}
+          file 'environments/y.json', {}
+          file 'nodes/y.json', {}
+          file 'roles/y.json', {}
+          file 'users/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
+
+          it 'knife upload adds the new files' do
+            knife('upload /').should_succeed <<EOM
+Created /clients/y.json
+Updated /cookbooks/x-1.0.0
+Created /cookbooks/x-2.0.0
+Created /cookbooks/y-1.0.0
+Created /data_bags/x/z.json
+Created /data_bags/y
+Created /data_bags/y/zz.json
+Created /environments/y.json
+Created /nodes/y.json
+Created /roles/y.json
+Created /users/y.json
+EOM
+            knife('diff --name-status /').should_succeed ''
+          end
+        end
+      end
+
+      when_the_repository 'is empty' do
+        it 'knife upload does nothing' do
+          knife('upload /').should_succeed ''
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients
+D\t/cookbooks
+D\t/data_bags
+D\t/environments
+D\t/nodes
+D\t/roles
+D\t/users
+EOM
+        end
+
+        it 'knife upload --purge deletes nothing' do
+          knife('upload --purge /').should_fail <<EOM
+ERROR: /clients cannot be deleted.
+ERROR: /cookbooks cannot be deleted.
+ERROR: /data_bags cannot be deleted.
+ERROR: /environments cannot be deleted.
+ERROR: /nodes cannot be deleted.
+ERROR: /roles cannot be deleted.
+ERROR: /users cannot be deleted.
+EOM
+          knife('diff --name-status /').should_succeed <<EOM
+D\t/clients
+D\t/cookbooks
+D\t/data_bags
+D\t/environments
+D\t/nodes
+D\t/roles
+D\t/users
+EOM
+        end
+
+        context 'when current directory is top level' do
+          cwd '.'
+          it 'knife upload with no parameters reports an error' do
+            knife('upload').should_fail "FATAL: Must specify at least one argument.  If you want to upload everything in this directory, type \"knife upload .\"\n", :stdout => /USAGE/
+          end
+        end
+      end
+    end
+
+    # Test upload of an item when the other end doesn't even have the container
+    when_the_chef_server 'is empty' do
+      when_the_repository 'has two data bag items' do
+        file 'data_bags/x/y.json', {}
+        file 'data_bags/x/z.json', {}
+
+        it 'knife upload of one data bag item itself succeeds' do
+          knife('upload /data_bags/x/y.json').should_succeed <<EOM
+Created /data_bags/x
+Created /data_bags/x/y.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+A\t/data_bags/x/z.json
+EOM
+        end
+      end
+    end
+
+    when_the_chef_server 'has three data bag items' do
+      data_bag 'x', { 'deleted' => {}, 'modified' => {}, 'unmodified' => {} }
+      when_the_repository 'has a modified, unmodified, added and deleted data bag item' do
+        file 'data_bags/x/added.json', {}
+        file 'data_bags/x/modified.json', { 'foo' => 'bar' }
+        file 'data_bags/x/unmodified.json', {}
+
+        it 'knife upload of the modified file succeeds' do
+          knife('upload /data_bags/x/modified.json').should_succeed <<EOM
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload of the unmodified file does nothing' do
+          knife('upload /data_bags/x/unmodified.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload of the added file succeeds' do
+          knife('upload /data_bags/x/added.json').should_succeed <<EOM
+Created /data_bags/x/added.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+M\t/data_bags/x/modified.json
+EOM
+        end
+        it 'knife upload of the deleted file does nothing' do
+          knife('upload /data_bags/x/deleted.json').should_succeed ''
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload --purge of the deleted file deletes it' do
+          knife('upload --purge /data_bags/x/deleted.json').should_succeed <<EOM
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+M\t/data_bags/x/modified.json
+A\t/data_bags/x/added.json
+EOM
+        end
+        it 'knife upload of the entire data bag uploads everything' do
+          knife('upload /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+EOM
+          knife('diff --name-status /data_bags').should_succeed <<EOM
+D\t/data_bags/x/deleted.json
+EOM
+        end
+        it 'knife upload --purge of the entire data bag uploads everything' do
+          knife('upload --purge /data_bags/x').should_succeed <<EOM
+Created /data_bags/x/added.json
+Updated /data_bags/x/modified.json
+Deleted extra entry /data_bags/x/deleted.json (purge is on)
+EOM
+          knife('diff --name-status /data_bags').should_succeed ''
+        end
+        context 'when cwd is the /data_bags directory' do
+          cwd 'data_bags'
+          it 'knife upload fails' do
+            knife('upload').should_fail "FATAL: Must specify at least one argument.  If you want to upload everything in this directory, type \"knife upload .\"\n", :stdout => /USAGE/
+          end
+          it 'knife upload --purge . uploads everything' do
+            knife('upload --purge .').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+          it 'knife upload --purge * uploads everything' do
+            knife('upload --purge *').should_succeed <<EOM
+Created x/added.json
+Updated x/modified.json
+Deleted extra entry x/deleted.json (purge is on)
+EOM
+            knife('diff --name-status /data_bags').should_succeed ''
+          end
+        end
+      end
+    end
+
+    # Cookbook upload is a funny thing ... direct cookbook upload works, but
+    # upload of a file is designed not to work at present.  Make sure that is the
+    # case.
+    when_the_chef_server 'has a cookbook' do
+      cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'z.rb' => '' }
+
+      when_the_repository 'has a modified, extra and missing file for the cookbook' do
+        file 'cookbooks/x-1.0.0/metadata.rb', 'version  "1.0.0"'
+        file 'cookbooks/x-1.0.0/y.rb', 'hi'
+
+        it 'knife upload of any individual file fails' do
+          knife('upload /cookbooks/x-1.0.0/metadata.rb').should_fail "ERROR: /cookbooks/x-1.0.0/metadata.rb cannot be updated.\n"
+          knife('upload /cookbooks/x-1.0.0/y.rb').should_fail "ERROR: /cookbooks/x-1.0.0 cannot have a child created under it.\n"
+          knife('upload --purge /cookbooks/x-1.0.0/z.rb').should_fail "ERROR: /cookbooks/x-1.0.0/z.rb cannot be deleted.\n"
+        end
+
+        # TODO this is a bit of an inconsistency: if we didn't specify --purge,
+        # technically we shouldn't have deleted missing files.  But ... cookbooks
+        # are a special case.
+        it 'knife upload of the cookbook itself succeeds' do
+          knife('upload /cookbooks/x-1.0.0').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+
+        it 'knife upload --purge of the cookbook itself succeeds' do
+          knife('upload /cookbooks/x-1.0.0').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has a missing file for the cookbook' do
+        file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+
+        it 'knife upload of the cookbook succeeds' do
+          knife('upload /cookbooks/x-1.0.0').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has an extra file for the cookbook' do
+        file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+        file 'cookbooks/x-1.0.0/z.rb', ''
+        file 'cookbooks/x-1.0.0/blah.rb', ''
+
+        it 'knife upload of the cookbook succeeds' do
+          knife('upload /cookbooks/x-1.0.0').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
+
+    when_the_repository 'has a cookbook' do
+      file 'cookbooks/x-1.0.0/metadata.rb', 'version "1.0.0"'
+      file 'cookbooks/x-1.0.0/onlyin1.0.0.rb', 'old_text'
+
+      when_the_chef_server 'has a later version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => '' }
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife upload /cookbooks uploads the local version' do
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+M\t/cookbooks/x-1.0.0/onlyin1.0.0.rb
+D\t/cookbooks/x-1.0.1
+EOM
+          knife('upload --purge /cookbooks').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0
+Deleted extra entry /cookbooks/x-1.0.1 (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook' do
+        cookbook 'x', '1.0.0', { 'metadata.rb' => 'version "1.0.0"', 'onlyin1.0.0.rb' => ''}
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+        it 'knife upload /cookbooks uploads the local version' do
+          knife('upload --purge /cookbooks').should_succeed <<EOM
+Updated /cookbooks/x-1.0.0
+Deleted extra entry /cookbooks/x-0.9.9 (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has a later version for the cookbook, and no current version' do
+        cookbook 'x', '1.0.1', { 'metadata.rb' => 'version "1.0.1"', 'onlyin1.0.1.rb' => 'hi' }
+
+        it 'knife upload /cookbooks/x uploads the local version' do
+          knife('diff --name-status /cookbooks').should_succeed <<EOM
+D\t/cookbooks/x-1.0.1
+A\t/cookbooks/x-1.0.0
+EOM
+          knife('upload --purge /cookbooks').should_succeed <<EOM
+Created /cookbooks/x-1.0.0
+Deleted extra entry /cookbooks/x-1.0.1 (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+
+      when_the_chef_server 'has an earlier version for the cookbook, and no current version' do
+        cookbook 'x', '0.9.9', { 'metadata.rb' => 'version "0.9.9"', 'onlyin0.9.9.rb' => 'hi' }
+
+        it 'knife upload /cookbooks/x uploads the new version' do
+          knife('upload --purge /cookbooks').should_succeed <<EOM
+Created /cookbooks/x-1.0.0
+Deleted extra entry /cookbooks/x-0.9.9 (purge is on)
+EOM
+          knife('diff --name-status /cookbooks').should_succeed ''
+        end
+      end
+    end
+
+    when_the_chef_server 'has an environment' do
+      environment 'x', {}
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife upload tries and fails' do
+          knife('upload /environments/x.json').should_fail "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\nERROR: /environments/x.json failed to write: Parse error reading JSON: A JSON text must at least contain two octets!\n"
+          knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n", :stderr => "WARN: Parse error reading #{path_to('environments/x.json')} as JSON: A JSON text must at least contain two octets!\n"
+        end
+      end
+
+      when_the_repository 'has the same environment with the wrong name in the file' do
+        file 'environments/x.json', { 'name' => 'y' }
+        it 'knife upload fails' do
+          knife('upload /environments/x.json').should_fail "ERROR: /environments/x.json failed to write: Name must be 'x' (is 'y')\n"
+          knife('diff --name-status /environments/x.json').should_succeed "M\t/environments/x.json\n"
+        end
+      end
+
+      when_the_repository 'has the same environment with no name in the file' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        it 'knife upload succeeds' do
+          knife('upload /environments/x.json').should_succeed "Updated /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+    end
+
+    when_the_chef_server 'is empty' do
+      when_the_repository 'has an environment with bad JSON' do
+        file 'environments/x.json', '{'
+        it 'knife upload tries and fails' do
+          knife('upload /environments/x.json').should_fail "ERROR: /environments failed to create_child: Parse error reading JSON creating child 'x.json': A JSON text must at least contain two octets!\n"
+          knife('diff --name-status /environments/x.json').should_succeed "A\t/environments/x.json\n"
+        end
+      end
+
+      when_the_repository 'has an environment with the wrong name in the file' do
+        file 'environments/x.json', { 'name' => 'y' }
+        it 'knife upload fails' do
+          knife('upload /environments/x.json').should_fail "ERROR: /environments failed to create_child: Error creating 'x.json': Name must be 'x' (is 'y')\n"
+          knife('diff --name-status /environments/x.json').should_succeed "A\t/environments/x.json\n"
+        end
+      end
+
+      when_the_repository 'has an environment with no name in the file' do
+        file 'environments/x.json', { 'description' => 'hi' }
+        it 'knife upload succeeds' do
+          knife('upload /environments/x.json').should_succeed "Created /environments/x.json\n"
+          knife('diff --name-status /environments/x.json').should_succeed ''
+        end
+      end
+
+      when_the_repository 'has a data bag with no id in the file' do
+        file 'data_bags/bag/x.json', { 'foo' => 'bar' }
+        it 'knife upload succeeds' do
+          knife('upload /data_bags/bag/x.json').should_succeed "Created /data_bags/bag\nCreated /data_bags/bag/x.json\n"
+          knife('diff --name-status /data_bags/bag/x.json').should_succeed ''
+        end
+      end
+    end
+  end # with versioned cookbooks
+
+  when_the_chef_server 'has a user' do
+    user 'x', {}
+    when_the_repository 'has the same user with json_class in it' do
+      file 'users/x.json', { 'admin' => true, 'json_class' => 'Chef::WebUIUser' }
+      it 'knife upload /users/x.json succeeds' do
+        knife('upload /users/x.json').should_succeed "Updated /users/x.json\n"
+      end
+    end
+  end
+end
diff --git a/spec/integration/solo/solo_spec.rb b/spec/integration/solo/solo_spec.rb
new file mode 100644
index 0000000..94d279c
--- /dev/null
+++ b/spec/integration/solo/solo_spec.rb
@@ -0,0 +1,112 @@
+require 'support/shared/integration/integration_helper'
+require 'chef/mixin/shell_out'
+require 'chef/run_lock'
+require 'chef/config'
+require 'timeout'
+require 'fileutils'
+
+describe "chef-solo" do
+  extend IntegrationSupport
+  include Chef::Mixin::ShellOut
+
+  let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..") }
+
+  when_the_repository "has a cookbook with a basic recipe" do
+    file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+    file 'cookbooks/x/recipes/default.rb', 'puts "ITWORKS"'
+
+    it "should complete with success" do
+      file 'config/solo.rb', <<EOM
+cookbook_path "#{path_to('cookbooks')}"
+file_cache_path "#{path_to('config/cache')}"
+EOM
+      result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
+      result.error!
+      result.stdout.should include("ITWORKS")
+    end
+
+    it "should evaluate its node.json file" do
+      file 'config/solo.rb', <<EOM
+cookbook_path "#{path_to('cookbooks')}"
+file_cache_path "#{path_to('config/cache')}"
+EOM
+
+      file 'config/node.json',<<-E
+{"run_list":["x::default"]}
+E
+
+      result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -j '#{path_to('config/node.json')}' -l debug", :cwd => chef_dir)
+      result.error!
+      result.stdout.should include("ITWORKS")
+    end
+
+  end
+
+  when_the_repository "has a cookbook with a recipe with sleep" do
+    directory 'logs'
+    file 'logs/runs.log', ''
+    file 'cookbooks/x/metadata.rb', 'version "1.0.0"'
+    file 'cookbooks/x/recipes/default.rb', <<EOM
+ruby_block "sleeping" do
+  block do
+    sleep 5
+  end
+end
+EOM
+    # Ruby 1.8.7 doesn't have Process.spawn :(
+    it "while running solo concurrently", :ruby_gte_19_only => true do
+      file 'config/solo.rb', <<EOM
+cookbook_path "#{path_to('cookbooks')}"
+file_cache_path "#{path_to('config/cache')}"
+EOM
+      # We have a timeout protection here so that if due to some bug
+      # run_lock gets stuck we can discover it.
+      lambda {
+        Timeout.timeout(120) do
+          chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..")
+
+          # Instantiate the first chef-solo run
+          s1 = Process.spawn("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' \
+-l debug -L #{path_to('logs/runs.log')}", :chdir => chef_dir)
+
+          # Give it some time to progress
+          sleep 1
+
+          # Instantiate the second chef-solo run
+          s2 = Process.spawn("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' \
+-l debug -L #{path_to('logs/runs.log')}", :chdir => chef_dir)
+
+          Process.waitpid(s1)
+          Process.waitpid(s2)
+        end
+      }.should_not raise_error(Timeout::Error)
+
+      # Unfortunately file / directory helpers in integration tests
+      # are implemented using before(:each) so we need to do all below
+      # checks in one example.
+      run_log = File.read(path_to('logs/runs.log'))
+
+      # both of the runs should succeed
+      run_log.lines.reject {|l| !l.include? "INFO: Chef Run complete in"}.length.should == 2
+
+      # second run should have a message which indicates it's waiting for the first run
+      pid_lines = run_log.lines.reject {|l| !l.include? "Chef-client pid:"}
+      pid_lines.length.should == 2
+      pids = pid_lines.map {|l| l.split(" ").last}
+      run_log.should include("Chef client #{pids[0]} is running, will wait for it to finish and then run.")
+
+      # second run should start after first run ends
+      starts = [ ]
+      ends = [ ]
+      run_log.lines.each_with_index do |line, index|
+        if line.include? "Chef-client pid:"
+          starts << index
+        elsif line.include? "INFO: Chef Run complete in"
+          ends << index
+        end
+      end
+      starts[1].should > ends[0]
+    end
+
+  end
+end
diff --git a/spec/rcov.opts b/spec/rcov.opts
new file mode 100644
index 0000000..484626e
--- /dev/null
+++ b/spec/rcov.opts
@@ -0,0 +1,2 @@
+--exclude
+spec,bin,/Library/Ruby
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 0000000..618f782
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,179 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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.
+
+# If you need to add anything in here, don't.
+# Add it to one of the files in spec/support
+
+# Configure this first so it doesn't trigger annoying warning when we use it.
+# Main rspec configuration comes later
+RSpec.configure do |config|
+  config.treat_symbols_as_metadata_keys_with_true_values = true
+end
+
+# Abuse ruby's constant lookup to avoid undefined constant errors
+module Shell
+  JUST_TESTING_MOVE_ALONG = true unless defined? JUST_TESTING_MOVE_ALONG
+  IRB = nil unless defined? IRB
+end
+
+# Ruby 1.9 Compat
+$:.unshift File.expand_path("../..", __FILE__)
+
+require 'rubygems'
+require 'rspec/mocks'
+
+$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
+$:.unshift(File.expand_path("../lib", __FILE__))
+$:.unshift(File.dirname(__FILE__))
+
+if ENV["COVERAGE"]
+  require 'simplecov'
+  SimpleCov.start do
+    add_filter "/spec/"
+    add_group "Remote File", "remote_file"
+    add_group "Resources", "/resource/"
+    add_group "Providers", "/provider/"
+    add_group "Knife", "knife"
+  end
+end
+
+require 'chef'
+require 'chef/knife'
+
+Dir['lib/chef/knife/**/*.rb'].
+  map {|f| f.gsub('lib/', '') }.
+  map {|f| f.gsub(%r[\.rb$], '') }.
+  each {|f| require f }
+
+require 'chef/mixins'
+require 'chef/dsl'
+require 'chef/application'
+require 'chef/applications'
+
+require 'chef/shell'
+require 'chef/util/file_edit'
+
+require 'chef/config'
+
+# If you want to load anything into the testing environment
+# without versioning it, add it to spec/support/local_gems.rb
+require 'spec/support/local_gems.rb' if File.exists?(File.join(File.dirname(__FILE__), 'support', 'local_gems.rb'))
+
+# Explicitly require spec helpers that need to load first
+require 'spec/support/platform_helpers'
+
+# Autoloads support files
+# Excludes support/platforms by default
+# Do not change the gsub.
+Dir["spec/support/**/*.rb"].
+  reject { |f| f =~ %r{^spec/support/platforms} }.
+  map { |f| f.gsub(%r{.rb$}, '') }.
+  map { |f| f.gsub(%r[spec/], '')}.
+  each { |f| require f }
+
+
+OHAI_SYSTEM = Ohai::System.new
+OHAI_SYSTEM.require_plugin("os")
+OHAI_SYSTEM.require_plugin("platform")
+TEST_PLATFORM = OHAI_SYSTEM["platform"].dup.freeze
+TEST_PLATFORM_VERSION = OHAI_SYSTEM["platform_version"].dup.freeze
+
+RSpec.configure do |config|
+  config.include(Matchers)
+  config.filter_run :focus => true
+  config.filter_run_excluding :external => true
+
+  # Tests that randomly fail, but may have value.
+  config.filter_run_excluding :volatile => true
+
+  # Add jruby filters here
+  config.filter_run_excluding :windows_only => true unless windows?
+  config.filter_run_excluding :not_supported_on_win2k3 => true if windows_win2k3?
+  config.filter_run_excluding :not_supported_on_solaris => true if solaris?
+  config.filter_run_excluding :win2k3_only => true unless windows_win2k3?
+  config.filter_run_excluding :windows64_only => true unless windows64?
+  config.filter_run_excluding :windows32_only => true unless windows32?
+  config.filter_run_excluding :system_windows_service_gem_only => true unless system_windows_service_gem?
+  config.filter_run_excluding :unix_only => true unless unix?
+  # Remove this filter once these issues are fixed: OC-9764, OC-9765, OC-9766, OC-9767
+  config.filter_run_excluding :unsupported_group_provider_platform => true if (os_x? or solaris? or freebsd? or suse?)
+  config.filter_run_excluding :supports_cloexec => true unless supports_cloexec?
+  config.filter_run_excluding :selinux_only => true unless selinux_enabled?
+  config.filter_run_excluding :ruby_18_only => true unless ruby_18?
+  config.filter_run_excluding :ruby_19_only => true unless ruby_19?
+  config.filter_run_excluding :ruby_gte_19_only => true unless ruby_gte_19?
+  config.filter_run_excluding :ruby_20_only => true unless ruby_20?
+  config.filter_run_excluding :ruby_gte_20_only => true unless ruby_gte_20?
+  config.filter_run_excluding :requires_root => true unless ENV['USER'] == 'root' || ENV['LOGIN'] == 'root'
+  config.filter_run_excluding :requires_root_or_running_windows => true unless (ENV['USER'] == 'root' or windows?)
+  config.filter_run_excluding :requires_unprivileged_user => true if ENV['USER'] == 'root'
+  config.filter_run_excluding :uses_diff => true unless has_diff?
+
+  running_platform_arch = `uname -m`.strip
+
+  config.filter_run_excluding :arch => lambda {|target_arch|
+    running_platform_arch != target_arch
+  }
+
+  # Functional Resource tests that are provider-specific:
+  # context "on platforms that use useradd", :provider => {:user => Chef::Provider::User::Useradd}} do #...
+  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
+      true
+    end
+  }
+
+  config.run_all_when_everything_filtered = true
+  config.treat_symbols_as_metadata_keys_with_true_values = true
+
+  config.before(:each) do
+    Chef::Config.reset
+  end
+end
+
+require 'webrick/utils'
+
+#    Webrick uses a centralized/synchronized timeout manager. It works by
+#    starting a thread to check for timeouts on an interval. The timeout
+#    checker thread cannot be stopped or canceled in any easy way, and it
+#    makes calls to Time.new, which fail when rspec is in the process of
+#    creating a method stub for that method. Since our tests don't rely on
+#    any timeout behavior enforced by webrick, disable the timeout manager
+#    via a monkey patch.
+#
+#    Hopefully this fails loudly if the webrick code should change. As of this
+#    writing, the relevant code is in webrick/utils, which can be located on
+#    your system with:
+#
+#    $ gem which webrick/utils
+module WEBrick
+  module Utils
+    class TimeoutHandler
+      def initialize
+        @timeout_info = Hash.new
+      end
+    end
+  end
+end
diff --git a/spec/stress/win32/file_spec.rb b/spec/stress/win32/file_spec.rb
new file mode 100644
index 0000000..ef01aa6
--- /dev/null
+++ b/spec/stress/win32/file_spec.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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/win32/file' if windows?
+
+describe 'Chef::ReservedNames::Win32::File', :windows_only do
+  before(:each) do
+    @path = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "data", "old_home_dir", "my-dot-emacs"))
+  end
+
+  it "should not leak memory" do
+    test = lambda { Chef::ReservedNames::Win32::File.symlink?(@path) }
+    test.should_not leak_memory(:warmup => 50, :iterations => 100)
+  end
+
+  it "should not leak handles" do
+    test = lambda { Chef::ReservedNames::Win32::File.symlink?(@path) }
+    test.should_not leak_handles(:warmup => 50, :iterations => 100)
+  end
+
+end
diff --git a/spec/stress/win32/memory_spec.rb b/spec/stress/win32/memory_spec.rb
new file mode 100644
index 0000000..ed3ad30
--- /dev/null
+++ b/spec/stress/win32/memory_spec.rb
@@ -0,0 +1,22 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe 'Chef::ReservedNames::Win32::Memory', :windows_only do
+end
diff --git a/spec/stress/win32/security_spec.rb b/spec/stress/win32/security_spec.rb
new file mode 100644
index 0000000..e506b71
--- /dev/null
+++ b/spec/stress/win32/security_spec.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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'
+
+if windows?
+  require 'chef/win32/security'
+  require 'tmpdir'
+  require 'fileutils'
+end
+
+describe 'Chef::ReservedNames::Win32::Security', :windows_only do
+
+  def monkeyfoo
+    File.join(CHEF_SPEC_DATA, "monkeyfoo").gsub("/", "\\")
+  end
+
+  before :all do
+    @test_tempdir = File.join(Dir::tmpdir, "cheftests", "chef_win32_security")
+    FileUtils.mkdir_p(@test_tempdir)
+    @monkeyfoo = File.join(@test_tempdir, "monkeyfoo.txt")
+  end
+
+  before :each do
+    File.delete(@monkeyfoo) if File.exist?(@monkeyfoo)
+    # Make a file.
+    File.open(@monkeyfoo, "w") do |file|
+      file.write("hi")
+    end
+  end
+
+  after :all do
+    FileUtils.rm_rf(@test_tempdir)
+  end
+
+  it "should not leak when retrieving and reading the ACE from a file", :volatile do
+    lambda {
+      sids = Chef::ReservedNames::Win32::Security::SecurableObject.new(@monkeyfoo).security_descriptor.dacl.select { |ace| ace.sid }
+      GC.start
+    }.should_not leak_memory(:warmup => 50, :iterations => 100)
+  end
+
+  it "should not leak when creating a new ACL and setting it on a file", :volatile do
+    securable_object = Security::SecurableObject.new(@monkeyfoo)
+    lambda {
+      securable_object.dacl = Chef::ReservedNames::Win32::Security::ACL.create([
+        Chef::ReservedNames::Win32::Security::ACE.access_allowed(Chef::ReservedNames::Win32::Security::SID.Everyone, Chef::ReservedNames::Win32::API::Security::GENERIC_READ),
+        Chef::ReservedNames::Win32::Security::ACE.access_denied(Chef::ReservedNames::Win32::Security::SID.from_account("Users"), Chef::ReservedNames::Win32::API::Security::GENERIC_ALL)
+      ])
+      GC.start
+    }.should_not leak_memory(:warmup => 50, :iterations => 100)
+  end
+
+end
diff --git a/spec/support/chef_helpers.rb b/spec/support/chef_helpers.rb
new file mode 100644
index 0000000..542bee2
--- /dev/null
+++ b/spec/support/chef_helpers.rb
@@ -0,0 +1,95 @@
+# 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.
+#
+CHEF_SPEC_DATA = File.expand_path(File.dirname(__FILE__) + "/../data/")
+CHEF_SPEC_BACKUP_PATH = File.join(Dir.tmpdir, 'test-backup-path')
+
+Chef::Config[:log_level] = :fatal
+Chef::Config[:persistent_queue] = false
+Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+
+Chef::Log.level(Chef::Config.log_level)
+Chef::Config.solo(false)
+
+Chef::Log.logger = Logger.new(StringIO.new)
+
+def sha256_checksum(path)
+  Digest::SHA256.hexdigest(File.read(path))
+end
+
+# From Ruby 1.9.2+
+# Here for backwards compatibility with Ruby 1.8.7
+# http://rubydoc.info/stdlib/tmpdir/1.9.2/Dir/Tmpname
+def make_tmpname(prefix_suffix, n = nil)
+  case prefix_suffix
+  when String
+    prefix = prefix_suffix
+    suffix = ""
+  when Array
+    prefix = prefix_suffix[0]
+    suffix = prefix_suffix[1]
+  else
+    raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
+  end
+  t = Time.now.strftime("%Y%m%d")
+  path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
+  path << "-#{n}" if n
+  path << suffix
+end
+
+# NOTE:
+# This is a temporary fix to get tests passing on systems that have no `diff`
+# until we can replace shelling out to `diff` with ruby diff-lcs
+def has_diff?
+  begin
+    diff_cmd = Mixlib::ShellOut.new("diff -v")
+    diff_cmd.run_command
+    true
+  rescue Errno::ENOENT
+    false
+  end
+end
+
+# This is a helper to determine if the ruby in the PATH contains
+# win32/service gem. windows_service_manager tests create a windows
+# service that starts with the system ruby and requires this gem.
+def system_windows_service_gem?
+  windows_service_gem_check_command = "ruby -e 'require \"win32/daemon\"' > /dev/null 2>&1"
+  if defined?(Bundler)
+    Bundler.with_clean_env do
+      # This returns true if the gem can be loaded
+      system windows_service_gem_check_command
+    end
+  else
+    # This returns true if the gem can be loaded
+    system windows_service_gem_check_command
+  end
+end
+
+# This is a helper to canonicalize paths that we're using in the file
+# tests.
+def canonicalize_path(path)
+  windows? ? path.gsub('/', '\\') : path
+end
+
+# Check if a cmd exists on the PATH
+def which(cmd)
+  paths = ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/bin', '/usr/bin', '/sbin', '/usr/sbin' ]
+  paths.each do |path|
+    filename = File.join(path, cmd)
+    return filename if File.executable?(filename)
+  end
+  false
+end
diff --git a/spec/support/lib/chef/provider/easy.rb b/spec/support/lib/chef/provider/easy.rb
new file mode 100644
index 0000000..a4d285a
--- /dev/null
+++ b/spec/support/lib/chef/provider/easy.rb
@@ -0,0 +1,35 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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.
+#
+
+class Chef
+  class Provider
+    class Easy < Chef::Provider
+      def load_current_resource
+        true
+      end
+
+      def action_sell
+        true
+      end
+
+      def action_buy
+        true
+      end
+    end
+  end
+end
diff --git a/spec/support/lib/chef/provider/snakeoil.rb b/spec/support/lib/chef/provider/snakeoil.rb
new file mode 100644
index 0000000..e9d01f6
--- /dev/null
+++ b/spec/support/lib/chef/provider/snakeoil.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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.
+#
+
+class Chef
+  class Provider
+    class SnakeOil < Chef::Provider
+      def load_current_resource
+        true
+      end
+
+      def action_purr
+        @new_resource.updated_by_last_action(true)
+        true
+      end
+
+      def action_sell
+        true
+      end
+
+      def action_buy
+        true
+      end
+    end
+  end
+end
diff --git a/spec/support/lib/chef/resource/cat.rb b/spec/support/lib/chef/resource/cat.rb
new file mode 100644
index 0000000..ecca50c
--- /dev/null
+++ b/spec/support/lib/chef/resource/cat.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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.
+#
+
+class Chef
+  class Resource
+    class Cat < Chef::Resource
+
+      attr_accessor :action
+
+      def initialize(name, run_context=nil)
+        @resource_name = :cat
+        super
+        @action = "sell"
+      end
+
+      def pretty_kitty(arg=nil)
+        if arg == true or arg == false
+          @pretty_kitty = arg
+        end
+        @pretty_kitty
+      end
+    end
+  end
+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
new file mode 100644
index 0000000..296d2cd
--- /dev/null
+++ b/spec/support/lib/chef/resource/one_two_three_four.rb
@@ -0,0 +1,41 @@
+#
+# Author:: John Hampton (<john at cleanoffer.com>)
+# Copyright:: Copyright (c) 2009 CleanOffer, 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 Resource
+    class OneTwoThreeFour < Chef::Resource
+      attr_reader :i_can_count
+
+      def initialize(name, run_context)
+        @resource_name = :one_two_three_four
+        super
+      end
+
+      def i_can_count(tf)
+        @i_can_count = tf
+      end
+
+      def something(arg=nil)
+        if arg == true or arg == false
+          @something = arg
+        end
+        @something
+      end
+    end
+  end
+end
diff --git a/spec/support/lib/chef/resource/with_state.rb b/spec/support/lib/chef/resource/with_state.rb
new file mode 100644
index 0000000..226de0a
--- /dev/null
+++ b/spec/support/lib/chef/resource/with_state.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require '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
+    end
+  end
+end
diff --git a/spec/support/lib/chef/resource/zen_master.rb b/spec/support/lib/chef/resource/zen_master.rb
new file mode 100644
index 0000000..7a7076c
--- /dev/null
+++ b/spec/support/lib/chef/resource/zen_master.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/json_compat'
+
+class Chef
+  class Resource
+    class ZenMaster < Chef::Resource
+      attr_reader :peace
+
+      def initialize(name, run_context=nil)
+        @resource_name = :zen_master
+        super
+      end
+
+      def peace(tf)
+        @peace = tf
+      end
+
+      def something(arg=nil)
+        if arg == true or arg == false
+          @something = arg
+        end
+        @something
+      end
+    end
+  end
+end
diff --git a/spec/support/lib/library_load_order.rb b/spec/support/lib/library_load_order.rb
new file mode 100644
index 0000000..c47a2f2
--- /dev/null
+++ b/spec/support/lib/library_load_order.rb
@@ -0,0 +1,22 @@
+# Helper module to track the load order of library files.
+# Used by `cookbook_compiler_spec.rb`
+#
+# This module must be loaded for any tests that load the cookbook
+# data/run_context/cookbooks/test to succeed.
+module LibraryLoadOrder
+  extend self
+
+  def load_order
+    @load_order ||= []
+  end
+
+  def reset!
+    @load_order = nil
+  end
+
+  def record(file)
+    load_order << file
+  end
+end
+
+
diff --git a/spec/support/matchers/leak.rb b/spec/support/matchers/leak.rb
new file mode 100644
index 0000000..908770f
--- /dev/null
+++ b/spec/support/matchers/leak.rb
@@ -0,0 +1,96 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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.
+#
+
+module Matchers
+  module LeakBase
+    include RSpec::Matchers
+
+    def initialize(opts={}, &block)
+      @warmup = opts[:warmup] || 5
+      @iterations = opts[:iterations] || 100
+      @variance = opts[:variance] || 1000
+    end
+
+    def failure_message_for_should
+      "expected final measure [#{@final_measure}] to be greater than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]"
+    end
+
+    def failure_message_for_should_not
+      "expected final measure [#{@final_measure}] to be less than or within +/- #{@variance} delta of initial measure [#{@initial_measure}]"
+    end
+
+    private
+    def match(measure, given_proc)
+      profiler.start
+
+      @initial_measure = 0
+      @final_measure = 0
+
+      @warmup.times do
+        given_proc.call
+      end
+
+      @initial_measure = profiler.send(measure)
+
+      @iterations.times do
+        given_proc.call
+      end
+
+      profiler.stop
+
+      @final_measure = profiler.send(measure)
+      @final_measure > (@initial_measure + @variance)
+    end
+
+    def profiler
+      @profiler ||= begin
+        if Chef::Platform.windows?
+          require File.join(File.dirname(__FILE__), '..', 'platforms', 'prof', 'win32')
+          RSpec::Prof::Win32::Profiler.new
+        else
+          require File.join(File.dirname(__FILE__), '..', 'prof', 'gc')
+          RSpec::Prof::GC::Profiler.new
+        end
+      end
+    end
+
+  end
+
+  class LeakMemory
+    include LeakBase
+
+    def matches?(given_proc)
+      match(:working_set_size, given_proc)
+    end
+  end
+
+  class LeakHandles
+    include LeakBase
+
+    def matches?(given_proc)
+      match(:handle_count, given_proc)
+    end
+  end
+
+  def leak_memory(opts, &block)
+    Matchers::LeakMemory.new(opts, &block)
+  end
+  def leak_handles(opts, &block)
+    Matchers::LeakHandles.new(opts, &block)
+  end
+end
diff --git a/spec/support/mock/constant.rb b/spec/support/mock/constant.rb
new file mode 100644
index 0000000..c706ad2
--- /dev/null
+++ b/spec/support/mock/constant.rb
@@ -0,0 +1,52 @@
+# Allows easy mocking of global and class constants
+
+# Inspired by:
+# http://missingbit.blogspot.com/2011/07/stubbing-constants-in-rspec_20.html
+# http://digitaldumptruck.jotabout.com/?p=551
+
+def mock_constants(constants, &block)
+  saved_constants = {}
+  constants.each do |constant, val|
+    source_object, const_name = parse_constant(constant)
+    saved_constants[constant] = source_object.const_get(const_name)
+    with_warnings(nil) {source_object.const_set(const_name, val) }
+  end
+
+  begin
+    block.call
+  ensure
+    constants.each do |constant, val|
+      source_object, const_name = parse_constant(constant)
+      with_warnings(nil) { source_object.const_set(const_name, saved_constants[constant]) }
+    end
+  end
+end
+
+def parse_constant(constant)
+  source, _, constant_name = constant.to_s.rpartition('::')
+  [constantize(source), constant_name]
+end
+
+# Taken from ActiveSupport
+
+# File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 3
+#
+# Sets $VERBOSE for the duration of the block and back to its original value afterwards.
+def with_warnings(flag)
+  old_verbose, $VERBOSE = $VERBOSE, flag
+  yield
+ensure
+  $VERBOSE = old_verbose
+end
+
+# File activesupport/lib/active_support/inflector/methods.rb, line 209
+def constantize(camel_cased_word)
+  names = camel_cased_word.split('::')
+  names.shift if names.empty? || names.first.empty?
+
+  constant = Object
+  names.each do |name|
+    constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
+  end
+  constant
+end
diff --git a/spec/support/mock/platform.rb b/spec/support/mock/platform.rb
new file mode 100644
index 0000000..78b704e
--- /dev/null
+++ b/spec/support/mock/platform.rb
@@ -0,0 +1,18 @@
+# makes Chef think it's running on a certain platform..useful for unit testing
+# platform-specific functionality.
+#
+# If a block is given yields to the block with +RUBY_PLATFORM+ set to
+# 'i386-mingw32' (windows) or 'x86_64-darwin11.2.0' (unix).  Usueful for
+# testing code that mixes in platform specific modules like +Chef::Mixin::Securable+
+# or +Chef::FileAccessControl+
+def platform_mock(platform = :unix, &block)
+  Chef::Platform.stub!(:windows?).and_return(platform == :windows ? true : false)
+  ENV['SYSTEMDRIVE'] = (platform == :windows ? 'C:' : nil)
+  if block_given?
+    mock_constants({"RUBY_PLATFORM" => (platform == :windows ? 'i386-mingw32' : 'x86_64-darwin11.2.0'),
+                    "File::PATH_SEPARATOR" => (platform == :windows ? ";" : ":"),
+                    "File::ALT_SEPARATOR" => (platform == :windows ? "\\" : nil) }) do
+yield
+    end
+  end
+end
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
new file mode 100644
index 0000000..1501a16
--- /dev/null
+++ b/spec/support/platform_helpers.rb
@@ -0,0 +1,97 @@
+require 'fcntl'
+
+def ruby_gte_20?
+  RUBY_VERSION.to_f >= 2.0
+end
+
+def ruby_gte_19?
+  RUBY_VERSION.to_f >= 1.9
+end
+
+def ruby_20?
+  !!(RUBY_VERSION =~ /^2.0/)
+end
+
+def ruby_19?
+  !!(RUBY_VERSION =~ /^1.9/)
+end
+
+def ruby_18?
+  !!(RUBY_VERSION =~ /^1.8/)
+end
+
+def windows?
+  !!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
+end
+
+def windows_win2k3?
+  return false unless windows?
+  require 'ruby-wmi'
+
+  host = WMI::Win32_OperatingSystem.find(:first)
+  (host.version && host.version.start_with?("5.2"))
+end
+
+# detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system)
+def windows64?
+  windows? && ( ENV['PROCESSOR_ARCHITECTURE'] == 'AMD64' || ENV['PROCESSOR_ARCHITEW6432'] == 'AMD64' )
+end
+
+# detects if the hardware is 32-bit
+def windows32?
+  windows? && !windows64?
+end
+
+# def jruby?
+
+def unix?
+  !windows?
+end
+
+def os_x?
+  !!(RUBY_PLATFORM =~ /darwin/)
+end
+
+def solaris?
+  !!(RUBY_PLATFORM =~ /solaris/)
+end
+
+def freebsd?
+  !!(RUBY_PLATFORM =~ /freebsd/)
+end
+
+def aix?
+  !!(RUBY_PLATFORM =~ /aix/)
+end
+
+def supports_cloexec?
+  Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC')
+end
+
+DEV_NULL = windows? ? 'NUL' : '/dev/null'
+
+def selinux_enabled?
+  # This code is currently copied from lib/chef/util/selinux to make
+  # specs independent of product.
+  selinuxenabled_path = which("selinuxenabled")
+  if selinuxenabled_path
+    cmd = Mixlib::ShellOut.new(selinuxenabled_path, :returns => [0,1])
+    cmd_result = cmd.run_command
+    case cmd_result.exitstatus
+    when 1
+      return false
+    when 0
+      return true
+    else
+      raise RuntimeError, "Unknown exit code from command #{selinuxenabled_path}: #{cmd.exitstatus}"
+    end
+  else
+    # We assume selinux is not enabled if selinux utils are not
+    # installed.
+    return false
+  end
+end
+
+def suse?
+  File.exists?("/etc/SuSE-release")
+end
diff --git a/spec/support/platforms/prof/gc.rb b/spec/support/platforms/prof/gc.rb
new file mode 100644
index 0000000..6ca50df
--- /dev/null
+++ b/spec/support/platforms/prof/gc.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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.
+#
+
+module RSpec
+  module Prof
+    module GC
+      class Profiler
+
+        # GC 1 invokes.
+        # Index    Invoke Time(sec)       Use Size(byte)     Total Size(byte)         Total Object                    GC time(ms)
+        #     1               0.012               159240               212940                10647         0.00000000000001530000
+        LINE_PATTERN = /^\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)\s+([\d\.]*)$/
+
+        def start
+          ::GC::Profiler.enable unless ::GC::Profiler.enabled?
+        end
+
+        def stop
+          ::GC::Profiler.disable
+        end
+
+        def working_set_size
+          begin
+            ::GC.start
+            ::GC::Profiler.result.scan(LINE_PATTERN)[-1][2].to_i if ::GC::Profiler.enabled?
+          ensure
+            ::GC::Profiler.clear
+          end
+        end
+
+        def handle_count
+          0
+        end
+
+      end
+    end
+  end
+end
+
diff --git a/spec/support/platforms/prof/win32.rb b/spec/support/platforms/prof/win32.rb
new file mode 100644
index 0000000..ab256ff
--- /dev/null
+++ b/spec/support/platforms/prof/win32.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/process'
+
+module RSpec
+  module Prof
+    module Win32
+      class Profiler
+
+        def start
+          GC.start
+        end
+
+        def stop
+          GC.start
+        end
+
+        def working_set_size
+          Chef::ReservedNames::Win32::Process.get_current_process.memory_info[:WorkingSetSize]
+        end
+
+        def handle_count
+          Chef::ReservedNames::Win32::Process.get_current_process.handle_count
+        end
+      end
+
+    end
+  end
+end
+
diff --git a/spec/support/platforms/win32/spec_service.rb b/spec/support/platforms/win32/spec_service.rb
new file mode 100644
index 0000000..3e1f6c3
--- /dev/null
+++ b/spec/support/platforms/win32/spec_service.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Serdar Sutay (<serdar at lambda.local>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'win32/daemon'
+
+class SpecService < ::Win32::Daemon
+  def service_init
+    @test_service_file = "#{ENV['TMP']}/spec_service_file"
+  end
+
+  def service_main(*startup_parameters)
+    while running? do
+      if !File.exists?(@test_service_file)
+        File.open(@test_service_file, 'wb') do |f|
+          f.write("This file is created by SpecService")
+        end
+      end
+
+      sleep 1
+    end
+  end
+
+  ################################################################################
+  # Control Signal Callback Methods
+  ################################################################################
+
+  def service_stop
+  end
+
+  def service_pause
+  end
+
+  def service_resume
+  end
+
+  def service_shutdown
+  end
+end
+
+# To run this file as a service, it must be called as a script from within
+# the Windows Service framework.  In that case, kick off the main loop!
+if __FILE__ == $0
+  SpecService.mainloop
+end
diff --git a/spec/support/shared/functional/diff_disabled.rb b/spec/support/shared/functional/diff_disabled.rb
new file mode 100644
index 0000000..7ee9808
--- /dev/null
+++ b/spec/support/shared/functional/diff_disabled.rb
@@ -0,0 +1,10 @@
+
+shared_context "diff disabled"  do
+  before do
+    Chef::Config[:diff_disabled] = true
+  end
+
+  after do
+    Chef::Config[:diff_disabled] = false
+  end
+end
diff --git a/spec/support/shared/functional/directory_resource.rb b/spec/support/shared/functional/directory_resource.rb
new file mode 100644
index 0000000..ee90854
--- /dev/null
+++ b/spec/support/shared/functional/directory_resource.rb
@@ -0,0 +1,176 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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.
+#
+
+shared_examples_for "a directory resource" do
+
+  include_context "diff disabled"
+
+  let(:expect_updated?) {true}
+
+  context "when the target directory does not exist" do
+    before do
+      # assert pre-condition
+      File.should_not exist(path)
+    end
+
+    describe "when running action :create" do
+      context "and the recursive option is not set" do
+        before do
+          resource.run_action(:create)
+        end
+
+        it "creates the directory when the :create action is run" do
+          File.should exist(path)
+        end
+
+        it "is marked updated by last action" do
+          resource.should be_updated_by_last_action
+        end
+      end
+
+      context "and the recursive option is set" do
+        before do
+          File.should_not exist(path)
+
+          resource.recursive(true)
+          @recursive_path = File.join(path, 'red-headed-stepchild')
+          resource.path(@recursive_path)
+          resource.run_action(:create)
+        end
+
+        it "recursively creates required directories" do
+          File.should exist(path)
+          File.should exist(@recursive_path)
+        end
+
+        it "is marked updated by last action" do
+          resource.should be_updated_by_last_action
+        end
+      end
+    end
+
+    # Set up the context for security tests
+    def allowed_acl(sid, expected_perms)
+      [
+       ACE.access_allowed(sid, expected_perms[:specific]),
+       ACE.access_allowed(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE))
+      ]
+    end
+
+    def denied_acl(sid, expected_perms)
+      [
+       ACE.access_denied(sid, expected_perms[:specific]),
+       ACE.access_denied(sid, expected_perms[:generic], (Chef::ReservedNames::Win32::API::Security::INHERIT_ONLY_ACE | Chef::ReservedNames::Win32::API::Security::CONTAINER_INHERIT_ACE | Chef::ReservedNames::Win32::API::Security::OBJECT_INHERIT_ACE))
+      ]
+    end
+
+    def parent_inheritable_acls
+      dummy_directory_path = File.join(test_file_dir, "dummy_directory")
+      dummy_directory = FileUtils.mkdir_p(dummy_directory_path)
+      dummy_desc = get_security_descriptor(dummy_directory_path)
+      FileUtils.rm_rf(dummy_directory_path)
+      dummy_desc
+    end
+
+    it_behaves_like "a securable resource without existing target"
+  end
+
+  context "when the target directory exists" do
+    before(:each) do
+      # For resources such as remote_directory, simply creating the base
+      # directory isn't enough to test that the system is in the desired state,
+      # so we run the resource twice--otherwise the updated_by_last_action test
+      # will fail.
+      resource.dup.run_action(:create)
+      File.should exist(path)
+
+      resource.run_action(:create)
+    end
+
+    describe "when running action :create" do
+      before do
+        resource.run_action(:create)
+      end
+
+      it "does not re-create the directory" do
+        File.should exist(path)
+      end
+
+      it "is not marked updated by last action" do
+        resource.should_not be_updated_by_last_action
+      end
+    end
+
+    describe "when running action :delete" do
+      context "without the recursive option" do
+        before do
+          resource.run_action(:delete)
+        end
+
+        it "deletes the directory" do
+          File.should_not exist(path)
+        end
+
+        it "is marked as updated by last action" do
+          resource.should be_updated_by_last_action
+        end
+      end
+
+      context "with the recursive option" do
+        before do
+          FileUtils.mkdir(File.join(path, 'red-headed-stepchild'))
+          resource.recursive(true)
+          resource.run_action(:delete)
+        end
+
+        it "recursively deletes directories" do
+          File.should_not exist(path)
+        end
+      end
+    end
+  end
+
+end
+
+shared_context Chef::Resource::Directory do
+  # We create the  directory than tmp to exercise different file
+  # deployment strategies more completely.
+  let(:test_file_dir) do
+    if windows?
+      File.join(ENV['systemdrive'], "test-dir")
+    else
+      File.join(CHEF_SPEC_DATA, "test-dir")
+    end
+  end
+
+  let(:path) do
+    File.join(test_file_dir, make_tmpname(directory_base))
+  end
+
+  before do
+    FileUtils::mkdir_p(test_file_dir)
+  end
+
+  after do
+    FileUtils::rm_rf(test_file_dir)
+  end
+
+  after(:each) do
+    FileUtils.rm_r(path) if File.exists?(path)
+  end
+end
diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb
new file mode 100644
index 0000000..4404859
--- /dev/null
+++ b/spec/support/shared/functional/file_resource.rb
@@ -0,0 +1,950 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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.
+#
+
+shared_context "deploying with move" do
+  before do
+    Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+    Chef::Config[:file_atomic_update] = true
+  end
+end
+
+shared_context "deploying with copy" do
+  before do
+    Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+    Chef::Config[:file_atomic_update] = false
+  end
+end
+
+shared_context "deploying via tmpdir" do
+  before do
+    Chef::Config[:file_staging_uses_destdir] = false
+    Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+  end
+end
+
+shared_context "deploying via destdir" do
+  before do
+    Chef::Config[:file_staging_uses_destdir] = true
+    Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+  end
+end
+
+
+shared_examples_for "a file with the wrong content" do
+  before do
+    # Assert starting state is as expected
+    File.should exist(path)
+    # Kinda weird, in this case @expected_checksum is the cksum of the file
+    # with incorrect content.
+    sha256_checksum(path).should == @expected_checksum
+  end
+
+  include_context "diff disabled"
+
+  context "when running action :create" do
+    context "with backups enabled" do
+      before do
+        resource.run_action(:create)
+      end
+
+      it "overwrites the file with the updated content when the :create action is run" do
+        File.stat(path).mtime.should > @expected_mtime
+        sha256_checksum(path).should_not == @expected_checksum
+      end
+
+      it "backs up the existing file" do
+        Dir.glob(backup_glob).size.should equal(1)
+      end
+
+      it "is marked as updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+
+      it "should restore the security contexts on selinux", :selinux_only do
+        selinux_security_context_restored?(path).should be_true
+      end
+    end
+
+    context "with backups disabled" do
+      before do
+        resource.backup(0)
+        resource.run_action(:create)
+      end
+
+      it "should not attempt to backup the existing file if :backup == 0" do
+        Dir.glob(backup_glob).size.should equal(0)
+      end
+
+      it "should restore the security contexts on selinux", :selinux_only do
+        selinux_security_context_restored?(path).should be_true
+      end
+    end
+  end
+
+  describe "when running action :create_if_missing" do
+    before do
+      resource.run_action(:create_if_missing)
+    end
+
+    it "doesn't overwrite the file when the :create_if_missing action is run" do
+      File.stat(path).mtime.should == @expected_mtime
+      sha256_checksum(path).should == @expected_checksum
+    end
+
+    it "is not marked as updated" do
+      resource.should_not be_updated_by_last_action
+    end
+
+    it "should restore the security contexts on selinux", :selinux_only do
+      selinux_security_context_restored?(path).should be_true
+    end
+  end
+
+  describe "when running action :delete" do
+    before do
+      resource.run_action(:delete)
+    end
+
+    it "deletes the file" do
+      File.should_not exist(path)
+    end
+
+    it "is marked as updated by last action" do
+      resource.should be_updated_by_last_action
+    end
+  end
+end
+
+shared_examples_for "a file with the correct content" do
+  before do
+    # Assert starting state is as expected
+    File.should exist(path)
+    sha256_checksum(path).should == @expected_checksum
+  end
+
+  include_context "diff disabled"
+
+  describe "when running action :create" do
+    before do
+      resource.run_action(:create)
+    end
+    it "does not overwrite the original when the :create action is run" do
+      sha256_checksum(path).should == @expected_checksum
+    end
+
+    it "does not update the mtime of the file when the :create action is run" do
+      File.stat(path).mtime.should == @expected_mtime
+    end
+
+    it "is not marked as updated by last action" do
+      resource.should_not be_updated_by_last_action
+    end
+
+    it "should restore the security contexts on selinux", :selinux_only do
+      selinux_security_context_restored?(path).should be_true
+    end
+  end
+
+  describe "when running action :create_if_missing" do
+    before do
+      resource.run_action(:create_if_missing)
+    end
+
+    it "doesn't overwrite the file when the :create_if_missing action is run" do
+      sha256_checksum(path).should == @expected_checksum
+    end
+
+    it "is not marked as updated by last action" do
+      resource.should_not be_updated_by_last_action
+    end
+
+    it "should restore the security contexts on selinux", :selinux_only do
+      selinux_security_context_restored?(path).should be_true
+    end
+  end
+
+  describe "when running action :delete" do
+    before do
+      resource.run_action(:delete)
+    end
+
+    it "deletes the file when the :delete action is run" do
+      File.should_not exist(path)
+    end
+
+    it "is marked as updated by last action" do
+      resource.should be_updated_by_last_action
+    end
+  end
+end
+
+shared_examples_for "a file resource" do
+  describe "when deploying with :move" do
+
+    include_context "deploying with move"
+
+    describe "when deploying via tmpdir" do
+
+      include_context "deploying via tmpdir"
+
+      it_behaves_like "a configured file resource"
+    end
+
+    describe "when deploying via destdir" do
+
+      include_context "deploying via destdir"
+
+      it_behaves_like "a configured file resource"
+    end
+  end
+
+  describe "when deploying with :copy" do
+
+    include_context "deploying with copy"
+
+    describe "when deploying via tmpdir" do
+
+      include_context "deploying via tmpdir"
+
+      it_behaves_like "a configured file resource"
+    end
+
+    describe "when deploying via destdir" do
+
+      include_context "deploying via destdir"
+
+      it_behaves_like "a configured file resource"
+    end
+  end
+
+  describe "when running under why run" do
+
+    before do
+      Chef::Config[:why_run] = true
+    end
+
+    after do
+      Chef::Config[:why_run] = false
+    end
+
+    context "and the resource has a path with a missing intermediate directory" do
+      # CHEF-3978
+
+      let(:path) do
+        File.join(test_file_dir, "intermediate_dir", make_tmpname(file_base))
+      end
+
+      it "successfully doesn't create the file" do
+        resource.run_action(:create) # should not raise
+        File.should_not exist(path)
+      end
+    end
+
+  end
+
+  describe "when setting atomic_update" do
+    it "booleans should work" do
+      lambda {resource.atomic_update(true)}.should_not raise_error
+      lambda {resource.atomic_update(false)}.should_not raise_error
+    end
+
+    it "anything else should raise an error" do
+      lambda {resource.atomic_update(:copy)}.should raise_error(ArgumentError)
+      lambda {resource.atomic_update(:move)}.should raise_error(ArgumentError)
+      lambda {resource.atomic_update(958)}.should raise_error(ArgumentError)
+    end
+  end
+
+end
+
+shared_examples_for "file resource not pointing to a real file" do
+  def symlink?(file_path)
+    if windows?
+      Chef::ReservedNames::Win32::File.symlink?(file_path)
+    else
+      File.symlink?(file_path)
+    end
+  end
+
+  def real_file?(file_path)
+    !symlink?(file_path) && File.file?(file_path)
+  end
+
+  describe "when force_unlink is set to true" do
+    it ":create unlinks the target" do
+      real_file?(path).should be_false
+      resource.force_unlink(true)
+      resource.run_action(:create)
+      real_file?(path).should be_true
+      binread(path).should == expected_content
+      resource.should be_updated_by_last_action
+    end
+  end
+
+  describe "when force_unlink is set to false" do
+    it ":create raises an error" do
+      lambda {resource.run_action(:create) }.should raise_error(Chef::Exceptions::FileTypeMismatch)
+    end
+  end
+
+  describe "when force_unlink is not set (default)" do
+    it ":create raises an error" do
+      lambda {resource.run_action(:create) }.should raise_error(Chef::Exceptions::FileTypeMismatch)
+    end
+  end
+end
+
+shared_examples_for "a configured file resource" do
+
+  include_context "diff disabled"
+
+  before do
+    Chef::Log.level = :info
+  end
+
+   # note the stripping of the drive letter from the tmpdir on windows
+  let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, test_file_dir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") }
+
+  # Most tests update the resource, but a few do not. We need to test that the
+  # resource is marked updated or not correctly, but the test contexts are
+  # composed between correct/incorrect content and correct/incorrect
+  # permissions. We override this "let" definition in the context where content
+  # and permissions are correct.
+  let(:expect_updated?) { true }
+
+  include Chef::Mixin::ShellOut
+
+  def selinux_security_context_restored?(path)
+    @restorecon_path = which("restorecon") if @restorecon_path.nil?
+    restorecon_test_command = "#{@restorecon_path} -n -v #{path}"
+    cmdresult = shell_out(restorecon_test_command)
+    # restorecon will print the required changes to stdout if any is
+    # needed
+    cmdresult.stdout.empty?
+  end
+
+  def binread(file)
+    content = File.open(file, "rb") do |f|
+      f.read
+    end
+    content.force_encoding(Encoding::BINARY) if "".respond_to?(:force_encoding)
+    content
+  end
+
+  context "when the target file is a symlink", :not_supported_on_win2k3 do
+    let(:symlink_target) {
+      File.join(CHEF_SPEC_DATA, "file-test-target")
+    }
+
+
+    describe "when configured not to manage symlink's target" do
+      before(:each) do
+        # configure not to manage symlink source
+        resource.manage_symlink_source(false)
+
+        # create symlinks for test context
+        FileUtils.touch(symlink_target)
+
+        if windows?
+          Chef::ReservedNames::Win32::File.symlink(symlink_target, path)
+        else
+          File.symlink(symlink_target, path)
+        end
+      end
+
+      after(:each) do
+        FileUtils.rm_rf(symlink_target)
+        FileUtils.rm_rf(path)
+      end
+
+      describe "when symlink target has correct content" do
+        before(:each) do
+          File.open(symlink_target, "wb") { |f| f.print expected_content }
+        end
+
+        it_behaves_like "file resource not pointing to a real file"
+      end
+
+      describe "when symlink target has the wrong content" do
+        before(:each) do
+          File.open(symlink_target, "wb") { |f| f.print "This is so wrong!!!" }
+        end
+
+        after(:each) do
+          # symlink should never be followed
+          binread(symlink_target).should == "This is so wrong!!!"
+        end
+
+        it_behaves_like "file resource not pointing to a real file"
+      end
+    end
+
+    # Unix-only for now. Windows behavior may differ because of how ACL
+    # management handles symlinks. Since symlinks are rare on Windows and this
+    # feature primarily exists to support the case where a well-known file
+    # (e.g., resolv.conf) has been converted to a symlink, we're okay with the
+    # discrepancy.
+    context "when configured to manage the symlink source", :unix_only do
+
+      before do
+        resource.manage_symlink_source(true)
+      end
+
+      context "but the symlink is part of a loop" do
+        let(:link1_path) { File.join(CHEF_SPEC_DATA, "points-to-link2") }
+        let(:link2_path) { File.join(CHEF_SPEC_DATA, "points-to-link1") }
+
+        before do
+          # point resource at link1:
+          resource.path(link1_path)
+          # create symlinks for test context
+          File.symlink(link1_path, link2_path)
+          File.symlink(link2_path, link1_path)
+        end
+
+        after(:each) do
+          FileUtils.rm_rf(link1_path)
+          FileUtils.rm_rf(link2_path)
+        end
+
+        it "raises an InvalidSymlink error" do
+          lambda { resource.run_action(:create) }.should raise_error(Chef::Exceptions::InvalidSymlink)
+        end
+
+        it "issues a warning/assumption in whyrun mode" do
+          begin
+            Chef::Config[:why_run] = true
+            resource.run_action(:create) # should not raise
+          ensure
+            Chef::Config[:why_run] = false
+          end
+        end
+      end
+
+      context "but the symlink points to a nonexistent file" do
+        let(:link_path) { File.join(CHEF_SPEC_DATA, "points-to-nothing") }
+        let(:not_existent_source) { File.join(CHEF_SPEC_DATA, "i-am-not-here") }
+
+        before do
+          resource.path(link_path)
+          # create symlinks for test context
+          File.symlink(not_existent_source, link_path)
+          FileUtils.rm_rf(not_existent_source)
+        end
+
+        after(:each) do
+          FileUtils.rm_rf(link_path)
+        end
+        it "raises an InvalidSymlink error" do
+          lambda { resource.run_action(:create) }.should raise_error(Chef::Exceptions::InvalidSymlink)
+        end
+
+        it "issues a warning/assumption in whyrun mode" do
+          begin
+            Chef::Config[:why_run] = true
+            resource.run_action(:create) # should not raise
+          ensure
+            Chef::Config[:why_run] = false
+          end
+        end
+      end
+
+      context "but the symlink is points to a non-file fs entry" do
+        let(:link_path) { File.join(CHEF_SPEC_DATA, "points-to-dir") }
+        let(:not_a_file_path) { File.join(CHEF_SPEC_DATA, "dir-at-end-of-symlink") }
+
+        before do
+          # point resource at link1:
+          resource.path(link_path)
+          # create symlinks for test context
+          File.symlink(not_a_file_path, link_path)
+          Dir.mkdir(not_a_file_path)
+        end
+
+        after(:each) do
+          FileUtils.rm_rf(link_path)
+          FileUtils.rm_rf(not_a_file_path)
+        end
+
+        it "raises an InvalidSymlink error" do
+          lambda { resource.run_action(:create) }.should raise_error(Chef::Exceptions::FileTypeMismatch)
+        end
+
+        it "issues a warning/assumption in whyrun mode" do
+          begin
+            Chef::Config[:why_run] = true
+            resource.run_action(:create) # should not raise
+          ensure
+            Chef::Config[:why_run] = false
+          end
+        end
+      end
+
+      context "when the symlink source is a real file" do
+
+        let(:wrong_content) { "this is the wrong content" }
+        let(:link_path) { File.join(CHEF_SPEC_DATA, "points-to-real-file") }
+
+        before do
+          # point resource at link:
+          resource.path(link_path)
+          # create symlinks for test context
+          File.symlink(path, link_path)
+        end
+
+        after(:each) do
+          # shared examples should not change our test setup of a file resource
+          # pointing at a symlink:
+          resource.path.should == link_path
+          FileUtils.rm_rf(link_path)
+        end
+
+        context "and the permissions are incorrect" do
+          before do
+            # Create source (real) file
+            File.open(path, "wb") { |f| f.write(expected_content) }
+          end
+
+
+          include_context "setup broken permissions"
+
+          include_examples "a securable resource with existing target"
+
+          it "does not replace the symlink with a real file" do
+            resource.run_action(:create)
+            File.should be_symlink(link_path)
+          end
+
+        end
+
+        context "and the content is incorrect" do
+          before do
+            # Create source (real) file
+            File.open(path, "wb") { |f| f.write(wrong_content) }
+          end
+
+          it "updates the source file content" do
+            pending
+          end
+
+          it "marks the resource as updated" do
+            resource.run_action(:create)
+            resource.should be_updated_by_last_action
+          end
+
+          it "does not replace the symlink with a real file" do
+            resource.run_action(:create)
+            File.should be_symlink(link_path)
+          end
+        end
+
+        context "and the content and permissions are correct" do
+          let(:expect_updated?) { false }
+
+          before do
+            # Create source (real) file
+            File.open(path, "wb") { |f| f.write(expected_content) }
+          end
+          include_context "setup correct permissions"
+
+          include_examples "a securable resource with existing target"
+
+        end
+
+      end
+
+      context "when the symlink points to a symlink which points to a real file" do
+
+        let(:wrong_content) { "this is the wrong content" }
+        let(:link_to_file_path) { File.join(CHEF_SPEC_DATA, "points-to-real-file") }
+        let(:link_to_link_path) { File.join(CHEF_SPEC_DATA, "points-to-next-link") }
+
+        before do
+          # point resource at link:
+          resource.path(link_to_link_path)
+          # create symlinks for test context
+          File.symlink(path, link_to_file_path)
+          File.symlink(link_to_file_path, link_to_link_path)
+
+          # Create source (real) file
+          File.open(path, "wb") { |f| f.write(wrong_content) }
+        end
+
+        include_context "setup broken permissions"
+
+        include_examples "a securable resource with existing target"
+
+        after(:each) do
+          # shared examples should not change our test setup of a file resource
+          # pointing at a symlink:
+          resource.path.should == link_to_link_path
+          FileUtils.rm_rf(link_to_file_path)
+          FileUtils.rm_rf(link_to_link_path)
+        end
+
+        it "does not replace the symlink with a real file" do
+          resource.run_action(:create)
+          File.should be_symlink(link_to_link_path)
+          File.should be_symlink(link_to_file_path)
+        end
+
+      end
+    end
+  end
+
+  context "when the target file is a directory" do
+    before(:each) do
+      FileUtils.mkdir_p(path)
+    end
+
+    after(:each) do
+      FileUtils.rm_rf(path)
+    end
+
+    it_behaves_like "file resource not pointing to a real file"
+  end
+
+  context "when the target file is a blockdev",:unix_only, :requires_root, :not_supported_on_solaris do
+    include Chef::Mixin::ShellOut
+    let(:path) do
+      File.join(CHEF_SPEC_DATA, "testdev")
+    end
+
+    before(:each) do
+      result = shell_out("mknod #{path} b 1 2")
+      result.stderr.empty?
+    end
+
+    after(:each) do
+      FileUtils.rm_rf(path)
+    end
+
+    it_behaves_like "file resource not pointing to a real file"
+  end
+
+  context "when the target file is a chardev",:unix_only, :requires_root, :not_supported_on_solaris do
+    include Chef::Mixin::ShellOut
+    let(:path) do
+      File.join(CHEF_SPEC_DATA, "testdev")
+    end
+
+    before(:each) do
+      result = shell_out("mknod #{path} c 1 2")
+      result.stderr.empty?
+    end
+
+    after(:each) do
+      FileUtils.rm_rf(path)
+    end
+
+    it_behaves_like "file resource not pointing to a real file"
+  end
+
+  context "when the target file is a pipe",:unix_only do
+    include Chef::Mixin::ShellOut
+    let(:path) do
+      File.join(CHEF_SPEC_DATA, "testpipe")
+    end
+
+    before(:each) do
+      result = shell_out("mkfifo #{path}")
+      result.stderr.empty?
+    end
+
+    after(:each) do
+      FileUtils.rm_rf(path)
+    end
+
+    it_behaves_like "file resource not pointing to a real file"
+  end
+
+  context "when the target file is a socket",:unix_only do
+    require 'socket'
+
+    # It turns out that the path to a socket can have at most ~104
+    # bytes. Therefore we are creating our sockets in tmpdir so that
+    # they have a shorter path.
+    let(:test_socket_dir) { File.join(Dir.tmpdir, "sockets") }
+
+    before do
+      FileUtils::mkdir_p(test_socket_dir)
+    end
+
+    after do
+      FileUtils::rm_rf(test_socket_dir)
+    end
+
+    let(:path) do
+      File.join(test_socket_dir, "testsocket")
+    end
+
+    before(:each) do
+      path.bytesize.should <= 104
+      UNIXServer.new(path)
+    end
+
+    after(:each) do
+      FileUtils.rm_rf(path)
+    end
+
+    it_behaves_like "file resource not pointing to a real file"
+  end
+
+  # Regression test for http://tickets.opscode.com/browse/CHEF-4082
+  context "when notification is configured" do
+    describe "when path is specified with normal seperator" do
+      before do
+        @notified_resource = Chef::Resource.new("punk", resource.run_context)
+        resource.notifies(:run, @notified_resource, :immediately)
+        resource.run_action(:create)
+      end
+
+      it "should notify the other resources correctly" do
+        resource.should be_updated_by_last_action
+        resource.run_context.immediate_notifications(resource).length.should == 1
+      end
+    end
+
+    describe "when path is specified with windows seperator", :windows_only do
+      let(:path) {
+        File.join(test_file_dir, make_tmpname(file_base)).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)
+      }
+
+      before do
+        @notified_resource = Chef::Resource.new("punk", resource.run_context)
+        resource.notifies(:run, @notified_resource, :immediately)
+        resource.run_action(:create)
+      end
+
+      it "should notify the other resources correctly" do
+        resource.should be_updated_by_last_action
+        resource.run_context.immediate_notifications(resource).length.should == 1
+      end
+    end
+  end
+
+  context "when the target file does not exist" do
+    before do
+      # Assert starting state is expected
+      File.should_not exist(path)
+    end
+
+    describe "when running action :create" do
+      before do
+        resource.run_action(:create)
+      end
+
+      it "creates the file when the :create action is run" do
+        File.should exist(path)
+      end
+
+      it "creates the file with the correct content when the :create action is run" do
+        binread(path).should == expected_content
+      end
+
+      it "is marked as updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+
+      it "should restore the security contexts on selinux", :selinux_only do
+        selinux_security_context_restored?(path).should be_true
+      end
+    end
+
+    describe "when running action :create_if_missing" do
+      before do
+        resource.run_action(:create_if_missing)
+      end
+
+      it "creates the file with the correct content" do
+        binread(path).should == expected_content
+      end
+
+      it "is marked as updated by last action" do
+        resource.should be_updated_by_last_action
+      end
+
+      it "should restore the security contexts on selinux", :selinux_only do
+        selinux_security_context_restored?(path).should be_true
+      end
+    end
+
+    describe "when running action :delete" do
+      before do
+        resource.run_action(:delete)
+      end
+
+      it "deletes the file when the :delete action is run" do
+        File.should_not exist(path)
+      end
+
+      it "is not marked updated by last action" do
+        resource.should_not be_updated_by_last_action
+      end
+    end
+  end
+
+  # Set up the context for security tests
+  def allowed_acl(sid, expected_perms)
+    [ ACE.access_allowed(sid, expected_perms[:specific]) ]
+  end
+
+  def denied_acl(sid, expected_perms)
+    [ ACE.access_denied(sid, expected_perms[:specific]) ]
+  end
+
+  def parent_inheritable_acls
+    dummy_file_path = File.join(test_file_dir, "dummy_file")
+    FileUtils.touch(dummy_file_path)
+    dummy_desc = get_security_descriptor(dummy_file_path)
+    FileUtils.rm_rf(dummy_file_path)
+    dummy_desc
+  end
+
+  it_behaves_like "a securable resource without existing target"
+
+  context "when the target file has the wrong content" do
+    before(:each) do
+      File.open(path, "wb") { |f| f.print "This is so wrong!!!" }
+      now = Time.now.to_i
+      File.utime(now - 9000, now - 9000, path)
+
+      @expected_mtime = File.stat(path).mtime
+      @expected_checksum = sha256_checksum(path)
+    end
+
+    describe "and the target file has the correct permissions" do
+      include_context "setup correct permissions"
+
+      it_behaves_like "a file with the wrong content"
+
+      it_behaves_like "a securable resource with existing target"
+    end
+
+    context "and the target file has incorrect permissions" do
+      include_context "setup broken permissions"
+
+      it_behaves_like "a file with the wrong content"
+
+      it_behaves_like "a securable resource with existing target"
+    end
+  end
+
+  context "when the target file has the correct content" do
+    before(:each) do
+      File.open(path, "wb") { |f| f.print expected_content }
+      now = Time.now.to_i
+      File.utime(now - 9000, now - 9000, path)
+
+      @expected_mtime = File.stat(path).mtime
+      @expected_checksum = sha256_checksum(path)
+    end
+
+    describe "and the target file has the correct permissions" do
+
+      # When permissions and content are correct, chef should do nothing and
+      # the resource should not be marked updated.
+      let(:expect_updated?) { false }
+
+      include_context "setup correct permissions"
+
+      it_behaves_like "a file with the correct content"
+
+      it_behaves_like "a securable resource with existing target"
+    end
+
+    context "and the target file has incorrect permissions" do
+      include_context "setup broken permissions"
+
+      it_behaves_like "a file with the correct content"
+
+      it_behaves_like "a securable resource with existing target"
+    end
+  end
+
+  # Regression test for http://tickets.opscode.com/browse/CHEF-4419
+  context "when the path starts with '/' and target file exists", :windows_only do
+    let(:path) do
+      File.join(test_file_dir[2..test_file_dir.length], make_tmpname(file_base))
+    end
+
+    before do
+      File.open(path, "wb") { |f| f.print expected_content }
+      now = Time.now.to_i
+      File.utime(now - 9000, now - 9000, path)
+
+      @expected_mtime = File.stat(path).mtime
+      @expected_checksum = sha256_checksum(path)
+    end
+
+    describe ":create action should run without any updates" do
+      before do
+        # Assert starting state is as expected
+        File.should exist(path)
+        sha256_checksum(path).should == @expected_checksum
+        resource.run_action(:create)
+      end
+
+      it "does not overwrite the original when the :create action is run" do
+        sha256_checksum(path).should == @expected_checksum
+      end
+
+      it "does not update the mtime of the file when the :create action is run" do
+        File.stat(path).mtime.should == @expected_mtime
+      end
+
+      it "is not marked as updated by last action" do
+        resource.should_not be_updated_by_last_action
+      end
+    end
+  end
+
+end
+
+shared_context Chef::Resource::File  do
+  if windows?
+    require 'chef/win32/file'
+  end
+
+  # We create the files in a different directory than tmp to exercise
+  # different file deployment strategies more completely.
+  let(:test_file_dir) do
+    if windows?
+      File.join(ENV['systemdrive'], "test-dir")
+    else
+      File.join(CHEF_SPEC_DATA, "test-dir")
+    end
+  end
+
+  let(:path) do
+    File.join(test_file_dir, make_tmpname(file_base))
+  end
+
+  before do
+    FileUtils::mkdir_p(test_file_dir)
+  end
+
+  after(:each) do
+    FileUtils.rm_r(path) if File.exists?(path)
+    FileUtils.rm_r(CHEF_SPEC_BACKUP_PATH) if File.exists?(CHEF_SPEC_BACKUP_PATH)
+  end
+
+  after do
+    FileUtils::rm_rf(test_file_dir)
+  end
+end
diff --git a/spec/support/shared/functional/knife.rb b/spec/support/shared/functional/knife.rb
new file mode 100644
index 0000000..e96de7c
--- /dev/null
+++ b/spec/support/shared/functional/knife.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: AJ Christensen (<aj at junglist.gen.nz>)
+# Author:: Ho-Sheng Hsiao (<hosh at opscode.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.
+#
+module SpecHelpers
+  module Knife
+    def redefine_argv(value)
+      Object.send(:remove_const, :ARGV)
+      Object.send(:const_set, :ARGV, value)
+    end
+
+    def with_argv(*argv)
+      original_argv = ARGV
+      redefine_argv(argv.flatten)
+      begin
+        yield
+      ensure
+        redefine_argv(original_argv)
+      end
+    end
+  end
+end
diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb
new file mode 100644
index 0000000..3952b78
--- /dev/null
+++ b/spec/support/shared/functional/securable_resource.rb
@@ -0,0 +1,533 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Author:: Mark Mzyk (<mmzyk at opscode.com>)
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2011 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 'etc'
+
+shared_context "setup correct permissions" do
+  if windows?
+    include_context "use Windows permissions"
+  end
+
+  # I could not get this to work with :requires_unprivileged_user for whatever
+  # reason. The setup when running as root is the same as non-root, except we
+  # also do a chown, so this sets up correct context for either case.
+  before :each, :unix_only do
+    File.chmod(0776, path)
+    now = Time.now.to_i
+    File.utime(now - 9000, now - 9000, path)
+  end
+
+  # Root only context.
+  before :each, :unix_only, :requires_root do
+    File.chown(Etc.getpwnam('nobody').uid, 1337, path)
+  end
+
+  before :each, :windows_only do
+    so = SecurableObject.new(path)
+    so.owner = SID.Administrator
+    so.group = SID.Administrators
+    dacl = ACL.create(denied_acl(SID.Guest, expected_modify_perms) +
+                      allowed_acl(SID.Guest, expected_read_perms))
+    so.dacl = dacl
+  end
+end
+
+shared_context "setup broken permissions" do
+  if windows?
+    include_context "use Windows permissions"
+  end
+
+  before :each, :unix_only do
+    File.chmod(0644, path)
+  end
+
+  before :each, :unix_only, :requires_root do
+    File.chown(0, 0, path)
+  end
+
+  before :each, :windows_only do
+    so = SecurableObject.new(path)
+    so.owner = SID.Guest
+    so.group = SID.Everyone
+    dacl = ACL.create(allowed_acl(SID.Guest, expected_modify_perms))
+    so.set_dacl(dacl, true)
+  end
+end
+
+shared_context "use Windows permissions", :windows_only do
+  if windows?
+    SID ||= Chef::ReservedNames::Win32::Security::SID
+    ACE ||= Chef::ReservedNames::Win32::Security::ACE
+    ACL ||= Chef::ReservedNames::Win32::Security::ACL
+    SecurableObject ||= Chef::ReservedNames::Win32::Security::SecurableObject
+  end
+
+  def get_security_descriptor(path)
+    Chef::ReservedNames::Win32::Security.get_named_security_info(path)
+  end
+
+  def explicit_aces
+    descriptor.dacl.select { |ace| ace.explicit? }
+  end
+
+  def extract_ace_properties(aces)
+    hashes = []
+    aces.each do |ace|
+      hashes << { :mask => ace.mask, :type => ace.type, :flags => ace.flags }
+    end
+    hashes
+  end
+
+  # Standard expected rights
+  let(:expected_read_perms) do
+    {
+      :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ,
+      :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ,
+    }
+  end
+
+  let(:expected_read_execute_perms) do
+    {
+      :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE,
+      :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE
+    }
+  end
+
+  let(:expected_write_perms) do
+    {
+      :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE,
+      :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE
+    }
+  end
+
+  let(:expected_modify_perms) do
+    {
+      :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE,
+      :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE
+    }
+  end
+
+  let(:expected_full_control_perms) do
+    {
+      :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_ALL,
+      :specific => Chef::ReservedNames::Win32::API::Security::FILE_ALL_ACCESS
+    }
+  end
+
+  RSpec::Matchers.define :have_expected_properties do |mask, type, flags|
+    match do |ace|
+      ace.mask == mask
+      ace.type == type
+      ace.flags == flags
+    end
+  end
+
+  def descriptor
+    get_security_descriptor(path)
+  end
+end
+
+shared_examples_for "a securable resource with existing target" do
+
+  include_context "diff disabled"
+
+  context "on Unix", :unix_only do
+    let(:expected_user_name) { 'nobody' }
+    let(:expected_uid) { Etc.getpwnam(expected_user_name).uid }
+    let(:desired_gid) { 1337 }
+    let(:expected_gid) { 1337 }
+
+    pending "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true
+    pending "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
+        resource.run_action(:create)
+      end
+
+      it "should set an owner" do
+        File.lstat(path).uid.should == expected_uid
+      end
+
+      it "is marked as updated only if changes are made" do
+        resource.updated_by_last_action?.should == expect_updated?
+      end
+
+    end
+
+    describe "when setting the group", :requires_root do
+      before do
+        resource.group desired_gid
+        resource.run_action(:create)
+      end
+
+      it "should set a group" do
+        File.lstat(path).gid.should == expected_gid
+      end
+
+      it "is marked as updated only if changes are made" do
+        resource.updated_by_last_action?.should == expect_updated?
+      end
+
+    end
+
+    describe "when setting the permissions from octal given as a String" do
+      before do
+        @mode_string = '776'
+        resource.mode @mode_string
+        resource.run_action(:create)
+      end
+
+      it "should set permissions as specified" do
+        pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
+          (File.lstat(path).mode & 007777).should == (@mode_string.oct & 007777)
+        end
+      end
+
+      it "is marked as updated only if changes are made" do
+        resource.updated_by_last_action?.should == expect_updated?
+      end
+    end
+
+    describe "when setting permissions from a literal octal Integer" do
+      before do
+        @mode_integer = 0776
+        resource.mode @mode_integer
+        resource.run_action(:create)
+      end
+
+      it "should set permissions in numeric form as a ruby-interpreted octal" do
+        pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
+          (File.lstat(path).mode & 007777).should == (@mode_integer & 007777)
+        end
+      end
+
+      it "is marked as updated only if changes are made" do
+        resource.updated_by_last_action?.should == expect_updated?
+      end
+    end
+  end
+
+  context "on Windows", :windows_only do
+    include_context "use Windows permissions"
+
+    describe "when setting owner" do
+      before do
+        resource.owner('Administrator')
+        resource.run_action(:create)
+      end
+
+      it "should set the owner" do
+        descriptor.owner.should == SID.Administrator
+      end
+
+      it "is marked as updated only if changes are made" do
+        resource.updated_by_last_action?.should == expect_updated?
+      end
+    end
+
+    describe "when setting group" do
+      before do
+        resource.group('Administrators')
+        resource.run_action(:create)
+      end
+
+      it "should set the group" do
+        descriptor.group.should == SID.Administrators
+      end
+
+      it "is marked as updated only if changes are made" do
+        resource.updated_by_last_action?.should == expect_updated?
+      end
+    end
+
+    describe "when setting rights and deny_rights" do
+      before do
+        resource.deny_rights(:modify, 'Guest')
+        resource.rights(:read, 'Guest')
+        resource.run_action(:create)
+      end
+
+      it "should set the rights and deny_rights" do
+        explicit_aces.should == denied_acl(SID.Guest, expected_modify_perms) + allowed_acl(SID.Guest, expected_read_perms)
+      end
+
+      it "is marked as updated only if changes are made" do
+        resource.updated_by_last_action?.should == expect_updated?
+      end
+    end
+  end
+end
+
+shared_examples_for "a securable resource without existing target" do
+
+  include_context "diff disabled"
+
+  context "on Unix", :unix_only do
+    pending "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
+      File.exist?(path).should == false
+      resource.run_action(:create)
+      descriptor.owner.should == SID.Administrators
+    end
+
+    it "sets owner when owner is specified" do
+      resource.owner 'Guest'
+      resource.run_action(:create)
+      descriptor.owner.should == SID.Guest
+    end
+
+    it "fails to set owner when owner has invalid characters" do
+      lambda { resource.owner 'Lance "The Nose" Glindenberry III' }.should raise_error#(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "sets owner when owner is specified with a \\" do
+      resource.owner "#{ENV['USERDOMAIN']}\\Guest"
+      resource.run_action(:create)
+      descriptor.owner.should == SID.Guest
+    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'
+      resource.run_action(:create)
+      descriptor.owner.should == SID.Guest
+
+      new_resource = create_resource
+      new_resource.owner.should == nil
+      new_resource.run_action(:create)
+      descriptor.owner.should == SID.Guest
+    end
+
+    it "sets group to None on create if group is not specified" do
+      resource.group.should == nil
+      File.exist?(path).should == false
+      resource.run_action(:create)
+      descriptor.group.should == SID.None
+    end
+
+    it "sets group when group is specified" do
+      resource.group 'Everyone'
+      resource.run_action(:create)
+      descriptor.group.should == SID.Everyone
+    end
+
+    it "fails to set group when group has invalid characters" do
+      lambda { resource.group 'Lance "The Nose" Glindenberry III' }.should 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" do
+        resource.group "#{ENV['COMPUTERNAME']}\\Administrators"
+        resource.run_action(:create)
+        descriptor.group.should == SID.Everyone
+      end
+    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'
+      resource.run_action(:create)
+      descriptor.group.should == SID.Everyone
+
+      new_resource = create_resource
+      new_resource.group.should == nil
+      new_resource.run_action(:create)
+      descriptor.group.should == SID.Everyone
+    end
+
+    describe "with rights and deny_rights attributes" do
+
+      it "correctly sets :read rights" do
+        resource.rights(:read, 'Guest')
+        resource.run_action(:create)
+        explicit_aces.should == allowed_acl(SID.Guest, expected_read_perms)
+      end
+
+      it "correctly sets :read_execute rights" do
+        resource.rights(:read_execute, 'Guest')
+        resource.run_action(:create)
+        explicit_aces.should == allowed_acl(SID.Guest, expected_read_execute_perms)
+      end
+
+      it "correctly sets :write rights" do
+        resource.rights(:write, 'Guest')
+        resource.run_action(:create)
+        explicit_aces.should == allowed_acl(SID.Guest, expected_write_perms)
+      end
+
+      it "correctly sets :modify rights" do
+        resource.rights(:modify, 'Guest')
+        resource.run_action(:create)
+        explicit_aces.should == allowed_acl(SID.Guest, expected_modify_perms)
+      end
+
+      it "correctly sets :full_control rights" do
+        resource.rights(:full_control, 'Guest')
+        resource.run_action(:create)
+        explicit_aces.should == allowed_acl(SID.Guest, expected_full_control_perms)
+      end
+
+      it "correctly sets deny_rights" do
+        # deny is an ACE with full rights, but is a deny type ace, not an allow type
+        resource.deny_rights(:full_control, 'Guest')
+        resource.run_action(:create)
+        explicit_aces.should == denied_acl(SID.Guest, expected_full_control_perms)
+      end
+
+      it "Sets multiple rights" do
+        resource.rights(:read, 'Everyone')
+        resource.rights(:modify, 'Guest')
+        resource.run_action(:create)
+
+        explicit_aces.should ==
+          allowed_acl(SID.Everyone, expected_read_perms) +
+          allowed_acl(SID.Guest, expected_modify_perms)
+      end
+
+      it "Sets deny_rights ahead of rights" do
+        resource.rights(:read, 'Everyone')
+        resource.deny_rights(:modify, 'Guest')
+        resource.run_action(:create)
+
+        explicit_aces.should ==
+          denied_acl(SID.Guest, expected_modify_perms) +
+          allowed_acl(SID.Everyone, expected_read_perms)
+      end
+
+      it "Sets deny_rights ahead of rights when specified in reverse order" do
+        resource.deny_rights(:modify, 'Guest')
+        resource.rights(:read, 'Everyone')
+        resource.run_action(:create)
+
+        explicit_aces.should ==
+          denied_acl(SID.Guest, expected_modify_perms) +
+          allowed_acl(SID.Everyone, expected_read_perms)
+      end
+
+    end
+
+    context "with a mode attribute" do
+      if windows?
+        Security ||= Chef::ReservedNames::Win32::API::Security
+      end
+
+      it "respects mode in string form as an octal number" do
+        #on windows, mode cannot modify owner and/or group permissons
+        #unless the owner and/or group as appropriate is specified
+        resource.mode '400'
+        resource.owner 'Guest'
+        resource.group 'Everyone'
+        resource.run_action(:create)
+
+        explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ) ]
+      end
+
+      it "respects mode in numeric form as a ruby-interpreted octal" do
+        resource.mode 0700
+        resource.owner 'Guest'
+        resource.run_action(:create)
+
+        explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE) ]
+      end
+
+      it "respects the owner, group and everyone bits of mode" do
+        resource.mode 0754
+        resource.owner 'Guest'
+        resource.group 'Administrators'
+        resource.run_action(:create)
+
+        explicit_aces.should == [
+          ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE),
+          ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_EXECUTE),
+          ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_READ)
+        ]
+      end
+
+      it "respects the individual read, write and execute bits of mode" do
+        resource.mode 0421
+        resource.owner 'Guest'
+        resource.group 'Administrators'
+        resource.run_action(:create)
+
+        explicit_aces.should == [
+          ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ),
+          ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_WRITE | Security::DELETE),
+          ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_EXECUTE)
+        ]
+      end
+
+      it 'warns when mode tries to set owner bits but owner is not specified' do
+        @warn = []
+        Chef::Log.stub!(:warn) { |msg| @warn << msg }
+
+        resource.mode 0400
+        resource.run_action(:create)
+
+        @warn.include?("Mode 400 includes bits for the owner, but owner is not specified").should be_true
+      end
+
+      it 'warns when mode tries to set group bits but group is not specified' do
+        @warn = []
+        Chef::Log.stub!(:warn) { |msg| @warn << msg }
+
+        resource.mode 0040
+        resource.run_action(:create)
+
+        @warn.include?("Mode 040 includes bits for the group, but group is not specified").should be_true
+      end
+    end
+
+    it "does not inherit aces if inherits is set to false" do
+      # We need at least one ACE if we're creating a securable without
+      # inheritance
+      resource.rights(:full_control, 'Administrators')
+      resource.inherits(false)
+      resource.run_action(:create)
+
+      descriptor.dacl.each do | ace |
+        ace.inherited?.should == false
+      end
+    end
+
+    it "has the inheritable acls of parent directory if no acl is specified" do
+      File.exist?(path).should == false
+
+      parent_acls = parent_inheritable_acls
+
+      resource.run_action(:create)
+
+      descriptor.dacl.each_with_index do |ace, index|
+        # On Windows Server 2003 OS creates a default non-inheritable
+        # ACL during file creation unless otherwise specified.
+        ace.inherited?.should == true unless windows_win2k3?
+        ace.should == parent_acls.dacl[index]
+      end
+    end
+
+  end
+end
+
diff --git a/spec/support/shared/functional/securable_resource_with_reporting.rb b/spec/support/shared/functional/securable_resource_with_reporting.rb
new file mode 100644
index 0000000..2bfc257
--- /dev/null
+++ b/spec/support/shared/functional/securable_resource_with_reporting.rb
@@ -0,0 +1,385 @@
+
+ALL_EXPANDED_PERMISSIONS = ["generic read",
+                            "generic write",
+                            "generic execute",
+                            "generic all",
+                            "delete",
+                            "read permissions",
+                            "change permissions",
+                            "take ownership",
+                            "synchronize",
+                            "access system security",
+                            "read data / list directory",
+                            "write data / add file",
+                            "append data / add subdirectory",
+                            "read extended attributes",
+                            "write extended attributes",
+                            "execute / traverse",
+                            "delete child",
+                            "read attributes",
+                            "write attributes"]
+
+
+shared_examples_for "a securable resource with reporting" do
+
+  include_context "diff disabled"
+
+  let(:current_resource) do
+    provider = resource.provider_for_action(resource.action)
+    provider.load_current_resource
+    provider.current_resource
+  end
+
+  # Default mode varies based on implementation. Providers that use a tempfile
+  # will default to 0600. Providers that use File.open will default to 0666 -
+  # umask
+  # let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+
+  describe "reading file security metadata for reporting on unix", :unix_only => true do
+    # According to POSIX standard created files get either the
+    # effective gid of the process or inherits the gid of the parent
+    # directory based on file system. Since it's hard to guess what
+    # would happen on each platform we create a dummy file and see
+    # what the group name should be.
+    before do
+      FileUtils.touch(path)
+      @expected_gid = File.stat(path).gid
+      @expected_group_name = Etc.getgrgid(@expected_gid).name
+      FileUtils.rm_rf(path)
+    end
+
+    context "when the target file doesn't exist" do
+      before do
+        resource.action(:create)
+      end
+
+      it "has empty values for file metadata in 'current_resource'" do
+        current_resource.owner.should be_nil
+        current_resource.group.should be_nil
+        current_resource.mode.should be_nil
+      end
+
+      context "and no security metadata is specified in new_resource" do
+        it "sets the metadata values on the new_resource as strings after creating" do
+          resource.run_action(:create)
+          # TODO: most stable way to specify?
+          resource.owner.should == Etc.getpwuid(Process.uid).name
+          resource.group.should == @expected_group_name
+          resource.mode.should == "0#{default_mode}"
+        end
+      end
+
+      context "and owner is specified with a String (username) in new_resource", :requires_root => true do
+
+        # TODO/bug: duplicated from the "securable resource" tests
+        let(:expected_user_name) { 'nobody' }
+
+        before do
+          resource.owner(expected_user_name)
+          resource.run_action(:create)
+        end
+
+        it "sets the owner on new_resource to the username (String) of the desired owner" do
+          resource.owner.should == expected_user_name
+        end
+
+      end
+
+      context "and owner is specified with an Integer (uid) in new_resource", :requires_root => true do
+
+        # TODO: duplicated from "securable resource"
+        let(:expected_user_name) { 'nobody' }
+        let(:expected_uid) { Etc.getpwnam(expected_user_name).uid }
+        let(:desired_gid) { 1337 }
+        let(:expected_gid) { 1337 }
+
+        before do
+          resource.owner(expected_uid)
+          resource.run_action(:create)
+        end
+
+        it "sets the owner on new_resource to the uid (Integer) of the desired owner" do
+          resource.owner.should == expected_uid
+        end
+      end
+
+      context "and group is specified with a String (group name)", :requires_root => true do
+
+        let(:expected_group_name) { Etc.getgrent.name }
+
+        before do
+          resource.group(expected_group_name)
+          resource.run_action(:create)
+        end
+
+        it "sets the group on new_resource to the group name (String) of the group" do
+          resource.group.should == expected_group_name
+        end
+
+      end
+
+      context "and group is specified with an Integer (gid)", :requires_root => true do
+        let(:expected_gid) { Etc.getgrent.gid }
+
+        before do
+          resource.group(expected_gid)
+          resource.run_action(:create)
+        end
+
+        it "sets the group on new_resource to the gid (Integer)" do
+          resource.group.should == expected_gid
+        end
+
+      end
+
+      context "and mode is specified as a String" do
+        # Need full permission for owner here or else remote directory gets
+        # into trouble trying to manage nested directories
+        let(:set_mode) { "0740" }
+        let(:expected_mode) { "0740" }
+
+        before do
+          resource.mode(set_mode)
+          resource.run_action(:create)
+        end
+
+        it "sets mode on the new_resource as a String" do
+          resource.mode.should == expected_mode
+        end
+      end
+
+      context "and mode is specified as an Integer" do
+        let(:set_mode) { 00740 }
+
+        let(:expected_mode) { "0740" }
+        before do
+          resource.mode(set_mode)
+          resource.run_action(:create)
+        end
+
+        it "sets mode on the new resource as a String" do
+          resource.mode.should == expected_mode
+        end
+      end
+    end
+
+    context "when the target file exists" do
+      before do
+        FileUtils.touch(resource.path)
+        resource.action(:create)
+      end
+
+      context "and no security metadata is specified in new_resource" do
+        it "sets the current values on current resource as strings" do
+          # TODO: most stable way to specify?
+          current_resource.owner.should == Etc.getpwuid(Process.uid).name
+          current_resource.group.should == @expected_group_name
+          current_resource.mode.should == "0#{((0100666 - File.umask) & 07777).to_s(8)}"
+        end
+      end
+
+      context "and owner is specified with a String (username) in new_resource" do
+
+        let(:expected_user_name) { Etc.getpwuid(Process.uid).name }
+
+        before do
+          resource.owner(expected_user_name)
+        end
+
+        it "sets the owner on new_resource to the username (String) of the desired owner" do
+          current_resource.owner.should == expected_user_name
+        end
+
+      end
+
+      context "and owner is specified with an Integer (uid) in new_resource" do
+
+        let(:expected_uid) { Process.uid }
+
+        before do
+          resource.owner(expected_uid)
+        end
+
+        it "sets the owner on new_resource to the uid (Integer) of the desired owner" do
+          current_resource.owner.should == expected_uid
+        end
+      end
+
+      context "and group is specified with a String (group name)" do
+        before do
+          resource.group(@expected_group_name)
+        end
+
+        it "sets the group on new_resource to the group name (String) of the group" do
+          current_resource.group.should == @expected_group_name
+        end
+
+      end
+
+      context "and group is specified with an Integer (gid)" do
+        before do
+          resource.group(@expected_gid)
+        end
+
+        it "sets the group on new_resource to the gid (Integer)" do
+          current_resource.group.should == @expected_gid
+        end
+
+      end
+
+      context "and mode is specified as a String" do
+        let(:default_create_mode) { (0100666 - File.umask) }
+        let(:expected_mode) { "0#{(default_create_mode & 07777).to_s(8)}" }
+
+        before do
+          resource.mode(expected_mode)
+        end
+
+        it "sets mode on the new_resource as a String" do
+          current_resource.mode.should == expected_mode
+        end
+      end
+
+      context "and mode is specified as an Integer" do
+        let(:set_mode) { (0100666 - File.umask) & 07777 }
+        let(:expected_mode) { "0#{set_mode.to_s(8)}" }
+
+        before do
+          resource.mode(set_mode)
+        end
+
+        it "sets mode on the new resource as a String" do
+          current_resource.mode.should == expected_mode
+        end
+      end
+    end
+  end
+
+  describe "reading file security metadata for reporting on windows", :windows_only do
+
+    before do
+      pending "windows reporting not yet fully supported"
+    end
+
+
+    context "when the target file doesn't exist" do
+
+      # Windows reporting data should look like this (+/- ish):
+      # { "owner" => "bob", "checksum" => "ffff", "access control" => { "bob" => { "permissions" => ["perm1", "perm2", ...], "flags" => [] }}}
+
+
+      before do
+        resource.action(:create)
+      end
+
+      it "has empty values for file metadata in 'current_resource'" do
+        current_resource.owner.should be_nil
+        current_resource.expanded_rights.should be_nil
+      end
+
+      context "and no security metadata is specified in new_resource" do
+        it "sets the metadata values on the new_resource as strings after creating" do
+          resource.run_action(:create)
+          # TODO: most stable way to specify?
+          resource.owner.should == etc.getpwuid(process.uid).name
+          resource.state[:expanded_rights].should == { "CURRENTUSER" => { "permissions" => ALL_EXPANDED_PERMISSIONS, "flags" => [] }}
+          resource.state[:expanded_deny_rights].should == {}
+          resource.state[:inherits].should be_true
+        end
+      end
+
+
+      context "and owner is specified with a string (username) in new_resource" do
+
+        # TODO/bug: duplicated from the "securable resource" tests
+        let(:expected_user_name) { 'Guest' }
+
+        before do
+          resource.owner(expected_user_name)
+          resource.run_action(:create)
+        end
+
+        it "sets the owner on new_resource to the username (string) of the desired owner" do
+          resource.owner.should == expected_user_name
+        end
+
+      end
+
+      context "and owner is specified with a fully qualified domain user" do
+
+        # TODO: duplicated from "securable resource"
+        let(:expected_user_name) { 'domain\user' }
+
+        before do
+          resource.owner(expected_user_name)
+          resource.run_action(:create)
+        end
+
+        it "sets the owner on new_resource to the fully qualified name of the desired owner" do
+          resource.owner.should == expected_user_name
+        end
+      end
+
+    end
+
+    context "when the target file exists" do
+      before do
+        FileUtils.touch(resource.path)
+        resource.action(:create)
+      end
+
+      context "and no security metadata is specified in new_resource" do
+        it "sets the current values on current resource as strings" do
+          # TODO: most stable way to specify?
+          current_resource.owner.should == etc.getpwuid(process.uid).name
+          current_resource.expanded_rights.should == { "CURRENTUSER" => ALL_EXPANDED_PERMISSIONS }
+        end
+      end
+
+      context "and owner is specified with a string (username) in new_resource" do
+
+        let(:expected_user_name) { etc.getpwuid(process.uid).name }
+
+        before do
+          resource.owner(expected_user_name)
+        end
+
+        it "sets the owner on current_resource to the username (string) of the desired owner" do
+          current_resource.owner.should == expected_user_name
+        end
+
+      end
+
+      context "and owner is specified as a fully qualified 'domain\\user' in new_resource" do
+
+        let(:expected_user_name) { 'domain\user' }
+
+        before do
+          resource.owner(expected_user_name)
+        end
+
+        it "sets the owner on current_resource to the fully qualified name of the desired owner" do
+          current_resource.owner.should == expected_uid
+        end
+      end
+
+      context "and access rights are specified on the new_resource" do
+        # TODO: before do blah
+
+        it "sets the expanded_rights on the current resource" do
+          pending
+        end
+      end
+
+      context "and no access rights are specified on the current resource" do
+        # TODO: before do blah
+
+        it "sets the expanded rights on the current resource" do
+          pending
+        end
+      end
+
+
+    end
+  end
+end
diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb
new file mode 100644
index 0000000..afeb4c0
--- /dev/null
+++ b/spec/support/shared/functional/windows_script.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Shared context used by both Powershell and Batch script provider
+# tests.
+
+shared_context Chef::Resource::WindowsScript do
+  before(:all) do
+
+    ohai_reader = Ohai::System.new
+    ohai_reader.require_plugin("os")
+    ohai_reader.require_plugin("windows::platform")
+
+    new_node = Chef::Node.new
+    new_node.consume_external_attrs(ohai_reader.data,{})
+
+    events = Chef::EventDispatch::Dispatcher.new
+
+    @run_context = Chef::RunContext.new(new_node, {}, events)
+  end
+
+  let(:script_output_path) do
+    File.join(Dir.tmpdir, make_tmpname("windows_script_test"))
+  end
+
+  before(:each) do
+k    File.delete(script_output_path) if File.exists?(script_output_path)
+  end
+
+  after(:each) do
+    File.delete(script_output_path) if File.exists?(script_output_path)
+  end
+end
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
new file mode 100644
index 0000000..0c4bf99
--- /dev/null
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -0,0 +1,149 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Author:: Ho-Sheng Hsiao (<hosh at opscode.com>)
+# Copyright:: Copyright (c) 2012, 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'tmpdir'
+require 'fileutils'
+require 'chef/config'
+require 'chef_zero/rspec'
+require 'json'
+require 'support/shared/integration/knife_support'
+require 'spec_helper'
+
+module IntegrationSupport
+  include ChefZero::RSpec
+
+  def when_the_repository(description, *args, &block)
+    context "When the local repository #{description}", *args do
+      before :each do
+        raise "Can only create one directory per test" if @repository_dir
+        @repository_dir = Dir.mktmpdir('chef_repo')
+        Chef::Config.chef_repo_path = @repository_dir
+        %w(client cookbook data_bag environment node role user).each do |object_name|
+          Chef::Config.delete("#{object_name}_path".to_sym)
+        end
+      end
+
+      after :each do
+        if @repository_dir
+          begin
+            %w(client cookbook data_bag environment node role user).each do |object_name|
+              Chef::Config.delete("#{object_name}_path".to_sym)
+            end
+            Chef::Config.delete(:chef_repo_path)
+            FileUtils.remove_entry_secure(@repository_dir)
+          ensure
+            @repository_dir = nil
+          end
+        end
+      end
+
+      def directory(relative_path, &block)
+        old_parent_path = @parent_path
+        @parent_path = path_to(relative_path)
+        FileUtils.mkdir_p(@parent_path)
+        instance_eval(&block) if block
+        @parent_path = old_parent_path
+      end
+
+      def file(relative_path, contents)
+        filename = path_to(relative_path)
+        dir = File.dirname(filename)
+        FileUtils.mkdir_p(dir) unless dir == '.'
+        File.open(filename, 'w') do |file|
+          raw = case contents
+                when Hash
+                  JSON.pretty_generate(contents)
+                when Array
+                  contents.join("\n")
+                else
+                  contents
+                end
+          file.write(raw)
+        end
+      end
+
+      def symlink(relative_path, relative_dest)
+        filename = path_to(relative_path)
+        dir = File.dirname(filename)
+        FileUtils.mkdir_p(dir) unless dir == '.'
+        dest_filename = path_to(relative_dest)
+        File.symlink(dest_filename, filename)
+      end
+
+      def path_to(relative_path)
+        File.expand_path(relative_path, (@parent_path || @repository_dir))
+      end
+
+      def self.path_to(relative_path)
+        File.expand_path(relative_path, (@parent_path || @repository_dir))
+      end
+
+      def self.directory(relative_path, &block)
+        before :each do
+          directory(relative_path, &block)
+        end
+      end
+
+      def self.file(relative_path, contents)
+        before :each do
+          file(relative_path, contents)
+        end
+      end
+
+      def self.symlink(relative_path, relative_dest)
+        before :each do
+          symlink(relative_path, relative_dest)
+        end
+      end
+
+      def self.cwd(relative_path)
+        before :each do
+          @old_cwd = Dir.pwd
+          Dir.chdir(path_to(relative_path))
+        end
+        after :each do
+          Dir.chdir(@old_cwd)
+        end
+      end
+
+      instance_eval(&block)
+    end
+  end
+
+  # Versioned cookbooks
+
+  def with_versioned_cookbooks(_metadata = {}, &block)
+    _m = { :versioned_cookbooks => true }.merge(_metadata)
+    context 'with versioned cookbooks', _m do
+      before(:each) { Chef::Config[:versioned_cookbooks] = true }
+      after(:each)  { Chef::Config.delete(:versioned_cookbooks) }
+      instance_eval(&block)
+    end
+  end
+
+  def without_versioned_cookbooks(_metadata = {}, &block)
+    _m = { :versioned_cookbooks => false }.merge(_metadata)
+    context 'with versioned cookbooks', _m do
+      # Just make sure this goes back to default
+      before(:each) { Chef::Config[:versioned_cookbooks] = false }
+      after(:each)  { Chef::Config.delete(:versioned_cookbooks) }
+      instance_eval(&block)
+    end
+  end
+end
diff --git a/spec/support/shared/integration/knife_support.rb b/spec/support/shared/integration/knife_support.rb
new file mode 100644
index 0000000..036972e
--- /dev/null
+++ b/spec/support/shared/integration/knife_support.rb
@@ -0,0 +1,171 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'chef/config'
+require 'chef/knife'
+require 'chef/application/knife'
+require 'logger'
+require 'chef/log'
+
+module KnifeSupport
+  DEBUG = ENV['DEBUG']
+  def knife(*args, &block)
+    # Allow knife('role from file roles/blah.json') rather than requiring the
+    # arguments to be split like knife('role', 'from', 'file', 'roles/blah.json')
+    # If any argument will have actual spaces in it, the long form is required.
+    # (Since knife commands always start with the command name, and command
+    # names with spaces are always multiple args, this is safe.)
+    if args.length == 1
+      args = args[0].split(/\s+/)
+    end
+
+    # Make output stable
+    Chef::Config[:concurrency] = 1
+
+    # Work on machines where we can't access /var
+    checksums_cache_dir = Dir.mktmpdir('checksums') do |checksums_cache_dir|
+      Chef::Config[:cache_options] = {
+        :path => checksums_cache_dir,
+        :skip_expires => true
+      }
+
+      # This is Chef::Knife.run without load_commands--we'll load stuff
+      # ourselves, thank you very much
+      stdout = StringIO.new
+      stderr = StringIO.new
+      old_loggers = Chef::Log.loggers
+      old_log_level = Chef::Log.level
+      begin
+        puts "knife: #{args.join(' ')}" if DEBUG
+        subcommand_class = Chef::Knife.subcommand_class_from(args)
+        subcommand_class.options = Chef::Application::Knife.options.merge(subcommand_class.options)
+        subcommand_class.load_deps
+        instance = subcommand_class.new(args)
+
+        # Capture stdout/stderr
+        instance.ui = Chef::Knife::UI.new(stdout, stderr, STDIN, {})
+
+        # Don't print stuff
+        Chef::Config[:verbosity] = ( DEBUG ? 2 : 0 )
+        instance.config[:config_file] = File.join(CHEF_SPEC_DATA, "null_config.rb")
+
+
+        # Configure chef with a (mostly) blank knife.rb
+        # We set a global and then mutate it in our stub knife.rb so we can be
+        # extra sure that we're not loading someone's real knife.rb and then
+        # running test scenarios against a real chef server. If things don't
+        # smell right, abort.
+
+        $__KNIFE_INTEGRATION_FAILSAFE_CHECK = "ole"
+        instance.configure_chef
+
+        unless $__KNIFE_INTEGRATION_FAILSAFE_CHECK == "ole ole"
+          raise Exception, "Potential misconfiguration of integration tests detected. Aborting test."
+        end
+
+        logger = Logger.new(stderr)
+        logger.formatter = proc { |severity, datetime, progname, msg| "#{severity}: #{msg}\n" }
+        Chef::Log.use_log_devices([logger])
+        Chef::Log.level = ( DEBUG ? :debug : :warn )
+        Chef::Log::Formatter.show_time = false
+
+        instance.run_with_pretty_exceptions(true)
+
+        exit_code = 0
+
+      # This is how rspec catches exit()
+      rescue SystemExit => e
+        exit_code = e.status
+      ensure
+        Chef::Log.use_log_devices(old_loggers)
+        Chef::Log.level = old_log_level
+        Chef::Config.delete(:cache_options)
+        Chef::Config.delete(:concurrency)
+      end
+
+      KnifeResult.new(stdout.string, stderr.string, exit_code)
+    end
+  end
+
+  private
+
+  class KnifeResult
+    def initialize(stdout, stderr, exit_code)
+      @stdout = stdout
+      @stderr = stderr
+      @exit_code = exit_code
+    end
+
+    attr_reader :stdout
+    attr_reader :stderr
+    attr_reader :exit_code
+
+    def should_fail(*args)
+      expected = {}
+      args.each do |arg|
+        if arg.is_a?(Hash)
+          expected.merge!(arg)
+        elsif arg.is_a?(Integer)
+          expected[:exit_code] = arg
+        else
+          expected[:stderr] = arg
+        end
+      end
+      expected[:exit_code] = 1 if !expected[:exit_code]
+      should_result_in(expected)
+    end
+
+    def should_succeed(*args)
+      expected = {}
+      args.each do |arg|
+        if arg.is_a?(Hash)
+          expected.merge!(arg)
+        else
+          expected[:stdout] = arg
+        end
+      end
+      should_result_in(expected)
+    end
+
+    private
+
+    def should_result_in(expected)
+      expected[:stdout] = '' if !expected[:stdout]
+      expected[:stderr] = '' if !expected[:stderr]
+      expected[:exit_code] = 0 if !expected[:exit_code]
+      # TODO make this go away
+      stderr_actual = @stderr.sub(/^WARNING: No knife configuration file found\n/, '')
+
+      if expected[:stderr].is_a?(Regexp)
+        stderr_actual.should =~ expected[:stderr]
+      else
+        stderr_actual.should == expected[:stderr]
+      end
+      stdout_actual = @stdout
+      if Chef::Platform.windows?
+        stderr_actual = stderr_actual.gsub("\r\n", "\n")
+        stdout_actual = stdout_actual.gsub("\r\n", "\n")
+      end
+      @exit_code.should == expected[:exit_code]
+      if expected[:stdout].is_a?(Regexp)
+        stdout_actual.should =~ expected[:stdout]
+      else
+        stdout_actual.should == expected[:stdout]
+      end
+    end
+  end
+end
diff --git a/spec/support/shared/unit/api_error_inspector.rb b/spec/support/shared/unit/api_error_inspector.rb
new file mode 100644
index 0000000..8231ceb
--- /dev/null
+++ b/spec/support/shared/unit/api_error_inspector.rb
@@ -0,0 +1,192 @@
+#
+# Author:: Daniel DeLeo (<dan 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.
+#
+
+
+
+# == API Error Inspector Examples
+# These tests are work in progress. They exercise the code enough to ensure it
+# runs without error, but don't make assertions about the output. This is
+# because aspects such as how information gets formatted, what's included, etc.
+# are still in flux. When testing an inspector, change the outputter to use
+# STDOUT and manually check the ouput.
+
+shared_examples_for "an api error inspector" do
+
+  before do
+    @node_name = "test-node.example.com"
+    @config = {
+      :validation_client_name => "testorg-validator",
+      :validation_key => "/etc/chef/testorg-validator.pem",
+      :chef_server_url => "https://chef-api.example.com",
+      :node_name => "testnode-name",
+      :client_key => "/etc/chef/client.pem"
+    }
+    @description = Chef::Formatters::ErrorDescription.new("Error registering the node:")
+    @outputter = Chef::Formatters::Outputter.new(StringIO.new, STDERR)
+    #@outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+
+  end
+
+  describe "when explaining a network error" do
+    before do
+      @exception = Errno::ECONNREFUSED.new("connection refused")
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining a 'private key missing' error" do
+    before do
+      @exception = Chef::Exceptions::PrivateKeyMissing.new("no private key yo")
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining a 401 caused by clock skew" do
+    before do
+      @response_body = "synchronize the clock on your host"
+      @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining a 401 (no clock skew)" do
+    before do
+      @response_body = "check your key and node name"
+      @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining a 403" do
+    before do
+      @response_body = "forbidden"
+      @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) forbidden", @response)
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining a 400" do
+    before do
+      @response_body = "didn't like your data"
+      @response = Net::HTTPBadRequest.new("1.1", "400", "(response) bad request")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) bad request", @response)
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining a 404" do
+    before do
+      @response_body = "probably caused by a redirect to a get"
+      @response = Net::HTTPNotFound.new("1.1", "404", "(response) not found")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) not found", @response)
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+  end
+
+  describe "when explaining a 500" do
+    before do
+      @response_body = "sad trombone"
+      @response = Net::HTTPInternalServerError.new("1.1", "500", "(response) internal server error")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPFatalError.new("(exception) internal server error", @response)
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+  end
+
+  describe "when explaining a 503" do
+    before do
+      @response_body = "sad trombone orchestra"
+      @response = Net::HTTPBadGateway.new("1.1", "502", "(response) bad gateway")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPFatalError.new("(exception) bad gateway", @response)
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+  end
+
+  describe "when explaining an unknown error" do
+    before do
+      @exception = RuntimeError.new("(exception) something went wrong")
+      @inspector = described_class.new(@node_name, @exception, @config)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+  end
+
+end
diff --git a/spec/support/shared/unit/execute_resource.rb b/spec/support/shared/unit/execute_resource.rb
new file mode 100644
index 0000000..609e77a
--- /dev/null
+++ b/spec/support/shared/unit/execute_resource.rb
@@ -0,0 +1,125 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+shared_examples_for "an execute resource" do
+
+  before(:each) do
+    @resource = execute_resource
+  end
+
+  it "should create a new Chef::Resource::Execute" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Execute)
+  end
+
+  it "should set the command to the first argument to new" do
+    @resource.command.should eql(resource_instance_name)
+  end
+
+  it "should accept an array on instantiation, too" do
+    resource = Chef::Resource::Execute.new(%w{something else})
+    resource.should be_a_kind_of(Chef::Resource)
+    resource.should be_a_kind_of(Chef::Resource::Execute)
+    resource.command.should eql(%w{something else})
+  end
+
+  it "should accept a string for the command to run" do
+    @resource.command "something"
+    @resource.command.should eql("something")
+  end
+
+  it "should accept an array for the command to run" do
+    @resource.command %w{something else}
+    @resource.command.should eql(%w{something else})
+  end
+
+  it "should accept a string for the cwd" do
+    @resource.cwd "something"
+    @resource.cwd.should eql("something")
+  end
+
+  it "should accept a hash for the environment" do
+    test_hash = { :one => :two }
+    @resource.environment(test_hash)
+    @resource.environment.should eql(test_hash)
+  end
+
+  it "allows the environment to be specified with #env" do
+    @resource.should respond_to(:env)
+  end
+
+  it "should accept a string for the group" do
+    @resource.group "something"
+    @resource.group.should eql("something")
+  end
+
+  it "should accept an integer for the group" do
+    @resource.group 1
+    @resource.group.should eql(1)
+  end
+
+  it "should accept an array for the execution path" do
+    @resource.path ["woot"]
+    @resource.path.should eql(["woot"])
+  end
+
+  it "should accept an integer for the return code" do
+    @resource.returns 1
+    @resource.returns.should eql(1)
+  end
+
+  it "should accept an integer for the timeout" do
+    @resource.timeout 1
+    @resource.timeout.should eql(1)
+  end
+
+  it "should accept a string for the user" do
+    @resource.user "something"
+    @resource.user.should eql("something")
+  end
+
+  it "should accept an integer for the user" do
+    @resource.user 1
+    @resource.user.should eql(1)
+  end
+
+  it "should accept a string for creates" do
+    @resource.creates "something"
+    @resource.creates.should eql("something")
+  end
+
+  describe "when it has cwd, environment, group, path, return value, and a user" do
+    before do
+      @resource.command("grep")
+      @resource.cwd("/tmp/")
+      @resource.environment({ :one => :two })
+      @resource.group("legos")
+      @resource.path(["/var/local/"])
+      @resource.returns(1)
+      @resource.user("root")
+    end
+
+    it "returns the command as its identity" do
+      @resource.identity.should == "grep"
+    end
+  end
+end
+
diff --git a/spec/support/shared/unit/file_system_support.rb b/spec/support/shared/unit/file_system_support.rb
new file mode 100644
index 0000000..3e771dd
--- /dev/null
+++ b/spec/support/shared/unit/file_system_support.rb
@@ -0,0 +1,70 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# 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/chef_fs/file_system'
+require 'chef/chef_fs/file_system/memory_root'
+require 'chef/chef_fs/file_system/memory_dir'
+require 'chef/chef_fs/file_system/memory_file'
+
+module FileSystemSupport
+  def memory_fs(pretty_name, value, cannot_be_in_regex = nil)
+    if !value.is_a?(Hash)
+      raise "memory_fs() must take a Hash"
+    end
+    dir = Chef::ChefFS::FileSystem::MemoryRoot.new(pretty_name, cannot_be_in_regex)
+    value.each do |key, child|
+      dir.add_child(memory_fs_value(child, key.to_s, dir))
+    end
+    dir
+  end
+
+  def memory_fs_value(value, name = '', parent = nil)
+    if value.is_a?(Hash)
+      dir = Chef::ChefFS::FileSystem::MemoryDir.new(name, parent)
+      value.each do |key, child|
+        dir.add_child(memory_fs_value(child, key.to_s, dir))
+      end
+      dir
+    else
+      Chef::ChefFS::FileSystem::MemoryFile.new(name, parent, value || "#{name}\n")
+    end
+  end
+
+  def pattern(p)
+    Chef::ChefFS::FilePattern.new(p)
+  end
+
+  def return_paths(*expected)
+    ReturnPaths.new(expected)
+  end
+
+  def no_blocking_calls_allowed
+    [ Chef::ChefFS::FileSystem::MemoryFile, Chef::ChefFS::FileSystem::MemoryDir ].each do |c|
+      [ :children, :exists?, :read ].each do |m|
+        c.any_instance.stub(m).and_raise("#{m.to_s} should not be called")
+      end
+    end
+  end
+
+  def list_should_yield_paths(fs, pattern_str, *expected_paths)
+    result_paths = []
+    Chef::ChefFS::FileSystem.list(fs, pattern(pattern_str)).each { |result| result_paths << result.path }
+    result_paths.should =~ expected_paths
+  end
+end
+
diff --git a/spec/support/shared/unit/platform_introspector.rb b/spec/support/shared/unit/platform_introspector.rb
new file mode 100644
index 0000000..d596e29
--- /dev/null
+++ b/spec/support/shared/unit/platform_introspector.rb
@@ -0,0 +1,162 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010, 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.
+#
+
+
+shared_examples_for "a platform introspector" do
+  before(:each) do
+    @platform_hash = {}
+    %w{openbsd freebsd}.each do |x|
+      @platform_hash[x] = {
+        "default" => x,
+        "1.2.3" => "#{x}-1.2.3"
+      }
+    end
+    @platform_hash["debian"] = {["5", "6"] => "debian-5/6", "default" => "debian"}
+    @platform_hash["default"] = "default"
+
+    @platform_family_hash = {
+      "debian" => "debian value",
+      [:rhel, :fedora] => "redhatty value",
+      "suse" => "suse value",
+      :default => "default value"
+    }
+  end
+
+  it "returns a default value when there is no known platform" do
+    node = Hash.new
+    platform_introspector.value_for_platform(@platform_hash).should == "default"
+  end
+
+  it "returns a default value when there is no known platform family" do
+    platform_introspector.value_for_platform_family(@platform_family_hash).should == "default value"
+  end
+
+  it "returns a default value when the current platform doesn't match" do
+    node.automatic_attrs[:platform] = "not-a-known-platform"
+    platform_introspector.value_for_platform(@platform_hash).should == "default"
+  end
+
+  it "returns a default value when current platform_family doesn't match" do
+    node.automatic_attrs[:platform_family] = "ultra-derived-linux"
+    platform_introspector.value_for_platform_family(@platform_family_hash).should == "default value"
+  end
+
+  it "returns a value based on the current platform" do
+    node.automatic_attrs[:platform] = "openbsd"
+    platform_introspector.value_for_platform(@platform_hash).should == "openbsd"
+  end
+
+  it "returns a value based on the current platform family" do
+    node.automatic_attrs[:platform_family] = "debian"
+    platform_introspector.value_for_platform_family(@platform_family_hash).should == "debian value"
+  end
+
+  it "returns a version-specific value based on the current platform" do
+    node.automatic_attrs[:platform] = "openbsd"
+    node.automatic_attrs[:platform_version] = "1.2.3"
+    platform_introspector.value_for_platform(@platform_hash).should == "openbsd-1.2.3"
+  end
+
+  it "returns a value based on the current platform if version not found" do
+    node.automatic_attrs[:platform] = "openbsd"
+    node.automatic_attrs[:platform_version] = "0.0.0"
+    platform_introspector.value_for_platform(@platform_hash).should == "openbsd"
+  end
+
+  describe "when platform versions is an array" do
+    it "returns a version-specific value based on the current platform" do
+      node.automatic_attrs[:platform] = "debian"
+      node.automatic_attrs[:platform_version] = "6"
+      platform_introspector.value_for_platform(@platform_hash).should == "debian-5/6"
+    end
+
+    it "returns a value based on the current platform if version not found" do
+      node.automatic_attrs[:platform] = "debian"
+      node.automatic_attrs[:platform_version] = "0.0.0"
+      platform_introspector.value_for_platform(@platform_hash).should == "debian"
+    end
+  end
+
+  describe "when checking platform?" do
+
+    it "returns true if the node is a provided platform and platforms are provided as symbols" do
+      node.automatic_attrs[:platform] = 'ubuntu'
+      platform_introspector.platform?([:redhat, :ubuntu]).should == true
+    end
+
+    it "returns true if the node is a provided platform and platforms are provided as strings" do
+      node.automatic_attrs[:platform] = 'ubuntu'
+      platform_introspector.platform?(["redhat", "ubuntu"]).should == true
+    end
+
+    it "returns false if the node is not of the provided platforms" do
+      node.automatic_attrs[:platform] = 'ubuntu'
+      platform_introspector.platform?(:splatlinux).should == false
+    end
+  end
+
+  describe "when checking platform_family?" do
+
+    it "returns true if the node is in a provided platform family and families are provided as symbols" do
+      node.automatic_attrs[:platform_family] = 'debian'
+      platform_introspector.platform_family?([:rhel, :debian]).should == true
+    end
+
+    it "returns true if the node is a provided platform and platforms are provided as strings" do
+      node.automatic_attrs[:platform_family] = 'rhel'
+      platform_introspector.platform_family?(["rhel", "debian"]).should == true
+    end
+
+    it "returns false if the node is not of the provided platforms" do
+      node.automatic_attrs[:platform_family] = 'suse'
+      platform_introspector.platform_family?(:splatlinux).should == false
+    end
+
+    it "returns false if the node is not of the provided platforms and platform_family is not set" do
+      platform_introspector.platform_family?(:splatlinux).should == false
+    end
+
+  end
+  # NOTE: this is a regression test for bug CHEF-1514
+  describe "when the value is an array" do
+    before do
+      @platform_hash = {
+        "debian" => { "4.0" => [ :restart, :reload ], "default" => [ :restart, :reload, :status ] },
+        "ubuntu" => { "default" => [ :restart, :reload, :status ] },
+        "centos" => { "default" => [ :restart, :reload, :status ] },
+        "redhat" => { "default" => [ :restart, :reload, :status ] },
+        "fedora" => { "default" => [ :restart, :reload, :status ] },
+        "default" => { "default" => [:restart, :reload ] }}
+    end
+
+    it "returns the correct default for a given platform" do
+      node.automatic_attrs[:platform] = "debian"
+      node.automatic_attrs[:platform_version] = '9000'
+      platform_introspector.value_for_platform(@platform_hash).should == [ :restart, :reload, :status ]
+    end
+
+    it "returns the correct platform+version specific value " do
+      node.automatic_attrs[:platform] = "debian"
+      node.automatic_attrs[:platform_version] = '4.0'
+      platform_introspector.value_for_platform(@platform_hash).should == [:restart, :reload]
+    end
+  end
+
+end
+
diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb
new file mode 100644
index 0000000..11e991d
--- /dev/null
+++ b/spec/support/shared/unit/provider/file.rb
@@ -0,0 +1,609 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tmpdir'
+if windows?
+  require 'chef/win32/file'
+end
+
+# Filesystem stubs
+def file_symlink_class
+  if windows?
+    Chef::ReservedNames::Win32::File
+  else
+    File
+  end
+end
+
+def normalized_path
+  File.expand_path(resource_path)
+end
+
+def setup_normal_file
+  File.stub!(:exists?).with(resource_path).and_return(true)
+  File.stub!(:directory?).with(resource_path).and_return(false)
+  File.stub!(:directory?).with(enclosing_directory).and_return(true)
+  File.stub!(:writable?).with(resource_path).and_return(true)
+  file_symlink_class.stub!(:symlink?).with(resource_path).and_return(false)
+  file_symlink_class.stub!(:symlink?).with(normalized_path).and_return(false)
+end
+
+def setup_missing_file
+  File.stub!(:exists?).with(resource_path).and_return(false)
+  File.stub!(:directory?).with(resource_path).and_return(false)
+  File.stub!(:directory?).with(enclosing_directory).and_return(true)
+  File.stub!(:writable?).with(resource_path).and_return(false)
+  file_symlink_class.stub!(:symlink?).with(resource_path).and_return(false)
+end
+
+def setup_symlink
+  File.stub!(:exists?).with(resource_path).and_return(true)
+  File.stub!(:directory?).with(normalized_path).and_return(false)
+  File.stub!(:directory?).with(enclosing_directory).and_return(true)
+  File.stub!(:writable?).with(resource_path).and_return(true)
+  file_symlink_class.stub!(:symlink?).with(resource_path).and_return(true)
+  file_symlink_class.stub!(:symlink?).with(normalized_path).and_return(true)
+end
+
+def setup_unwritable_file
+  File.stub!(:exists?).with(resource_path).and_return(true)
+  File.stub!(:directory?).with(resource_path).and_return(false)
+  File.stub!(:directory?).with(enclosing_directory).and_return(true)
+  File.stub!(:writable?).with(resource_path).and_return(false)
+  file_symlink_class.stub!(:symlink?).with(resource_path).and_return(false)
+end
+
+def setup_missing_enclosing_directory
+  File.stub!(:exists?).with(resource_path).and_return(false)
+  File.stub!(:directory?).with(resource_path).and_return(false)
+  File.stub!(:directory?).with(enclosing_directory).and_return(false)
+  File.stub!(:writable?).with(resource_path).and_return(false)
+  file_symlink_class.stub!(:symlink?).with(resource_path).and_return(false)
+end
+
+shared_examples_for Chef::Provider::File do
+
+  it "should return a #{described_class}" do
+    provider.should be_a_kind_of(described_class)
+  end
+
+  it "should store the resource passed to new as new_resource" do
+    provider.new_resource.should eql(resource)
+  end
+
+  it "should store the node passed to new as node" do
+    provider.node.should eql(node)
+  end
+
+  context "when loading the current resource" do
+
+    context "when running load_current_resource and the file exists" do
+      before do
+        setup_normal_file
+        provider.load_current_resource
+      end
+
+      it "should load a current resource based on the one specified at construction" do
+        provider.current_resource.should be_a_kind_of(Chef::Resource::File)
+      end
+
+      it "the loaded current_resource name should be the same as the resource name" do
+        provider.current_resource.name.should eql(resource.name)
+      end
+
+      it "the loaded current_resource path should be the same as the resoure path" do
+        provider.current_resource.path.should eql(resource.path)
+      end
+
+      it "the loaded current_resource content should be nil" do
+        provider.current_resource.content.should eql(nil)
+      end
+    end
+
+    context "when running load_current_resource and the file does not exist" do
+      before do
+        setup_missing_file
+        provider.load_current_resource
+      end
+
+      it "the current_resource should be a Chef::Resource::File" do
+        provider.current_resource.should be_a_kind_of(Chef::Resource::File)
+      end
+
+      it "the current_resource name should be the same as the resource name" do
+        provider.current_resource.name.should eql(resource.name)
+      end
+
+      it "the current_resource path should be the same as the resource path" do
+        provider.current_resource.path.should eql(resource.path)
+      end
+
+      it "the loaded current_resource content should be nil" do
+        provider.current_resource.content.should eql(nil)
+      end
+    end
+
+    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
+        Chef::Platform.stub!(:windows?).and_return(false)
+        # mock up the filesystem to behave like unix
+        setup_normal_file
+        stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+        resource_real_path = File.realpath(resource.path)
+        File.should_receive(:stat).with(resource_real_path).at_least(:once).and_return(stat_struct)
+        Etc.stub!(:getgrgid).with(0).and_return(mock("Group Ent", :name => "wheel"))
+        Etc.stub!(:getpwuid).with(0).and_return(mock("User Ent", :name => "root"))
+      end
+
+      context "when the new_resource does not specify any state" do
+        before do
+          provider.load_current_resource
+        end
+
+        it "should load the permissions into the current_resource" do
+          provider.current_resource.mode.should == "0600"
+          provider.current_resource.owner.should == "root"
+          provider.current_resource.group.should == "wheel"
+        end
+
+        it "should not set the new_resource permissions" do
+          provider.new_resource.group.should be_nil
+          provider.new_resource.owner.should be_nil
+          provider.new_resource.mode.should be_nil
+        end
+      end
+
+      context "when the new_resource explicitly specifies resource state as numbers" do
+        before do
+          resource.owner(1)
+          resource.group(1)
+          resource.mode(0644)
+          provider.load_current_resource
+        end
+
+        it "should load the permissions into the current_resource as numbers" do
+          # Mode is always loaded as string for reporting purposes.
+          provider.current_resource.mode.should == "0600"
+          provider.current_resource.owner.should == 0
+          provider.current_resource.group.should == 0
+        end
+
+        it "should not set the new_resource permissions" do
+          provider.new_resource.group.should == 1
+          provider.new_resource.owner.should == 1
+          provider.new_resource.mode.should == 0644
+        end
+      end
+
+      context "when the new_resource explicitly specifies resource state as symbols" do
+        before do
+          resource.owner("macklemore")
+          resource.group("seattlehiphop")
+          resource.mode("0321")
+          provider.load_current_resource
+        end
+
+        it "should load the permissions into the current_resource as symbols" do
+          provider.current_resource.mode.should == "0600"
+          provider.current_resource.owner.should == "root"
+          provider.current_resource.group.should == "wheel"
+        end
+
+        it "should not set the new_resource permissions" do
+          provider.new_resource.group.should == "seattlehiphop"
+          provider.new_resource.owner.should == "macklemore"
+          provider.new_resource.mode.should == "0321"
+        end
+      end
+
+    end
+
+    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
+        Chef::Platform.stub!(:windows?).and_return(false)
+        setup_missing_file
+      end
+
+      context "when the new_resource does not specify any state" do
+        before do
+          provider.load_current_resource
+        end
+
+        it "the current_resource permissions should be nil" do
+          provider.current_resource.mode.should be_nil
+          provider.current_resource.owner.should be_nil
+          provider.current_resource.group.should be_nil
+        end
+
+        it "should not set the new_resource permissions" do
+          provider.new_resource.group.should be_nil
+          provider.new_resource.owner.should be_nil
+          provider.new_resource.mode.should be_nil
+        end
+      end
+
+      context "when the new_resource explicitly specifies resource state" do
+        before do
+          resource.owner(63945)
+          resource.group(51948)
+          resource.mode(0123)
+          provider.load_current_resource
+        end
+
+        it "the current_resource permissions should be nil" do
+          provider.current_resource.mode.should be_nil
+          provider.current_resource.owner.should be_nil
+          provider.current_resource.group.should be_nil
+        end
+
+        it "should not set the new_resource permissions" do
+          provider.new_resource.group.should == 51948
+          provider.new_resource.owner.should == 63945
+          provider.new_resource.mode.should == 0123
+        end
+      end
+    end
+  end
+
+  context "when loading the new_resource after the run" do
+
+    before do
+      # fake that we're on unix even if we're on windows
+      Chef::Platform.stub!(:windows?).and_return(false)
+      # mock up the filesystem to behave like unix
+      setup_normal_file
+      stat_struct = mock("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
+      resource_real_path = File.realpath(resource.path)
+      File.stub!(:stat).with(resource_real_path).and_return(stat_struct)
+      Etc.stub!(:getgrgid).with(0).and_return(mock("Group Ent", :name => "wheel"))
+      Etc.stub!(:getpwuid).with(0).and_return(mock("User Ent", :name => "root"))
+      provider.send(:load_resource_attributes_from_file, resource)
+    end
+
+    it "new_resource should record the new permission information" do
+      provider.new_resource.group.should == "wheel"
+      provider.new_resource.owner.should == "root"
+      provider.new_resource.mode.should == "0600"
+    end
+  end
+
+  context "when reporting security metadata on windows" do
+    it "records the file owner" do
+      pending
+    end
+
+    it "records rights for each user in the ACL" do
+      pending
+    end
+
+    it "records deny_rights for each user in the ACL" do
+      pending
+    end
+  end
+
+  context "define_resource_requirements" do
+    context "when the enclosing directory does not exist" do
+      before { setup_missing_enclosing_directory }
+
+      [:create, :create_if_missing, :touch].each do |action|
+        context "action #{action}" do
+          it "raises EnclosingDirectoryDoesNotExist" do
+            lambda {provider.run_action(action)}.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+          end
+
+          it "does not raise an exception in why-run mode" do
+            Chef::Config[:why_run] = true
+            lambda {provider.run_action(action)}.should_not raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+            Chef::Config[:why_run] = false
+          end
+        end
+      end
+    end
+
+    context "when the file exists but is not deletable" do
+      before { setup_unwritable_file }
+
+      it "action delete raises InsufficientPermissions" do
+        lambda {provider.run_action(:delete)}.should raise_error(Chef::Exceptions::InsufficientPermissions)
+      end
+
+      it "action delete also raises InsufficientPermissions in why-run mode" do
+        Chef::Config[:why_run] = true
+        lambda {provider.run_action(:delete)}.should raise_error(Chef::Exceptions::InsufficientPermissions)
+        Chef::Config[:why_run] = false
+      end
+    end
+  end
+
+  context "action create" do
+    it "should create the file, update its contents and then set the acls on the file"  do
+      setup_missing_file
+      provider.should_receive(:do_create_file)
+      provider.should_receive(:do_contents_changes)
+      provider.should_receive(:do_acl_changes)
+      provider.should_receive(:load_resource_attributes_from_file)
+      provider.run_action(:create)
+    end
+
+    context "do_create_file" do
+      context "when the file exists" do
+        before { setup_normal_file }
+        it "should not create the file" do
+          provider.deployment_strategy.should_not_receive(:create).with(resource_path)
+          provider.send(:do_create_file)
+          provider.send(:file_created?).should == false
+        end
+      end
+      context "when the file does not exist" do
+        before { setup_missing_file }
+        it "should create the file" do
+          provider.deployment_strategy.should_receive(:create).with(resource_path)
+          provider.send(:do_create_file)
+          provider.send(:file_created?).should == true
+        end
+      end
+    end
+
+    context "do_contents_changes" do
+      context "when there is content to deploy" do
+        before do
+          setup_normal_file
+          provider.load_current_resource
+          tempfile = double('Tempfile', :path => "/tmp/foo-bar-baz")
+          content.stub!(:tempfile).and_return(tempfile)
+          File.should_receive(:exists?).with("/tmp/foo-bar-baz").and_return(true)
+          tempfile.should_receive(:unlink).once
+        end
+
+        context "when the contents have changed" do
+          let(:tempfile_path) { "/tmp/foo-bar-baz" }
+          let(:tempfile_sha256) { "42971f0ddce0cb20cf7660a123ffa1a1543beb2f1e7cd9d65858764a27f3201d" }
+          let(:diff_for_reporting) { "+++\n---\n+foo\n-bar\n" }
+          before do
+            provider.stub!(:contents_changed?).and_return(true)
+            diff = double('Diff', :for_output => ['+++','---','+foo','-bar'],
+                                  :for_reporting => diff_for_reporting )
+            diff.stub!(:diff).with(resource_path, tempfile_path).and_return(true)
+            provider.should_receive(:diff).at_least(:once).and_return(diff)
+            provider.should_receive(:checksum).with(tempfile_path).and_return(tempfile_sha256)
+            provider.should_receive(:checksum).with(resource_path).and_return(tempfile_sha256)
+            provider.deployment_strategy.should_receive(:deploy).with(tempfile_path, normalized_path)
+          end
+          context "when the file was created" do
+            before { provider.should_receive(:file_created?).at_least(:once).and_return(true) }
+            it "does not backup the file and does not produce a diff for reporting" do
+              provider.should_not_receive(:do_backup)
+              provider.send(:do_contents_changes)
+              resource.diff.should be_nil
+            end
+          end
+          context "when the file was not created" do
+            before { provider.should_receive(:file_created?).at_least(:once).and_return(false) }
+            it "backs up the file and produces a diff for reporting" do
+              provider.should_receive(:do_backup)
+              provider.send(:do_contents_changes)
+              resource.diff.should == diff_for_reporting
+            end
+          end
+        end
+
+        it "does nothing when the contents have not changed"  do
+          provider.stub!(:contents_changed?).and_return(false)
+          provider.should_not_receive(:diff)
+          provider.send(:do_contents_changes)
+        end
+      end
+
+      it "does nothing when there is no content to deploy (tempfile returned from contents is nil)" do
+        provider.send(:content).should_receive(:tempfile).at_least(:once).and_return(nil)
+        provider.should_not_receive(:diff)
+        lambda{ provider.send(:do_contents_changes) }.should_not raise_error
+      end
+
+      it "raises an exception when the content object returns a tempfile with a nil path" do
+        tempfile = double('Tempfile', :path => nil)
+        provider.send(:content).should_receive(:tempfile).at_least(:once).and_return(tempfile)
+        lambda{ provider.send(:do_contents_changes) }.should raise_error
+      end
+
+      it "raises an exception when the content object returns a tempfile that does not exist" do
+        tempfile = double('Tempfile', :path => "/tmp/foo-bar-baz")
+        provider.send(:content).should_receive(:tempfile).at_least(:once).and_return(tempfile)
+        File.should_receive(:exists?).with("/tmp/foo-bar-baz").and_return(false)
+        lambda{ provider.send(:do_contents_changes) }.should raise_error
+      end
+    end
+
+    context "do_acl_changes" do
+      it "needs tests" do
+        pending
+      end
+    end
+
+    context "do_selinux" do
+      context "when resource is updated" do
+        before do
+          setup_normal_file
+          provider.load_current_resource
+          provider.stub!(:resource_updated?).and_return(true)
+        end
+
+        it "should check for selinux_enabled? by default" do
+          provider.should_receive(:selinux_enabled?)
+          provider.send(:do_selinux)
+        end
+
+        context "when selinux fixup is enabled in the config" do
+          before do
+            @original_selinux_fixup = Chef::Config[:enable_selinux_file_permission_fixup]
+            Chef::Config[:enable_selinux_file_permission_fixup] = true
+          end
+
+          after do
+            Chef::Config[:enable_selinux_file_permission_fixup] = @original_selinux_fixup
+          end
+
+          context "when selinux is enabled on the system" do
+            before do
+              provider.should_receive(:selinux_enabled?).and_return(true)
+            end
+
+            it "restores security context on the file" do
+              provider.should_receive(:restore_security_context).with(normalized_path, false)
+              provider.send(:do_selinux)
+            end
+
+            it "restores security context recursively when told so" do
+              provider.should_receive(:restore_security_context).with(normalized_path, true)
+              provider.send(:do_selinux, true)
+            end
+          end
+
+          context "when selinux is disabled on the system" do
+            before do
+              provider.should_receive(:selinux_enabled?).and_return(false)
+            end
+
+            it "should not restore security context" do
+              provider.should_not_receive(:restore_security_context)
+              provider.send(:do_selinux)
+            end
+          end
+        end
+
+        context "when selinux fixup is disabled in the config" do
+          before do
+            @original_selinux_fixup = Chef::Config[:enable_selinux_file_permission_fixup]
+            Chef::Config[:enable_selinux_file_permission_fixup] = false
+          end
+
+          after do
+            Chef::Config[:enable_selinux_file_permission_fixup] = @original_selinux_fixup
+          end
+
+          it "should not check for selinux_enabled?" do
+            provider.should_not_receive(:selinux_enabled?)
+            provider.send(:do_selinux)
+          end
+        end
+      end
+
+      context "when resource is not updated" do
+        before do
+          provider.stub!(:resource_updated?).and_return(false)
+        end
+
+        it "should not check for selinux_enabled?" do
+          provider.should_not_receive(:selinux_enabled?)
+          provider.send(:do_selinux)
+        end
+      end
+    end
+
+  end
+
+  context "action delete" do
+    context "when the file exists" do
+      context "when the file is writable" do
+        context "when the file is not a symlink" do
+          before { setup_normal_file }
+          it "should backup and delete the file and be updated by the last action" do
+            provider.should_receive(:do_backup).at_least(:once).and_return(true)
+            File.should_receive(:delete).with(resource_path).and_return(true)
+            provider.run_action(:delete)
+            resource.should be_updated_by_last_action
+          end
+        end
+        context "when the file is a symlink" do
+          before { setup_symlink }
+          it "should not backup the symlink" do
+            provider.should_not_receive(:do_backup)
+            File.should_receive(:delete).with(resource_path).and_return(true)
+            provider.run_action(:delete)
+            resource.should be_updated_by_last_action
+          end
+        end
+      end
+      context "when the file is not writable" do
+        before { setup_unwritable_file }
+        it "should not try to backup or delete the file, and should not be updated by last action" do
+          provider.should_not_receive(:do_backup)
+          File.should_not_receive(:delete)
+          lambda { provider.run_action(:delete) }.should raise_error()
+          resource.should_not be_updated_by_last_action
+        end
+      end
+    end
+
+    context "when the file does not exist" do
+      before { setup_missing_file }
+
+      it "should not try to backup or delete the file, and should not be updated by last action" do
+        provider.should_not_receive(:do_backup)
+        File.should_not_receive(:delete)
+        lambda { provider.run_action(:delete) }.should_not raise_error()
+        resource.should_not be_updated_by_last_action
+      end
+    end
+  end
+
+  context "action touch" do
+    context "when the file does not exist" do
+      before { setup_missing_file }
+      it "should update the atime/mtime on action_touch" do
+        File.should_receive(:utime).once
+        provider.should_receive(:action_create)
+        provider.run_action(:touch)
+        resource.should be_updated_by_last_action
+      end
+    end
+    context "when the file exists" do
+      before { setup_normal_file }
+      it "should update the atime/mtime on action_touch" do
+        File.should_receive(:utime).once
+        provider.should_receive(:action_create)
+        provider.run_action(:touch)
+        resource.should be_updated_by_last_action
+      end
+    end
+  end
+
+  context "action create_if_missing" do
+    context "when the file does not exist" do
+      before { setup_missing_file }
+      it "should call action_create" do
+        provider.should_receive(:action_create)
+        provider.run_action(:create_if_missing)
+      end
+    end
+
+    context "when the file exists" do
+      before { setup_normal_file }
+      it "should not call action_create" do
+        provider.should_not_receive(:action_create)
+        provider.run_action(:create_if_missing)
+      end
+    end
+
+  end
+
+end
+
diff --git a/spec/support/shared/unit/provider/useradd_based_user_provider.rb b/spec/support/shared/unit/provider/useradd_based_user_provider.rb
new file mode 100644
index 0000000..3b8f867
--- /dev/null
+++ b/spec/support/shared/unit/provider/useradd_based_user_provider.rb
@@ -0,0 +1,407 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010, 2013 Opscode, Inc.
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+shared_examples_for "a useradd-based user provider" do |supported_useradd_options|
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::User.new("adam", @run_context)
+    @new_resource.comment "Adam Jacob"
+    @new_resource.uid 1000
+    @new_resource.gid 1000
+    @new_resource.home "/home/adam"
+    @new_resource.shell "/usr/bin/zsh"
+    @new_resource.password "abracadabra"
+    @new_resource.system false
+    @new_resource.manage_home false
+    @new_resource.non_unique false
+    @current_resource = Chef::Resource::User.new("adam", @run_context)
+    @current_resource.comment "Adam Jacob"
+    @current_resource.uid 1000
+    @current_resource.gid 1000
+    @current_resource.home "/home/adam"
+    @current_resource.shell "/usr/bin/zsh"
+    @current_resource.password "abracadabra"
+    @current_resource.system false
+    @current_resource.manage_home false
+    @current_resource.non_unique false
+    @current_resource.supports({:manage_home => false, :non_unique => false})
+  end
+
+  describe "when setting option" do
+
+    supported_useradd_options.each do |attribute, option|
+      it "should check for differences in #{attribute} between the new and current resources" do
+        @current_resource.should_receive(attribute)
+        @new_resource.should_receive(attribute)
+        provider.universal_options
+      end
+
+      it "should set the option for #{attribute} if the new resources #{attribute} is not nil" do
+        @new_resource.stub!(attribute).and_return("hola")
+        provider.universal_options.should eql([option, 'hola'])
+      end
+
+      it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management" do
+        @new_resource.stub!(:supports).and_return({:manage_home => false,
+                                                    :non_unique => false})
+        @new_resource.stub!(attribute).and_return("hola")
+        provider.universal_options.should eql([option, 'hola'])
+      end
+
+      it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management (using real attributes)" do
+        @new_resource.stub!(:manage_home).and_return(false)
+        @new_resource.stub!(:non_unique).and_return(false)
+        @new_resource.stub!(attribute).and_return("hola")
+        provider.universal_options.should eql([option, 'hola'])
+      end
+    end
+
+    it "should combine all the possible options" do
+      combined_opts = []
+      supported_useradd_options.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option|
+        @new_resource.stub!(attribute).and_return("hola")
+        combined_opts << option << 'hola'
+      end
+      provider.universal_options.should eql(combined_opts)
+    end
+
+    describe "when we want to create a system user" do
+      before do
+        @new_resource.manage_home(true)
+        @new_resource.non_unique(false)
+      end
+
+      it "should set useradd -r" do
+        @new_resource.system(true)
+        provider.useradd_options.should == [ "-r" ]
+      end
+    end
+
+    describe "when the resource has a different home directory and supports home directory management" do
+      before do
+        @new_resource.stub!(:home).and_return("/wowaweea")
+        @new_resource.stub!(:supports).and_return({:manage_home => true,
+                                                   :non_unique => false})
+      end
+
+      it "should set -m -d /homedir" do
+        provider.universal_options.should == %w[-d /wowaweea -m]
+        provider.useradd_options.should == []
+      end
+    end
+
+    describe "when the resource has a different home directory and supports home directory management (using real attributes)" do
+      before do
+        @new_resource.stub!(:home).and_return("/wowaweea")
+        @new_resource.stub!(:manage_home).and_return(true)
+        @new_resource.stub!(:non_unique).and_return(false)
+      end
+
+      it "should set -m -d /homedir" do
+        provider.universal_options.should eql(%w[-d /wowaweea -m])
+        provider.useradd_options.should == []
+      end
+    end
+
+    describe "when the resource supports non_unique ids" do
+      before do
+        @new_resource.stub!(:supports).and_return({:manage_home => false,
+                                                  :non_unique => true})
+      end
+
+      it "should set -m -o" do
+        provider.universal_options.should eql([ "-o" ])
+      end
+    end
+
+    describe "when the resource supports non_unique ids (using real attributes)" do
+      before do
+        @new_resource.stub!(:manage_home).and_return(false)
+        @new_resource.stub!(:non_unique).and_return(true)
+      end
+
+      it "should set -m -o" do
+        provider.universal_options.should eql([ "-o" ])
+      end
+    end
+  end
+
+  describe "when creating a user" do
+    before(:each) do
+      @current_resource = Chef::Resource::User.new(@new_resource.name, @run_context)
+      @current_resource.username(@new_resource.username)
+      provider.current_resource = @current_resource
+      provider.new_resource.manage_home true
+      provider.new_resource.home "/Users/mud"
+      provider.new_resource.gid '23'
+    end
+
+    it "runs useradd with the computed command options" do
+      command = ["useradd",
+                  "-c",  'Adam Jacob',
+                  "-g", '23' ]
+      command.concat(["-p", 'abracadabra']) if supported_useradd_options.key?("password")
+      command.concat([ "-s", '/usr/bin/zsh',
+                       "-u", '1000',
+                       "-d", '/Users/mud',
+                       "-m",
+                       "adam" ])
+      provider.should_receive(:shell_out!).with(*command).and_return(true)
+      provider.create_user
+    end
+
+    describe "and home is not specified for new system user resource" do
+
+      before do
+        provider.new_resource.system true
+        # there is no public API to set attribute's value to nil
+        provider.new_resource.instance_variable_set("@home", nil)
+      end
+
+      it "should not include -m or -d in the command options" do
+        command = ["useradd",
+                    "-c", 'Adam Jacob',
+                    "-g", '23']
+        command.concat(["-p", 'abracadabra']) if supported_useradd_options.key?("password")
+        command.concat([ "-s", '/usr/bin/zsh',
+                         "-u", '1000',
+                         "-r",
+                         "adam" ])
+        provider.should_receive(:shell_out!).with(*command).and_return(true)
+        provider.create_user
+      end
+
+    end
+
+  end
+
+  describe "when managing a user" do
+    before(:each) do
+      provider.new_resource.manage_home true
+      provider.new_resource.home "/Users/mud"
+      provider.new_resource.gid '23'
+    end
+
+    # CHEF-3423, -m must come before the username
+    # CHEF-4305, -d must come before -m to support CentOS/RHEL 5
+    it "runs usermod with the computed command options" do
+      command = ["usermod",
+                  "-g", '23',
+                  "-d", '/Users/mud',
+                  "-m",
+                  "adam" ]
+      provider.should_receive(:shell_out!).with(*command).and_return(true)
+      provider.manage_user
+    end
+
+    it "does not set the -r option to usermod" do
+      @new_resource.system(true)
+      command = ["usermod",
+                  "-g", '23',
+                  "-d", '/Users/mud',
+                  "-m",
+                  "adam" ]
+      provider.should_receive(:shell_out!).with(*command).and_return(true)
+      provider.manage_user
+    end
+
+    it "CHEF-3429: does not set -m if we aren't changing the home directory" do
+      provider.should_receive(:updating_home?).and_return(false)
+      command = ["usermod",
+                  "-g", '23',
+                  "adam" ]
+      provider.should_receive(:shell_out!).with(*command).and_return(true)
+      provider.manage_user
+    end
+  end
+
+  describe "when removing a user" do
+
+    it "should run userdel with the new resources user name" do
+      provider.should_receive(:shell_out!).with("userdel", @new_resource.username).and_return(true)
+      provider.remove_user
+    end
+
+    it "should run userdel with the new resources user name and -r if manage_home is true" do
+      @new_resource.supports({ :manage_home => true,
+                               :non_unique => false})
+      provider.should_receive(:shell_out!).with("userdel", "-r", @new_resource.username).and_return(true)
+      provider.remove_user
+    end
+
+    it "should run userdel with the new resources user name if non_unique is true" do
+      @new_resource.supports({ :manage_home => false,
+                               :non_unique => true})
+      provider.should_receive(:shell_out!).with("userdel", @new_resource.username).and_return(true)
+      provider.remove_user
+    end
+  end
+
+  describe "when checking the lock" do
+    # lazy initialize so we can modify stdout and stderr strings
+    let(:passwd_s_status) do
+      mock("Mixlib::ShellOut command", :exitstatus => 0, :stdout => @stdout, :stderr => @stderr)
+    end
+
+    before(:each) do
+      # @node = Chef::Node.new
+      # @new_resource = mock("Chef::Resource::User",
+      #   :nil_object => true,
+      #   :username => "adam"
+      # )
+      #provider = Chef::Provider::User::Useradd.new(@node, @new_resource)
+      @stdout = "root P 09/02/2008 0 99999 7 -1"
+      @stderr = ""
+    end
+
+    it "should return false if status begins with P" do
+      provider.should_receive(:shell_out!).
+        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
+        and_return(passwd_s_status)
+      provider.check_lock.should eql(false)
+    end
+
+    it "should return false if status begins with N" do
+      @stdout = "root N"
+      provider.should_receive(:shell_out!).
+        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
+        and_return(passwd_s_status)
+      provider.check_lock.should eql(false)
+    end
+
+    it "should return true if status begins with L" do
+      @stdout = "root L"
+      provider.should_receive(:shell_out!).
+        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
+        and_return(passwd_s_status)
+      provider.check_lock.should eql(true)
+    end
+
+    it "should raise a Chef::Exceptions::User if passwd -S fails on anything other than redhat/centos" do
+      @node.automatic_attrs[:platform] = 'ubuntu'
+      provider.should_receive(:shell_out!).
+        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
+        and_return(passwd_s_status)
+      passwd_s_status.should_receive(:exitstatus).and_return(1)
+      lambda { provider.check_lock }.should raise_error(Chef::Exceptions::User)
+    end
+
+    ['redhat', 'centos'].each do |os|
+      it "should not raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is version 0.73-1" do
+        @node.automatic_attrs[:platform] = os
+        passwd_s_status.should_receive(:exitstatus).and_return(1)
+        provider.should_receive(:shell_out!).
+          with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
+          and_return(passwd_s_status)
+        rpm_status = mock("Mixlib::ShellOut command", :exitstatus => 0, :stdout => "passwd-0.73-1\n", :stderr => "")
+        provider.should_receive(:shell_out!).with("rpm -q passwd").and_return(rpm_status)
+        lambda { provider.check_lock }.should_not raise_error(Chef::Exceptions::User)
+      end
+
+      it "should raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is not version 0.73-1" do
+        @node.automatic_attrs[:platform] = os
+        passwd_s_status.should_receive(:exitstatus).and_return(1)
+        provider.should_receive(:shell_out!).
+          with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
+          and_return(passwd_s_status)
+        rpm_status = mock("Mixlib::ShellOut command", :exitstatus => 0, :stdout => "passwd-0.73-2\n", :stderr => "")
+        provider.should_receive(:shell_out!).with("rpm -q passwd").and_return(rpm_status)
+        lambda { provider.check_lock }.should raise_error(Chef::Exceptions::User)
+      end
+
+      it "should raise a ShellCommandFailed exception if passwd -S exits with something other than 0 or 1 on #{os}" do
+        @node.automatic_attrs[:platform] = os
+        provider.should_receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+        lambda { provider.check_lock }.should raise_error(Mixlib::ShellOut::ShellCommandFailed)
+      end
+    end
+  end
+
+  describe "when locking the user" do
+    it "should run usermod -L with the new resources username" do
+      provider.should_receive(:shell_out!).with("usermod", "-L", @new_resource.username)
+      provider.lock_user
+    end
+  end
+
+  describe "when unlocking the user" do
+    it "should run usermod -L with the new resources username" do
+      provider.should_receive(:shell_out!).with("usermod", "-U", @new_resource.username)
+      provider.unlock_user
+    end
+  end
+
+  describe "when checking if home needs updating" do
+    [
+     {
+       "action" => "should return false if home matches",
+       "current_resource_home" => [ "/home/laurent" ],
+       "new_resource_home" => [ "/home/laurent" ],
+       "expected_result" => false
+     },
+     {
+       "action" => "should return true if home doesn't match",
+       "current_resource_home" => [ "/home/laurent" ],
+       "new_resource_home" => [ "/something/else" ],
+       "expected_result" => true
+     },
+     {
+       "action" => "should return false if home only differs by trailing slash",
+       "current_resource_home" => [ "/home/laurent" ],
+       "new_resource_home" => [ "/home/laurent/", "/home/laurent" ],
+       "expected_result" => false
+     },
+     {
+       "action" => "should return false if home is an equivalent path",
+       "current_resource_home" => [ "/home/laurent" ],
+       "new_resource_home" => [ "/home/./laurent", "/home/laurent" ],
+       "expected_result" => false
+     },
+    ].each do |home_check|
+      it home_check["action"] do
+        provider.current_resource.home home_check["current_resource_home"].first
+        @current_home_mock = mock("Pathname")
+        provider.new_resource.home home_check["new_resource_home"].first
+        @new_home_mock = mock("Pathname")
+
+        Pathname.should_receive(:new).with(@current_resource.home).and_return(@current_home_mock)
+        @current_home_mock.should_receive(:cleanpath).and_return(home_check["current_resource_home"].last)
+        Pathname.should_receive(:new).with(@new_resource.home).and_return(@new_home_mock)
+        @new_home_mock.should_receive(:cleanpath).and_return(home_check["new_resource_home"].last)
+
+        provider.updating_home?.should == home_check["expected_result"]
+      end
+    end
+    it "should return true if the current home does not exist but a home is specified by the new resource" do
+      @new_resource = Chef::Resource::User.new("adam", @run_context)
+      @current_resource = Chef::Resource::User.new("adam", @run_context)
+      provider = Chef::Provider::User::Useradd.new(@new_resource, @run_context)
+      provider.current_resource = @current_resource
+      @current_resource.home nil
+      @new_resource.home "/home/kitten"
+
+      provider.updating_home?.should == true
+    end
+  end
+end
+
diff --git a/spec/support/shared/unit/script_resource.rb b/spec/support/shared/unit/script_resource.rb
new file mode 100644
index 0000000..5f37506
--- /dev/null
+++ b/spec/support/shared/unit/script_resource.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+shared_examples_for "a script resource" do
+
+  before(:each) do
+    @resource = script_resource
+  end
+
+  it "should create a new Chef::Resource::Script" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Script)
+  end
+
+  it "should have a resource name of :script" do
+    @resource.resource_name.should eql(resource_name)
+  end
+
+  it "should set command to the argument provided to new" do
+    @resource.command.should eql(resource_instance_name)
+  end
+
+  it "should accept a string for the code" do
+    @resource.code "hey jude"
+    @resource.code.should eql("hey jude")
+  end
+
+  it "should accept a string for the flags" do
+    @resource.flags "-f"
+    @resource.flags.should eql("-f")
+  end
+
+end
+
diff --git a/spec/support/shared/unit/windows_script_resource.rb b/spec/support/shared/unit/windows_script_resource.rb
new file mode 100644
index 0000000..23dbfbe
--- /dev/null
+++ b/spec/support/shared/unit/windows_script_resource.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'support/shared/unit/execute_resource'
+require 'support/shared/unit/script_resource'
+
+shared_examples_for "a Windows script resource" do
+  before(:each) do
+    node = Chef::Node.new
+
+    node.default["kernel"] = Hash.new
+    node.default["kernel"][:machine] = :x86_64.to_s
+
+    run_context = Chef::RunContext.new(node, nil, nil)
+
+    @resource = resource_instance
+
+  end
+
+  it "should be a kind of Chef::Resource::WindowsScript" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::WindowsScript)
+  end
+
+  context "script" do
+    let(:script_resource) { resource_instance }
+    it_should_behave_like "a script resource"
+  end
+
+end
+
diff --git a/spec/tiny_server.rb b/spec/tiny_server.rb
new file mode 100644
index 0000000..340c5d5
--- /dev/null
+++ b/spec/tiny_server.rb
@@ -0,0 +1,204 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'rubygems'
+require 'webrick'
+require 'webrick/https'
+require 'rack'
+#require 'thin'
+require 'singleton'
+require 'chef/json_compat'
+require 'open-uri'
+require 'chef/config'
+
+module TinyServer
+
+  class Server < Rack::Server
+
+    attr_writer :app
+
+    def self.setup(options=nil, &block)
+      tiny_app = new(options)
+      app_code = Rack::Builder.new(&block).to_app
+      tiny_app.app = app_code
+      tiny_app
+    end
+
+    def shutdown
+      server.shutdown
+    end
+  end
+
+  class Manager
+
+    # 5 == debug, 3 == warning
+    LOGGER = WEBrick::Log.new(STDOUT, 3)
+    DEFAULT_OPTIONS = {
+      :server => 'webrick',
+      :Port => 9000,
+      :Host => 'localhost',
+      :environment => :none,
+      :Logger => LOGGER,
+      :AccessLog => [] # Remove this option to enable the access log when debugging.
+    }
+
+    def initialize(options=nil)
+      @options = options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
+      @creator = caller.first
+    end
+
+    def start
+      @server_thread = Thread.new do
+        @server = Server.setup(@options) do
+          run API.instance
+        end
+        @server.start
+      end
+      block_until_started
+    end
+
+    def url
+      "http://localhost:#{@options[:Port]}"
+    end
+
+    def block_until_started
+      200.times do
+        if started? && !@server.nil?
+          return true
+        end
+      end
+      raise "ivar weirdness" if started? && @server.nil?
+      raise "TinyServer failed to boot :/"
+    end
+
+    def started?
+      open(url)
+      true
+    rescue OpenURI::HTTPError
+      true
+    rescue Errno::ECONNREFUSED, EOFError, Errno::ECONNRESET => e
+      sleep 0.1
+      # If the host has ":::1 localhost" in its hosts file and if IPv6
+      # is not enabled we can get NetworkUnreachable exception...
+    rescue Errno::ENETUNREACH => e
+      sleep 0.1
+      false
+    end
+
+    def stop
+      # yes, this is terrible.
+      @server.shutdown
+      @server_thread.kill
+      @server_thread.join
+      @server_thread = nil
+    end
+
+  end
+
+  class API
+    include Singleton
+
+    GET     = "GET"
+    PUT     = "PUT"
+    POST    = "POST"
+    DELETE  = "DELETE"
+
+    attr_reader :routes
+
+    def initialize
+      clear
+    end
+
+    def clear
+      @routes = {GET => [], PUT => [], POST => [], DELETE => []}
+    end
+
+    def get(path, response_code, data=nil, headers=nil, &block)
+      @routes[GET] << Route.new(path, Response.new(response_code, data, headers, &block))
+    end
+
+    def put(path, response_code, data=nil, headers=nil, &block)
+      @routes[PUT] << Route.new(path, Response.new(response_code, data, headers, &block))
+    end
+
+    def post(path, response_code, data=nil, headers=nil, &block)
+      @routes[POST] << Route.new(path, Response.new(response_code, data, headers, &block))
+    end
+
+    def delete(path, response_code, data=nil, headers=nil, &block)
+      @routes[DELETE] << Route.new(path, Response.new(response_code, data, headers, &block))
+    end
+
+    def call(env)
+      if response = response_for_request(env)
+        response.call
+      else
+        debug_info = {:message => "no data matches the request for #{env['REQUEST_URI']}",
+                      :available_routes => @routes, :request => env}
+        # Uncomment me for glorious debugging
+        # pp :not_found => debug_info
+        [404, {'Content-Type' => 'application/json'}, debug_info.to_json]
+      end
+    end
+
+    def response_for_request(env)
+      if route = @routes[env["REQUEST_METHOD"]].find { |route| route.matches_request?(env["REQUEST_URI"]) }
+        route.response
+      end
+    end
+  end
+
+  class Route
+    attr_reader :response
+
+    def initialize(path_spec, response)
+      @path_spec, @response = path_spec, response
+    end
+
+    def matches_request?(uri)
+      uri = URI.parse(uri).request_uri
+      @path_spec === uri
+    end
+
+    def to_s
+      "#{@path_spec} => (#{@response})"
+    end
+
+  end
+
+  class Response
+    HEADERS = {'Content-Type' => 'application/json'}
+
+    def initialize(response_code=200, data=nil, headers=nil, &block)
+      @response_code, @data = response_code, data
+      @response_headers = headers ? HEADERS.merge(headers) : HEADERS
+      @block = block_given? ? block : nil
+    end
+
+    def call
+      data = @data || @block.call
+      [@response_code, @response_headers, Array(data)]
+    end
+
+    def to_s
+      "#{@response_code} => #{(@data|| @block)}"
+    end
+
+  end
+
+end
diff --git a/spec/unit/api_client/registration_spec.rb b/spec/unit/api_client/registration_spec.rb
new file mode 100644
index 0000000..0d21730
--- /dev/null
+++ b/spec/unit/api_client/registration_spec.rb
@@ -0,0 +1,172 @@
+#
+# Author:: Daniel DeLeo (<dan 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 'tempfile'
+
+require 'chef/api_client/registration'
+
+describe Chef::ApiClient::Registration do
+  let(:key_location) do
+    make_tmpname("client-registration-key")
+  end
+
+  let(:registration) { Chef::ApiClient::Registration.new("silent-bob", key_location) }
+
+  let :private_key_data do
+    File.open(Chef::Config[:validation_key], "r") {|f| f.read.chomp }
+  end
+
+  before do
+    Chef::Config[:validation_client_name] = "test-validator"
+    Chef::Config[:validation_key] = File.expand_path('ssl/private_key.pem', CHEF_SPEC_DATA)
+  end
+
+  after do
+    File.unlink(key_location) if File.exist?(key_location)
+    Chef::Config[:validation_client_name] = nil
+    Chef::Config[:validation_key] = nil
+  end
+
+  it "has an HTTP client configured with validator credentials" do
+    registration.http_api.should be_a_kind_of(Chef::REST)
+    registration.http_api.client_name.should == "test-validator"
+    registration.http_api.signing_key.should == private_key_data
+  end
+
+  describe "when creating/updating the client on the server" do
+    let(:http_mock) { mock("Chef::REST mock") }
+
+    before do
+      registration.stub!(:http_api).and_return(http_mock)
+    end
+
+    it "creates a new ApiClient on the server using the validator identity" do
+      response = {"uri" => "https://chef.local/clients/silent-bob",
+                  "private_key" => "--begin rsa key etc--"}
+      http_mock.should_receive(:post).
+        with("clients", :name => 'silent-bob', :admin => false).
+        and_return(response)
+      registration.create_or_update.should == response
+      registration.private_key.should == "--begin rsa key etc--"
+    end
+
+    context "and the client already exists on a Chef 10 server" do
+      it "requests a new key from the server and saves it" do
+        response = {"name" => "silent-bob", "private_key" => "--begin rsa key etc--" }
+
+        response_409 = Net::HTTPConflict.new("1.1", "409", "Conflict")
+        exception_409 = Net::HTTPServerException.new("409 conflict", response_409)
+
+        http_mock.should_receive(:post).and_raise(exception_409)
+        http_mock.should_receive(:put).
+          with("clients/silent-bob", :name => 'silent-bob', :admin => false, :private_key => true).
+          and_return(response)
+        registration.create_or_update.should == response
+        registration.private_key.should == "--begin rsa key etc--"
+      end
+    end
+
+    context "and the client already exists on a Chef 11 server" do
+      it "requests a new key from the server and saves it" do
+        response = Chef::ApiClient.new
+        response.name("silent-bob")
+        response.private_key("--begin rsa key etc--")
+
+        response_409 = Net::HTTPConflict.new("1.1", "409", "Conflict")
+        exception_409 = Net::HTTPServerException.new("409 conflict", response_409)
+
+        http_mock.should_receive(:post).and_raise(exception_409)
+        http_mock.should_receive(:put).
+          with("clients/silent-bob", :name => 'silent-bob', :admin => false, :private_key => true).
+          and_return(response)
+        registration.create_or_update.should == response
+        registration.private_key.should == "--begin rsa key etc--"
+      end
+    end
+  end
+
+  describe "when writing the private key to disk" do
+    before do
+      registration.stub!(:private_key).and_return('--begin rsa key etc--')
+    end
+
+    # Permission read via File.stat is busted on windows, though creating the
+    # file with 0600 has the desired effect of giving access rights to the
+    # owner only. A platform-specific functional test would be helpful.
+    it "creates the file with 0600 permissions", :unix_only do
+      File.should_not exist(key_location)
+      registration.write_key
+      File.should exist(key_location)
+      stat = File.stat(key_location)
+      (stat.mode & 07777).should == 0600
+    end
+
+    it "writes the private key content to the file" do
+      registration.write_key
+      IO.read(key_location).should == "--begin rsa key etc--"
+    end
+  end
+
+  describe "when registering a client" do
+
+    let(:http_mock) { mock("Chef::REST mock") }
+
+    before do
+      registration.stub!(:http_api).and_return(http_mock)
+    end
+
+    it "creates the client on the server and writes the key" do
+      response = {"uri" => "http://chef.local/clients/silent-bob",
+                  "private_key" => "--begin rsa key etc--" }
+      http_mock.should_receive(:post).ordered.and_return(response)
+      registration.run
+      IO.read(key_location).should == "--begin rsa key etc--"
+    end
+
+    it "retries up to 5 times" do
+      response_500 = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error")
+      exception_500 = Net::HTTPFatalError.new("500 Internal Server Error", response_500)
+
+      http_mock.should_receive(:post).ordered.and_raise(exception_500) # 1
+      http_mock.should_receive(:post).ordered.and_raise(exception_500) # 2
+      http_mock.should_receive(:post).ordered.and_raise(exception_500) # 3
+      http_mock.should_receive(:post).ordered.and_raise(exception_500) # 4
+      http_mock.should_receive(:post).ordered.and_raise(exception_500) # 5
+
+      response = {"uri" => "http://chef.local/clients/silent-bob",
+                  "private_key" => "--begin rsa key etc--" }
+      http_mock.should_receive(:post).ordered.and_return(response)
+      registration.run
+      IO.read(key_location).should == "--begin rsa key etc--"
+    end
+
+    it "gives up retrying after the max attempts" do
+      response_500 = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error")
+      exception_500 = Net::HTTPFatalError.new("500 Internal Server Error", response_500)
+
+      http_mock.should_receive(:post).exactly(6).times.and_raise(exception_500)
+
+      lambda {registration.run}.should raise_error(Net::HTTPFatalError)
+    end
+
+  end
+
+end
+
+
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
new file mode 100644
index 0000000..0df863c
--- /dev/null
+++ b/spec/unit/api_client_spec.rb
@@ -0,0 +1,260 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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/api_client'
+require 'tempfile'
+
+describe Chef::ApiClient do
+  before(:each) do
+    @client = Chef::ApiClient.new
+  end
+
+  it "has a name attribute" do
+    @client.name("ops_master")
+    @client.name.should == "ops_master"
+  end
+
+  it "does not allow spaces in the name" do
+    lambda { @client.name "ops master" }.should raise_error(ArgumentError)
+  end
+
+  it "only allows string values for the name" do
+    lambda { @client.name Hash.new }.should raise_error(ArgumentError)
+  end
+
+  it "has an admin flag attribute" do
+    @client.admin(true)
+    @client.admin.should be_true
+  end
+
+  it "defaults to non-admin" do
+    @client.admin.should be_false
+  end
+
+  it "allows only boolean values for the admin flag" do
+    lambda { @client.admin(false) }.should_not raise_error
+    lambda { @client.admin(Hash.new) }.should raise_error(ArgumentError)
+  end
+
+  it "has a 'validator' flag attribute" do
+    @client.validator(true)
+    @client.validator.should be_true
+  end
+
+  it "defaults to non-validator" do
+    @client.validator.should be_false
+  end
+
+  it "allows only boolean values for the 'validator' flag" do
+    lambda { @client.validator(false) }.should_not raise_error
+    lambda { @client.validator(Hash.new) }.should raise_error(ArgumentError)
+  end
+
+  it "has a public key attribute" do
+    @client.public_key("super public")
+    @client.public_key.should == "super public"
+  end
+
+  it "accepts only String values for the public key" do
+    lambda { @client.public_key "" }.should_not raise_error
+    lambda { @client.public_key Hash.new }.should raise_error(ArgumentError)
+  end
+
+
+  it "has a private key attribute" do
+    @client.private_key("super private")
+    @client.private_key.should == "super private"
+  end
+
+  it "accepts only String values for the private key" do
+    lambda { @client.private_key "" }.should_not raise_error
+    lambda { @client.private_key Hash.new }.should raise_error(ArgumentError)
+  end
+
+  describe "when serializing to JSON" do
+    before(:each) do
+      @client.name("black")
+      @client.public_key("crowes")
+      @json = @client.to_json
+    end
+
+    it "serializes as a JSON object" do
+      @json.should match(/^\{.+\}$/)
+    end
+
+    it "includes the name value" do
+      @json.should include(%q{"name":"black"})
+    end
+
+    it "includes the public key value" do
+      @json.should include(%{"public_key":"crowes"})
+    end
+
+    it "includes the 'admin' flag" do
+      @json.should include(%q{"admin":false})
+    end
+
+    it "includes the 'validator' flag" do
+      @json.should include(%q{"validator":false})
+    end
+
+    it "includes the private key when present" do
+      @client.private_key("monkeypants")
+      @client.to_json.should include(%q{"private_key":"monkeypants"})
+    end
+
+    it "does not include the private key if not present" do
+      @json.should_not include("private_key")
+    end
+  end
+
+  describe "when deserializing from JSON" do
+    before(:each) do
+      client = {
+      "name" => "black",
+      "public_key" => "crowes",
+      "private_key" => "monkeypants",
+      "admin" => true,
+      "validator" => true,
+      "json_class" => "Chef::ApiClient"
+      }
+      @client = Chef::JSONCompat.from_json(client.to_json)
+    end
+
+    it "should deserialize to a Chef::ApiClient object" do
+      @client.should be_a_kind_of(Chef::ApiClient)
+    end
+
+    it "preserves the name" do
+      @client.name.should == "black"
+    end
+
+    it "preserves the public key" do
+      @client.public_key.should == "crowes"
+    end
+
+    it "preserves the admin status" do
+      @client.admin.should be_true
+    end
+
+    it "preserves the 'validator' status" do
+      @client.validator.should be_true
+    end
+
+    it "includes the private key if present" do
+      @client.private_key.should == "monkeypants"
+    end
+
+  end
+
+  describe "with correctly configured API credentials" do
+    before do
+      Chef::Config[:node_name] = "silent-bob"
+      Chef::Config[:client_key] = File.expand_path('ssl/private_key.pem', CHEF_SPEC_DATA)
+    end
+
+    after do
+      Chef::Config[:node_name] = nil
+      Chef::Config[:client_key] = nil
+    end
+
+    let :private_key_data do
+      File.open(Chef::Config[:client_key], "r") {|f| f.read.chomp }
+    end
+
+    it "has an HTTP client configured with default credentials" do
+      @client.http_api.should be_a_kind_of(Chef::REST)
+      @client.http_api.client_name.should == "silent-bob"
+      @client.http_api.signing_key.to_s.should == private_key_data
+    end
+  end
+
+
+  describe "when requesting a new key" do
+    before do
+      @http_client = mock("Chef::REST mock")
+      Chef::REST.stub!(:new).and_return(@http_client)
+    end
+
+    context "and the client does not exist on the server" do
+      before do
+        @a_404_response = Net::HTTPNotFound.new("404 not found and such", nil, nil)
+        @a_404_exception = Net::HTTPServerException.new("404 not found exception", @a_404_response)
+
+        @http_client.should_receive(:get).with("clients/lost-my-key").and_raise(@a_404_exception)
+      end
+
+      it "raises a 404 error" do
+        lambda { Chef::ApiClient.reregister("lost-my-key") }.should raise_error(Net::HTTPServerException)
+      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")
+        @http_client.should_receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key)
+      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")
+          @http_client.should_receive(:put).
+            with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true).
+            and_return(@api_client_with_key)
+        end
+
+        it "returns an ApiClient with a private key" do
+          response = Chef::ApiClient.reregister("lost-my-key")
+          # no sane == method for ApiClient :'(
+          response.should == @api_client_without_key
+          response.private_key.should == "the new private key"
+          response.name.should == "lost-my-key"
+          response.admin.should be_false
+        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"}
+          @http_client.should_receive(:put).
+            with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true).
+            and_return(@api_client_with_key)
+        end
+
+        it "returns an ApiClient with a private key" do
+          response = Chef::ApiClient.reregister("lost-my-key")
+          # no sane == method for ApiClient :'(
+          response.should == @api_client_without_key
+          response.private_key.should == "the new private key"
+          response.name.should == "lost-my-key"
+          response.admin.should be_false
+          response.validator.should be_false
+        end
+      end
+
+    end
+  end
+end
+
+
diff --git a/spec/unit/application/agent_spec.rb b/spec/unit/application/agent_spec.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/unit/application/apply.rb b/spec/unit/application/apply.rb
new file mode 100644
index 0000000..0dc2454
--- /dev/null
+++ b/spec/unit/application/apply.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Bryan W. Berry (<bryan.berry at gmail.com>)
+# Copyright:: Copyright (c) 2012 Bryan W. Berry
+# 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::Application::Apply do
+
+  before do
+    @app = Chef::Application::Recipe.new
+    @app.stub!(:configure_logging).and_return(true)
+    @recipe_text = "package 'nyancat'"
+    Chef::Config[:solo] = true
+  end
+
+  describe "configuring the application" do
+    it "should set solo mode to true" do
+      @app.reconfigure
+      Chef::Config[:solo].should be_true
+    end
+  end
+  describe "read_recipe_file" do
+    before do
+      @recipe_file_name = "foo.rb"
+      @recipe_path = File.expand_path("foo.rb")
+      @recipe_file = mock("Tempfile (mock)", :read => @recipe_text)
+      @app.stub!(:open).with(@recipe_path).and_return(@recipe_file)
+      File.stub!(:exist?).with("foo.rb").and_return(true)
+      Chef::Application.stub!(:fatal!).and_return(true)
+    end
+    it "should read text properly" do
+      @app.read_recipe_file(@recipe_file_name)[0].should == @recipe_text
+    end
+    it "should return a file_handle" do
+      @app.read_recipe_file(@recipe_file_name)[1].should be_instance_of(RSpec::Mocks::Mock)
+    end
+    describe "when recipe doesn't exist" do
+      before do
+        File.stub!(:exist?).with(@recipe_file_name).and_return(false)
+      end
+      it "should raise a fatal" do
+        Chef::Application.should_receive(:fatal!)
+        @app.read_recipe_file(@recipe_file_name)
+      end
+    end
+  end
+  describe "temp_recipe_file" do
+    before do
+      @app.instance_variable_set(:@recipe_text, @recipe_text)
+      @app.temp_recipe_file
+      @recipe_fh = @app.instance_variable_get(:@recipe_fh)
+    end
+    it "should open a tempfile" do
+      @recipe_fh.path.should match(/.*recipe-temporary-file.*/)
+    end
+    it "should write recipe text to the tempfile" do
+      @recipe_fh.read.should == @recipe_text
+    end
+    it "should save the filename for later use" do
+      @recipe_fh.path.should == @app.instance_variable_get(:@recipe_filename)
+    end
+  end
+end
diff --git a/spec/unit/application/client_spec.rb b/spec/unit/application/client_spec.rb
new file mode 100644
index 0000000..c438789
--- /dev/null
+++ b/spec/unit/application/client_spec.rb
@@ -0,0 +1,149 @@
+#
+# Author:: AJ Christensen (<aj at junglist.gen.nz>)
+# 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'
+
+describe Chef::Application::Client, "reconfigure" do
+  before do
+    @original_argv = ARGV.dup
+    ARGV.clear
+
+    @app = Chef::Application::Client.new
+    @app.stub!(:configure_opt_parser).and_return(true)
+    @app.stub!(:configure_chef).and_return(true)
+    @app.stub!(:configure_logging).and_return(true)
+    Chef::Config[:interval] = 10
+
+    Chef::Config[:once] = false
+  end
+
+  after do
+    ARGV.replace(@original_argv)
+  end
+
+  describe "when in daemonized mode and no interval has been set" do
+    before do
+      Chef::Config[:daemonize] = true
+      Chef::Config[:interval] = nil
+    end
+
+    it "should set the interval to 1800" do
+      @app.reconfigure
+      Chef::Config.interval.should == 1800
+    end
+  end
+
+  describe "when configured to run once" do
+    before do
+      Chef::Config[:once] = true
+      Chef::Config[:daemonize] = false
+      Chef::Config[:splay] = 60
+      Chef::Config[:interval] = 1800
+    end
+
+    it "ignores the splay" do
+      @app.reconfigure
+      Chef::Config.splay.should be_nil
+    end
+
+    it "forces the interval to nil" do
+      @app.reconfigure
+      Chef::Config.interval.should be_nil
+    end
+
+  end
+
+  describe "when the json_attribs configuration option is specified" do
+
+    let(:json_attribs) { {"a" => "b"} }
+    let(:config_fetcher) { double(Chef::ConfigFetcher, :fetch_json => json_attribs) }
+    let(:json_source) { "https://foo.com/foo.json" }
+
+    before do
+      Chef::Config[:json_attribs] = json_source
+      Chef::ConfigFetcher.should_receive(:new).with(json_source).
+        and_return(config_fetcher)
+    end
+
+    it "reads the JSON attributes from the specified source" do
+      @app.reconfigure
+      @app.chef_client_json.should == json_attribs
+    end
+  end
+end
+
+describe Chef::Application::Client, "setup_application" do
+  before do
+    @app = Chef::Application::Client.new
+    # this is all stuff the reconfigure method needs
+    @app.stub!(:configure_opt_parser).and_return(true)
+    @app.stub!(:configure_chef).and_return(true)
+    @app.stub!(:configure_logging).and_return(true)
+  end
+
+  it "should change privileges" do
+    Chef::Daemon.should_receive(:change_privilege).and_return(true)
+    @app.setup_application
+  end
+  after do
+    Chef::Config[:solo] = false
+  end
+end
+
+describe Chef::Application::Client, "configure_chef" do
+  before do
+    @original_argv = ARGV.dup
+    ARGV.clear
+    @app = Chef::Application::Client.new
+    @app.configure_chef
+  end
+
+  after do
+    ARGV.replace(@original_argv)
+  end
+
+  it "should set the colored output to false by default on windows and true otherwise" do
+    if windows?
+      Chef::Config[:color].should be_false
+    else
+      Chef::Config[:color].should be_true
+    end
+  end
+end
+
+describe Chef::Application::Client, "run_application", :unix_only do
+  before do
+    @pipe = IO.pipe
+    @app = Chef::Application::Client.new
+    @app.stub(:run_chef_client) do
+      @pipe[1].puts 'started'
+      sleep 1
+      @pipe[1].puts 'finished'
+    end
+  end
+
+  it "should exit gracefully when sent SIGTERM" do
+    pid = fork do
+      @app.run_application
+    end
+    @pipe[0].gets.should == "started\n"
+    Process.kill("TERM", pid)
+    Process.wait
+    IO.select([@pipe[0]], nil, nil, 0).should_not be_nil
+    @pipe[0].gets.should == "finished\n"
+  end
+end
diff --git a/spec/unit/application/knife_spec.rb b/spec/unit/application/knife_spec.rb
new file mode 100644
index 0000000..16f94c7
--- /dev/null
+++ b/spec/unit/application/knife_spec.rb
@@ -0,0 +1,169 @@
+#
+# Author:: AJ Christensen (<aj at junglist.gen.nz>)
+# 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_SPEC_DATA}/knife_subcommand/test_yourself"
+
+describe Chef::Application::Knife do
+  include SpecHelpers::Knife
+
+  before(:all) do
+    class NoopKnifeCommand < Chef::Knife
+      option :opt_with_default,
+        :short => "-D VALUE",
+        :long => "-optwithdefault VALUE",
+        :default => "default-value"
+
+      def run
+      end
+    end
+  end
+
+  before(:each) do
+    @knife = Chef::Application::Knife.new
+    @knife.stub!(:puts)
+    Chef::Knife.stub!(:list_commands)
+  end
+
+  it "should exit 1 and print the options if no arguments are given at all" do
+    with_argv([]) do
+      lambda { @knife.run }.should raise_error(SystemExit) { |e| e.status.should == 1 }
+    end
+  end
+
+  it "should exit 2 if run without a sub command" do
+    with_argv("--user", "adam") do
+      Chef::Log.should_receive(:error).with(/you need to pass a sub\-command/i)
+      lambda { @knife.run }.should raise_error(SystemExit) { |e| e.status.should == 2 }
+    end
+  end
+
+  it "should run a sub command with the applications command line option prototype" do
+    with_argv(*%w{noop knife command with some args}) do
+      knife = mock(Chef::Knife)
+      Chef::Knife.should_receive(:run).with(ARGV, @knife.options).and_return(knife)
+      @knife.should_receive(:exit).with(0)
+      @knife.run
+    end
+  end
+
+  it "should set the colored output to false by default on windows and true otherwise" do
+    with_argv(*%w{noop knife command}) do
+      @knife.should_receive(:exit).with(0)
+      @knife.run
+    end
+    if windows?
+      Chef::Config[:color].should be_false
+    else
+      Chef::Config[:color].should be_true
+    end
+  end
+
+  describe "when given a path to the client key" do
+    it "expands a relative path relative to the CWD" do
+      relative_path = '.chef/client.pem'
+      Dir.stub!(:pwd).and_return(CHEF_SPEC_DATA)
+      with_argv(*%W{noop knife command -k #{relative_path}}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:client_key].should == File.join(CHEF_SPEC_DATA, relative_path)
+    end
+
+    it "expands a ~/home/path to the correct full path" do
+      home_path = '~/.chef/client.pem'
+      with_argv(*%W{noop knife command -k #{home_path}}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:client_key].should == File.join(ENV['HOME'], '.chef/client.pem').gsub((File::ALT_SEPARATOR || '\\'), File::SEPARATOR)
+    end
+
+    it "does not expand a full path" do
+      full_path = if windows?
+        'C:/chef/client.pem'
+      else
+        '/etc/chef/client.pem'
+      end
+      with_argv(*%W{noop knife command -k #{full_path}}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:client_key].should == full_path
+    end
+
+  end
+
+  describe "with environment configuration" do
+    before do
+      Chef::Config[:environment] = nil
+    end
+
+    it "should default to no environment" do
+      with_argv(*%w{noop knife command}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:environment].should == nil
+    end
+
+    it "should load the environment from the config file" do
+      config_file = File.join(CHEF_SPEC_DATA,"environment-config.rb")
+      with_argv(*%W{noop knife command -c #{config_file}}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:environment].should == 'production'
+    end
+
+    it "should load the environment from the CLI options" do
+      with_argv(*%W{noop knife command -E development}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:environment].should == 'development'
+    end
+
+    it "should override the config file environment with the CLI environment" do
+      config_file = File.join(CHEF_SPEC_DATA,"environment-config.rb")
+      with_argv(*%W{noop knife command -c #{config_file} -E override}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:environment].should == 'override'
+    end
+
+    it "should override the config file environment with the CLI environment regardless of order" do
+      config_file = File.join(CHEF_SPEC_DATA,"environment-config.rb")
+      with_argv(*%W{noop knife command -E override -c #{config_file}}) do
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+      Chef::Config[:environment].should == 'override'
+    end
+
+    it "should run a sub command with the applications command line option prototype" do
+      with_argv(*%w{noop knife command with some args}) do
+        knife = mock(Chef::Knife)
+        Chef::Knife.should_receive(:run).with(ARGV, @knife.options).and_return(knife)
+        @knife.should_receive(:exit).with(0)
+        @knife.run
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/application/server_spec.rb b/spec/unit/application/server_spec.rb
new file mode 100644
index 0000000..e69de29
diff --git a/spec/unit/application/solo_spec.rb b/spec/unit/application/solo_spec.rb
new file mode 100644
index 0000000..41e0cf3
--- /dev/null
+++ b/spec/unit/application/solo_spec.rb
@@ -0,0 +1,127 @@
+#
+# Author:: AJ Christensen (<aj at junglist.gen.nz>)
+# 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'
+
+describe Chef::Application::Solo do
+  before do
+    @app = Chef::Application::Solo.new
+    @app.stub!(:configure_opt_parser).and_return(true)
+    @app.stub!(:configure_chef).and_return(true)
+    @app.stub!(:configure_logging).and_return(true)
+    Chef::Config[:recipe_url] = false
+    Chef::Config[:json_attribs] = false
+    Chef::Config[:solo] = true
+  end
+
+  describe "configuring the application" do
+    it "should set solo mode to true" do
+      @app.reconfigure
+      Chef::Config[:solo].should be_true
+    end
+
+    describe "when in daemonized mode and no interval has been set" do
+      before do
+        Chef::Config[:daemonize] = true
+      end
+
+      it "should set the interval to 1800" do
+        Chef::Config[:interval] = nil
+        @app.reconfigure
+        Chef::Config[:interval].should == 1800
+      end
+    end
+
+    describe "when the json_attribs configuration option is specified" do
+
+      let(:json_attribs) { {"a" => "b"} }
+      let(:config_fetcher) { double(Chef::ConfigFetcher, :fetch_json => json_attribs) }
+      let(:json_source) { "https://foo.com/foo.json" }
+
+      before do
+        Chef::Config[:json_attribs] = json_source
+        Chef::ConfigFetcher.should_receive(:new).with(json_source).
+          and_return(config_fetcher)
+      end
+
+      it "reads the JSON attributes from the specified source" do
+        @app.reconfigure
+        @app.chef_client_json.should == json_attribs
+      end
+    end
+
+
+
+    describe "when the recipe_url configuration option is specified" do
+      before do
+        Chef::Config[:cookbook_path] = "#{Dir.tmpdir}/chef-solo/cookbooks"
+        Chef::Config[:recipe_url] = "http://junglist.gen.nz/recipes.tgz"
+        FileUtils.stub!(:mkdir_p).and_return(true)
+        @tarfile = StringIO.new("remote_tarball_content")
+        @app.stub!(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(@tarfile)
+
+        @target_file = StringIO.new
+        File.stub!(:open).with("#{Dir.tmpdir}/chef-solo/recipes.tgz", "wb").and_yield(@target_file)
+
+        Chef::Mixin::Command.stub!(:run_command).and_return(true)
+      end
+
+      it "should create the recipes path based on the parent of the cookbook path" do
+        FileUtils.should_receive(:mkdir_p).with("#{Dir.tmpdir}/chef-solo").and_return(true)
+        @app.reconfigure
+      end
+
+      it "should download the recipes" do
+        @app.should_receive(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(@tarfile)
+        @app.reconfigure
+      end
+
+      it "should write the recipes to the target path" do
+        @app.reconfigure
+        @target_file.string.should == "remote_tarball_content"
+      end
+
+      it "should untar the target file to the parent of the cookbook path" do
+        Chef::Mixin::Command.should_receive(:run_command).with({:command => "tar zxvf #{Dir.tmpdir}/chef-solo/recipes.tgz -C #{Dir.tmpdir}/chef-solo"}).and_return(true)
+        @app.reconfigure
+      end
+    end
+  end
+
+
+  describe "after the application has been configured" do
+    before do
+      Chef::Config[:solo] = true
+
+      Chef::Daemon.stub!(:change_privilege)
+      @chef_client = mock("Chef::Client")
+      Chef::Client.stub!(:new).and_return(@chef_client)
+      @app = Chef::Application::Solo.new
+      # this is all stuff the reconfigure method needs
+      @app.stub!(:configure_opt_parser).and_return(true)
+      @app.stub!(:configure_chef).and_return(true)
+      @app.stub!(:configure_logging).and_return(true)
+    end
+
+    it "should change privileges" do
+      Chef::Daemon.should_receive(:change_privilege).and_return(true)
+      @app.setup_application
+    end
+  end
+
+end
+
diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb
new file mode 100644
index 0000000..1606c82
--- /dev/null
+++ b/spec/unit/application_spec.rb
@@ -0,0 +1,320 @@
+#
+# Author:: AJ Christensen (<aj at junglist.gen.nz>)
+# Author:: Mark Mzyk (mmzyk at opscode.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'
+
+describe Chef::Application do
+  before do
+    @original_argv = ARGV.dup
+    ARGV.clear
+    Chef::Log.logger = Logger.new(StringIO.new)
+    @app = Chef::Application.new
+    Dir.stub!(:chdir).and_return(0)
+    @app.stub!(:reconfigure)
+    Chef::Log.init(STDERR)
+  end
+
+  after do
+    ARGV.replace(@original_argv)
+  end
+
+  describe "reconfigure" do
+    before do
+      @app = Chef::Application.new
+      @app.stub!(:configure_chef).and_return(true)
+      @app.stub!(:configure_logging).and_return(true)
+    end
+
+    it "should configure chef" do
+      @app.should_receive(:configure_chef).and_return(true)
+      @app.reconfigure
+    end
+
+    it "should configure logging" do
+      @app.should_receive(:configure_logging).and_return(true)
+      @app.reconfigure
+    end
+
+  end
+
+  describe Chef::Application do
+    before do
+      @app = Chef::Application.new
+    end
+
+    describe "run" do
+      before do
+        @app.stub!(:setup_application).and_return(true)
+        @app.stub!(:run_application).and_return(true)
+        @app.stub!(:configure_chef).and_return(true)
+        @app.stub!(:configure_logging).and_return(true)
+      end
+
+      it "should reconfigure the application before running" do
+        @app.should_receive(:reconfigure).and_return(true)
+        @app.run
+      end
+
+      it "should setup the application before running it" do
+        @app.should_receive(:setup_application).and_return(true)
+        @app.run
+      end
+
+      it "should run the actual application" do
+        @app.should_receive(:run_application).and_return(true)
+        @app.run
+      end
+    end
+  end
+
+  describe "configure_chef" do
+    before do
+      @app = Chef::Application.new
+      #Chef::Config.stub!(:merge!).and_return(true)
+      @app.stub!(:parse_options).and_return(true)
+    end
+
+    it "should parse the commandline options" do
+      @app.should_receive(:parse_options).and_return(true)
+      @app.config[:config_file] = "/etc/chef/default.rb" #have a config file set, to prevent triggering error block
+      @app.configure_chef
+    end
+
+    describe "when a config_file is present" do
+      let(:config_content) { "rspec_ran('true')" }
+      let(:config_location) { "/etc/chef/default.rb" }
+
+      let(:config_location_pathname) do
+        p = Pathname.new(config_location)
+        p.stub(:realpath).and_return(config_location)
+        p
+      end
+
+      before do
+        @app.config[:config_file] = config_location
+        Pathname.stub(:new).with(config_location).and_return(config_location_pathname)
+        File.should_receive(:read).
+          with(config_location).
+          and_return(config_content)
+      end
+
+      it "should configure chef::config from a file" do
+        Chef::Config.should_receive(:from_string).with(config_content, config_location)
+        @app.configure_chef
+      end
+
+      it "should merge the local config hash into chef::config" do
+        #File.should_receive(:open).with("/etc/chef/default.rb").and_yield(@config_file)
+        @app.configure_chef
+        Chef::Config.rspec_ran.should == "true"
+      end
+
+    end
+
+    describe "when there is no config_file defined" do
+      before do
+        @app.config[:config_file] = nil
+      end
+
+      it "should emit a warning" do
+        Chef::Config.should_not_receive(:from_file).with("/etc/chef/default.rb")
+        Chef::Log.should_receive(:warn).with("No config file found or specified on command line, using command line options.")
+        @app.configure_chef
+      end
+    end
+
+    describe "when the config file is set and not found" do
+      before do
+        @app.config[:config_file] = "/etc/chef/notfound"
+      end
+      it "should use the passed in command line options and defaults" do
+        Chef::Config.should_receive(:merge!)
+        @app.configure_chef
+      end
+    end
+  end
+
+  describe "when configuring the logger" do
+    before do
+      @app = Chef::Application.new
+      Chef::Log.stub!(:init)
+    end
+
+    it "should initialise the chef logger" do
+      Chef::Log.stub!(:level=)
+      @monologger = mock("Monologger")
+      MonoLogger.should_receive(:new).with(Chef::Config[:log_location]).and_return(@monologger)
+      Chef::Log.should_receive(:init).with(@monologger)
+      @app.configure_logging
+    end
+
+    shared_examples_for "log_level_is_auto" do
+      context "when STDOUT is to a tty" do
+        before do
+          STDOUT.stub!(:tty?).and_return(true)
+        end
+
+        it "configures the log level to :warn" do
+          @app.configure_logging
+          Chef::Log.level.should == :warn
+        end
+
+        context "when force_logger is configured" do
+          before do
+            Chef::Config[:force_logger] = true
+          end
+
+          it "configures the log level to info" do
+            @app.configure_logging
+            Chef::Log.level.should == :info
+          end
+        end
+      end
+
+      context "when STDOUT is not to a tty" do
+        before do
+          STDOUT.stub!(:tty?).and_return(false)
+        end
+
+        it "configures the log level to :info" do
+          @app.configure_logging
+          Chef::Log.level.should == :info
+        end
+
+        context "when force_formatter is configured" do
+          before do
+            Chef::Config[:force_formatter] = true
+          end
+          it "sets the log level to :warn" do
+            @app.configure_logging
+            Chef::Log.level.should == :warn
+          end
+        end
+      end
+    end
+
+    context "when log_level is not set" do
+      it_behaves_like "log_level_is_auto"
+    end
+
+    context "when log_level is :auto" do
+      before do
+        Chef::Config[:log_level] = :auto
+      end
+
+      it_behaves_like "log_level_is_auto"
+    end
+  end
+
+  describe "class method: fatal!" do
+    before do
+      STDERR.stub!(:puts).with("FATAL: blah").and_return(true)
+      Chef::Log.stub!(:fatal).with("blah").and_return(true)
+      Process.stub!(:exit).and_return(true)
+    end
+
+    it "should log an error message to the logger" do
+      Chef::Log.should_receive(:fatal).with("blah").and_return(true)
+      Chef::Application.fatal! "blah"
+    end
+
+    describe "when an exit code is supplied" do
+      it "should exit with the given exit code" do
+        Process.should_receive(:exit).with(-100).and_return(true)
+        Chef::Application.fatal! "blah", -100
+      end
+    end
+
+    describe "when an exit code is not supplied" do
+      it "should exit with the default exit code" do
+        Process.should_receive(:exit).with(-1).and_return(true)
+        Chef::Application.fatal! "blah"
+      end
+    end
+
+  end
+
+  describe "setup_application" do
+    before do
+      @app = Chef::Application.new
+    end
+
+    it "should raise an error" do
+      lambda { @app.setup_application }.should raise_error(Chef::Exceptions::Application)
+    end
+  end
+
+  describe "run_application" do
+    before do
+      @app = Chef::Application.new
+    end
+
+    it "should raise an error" do
+      lambda { @app.run_application }.should raise_error(Chef::Exceptions::Application)
+    end
+  end
+
+  context "when the config file is not available" do
+    it "should warn for bad config file path" do
+      @app.config[:config_file] = "/tmp/non-existing-dir/file"
+      config_file_regexp = Regexp.new @app.config[:config_file]
+      Chef::Log.should_receive(:warn).at_least(:once).with(config_file_regexp).and_return(true)
+      Chef::Log.should_receive(:warn).any_number_of_times.and_return(true)
+      @app.configure_chef
+    end
+  end
+
+  describe "configuration errors" do
+    before do
+      Process.should_receive(:exit)
+    end
+
+    def raises_informative_fatals_on_configure_chef
+      config_file_regexp = Regexp.new @app.config[:config_file]
+      Chef::Log.should_receive(:fatal).
+        with(/Configuration error/)
+      Chef::Log.should_receive(:fatal).
+        with(config_file_regexp).
+        at_least(1).times
+      @app.configure_chef
+    end
+
+    describe "when config file exists but contains errors" do
+      def create_config_file(text)
+        @config_file = Tempfile.new("rspec-chef-config")
+        @config_file.write(text)
+        @config_file.close
+        @app.config[:config_file] = @config_file.path
+      end
+
+      after(:each) do
+        @config_file.unlink
+      end
+
+      it "should raise informative fatals for missing log file dir" do
+        create_config_file('log_location "/tmp/non-existing-dir/logfile"')
+        raises_informative_fatals_on_configure_chef
+      end
+
+      it "should raise informative fatals for badly written config" do
+        create_config_file("text that should break the config parsing")
+        raises_informative_fatals_on_configure_chef
+      end
+    end
+  end
+end
diff --git a/spec/unit/checksum/storage/filesystem_spec.rb b/spec/unit/checksum/storage/filesystem_spec.rb
new file mode 100644
index 0000000..144dc69
--- /dev/null
+++ b/spec/unit/checksum/storage/filesystem_spec.rb
@@ -0,0 +1,70 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/checksum/storage/filesystem'
+
+describe Chef::Checksum::Storage::Filesystem do
+
+  before do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    @now = Time.now
+
+    Time.stub!(:now).and_return(@now)
+    Chef::Config.stub!(:checksum_path).and_return("/var/chef/checksums")
+
+    @checksum_of_the_file = "3fafecfb15585ede6b840158cbc2f399"
+    @storage = Chef::Checksum::Storage::Filesystem.new("/not/used/path", @checksum_of_the_file)
+  end
+
+  it "has the path to the file in the checksum repo" do
+    @storage.file_location.should == "/var/chef/checksums/3f/3fafecfb15585ede6b840158cbc2f399"
+  end
+
+  it "has the path the file's subdirectory in the checksum repo" do
+    @storage.checksum_repo_directory.should == "/var/chef/checksums/3f"
+  end
+
+  it "commits a file from a given location to the checksum repo location" do
+    File.should_receive(:rename).with("/tmp/arbitrary_file_location", @storage.file_location)
+    FileUtils.should_receive(:mkdir_p).with("/var/chef/checksums/3f")
+
+    @storage.commit("/tmp/arbitrary_file_location")
+  end
+
+  it "reverts committing a file" do
+    File.should_receive(:rename).with("/tmp/arbitrary_file_location", @storage.file_location)
+    FileUtils.should_receive(:mkdir_p).with("/var/chef/checksums/3f")
+    @storage.commit("/tmp/arbitrary_file_location")
+
+    File.should_receive(:rename).with(@storage.file_location, "/tmp/arbitrary_file_location")
+    @storage.revert("/tmp/arbitrary_file_location")
+  end
+
+  it "deletes the file" do
+    FileUtils.should_receive(:rm).with(@storage.file_location)
+    @storage.purge
+  end
+
+  it "successfully purges even if its file has been deleted from the repo" do
+    FileUtils.should_receive(:rm).with(@storage.file_location).and_raise(Errno::ENOENT)
+    lambda {@storage.purge}.should_not raise_error
+  end
+
+end
diff --git a/spec/unit/chef_fs/diff_spec.rb b/spec/unit/chef_fs/diff_spec.rb
new file mode 100644
index 0000000..2133d05
--- /dev/null
+++ b/spec/unit/chef_fs/diff_spec.rb
@@ -0,0 +1,328 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/chef_fs/file_pattern'
+require 'chef/chef_fs/command_line'
+
+# Removes the date stamp from the diff and replaces it with ' DATE'
+# example match: "/dev/null\t2012-10-16 16:15:54.000000000 +0000"
+# windows match: "--- /dev/null\tTue Oct 16 18:04:34 2012"
+def remove_os_differences(diff)
+  diff = diff.gsub(/([+-]{3}.*)\t.*/, '\1 DATE')
+  diff.gsub(/^@@ -\d(,\d)? \+\d(,\d)? @@/, 'CONTEXT_LINE_NUMBERS')
+end
+
+describe 'diff', :uses_diff => true do
+  include FileSystemSupport
+
+  context 'with two filesystems with all types of difference' do
+    let(:a) {
+      memory_fs('a', {
+        :both_dirs => {
+          :sub_both_dirs => { :subsub => nil },
+          :sub_both_files => nil,
+          :sub_both_files_different => "a\n",
+          :sub_both_dirs_empty => {},
+          :sub_dirs_empty_in_a_filled_in_b => {},
+          :sub_dirs_empty_in_b_filled_in_a => { :subsub => nil },
+          :sub_a_only_dir => { :subsub => nil },
+          :sub_a_only_file => nil,
+          :sub_dir_in_a_file_in_b => {},
+          :sub_file_in_a_dir_in_b => nil
+        },
+        :both_files => nil,
+        :both_files_different => "a\n",
+        :both_dirs_empty => {},
+        :dirs_empty_in_a_filled_in_b => {},
+        :dirs_empty_in_b_filled_in_a => { :subsub => nil },
+        :dirs_in_a_cannot_be_in_b => {},
+        :file_in_a_cannot_be_in_b => nil,
+        :a_only_dir => { :subsub => nil },
+        :a_only_file => nil,
+        :dir_in_a_file_in_b => {},
+        :file_in_a_dir_in_b => nil
+      }, /cannot_be_in_a/)
+    }
+    let(:b) {
+      memory_fs('b', {
+        :both_dirs => {
+          :sub_both_dirs => { :subsub => nil },
+          :sub_both_files => nil,
+          :sub_both_files_different => "b\n",
+          :sub_both_dirs_empty => {},
+          :sub_dirs_empty_in_a_filled_in_b => { :subsub => nil },
+          :sub_dirs_empty_in_b_filled_in_a => {},
+          :sub_b_only_dir => { :subsub => nil },
+          :sub_b_only_file => nil,
+          :sub_dir_in_a_file_in_b => nil,
+          :sub_file_in_a_dir_in_b => {}
+        },
+        :both_files => nil,
+        :both_files_different => "b\n",
+        :both_dirs_empty => {},
+        :dirs_empty_in_a_filled_in_b => { :subsub => nil },
+        :dirs_empty_in_b_filled_in_a => {},
+        :dirs_in_b_cannot_be_in_a => {},
+        :file_in_b_cannot_be_in_a => nil,
+        :b_only_dir => { :subsub => nil },
+        :b_only_file => nil,
+        :dir_in_a_file_in_b => nil,
+        :file_in_a_dir_in_b => {}
+      }, /cannot_be_in_b/)
+    }
+    it 'Chef::ChefFS::CommandLine.diff_print(/)' do
+      results = []
+      Chef::ChefFS::CommandLine.diff_print(pattern('/'), a, b, nil, nil) do |diff|
+        results << remove_os_differences(diff)
+      end
+      results.should =~ [
+        'diff --knife a/both_dirs/sub_both_files_different b/both_dirs/sub_both_files_different
+--- a/both_dirs/sub_both_files_different DATE
++++ b/both_dirs/sub_both_files_different DATE
+CONTEXT_LINE_NUMBERS
+-a
++b
+','diff --knife a/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub
+new file
+--- /dev/null DATE
++++ b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub DATE
+CONTEXT_LINE_NUMBERS
++subsub
+','diff --knife a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub b/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub
+deleted file
+--- a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-subsub
+','Only in a/both_dirs: sub_a_only_dir
+','diff --knife a/both_dirs/sub_a_only_file b/both_dirs/sub_a_only_file
+deleted file
+--- a/both_dirs/sub_a_only_file DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-sub_a_only_file
+','File a/both_dirs/sub_dir_in_a_file_in_b is a directory while file b/both_dirs/sub_dir_in_a_file_in_b is a regular file
+','File a/both_dirs/sub_file_in_a_dir_in_b is a regular file while file b/both_dirs/sub_file_in_a_dir_in_b is a directory
+','Only in b/both_dirs: sub_b_only_dir
+','diff --knife a/both_dirs/sub_b_only_file b/both_dirs/sub_b_only_file
+new file
+--- /dev/null DATE
++++ b/both_dirs/sub_b_only_file DATE
+CONTEXT_LINE_NUMBERS
++sub_b_only_file
+','diff --knife a/both_files_different b/both_files_different
+--- a/both_files_different DATE
++++ b/both_files_different DATE
+CONTEXT_LINE_NUMBERS
+-a
++b
+','diff --knife a/dirs_empty_in_a_filled_in_b/subsub b/dirs_empty_in_a_filled_in_b/subsub
+new file
+--- /dev/null DATE
++++ b/dirs_empty_in_a_filled_in_b/subsub DATE
+CONTEXT_LINE_NUMBERS
++subsub
+','diff --knife a/dirs_empty_in_b_filled_in_a/subsub b/dirs_empty_in_b_filled_in_a/subsub
+deleted file
+--- a/dirs_empty_in_b_filled_in_a/subsub DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-subsub
+','Only in a: a_only_dir
+','diff --knife a/a_only_file b/a_only_file
+deleted file
+--- a/a_only_file DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-a_only_file
+','File a/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file
+','File a/file_in_a_dir_in_b is a regular file while file b/file_in_a_dir_in_b is a directory
+','Only in b: b_only_dir
+','diff --knife a/b_only_file b/b_only_file
+new file
+--- /dev/null DATE
++++ b/b_only_file DATE
+CONTEXT_LINE_NUMBERS
++b_only_file
+' ]
+    end
+    it 'Chef::ChefFS::CommandLine.diff_print(/both_dirs)' do
+      results = []
+      Chef::ChefFS::CommandLine.diff_print(pattern('/both_dirs'), a, b, nil, nil) do |diff|
+        results << remove_os_differences(diff)
+      end
+      results.should =~ [
+        'diff --knife a/both_dirs/sub_both_files_different b/both_dirs/sub_both_files_different
+--- a/both_dirs/sub_both_files_different DATE
++++ b/both_dirs/sub_both_files_different DATE
+CONTEXT_LINE_NUMBERS
+-a
++b
+','diff --knife a/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub
+new file
+--- /dev/null DATE
++++ b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub DATE
+CONTEXT_LINE_NUMBERS
++subsub
+','diff --knife a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub b/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub
+deleted file
+--- a/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-subsub
+','Only in a/both_dirs: sub_a_only_dir
+','diff --knife a/both_dirs/sub_a_only_file b/both_dirs/sub_a_only_file
+deleted file
+--- a/both_dirs/sub_a_only_file DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-sub_a_only_file
+','File a/both_dirs/sub_dir_in_a_file_in_b is a directory while file b/both_dirs/sub_dir_in_a_file_in_b is a regular file
+','File a/both_dirs/sub_file_in_a_dir_in_b is a regular file while file b/both_dirs/sub_file_in_a_dir_in_b is a directory
+','Only in b/both_dirs: sub_b_only_dir
+','diff --knife a/both_dirs/sub_b_only_file b/both_dirs/sub_b_only_file
+new file
+--- /dev/null DATE
++++ b/both_dirs/sub_b_only_file DATE
+CONTEXT_LINE_NUMBERS
++sub_b_only_file
+' ]
+    end
+    it 'Chef::ChefFS::CommandLine.diff_print(/) with depth 1' do
+      results = []
+      Chef::ChefFS::CommandLine.diff_print(pattern('/'), a, b, 1, nil) do |diff|
+        results << remove_os_differences(diff)
+      end
+      results.should =~ [
+'Common subdirectories: b/both_dirs
+','diff --knife a/both_files_different b/both_files_different
+--- a/both_files_different DATE
++++ b/both_files_different DATE
+CONTEXT_LINE_NUMBERS
+-a
++b
+','Common subdirectories: b/both_dirs_empty
+','Common subdirectories: b/dirs_empty_in_b_filled_in_a
+','Common subdirectories: b/dirs_empty_in_a_filled_in_b
+','Only in a: a_only_dir
+','diff --knife a/a_only_file b/a_only_file
+deleted file
+--- a/a_only_file DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-a_only_file
+','File a/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file
+','File a/file_in_a_dir_in_b is a regular file while file b/file_in_a_dir_in_b is a directory
+','Only in b: b_only_dir
+','diff --knife a/b_only_file b/b_only_file
+new file
+--- /dev/null DATE
++++ b/b_only_file DATE
+CONTEXT_LINE_NUMBERS
++b_only_file
+' ]
+    end
+    it 'Chef::ChefFS::CommandLine.diff_print(/*_*) with depth 0' do
+      results = []
+      Chef::ChefFS::CommandLine.diff_print(pattern('/*_*'), a, b, 0, nil) do |diff|
+        results << remove_os_differences(diff)
+      end
+      results.should =~ [
+'Common subdirectories: b/both_dirs
+','diff --knife a/both_files_different b/both_files_different
+--- a/both_files_different DATE
++++ b/both_files_different DATE
+CONTEXT_LINE_NUMBERS
+-a
++b
+','Common subdirectories: b/both_dirs_empty
+','Common subdirectories: b/dirs_empty_in_b_filled_in_a
+','Common subdirectories: b/dirs_empty_in_a_filled_in_b
+','Only in a: a_only_dir
+','diff --knife a/a_only_file b/a_only_file
+deleted file
+--- a/a_only_file DATE
++++ /dev/null DATE
+CONTEXT_LINE_NUMBERS
+-a_only_file
+','File a/dir_in_a_file_in_b is a directory while file b/dir_in_a_file_in_b is a regular file
+','File a/file_in_a_dir_in_b is a regular file while file b/file_in_a_dir_in_b is a directory
+','Only in b: b_only_dir
+','diff --knife a/b_only_file b/b_only_file
+new file
+--- /dev/null DATE
++++ b/b_only_file DATE
+CONTEXT_LINE_NUMBERS
++b_only_file
+' ]
+    end
+    it 'Chef::ChefFS::CommandLine.diff_print(/) in name-only mode' do
+      results = []
+      Chef::ChefFS::CommandLine.diff_print(pattern('/'), a, b, nil, :name_only) do |diff|
+        results << remove_os_differences(diff)
+      end
+      results.should =~ [
+          "b/both_dirs/sub_both_files_different\n",
+          "b/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub\n",
+          "b/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub\n",
+          "b/both_dirs/sub_a_only_dir\n",
+          "b/both_dirs/sub_a_only_file\n",
+          "b/both_dirs/sub_b_only_dir\n",
+          "b/both_dirs/sub_b_only_file\n",
+          "b/both_dirs/sub_dir_in_a_file_in_b\n",
+          "b/both_dirs/sub_file_in_a_dir_in_b\n",
+          "b/both_files_different\n",
+          "b/dirs_empty_in_b_filled_in_a/subsub\n",
+          "b/dirs_empty_in_a_filled_in_b/subsub\n",
+          "b/a_only_dir\n",
+          "b/a_only_file\n",
+          "b/b_only_dir\n",
+          "b/b_only_file\n",
+          "b/dir_in_a_file_in_b\n",
+          "b/file_in_a_dir_in_b\n"
+      ]
+    end
+    it 'Chef::ChefFS::CommandLine.diff_print(/) in name-status mode' do
+      results = []
+      Chef::ChefFS::CommandLine.diff_print(pattern('/'), a, b, nil, :name_status) do |diff|
+        results << remove_os_differences(diff)
+      end
+      results.should =~ [
+          "M\tb/both_dirs/sub_both_files_different\n",
+          "D\tb/both_dirs/sub_dirs_empty_in_b_filled_in_a/subsub\n",
+          "A\tb/both_dirs/sub_dirs_empty_in_a_filled_in_b/subsub\n",
+          "D\tb/both_dirs/sub_a_only_dir\n",
+          "D\tb/both_dirs/sub_a_only_file\n",
+          "A\tb/both_dirs/sub_b_only_dir\n",
+          "A\tb/both_dirs/sub_b_only_file\n",
+          "T\tb/both_dirs/sub_dir_in_a_file_in_b\n",
+          "T\tb/both_dirs/sub_file_in_a_dir_in_b\n",
+          "M\tb/both_files_different\n",
+          "D\tb/dirs_empty_in_b_filled_in_a/subsub\n",
+          "A\tb/dirs_empty_in_a_filled_in_b/subsub\n",
+          "D\tb/a_only_dir\n",
+          "D\tb/a_only_file\n",
+          "A\tb/b_only_dir\n",
+          "A\tb/b_only_file\n",
+          "T\tb/dir_in_a_file_in_b\n",
+          "T\tb/file_in_a_dir_in_b\n"
+      ]
+    end
+  end
+end
diff --git a/spec/unit/chef_fs/file_pattern_spec.rb b/spec/unit/chef_fs/file_pattern_spec.rb
new file mode 100644
index 0000000..bac393a
--- /dev/null
+++ b/spec/unit/chef_fs/file_pattern_spec.rb
@@ -0,0 +1,526 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/chef_fs/file_pattern'
+
+describe Chef::ChefFS::FilePattern do
+  def p(str)
+    Chef::ChefFS::FilePattern.new(str)
+  end
+
+  # Different kinds of patterns
+  context 'with empty pattern ""' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('') }
+    it 'match?' do
+      pattern.match?('').should be_true
+      pattern.match?('/').should be_false
+      pattern.match?('a').should be_false
+      pattern.match?('a/b').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should == ''
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('').should be_false
+      pattern.could_match_children?('a/b').should be_false
+    end
+  end
+
+  context 'with root pattern "/"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/') }
+    it 'match?' do
+      pattern.match?('/').should be_true
+      pattern.match?('').should be_false
+      pattern.match?('a').should be_false
+      pattern.match?('/a').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should == '/'
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('').should be_false
+      pattern.could_match_children?('/').should be_false
+      pattern.could_match_children?('a').should be_false
+      pattern.could_match_children?('a/b').should be_false
+      pattern.could_match_children?('/a').should be_false
+    end
+  end
+
+  context 'with simple pattern "abc"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('abc') }
+    it 'match?' do
+      pattern.match?('abc').should be_true
+      pattern.match?('a').should be_false
+      pattern.match?('abcd').should be_false
+      pattern.match?('/abc').should be_false
+      pattern.match?('').should be_false
+      pattern.match?('/').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should == 'abc'
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('').should be_false
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/abc').should be_false
+    end
+  end
+
+  context 'with simple pattern "/abc"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc') }
+    it 'match?' do
+      pattern.match?('/abc').should be_true
+      pattern.match?('abc').should be_false
+      pattern.match?('a').should be_false
+      pattern.match?('abcd').should be_false
+      pattern.match?('').should be_false
+      pattern.match?('/').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should == '/abc'
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/abc').should be_false
+      pattern.could_match_children?('/').should be_true
+      pattern.could_match_children?('').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+    end
+  end
+
+  context 'with simple pattern "abc/def/ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('abc/def/ghi') }
+    it 'match?' do
+      pattern.match?('abc/def/ghi').should be_true
+      pattern.match?('/abc/def/ghi').should be_false
+      pattern.match?('abc').should be_false
+      pattern.match?('abc/def').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should == 'abc/def/ghi'
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('abc').should be_true
+      pattern.could_match_children?('xyz').should be_false
+      pattern.could_match_children?('/abc').should be_false
+      pattern.could_match_children?('abc/def').should be_true
+      pattern.could_match_children?('abc/xyz').should be_false
+      pattern.could_match_children?('abc/def/ghi').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('abc').should == 'def'
+      pattern.exact_child_name_under('abc/def').should == 'ghi'
+    end
+  end
+
+  context 'with simple pattern "/abc/def/ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/def/ghi') }
+    it 'match?' do
+      pattern.match?('/abc/def/ghi').should be_true
+      pattern.match?('abc/def/ghi').should be_false
+      pattern.match?('/abc').should be_false
+      pattern.match?('/abc/def').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should == '/abc/def/ghi'
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/xyz').should be_false
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/abc/def').should be_true
+      pattern.could_match_children?('/abc/xyz').should be_false
+      pattern.could_match_children?('/abc/def/ghi').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+      pattern.exact_child_name_under('/abc').should == 'def'
+      pattern.exact_child_name_under('/abc/def').should == 'ghi'
+    end
+  end
+
+  context 'with simple pattern "a\*\b"', :pending => (Chef::Platform.windows?) do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('a\*\b') }
+    it 'match?' do
+      pattern.match?('a*b').should be_true
+      pattern.match?('ab').should be_false
+      pattern.match?('acb').should be_false
+      pattern.match?('ab').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should == 'a*b'
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('a/*b').should be_false
+    end
+  end
+
+  context 'with star pattern "/abc/*/ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/*/ghi') }
+    it 'match?' do
+      pattern.match?('/abc/def/ghi').should be_true
+      pattern.match?('/abc/ghi').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/xyz').should be_false
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/abc/def').should be_true
+      pattern.could_match_children?('/abc/xyz').should be_true
+      pattern.could_match_children?('/abc/def/ghi').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+      pattern.exact_child_name_under('/abc').should == nil
+      pattern.exact_child_name_under('/abc/def').should == 'ghi'
+    end
+  end
+
+  context 'with star pattern "/abc/d*f/ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d*f/ghi') }
+    it 'match?' do
+      pattern.match?('/abc/def/ghi').should be_true
+      pattern.match?('/abc/dxf/ghi').should be_true
+      pattern.match?('/abc/df/ghi').should be_true
+      pattern.match?('/abc/dxyzf/ghi').should be_true
+      pattern.match?('/abc/d/ghi').should be_false
+      pattern.match?('/abc/f/ghi').should be_false
+      pattern.match?('/abc/ghi').should be_false
+      pattern.match?('/abc/xyz/ghi').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/xyz').should be_false
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/abc/def').should be_true
+      pattern.could_match_children?('/abc/xyz').should be_false
+      pattern.could_match_children?('/abc/dxyzf').should be_true
+      pattern.could_match_children?('/abc/df').should be_true
+      pattern.could_match_children?('/abc/d').should be_false
+      pattern.could_match_children?('/abc/f').should be_false
+      pattern.could_match_children?('/abc/def/ghi').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+      pattern.exact_child_name_under('/abc').should == nil
+      pattern.exact_child_name_under('/abc/def').should == 'ghi'
+    end
+  end
+
+  context 'with star pattern "/abc/d??f/ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d??f/ghi') }
+    it 'match?' do
+      pattern.match?('/abc/deef/ghi').should be_true
+      pattern.match?('/abc/deeef/ghi').should be_false
+      pattern.match?('/abc/def/ghi').should be_false
+      pattern.match?('/abc/df/ghi').should be_false
+      pattern.match?('/abc/d/ghi').should be_false
+      pattern.match?('/abc/f/ghi').should be_false
+      pattern.match?('/abc/ghi').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/xyz').should be_false
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/abc/deef').should be_true
+      pattern.could_match_children?('/abc/deeef').should be_false
+      pattern.could_match_children?('/abc/def').should be_false
+      pattern.could_match_children?('/abc/df').should be_false
+      pattern.could_match_children?('/abc/d').should be_false
+      pattern.could_match_children?('/abc/f').should be_false
+      pattern.could_match_children?('/abc/deef/ghi').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+      pattern.exact_child_name_under('/abc').should == nil
+      pattern.exact_child_name_under('/abc/deef').should == 'ghi'
+    end
+  end
+
+  context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :pending => (Chef::Platform.windows?) do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d[a-z][0-9]f/ghi') }
+    it 'match?' do
+      pattern.match?('/abc/de1f/ghi').should be_true
+      pattern.match?('/abc/deef/ghi').should be_false
+      pattern.match?('/abc/d11f/ghi').should be_false
+      pattern.match?('/abc/de11f/ghi').should be_false
+      pattern.match?('/abc/dee1f/ghi').should be_false
+      pattern.match?('/abc/df/ghi').should be_false
+      pattern.match?('/abc/d/ghi').should be_false
+      pattern.match?('/abc/f/ghi').should be_false
+      pattern.match?('/abc/ghi').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/xyz').should be_false
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/abc/de1f').should be_true
+      pattern.could_match_children?('/abc/deef').should be_false
+      pattern.could_match_children?('/abc/d11f').should be_false
+      pattern.could_match_children?('/abc/de11f').should be_false
+      pattern.could_match_children?('/abc/dee1f').should be_false
+      pattern.could_match_children?('/abc/def').should be_false
+      pattern.could_match_children?('/abc/df').should be_false
+      pattern.could_match_children?('/abc/d').should be_false
+      pattern.could_match_children?('/abc/f').should be_false
+      pattern.could_match_children?('/abc/de1f/ghi').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+      pattern.exact_child_name_under('/abc').should == nil
+      pattern.exact_child_name_under('/abc/de1f').should == 'ghi'
+    end
+  end
+
+  context 'with star pattern "/abc/**/ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/**/ghi') }
+    it 'match?' do
+      pattern.match?('/abc/def/ghi').should be_true
+      pattern.match?('/abc/d/e/f/ghi').should be_true
+      pattern.match?('/abc/ghi').should be_false
+      pattern.match?('/abcdef/d/ghi').should be_false
+      pattern.match?('/abc/d/defghi').should be_false
+      pattern.match?('/xyz').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/abc/d').should be_true
+      pattern.could_match_children?('/abc/d/e').should be_true
+      pattern.could_match_children?('/abc/d/e/f').should be_true
+      pattern.could_match_children?('/abc/def/ghi').should be_true
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/xyz').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+      pattern.exact_child_name_under('/abc').should == nil
+      pattern.exact_child_name_under('/abc/def').should == nil
+    end
+  end
+
+  context 'with star pattern "/abc**/ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc**/ghi') }
+    it 'match?' do
+      pattern.match?('/abc/def/ghi').should be_true
+      pattern.match?('/abc/d/e/f/ghi').should be_true
+      pattern.match?('/abc/ghi').should be_true
+      pattern.match?('/abcdef/ghi').should be_true
+      pattern.match?('/abc/defghi').should be_false
+      pattern.match?('/xyz').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/abcdef').should be_true
+      pattern.could_match_children?('/abc/d/e').should be_true
+      pattern.could_match_children?('/abc/d/e/f').should be_true
+      pattern.could_match_children?('/abc/def/ghi').should be_true
+      pattern.could_match_children?('abc').should be_false
+    end
+    it 'could_match_children? /abc** returns false for /xyz' do
+      pending 'Make could_match_children? more rigorous' do
+        # At the moment, we return false for this, but in the end it would be nice to return true:
+        pattern.could_match_children?('/xyz').should be_false
+      end
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == nil
+      pattern.exact_child_name_under('/abc').should == nil
+      pattern.exact_child_name_under('/abc/def').should == nil
+    end
+  end
+
+  context 'with star pattern "/abc/**ghi"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/**ghi') }
+    it 'match?' do
+      pattern.match?('/abc/def/ghi').should be_true
+      pattern.match?('/abc/def/ghi/ghi').should be_true
+      pattern.match?('/abc/def/ghi/jkl').should be_false
+      pattern.match?('/abc/d/e/f/ghi').should be_true
+      pattern.match?('/abc/ghi').should be_true
+      pattern.match?('/abcdef/ghi').should be_false
+      pattern.match?('/abc/defghi').should be_true
+      pattern.match?('/xyz').should be_false
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+    it 'could_match_children?' do
+      pattern.could_match_children?('/abc').should be_true
+      pattern.could_match_children?('/abcdef').should be_false
+      pattern.could_match_children?('/abc/d/e').should be_true
+      pattern.could_match_children?('/abc/d/e/f').should be_true
+      pattern.could_match_children?('/abc/def/ghi').should be_true
+      pattern.could_match_children?('abc').should be_false
+      pattern.could_match_children?('/xyz').should be_false
+    end
+    it 'exact_child_name_under' do
+      pattern.exact_child_name_under('/').should == 'abc'
+      pattern.exact_child_name_under('/abc').should == nil
+      pattern.exact_child_name_under('/abc/def').should == nil
+    end
+  end
+
+  context 'with star pattern "a**b**c"' do
+    let(:pattern) { Chef::ChefFS::FilePattern.new('a**b**c') }
+    it 'match?' do
+      pattern.match?('axybzwc').should be_true
+      pattern.match?('abc').should be_true
+      pattern.match?('axyzwc').should be_false
+      pattern.match?('ac').should be_false
+      pattern.match?('a/x/y/b/z/w/c').should be_true
+    end
+    it 'exact_path' do
+      pattern.exact_path.should be_nil
+    end
+  end
+
+  context 'normalization tests' do
+    it 'handles trailing slashes' do
+      p('abc/').normalized_pattern.should == 'abc'
+      p('abc/').exact_path.should == 'abc'
+      p('abc/').match?('abc').should be_true
+      p('//').normalized_pattern.should == '/'
+      p('//').exact_path.should == '/'
+      p('//').match?('/').should be_true
+      p('/./').normalized_pattern.should == '/'
+      p('/./').exact_path.should == '/'
+      p('/./').match?('/').should be_true
+    end
+    it 'handles multiple slashes' do
+      p('abc//def').normalized_pattern.should == 'abc/def'
+      p('abc//def').exact_path.should == 'abc/def'
+      p('abc//def').match?('abc/def').should be_true
+      p('abc//').normalized_pattern.should == 'abc'
+      p('abc//').exact_path.should == 'abc'
+      p('abc//').match?('abc').should be_true
+    end
+    it 'handles dot' do
+      p('abc/./def').normalized_pattern.should == 'abc/def'
+      p('abc/./def').exact_path.should == 'abc/def'
+      p('abc/./def').match?('abc/def').should be_true
+      p('./abc/def').normalized_pattern.should == 'abc/def'
+      p('./abc/def').exact_path.should == 'abc/def'
+      p('./abc/def').match?('abc/def').should be_true
+      p('/.').normalized_pattern.should == '/'
+      p('/.').exact_path.should == '/'
+      p('/.').match?('/').should be_true
+    end
+    it 'handles dot by itself', :pending => "decide what to do with dot by itself" do
+      p('.').normalized_pattern.should == '.'
+      p('.').exact_path.should == '.'
+      p('.').match?('.').should be_true
+      p('./').normalized_pattern.should == '.'
+      p('./').exact_path.should == '.'
+      p('./').match?('.').should be_true
+    end
+    it 'handles dotdot' do
+      p('abc/../def').normalized_pattern.should == 'def'
+      p('abc/../def').exact_path.should == 'def'
+      p('abc/../def').match?('def').should be_true
+      p('abc/def/../..').normalized_pattern.should == ''
+      p('abc/def/../..').exact_path.should == ''
+      p('abc/def/../..').match?('').should be_true
+      p('/*/../def').normalized_pattern.should == '/def'
+      p('/*/../def').exact_path.should == '/def'
+      p('/*/../def').match?('/def').should be_true
+      p('/*/*/../def').normalized_pattern.should == '/*/def'
+      p('/*/*/../def').exact_path.should be_nil
+      p('/*/*/../def').match?('/abc/def').should be_true
+      p('/abc/def/../..').normalized_pattern.should == '/'
+      p('/abc/def/../..').exact_path.should == '/'
+      p('/abc/def/../..').match?('/').should be_true
+      p('abc/../../def').normalized_pattern.should == '../def'
+      p('abc/../../def').exact_path.should == '../def'
+      p('abc/../../def').match?('../def').should be_true
+    end
+    it 'handles dotdot with double star' do
+      p('abc**/def/../ghi').exact_path.should be_nil
+      p('abc**/def/../ghi').match?('abc/ghi').should be_true
+      p('abc**/def/../ghi').match?('abc/x/y/z/ghi').should be_true
+      p('abc**/def/../ghi').match?('ghi').should be_false
+    end
+    it 'raises error on dotdot with overlapping double star' do
+      lambda { Chef::ChefFS::FilePattern.new('abc/**/../def').exact_path }.should raise_error(ArgumentError)
+      lambda { Chef::ChefFS::FilePattern.new('abc/**/abc/../../def').exact_path }.should raise_error(ArgumentError)
+    end
+    it 'handles leading dotdot' do
+      p('../abc/def').exact_path.should == '../abc/def'
+      p('../abc/def').match?('../abc/def').should be_true
+      p('/../abc/def').exact_path.should == '/abc/def'
+      p('/../abc/def').match?('/abc/def').should be_true
+      p('..').exact_path.should == '..'
+      p('..').match?('..').should be_true
+      p('/..').exact_path.should == '/'
+      p('/..').match?('/').should be_true
+    end
+  end
+
+
+  # match?
+  #  - single element matches (empty, fixed, ?, *, characters, escapes)
+  #  - nested matches
+  #  - absolute matches
+  #  - trailing slashes
+  #  - **
+
+  # exact_path
+  #  - empty
+  #  - single element and nested matches, with escapes
+  #  - absolute and relative
+  #  - ?, *, characters, **
+
+  # could_match_children?
+  #
+  #
+  #
+  #
+  context 'with pattern "abc"' do
+  end
+
+  context 'with pattern "/abc"' do
+  end
+
+  context 'with pattern "abc/def/ghi"' do
+  end
+
+  context 'with pattern "/abc/def/ghi"' do
+  end
+
+  # Exercise the different methods to their maximum
+end
diff --git a/spec/unit/chef_fs/file_system/operation_failed_error_spec.rb b/spec/unit/chef_fs/file_system/operation_failed_error_spec.rb
new file mode 100644
index 0000000..570246c
--- /dev/null
+++ b/spec/unit/chef_fs/file_system/operation_failed_error_spec.rb
@@ -0,0 +1,47 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/chef_fs/file_system/operation_failed_error'
+
+describe Chef::ChefFS::FileSystem::OperationFailedError do
+  context 'message' do
+    let(:error_message) { 'HTTP error writing: 400 "Bad Request"' }
+
+    context 'has a cause attribute and HTTP result code is 400' do
+      it 'include error cause' do
+        allow_message_expectations_on_nil
+        response_body = '{"error":["Invalid key test in request body"]}'
+        @response.stub(:code).and_return("400")
+        @response.stub(:body).and_return(response_body)
+        exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+        proc {
+          raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, exception), error_message
+        }.should raise_error(Chef::ChefFS::FileSystem::OperationFailedError, "#{error_message} cause: #{response_body}")
+      end
+    end
+
+    context 'does not have a cause attribute' do
+      it 'does not include error cause' do
+        proc {
+          raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self), error_message
+        }.should raise_error(Chef::ChefFS::FileSystem::OperationFailedError, error_message)
+      end
+    end
+  end
+end
diff --git a/spec/unit/chef_fs/file_system_spec.rb b/spec/unit/chef_fs/file_system_spec.rb
new file mode 100644
index 0000000..383a2c8
--- /dev/null
+++ b/spec/unit/chef_fs/file_system_spec.rb
@@ -0,0 +1,135 @@
+#
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/chef_fs/file_system'
+require 'chef/chef_fs/file_pattern'
+
+describe Chef::ChefFS::FileSystem do
+  include FileSystemSupport
+
+  context 'with empty filesystem' do
+    let(:fs) { memory_fs('', {}) }
+
+    context 'list' do
+      it '/' do
+        list_should_yield_paths(fs, '/', '/')
+      end
+      it '/a' do
+        list_should_yield_paths(fs, '/a', '/a')
+      end
+      it '/a/b' do
+        list_should_yield_paths(fs, '/a/b', '/a/b')
+      end
+      it '/*' do
+        list_should_yield_paths(fs, '/*', '/')
+      end
+    end
+
+    context 'resolve_path' do
+      it '/' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/').path.should == '/'
+      end
+      it 'nonexistent /a' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/a').path.should == '/a'
+      end
+      it 'nonexistent /a/b' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/a/b').path.should == '/a/b'
+      end
+    end
+  end
+
+  context 'with a populated filesystem' do
+    let(:fs) {
+      memory_fs('', {
+        :a => {
+          :aa => {
+            :c => '',
+            :zz => ''
+          },
+          :ab => {
+            :c => '',
+          }
+        },
+        :x => ''
+      })
+    }
+    context 'list' do
+      it '/**' do
+        list_should_yield_paths(fs, '/**', '/', '/a', '/x', '/a/aa', '/a/aa/c', '/a/aa/zz', '/a/ab', '/a/ab/c')
+      end
+      it '/' do
+        list_should_yield_paths(fs, '/', '/')
+      end
+      it '/*' do
+        list_should_yield_paths(fs, '/*', '/', '/a', '/x')
+      end
+      it '/*/*' do
+        list_should_yield_paths(fs, '/*/*', '/a/aa', '/a/ab')
+      end
+      it '/*/*/*' do
+        list_should_yield_paths(fs, '/*/*/*', '/a/aa/c', '/a/aa/zz', '/a/ab/c')
+      end
+      it '/*/*/?' do
+        list_should_yield_paths(fs, '/*/*/?', '/a/aa/c', '/a/ab/c')
+      end
+      it '/a/*/c' do
+        list_should_yield_paths(fs, '/a/*/c', '/a/aa/c', '/a/ab/c')
+      end
+      it '/**b/c' do
+        list_should_yield_paths(fs, '/**b/c', '/a/ab/c')
+      end
+      it '/a/ab/c' do
+        no_blocking_calls_allowed
+        list_should_yield_paths(fs, '/a/ab/c', '/a/ab/c')
+      end
+      it 'nonexistent /a/ab/blah' do
+        no_blocking_calls_allowed
+        list_should_yield_paths(fs, '/a/ab/blah', '/a/ab/blah')
+      end
+      it 'nonexistent /a/ab/blah/bjork' do
+        no_blocking_calls_allowed
+        list_should_yield_paths(fs, '/a/ab/blah/bjork', '/a/ab/blah/bjork')
+      end
+    end
+
+    context 'resolve_path' do
+      before(:each) do
+        no_blocking_calls_allowed
+      end
+      it 'resolves /' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/').path.should == '/'
+      end
+      it 'resolves /x' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/x').path.should == '/x'
+      end
+      it 'resolves /a' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/a').path.should == '/a'
+      end
+      it 'resolves /a/aa' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/a/aa').path.should == '/a/aa'
+      end
+      it 'resolves /a/aa/zz' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/a/aa/zz').path.should == '/a/aa/zz'
+      end
+      it 'resolves nonexistent /y/x/w' do
+        Chef::ChefFS::FileSystem.resolve_path(fs, '/y/x/w').path.should == '/y/x/w'
+      end
+    end
+  end
+end
diff --git a/spec/unit/chef_spec.rb b/spec/unit/chef_spec.rb
new file mode 100644
index 0000000..b0f0359
--- /dev/null
+++ b/spec/unit/chef_spec.rb
@@ -0,0 +1,25 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef do
+  it "should have a version defined" do
+    Chef::VERSION.should match(/(\d+)\.(\d+)\.(\d+)/)
+  end
+end
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
new file mode 100644
index 0000000..67eb97f
--- /dev/null
+++ b/spec/unit/client_spec.rb
@@ -0,0 +1,465 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright 2008-2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'chef/run_context'
+require 'chef/rest'
+require 'rbconfig'
+
+shared_examples_for Chef::Client do
+  before do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    # Node/Ohai data
+    @hostname = "hostname"
+    @fqdn = "hostname.example.org"
+    Chef::Config[:node_name] = @fqdn
+    ohai_data = { :fqdn             => @fqdn,
+                  :hostname         => @hostname,
+                  :platform         => 'example-platform',
+                  :platform_version => 'example-platform-1.0',
+                  :data             => {} }
+    ohai_data.stub!(:all_plugins).and_return(true)
+    ohai_data.stub!(:data).and_return(ohai_data)
+    Ohai::System.stub!(:new).and_return(ohai_data)
+
+    @node = Chef::Node.new
+    @node.name(@fqdn)
+    @node.chef_environment("_default")
+
+    @client = Chef::Client.new
+    @client.node = @node
+  end
+
+  describe "authentication protocol selection" do
+    after do
+      Chef::Config[:authentication_protocol_version] = "1.0"
+    end
+
+    context "when the node name is <= 90 bytes" do
+      it "does not force the authentication protocol to 1.1" do
+        Chef::Config[:node_name] = ("f" * 90)
+        # ugly that this happens as a side effect of a getter :(
+        @client.node_name
+        Chef::Config[:authentication_protocol_version].should == "1.0"
+      end
+    end
+
+    context "when the node name is > 90 bytes" do
+      it "sets the authentication protocol to version 1.1" do
+        Chef::Config[:node_name] = ("f" * 91)
+        # ugly that this happens as a side effect of a getter :(
+        @client.node_name
+        Chef::Config[:authentication_protocol_version].should == "1.1"
+      end
+    end
+  end
+
+  describe "configuring output formatters" do
+    context "when no formatter has been configured" do
+      before do
+        @client = Chef::Client.new
+      end
+
+      context "and STDOUT is a TTY" do
+        before do
+          STDOUT.stub!(:tty?).and_return(true)
+        end
+
+        it "configures the :doc formatter" do
+          @client.formatters_for_run.should == [[:doc]]
+        end
+
+        context "and force_logger is set" do
+          before do
+            Chef::Config[:force_logger] = true
+          end
+
+          it "configures the :null formatter" do
+            Chef::Config[:force_logger].should be_true
+            @client.formatters_for_run.should == [[:null]]
+          end
+
+        end
+
+      end
+
+      context "and STDOUT is not a TTY" do
+        before do
+          STDOUT.stub!(:tty?).and_return(false)
+        end
+
+        it "configures the :null formatter" do
+          @client.formatters_for_run.should == [[:null]]
+        end
+
+        context "and force_formatter is set" do
+          before do
+            Chef::Config[:force_formatter] = true
+          end
+          it "it configures the :doc formatter" do
+            @client.formatters_for_run.should == [[:doc]]
+          end
+        end
+      end
+
+    end
+
+    context "when a formatter is configured" do
+      context "with no output path" do
+        before do
+          @client = Chef::Client.new
+          Chef::Config.add_formatter(:min)
+        end
+
+        it "does not configure a default formatter" do
+          @client.formatters_for_run.should == [[:min, nil]]
+        end
+
+        it "configures the formatter for STDOUT/STDERR" do
+          configured_formatters = @client.configure_formatters
+          min_formatter = configured_formatters[0]
+          min_formatter.output.out.should == STDOUT
+          min_formatter.output.err.should == STDERR
+        end
+      end
+
+      context "with an output path" do
+        before do
+          @client = Chef::Client.new
+          @tmpout = Tempfile.open("rspec-for-client-formatter-selection-#{Process.pid}")
+          Chef::Config.add_formatter(:min, @tmpout.path)
+        end
+
+        after do
+          @tmpout.close unless @tmpout.closed?
+          @tmpout.unlink
+        end
+
+        it "configures the formatter for the file path" do
+          configured_formatters = @client.configure_formatters
+          min_formatter = configured_formatters[0]
+          min_formatter.output.out.path.should == @tmpout.path
+          min_formatter.output.err.path.should == @tmpout.path
+        end
+      end
+
+    end
+  end
+
+  describe "run" do
+
+    it "should identify the node and run ohai, then register the client" do
+      mock_chef_rest_for_node = mock("Chef::REST (node)")
+      mock_chef_rest_for_client = mock("Chef::REST (client)")
+      mock_chef_rest_for_node_save = mock("Chef::REST (node save)")
+      mock_chef_runner = mock("Chef::Runner")
+
+      # --Client.register
+      #   Make sure Client#register thinks the client key doesn't
+      #   exist, so it tries to register and create one.
+      File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(false)
+
+      #   Client.register will register with the validation client name.
+      Chef::ApiClient::Registration.any_instance.should_receive(:run)
+      #   Client.register will then turn around create another
+      #   Chef::REST object, this time with the client key it got from the
+      #   previous step.
+      Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).exactly(1).and_return(mock_chef_rest_for_node)
+
+      # --Client#build_node
+      #   looks up the node, which we will return, then later saves it.
+      Chef::Node.should_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.)
+      Chef::ResourceReporter.any_instance.should_receive(:node_load_completed)
+
+      # --ResourceReporter#run_completed
+      #   updates the server with the resource history
+      #   (has its own tests, so stubbing it here.)
+      Chef::ResourceReporter.any_instance.should_receive(:run_completed)
+      # --Client#setup_run_context
+      # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+      #
+      Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
+      mock_chef_rest_for_node.should_receive(:post_rest).with("environments/_default/cookbook_versions", {:run_list => []}).and_return({})
+
+      # --Client#converge
+      Chef::Runner.should_receive(:new).and_return(mock_chef_runner)
+      mock_chef_runner.should_receive(:converge).and_return(true)
+
+      # --Client#save_updated_node
+      Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_node_save)
+      mock_chef_rest_for_node_save.should_receive(:put_rest).with("nodes/#{@fqdn}", @node).and_return(true)
+
+      Chef::RunLock.any_instance.should_receive(:acquire)
+      Chef::RunLock.any_instance.should_receive(:save_pid)
+      Chef::RunLock.any_instance.should_receive(:release)
+
+      # Post conditions: check that node has been filled in correctly
+      @client.should_receive(:run_started)
+      @client.should_receive(:run_completed_successfully)
+
+      if(Chef::Config[:client_fork] && !windows?)
+        require 'stringio'
+        if(Chef::Config[:pipe_node])
+          pipe_sim = StringIO.new
+          pipe_sim.should_receive(:close).exactly(4).and_return(nil)
+          res = ''
+          pipe_sim.should_receive(:puts) do |string|
+            res.replace(string)
+          end
+          pipe_sim.should_receive(:gets).and_return(res)
+          IO.should_receive(:pipe).and_return([pipe_sim, pipe_sim])
+          IO.should_receive(:select).and_return(true)
+        end
+        proc_ret = Class.new.new
+        proc_ret.should_receive(:success?).and_return(true)
+        Process.should_receive(:waitpid2).and_return([1, proc_ret])
+        @client.should_receive(:exit).and_return(nil)
+        @client.should_receive(:fork) do |&block|
+          block.call
+        end
+      end
+
+      # This is what we're testing.
+      @client.run
+
+      if(!Chef::Config[:client_fork] || Chef::Config[:pipe_node])
+        @node.automatic_attrs[:platform].should == "example-platform"
+        @node.automatic_attrs[:platform_version].should == "example-platform-1.0"
+      end
+    end
+
+    it "should remove the run_lock on failure of #load_node" do
+      @run_lock = mock("Chef::RunLock", :acquire => true)
+      Chef::RunLock.stub!(:new).and_return(@run_lock)
+
+      @events = mock("Chef::EventDispatch::Dispatcher").as_null_object
+      Chef::EventDispatch::Dispatcher.stub!(:new).and_return(@events)
+
+      # @events is created on Chef::Client.new, so we need to recreate it after mocking
+      @client = Chef::Client.new
+      @client.stub!(:load_node).and_raise(Exception)
+      @run_lock.should_receive(:release)
+      if(Chef::Config[:client_fork] && !windows?)
+        @client.should_receive(:fork) do |&block|
+          block.call
+        end
+      end
+      lambda { @client.run }.should raise_error(Exception)
+    end
+
+    describe "when notifying other objects of the status of the chef run" do
+      before do
+        Chef::Client.clear_notifications
+        Chef::Node.stub!(:find_or_create).and_return(@node)
+        @node.stub!(:save)
+        @client.build_node
+      end
+
+      it "notifies observers that the run has started" do
+        notified = false
+        Chef::Client.when_run_starts do |run_status|
+          run_status.node.should == @node
+          notified = true
+        end
+
+        @client.run_started
+        notified.should be_true
+      end
+
+      it "notifies observers that the run has completed successfully" do
+        notified = false
+        Chef::Client.when_run_completes_successfully do |run_status|
+          run_status.node.should == @node
+          notified = true
+        end
+
+        @client.run_completed_successfully
+        notified.should be_true
+      end
+
+      it "notifies observers that the run failed" do
+        notified = false
+        Chef::Client.when_run_fails do |run_status|
+          run_status.node.should == @node
+          notified = true
+        end
+
+        @client.run_failed
+        notified.should be_true
+      end
+    end
+  end
+
+  describe "build_node" do
+    it "should expand the roles and recipes for the node" do
+      @node.run_list << "role[role_containing_cookbook1]"
+      role_containing_cookbook1 = Chef::Role.new
+      role_containing_cookbook1.name("role_containing_cookbook1")
+      role_containing_cookbook1.run_list << "cookbook1"
+
+      # build_node will call Node#expand! with server, which will
+      # eventually hit the server to expand the included role.
+      mock_chef_rest = mock("Chef::REST")
+      mock_chef_rest.should_receive(:get_rest).with("roles/role_containing_cookbook1").and_return(role_containing_cookbook1)
+      Chef::REST.should_receive(:new).and_return(mock_chef_rest)
+
+      # check pre-conditions.
+      @node[:roles].should be_nil
+      @node[:recipes].should be_nil
+
+      @client.build_node
+
+      # check post-conditions.
+      @node[:roles].should_not be_nil
+      @node[:roles].length.should == 1
+      @node[:roles].should include("role_containing_cookbook1")
+      @node[:recipes].should_not be_nil
+      @node[:recipes].length.should == 1
+      @node[:recipes].should include("cookbook1")
+    end
+  end
+
+  describe "windows_admin_check" do
+    before do
+      @client = Chef::Client.new
+    end
+
+    context "platform is not windows" do
+      before do
+        Chef::Platform.stub(:windows?).and_return(false)
+      end
+
+      it "shouldn't be called" do
+        @client.should_not_receive(:has_admin_privileges?)
+        @client.do_windows_admin_check
+      end
+    end
+
+    context "platform is windows" do
+      before do
+        Chef::Platform.stub(:windows?).and_return(true)
+      end
+
+      it "should be called" do
+        @client.should_receive(:has_admin_privileges?)
+        @client.do_windows_admin_check
+      end
+
+      context "admin privileges exist" do
+        before do
+          @client.should_receive(:has_admin_privileges?).and_return(true)
+        end
+
+        it "should not log a warning message" do
+          Chef::Log.should_not_receive(:warn)
+          @client.do_windows_admin_check
+        end
+
+        context "fatal admin check is configured" do
+          it "should not raise an exception" do
+            @client.do_windows_admin_check.should_not raise_error(Chef::Exceptions::WindowsNotAdmin)
+          end
+        end
+      end
+
+      context "admin privileges doesn't exist" do
+        before do
+          @client.should_receive(:has_admin_privileges?).and_return(false)
+        end
+
+        it "should log a warning message" do
+          Chef::Log.should_receive(:warn)
+          @client.do_windows_admin_check
+        end
+
+        context "fatal admin check is configured" do
+          it "should raise an exception" do
+            @client.do_windows_admin_check.should_not raise_error(Chef::Exceptions::WindowsNotAdmin)
+          end
+        end
+      end
+    end
+  end
+
+  describe "when a run list override is provided" do
+    before do
+      @node = Chef::Node.new
+      @node.name(@fqdn)
+      @node.chef_environment("_default")
+      @node.automatic_attrs[:platform] = "example-platform"
+      @node.automatic_attrs[:platform_version] = "example-platform-1.0"
+    end
+
+    it "should permit spaces in overriding run list" do
+      @client = Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
+    end
+
+    it "should override the run list and save original runlist" do
+      @client = Chef::Client.new(nil, :override_runlist => 'role[test_role]')
+      @client.node = @node
+
+      @node.run_list << "role[role_containing_cookbook1]"
+
+      override_role = Chef::Role.new
+      override_role.name 'test_role'
+      override_role.run_list << 'cookbook1'
+
+      original_runlist = @node.run_list.dup
+
+      mock_chef_rest = mock("Chef::REST")
+      mock_chef_rest.should_receive(:get_rest).with("roles/test_role").and_return(override_role)
+      Chef::REST.should_receive(:new).and_return(mock_chef_rest)
+
+      @node.should_receive(:save).and_return(nil)
+
+      @client.build_node
+
+      @node[:roles].should_not be_nil
+      @node[:roles].should eql(['test_role'])
+      @node[:recipes].should eql(['cookbook1'])
+
+      @client.save_updated_node
+
+      @node.run_list.should == original_runlist
+
+    end
+  end
+
+end
+
+describe Chef::Client do
+  Chef::Config[:client_fork] = false
+  it_behaves_like Chef::Client
+end
+
+describe "Chef::Client Forked" do
+  before do
+    Chef::Config[:client_fork] = true
+  end
+
+  it_behaves_like Chef::Client
+
+end
diff --git a/spec/unit/config_fetcher_spec.rb b/spec/unit/config_fetcher_spec.rb
new file mode 100644
index 0000000..c295218
--- /dev/null
+++ b/spec/unit/config_fetcher_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+require 'chef/config_fetcher'
+describe Chef::ConfigFetcher do
+  let(:valid_json) { {:a=>"b"}.to_json }
+  let(:invalid_json) { %q[{"syntax-error": "missing quote}] }
+  let(:http) { double("Chef::HTTP::Simple") }
+
+  let(:config_location_regex) { Regexp.escape(config_location) }
+  let(:invalid_json_error_regex) { %r[Could not parse the provided JSON file \(#{config_location_regex}\)] }
+
+  let(:config_jail_path) { nil }
+
+  let(:fetcher) { Chef::ConfigFetcher.new(config_location, config_jail_path) }
+
+  context "when loading a local file" do
+    let(:config_location) { "/etc/chef/client.rb" }
+    let(:config_content) { "# The client.rb content" }
+
+    it "reads the file from disk" do
+      ::File.should_receive(:read).
+        with(config_location).
+        and_return(config_content)
+      fetcher.read_config.should == config_content
+    end
+
+    context "and consuming JSON" do
+
+      let(:config_location) { "/etc/chef/first-boot.json" }
+
+
+      it "returns the parsed JSON" do
+        ::File.should_receive(:read).
+          with(config_location).
+          and_return(valid_json)
+
+        fetcher.fetch_json.should == {"a" => "b"}
+      end
+
+      context "and the JSON is invalid" do
+
+        it "reports the JSON error" do
+
+
+          ::File.should_receive(:read).
+            with(config_location).
+            and_return(invalid_json)
+
+          Chef::Application.should_receive(:fatal!).
+            with(invalid_json_error_regex, 2)
+          fetcher.fetch_json
+        end
+      end
+    end
+
+  end
+
+  context "when loading a file over HTTP" do
+
+    let(:config_location) { "https://example.com/client.rb" }
+    let(:config_content) { "# The client.rb content" }
+
+    before do
+      Chef::HTTP::Simple.should_receive(:new).
+        with(config_location).
+        and_return(http)
+    end
+
+    it "reads the file over HTTP" do
+        http.should_receive(:get).
+          with("").and_return(config_content)
+      fetcher.read_config.should == config_content
+    end
+
+    context "and consuming JSON" do
+      let(:config_location) { "https://example.com/foo.json" }
+
+      it "fetches the file and parses it" do
+        http.should_receive(:get).
+          with("").and_return(valid_json)
+        fetcher.fetch_json.should == {"a" => "b"}
+      end
+
+      context "and the JSON is invalid" do
+        it "reports the JSON error" do
+          http.should_receive(:get).
+            with("").and_return(invalid_json)
+
+          Chef::Application.should_receive(:fatal!).
+            with(invalid_json_error_regex, 2)
+          fetcher.fetch_json
+        end
+      end
+    end
+
+  end
+
+
+end
diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb
new file mode 100644
index 0000000..0a24603
--- /dev/null
+++ b/spec/unit/config_spec.rb
@@ -0,0 +1,363 @@
+#
+# 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'
+
+describe Chef::Config do
+  before(:all) do
+    @original_env = { 'HOME' => ENV['HOME'], 'SYSTEMDRIVE' => ENV['SYSTEMDRIVE'], 'HOMEPATH' => ENV['HOMEPATH'], 'USERPROFILE' => ENV['USERPROFILE'] }
+  end
+
+  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
+      Chef::Config.chef_server_url.should == "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
+        Chef::Config.chef_server_url.should == "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
+        Chef::Config.chef_server_url.should == "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
+      Chef::Config.formatters.should == []
+    end
+
+    it "configures a formatter with a short name" do
+      Chef::Config.add_formatter(:doc)
+      Chef::Config.formatters.should == [[:doc, nil]]
+    end
+
+    it "configures a formatter with a file output" do
+      Chef::Config.add_formatter(:doc, "/var/log/formatter.log")
+      Chef::Config.formatters.should == [[:doc, "/var/log/formatter.log"]]
+    end
+
+  end
+
+  describe "class method: manage_secret_key" do
+    before do
+      Chef::FileCache.stub!(:load).and_return(true)
+      Chef::FileCache.stub!(:has_key?).with("chef_server_cookie_id").and_return(false)
+    end
+
+    it "should generate and store a chef server cookie id" do
+      Chef::FileCache.should_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
+        Chef::FileCache.stub!(:has_key?).with("chef_server_cookie_id").and_return(true)
+      end
+
+      it "should not generate and store a chef server cookie id" do
+        Chef::FileCache.should_not_receive(:store).with("chef_server_cookie_id", /\w{40}/)
+        Chef::Config.manage_secret_key
+      end
+    end
+
+  end
+
+  describe "config attribute writer: log_method=" do
+    describe "when given an object that responds to sync= e.g. IO" do
+      it "should configure itself to use the IO as log_location" do
+        Chef::Config.log_location = STDOUT
+        Chef::Config.log_location.should == STDOUT
+      end
+    end
+
+    describe "when given an object that is stringable (to_str)" do
+      before do
+        @mockfile = mock("File", :path => "/var/log/chef/client.log", :sync= => true)
+        File.should_receive(:new).
+          with("/var/log/chef/client.log", "a").
+          and_return(@mockfile)
+      end
+
+      it "should configure itself to use a File object based upon the String" do
+        Chef::Config.log_location = "/var/log/chef/client.log"
+        Chef::Config.log_location.path.should == "/var/log/chef/client.log"
+      end
+    end
+  end
+
+  describe "class method: plaform_specific_path" do
+    it "should return given path on non-windows systems" do
+      platform_mock :unix do
+        path = "/etc/chef/cookbooks"
+        Chef::Config.platform_specific_path(path).should == "/etc/chef/cookbooks"
+      end
+    end
+
+    it "should return a windows path on windows systems" do
+      platform_mock :windows do
+        path = "/etc/chef/cookbooks"
+        ENV.stub!(:[]).with('SYSTEMDRIVE').and_return('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
+        Chef::Config.platform_specific_path(path).should == "C:\\chef\\cookbooks"
+      end
+    end
+  end
+
+  describe "default values" do
+
+    it "Chef::Config[:file_backup_path] defaults to /var/chef/backup" do
+      backup_path = if windows?
+        "#{ENV['SYSTEMDRIVE']}\\chef\\backup"
+      else
+        "/var/chef/backup"
+      end
+      Chef::Config[:file_backup_path].should == backup_path
+    end
+
+    it "Chef::Config[:ssl_verify_mode] defaults to :verify_none" do
+      Chef::Config[:ssl_verify_mode].should == :verify_none
+    end
+
+    it "Chef::Config[:ssl_ca_path] defaults to nil" do
+      Chef::Config[:ssl_ca_path].should be_nil
+    end
+
+    describe "when on UNIX" do
+      before do
+        Chef::Config.stub(:on_windows?).and_return(false)
+      end
+
+      it "Chef::Config[:ssl_ca_file] defaults to nil" do
+        Chef::Config[:ssl_ca_file].should be_nil
+      end
+    end
+
+    it "Chef::Config[:data_bag_path] defaults to /var/chef/data_bags" do
+      data_bag_path =
+        Chef::Config.platform_specific_path("/var/chef/data_bags")
+      Chef::Config[:data_bag_path].should == data_bag_path
+    end
+
+    it "Chef::Config[:environment_path] defaults to /var/chef/environments" do
+      environment_path = if windows?
+        "C:\\chef\\environments"
+      else
+        "/var/chef/environments"
+      end
+
+      Chef::Config[:environment_path].should == environment_path
+    end
+
+    describe "joining platform specific paths" do
+
+      context "on UNIX" do
+        before do
+          Chef::Config.stub(:on_windows?).and_return(false)
+        end
+
+        it "joins components when some end with separators" do
+          Chef::Config.path_join("/foo/", "bar", "baz").should == "/foo/bar/baz"
+        end
+
+        it "joins components that don't end in separators" do
+          Chef::Config.path_join("/foo", "bar", "baz").should == "/foo/bar/baz"
+        end
+
+      end
+
+      context "on Windows" do
+        before do
+          Chef::Config.stub(:on_windows?).and_return(true)
+        end
+
+        it "joins components with the windows separator" do
+          Chef::Config.path_join('c:\\foo\\', 'bar', "baz").should == 'c:\\foo\\bar\\baz'
+        end
+      end
+    end
+
+    describe "setting the config dir" do
+
+      before do
+        Chef::Config.stub(:on_windows?).and_return(false)
+        Chef::Config.config_file = "/etc/chef/client.rb"
+      end
+
+      context "by default" do
+        it "is the parent dir of the config file" do
+          Chef::Config.config_dir.should == "/etc/chef"
+        end
+      end
+
+      context "when chef is running in local mode" do
+        before do
+          Chef::Config.local_mode = true
+          Chef::Config.user_home = "/home/charlie"
+        end
+
+        it "is in the user's home dir" do
+          Chef::Config.config_dir.should == "/home/charlie/.chef/"
+        end
+      end
+
+      context "when explicitly set" do
+        before do
+          Chef::Config.config_dir = "/other/config/dir/"
+        end
+
+        it "uses the explicit value" do
+          Chef::Config.config_dir.should == "/other/config/dir/"
+        end
+      end
+
+    end
+
+    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
+        Chef::Config.stub(:_this_file).and_return(default_config_location)
+        Chef::Config.embedded_dir.should == "c:/opscode/chef/embedded"
+      end
+
+      it "finds the embedded dir in a custom install location" do
+        Chef::Config.stub(:_this_file).and_return(alternate_install_location)
+        Chef::Config.embedded_dir.should == "c:/my/alternate/install/place/chef/embedded"
+      end
+
+      it "doesn't error when not in an omnibus install" do
+        Chef::Config.stub(:_this_file).and_return(non_omnibus_location)
+        Chef::Config.embedded_dir.should be_nil
+      end
+
+      it "sets the ssl_ca_cert path if the cert file is available" do
+        Chef::Config.stub(:_this_file).and_return(default_config_location)
+        Chef::Config.stub(:on_windows?).and_return(true)
+        File.stub(:exist?).with(default_ca_file).and_return(true)
+        Chef::Config.ssl_ca_file.should == default_ca_file
+      end
+    end
+  end
+
+  describe "Chef::Config[:user_home]" do
+    it "should set when HOME is provided" do
+      ENV['HOME'] = "/home/kitten"
+      load File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib", "chef", "config.rb"))
+      Chef::Config[:user_home].should == "/home/kitten"
+    end
+
+    it "should be set when only USERPROFILE is provided" do
+      ENV['HOME'], ENV['SYSTEMDRIVE'],  ENV['HOMEPATH'] = nil, nil, nil
+      ENV['USERPROFILE'] = "/users/kitten"
+      load File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib", "chef", "config.rb"))
+      Chef::Config[:user_home].should == "/users/kitten"
+    end
+
+    after(:each) do
+      @original_env.each do |env_setting|
+        ENV[env_setting[0]] = env_setting[1]
+      end
+    end
+  end
+
+  describe "Chef::Config[:encrypted_data_bag_secret]" do
+    db_secret_default_path =
+      Chef::Config.platform_specific_path("/etc/chef/encrypted_data_bag_secret")
+
+    let(:db_secret_default_path){ db_secret_default_path }
+
+    before do
+      File.stub(:exist?).with(db_secret_default_path).and_return(secret_exists)
+      # ugh...the only way to properly test this since the conditional
+      # is evaluated at file load/require time.
+      $LOADED_FEATURES.delete_if{|f| f =~ /chef\/config\.rb/}
+      require 'chef/config'
+    end
+
+    context "#{db_secret_default_path} exists" do
+      let(:secret_exists) { true }
+      it "sets the value to #{db_secret_default_path}" do
+        Chef::Config[:encrypted_data_bag_secret].should eq db_secret_default_path
+      end
+    end
+
+    context "#{db_secret_default_path} does not exist" do
+      let(:secret_exists) { false }
+      it "sets the value to nil" do
+        Chef::Config[:encrypted_data_bag_secret].should be_nil
+      end
+    end
+  end
+
+  describe "Chef::Config[:log_location]" do
+    it "raises ConfigurationError when log_location directory is missing" do
+      missing_path = "/tmp/non-existing-dir/file"
+      expect{Chef::Config.log_location = missing_path}.to raise_error Chef::Exceptions::ConfigurationError
+    end
+  end
+end
diff --git a/spec/unit/cookbook/chefignore_spec.rb b/spec/unit/cookbook/chefignore_spec.rb
new file mode 100644
index 0000000..aacb60c
--- /dev/null
+++ b/spec/unit/cookbook/chefignore_spec.rb
@@ -0,0 +1,39 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'spec_helper'
+
+describe Chef::Cookbook::Chefignore do
+  before do
+    @chefignore = Chef::Cookbook::Chefignore.new(File.join(CHEF_SPEC_DATA, 'cookbooks'))
+  end
+
+  it "loads the globs in the chefignore file" do
+    @chefignore.ignores.should =~ %w[recipes/ignoreme.rb ignored]
+  end
+
+  it "removes items from an array that match the ignores" do
+    file_list = %w[ recipes/ignoreme.rb recipes/dontignoreme.rb ]
+    @chefignore.remove_ignores_from(file_list).should == %w[recipes/dontignoreme.rb]
+  end
+
+  it "determines if a file is ignored" do
+    @chefignore.ignored?('ignored').should be_true
+    @chefignore.ignored?('recipes/ignoreme.rb').should be_true
+    @chefignore.ignored?('recipes/dontignoreme.rb').should be_false
+  end
+end
diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb
new file mode 100644
index 0000000..9822146
--- /dev/null
+++ b/spec/unit/cookbook/metadata_spec.rb
@@ -0,0 +1,627 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright 2008-2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/cookbook/metadata'
+
+describe Chef::Cookbook::Metadata do
+  before(:each) do
+    @cookbook = Chef::CookbookVersion.new('test_cookbook')
+    @meta = Chef::Cookbook::Metadata.new(@cookbook)
+  end
+
+  describe "when comparing for equality" do
+    before do
+      @fields = [ :name, :description, :long_description, :maintainer,
+                  :maintainer_email, :license, :platforms, :dependencies,
+                  :recommendations, :suggestions, :conflicting, :providing,
+                  :replacing, :attributes, :groupings, :recipes, :version]
+    end
+
+    it "does not depend on object identity for equality" do
+      @meta.should == @meta.dup
+    end
+
+    it "is not equal to another object if it isn't have all of the metadata fields" do
+      @fields.each_index do |field_to_remove|
+        fields_to_include = @fields.dup
+        fields_to_include.delete_at(field_to_remove)
+        almost_duck_type = Struct.new(*fields_to_include).new
+        @fields.each do |field|
+          setter = "#{field}="
+          metadata_value = @meta.send(field)
+          almost_duck_type.send(setter, metadata_value) if almost_duck_type.respond_to?(setter)
+          @mets.should_not == almost_duck_type
+        end
+      end
+    end
+
+    it "is equal to another object if it has equal values for all metadata fields" do
+      duck_type = Struct.new(*@fields).new
+      @fields.each do |field|
+        setter = "#{field}="
+        metadata_value = @meta.send(field)
+        duck_type.send(setter, metadata_value)
+      end
+      @meta.should == duck_type
+    end
+
+    it "is not equal if any values are different" do
+      duck_type_class = Struct.new(*@fields)
+      @fields.each do |field_to_change|
+        duck_type = duck_type_class.new
+
+        @fields.each do |field|
+          setter = "#{field}="
+          metadata_value = @meta.send(field)
+          duck_type.send(setter, metadata_value)
+        end
+
+        field_to_change
+
+        duck_type.send("#{field_to_change}=".to_sym, :epic_fail)
+        @meta.should_not == duck_type
+      end
+    end
+
+  end
+
+  describe "when first created" do
+    it "should return a Chef::Cookbook::Metadata object" do
+      @meta.should be_a_kind_of(Chef::Cookbook::Metadata)
+    end
+
+    it "should allow a cookbook as the first argument" do
+      lambda { Chef::Cookbook::Metadata.new(@cookbook) }.should_not raise_error
+    end
+
+    it "should allow an maintainer name for the second argument" do
+      lambda { Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown') }.should_not raise_error
+    end
+
+    it "should set the maintainer name from the second argument" do
+      md = Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown')
+      md.maintainer.should == 'Bobo T. Clown'
+    end
+
+    it "should allow an maintainer email for the third argument" do
+      lambda { Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo at clown.co') }.should_not raise_error
+    end
+
+    it "should set the maintainer email from the third argument" do
+      md = Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo at clown.co')
+      md.maintainer_email.should == 'bobo at clown.co'
+    end
+
+    it "should allow a license for the fourth argument" do
+      lambda { Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo at clown.co', 'Clown License v1') }.should_not raise_error
+    end
+
+    it "should set the license from the fourth argument" do
+      md = Chef::Cookbook::Metadata.new(@cookbook, 'Bobo T. Clown', 'bobo at clown.co', 'Clown License v1')
+      md.license.should == 'Clown License v1'
+    end
+  end
+
+  describe "cookbook" do
+    it "should return the cookbook we were initialized with" do
+      @meta.cookbook.should eql(@cookbook)
+    end
+  end
+
+  describe "name" do
+    it "should return the name of the cookbook" do
+      @meta.name.should eql(@cookbook.name)
+    end
+  end
+
+  describe "platforms" do
+    it "should return the current platform hash" do
+      @meta.platforms.should be_a_kind_of(Hash)
+    end
+  end
+
+  describe "adding a supported platform" do
+    it "should support adding a supported platform with a single expression" do
+      @meta.supports("ubuntu", ">= 8.04")
+      @meta.platforms["ubuntu"].should == '>= 8.04'
+    end
+  end
+
+  describe "meta-data attributes" do
+    params = {
+      :maintainer => "Adam Jacob",
+      :maintainer_email => "adam at opscode.com",
+      :license => "Apache v2.0",
+      :description => "Foobar!",
+      :long_description => "Much Longer\nSeriously",
+      :version => "0.6.0"
+    }
+    params.sort { |a,b| a.to_s <=> b.to_s }.each do |field, field_value|
+      describe field do
+        it "should be set-able via #{field}" do
+          @meta.send(field, field_value).should eql(field_value)
+        end
+        it "should be get-able via #{field}" do
+          @meta.send(field, field_value)
+          @meta.send(field).should eql(field_value)
+        end
+      end
+    end
+
+    describe "version transformation" do
+      it "should transform an '0.6' version to '0.6.0'" do
+        @meta.send(:version, "0.6").should eql("0.6.0")
+      end
+
+      it "should spit out '0.6.0' after transforming '0.6'" do
+        @meta.send(:version, "0.6")
+        @meta.send(:version).should eql("0.6.0")
+      end
+    end
+  end
+
+  describe "describing dependencies" do
+    dep_types = {
+      :depends     => [ :dependencies, "foo::bar", "> 0.2" ],
+      :recommends  => [ :recommendations, "foo::bar", ">= 0.2" ],
+      :suggests    => [ :suggestions, "foo::bar", "> 0.2" ],
+      :conflicts   => [ :conflicting, "foo::bar", "~> 0.2" ],
+      :provides    => [ :providing, "foo::bar", "<= 0.2" ],
+      :replaces    => [ :replacing, "foo::bar", "= 0.2.1" ],
+    }
+    dep_types.sort { |a,b| a.to_s <=> b.to_s }.each do |dep, dep_args|
+      check_with = dep_args.shift
+      describe dep do
+        it "should be set-able via #{dep}" do
+          @meta.send(dep, *dep_args).should == dep_args[1]
+        end
+        it "should be get-able via #{check_with}" do
+          @meta.send(dep, *dep_args)
+          @meta.send(check_with).should == { dep_args[0] => dep_args[1] }
+        end
+      end
+    end
+
+
+    describe "in the obsoleted format" do
+      dep_types = {
+        :depends     => [ "foo::bar", "> 0.2", "< 1.0" ],
+        :recommends  => [ "foo::bar", ">= 0.2", "< 1.0" ],
+        :suggests    => [ "foo::bar", "> 0.2", "< 1.0" ],
+        :conflicts   => [ "foo::bar", "> 0.2", "< 1.0" ],
+        :provides    => [ "foo::bar", "> 0.2", "< 1.0" ],
+        :replaces    => [ "foo::bar", "> 0.2.1", "< 1.0" ],
+      }
+
+      dep_types.each do |dep, dep_args|
+        it "for #{dep} raises an informative error instead of vomiting on your shoes" do
+          lambda {@meta.send(dep, *dep_args)}.should raise_error(Chef::Exceptions::ObsoleteDependencySyntax)
+        end
+      end
+    end
+
+
+    describe "with obsolete operators" do
+      dep_types = {
+        :depends     => [ "foo::bar", ">> 0.2"],
+        :recommends  => [ "foo::bar", ">> 0.2"],
+        :suggests    => [ "foo::bar", ">> 0.2"],
+        :conflicts   => [ "foo::bar", ">> 0.2"],
+        :provides    => [ "foo::bar", ">> 0.2"],
+        :replaces    => [ "foo::bar", ">> 0.2.1"],
+      }
+
+      dep_types.each do |dep, dep_args|
+        it "for #{dep} raises an informative error instead of vomiting on your shoes" do
+          lambda {@meta.send(dep, *dep_args)}.should raise_error(Chef::Exceptions::InvalidVersionConstraint)
+        end
+      end
+    end
+  end
+
+  describe "attribute groupings" do
+    it "should allow you set a grouping" do
+      group = {
+        "title" => "MySQL Tuning",
+        "description" => "Setting from the my.cnf file that allow you to tune your mysql server"
+      }
+      @meta.grouping("/db/mysql/databases/tuning", group).should == group
+    end
+    it "should not accept anything but a string for display_name" do
+      lambda {
+        @meta.grouping("db/mysql/databases", :title => "foo")
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.grouping("db/mysql/databases", :title => Hash.new)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should not accept anything but a string for the description" do
+      lambda {
+        @meta.grouping("db/mysql/databases", :description => "foo")
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.grouping("db/mysql/databases", :description => Hash.new)
+      }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "cookbook attributes" do
+    it "should allow you set an attributes metadata" do
+      attrs = {
+        "display_name" => "MySQL Databases",
+        "description" => "Description of MySQL",
+        "choice" => ['dedicated', 'shared'],
+        "calculated" => false,
+        "type" => 'string',
+        "required" => 'recommended',
+        "recipes" => [ "mysql::server", "mysql::master" ],
+        "default" => [ ]
+      }
+      @meta.attribute("/db/mysql/databases", attrs).should == attrs
+    end
+
+    it "should not accept anything but a string for display_name" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :display_name => "foo")
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :display_name => Hash.new)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should not accept anything but a string for the description" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :description => "foo")
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :description => Hash.new)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should not accept anything but an array of strings for choice" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :choice => ['dedicated', 'shared'])
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :choice => [10, 'shared'])
+      }.should raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :choice => Hash.new)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should set choice to empty array by default" do
+      @meta.attribute("db/mysql/databases", {})
+      @meta.attributes["db/mysql/databases"][:choice].should == []
+    end
+
+     it "should let calculated be true or false" do
+       lambda {
+         @meta.attribute("db/mysql/databases", :calculated => true)
+       }.should_not raise_error(ArgumentError)
+       lambda {
+         @meta.attribute("db/mysql/databases", :calculated => false)
+       }.should_not raise_error(ArgumentError)
+       lambda {
+         @meta.attribute("db/mysql/databases", :calculated => Hash.new)
+       }.should raise_error(ArgumentError)
+     end
+
+     it "should set calculated to false by default" do
+       @meta.attribute("db/mysql/databases", {})
+       @meta.attributes["db/mysql/databases"][:calculated].should == false
+     end
+
+    it "accepts String for the attribute type" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :type => "string")
+      }.should_not raise_error(ArgumentError)
+    end
+
+    it "accepts Array for the attribute type" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :type => "array")
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :type => Array.new)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "accepts symbol for the attribute type" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :type => "symbol")
+      }.should_not raise_error(ArgumentError)
+    end
+
+     it "should let type be hash (backwards compatability only)" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :type => "hash")
+      }.should_not raise_error(ArgumentError)
+    end
+
+    it "should let required be required, recommended or optional" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :required => 'required')
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :required => 'recommended')
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :required => 'optional')
+      }.should_not raise_error(ArgumentError)
+    end
+
+    it "should convert required true to required" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :required => true)
+      }.should_not raise_error(ArgumentError)
+      #attrib = @meta.attributes["db/mysql/databases"][:required].should == "required"
+    end
+
+    it "should convert required false to optional" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :required => false)
+      }.should_not raise_error(ArgumentError)
+      #attrib = @meta.attributes["db/mysql/databases"][:required].should == "optional"
+    end
+
+    it "should set required to 'optional' by default" do
+      @meta.attribute("db/mysql/databases", {})
+      @meta.attributes["db/mysql/databases"][:required].should == 'optional'
+    end
+
+    it "should make sure recipes is an array" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :recipes => [])
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :required => Hash.new)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should set recipes to an empty array by default" do
+      @meta.attribute("db/mysql/databases", {})
+      @meta.attributes["db/mysql/databases"][:recipes].should == []
+    end
+
+    it "should allow the default value to be a string, array, or hash" do
+      lambda {
+        @meta.attribute("db/mysql/databases", :default => [])
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :default => {})
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :default => "alice in chains")
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        @meta.attribute("db/mysql/databases", :required => :not_gonna_do_it)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should error if default used with calculated" do
+      lambda {
+        attrs = {
+          :calculated => true,
+          :default => [ "I thought you said calculated" ]
+        }
+        @meta.attribute("db/mysql/databases", attrs)
+      }.should raise_error(ArgumentError)
+      lambda {
+        attrs = {
+          :calculated => true,
+          :default => "I thought you said calculated"
+        }
+        @meta.attribute("db/mysql/databases", attrs)
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should allow a default that is a choice" do
+      lambda {
+        attrs = {
+          :choice => [ "a", "b", "c"],
+          :default => "b"
+        }
+        @meta.attribute("db/mysql/databases", attrs)
+      }.should_not raise_error(ArgumentError)
+      lambda {
+        attrs = {
+          :choice => [ "a", "b", "c", "d", "e"],
+          :default => ["b", "d"]
+        }
+        @meta.attribute("db/mysql/databases", attrs)
+      }.should_not raise_error(ArgumentError)
+     end
+
+    it "should error if default is not a choice" do
+      lambda {
+        attrs = {
+          :choice => [ "a", "b", "c"],
+          :default => "d"
+        }
+        @meta.attribute("db/mysql/databases", attrs)
+      }.should raise_error(ArgumentError)
+      lambda {
+        attrs = {
+          :choice => [ "a", "b", "c", "d", "e"],
+          :default => ["b", "z"]
+        }
+        @meta.attribute("db/mysql/databases", attrs)
+      }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "recipes" do
+    before(:each) do
+      @cookbook.recipe_files = [ "default.rb", "enlighten.rb" ]
+      @meta = Chef::Cookbook::Metadata.new(@cookbook)
+    end
+
+    it "should have the names of the recipes" do
+      @meta.recipes["test_cookbook"].should == ""
+      @meta.recipes["test_cookbook::enlighten"].should == ""
+    end
+
+    it "should let you set the description for a recipe" do
+      @meta.recipe "test_cookbook", "It, um... tests stuff?"
+      @meta.recipes["test_cookbook"].should == "It, um... tests stuff?"
+    end
+
+    it "should automatically provide each recipe" do
+      @meta.providing.has_key?("test_cookbook").should == true
+      @meta.providing.has_key?("test_cookbook::enlighten").should == true
+    end
+
+  end
+
+  describe "json" do
+    before(:each) do
+      @cookbook.recipe_files = [ "default.rb", "enlighten.rb" ]
+      @meta = Chef::Cookbook::Metadata.new(@cookbook)
+      @meta.version "1.0"
+      @meta.maintainer "Bobo T. Clown"
+      @meta.maintainer_email "bobo at example.com"
+      @meta.long_description "I have a long arm!"
+      @meta.supports :ubuntu, "> 8.04"
+      @meta.depends "bobo", "= 1.0"
+      @meta.depends "bobotclown", "= 1.1"
+      @meta.recommends "snark", "< 3.0"
+      @meta.suggests "kindness", "> 2.0"
+      @meta.conflicts "hatred"
+      @meta.provides "foo(:bar, :baz)"
+      @meta.replaces "snarkitron"
+      @meta.recipe "test_cookbook::enlighten", "is your buddy"
+      @meta.attribute "bizspark/has_login",
+        :display_name => "You have nothing"
+      @meta.version "1.2.3"
+    end
+
+    describe "serialize" do
+      before(:each) do
+        @serial = Chef::JSONCompat.from_json(@meta.to_json)
+      end
+
+      it "should serialize to a json hash" do
+        Chef::JSONCompat.from_json(@meta.to_json).should be_a_kind_of(Hash)
+      end
+
+      %w{
+        name
+        description
+        long_description
+        maintainer
+        maintainer_email
+        license
+        platforms
+        dependencies
+        suggestions
+        recommendations
+        conflicting
+        providing
+        replacing
+        attributes
+        recipes
+        version
+      }.each do |t|
+        it "should include '#{t}'" do
+          @serial[t].should == @meta.send(t.to_sym)
+        end
+      end
+    end
+
+    describe "deserialize" do
+      before(:each) do
+        @deserial = Chef::Cookbook::Metadata.from_json(@meta.to_json)
+      end
+
+      it "should deserialize to a Chef::Cookbook::Metadata object" do
+        @deserial.should be_a_kind_of(Chef::Cookbook::Metadata)
+      end
+
+      %w{
+        name
+        description
+        long_description
+        maintainer
+        maintainer_email
+        license
+        platforms
+        dependencies
+        suggestions
+        recommendations
+        conflicting
+        providing
+        replacing
+        attributes
+        recipes
+        version
+      }.each do |t|
+        it "should match '#{t}'" do
+          @deserial.send(t.to_sym).should == @meta.send(t.to_sym)
+        end
+      end
+    end
+
+    describe "from_hash" do
+      before(:each) do
+        @hash = @meta.to_hash
+      end
+
+      [:dependencies,
+       :recommendations,
+       :suggestions,
+       :conflicting,
+       :replacing].each do |to_check|
+        it "should transform deprecated greater than syntax for :#{to_check.to_s}" do
+          @hash[to_check.to_s]["foo::bar"] = ">> 0.2"
+          deserial = Chef::Cookbook::Metadata.from_hash(@hash)
+          deserial.send(to_check)["foo::bar"].should == '> 0.2'
+        end
+
+        it "should transform deprecated less than syntax for :#{to_check.to_s}" do
+          @hash[to_check.to_s]["foo::bar"] = "<< 0.2"
+          deserial = Chef::Cookbook::Metadata.from_hash(@hash)
+          deserial.send(to_check)["foo::bar"].should == '< 0.2'
+        end
+
+        it "should ignore multiple dependency constraints for :#{to_check.to_s}" do
+          @hash[to_check.to_s]["foo::bar"] = [ ">= 1.0", "<= 5.2" ]
+          deserial = Chef::Cookbook::Metadata.from_hash(@hash)
+          deserial.send(to_check)["foo::bar"].should == []
+        end
+
+        it "should accept an empty array of dependency constraints for :#{to_check.to_s}" do
+          @hash[to_check.to_s]["foo::bar"] = []
+          deserial = Chef::Cookbook::Metadata.from_hash(@hash)
+          deserial.send(to_check)["foo::bar"].should == []
+        end
+
+        it "should accept single-element arrays of dependency constraints for :#{to_check.to_s}" do
+          @hash[to_check.to_s]["foo::bar"] = [ ">= 2.0" ]
+          deserial = Chef::Cookbook::Metadata.from_hash(@hash)
+          deserial.send(to_check)["foo::bar"].should == ">= 2.0"
+        end
+      end
+    end
+
+  end
+
+end
diff --git a/spec/unit/cookbook/synchronizer_spec.rb b/spec/unit/cookbook/synchronizer_spec.rb
new file mode 100644
index 0000000..31f30c5
--- /dev/null
+++ b/spec/unit/cookbook/synchronizer_spec.rb
@@ -0,0 +1,306 @@
+require 'spec_helper'
+require 'chef/cookbook/synchronizer'
+require 'chef/cookbook_version'
+
+describe Chef::CookbookCacheCleaner do
+  describe "when cleaning up unused cookbook components" do
+
+    before do
+      @cleaner = Chef::CookbookCacheCleaner.instance
+      @cleaner.reset!
+    end
+
+    it "removes all files that belong to unused cookbooks" do
+    end
+
+    it "removes all files not validated during the chef run" do
+      file_cache = mock("Chef::FileCache with files from unused cookbooks")
+      unused_template_files = %w{cookbooks/unused/templates/default/foo.conf.erb cookbooks/unused/tempaltes/default/bar.conf.erb}
+      valid_cached_cb_files = %w{cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb}
+      @cleaner.mark_file_as_valid('cookbooks/valid1/recipes/default.rb')
+      @cleaner.mark_file_as_valid('cookbooks/valid2/recipes/default.rb')
+      file_cache.should_receive(:find).with(File.join(%w{cookbooks ** *})).and_return(valid_cached_cb_files + unused_template_files)
+      file_cache.should_receive(:delete).with('cookbooks/unused/templates/default/foo.conf.erb')
+      file_cache.should_receive(:delete).with('cookbooks/unused/tempaltes/default/bar.conf.erb')
+      cookbook_hash = {"valid1"=> {}, "valid2" => {}}
+      @cleaner.stub!(:cache).and_return(file_cache)
+      @cleaner.cleanup_file_cache
+    end
+
+    describe "on chef-solo" do
+      before do
+        Chef::Config[:solo] = true
+      end
+
+      after do
+        Chef::Config[:solo] = false
+      end
+
+      it "does not remove anything" do
+        @cleaner.cache.stub!(:find).and_return(%w{cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb})
+        @cleaner.cache.should_not_receive(:delete)
+        @cleaner.cleanup_file_cache
+      end
+
+    end
+
+  end
+end
+
+describe Chef::CookbookSynchronizer do
+  before do
+    segments = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
+    @cookbook_manifest = {}
+    @cookbook_a = Chef::CookbookVersion.new("cookbook_a")
+    @cookbook_a_manifest = segments.inject({}) {|h, segment| h[segment.to_s] = []; h}
+    @cookbook_a_default_recipe = { "path" => "recipes/default.rb",
+                                   "url"  => "http://chef.example.com/abc123",
+                                   "checksum" => "abc123" }
+    @cookbook_a_manifest["recipes"] = [ @cookbook_a_default_recipe ]
+
+    @cookbook_a_default_attrs  = { "path" => "attributes/default.rb",
+                                   "url"  => "http://chef.example.com/abc456",
+                                   "checksum" => "abc456" }
+    @cookbook_a_manifest["attributes"] = [ @cookbook_a_default_attrs ]
+    @cookbook_a_manifest["templates"] = [{"path" => "templates/default/apache2.conf.erb", "url" => "http://chef.example.com/ffffff"}]
+    @cookbook_a_manifest["files"] = [{"path" => "files/default/megaman.conf", "url" => "http://chef.example.com/megaman.conf"}]
+    @cookbook_a.manifest = @cookbook_a_manifest
+    @cookbook_manifest["cookbook_a"] = @cookbook_a
+
+    @events = Chef::EventDispatch::Dispatcher.new
+    @synchronizer = Chef::CookbookSynchronizer.new(@cookbook_manifest, @events)
+  end
+
+  it "lists the cookbook names" do
+    @synchronizer.cookbook_names.should == %w[cookbook_a]
+  end
+
+  it "lists the cookbook manifests" do
+    @synchronizer.cookbooks.should == [@cookbook_a]
+  end
+
+  context "when the cache contains unneeded cookbooks" do
+    before do
+      @file_cache = mock("Chef::FileCache with files from unused cookbooks")
+      @valid_cached_cb_files = %w{cookbooks/valid1/recipes/default.rb cookbooks/valid2/recipes/default.rb}
+      @obsolete_cb_files = %w{cookbooks/old1/recipes/default.rb cookbooks/old2/recipes/default.rb}
+
+      @cookbook_hash = {"valid1"=> {}, "valid2" => {}}
+
+      @synchronizer = Chef::CookbookSynchronizer.new(@cookbook_hash, @events)
+    end
+
+    it "removes unneeded cookbooks" do
+      @file_cache.should_receive(:find).with(File.join(%w{cookbooks ** *})).and_return(@valid_cached_cb_files + @obsolete_cb_files)
+      @file_cache.should_receive(:delete).with('cookbooks/old1/recipes/default.rb')
+      @file_cache.should_receive(:delete).with('cookbooks/old2/recipes/default.rb')
+      @synchronizer.stub!(:cache).and_return(@file_cache)
+      @synchronizer.clear_obsoleted_cookbooks
+    end
+  end
+
+  describe "when syncing cookbooks with the server" do
+    before do
+      # Would rather not stub out methods on the test subject, but setting up
+      # the state is a PITA and tests for this behavior are above.
+      @synchronizer.stub!(:clear_obsoleted_cookbooks)
+
+      @server_api = mock("Chef::REST (mock)")
+      @file_cache = mock("Chef::FileCache (mock)")
+      @synchronizer.stub!(:server_api).and_return(@server_api)
+      @synchronizer.stub!(:cache).and_return(@file_cache)
+
+
+      @cookbook_a_default_recipe_tempfile = mock("Tempfile for cookbook_a default.rb recipe",
+                                                 :path => "/tmp/cookbook_a_recipes_default_rb")
+
+      @cookbook_a_default_attribute_tempfile = mock("Tempfile for cookbook_a default.rb attr file",
+                                                 :path => "/tmp/cookbook_a_attributes_default_rb")
+
+    end
+
+    context "when the cache does not contain the desired files" do
+      before do
+
+        # Files are not in the cache:
+        @file_cache.should_receive(:has_key?).
+          with("cookbooks/cookbook_a/recipes/default.rb").
+          and_return(false)
+        @file_cache.should_receive(:has_key?).
+          with("cookbooks/cookbook_a/attributes/default.rb").
+          and_return(false)
+
+        # Fetch and copy default.rb recipe
+        @server_api.should_receive(:get_rest).
+          with('http://chef.example.com/abc123', true).
+          and_return(@cookbook_a_default_recipe_tempfile)
+        @file_cache.should_receive(:move_to).
+          with("/tmp/cookbook_a_recipes_default_rb", "cookbooks/cookbook_a/recipes/default.rb")
+        @file_cache.should_receive(:load).
+          with("cookbooks/cookbook_a/recipes/default.rb", false).
+          and_return("/file-cache/cookbooks/cookbook_a/recipes/default.rb")
+
+        # Fetch and copy default.rb attribute file
+        @server_api.should_receive(:get_rest).
+          with('http://chef.example.com/abc456', true).
+          and_return(@cookbook_a_default_attribute_tempfile)
+        @file_cache.should_receive(:move_to).
+          with("/tmp/cookbook_a_attributes_default_rb", "cookbooks/cookbook_a/attributes/default.rb")
+        @file_cache.should_receive(:load).
+          with("cookbooks/cookbook_a/attributes/default.rb", false).
+          and_return("/file-cache/cookbooks/cookbook_a/attributes/default.rb")
+      end
+
+      it "fetches eagerly loaded files" do
+        @synchronizer.sync_cookbooks
+      end
+
+      it "does not fetch templates or cookbook files" do
+        # Implicitly tested in previous test; this test is just for behavior specification.
+        @server_api.should_not_receive(:get_rest).
+          with('http://chef.example.com/ffffff', true)
+
+        @synchronizer.sync_cookbooks
+      end
+
+      context "Chef::Config[:no_lazy_load] is true" do
+        before do
+          Chef::Config[:no_lazy_load] = true
+          @synchronizer = Chef::CookbookSynchronizer.new(@cookbook_manifest, @events)
+          @synchronizer.stub!(:server_api).and_return(@server_api)
+          @synchronizer.stub!(:cache).and_return(@file_cache)
+          @synchronizer.stub!(:clear_obsoleted_cookbooks)
+
+          @cookbook_a_file_default_tempfile = mock("Tempfile for cookbook_a megaman.conf file",
+                                                     :path => "/tmp/cookbook_a_file_default_tempfile")
+          @cookbook_a_template_default_tempfile = mock("Tempfile for cookbook_a apache.conf.erb template",
+                                                     :path => "/tmp/cookbook_a_template_default_tempfile")
+        end
+
+        after do
+          Chef::Config[:no_lazy_load] = false
+        end
+
+        it "fetches templates and cookbook files" do
+          @file_cache.should_receive(:has_key?).
+            with("cookbooks/cookbook_a/files/default/megaman.conf").
+            and_return(false)
+          @file_cache.should_receive(:has_key?).
+            with("cookbooks/cookbook_a/templates/default/apache2.conf.erb").
+            and_return(false)
+
+          @server_api.should_receive(:get_rest).
+            with('http://chef.example.com/megaman.conf', true).
+            and_return(@cookbook_a_file_default_tempfile)
+          @file_cache.should_receive(:move_to).
+            with("/tmp/cookbook_a_file_default_tempfile", "cookbooks/cookbook_a/files/default/megaman.conf")
+          @file_cache.should_receive(:load).
+            with("cookbooks/cookbook_a/files/default/megaman.conf", false).
+            and_return("/file-cache/cookbooks/cookbook_a/default/megaman.conf")
+
+          @server_api.should_receive(:get_rest).
+            with('http://chef.example.com/ffffff', true).
+            and_return(@cookbook_a_template_default_tempfile)
+          @file_cache.should_receive(:move_to).
+            with("/tmp/cookbook_a_template_default_tempfile", "cookbooks/cookbook_a/templates/default/apache2.conf.erb")
+          @file_cache.should_receive(:load).
+            with("cookbooks/cookbook_a/templates/default/apache2.conf.erb", false).
+            and_return("/file-cache/cookbooks/cookbook_a/templates/default/apache2.conf.erb")
+
+          @synchronizer.sync_cookbooks
+        end
+      end
+    end
+
+    context "when the cache contains outdated files" do
+      before do
+        # Files are in the cache:
+        @file_cache.should_receive(:has_key?).
+          with("cookbooks/cookbook_a/recipes/default.rb").
+          and_return(true)
+        @file_cache.should_receive(:has_key?).
+          with("cookbooks/cookbook_a/attributes/default.rb").
+          and_return(true)
+
+
+        # Fetch and copy default.rb recipe
+        @server_api.should_receive(:get_rest).
+          with('http://chef.example.com/abc123', true).
+          and_return(@cookbook_a_default_recipe_tempfile)
+        @file_cache.should_receive(:move_to).
+          with("/tmp/cookbook_a_recipes_default_rb", "cookbooks/cookbook_a/recipes/default.rb")
+        @file_cache.should_receive(:load).
+          with("cookbooks/cookbook_a/recipes/default.rb", false).
+          twice.
+          and_return("/file-cache/cookbooks/cookbook_a/recipes/default.rb")
+
+        # Current file has fff000, want abc123
+        Chef::CookbookVersion.should_receive(:checksum_cookbook_file).
+          with("/file-cache/cookbooks/cookbook_a/recipes/default.rb").
+          and_return("fff000")
+
+        # Fetch and copy default.rb attribute file
+        @server_api.should_receive(:get_rest).
+          with('http://chef.example.com/abc456', true).
+          and_return(@cookbook_a_default_attribute_tempfile)
+        @file_cache.should_receive(:move_to).
+          with("/tmp/cookbook_a_attributes_default_rb", "cookbooks/cookbook_a/attributes/default.rb")
+        @file_cache.should_receive(:load).
+          with("cookbooks/cookbook_a/attributes/default.rb", false).
+          twice.
+          and_return("/file-cache/cookbooks/cookbook_a/attributes/default.rb")
+
+        # Current file has fff000, want abc456
+        Chef::CookbookVersion.should_receive(:checksum_cookbook_file).
+          with("/file-cache/cookbooks/cookbook_a/attributes/default.rb").
+          and_return("fff000")
+      end
+
+      it "updates the outdated files" do
+        @synchronizer.sync_cookbooks
+      end
+    end
+
+    context "when the cache is up to date" do
+      before do
+        # Files are in the cache:
+        @file_cache.should_receive(:has_key?).
+          with("cookbooks/cookbook_a/recipes/default.rb").
+          and_return(true)
+        @file_cache.should_receive(:has_key?).
+          with("cookbooks/cookbook_a/attributes/default.rb").
+          and_return(true)
+
+        # Current file has abc123, want abc123
+        Chef::CookbookVersion.should_receive(:checksum_cookbook_file).
+          with("/file-cache/cookbooks/cookbook_a/recipes/default.rb").
+          and_return("abc123")
+
+        # Current file has abc456, want abc456
+        Chef::CookbookVersion.should_receive(:checksum_cookbook_file).
+          with("/file-cache/cookbooks/cookbook_a/attributes/default.rb").
+          and_return("abc456")
+
+        @file_cache.should_receive(:load).
+          with("cookbooks/cookbook_a/recipes/default.rb", false).
+          twice.
+          and_return("/file-cache/cookbooks/cookbook_a/recipes/default.rb")
+
+        @file_cache.should_receive(:load).
+          with("cookbooks/cookbook_a/attributes/default.rb", false).
+          twice.
+          and_return("/file-cache/cookbooks/cookbook_a/attributes/default.rb")
+      end
+
+      it "does not update files" do
+        @file_cache.should_not_receive(:move_to)
+        @server_api.should_not_receive(:get_rest)
+        @synchronizer.sync_cookbooks
+      end
+
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb
new file mode 100644
index 0000000..85d6950
--- /dev/null
+++ b/spec/unit/cookbook/syntax_check_spec.rb
@@ -0,0 +1,174 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require "chef/cookbook/syntax_check"
+
+describe Chef::Cookbook::SyntaxCheck do
+
+  let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') }
+  let(:syntax_check) { Chef::Cookbook::SyntaxCheck.new(cookbook_path) }
+
+  before do
+    Chef::Log.logger = Logger.new(StringIO.new)
+    Chef::Log.level = :warn # suppress "Syntax OK" messages
+
+
+    @attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, 'attributes', f) }
+    @defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, 'definitions', f)}
+    @recipes = %w{default.rb gigantor.rb one.rb}.map { |f| File.join(cookbook_path, 'recipes', f) }
+    @ruby_files = @attr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")]
+    basenames = %w{ helpers_via_partial_test.erb
+                    helper_test.erb
+                    openldap_stuff.conf.erb
+                    openldap_variable_stuff.conf.erb
+                    test.erb
+                    some_windows_line_endings.erb
+                    all_windows_line_endings.erb
+                    no_windows_line_endings.erb }
+    @template_files = basenames.map { |f| File.join(cookbook_path, 'templates', 'default', f)}
+  end
+
+  it "creates a syntax checker given the cookbook name when Chef::Config.cookbook_path is set" do
+    Chef::Config[:cookbook_path] = File.dirname(cookbook_path)
+    syntax_check = Chef::Cookbook::SyntaxCheck.for_cookbook(:openldap)
+    syntax_check.cookbook_path.should == cookbook_path
+  end
+
+  describe "when first created" do
+    it "has the path to the cookbook to syntax check" do
+      syntax_check.cookbook_path.should == cookbook_path
+    end
+
+    it "lists the ruby files in the cookbook" do
+      syntax_check.ruby_files.sort.should == @ruby_files.sort
+    end
+
+    it "lists the erb templates in the cookbook" do
+      syntax_check.template_files.sort.should == @template_files.sort
+    end
+
+  end
+
+  describe "when validating cookbooks" do
+    let(:cache_path) { Dir.mktmpdir }
+
+    before do
+      Chef::Config[:syntax_check_cache_path] = cache_path
+    end
+
+    after do
+      FileUtils.rm_rf(cache_path) if File.exist?(cache_path)
+    end
+
+    describe "and the files have not been syntax checked previously" do
+      it "shows that all ruby files require a syntax check" do
+        syntax_check.untested_ruby_files.sort.should == @ruby_files.sort
+      end
+
+      it "shows that all template files require a syntax check" do
+        syntax_check.untested_template_files.sort.should == @template_files.sort
+      end
+
+      it "removes a ruby file from the list of untested files after it is marked as validated" do
+        recipe = File.join(cookbook_path, 'recipes', 'default.rb')
+        syntax_check.validated(recipe)
+        syntax_check.untested_ruby_files.should_not include(recipe)
+      end
+
+      it "removes a template file from the list of untested files after it is marked as validated" do
+        template = File.join(cookbook_path, 'templates', 'default', 'test.erb')
+        syntax_check.validated(template)
+        syntax_check.untested_template_files.should_not include(template)
+      end
+
+      it "validates all ruby files" do
+        syntax_check.validate_ruby_files.should be_true
+        syntax_check.untested_ruby_files.should be_empty
+      end
+
+      it "validates all templates" do
+        syntax_check.validate_templates.should be_true
+        syntax_check.untested_template_files.should be_empty
+      end
+
+      describe "and a file has a syntax error" do
+        before do
+          cookbook_path = File.join(CHEF_SPEC_DATA, 'cookbooks', 'borken')
+          syntax_check.cookbook_path.replace(cookbook_path)
+        end
+
+        it "it indicates that a ruby file has a syntax error" do
+          syntax_check.validate_ruby_files.should be_false
+        end
+
+        it "does not remove the invalid file from the list of untested files" do
+          syntax_check.untested_ruby_files.should include(File.join(cookbook_path, 'recipes', 'default.rb'))
+          lambda { syntax_check.validate_ruby_files }.should_not change(syntax_check, :untested_ruby_files)
+        end
+
+        it "indicates that a template file has a syntax error" do
+          syntax_check.validate_templates.should be_false
+        end
+
+        it "does not remove the invalid template from the list of untested templates" do
+          syntax_check.untested_template_files.should include(File.join(cookbook_path, 'templates', 'default', 'borken.erb'))
+          lambda {syntax_check.validate_templates}.should_not change(syntax_check, :untested_template_files)
+        end
+
+      end
+
+      describe "and an ignored file has a syntax error" do
+        before do
+          cookbook_path = File.join(CHEF_SPEC_DATA, 'cookbooks', 'ignorken')
+          Chef::Config[:cookbook_path] = File.dirname(cookbook_path)
+          syntax_check.cookbook_path.replace(cookbook_path)
+          @ruby_files = [File.join(cookbook_path, 'recipes/default.rb')]
+        end
+
+        it "shows that ignored ruby files do not require a syntax check" do
+          syntax_check.untested_ruby_files.sort.should == @ruby_files.sort
+        end
+
+        it "does not indicate that a ruby file has a syntax error" do
+          syntax_check.validate_ruby_files.should be_true
+          syntax_check.untested_ruby_files.should be_empty
+        end
+
+      end
+
+    end
+
+    describe "and the files have been syntax checked previously" do
+      before do
+        syntax_check.untested_ruby_files.each { |f| syntax_check.validated(f) }
+        syntax_check.untested_template_files.each { |f| syntax_check.validated(f) }
+      end
+
+      it "does not syntax check ruby files" do
+        syntax_check.should_not_receive(:shell_out)
+        syntax_check.validate_ruby_files.should be_true
+      end
+
+      it "does not syntax check templates" do
+        syntax_check.should_not_receive(:shell_out)
+        syntax_check.validate_templates.should be_true
+      end
+    end
+  end
+end
diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb
new file mode 100644
index 0000000..4c21c12
--- /dev/null
+++ b/spec/unit/cookbook_loader_spec.rb
@@ -0,0 +1,224 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::CookbookLoader do
+  before(:each) do
+    @repo_paths = [ File.expand_path(File.join(CHEF_SPEC_DATA, "kitchen")),
+                    File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) ]
+    @cookbook_loader = Chef::CookbookLoader.new(@repo_paths)
+  end
+
+  describe "loading all cookbooks" do
+    before(:each) do
+      @cookbook_loader.load_cookbooks
+    end
+
+    describe "[]" do
+      it "should return cookbook objects with []" do
+        @cookbook_loader[:openldap].should be_a_kind_of(Chef::CookbookVersion)
+      end
+
+      it "should raise an exception if it cannot find a cookbook with []" do
+        lambda { @cookbook_loader[:monkeypoop] }.should raise_error(Chef::Exceptions::CookbookNotFoundInRepo)
+      end
+
+      it "should allow you to look up available cookbooks with [] and a symbol" do
+        @cookbook_loader[:openldap].name.should eql(:openldap)
+      end
+
+      it "should allow you to look up available cookbooks with [] and a string" do
+        @cookbook_loader["openldap"].name.should eql(:openldap)
+      end
+    end
+
+    describe "each" do
+      it "should allow you to iterate over cookbooks with each" do
+        seen = Hash.new
+        @cookbook_loader.each do |cookbook_name, cookbook|
+          seen[cookbook_name] = true
+        end
+        seen.should have_key("openldap")
+        seen.should have_key("apache2")
+      end
+
+      it "should iterate in alphabetical order" do
+        seen = Array.new
+        @cookbook_loader.each do |cookbook_name, cookbook|
+          seen << cookbook_name
+          end
+        seen[0].should == "angrybash"
+        seen[1].should == "apache2"
+        seen[2].should == "borken"
+        seen[3].should == "ignorken"
+        seen[4].should == "java"
+        seen[5].should == "openldap"
+      end
+    end
+
+    describe "load_cookbooks" do
+      it "should find all the cookbooks in the cookbook path" do
+        Chef::Config.cookbook_path << File.expand_path(File.join(CHEF_SPEC_DATA, "hidden-cookbooks"))
+        @cookbook_loader.load_cookbooks
+        @cookbook_loader.should have_key(:openldap)
+        @cookbook_loader.should have_key(:apache2)
+      end
+
+      it "should allow you to override an attribute file via cookbook_path" do
+        @cookbook_loader[:openldap].attribute_filenames.detect { |f|
+          f =~ /cookbooks\/openldap\/attributes\/default.rb/
+        }.should_not eql(nil)
+        @cookbook_loader[:openldap].attribute_filenames.detect { |f|
+          f =~ /kitchen\/openldap\/attributes\/default.rb/
+        }.should eql(nil)
+      end
+
+      it "should load different attribute files from deeper paths" do
+        @cookbook_loader[:openldap].attribute_filenames.detect { |f|
+          f =~ /kitchen\/openldap\/attributes\/robinson.rb/
+        }.should_not eql(nil)
+      end
+
+      it "should allow you to override a definition file via cookbook_path" do
+        @cookbook_loader[:openldap].definition_filenames.detect { |f|
+          f =~ /cookbooks\/openldap\/definitions\/client.rb/
+        }.should_not eql(nil)
+        @cookbook_loader[:openldap].definition_filenames.detect { |f|
+          f =~ /kitchen\/openldap\/definitions\/client.rb/
+        }.should eql(nil)
+      end
+
+      it "should load definition files from deeper paths" do
+        @cookbook_loader[:openldap].definition_filenames.detect { |f|
+          f =~ /kitchen\/openldap\/definitions\/drewbarrymore.rb/
+        }.should_not eql(nil)
+      end
+
+      it "should allow you to override a recipe file via cookbook_path" do
+        @cookbook_loader[:openldap].recipe_filenames.detect { |f|
+          f =~ /cookbooks\/openldap\/recipes\/gigantor.rb/
+        }.should_not eql(nil)
+        @cookbook_loader[:openldap].recipe_filenames.detect { |f|
+          f =~ /kitchen\/openldap\/recipes\/gigantor.rb/
+        }.should eql(nil)
+      end
+
+      it "should load recipe files from deeper paths" do
+        @cookbook_loader[:openldap].recipe_filenames.detect { |f|
+          f =~ /kitchen\/openldap\/recipes\/woot.rb/
+        }.should_not eql(nil)
+      end
+
+      it "should allow you to have an 'ignore' file, which skips loading files in later cookbooks" do
+        @cookbook_loader[:openldap].recipe_filenames.detect { |f|
+          f =~ /kitchen\/openldap\/recipes\/ignoreme.rb/
+        }.should eql(nil)
+      end
+
+      it "should find files that start with a ." do
+        @cookbook_loader[:openldap].file_filenames.detect { |f|
+          f =~ /\.dotfile$/
+        }.should =~ /\.dotfile$/
+        @cookbook_loader[:openldap].file_filenames.detect { |f|
+          f =~ /\.ssh\/id_rsa$/
+        }.should =~ /\.ssh\/id_rsa$/
+      end
+
+      it "should load the metadata for the cookbook" do
+        @cookbook_loader.metadata[:openldap].name.to_s.should == "openldap"
+        @cookbook_loader.metadata[:openldap].should be_a_kind_of(Chef::Cookbook::Metadata)
+      end
+
+      it "should check each cookbook directory only once (CHEF-3487)" do
+        cookbooks = []
+        @repo_paths.each do |repo_path|
+          cookbooks |= Dir[File.join(repo_path, "*")]
+        end
+        cookbooks.each do |cookbook|
+            File.should_receive(:directory?).with(cookbook).once;
+        end
+        @cookbook_loader.load_cookbooks
+      end
+    end # load_cookbooks
+
+  end # loading all cookbooks
+
+  describe "loading only one cookbook" do
+    before(:each) do
+      @cookbook_loader = Chef::CookbookLoader.new(@repo_paths)
+      @cookbook_loader.load_cookbook("openldap")
+    end
+
+    it "should have loaded the correct cookbook" do
+      seen = Hash.new
+      @cookbook_loader.each do |cookbook_name, cookbook|
+        seen[cookbook_name] = true
+      end
+      seen.should have_key("openldap")
+    end
+
+    it "should not duplicate keys when serialized to JSON" do
+      # Chef JSON serialization will generate duplicate keys if given
+      # a Hash containing matching string and symbol keys. See CHEF-4571.
+      aa = @cookbook_loader["openldap"]
+      aa.to_hash["metadata"].recipes.keys.should_not include(:openldap)
+      aa.to_hash["metadata"].recipes.keys.should include("openldap")
+      expected_desc = "Main Open LDAP configuration"
+      aa.to_hash["metadata"].recipes["openldap"].should == expected_desc
+      raw = aa.to_hash["metadata"].recipes.to_json
+      search_str = "\"openldap\":\""
+      key_idx = raw.index(search_str)
+      key_idx.should be > 0
+      dup_idx = raw[(key_idx + 1)..-1].index(search_str)
+      dup_idx.should be_nil
+    end
+
+    it "should not load the cookbook again when accessed" do
+      @cookbook_loader.should_not_receive('load_cookbook')
+      @cookbook_loader["openldap"]
+    end
+
+    it "should not load the other cookbooks" do
+      seen = Hash.new
+      @cookbook_loader.each do |cookbook_name, cookbook|
+        seen[cookbook_name] = true
+      end
+      seen.should_not have_key("apache2")
+    end
+
+    it "should load another cookbook lazily with []" do
+      @cookbook_loader["apache2"].should be_a_kind_of(Chef::CookbookVersion)
+    end
+
+    describe "loading all cookbooks after loading only one cookbook" do
+      before(:each) do
+        @cookbook_loader.load_cookbooks
+      end
+
+      it "should load all cookbooks" do
+        seen = Hash.new
+        @cookbook_loader.each do |cookbook_name, cookbook|
+          seen[cookbook_name] = true
+        end
+        seen.should have_key("openldap")
+        seen.should have_key("apache2")
+      end
+    end
+  end # loading only one cookbook
+end
diff --git a/spec/unit/cookbook_manifest_spec.rb b/spec/unit/cookbook_manifest_spec.rb
new file mode 100644
index 0000000..e87b8e1
--- /dev/null
+++ b/spec/unit/cookbook_manifest_spec.rb
@@ -0,0 +1,554 @@
+#
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe "Chef::CookbookVersion manifest" do
+  before(:each) do
+    @cookbook = Chef::CookbookVersion.new "test-cookbook"
+    @cookbook.manifest = {
+      "files" =>
+      [
+       # afile.rb
+       {
+         :name => "afile.rb",
+         :path => "files/host-examplehost.example.org/afile.rb",
+         :checksum => "csum-host",
+         :specificity => "host-examplehost.example.org"
+       },
+       {
+         :name => "afile.rb",
+         :path => "files/ubuntu-9.10/afile.rb",
+         :checksum => "csum-platver-full",
+         :specificity => "ubuntu-9.10"
+       },
+       {
+         :name => "afile.rb",
+         :path => "files/newubuntu-9/afile.rb",
+         :checksum => "csum-platver-partial",
+         :specificity => "newubuntu-9"
+       },
+       {
+         :name => "afile.rb",
+         :path => "files/ubuntu/afile.rb",
+         :checksum => "csum-plat",
+         :specificity => "ubuntu"
+       },
+       {
+         :name => "afile.rb",
+         :path => "files/default/afile.rb",
+         :checksum => "csum-default",
+         :specificity => "default"
+       },
+
+       # for different/odd platform_versions
+       {
+         :name => "bfile.rb",
+         :path => "files/fakeos-2.0.rc.1/bfile.rb",
+         :checksum => "csum2-platver-full",
+         :specificity => "fakeos-2.0.rc.1"
+       },
+       {
+         :name => "bfile.rb",
+         :path => "files/newfakeos-2.0.rc/bfile.rb",
+         :checksum => "csum2-platver-partial",
+         :specificity => "newfakeos-2.0.rc"
+       },
+       {
+         :name => "bfile.rb",
+         :path => "files/fakeos-maple tree/bfile.rb",
+         :checksum => "csum3-platver-full",
+         :specificity => "maple tree"
+       },
+       {
+         :name => "bfile.rb",
+         :path => "files/fakeos-1/bfile.rb",
+         :checksum => "csum4-platver-full",
+         :specificity => "fakeos-1"
+       },
+
+       # directory adirectory
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/host-examplehost.example.org/adirectory/anotherfile1.rb.host",
+         :checksum => "csum-host-1",
+         :specificity => "host-examplehost.example.org"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/host-examplehost.example.org/adirectory/anotherfile2.rb.host",
+         :checksum => "csum-host-2",
+         :specificity => "host-examplehost.example.org"
+       },
+
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/ubuntu-9.10/adirectory/anotherfile1.rb.platform-full-version",
+         :checksum => "csum-platver-full-1",
+         :specificity => "ubuntu-9.10"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/ubuntu-9.10/adirectory/anotherfile2.rb.platform-full-version",
+         :checksum => "csum-platver-full-2",
+         :specificity => "ubuntu-9.10"
+       },
+
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/newubuntu-9/adirectory/anotherfile1.rb.platform-partial-version",
+         :checksum => "csum-platver-partial-1",
+         :specificity => "newubuntu-9"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/newubuntu-9/adirectory/anotherfile2.rb.platform-partial-version",
+         :checksum => "csum-platver-partial-2",
+         :specificity => "nweubuntu-9"
+       },
+
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/ubuntu/adirectory/anotherfile1.rb.platform",
+         :checksum => "csum-plat-1",
+         :specificity => "ubuntu"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/ubuntu/adirectory/anotherfile2.rb.platform",
+         :checksum => "csum-plat-2",
+         :specificity => "ubuntu"
+       },
+
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/default/adirectory/anotherfile1.rb.default",
+         :checksum => "csum-default-1",
+         :specificity => "default"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/default/adirectory/anotherfile2.rb.default",
+         :checksum => "csum-default-2",
+         :specificity => "default"
+       },
+       # for different/odd platform_versions
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-full-version",
+         :checksum => "csum2-platver-full-1",
+         :specificity => "fakeos-2.0.rc.1"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/fakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-full-version",
+         :checksum => "csum2-platver-full-2",
+         :specificity => "fakeos-2.0.rc.1"
+       },
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile1.rb.platform-partial-version",
+         :checksum => "csum2-platver-partial-1",
+         :specificity => "newfakeos-2.0.rc"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/newfakeos-2.0.rc.1/adirectory/anotherfile2.rb.platform-partial-version",
+         :checksum => "csum2-platver-partial-2",
+         :specificity => "newfakeos-2.0.rc"
+       },
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/fakeos-maple tree/adirectory/anotherfile1.rb.platform-full-version",
+         :checksum => "csum3-platver-full-1",
+         :specificity => "fakeos-maple tree"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/fakeos-maple tree/adirectory/anotherfile2.rb.platform-full-version",
+         :checksum => "csum3-platver-full-2",
+         :specificity => "fakeos-maple tree"
+       },
+       {
+         :name => "anotherfile1.rb",
+         :path => "files/fakeos-1/adirectory/anotherfile1.rb.platform-full-version",
+         :checksum => "csum4-platver-full-1",
+         :specificity => "fakeos-1"
+       },
+       {
+         :name => "anotherfile2.rb",
+         :path => "files/fakeos-1/adirectory/anotherfile2.rb.platform-full-version",
+         :checksum => "csum4-platver-full-2",
+         :specificity => "fakeos-1"
+       },
+      ]
+    }
+
+  end
+
+
+  it "should return a manifest record based on priority preference: host" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "ubuntu"
+    node.automatic_attrs[:platform_version] = "9.10"
+    node.automatic_attrs[:fqdn] = "examplehost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum-host"
+  end
+
+  it "should return a manifest record based on priority preference: platform & full version" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "ubuntu"
+    node.automatic_attrs[:platform_version] = "9.10"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum-platver-full"
+  end
+
+  it "should return a manifest record based on priority preference: platform & partial version" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "newubuntu"
+    node.automatic_attrs[:platform_version] = "9.10"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum-platver-partial"
+  end
+
+  it "should return a manifest record based on priority preference: platform only" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "ubuntu"
+    node.automatic_attrs[:platform_version] = "1.0"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum-plat"
+  end
+
+  it "should return a manifest record based on priority preference: default" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "notubuntu"
+    node.automatic_attrs[:platform_version] = "1.0"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "afile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum-default"
+  end
+
+  it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "fakeos"
+    node.automatic_attrs[:platform_version] = "2.0.rc.1"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum2-platver-full"
+  end
+
+  it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "newfakeos"
+    node.automatic_attrs[:platform_version] = "2.0.rc.1"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum2-platver-partial"
+  end
+
+  it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "fakeos"
+    node.automatic_attrs[:platform_version] = "maple tree"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum3-platver-full"
+  end
+
+  it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "fakeos"
+    node.automatic_attrs[:platform_version] = "1"
+    node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+    manifest_record = @cookbook.preferred_manifest_record(node, :files, "bfile.rb")
+    manifest_record.should_not be_nil
+    manifest_record[:checksum].should == "csum4-platver-full"
+  end
+
+  describe "when fetching the contents of a directory by file specificity" do
+
+    it "should return a directory of manifest records based on priority preference: host" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "ubuntu"
+      node.automatic_attrs[:platform_version] = "9.10"
+      node.automatic_attrs[:fqdn] = "examplehost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum-host-1", "csum-host-2"]
+    end
+
+    it "should return a directory of manifest records based on priority preference: platform & full version" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "ubuntu"
+      node.automatic_attrs[:platform_version] = "9.10"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum-platver-full-1", "csum-platver-full-2"]
+    end
+
+    it "should return a directory of manifest records based on priority preference: platform & partial version" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "newubuntu"
+      node.automatic_attrs[:platform_version] = "9.10"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum-platver-partial-1", "csum-platver-partial-2"]
+    end
+
+    it "should return a directory of manifest records based on priority preference: platform only" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "ubuntu"
+      node.automatic_attrs[:platform_version] = "1.0"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum-plat-1", "csum-plat-2"]
+    end
+
+    it "should return a directory of manifest records based on priority preference: default" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "notubuntu"
+      node.automatic_attrs[:platform_version] = "1.0"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum-default-1", "csum-default-2"]
+    end
+
+    it "should return a manifest record based on priority preference: platform & full version - platform_version variant 1" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "fakeos"
+      node.automatic_attrs[:platform_version] = "2.0.rc.1"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum2-platver-full-1", "csum2-platver-full-2"]
+    end
+
+    it "should return a manifest record based on priority preference: platform & partial version - platform_version variant 1" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "newfakeos"
+      node.automatic_attrs[:platform_version] = "2.0.rc.1"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum2-platver-partial-1", "csum2-platver-partial-2"]
+    end
+
+    it "should return a manifest record based on priority preference: platform & full version - platform_version variant 2" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "fakeos"
+      node.automatic_attrs[:platform_version] = "maple tree"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum3-platver-full-1", "csum3-platver-full-2"]
+    end
+
+    it "should return a manifest record based on priority preference: platform & full version - platform_version variant 3" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "fakeos"
+      node.automatic_attrs[:platform_version] = "1"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      manifest_records = @cookbook.preferred_manifest_records_for_directory(node, :files, "adirectory")
+      manifest_records.should_not be_nil
+      manifest_records.size.should == 2
+
+      checksums = manifest_records.map{ |manifest_record| manifest_record[:checksum] }
+      checksums.sort.should == ["csum4-platver-full-1", "csum4-platver-full-2"]
+    end
+  end
+
+  ## Globbing the relative paths out of the manifest records ##
+
+  describe "when globbing for relative file paths based on filespecificity" do
+    it "should return a list of relative paths based on priority preference: host" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "ubuntu"
+      node.automatic_attrs[:platform_version] = "9.10"
+      node.automatic_attrs[:fqdn] = "examplehost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.host', 'anotherfile2.rb.host']
+    end
+
+    it "should return a list of relative paths based on priority preference: platform & full version" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "ubuntu"
+      node.automatic_attrs[:platform_version] = "9.10"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']
+    end
+
+    it "should return a list of relative paths based on priority preference: platform & partial version" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "newubuntu"
+      node.automatic_attrs[:platform_version] = "9.10"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.platform-partial-version', 'anotherfile2.rb.platform-partial-version']
+    end
+
+    it "should return a list of relative paths based on priority preference: platform only" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "ubuntu"
+      node.automatic_attrs[:platform_version] = "1.0"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.platform', 'anotherfile2.rb.platform']
+    end
+
+    it "should return a list of relative paths based on priority preference: default" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "notubuntu"
+      node.automatic_attrs[:platform_version] = "1.0"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.default', 'anotherfile2.rb.default']
+    end
+
+    it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 1" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "fakeos"
+      node.automatic_attrs[:platform_version] = "2.0.rc.1"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']
+    end
+
+    it "should return a list of relative paths based on priority preference: platform & partial version - platform_version variant 1" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "newfakeos"
+      node.automatic_attrs[:platform_version] = "2.0.rc.1"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.platform-partial-version', 'anotherfile2.rb.platform-partial-version']
+    end
+
+    it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 2" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "fakeos"
+      node.automatic_attrs[:platform_version] = "maple tree"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']
+    end
+
+    it "should return a list of relative paths based on priority preference: platform & full version - platform_version variant 3" do
+      node = Chef::Node.new
+      node.automatic_attrs[:platform] = "fakeos"
+      node.automatic_attrs[:platform_version] = "1"
+      node.automatic_attrs[:fqdn] = "differenthost.example.org"
+
+      filenames = @cookbook.relative_filenames_in_preferred_directory(node, :files, "adirectory")
+      filenames.should_not be_nil
+      filenames.size.should == 2
+
+      filenames.sort.should == ['anotherfile1.rb.platform-full-version', 'anotherfile2.rb.platform-full-version']
+    end
+  end
+end
diff --git a/spec/unit/cookbook_site_streaming_uploader.rb b/spec/unit/cookbook_site_streaming_uploader.rb
new file mode 100644
index 0000000..73a5134
--- /dev/null
+++ b/spec/unit/cookbook_site_streaming_uploader.rb
@@ -0,0 +1,200 @@
+#
+# Author:: Xabier de Zuazo (xabier at onddo.com)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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/cookbook_site_streaming_uploader'
+
+class FakeTempfile
+  def initialize(basename)
+    @basename = basename
+  end
+
+  def close
+  end
+
+  def path
+    "#{@basename}.ZZZ"
+  end
+
+end
+
+describe Chef::CookbookSiteStreamingUploader do
+
+  describe "create_build_dir" do
+
+    before(:each) do
+      @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, 'cookbooks'))
+      @loader = Chef::CookbookLoader.new(@cookbook_repo)
+      @loader.load_cookbooks
+      File.stub(:unlink).and_return()
+    end
+
+    it "should create the cookbook tmp dir" do
+      cookbook = @loader[:openldap]
+      files_count = Dir.glob(File.join(@cookbook_repo, cookbook.name.to_s, '**', '*'), File::FNM_DOTMATCH).count { |file| File.file?(file) }
+
+      Tempfile.should_receive(:new).with("chef-#{cookbook.name}-build").and_return(FakeTempfile.new("chef-#{cookbook.name}-build"))
+      FileUtils.should_receive(:mkdir_p).exactly(files_count + 1).times
+      FileUtils.should_receive(:cp).exactly(files_count).times
+      Chef::CookbookSiteStreamingUploader.create_build_dir(cookbook)
+    end
+
+  end # create_build_dir
+
+  describe "make_request" do
+
+    before(:each) do
+      @uri = "http://cookbooks.dummy.com/api/v1/cookbooks"
+      @secret_filename = File.join(CHEF_SPEC_DATA, 'ssl/private_key.pem')
+      @rsa_key = File.read(@secret_filename)
+      response = Net::HTTPResponse.new('1.0', '200', 'OK')
+      Net::HTTP.any_instance.stub(:request).and_return(response)
+    end
+
+    it "should send an http request" do
+      Net::HTTP.any_instance.should_receive(:request)
+      Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
+    end
+
+    it "should read the private key file" do
+      File.should_receive(:read).with(@secret_filename).and_return(@rsa_key)
+      Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
+    end
+
+    it "should add the authentication signed header" do
+      Mixlib::Authentication::SigningObject.any_instance.should_receive(:sign).and_return({})
+      Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
+    end
+
+    it "should be able to send post requests" do
+      post = Net::HTTP::Post.new(@uri, {})
+
+      Net::HTTP::Post.should_receive(:new).once.and_return(post)
+      Net::HTTP::Put.should_not_receive(:new)
+      Net::HTTP::Get.should_not_receive(:new)
+      Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
+    end
+
+    it "should be able to send put requests" do
+      put = Net::HTTP::Put.new(@uri, {})
+
+      Net::HTTP::Post.should_not_receive(:new)
+      Net::HTTP::Put.should_receive(:new).once.and_return(put)
+      Net::HTTP::Get.should_not_receive(:new)
+      Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, 'bill', @secret_filename)
+    end
+
+    it "should be able to receive files to attach as argument" do
+      Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, 'bill', @secret_filename, {
+        :myfile => File.new(File.join(CHEF_SPEC_DATA, 'config.rb')), # a dummy file
+      })
+    end
+
+    it "should be able to receive strings to attach as argument" do
+      Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, 'bill', @secret_filename, {
+        :mystring => 'Lorem ipsum',
+      })
+    end
+
+    it "should be able to receive strings and files as argument at the same time" do
+      Chef::CookbookSiteStreamingUploader.make_request(:put, @uri, 'bill', @secret_filename, {
+        :myfile1 => File.new(File.join(CHEF_SPEC_DATA, 'config.rb')),
+        :mystring1 => 'Lorem ipsum',
+        :myfile2 => File.new(File.join(CHEF_SPEC_DATA, 'config.rb')),
+        :mystring2 => 'Dummy text',
+      })
+    end
+
+  end # make_request
+
+  describe "StreamPart" do
+    before(:each) do
+      @file = File.new(File.join(CHEF_SPEC_DATA, 'config.rb'))
+      @stream_part = Chef::CookbookSiteStreamingUploader::StreamPart.new(@file, File.size(@file))
+    end
+
+    it "should create a StreamPart" do
+      @stream_part.should be_instance_of(Chef::CookbookSiteStreamingUploader::StreamPart)
+    end
+
+    it "should expose its size" do
+      @stream_part.size.should eql(File.size(@file))
+    end
+
+    it "should read with offset and how_much" do
+      content = @file.read(4)
+      @file.rewind
+      @stream_part.read(0, 4).should eql(content)
+    end
+
+  end # StreamPart
+
+  describe "StringPart" do
+    before(:each) do
+      @str = 'What a boring string'
+      @string_part = Chef::CookbookSiteStreamingUploader::StringPart.new(@str)
+    end
+
+    it "should create a StringPart" do
+      @string_part.should be_instance_of(Chef::CookbookSiteStreamingUploader::StringPart)
+    end
+
+    it "should expose its size" do
+      @string_part.size.should eql(@str.size)
+    end
+
+    it "should read with offset and how_much" do
+      @string_part.read(2, 4).should eql(@str[2, 4])
+    end
+
+  end # StringPart
+
+  describe "MultipartStream" do
+    before(:each) do
+      @string1 = "stream1"
+      @string2 = "stream2"
+      @stream1 = Chef::CookbookSiteStreamingUploader::StringPart.new(@string1)
+      @stream2 = Chef::CookbookSiteStreamingUploader::StringPart.new(@string2)
+      @parts = [ @stream1, @stream2 ]
+
+      @multipart_stream = Chef::CookbookSiteStreamingUploader::MultipartStream.new(@parts)
+    end
+
+    it "should create a MultipartStream" do
+      @multipart_stream.should be_instance_of(Chef::CookbookSiteStreamingUploader::MultipartStream)
+    end
+
+    it "should expose its size" do
+      @multipart_stream.size.should eql(@stream1.size + @stream2.size)
+    end
+
+    it "should read with how_much" do
+      @multipart_stream.read(10).should eql("#{@string1}#{@string2}"[0, 10])
+    end
+
+    it "should read receiving destination buffer as second argument (CHEF-4456: Ruby 2 compat)" do
+      dst_buf = ''
+      @multipart_stream.read(10, dst_buf)
+      dst_buf.should eql("#{@string1}#{@string2}"[0, 10])
+    end
+
+  end # MultipartStream
+
+end
+
diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb
new file mode 100644
index 0000000..c28a5c7
--- /dev/null
+++ b/spec/unit/cookbook_spec.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::CookbookVersion do
+#  COOKBOOK_PATH = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap"))
+  before(:each) do
+    @cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks"))
+    cl = Chef::CookbookLoader.new(@cookbook_repo)
+    cl.load_cookbooks
+    @cookbook_collection = Chef::CookbookCollection.new(cl)
+    @cookbook = @cookbook_collection[:openldap]
+    @node = Chef::Node.new
+    @node.name "JuliaChild"
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+  end
+
+  it "should have a name" do
+    @cookbook.name.should == :openldap
+  end
+
+  it "should allow you to set the list of attribute files and create the mapping from short names to paths" do
+    @cookbook.attribute_filenames = [ "attributes/one.rb", "attributes/two.rb" ]
+    @cookbook.attribute_filenames.should == [ "attributes/one.rb", "attributes/two.rb" ]
+    @cookbook.attribute_filenames_by_short_filename.keys.sort.should eql(["one", "two"])
+    @cookbook.attribute_filenames_by_short_filename["one"].should == "attributes/one.rb"
+    @cookbook.attribute_filenames_by_short_filename["two"].should == "attributes/two.rb"
+  end
+
+  it "should allow you to set the list of recipe files and create the mapping of recipe short name to filename" do
+    @cookbook.recipe_filenames = [ "recipes/one.rb", "recipes/two.rb" ]
+    @cookbook.recipe_filenames.should == [ "recipes/one.rb", "recipes/two.rb" ]
+    @cookbook.recipe_filenames_by_name.keys.sort.should eql(["one", "two"])
+    @cookbook.recipe_filenames_by_name["one"].should == "recipes/one.rb"
+    @cookbook.recipe_filenames_by_name["two"].should == "recipes/two.rb"
+  end
+
+  it "should generate a list of recipes by fully-qualified name" do
+    @cookbook.recipe_filenames = [ "recipes/one.rb", "/recipes/two.rb", "three.rb" ]
+    @cookbook.fully_qualified_recipe_names.include?("openldap::one").should == true
+    @cookbook.fully_qualified_recipe_names.include?("openldap::two").should == true
+    @cookbook.fully_qualified_recipe_names.include?("openldap::three").should == true
+  end
+
+  it "should find a preferred file" do
+    pending
+  end
+
+  it "should not return an unchanged preferred file" do
+    pending
+    @cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum').should be_nil
+  end
+
+  it "should allow you to include a fully-qualified recipe using the DSL" do
+    # DSL method include_recipe allows multiple arguments, so extract the first
+    recipe = @run_context.include_recipe("openldap::gigantor").first
+
+    recipe.recipe_name.should == "gigantor"
+    recipe.cookbook_name.should == :openldap
+    @run_context.resource_collection[0].name.should == "blanket"
+  end
+
+  it "should raise an ArgumentException if you try to load a bad recipe name" do
+    lambda { @cookbook.load_recipe("doesnt_exist", @node) }.should raise_error(ArgumentError)
+  end
+
+end
diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb
new file mode 100644
index 0000000..d186ef0
--- /dev/null
+++ b/spec/unit/cookbook_version_spec.rb
@@ -0,0 +1,323 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+
+describe Chef::CookbookVersion do
+  describe "when first created" do
+    before do
+      @cookbook_version = Chef::CookbookVersion.new("tatft")
+    end
+
+    it "has a name" do
+      @cookbook_version.name.should == 'tatft'
+    end
+
+    it "has no attribute files" do
+      @cookbook_version.attribute_filenames.should be_empty
+    end
+
+    it "has no resource definition files" do
+      @cookbook_version.definition_filenames.should be_empty
+    end
+
+    it "has no cookbook files" do
+      @cookbook_version.file_filenames.should be_empty
+    end
+
+    it "has no recipe files" do
+      @cookbook_version.recipe_filenames.should be_empty
+    end
+
+    it "has no library files" do
+      @cookbook_version.library_filenames.should be_empty
+    end
+
+    it "has no LWRP resource files" do
+      @cookbook_version.resource_filenames.should be_empty
+    end
+
+    it "has no LWRP provider files" do
+      @cookbook_version.provider_filenames.should be_empty
+    end
+
+    it "has no metadata files" do
+      @cookbook_version.metadata_filenames.should be_empty
+    end
+
+    it "is not frozen" do
+      @cookbook_version.should_not be_frozen_version
+    end
+
+    it "can be frozen" do
+      @cookbook_version.freeze_version
+      @cookbook_version.should be_frozen_version
+    end
+
+    it "is \"ready\"" do
+      # WTF is this? what are the valid states? and why aren't they set with encapsulating methods?
+      # [Dan 15-Jul-2010]
+      @cookbook_version.status.should == :ready
+    end
+
+    it "has empty metadata" do
+      @cookbook_version.metadata.should == Chef::Cookbook::Metadata.new
+    end
+
+    it "creates a manifest hash of its contents" do
+      expected = {"recipes"=>[],
+                  "definitions"=>[],
+                  "libraries"=>[],
+                  "attributes"=>[],
+                  "files"=>[],
+                  "templates"=>[],
+                  "resources"=>[],
+                  "providers"=>[],
+                  "root_files"=>[],
+                  "cookbook_name"=>"tatft",
+                  "metadata"=>Chef::Cookbook::Metadata.new,
+                  "version"=>"0.0.0",
+                  "name"=>"tatft-0.0.0"}
+      @cookbook_version.manifest.should == expected
+    end
+  end
+
+  describe "after the cookbook has been loaded" do
+    MD5 = /[0-9a-f]{32}/
+
+    before do
+      # Currently the cookbook loader finds all the files then tells CookbookVersion
+      # where they are.
+      @cookbook_version = Chef::CookbookVersion.new("tatft")
+
+      @cookbook = Hash.new { |hash, key| hash[key] = [] }
+
+      cookbook_root = File.join(CHEF_SPEC_DATA, 'cb_version_cookbooks', 'tatft')
+
+      # Dunno if the paths here are representitive of what is set by CookbookLoader...
+      @cookbook[:attribute_filenames]   = Dir[File.join(cookbook_root, 'attributes', '**', '*.rb')]
+      @cookbook[:definition_filenames]  = Dir[File.join(cookbook_root, 'definitions', '**', '*.rb')]
+      @cookbook[:file_filenames]        = Dir[File.join(cookbook_root, 'files', '**', '*.tgz')]
+      @cookbook[:recipe_filenames]      = Dir[File.join(cookbook_root, 'recipes', '**', '*.rb')]
+      @cookbook[:template_filenames]    = Dir[File.join(cookbook_root, 'templates', '**', '*.erb')]
+      @cookbook[:library_filenames]     = Dir[File.join(cookbook_root, 'libraries', '**', '*.rb')]
+      @cookbook[:resource_filenames]    = Dir[File.join(cookbook_root, 'resources', '**', '*.rb')]
+      @cookbook[:provider_filenames]    = Dir[File.join(cookbook_root, 'providers', '**', '*.rb')]
+      @cookbook[:root_filenames]        = Array(File.join(cookbook_root, 'README.rdoc'))
+      @cookbook[:metadata_filenames]    = Array(File.join(cookbook_root, 'metadata.json'))
+
+      @cookbook_version.attribute_filenames  = @cookbook[:attribute_filenames]
+      @cookbook_version.definition_filenames = @cookbook[:definition_filenames]
+      @cookbook_version.recipe_filenames     = @cookbook[:recipe_filenames]
+      @cookbook_version.template_filenames   = @cookbook[:template_filenames]
+      @cookbook_version.file_filenames       = @cookbook[:file_filenames]
+      @cookbook_version.library_filenames    = @cookbook[:library_filenames]
+      @cookbook_version.resource_filenames   = @cookbook[:resource_filenames]
+      @cookbook_version.provider_filenames   = @cookbook[:provider_filenames]
+      @cookbook_version.root_filenames       = @cookbook[:root_filenames]
+      @cookbook_version.metadata_filenames   = @cookbook[:metadata_filenames]
+
+      # Used to test file-specificity related file lookups
+      @node = Chef::Node.new
+      @node.set[:platform] = "ubuntu"
+      @node.set[:platform_version] = "13.04"
+      @node.name("testing")
+    end
+
+    it "generates a manifest containing the cookbook's files" do
+      manifest = @cookbook_version.manifest
+
+      manifest["metadata"].should == Chef::Cookbook::Metadata.new
+      manifest["cookbook_name"].should == "tatft"
+
+      manifest["recipes"].should have(1).recipe_file
+
+      recipe = manifest["recipes"].first
+      recipe["name"].should == "default.rb"
+      recipe["path"].should == "recipes/default.rb"
+      recipe["checksum"].should match(MD5)
+      recipe["specificity"].should == "default"
+
+      manifest["definitions"].should have(1).definition_file
+
+      definition = manifest["definitions"].first
+      definition["name"].should == "runit_service.rb"
+      definition["path"].should == "definitions/runit_service.rb"
+      definition["checksum"].should match(MD5)
+      definition["specificity"].should == "default"
+
+      manifest["libraries"].should have(1).library_file
+
+      library = manifest["libraries"].first
+      library["name"].should == "ownage.rb"
+      library["path"].should == "libraries/ownage.rb"
+      library["checksum"].should match(MD5)
+      library["specificity"].should == "default"
+
+      manifest["attributes"].should have(1).attribute_file
+
+      attribute_file = manifest["attributes"].first
+      attribute_file["name"].should == "default.rb"
+      attribute_file["path"].should == "attributes/default.rb"
+      attribute_file["checksum"].should match(MD5)
+      attribute_file["specificity"].should == "default"
+
+      manifest["files"].should have(1).cookbook_file
+
+      cookbook_file = manifest["files"].first
+      cookbook_file["name"].should == "giant_blob.tgz"
+      cookbook_file["path"].should == "files/default/giant_blob.tgz"
+      cookbook_file["checksum"].should match(MD5)
+      cookbook_file["specificity"].should == "default"
+
+      manifest["templates"].should have(1).template
+
+      template = manifest["templates"].first
+      template["name"].should == "configuration.erb"
+      template["path"].should == "templates/default/configuration.erb"
+      template["checksum"].should match(MD5)
+      template["specificity"].should == "default"
+
+      manifest["resources"].should have(1).lwr
+
+      lwr = manifest["resources"].first
+      lwr["name"].should == "lwr.rb"
+      lwr["path"].should == "resources/lwr.rb"
+      lwr["checksum"].should match(MD5)
+      lwr["specificity"].should == "default"
+
+      manifest["providers"].should have(1).lwp
+
+      lwp = manifest["providers"].first
+      lwp["name"].should == "lwp.rb"
+      lwp["path"].should == "providers/lwp.rb"
+      lwp["checksum"].should match(MD5)
+      lwp["specificity"].should == "default"
+
+      manifest["root_files"].should have(1).file_in_the_cookbook_root
+
+      readme = manifest["root_files"].first
+      readme["name"].should == "README.rdoc"
+      readme["path"].should == "README.rdoc"
+      readme["checksum"].should match(MD5)
+      readme["specificity"].should == "default"
+    end
+
+    it "determines whether a template is available for a given node" do
+      @cookbook_version.should have_template_for_node(@node, "configuration.erb")
+      @cookbook_version.should_not have_template_for_node(@node, "missing.erb")
+    end
+
+    it "determines whether a cookbook_file is available for a given node" do
+      @cookbook_version.should have_cookbook_file_for_node(@node, "giant_blob.tgz")
+      @cookbook_version.should_not have_cookbook_file_for_node(@node, "missing.txt")
+    end
+
+    describe "raises an error when attempting to load a missing cookbook_file and" do
+      before do
+        node = Chef::Node.new.tap do |n|
+          n.name("sample.node")
+          n.automatic_attrs[:fqdn] = "sample.example.com"
+          n.automatic_attrs[:platform] = "ubuntu"
+          n.automatic_attrs[:platform_version] = "10.04"
+        end
+        @attempt_to_load_file = lambda { @cookbook_version.preferred_manifest_record(node, :files, "no-such-thing.txt") }
+      end
+
+      it "describes the cookbook and version" do
+        useful_explanation = Regexp.new(Regexp.escape("Cookbook 'tatft' (0.0.0) does not contain"))
+        @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation)
+      end
+
+      it "lists suggested places to look" do
+        useful_explanation = Regexp.new(Regexp.escape("files/default/no-such-thing.txt"))
+        @attempt_to_load_file.should raise_error(Chef::Exceptions::FileNotFound, useful_explanation)
+      end
+    end
+
+  end
+
+
+  describe "<=>" do
+
+    it "should sort based on the version number" do
+      examples = [
+                  # smaller, larger
+                  ["1.0", "2.0"],
+                  ["1.2.3", "1.2.4"],
+                  ["1.2.3", "1.3.0"],
+                  ["1.2.3", "1.3"],
+                  ["1.2.3", "2.1.1"],
+                  ["1.2.3", "2.1"],
+                  ["1.2", "1.2.4"],
+                  ["1.2", "1.3.0"],
+                  ["1.2", "1.3"],
+                  ["1.2", "2.1.1"],
+                  ["1.2", "2.1"]
+                 ]
+      examples.each do |smaller, larger|
+        sm = Chef::CookbookVersion.new("foo")
+        lg = Chef::CookbookVersion.new("foo")
+        sm.version = smaller
+        lg.version = larger
+        sm.should be < lg
+        lg.should be > sm
+        sm.should_not == lg
+      end
+    end
+
+    it "should equate versions 1.2 and 1.2.0" do
+      a = Chef::CookbookVersion.new("foo")
+      b = Chef::CookbookVersion.new("foo")
+      a.version = "1.2"
+      b.version = "1.2.0"
+      a.should == b
+    end
+
+
+    it "should not allow you to sort cookbooks with different names" do
+      apt = Chef::CookbookVersion.new "apt"
+      apt.version = "1.0"
+      god = Chef::CookbookVersion.new "god"
+      god.version = "2.0"
+      lambda {apt <=> god}.should raise_error(Chef::Exceptions::CookbookVersionNameMismatch)
+    end
+  end
+
+  describe "when you set a version" do
+    before do
+      @cbv = Chef::CookbookVersion.new("version validation")
+    end
+    it "should accept valid cookbook versions" do
+      good_versions = %w(1.2 1.2.3 1000.80.50000 0.300.25)
+      good_versions.each do |v|
+        @cbv.version = v
+      end
+    end
+
+    it "should raise InvalidVersion for bad cookbook versions" do
+      bad_versions = ["1.2.3.4", "1.2.a4", "1", "a", "1.2 3", "1.2 a",
+                      "1 2 3", "1-2-3", "1_2_3", "1.2_3", "1.2-3"]
+      the_error = Chef::Exceptions::InvalidCookbookVersion
+      bad_versions.each do |v|
+        lambda {@cbv.version = v}.should raise_error(the_error)
+      end
+    end
+
+  end
+
+end
diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb
new file mode 100644
index 0000000..8d66e08
--- /dev/null
+++ b/spec/unit/daemon_spec.rb
@@ -0,0 +1,175 @@
+#
+# Author:: AJ Christensen (<aj at junglist.gen.nz>)
+# 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 'ostruct'
+
+describe Chef::Daemon do
+  before do
+    if windows?
+      mock_struct = #Struct::Passwd.new(nil, nil, 111, 111)
+      mock_struct = OpenStruct.new(:uid => 2342, :gid => 2342)
+      Etc.stub!(:getpwnam).and_return mock_struct
+      Etc.stub!(:getgrnam).and_return mock_struct
+      # mock unimplemented methods
+      Process.stub!(:initgroups).and_return nil
+      Process::GID.stub!(:change_privilege).and_return 11
+      Process::UID.stub!(:change_privilege).and_return 11
+    end
+  end
+
+  describe ".pid_file" do
+
+    describe "when the pid_file option has been set" do
+
+      before do
+        Chef::Config[:pid_file] = "/var/run/chef/chef-client.pid"
+      end
+
+      it "should return the supplied value" do
+        Chef::Daemon.pid_file.should eql("/var/run/chef/chef-client.pid")
+      end
+    end
+
+    describe "without the pid_file option set" do
+
+      before do
+        Chef::Daemon.name = "chef-client"
+      end
+
+      it "should return a valued based on @name" do
+        Chef::Daemon.pid_file.should eql("/tmp/chef-client.pid")
+      end
+
+    end
+  end
+
+  describe ".pid_from_file" do
+
+    before do
+      Chef::Config[:pid_file] = "/var/run/chef/chef-client.pid"
+    end
+
+    it "should suck the pid out of pid_file" do
+      File.should_receive(:read).with("/var/run/chef/chef-client.pid").and_return("1337")
+      Chef::Daemon.pid_from_file
+    end
+  end
+
+  describe ".change_privilege" do
+
+    before do
+      Chef::Application.stub!(:fatal!).and_return(true)
+      Chef::Config[:user] = 'aj'
+      Dir.stub!(:chdir)
+    end
+
+    it "changes the working directory to root" do
+      Dir.rspec_reset
+      Dir.should_receive(:chdir).with("/").and_return(0)
+      Chef::Daemon.change_privilege
+    end
+
+    describe "when the user and group options are supplied" do
+
+      before do
+        Chef::Config[:group] = 'staff'
+      end
+
+      it "should log an appropriate info message" do
+        Chef::Log.should_receive(:info).with("About to change privilege to aj:staff")
+        Chef::Daemon.change_privilege
+      end
+
+      it "should call _change_privilege with the user and group" do
+        Chef::Daemon.should_receive(:_change_privilege).with("aj", "staff")
+        Chef::Daemon.change_privilege
+      end
+    end
+
+    describe "when just the user option is supplied" do
+      it "should log an appropriate info message" do
+        Chef::Log.should_receive(:info).with("About to change privilege to aj")
+        Chef::Daemon.change_privilege
+      end
+
+      it "should call _change_privilege with just the user" do
+        Chef::Daemon.should_receive(:_change_privilege).with("aj")
+        Chef::Daemon.change_privilege
+      end
+    end
+  end
+
+  describe "._change_privilege" do
+
+    before do
+      Process.stub!(:euid).and_return(0)
+      Process.stub!(:egid).and_return(0)
+
+      Process::UID.stub!(:change_privilege).and_return(nil)
+      Process::GID.stub!(:change_privilege).and_return(nil)
+
+      @pw_user = mock("Struct::Passwd", :uid => 501)
+      @pw_group = mock("Struct::Group", :gid => 20)
+
+      Process.stub!(:initgroups).and_return(true)
+
+      Etc.stub!(:getpwnam).and_return(@pw_user)
+      Etc.stub!(:getgrnam).and_return(@pw_group)
+    end
+
+    describe "with sufficient privileges" do
+      before do
+        Process.stub!(:euid).and_return(0)
+        Process.stub!(:egid).and_return(0)
+      end
+
+      it "should initialize the supplemental group list" do
+        Process.should_receive(:initgroups).with("aj", 20)
+        Chef::Daemon._change_privilege("aj")
+      end
+
+      it "should attempt to change the process GID" do
+        Process::GID.should_receive(:change_privilege).with(20).and_return(20)
+        Chef::Daemon._change_privilege("aj")
+      end
+
+      it "should attempt to change the process UID" do
+        Process::UID.should_receive(:change_privilege).with(501).and_return(501)
+        Chef::Daemon._change_privilege("aj")
+      end
+    end
+
+    describe "with insufficient privileges" do
+      before do
+        Process.stub!(:euid).and_return(999)
+        Process.stub!(:egid).and_return(999)
+      end
+
+      it "should log an appropriate error message and fail miserably" do
+        Process.stub!(:initgroups).and_raise(Errno::EPERM)
+        error = "Operation not permitted"
+        if RUBY_PLATFORM.match("solaris2") || RUBY_PLATFORM.match("aix")
+          error = "Not owner"
+        end
+        Chef::Application.should_receive(:fatal!).with("Permission denied when trying to change 999:999 to 501:20. #{error}")
+        Chef::Daemon._change_privilege("aj")
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb
new file mode 100644
index 0000000..6bc5954
--- /dev/null
+++ b/spec/unit/data_bag_item_spec.rb
@@ -0,0 +1,280 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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/data_bag_item'
+
+describe Chef::DataBagItem do
+  before(:each) do
+    @data_bag_item = Chef::DataBagItem.new
+  end
+
+  describe "initialize" do
+    it "should be a Chef::DataBagItem" do
+      @data_bag_item.should be_a_kind_of(Chef::DataBagItem)
+    end
+  end
+
+  describe "data_bag" do
+    it "should let you set the data_bag to a string" do
+      @data_bag_item.data_bag("clowns").should == "clowns"
+    end
+
+    it "should return the current data_bag type" do
+      @data_bag_item.data_bag "clowns"
+      @data_bag_item.data_bag.should == "clowns"
+    end
+
+    it "should not accept spaces" do
+      lambda { @data_bag_item.data_bag "clown masters" }.should raise_error(ArgumentError)
+    end
+
+    it "should throw an ArgumentError if you feed it anything but a string" do
+      lambda { @data_bag_item.data_bag Hash.new }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "raw_data" do
+    it "should let you set the raw_data with a hash" do
+      lambda { @data_bag_item.raw_data = { "id" => "octahedron" } }.should_not raise_error
+    end
+
+    it "should let you set the raw_data from a mash" do
+      lambda { @data_bag_item.raw_data = Mash.new({ "id" => "octahedron" }) }.should_not raise_error
+    end
+
+    it "should raise an exception if you set the raw data without a key" do
+      lambda { @data_bag_item.raw_data = { "monkey" => "pants" } }.should raise_error(ArgumentError)
+    end
+
+    it "should raise an exception if you set the raw data to something other than a hash" do
+      lambda { @data_bag_item.raw_data = "katie rules" }.should raise_error(ArgumentError)
+    end
+
+    it "should accept alphanum/-/_ for the id" do
+      lambda { @data_bag_item.raw_data = { "id" => "h1-_" } }.should_not raise_error(ArgumentError)
+    end
+
+    it "should raise an exception if the id contains anything but alphanum/-/_" do
+      lambda { @data_bag_item.raw_data = { "id" => "!@#" } }.should raise_error(ArgumentError)
+    end
+
+    it "should return the raw data" do
+      @data_bag_item.raw_data = { "id" => "highway_of_emptiness" }
+      @data_bag_item.raw_data.should == { "id" => "highway_of_emptiness" }
+    end
+
+    it "should be a Mash by default" do
+      @data_bag_item.raw_data.should be_a_kind_of(Mash)
+    end
+  end
+
+  describe "object_name" do
+    before(:each) do
+      @data_bag_item.data_bag("dreams")
+      @data_bag_item.raw_data = { "id" => "the_beatdown" }
+    end
+
+    it "should return an object name based on the bag name and the raw_data id" do
+      @data_bag_item.object_name.should == "data_bag_item_dreams_the_beatdown"
+    end
+  end
+
+  describe "class method object_name" do
+    it "should return an object name based based on the bag name and an id" do
+      Chef::DataBagItem.object_name("zen", "master").should == "data_bag_item_zen_master"
+    end
+  end
+
+  describe "when used like a Hash" do
+    before(:each) do
+      @data_bag_item.raw_data = { "id" => "journey", "trials" => "been through" }
+    end
+
+    it "responds to keys" do
+      @data_bag_item.keys.should include("id")
+      @data_bag_item.keys.should include("trials")
+    end
+
+    it "supports element reference with []" do
+      @data_bag_item["id"].should == "journey"
+    end
+
+    it "implements all the methods of Hash" do
+      methods = [:rehash, :to_hash, :[], :fetch, :[]=, :store, :default,
+      :default=, :default_proc, :index, :size, :length,
+      :empty?, :each_value, :each_key, :each_pair, :each, :keys, :values,
+      :values_at, :delete, :delete_if, :reject!, :clear,
+      :invert, :update, :replace, :merge!, :merge, :has_key?, :has_value?,
+      :key?, :value?]
+      methods.each do |m|
+        @data_bag_item.should respond_to(m)
+      end
+    end
+
+  end
+
+  describe "to_hash" do
+    before(:each) do
+      @data_bag_item.data_bag("still_lost")
+      @data_bag_item.raw_data = { "id" => "whoa", "i_know" => "kung_fu" }
+      @to_hash = @data_bag_item.to_hash
+    end
+
+    it "should return a hash" do
+      @to_hash.should be_a_kind_of(Hash)
+    end
+
+    it "should have the raw_data keys as top level keys" do
+      @to_hash["id"].should == "whoa"
+      @to_hash["i_know"].should == "kung_fu"
+    end
+
+    it "should have the chef_type of data_bag_item" do
+      @to_hash["chef_type"].should == "data_bag_item"
+    end
+
+    it "should have the data_bag set" do
+      @to_hash["data_bag"].should == "still_lost"
+    end
+  end
+
+  describe "when deserializing from JSON" do
+    before(:each) do
+      @data_bag_item.data_bag('mars_volta')
+      @data_bag_item.raw_data = { "id" => "octahedron", "snooze" => { "finally" => :world_will }}
+      @deserial = Chef::JSONCompat.from_json(@data_bag_item.to_json)
+    end
+
+    it "should deserialize to a Chef::DataBagItem object" do
+      @deserial.should be_a_kind_of(Chef::DataBagItem)
+    end
+
+    it "should have a matching 'data_bag' value" do
+      @deserial.data_bag.should == @data_bag_item.data_bag
+    end
+
+    it "should have a matching 'id' key" do
+      @deserial["id"].should == "octahedron"
+    end
+
+    it "should have a matching 'snooze' key" do
+      @deserial["snooze"].should == { "finally" => "world_will" }
+    end
+  end
+
+  describe "when converting to a string" do
+    it "converts to a string in the form data_bag_item[ID]" do
+      @data_bag_item['id'] = "heart of darkness"
+      @data_bag_item.to_s.should == 'data_bag_item[heart of darkness]'
+    end
+
+    it "inspects as data_bag_item[BAG, ID, RAW_DATA]" do
+      raw_data = {"id" => "heart_of_darkness", "author" => "Conrad"}
+      @data_bag_item.raw_data = raw_data
+      @data_bag_item.data_bag("books")
+
+      @data_bag_item.inspect.should == "data_bag_item[\"books\", \"heart_of_darkness\", #{raw_data.inspect}]"
+    end
+  end
+
+  describe "save" do
+    before do
+      @rest = mock("Chef::REST")
+      Chef::REST.stub!(:new).and_return(@rest)
+      @data_bag_item['id'] = "heart of darkness"
+      raw_data = {"id" => "heart_of_darkness", "author" => "Conrad"}
+      @data_bag_item.raw_data = raw_data
+      @data_bag_item.data_bag("books")
+    end
+    it "should update the item when it already exists" do
+      @rest.should_receive(:put_rest).with("data/books/heart_of_darkness", @data_bag_item)
+      @data_bag_item.save
+    end
+
+    it "should create if the item is not found" do
+      exception = mock("404 error", :code => "404")
+      @rest.should_receive(:put_rest).and_raise(Net::HTTPServerException.new("foo", exception))
+      @rest.should_receive(:post_rest).with("data/books", @data_bag_item)
+      @data_bag_item.save
+    end
+    describe "when whyrun mode is enabled" do
+      before do
+        Chef::Config[:why_run] = true
+      end
+      after do
+        Chef::Config[:why_run] = false
+      end
+      it "should not save" do
+        @rest.should_not_receive(:put_rest)
+        @rest.should_not_receive(:post_rest)
+        @data_bag_item.data_bag("books")
+        @data_bag_item.save
+      end
+    end
+
+
+  end
+
+  describe "when loading" do
+    before do
+      @data_bag_item.raw_data = {"id" => "charlie", "shell" => "zsh", "ssh_keys" => %w{key1 key2}}
+      @data_bag_item.data_bag("users")
+    end
+
+    describe "from an API call" do
+      before do
+        @http_client = mock("Chef::REST")
+        Chef::REST.stub!(:new).and_return(@http_client)
+      end
+
+      it "converts raw data to a data bag item" do
+        @http_client.should_receive(:get_rest).with("data/users/charlie").and_return(@data_bag_item.to_hash)
+        item = Chef::DataBagItem.load(:users, "charlie")
+        item.should be_a_kind_of(Chef::DataBagItem)
+        item.should == @data_bag_item
+      end
+
+      it "does not convert when a DataBagItem is returned from the API call" do
+        @http_client.should_receive(:get_rest).with("data/users/charlie").and_return(@data_bag_item)
+        item = Chef::DataBagItem.load(:users, "charlie")
+        item.should be_a_kind_of(Chef::DataBagItem)
+        item.should equal(@data_bag_item)
+      end
+    end
+
+    describe "in solo mode" do
+      before do
+        Chef::Config[:solo] = true
+      end
+
+      after do
+        Chef::Config[:solo] = false
+      end
+
+      it "converts the raw data to a data bag item" do
+        Chef::DataBag.should_receive(:load).with('users').and_return({'charlie' => @data_bag_item.to_hash})
+        item = Chef::DataBagItem.load('users', 'charlie')
+        item.should be_a_kind_of(Chef::DataBagItem)
+        item.should == @data_bag_item
+      end
+    end
+
+  end
+
+end
diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb
new file mode 100644
index 0000000..9fbdc64
--- /dev/null
+++ b/spec/unit/data_bag_spec.rb
@@ -0,0 +1,174 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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/data_bag'
+
+describe Chef::DataBag do
+  before(:each) do
+    @data_bag = Chef::DataBag.new
+  end
+
+  describe "initialize" do
+    it "should be a Chef::DataBag" do
+      @data_bag.should be_a_kind_of(Chef::DataBag)
+    end
+  end
+
+  describe "name" do
+    it "should let you set the name to a string" do
+      @data_bag.name("clowns").should == "clowns"
+    end
+
+    it "should return the current name" do
+      @data_bag.name "clowns"
+      @data_bag.name.should == "clowns"
+    end
+
+    it "should not accept spaces" do
+      lambda { @data_bag.name "clown masters" }.should raise_error(ArgumentError)
+    end
+
+    it "should throw an ArgumentError if you feed it anything but a string" do
+      lambda { @data_bag.name Hash.new }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "deserialize" do
+    before(:each) do
+      @data_bag.name('mars_volta')
+      @deserial = Chef::JSONCompat.from_json(@data_bag.to_json)
+    end
+
+    it "should deserialize to a Chef::DataBag object" do
+      @deserial.should be_a_kind_of(Chef::DataBag)
+    end
+
+    %w{
+      name
+    }.each do |t|
+      it "should match '#{t}'" do
+        @deserial.send(t.to_sym).should == @data_bag.send(t.to_sym)
+      end
+    end
+
+  end
+
+  describe "when saving" do
+    before do
+      @data_bag.name('piggly_wiggly')
+      @rest = mock("Chef::REST")
+      Chef::REST.stub!(:new).and_return(@rest)
+    end
+
+    it "should silently proceed when the data bag already exists" do
+      exception = mock("409 error", :code => "409")
+      @rest.should_receive(:post_rest).and_raise(Net::HTTPServerException.new("foo", exception))
+      @data_bag.save
+    end
+
+    it "should create the data bag" do
+      @rest.should_receive(:post_rest).with("data", @data_bag)
+      @data_bag.save
+    end
+
+    describe "when whyrun mode is enabled" do
+      before do
+        Chef::Config[:why_run] = true
+      end
+      after do
+        Chef::Config[:why_run] = false
+      end
+      it "should not save" do
+        @rest.should_not_receive(:post_rest)
+        @data_bag.save
+      end
+    end
+
+  end
+  describe "when loading" do
+    describe "from an API call" do
+      before do
+        Chef::Config[:chef_server_url] = 'https://myserver.example.com'
+        @http_client = mock('Chef::REST')
+      end
+
+      it "should get the data bag from the server" do
+        Chef::REST.should_receive(:new).with('https://myserver.example.com').and_return(@http_client)
+        @http_client.should_receive(:get_rest).with('data/foo')
+        Chef::DataBag.load('foo')
+      end
+
+      it "should return the data bag" do
+        Chef::REST.stub!(:new).and_return(@http_client)
+        @http_client.should_receive(:get_rest).with('data/foo').and_return({'bar' => 'https://myserver.example.com/data/foo/bar'})
+        data_bag = Chef::DataBag.load('foo')
+        data_bag.should == {'bar' => 'https://myserver.example.com/data/foo/bar'}
+      end
+    end
+
+    describe "in solo mode" do
+      before do
+        Chef::Config[:solo] = true
+        Chef::Config[:data_bag_path] = '/var/chef/data_bags'
+      end
+
+      after do
+        Chef::Config[:solo] = false
+      end
+
+      it "should get the data bag from the data_bag_path" do
+        File.should_receive(:directory?).with('/var/chef/data_bags').and_return(true)
+        Dir.should_receive(:glob).with('/var/chef/data_bags/foo/*.json').and_return([])
+        Chef::DataBag.load('foo')
+      end
+
+      it "should get the data bag from the data_bag_path by symbolic name" do
+        File.should_receive(:directory?).with('/var/chef/data_bags').and_return(true)
+        Dir.should_receive(:glob).with('/var/chef/data_bags/foo/*.json').and_return([])
+        Chef::DataBag.load(:foo)
+      end
+
+      it "should return the data bag" do
+        File.should_receive(:directory?).with('/var/chef/data_bags').and_return(true)
+        Dir.stub!(:glob).and_return(["/var/chef/data_bags/foo/bar.json", "/var/chef/data_bags/foo/baz.json"])
+        IO.should_receive(:read).with('/var/chef/data_bags/foo/bar.json').and_return('{"id": "bar", "name": "Bob Bar" }')
+        IO.should_receive(:read).with('/var/chef/data_bags/foo/baz.json').and_return('{"id": "baz", "name": "John Baz" }')
+        data_bag = Chef::DataBag.load('foo')
+        data_bag.should == { 'bar' => { 'id' => 'bar', 'name' => 'Bob Bar' }, 'baz' => { 'id' => 'baz', 'name' => 'John Baz' }}
+      end
+
+      it "should return the data bag list" do
+        File.should_receive(:directory?).with('/var/chef/data_bags').and_return(true)
+        Dir.should_receive(:glob).and_return(["/var/chef/data_bags/foo", "/var/chef/data_bags/bar"])
+        data_bag_list = Chef::DataBag.list
+        data_bag_list.should == { 'bar' => 'bar', 'foo' => 'foo' }
+      end
+
+      it 'should raise an error if the configured data_bag_path is invalid' do
+        File.should_receive(:directory?).with('/var/chef/data_bags').and_return(false)
+
+        lambda {
+          Chef::DataBag.load('foo')
+        }.should raise_error Chef::Exceptions::InvalidDataBagPath, "Data bag path '/var/chef/data_bags' is invalid"
+      end
+
+    end
+  end
+
+end
diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb
new file mode 100644
index 0000000..58db295
--- /dev/null
+++ b/spec/unit/deprecation_spec.rb
@@ -0,0 +1,86 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/deprecation/warnings'
+
+describe Chef::Deprecation do
+
+  # Support code for Chef::Deprecation
+
+  def self.class_from_string(str)
+    str.split('::').inject(Object) do |mod, class_name|
+      mod.const_get(class_name)
+    end
+  end
+
+  module DeprecatedMethods
+    def deprecated_method(value)
+      @value = value
+    end
+
+    def get_value
+      @value
+    end
+  end
+
+  class TestClass
+    extend Chef::Deprecation::Warnings
+    include DeprecatedMethods
+    add_deprecation_warnings_for(DeprecatedMethods.instance_methods)
+  end
+
+  method_snapshot_file = File.join(CHEF_SPEC_DATA, "file-providers-method-snapshot-chef-11-4.json")
+  method_snapshot = JSON.parse(File.open(method_snapshot_file).read())
+
+  method_snapshot.each do |class_name, old_methods|
+    class_object = class_from_string(class_name)
+    current_methods = class_object.public_instance_methods.map(&:to_sym)
+
+    it "defines all methods on #{class_object} that were available in 11.0" do
+      old_methods.each do |old_method|
+        current_methods.should include(old_method.to_sym)
+      end
+    end
+  end
+
+  context 'deprecation warning messages' do
+    before(:each) do
+      @warning_output = [ ]
+      Chef::Log.stub!(:warn) { |msg| @warning_output << msg }
+    end
+
+    it 'should be enabled for deprecated methods' do
+      TestClass.new.deprecated_method(10)
+      @warning_output.should_not be_empty
+    end
+
+    it 'should contain stack trace' do
+      TestClass.new.deprecated_method(10)
+      @warning_output.join("").include?(".rb").should be_true
+    end
+  end
+
+  it 'deprecated methods should still be called' do
+    test_instance = TestClass.new
+    test_instance.deprecated_method(10)
+    test_instance.get_value.should == 10
+  end
+
+end
+
diff --git a/spec/unit/digester_spec.rb b/spec/unit/digester_spec.rb
new file mode 100644
index 0000000..fdf20ac
--- /dev/null
+++ b/spec/unit/digester_spec.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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::Digester do
+  before(:each) do
+    @cache = Chef::Digester.instance
+  end
+
+  describe "when computing checksums of cookbook files and templates" do
+
+    it "proxies the class method checksum_for_file to the instance" do
+      @cache.should_receive(:checksum_for_file).with("a_file_or_a_fail")
+      Chef::Digester.checksum_for_file("a_file_or_a_fail")
+    end
+
+    it "computes a checksum of a file" do
+      fixture_file = CHEF_SPEC_DATA + "/checksum/random.txt"
+      expected = "09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394"
+      @cache.checksum_for_file(fixture_file).should == expected
+    end
+
+    it "generates a checksum from a non-file IO object" do
+      io = StringIO.new("riseofthemachines\nriseofthechefs\n")
+      expected_md5 = '0e157ac1e2dd73191b76067fb6b4bceb'
+      @cache.generate_md5_checksum(io).should == expected_md5
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/dsl/data_query_spec.rb b/spec/unit/dsl/data_query_spec.rb
new file mode 100644
index 0000000..8960ad9
--- /dev/null
+++ b/spec/unit/dsl/data_query_spec.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/dsl/data_query'
+
+class DataQueryDSLTester
+  include Chef::DSL::DataQuery
+end
+
+describe Chef::DSL::DataQuery do
+  before(:each) do
+    @language = DataQueryDSLTester.new
+    @node = Hash.new
+    @language.stub!(:node).and_return(@node)
+  end
+
+  describe "when loading data bags and items" do
+    it "lists the items in a data bag" do
+      Chef::DataBag.should_receive(:load).with("bag_name").and_return("item_1" => "http://url_for/item_1", "item_2" => "http://url_for/item_2")
+      @language.data_bag("bag_name").sort.should == %w[item_1 item_2]
+    end
+
+    it "validates the name of the data bag you're trying to load" do
+      lambda {@language.data_bag("!# %^&& ")}.should raise_error(Chef::Exceptions::InvalidDataBagName)
+    end
+
+    it "fetches a data bag item" do
+      @item = Chef::DataBagItem.new
+      @item.data_bag("bag_name")
+      @item.raw_data = {"id" => "item_name", "FUU" => "FUU"}
+      Chef::DataBagItem.should_receive(:load).with("bag_name", "item_name").and_return(@item)
+      @language.data_bag_item("bag_name", "item_name").should == @item
+    end
+
+    it "validates the name of the data bag you're trying to load an item from" do
+      lambda {@language.data_bag_item(" %%^& ", "item_name")}.should raise_error(Chef::Exceptions::InvalidDataBagName)
+    end
+
+    it "validates the id of the data bag item you're trying to load" do
+      lambda {@language.data_bag_item("bag_name", " 987 (*&()")}.should raise_error(Chef::Exceptions::InvalidDataBagItemID)
+    end
+
+    it "validates that the id of the data bag item is not nil" do
+      lambda {@language.data_bag_item("bag_name", nil)}.should raise_error(Chef::Exceptions::InvalidDataBagItemID)
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/dsl/platform_introspection_spec.rb b/spec/unit/dsl/platform_introspection_spec.rb
new file mode 100644
index 0000000..f00d5a2
--- /dev/null
+++ b/spec/unit/dsl/platform_introspection_spec.rb
@@ -0,0 +1,130 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/dsl/platform_introspection'
+
+class LanguageTester
+  attr_reader :node
+  def initialize(node)
+    @node = node
+  end
+  include Chef::DSL::PlatformIntrospection
+end
+
+describe "PlatformIntrospection implementors" do
+
+  let(:node) { Chef::Node.new }
+  let(:platform_introspector) { LanguageTester.new(node) }
+
+  it_behaves_like "a platform introspector"
+
+end
+
+describe Chef::DSL::PlatformIntrospection::PlatformDependentValue do
+  before do
+    platform_hash = {
+      :openbsd => {:default => 'free, functional, secure'},
+      [:redhat, :centos, :fedora, :scientific] => {:default => '"stable"'},
+      :ubuntu => {'10.04' => 'using upstart more', :default => 'using init more'},
+      :default => 'bork da bork'
+    }
+    @platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformDependentValue.new(platform_hash)
+  end
+
+  it "returns the default value when the platform doesn't match" do
+    @platform_specific_value.value_for_node(:platform => :dos).should == 'bork da bork'
+  end
+
+  it "returns a value for a platform set as a group" do
+    @platform_specific_value.value_for_node(:platform => :centos).should == '"stable"'
+  end
+
+  it "returns a value for the platform when it was set as a symbol but fetched as a string" do
+    @platform_specific_value.value_for_node(:platform => "centos").should == '"stable"'
+  end
+
+  it "returns a value for a specific platform version" do
+    node = {:platform => 'ubuntu', :platform_version => '10.04'}
+    @platform_specific_value.value_for_node(node).should == 'using upstart more'
+  end
+
+  it "returns a platform-default value if the platform version doesn't match an explicit one" do
+    node = {:platform => 'ubuntu', :platform_version => '9.10' }
+    @platform_specific_value.value_for_node(node).should == 'using init more'
+  end
+
+  it "returns nil if there is no default and no platforms match" do
+    # this matches the behavior in the original implementation.
+    # whether or not it's correct is another matter.
+    platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformDependentValue.new({})
+    platform_specific_value.value_for_node(:platform => 'foo').should be_nil
+  end
+
+  it "raises an argument error if the platform hash is not correctly structured" do
+    bad_hash = {:ubuntu => :foo} # should be :ubuntu => {:default => 'foo'}
+    lambda {Chef::DSL::PlatformIntrospection::PlatformDependentValue.new(bad_hash)}.should raise_error(ArgumentError)
+  end
+
+end
+describe Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue do
+  before do
+    @array_values = [:stop, :start, :reload]
+
+    @platform_family_hash = {
+      "debian" => "debian value",
+      [:rhel, "fedora"] => "redhatty value",
+      "suse" => @array_values,
+      :gentoo => "gentoo value",
+      :default => "default value"
+    }
+
+    @platform_family_value = Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue.new(@platform_family_hash)
+  end
+
+  it "returns the default value when the platform family doesn't match" do
+    @platform_family_value.value_for_node(:platform_family => :os2).should == 'default value'
+  end
+
+
+  it "returns a value for the platform family when it was set as a string but fetched as a symbol" do
+    @platform_family_value.value_for_node(:platform_family => :debian).should == "debian value"
+  end
+
+  it "returns a value for the platform family when it was set as a symbol but fetched as a string" do
+    @platform_family_value.value_for_node(:platform_family => "gentoo").should == "gentoo value"
+  end
+
+  it "returns an array value stored for a platform family" do
+    @platform_family_value.value_for_node(:platform_family => "suse").should == @array_values
+  end
+
+  it "returns a value for the platform family when it was set within an array hash key as a symbol" do
+    @platform_family_value.value_for_node(:platform_family => :rhel).should == "redhatty value"
+  end
+
+  it "returns a value for the platform family when it was set within an array hash key as a string" do
+    @platform_family_value.value_for_node(:platform_family => "fedora").should == "redhatty value"
+  end
+
+  it "returns nil if there is no default and no platforms match" do
+    platform_specific_value = Chef::DSL::PlatformIntrospection::PlatformFamilyDependentValue.new({})
+    platform_specific_value.value_for_node(:platform_family => 'foo').should be_nil
+  end
+
+end
diff --git a/spec/unit/dsl/regsitry_helper_spec.rb b/spec/unit/dsl/regsitry_helper_spec.rb
new file mode 100644
index 0000000..7fe08e3
--- /dev/null
+++ b/spec/unit/dsl/regsitry_helper_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Copyright:: Copyright (c) 2011 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/dsl/registry_helper"
+require "spec_helper"
+
+describe Chef::Resource::RegistryKey do
+
+  before (:all) do
+    events = Chef::EventDispatch::Dispatcher.new
+    node = Chef::Node.new
+    ohai = Ohai::System.new
+    ohai.all_plugins
+    node.consume_external_attrs(ohai.data,{})
+    run_context = Chef::RunContext.new(node, {}, events)
+    @resource = Chef::Resource::new("foo", run_context)
+  end
+
+  context "tests registry dsl" do
+    it "resource can access registry_helper method registry_key_exists" do
+      @resource.respond_to?('registry_key_exists?').should == true
+    end
+    it "resource can access registry_helper method registry_get_values" do
+      @resource.respond_to?('registry_get_values').should == true
+    end
+    it "resource can access registry_helper method registry_has_subkey" do
+      @resource.respond_to?('registry_has_subkeys?').should == true
+    end
+    it "resource can access registry_helper method registry_get_subkeys" do
+      @resource.respond_to?('registry_get_subkeys').should == true
+    end
+    it "resource can access registry_helper method registry_value_exists" do
+      @resource.respond_to?('registry_value_exists?').should == true
+    end
+    it "resource can access registry_helper method data_value_exists" do
+      @resource.respond_to?('registry_data_exists?').should == true
+    end
+  end
+end
+
diff --git a/spec/unit/encrypted_data_bag_item_spec.rb b/spec/unit/encrypted_data_bag_item_spec.rb
new file mode 100644
index 0000000..18178d2
--- /dev/null
+++ b/spec/unit/encrypted_data_bag_item_spec.rb
@@ -0,0 +1,318 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright 2010-2011 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/encrypted_data_bag_item'
+
+module Version0Encryptor
+  def self.encrypt_value(plaintext_data, key)
+    data = plaintext_data.to_yaml
+
+    cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
+    cipher.encrypt
+    cipher.pkcs5_keyivgen(key)
+    encrypted_bytes = cipher.update(data)
+    encrypted_bytes << cipher.final
+    Base64.encode64(encrypted_bytes)
+  end
+end
+
+describe Chef::EncryptedDataBagItem::Encryptor  do
+
+  subject(:encryptor) { described_class.new(plaintext_data, key) }
+  let(:plaintext_data) { {"foo" => "bar"} }
+  let(:key) { "passwd" }
+
+  it "encrypts to format version 1 by default" do
+    encryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor)
+  end
+
+  describe "generating a random IV" do
+    it "generates a new IV for each encryption pass" do
+      encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key)
+
+      # No API in ruby OpenSSL to get the iv it used for the encryption back
+      # out. Instead we test if the encrypted data is the same. If it *is* the
+      # same, we assume the IV was the same each time.
+      encryptor.encrypted_data.should_not eq encryptor2.encrypted_data
+    end
+  end
+
+  describe "when encrypting a non-hash non-array value" do
+    let(:plaintext_data) { 5 }
+    it "serializes the value in a de-serializable way" do
+      Chef::JSONCompat.from_json(subject.serialized_data)["json_wrapper"].should eq 5
+    end
+
+  end
+
+  describe "wrapping secret values in an envelope" do
+    it "wraps the encrypted data in an envelope with the iv and version" do
+      final_data = encryptor.for_encrypted_item
+      final_data["encrypted_data"].should eq encryptor.encrypted_data
+      final_data["iv"].should eq Base64.encode64(encryptor.iv)
+      final_data["version"].should eq 1
+      final_data["cipher"].should eq"aes-256-cbc"
+    end
+  end
+
+  describe "when using version 2 format" do
+
+    before do
+      Chef::Config[:data_bag_encrypt_version] = 2
+    end
+
+    it "creates a version 2 encryptor" do
+      encryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor)
+    end
+
+    it "generates an hmac based on ciphertext including iv" do
+      encryptor2 = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, key)
+      encryptor.hmac.should_not eq(encryptor2.hmac)
+    end
+
+    it "includes the hmac in the envelope" do
+      final_data = encryptor.for_encrypted_item
+      final_data["hmac"].should eq(encryptor.hmac)
+    end
+  end
+
+end
+
+describe Chef::EncryptedDataBagItem::Decryptor do
+
+  subject(:decryptor) { described_class.for(encrypted_value, decryption_key) }
+  let(:plaintext_data) { {"foo" => "bar"} }
+  let(:encryption_key) { "passwd" }
+  let(:decryption_key) { encryption_key }
+
+  context "when decrypting a version 2 (JSON+aes-256-cbc+hmac-sha256+random iv) encrypted value" do
+    let(:encrypted_value) do
+      Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.new(plaintext_data, encryption_key).for_encrypted_item
+    end
+
+    let(:bogus_hmac) do
+      digest = OpenSSL::Digest::Digest.new("sha256")
+      raw_hmac = OpenSSL::HMAC.digest(digest, "WRONG", encrypted_value["encrypted_data"])
+      Base64.encode64(raw_hmac)
+    end
+
+    it "rejects the data if the hmac is wrong" do
+      encrypted_value["hmac"] = bogus_hmac
+      lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure)
+    end
+
+    it "rejects the data if the hmac is missing" do
+      encrypted_value.delete("hmac")
+      lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure)
+    end
+
+  end
+
+  context "when decrypting a version 1 (JSON+aes-256-cbc+random iv) encrypted value" do
+
+    let(:encrypted_value) do
+      Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, encryption_key).for_encrypted_item
+    end
+
+    it "selects the correct strategy for version 1" do
+      decryptor.should be_a_kind_of Chef::EncryptedDataBagItem::Decryptor::Version1Decryptor
+    end
+
+    it "decrypts the encrypted value" do
+      decryptor.decrypted_data.should eq({"json_wrapper" => plaintext_data}.to_json)
+    end
+
+    it "unwraps the encrypted data and returns it" do
+      decryptor.for_decrypted_item.should eq plaintext_data
+    end
+
+    describe "and the decryption step returns invalid data" do
+      it "raises a decryption failure error" do
+        # Over a large number of tests on a variety of systems, we occasionally
+        # see the decryption step "succeed" but return invalid data (e.g., not
+        # the original plain text) [CHEF-3858]
+        decryptor.should_receive(:decrypted_data).and_return("lksajdf")
+        lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure)
+      end
+    end
+
+    context "and the provided key is incorrect" do
+      let(:decryption_key) { "wrong-passwd" }
+
+      it "raises a sensible error" do
+        lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::DecryptionFailure)
+      end
+    end
+
+    context "and the cipher is not supported" do
+      let(:encrypted_value) do
+        ev = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data, encryption_key).for_encrypted_item
+        ev["cipher"] = "aes-256-foo"
+        ev
+      end
+
+      it "raises a sensible error" do
+        lambda { decryptor.for_decrypted_item }.should raise_error(Chef::EncryptedDataBagItem::UnsupportedCipher)
+      end
+    end
+
+    context "and version 2 format is required" do
+      before do
+        Chef::Config[:data_bag_decrypt_minimum_version] = 2
+      end
+
+      it "raises an error attempting to decrypt" do
+        lambda { decryptor }.should raise_error(Chef::EncryptedDataBagItem::UnacceptableEncryptedDataBagItemFormat)
+      end
+
+    end
+
+  end
+
+  context "when decrypting a version 0 (YAML+aes-256-cbc+no iv) encrypted value" do
+    let(:encrypted_value) do
+      Version0Encryptor.encrypt_value(plaintext_data, encryption_key)
+    end
+
+    it "selects the correct strategy for version 0" do
+      decryptor.should be_a_kind_of(Chef::EncryptedDataBagItem::Decryptor::Version0Decryptor)
+    end
+
+    it "decrypts the encrypted value" do
+      decryptor.for_decrypted_item.should eq plaintext_data
+    end
+
+    context "and version 1 format is required" do
+      before do
+        Chef::Config[:data_bag_decrypt_minimum_version] = 1
+      end
+
+      it "raises an error attempting to decrypt" do
+        lambda { decryptor }.should raise_error(Chef::EncryptedDataBagItem::UnacceptableEncryptedDataBagItemFormat)
+      end
+
+    end
+
+  end
+end
+
+describe Chef::EncryptedDataBagItem do
+  subject { described_class }
+  let(:encrypted_data_bag_item) { subject.new(encoded_data, secret) }
+  let(:plaintext_data) {{
+      "id" => "item_name",
+      "greeting" => "hello",
+      "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
+  }}
+  let(:secret) { "abc123SECRET" }
+  let(:encoded_data) { subject.encrypt_data_bag_item(plaintext_data, secret) }
+
+  describe "encrypting" do
+
+    it "doesn't encrypt the 'id' key" do
+      encoded_data["id"].should eq "item_name"
+    end
+
+    it "encrypts non-collection objects" do
+      encoded_data["greeting"]["version"].should eq 1
+      encoded_data["greeting"].should have_key("iv")
+
+      iv = encoded_data["greeting"]["iv"]
+      encryptor = Chef::EncryptedDataBagItem::Encryptor.new("hello", secret, iv)
+
+      encoded_data["greeting"]["encrypted_data"].should eq(encryptor.for_encrypted_item["encrypted_data"])
+    end
+
+    it "encrypts nested values" do
+      encoded_data["nested"]["version"].should eq 1
+      encoded_data["nested"].should have_key("iv")
+
+      iv = encoded_data["nested"]["iv"]
+      encryptor = Chef::EncryptedDataBagItem::Encryptor.new(plaintext_data["nested"], secret, iv)
+
+      encoded_data["nested"]["encrypted_data"].should eq(encryptor.for_encrypted_item["encrypted_data"])
+    end
+
+  end
+
+  describe "decrypting" do
+
+    it "doesn't try to decrypt 'id'" do
+      encrypted_data_bag_item["id"].should eq(plaintext_data["id"])
+    end
+
+    it "decrypts 'greeting'" do
+      encrypted_data_bag_item["greeting"].should eq(plaintext_data["greeting"])
+    end
+
+    it "decrypts 'nested'" do
+      encrypted_data_bag_item["nested"].should eq(plaintext_data["nested"])
+    end
+
+    it "decrypts everyting via to_hash" do
+      encrypted_data_bag_item.to_hash.should eq(plaintext_data)
+    end
+
+    it "handles missing keys gracefully" do
+      encrypted_data_bag_item["no-such-key"].should be_nil
+    end
+  end
+
+  describe "loading" do
+    it "should defer to Chef::DataBagItem.load" do
+      Chef::DataBagItem.stub(:load).with(:the_bag, "my_codes").and_return(encoded_data)
+      edbi = Chef::EncryptedDataBagItem.load(:the_bag, "my_codes", secret)
+      edbi["greeting"].should eq(plaintext_data["greeting"])
+    end
+  end
+
+  describe ".load_secret" do
+    subject(:loaded_secret) { Chef::EncryptedDataBagItem.load_secret(path) }
+    let(:path) { "/var/mysecret" }
+    let(:secret) { "opensesame" }
+    let(:stubbed_path) { path }
+    before do
+      ::File.stub(:exist?).with(stubbed_path).and_return(true)
+      IO.stub(:read).with(stubbed_path).and_return(secret)
+      Kernel.stub(:open).with(path).and_return(StringIO.new(secret))
+    end
+
+    it "reads from a specified path" do
+      loaded_secret.should eq secret
+    end
+
+    context "path argument is nil" do
+      let(:path) { nil }
+      let(:stubbed_path) { "/etc/chef/encrypted_data_bag_secret" }
+
+      it "reads from Chef::Config[:encrypted_data_bag_secret]" do
+        Chef::Config[:encrypted_data_bag_secret] = stubbed_path
+        loaded_secret.should eq secret
+      end
+    end
+
+    context "path argument is a URL" do
+      let(:path) { "http://www.opscode.com/" }
+
+      it "reads the URL" do
+        loaded_secret.should eq secret
+      end
+    end
+  end
+end
diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb
new file mode 100644
index 0000000..5f9675a
--- /dev/null
+++ b/spec/unit/environment_spec.rb
@@ -0,0 +1,460 @@
+#
+# Author:: Stephen Delano (<stephen at ospcode.com>)
+# Author:: Seth Falcon (<seth at ospcode.com>)
+# Author:: John Keiser (<jkeiser at ospcode.com>)
+# Author:: Kyle Goodwin (<kgoodwin at primerevenue.com>)
+# Copyright:: Copyright 2010-2011 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/environment'
+
+describe Chef::Environment do
+  before(:each) do
+    @environment = Chef::Environment.new
+  end
+
+  describe "initialize" do
+    it "should be a Chef::Environment" do
+      @environment.should be_a_kind_of(Chef::Environment)
+    end
+  end
+
+  describe "name" do
+    it "should let you set the name to a string" do
+      @environment.name("production").should == "production"
+    end
+
+    it "should return the current name" do
+      @environment.name("production")
+      @environment.name.should == "production"
+    end
+
+    it "should not accept spaces" do
+      lambda { @environment.name("production environment") }.should raise_error(ArgumentError)
+    end
+
+    it "should not accept anything but strings" do
+      lambda { @environment.name(Array.new) }.should raise_error(ArgumentError)
+      lambda { @environment.name(Hash.new) }.should raise_error(ArgumentError)
+      lambda { @environment.name(2) }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "description" do
+    it "should let you set the description to a string" do
+      @environment.description("this is my test environment").should == "this is my test environment"
+    end
+
+    it "should return the correct description" do
+      @environment.description("I like running tests")
+      @environment.description.should == "I like running tests"
+    end
+
+    it "should not accept anything but strings" do
+      lambda { @environment.description(Array.new) }.should raise_error(ArgumentError)
+      lambda { @environment.description(Hash.new) }.should raise_error(ArgumentError)
+      lambda { @environment.description(42) }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "default attributes" do
+    it "should let you set the attributes hash explicitly" do
+      @environment.default_attributes({ :one => 'two' }).should == { :one => 'two' }
+    end
+
+    it "should let you return the attributes hash" do
+      @environment.default_attributes({ :one => 'two' })
+      @environment.default_attributes.should == { :one => 'two' }
+    end
+
+    it "should throw an ArgumentError if we aren't a kind of hash" do
+      lambda { @environment.default_attributes(Array.new) }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "override attributes" do
+    it "should let you set the attributes hash explicitly" do
+      @environment.override_attributes({ :one => 'two' }).should == { :one => 'two' }
+    end
+
+    it "should let you return the attributes hash" do
+      @environment.override_attributes({ :one => 'two' })
+      @environment.override_attributes.should == { :one => 'two' }
+    end
+
+    it "should throw an ArgumentError if we aren't a kind of hash" do
+      lambda { @environment.override_attributes(Array.new) }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "cookbook_versions" do
+    before(:each) do
+      @cookbook_versions = {
+        "apt"     => "= 1.0.0",
+        "god"     => "= 2.0.0",
+        "apache2" => "= 4.2.0"
+      }
+    end
+
+    it "should let you set the cookbook versions in a hash" do
+      @environment.cookbook_versions(@cookbook_versions).should == @cookbook_versions
+    end
+
+    it "should return the cookbook versions" do
+      @environment.cookbook_versions(@cookbook_versions)
+      @environment.cookbook_versions.should == @cookbook_versions
+    end
+
+    it "should not accept anything but a hash" do
+      lambda { @environment.cookbook_versions("I am a string!") }.should raise_error(ArgumentError)
+      lambda { @environment.cookbook_versions(Array.new) }.should raise_error(ArgumentError)
+      lambda { @environment.cookbook_versions(42) }.should raise_error(ArgumentError)
+    end
+
+    it "should validate the hash" do
+      Chef::Environment.should_receive(:validate_cookbook_versions).with(@cookbook_versions).and_return true
+      @environment.cookbook_versions(@cookbook_versions)
+    end
+  end
+
+  describe "cookbook" do
+    it "should set the version of the cookbook in the cookbook_versions hash" do
+      @environment.cookbook("apt", "~> 1.2.3")
+      @environment.cookbook_versions["apt"].should == "~> 1.2.3"
+    end
+
+    it "should validate the cookbook version it is passed" do
+      Chef::Environment.should_receive(:validate_cookbook_version).with(">= 1.2.3").and_return true
+      @environment.cookbook("apt", ">= 1.2.3")
+    end
+  end
+
+  describe "update_from!" do
+    before(:each) do
+      @environment.name("prod")
+      @environment.description("this is prod")
+      @environment.cookbook_versions({ "apt" => "= 1.2.3" })
+
+      @example = Chef::Environment.new
+      @example.name("notevenprod")
+      @example.description("this is pre-prod")
+      @example.cookbook_versions({ "apt" => "= 2.3.4" })
+    end
+
+    it "should update everything but name" do
+      @environment.update_from!(@example)
+      @environment.name.should == "prod"
+      @environment.description.should == @example.description
+      @environment.cookbook_versions.should == @example.cookbook_versions
+    end
+  end
+
+  describe "to_hash" do
+    before(:each) do
+      @environment.name("spec")
+      @environment.description("Where we run the spec tests")
+      @environment.cookbook_versions({:apt => "= 1.2.3"})
+      @hash = @environment.to_hash
+    end
+
+    %w{name description cookbook_versions}.each do |t|
+      it "should include '#{t}'" do
+        @hash[t].should == @environment.send(t.to_sym)
+      end
+    end
+
+    it "should include 'json_class'" do
+      @hash["json_class"].should == "Chef::Environment"
+    end
+
+    it "should include 'chef_type'" do
+      @hash["chef_type"].should == "environment"
+    end
+  end
+
+  describe "to_json" do
+    before(:each) do
+      @environment.name("spec")
+      @environment.description("Where we run the spec tests")
+      @environment.cookbook_versions({:apt => "= 1.2.3"})
+      @json = @environment.to_json
+    end
+
+    %w{name description cookbook_versions}.each do |t|
+      it "should include '#{t}'" do
+        @json.should =~ /"#{t}":#{Regexp.escape(@environment.send(t.to_sym).to_json)}/
+      end
+    end
+
+    it "should include 'json_class'" do
+      @json.should =~ /"json_class":"Chef::Environment"/
+    end
+
+    it "should include 'chef_type'" do
+      @json.should =~ /"chef_type":"environment"/
+    end
+  end
+
+  describe "from_json" do
+    before(:each) do
+      @data = {
+        "name" => "production",
+        "description" => "We are productive",
+        "cookbook_versions" => {
+          "apt" => "= 1.2.3",
+          "god" => ">= 4.2.0",
+          "apache2" => "= 2.0.0"
+        },
+        "json_class" => "Chef::Environment",
+        "chef_type" => "environment"
+      }
+      @environment = Chef::JSONCompat.from_json(@data.to_json)
+    end
+
+    it "should return a Chef::Environment" do
+      @environment.should be_a_kind_of(Chef::Environment)
+    end
+
+    %w{name description cookbook_versions}.each do |t|
+      it "should match '#{t}'" do
+        @environment.send(t.to_sym).should == @data[t]
+      end
+    end
+  end
+
+  describe "self.validate_cookbook_versions" do
+    before(:each) do
+      @cookbook_versions = {
+        "apt"     => "= 1.0.0",
+        "god"     => "= 2.0.0",
+        "apache2" => "= 4.2.0"
+      }
+    end
+
+    it "should validate the version string of each cookbook" do
+      @cookbook_versions.each do |cookbook, version|
+        Chef::Environment.should_receive(:validate_cookbook_version).with(version).and_return true
+      end
+      Chef::Environment.validate_cookbook_versions(@cookbook_versions)
+    end
+
+    it "should return false if anything other than a hash is passed as the argument" do
+      Chef::Environment.validate_cookbook_versions(Array.new).should == false
+      Chef::Environment.validate_cookbook_versions(42).should == false
+      Chef::Environment.validate_cookbook_versions(Chef::CookbookVersion.new("meta")).should == false
+      Chef::Environment.validate_cookbook_versions("cookbook => 1.2.3").should == false
+    end
+  end
+
+  describe "self.validate_cookbook_version" do
+    it "should validate correct version numbers" do
+      Chef::Environment.validate_cookbook_version("= 1.2.3").should == true
+      Chef::Environment.validate_cookbook_version(">= 0.0.3").should == true
+      # A lone version is allowed, interpreted as implicit '='
+      Chef::Environment.validate_cookbook_version("1.2.3").should == true
+    end
+
+    it "should return false when an invalid version is given" do
+      Chef::Environment.validate_cookbook_version(Chef::CookbookVersion.new("meta")).should == false
+      Chef::Environment.validate_cookbook_version("= 1.2.3a").should == false
+      Chef::Environment.validate_cookbook_version("= 1").should == false
+      Chef::Environment.validate_cookbook_version("= a").should == false
+      Chef::Environment.validate_cookbook_version("= 1.2.3.4").should == false
+    end
+
+    describe "in solo mode" do
+      before do
+        Chef::Config[:solo] = true
+      end
+
+      after do
+        Chef::Config[:solo] = false
+      end
+
+      it "should raise and exception" do
+        lambda {
+          Chef::Environment.validate_cookbook_version("= 1.2.3.4")
+        }.should raise_error Chef::Exceptions::IllegalVersionConstraint,
+                             "Environment cookbook version constraints not allowed in chef-solo"
+      end
+    end
+
+  end
+
+  describe "when updating from a parameter hash" do
+    before do
+      @environment = Chef::Environment.new
+    end
+
+    it "updates the name from parameters[:name]" do
+      @environment.update_from_params(:name => "kurrupt")
+      @environment.name.should == "kurrupt"
+    end
+
+    it "validates the name given in the params" do
+      @environment.update_from_params(:name => "@$%^&*()").should be_false
+      @environment.invalid_fields[:name].should == %q|Option name's value @$%^&*() does not match regular expression /^[\-[:alnum:]_]+$/|
+    end
+
+    it "updates the description from parameters[:description]" do
+      @environment.update_from_params(:description => "wow, writing your own object mapper is kinda painful")
+      @environment.description.should == "wow, writing your own object mapper is kinda painful"
+    end
+
+    it "updates cookbook version constraints from the hash in parameters[:cookbook_version_constraints]" do
+      # NOTE: I'm only choosing this (admittedly weird) structure for the hash b/c the better more obvious
+      # one, i.e, {:cookbook_version_constraints => {COOKBOOK_NAME => CONSTRAINT}} is difficult to implement
+      # the way merb does params
+      params = {:name=>"superbowl", :cookbook_version => {"0" => "apache2 ~> 1.0.0", "1" => "nginx < 2.0.0"}}
+      @environment.update_from_params(params)
+      @environment.cookbook_versions.should == {"apache2" => "~> 1.0.0", "nginx" => "< 2.0.0"}
+    end
+
+    it "validates the cookbook constraints" do
+      params = {:cookbook_version => {"0" => "apache2 >>> 1.0.0"}}
+      @environment.update_from_params(params).should be_false
+      err_msg = @environment.invalid_fields[:cookbook_version]["0"]
+      err_msg.should == "apache2 >>> 1.0.0 is not a valid cookbook constraint"
+    end
+
+    it "is not valid if the name is not present" do
+      @environment.validate_required_attrs_present.should be_false
+      @environment.invalid_fields[:name].should == "name cannot be empty"
+    end
+
+    it "is not valid after updating from params if the name is not present" do
+      @environment.update_from_params({}).should be_false
+      @environment.invalid_fields[:name].should == "name cannot be empty"
+    end
+
+    it "updates default attributes from a JSON string in params[:attributes]" do
+      @environment.update_from_params(:name => "fuuu", :default_attributes => %q|{"fuuu":"RAGE"}|)
+      @environment.default_attributes.should == {"fuuu" => "RAGE"}
+    end
+
+    it "updates override attributes from a JSON string in params[:attributes]" do
+      @environment.update_from_params(:name => "fuuu", :override_attributes => %q|{"foo":"override"}|)
+      @environment.override_attributes.should == {"foo" => "override"}
+    end
+
+  end
+
+  describe "api model" do
+    before(:each) do
+      @rest = mock("Chef::REST")
+      Chef::REST.stub!(:new).and_return(@rest)
+      @query = mock("Chef::Search::Query")
+      Chef::Search::Query.stub!(:new).and_return(@query)
+    end
+
+    describe "list" do
+      describe "inflated" do
+        it "should return a hash of environment names and objects" do
+          e1 = mock("Chef::Environment", :name => "one")
+          @query.should_receive(:search).with(:environment).and_yield(e1)
+          r = Chef::Environment.list(true)
+          r["one"].should == e1
+        end
+      end
+
+      it "should return a hash of environment names and urls" do
+        @rest.should_receive(:get_rest).and_return({ "one" => "http://foo" })
+        r = Chef::Environment.list
+        r["one"].should == "http://foo"
+      end
+    end
+  end
+
+  describe "when loading" do
+    describe "in solo mode" do
+      before do
+        Chef::Config[:solo] = true
+        Chef::Config[:environment_path] = '/var/chef/environments'
+      end
+
+      after do
+        Chef::Config[:solo] = false
+      end
+
+      it "should get the environment from the environment_path" do
+        File.should_receive(:directory?).with(Chef::Config[:environment_path]).and_return(true)
+        File.should_receive(:exists?).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(false)
+        File.should_receive(:exists?).with(File.join(Chef::Config[:environment_path], 'foo.rb')).exactly(2).times.and_return(true)
+        File.should_receive(:readable?).with(File.join(Chef::Config[:environment_path], 'foo.rb')).and_return(true)
+        role_dsl="name \"foo\"\ndescription \"desc\"\n"
+        IO.should_receive(:read).with(File.join(Chef::Config[:environment_path], 'foo.rb')).and_return(role_dsl)
+        Chef::Environment.load('foo')
+      end
+
+      it "should return a Chef::Environment object from JSON" do
+        File.should_receive(:directory?).with(Chef::Config[:environment_path]).and_return(true)
+        File.should_receive(:exists?).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(true)
+        environment_hash = {
+          "name" => "foo",
+          "default_attributes" => {
+            "foo" => {
+              "bar" => 1
+            }
+          },
+          "json_class" => "Chef::Environment",
+          "description" => "desc",
+          "chef_type" => "environment"
+        }
+        IO.should_receive(:read).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(JSON.dump(environment_hash))
+        environment = Chef::Environment.load('foo')
+
+        environment.should be_a_kind_of(Chef::Environment)
+        environment.name.should == environment_hash['name']
+        environment.description.should == environment_hash['description']
+        environment.default_attributes.should == environment_hash['default_attributes']
+      end
+
+      it "should return a Chef::Environment object from Ruby DSL" do
+        File.should_receive(:directory?).with(Chef::Config[:environment_path]).and_return(true)
+        File.should_receive(:exists?).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(false)
+        File.should_receive(:exists?).with(File.join(Chef::Config[:environment_path], 'foo.rb')).exactly(2).times.and_return(true)
+        File.should_receive(:readable?).with(File.join(Chef::Config[:environment_path], 'foo.rb')).and_return(true)
+        role_dsl="name \"foo\"\ndescription \"desc\"\n"
+        IO.should_receive(:read).with(File.join(Chef::Config[:environment_path], 'foo.rb')).and_return(role_dsl)
+        environment = Chef::Environment.load('foo')
+
+        environment.should be_a_kind_of(Chef::Environment)
+        environment.name.should == 'foo'
+        environment.description.should == 'desc'
+      end
+
+      it 'should raise an error if the configured environment_path is invalid' do
+        File.should_receive(:directory?).with(Chef::Config[:environment_path]).and_return(false)
+
+        lambda {
+          Chef::Environment.load('foo')
+        }.should raise_error Chef::Exceptions::InvalidEnvironmentPath, "Environment path '/var/chef/environments' is invalid"
+      end
+
+      it 'should raise an error if the file does not exist' do
+        File.should_receive(:directory?).with(Chef::Config[:environment_path]).and_return(true)
+        File.should_receive(:exists?).with(File.join(Chef::Config[:environment_path], 'foo.json')).and_return(false)
+        File.should_receive(:exists?).with(File.join(Chef::Config[:environment_path], 'foo.rb')).and_return(false)
+
+        lambda {
+          Chef::Environment.load('foo')
+        }.should raise_error Chef::Exceptions::EnvironmentNotFound, "Environment 'foo' could not be loaded from disk"
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb
new file mode 100644
index 0000000..3e7b1ba
--- /dev/null
+++ b/spec/unit/exceptions_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Kyle Goodwin (<kgoodwin at primerevenue.com>)
+# Copyright:: Copyright (c) 2010 Thomas Bishop
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Exceptions do
+  exception_to_super_class = {
+    Chef::Exceptions::Application => RuntimeError,
+    Chef::Exceptions::Cron => RuntimeError,
+    Chef::Exceptions::Env => RuntimeError,
+    Chef::Exceptions::Exec => RuntimeError,
+    Chef::Exceptions::FileNotFound => RuntimeError,
+    Chef::Exceptions::Package => RuntimeError,
+    Chef::Exceptions::Service => RuntimeError,
+    Chef::Exceptions::Route => RuntimeError,
+    Chef::Exceptions::SearchIndex => RuntimeError,
+    Chef::Exceptions::Override => RuntimeError,
+    Chef::Exceptions::UnsupportedAction => RuntimeError,
+    Chef::Exceptions::MissingLibrary => RuntimeError,
+    Chef::Exceptions::MissingRole => RuntimeError,
+    Chef::Exceptions::CannotDetermineNodeName => RuntimeError,
+    Chef::Exceptions::User => RuntimeError,
+    Chef::Exceptions::Group => RuntimeError,
+    Chef::Exceptions::Link => RuntimeError,
+    Chef::Exceptions::Mount => RuntimeError,
+    Chef::Exceptions::PrivateKeyMissing => RuntimeError,
+    Chef::Exceptions::CannotWritePrivateKey => RuntimeError,
+    Chef::Exceptions::RoleNotFound => RuntimeError,
+    Chef::Exceptions::ValidationFailed => ArgumentError,
+    Chef::Exceptions::InvalidPrivateKey => ArgumentError,
+    Chef::Exceptions::ConfigurationError => ArgumentError,
+    Chef::Exceptions::RedirectLimitExceeded => RuntimeError,
+    Chef::Exceptions::AmbiguousRunlistSpecification => ArgumentError,
+    Chef::Exceptions::CookbookNotFound => RuntimeError,
+    Chef::Exceptions::AttributeNotFound => RuntimeError,
+    Chef::Exceptions::InvalidCommandOption => RuntimeError,
+    Chef::Exceptions::CommandTimeout => RuntimeError,
+    Mixlib::ShellOut::ShellCommandFailed => RuntimeError,
+    Chef::Exceptions::RequestedUIDUnavailable => RuntimeError,
+    Chef::Exceptions::InvalidHomeDirectory => ArgumentError,
+    Chef::Exceptions::DsclCommandFailed => RuntimeError,
+    Chef::Exceptions::UserIDNotFound => ArgumentError,
+    Chef::Exceptions::GroupIDNotFound => ArgumentError,
+    Chef::Exceptions::InvalidResourceReference => RuntimeError,
+    Chef::Exceptions::ResourceNotFound => RuntimeError,
+    Chef::Exceptions::InvalidResourceSpecification => ArgumentError,
+    Chef::Exceptions::SolrConnectionError => RuntimeError,
+    Chef::Exceptions::InvalidDataBagPath => ArgumentError,
+    Chef::Exceptions::InvalidEnvironmentPath => ArgumentError,
+    Chef::Exceptions::EnvironmentNotFound => RuntimeError,
+    Chef::Exceptions::InvalidVersionConstraint => ArgumentError,
+    Chef::Exceptions::IllegalVersionConstraint => NotImplementedError
+  }
+
+  exception_to_super_class.each do |exception, expected_super_class|
+    it "should have an exception class of #{exception} which inherits from #{expected_super_class}" do
+      lambda{ raise exception }.should raise_error(expected_super_class)
+    end
+  end
+end
diff --git a/spec/unit/file_access_control_spec.rb b/spec/unit/file_access_control_spec.rb
new file mode 100644
index 0000000..609bd82
--- /dev/null
+++ b/spec/unit/file_access_control_spec.rb
@@ -0,0 +1,302 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'ostruct'
+
+describe Chef::FileAccessControl do
+  describe "Unix" do
+    before do
+      platform_mock :unix do
+        # we have to re-load the file so the proper
+        # platform specific module is mixed in
+        @node = Chef::Node.new
+        load File.join(File.dirname(__FILE__), "..", "..", "lib", "chef", "file_access_control.rb")
+        @resource = Chef::Resource::File.new('/tmp/a_file.txt')
+        @resource.owner('toor')
+        @resource.group('wheel')
+        @resource.mode('0400')
+
+        @events = Chef::EventDispatch::Dispatcher.new
+        @run_context = Chef::RunContext.new(@node, {}, @events)
+        @current_resource = Chef::Resource::File.new('/tmp/different_file.txt')
+        @provider_requirements = Chef::Provider::ResourceRequirements.new(@resource, @run_context)
+        @provider = mock("File provider", :requirements => @provider_requirements, :manage_symlink_access? => false)
+
+        @fac = Chef::FileAccessControl.new(@current_resource, @resource, @provider)
+      end
+    end
+
+    it "has a resource" do
+      @fac.resource.should equal(@resource)
+    end
+
+    it "has a file to manage" do
+      @fac.file.should == '/tmp/different_file.txt'
+    end
+
+    it "is not modified yet" do
+      @fac.should_not be_modified
+    end
+
+    it "determines the uid of the owner specified by the resource" do
+      Etc.should_receive(:getpwnam).with('toor').and_return(OpenStruct.new(:uid => 2342))
+      @fac.target_uid.should == 2342
+    end
+
+    it "raises a Chef::Exceptions::UserIDNotFound error when Etc can't find the user's name" do
+      Etc.should_receive(:getpwnam).with('toor').and_raise(ArgumentError)
+      lambda { @fac.target_uid ; @provider_requirements.run(:create) }.should raise_error(Chef::Exceptions::UserIDNotFound, "cannot determine user id for 'toor', does the user exist on this system?")
+    end
+
+    it "does not attempt to resolve the uid if the user is not specified" do
+      resource = Chef::Resource::File.new("a file")
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.target_uid.should be_nil
+    end
+
+    it "does not want to update the owner if none is specified" do
+      resource = Chef::Resource::File.new("a file")
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.should_update_owner?.should be_false
+    end
+
+    it "raises an ArgumentError if the resource's owner is set to something wack" do
+      @resource.instance_variable_set(:@owner, :diaf)
+      lambda { @fac.target_uid ; @provider_requirements.run(:create) }.should raise_error(ArgumentError)
+    end
+
+    it "uses the resource's uid for the target uid when the resource's owner is specified by an integer" do
+      @resource.owner(2342)
+      @fac.target_uid.should == 2342
+    end
+
+    it "wraps uids to their negative complements to correctly handle negative uids" do
+      # More: Mac OS X (at least) has negative UIDs for 'nobody' and some other
+      # users. Ruby doesn't believe in negative UIDs so you get the diminished radix
+      # complement (i.e., it wraps around the maximum size of C unsigned int) of these
+      # uids. So we have to get ruby and negative uids to smoke the peace pipe
+      # with each other.
+      @resource.owner('nobody')
+      Etc.should_receive(:getpwnam).with('nobody').and_return(OpenStruct.new(:uid => (4294967294)))
+      @fac.target_uid.should == -2
+    end
+
+    it "does not wrap uids to their negative complements beyond -9" do
+      # More: when OSX userIDs are created by ActiveDirectory sync, it tends to use huge numbers
+      #  which had been incorrectly wrapped.  It does not look like the OSX IDs go below -2
+      @resource.owner('bigdude')
+      Etc.should_receive(:getpwnam).with('bigdude').and_return(OpenStruct.new(:uid => (4294967286)))
+      @fac.target_uid.should == 4294967286
+    end
+
+    it "wants to update the owner when the current owner is nil (creating a file)" do
+      @current_resource.owner(nil)
+      @resource.owner(2342)
+      @fac.should_update_owner?.should be_true
+    end
+
+    it "wants to update the owner when the current owner doesn't match desired" do
+      @current_resource.owner(3224)
+      @resource.owner(2342)
+      @fac.should_update_owner?.should be_true
+    end
+
+    it "includes updating ownership in its list of desired changes" do
+      resource = Chef::Resource::File.new("a file")
+      resource.owner(2342)
+      @current_resource.owner(100)
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.describe_changes.should == ["change owner from '100' to '2342'"]
+    end
+
+    it "sets the file's owner as specified in the resource when the current owner is incorrect" do
+      @resource.owner(2342)
+      File.should_receive(:chown).with(2342, nil, '/tmp/different_file.txt')
+      @fac.set_owner
+      @fac.should be_modified
+    end
+
+    it "doesn't set the file's owner if it already matches" do
+      @resource.owner(2342)
+      @current_resource.owner(2342)
+      File.should_not_receive(:chown)
+      @fac.set_owner
+      @fac.should_not be_modified
+    end
+
+    it "doesn't want to update a file's owner when it's already correct" do
+      @resource.owner(2342)
+      @current_resource.owner(2342)
+      @fac.should_update_owner?.should be_false
+    end
+
+    it "determines the gid of the group specified by the resource" do
+      Etc.should_receive(:getgrnam).with('wheel').and_return(OpenStruct.new(:gid => 2342))
+      @fac.target_gid.should == 2342
+    end
+
+    it "uses a user specified gid as the gid" do
+      @resource.group(2342)
+      @fac.target_gid.should == 2342
+    end
+
+    it "raises a Chef::Exceptions::GroupIDNotFound error when Etc can't find the user's name" do
+      Etc.should_receive(:getgrnam).with('wheel').and_raise(ArgumentError)
+      lambda { @fac.target_gid; @provider_requirements.run(:create) }.should raise_error(Chef::Exceptions::GroupIDNotFound, "cannot determine group id for 'wheel', does the group exist on this system?")
+    end
+
+    it "does not attempt to resolve a gid when none is supplied" do
+      resource = Chef::Resource::File.new('crab')
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.target_gid.should be_nil
+    end
+
+    it "does not want to update the group when no target group is specified" do
+      resource = Chef::Resource::File.new('crab')
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.should_update_group?.should be_false
+    end
+
+    it "raises an error when the supplied group name is an alien" do
+      @resource.instance_variable_set(:@group, :failburger)
+      lambda { @fac.target_gid; @provider_requirements.run(:create) }.should raise_error(ArgumentError)
+    end
+
+    it "wants to update the group when the current group is nil (creating a file)" do
+      @resource.group(2342)
+      @current_resource.group(nil)
+      @fac.should_update_group?.should be_true
+    end
+
+    it "wants to update the group when the current group doesn't match the target group" do
+      @resource.group(2342)
+      @current_resource.group(815)
+      @fac.should_update_group?.should be_true
+    end
+
+    it "includes updating the group in the list of changes" do
+      resource = Chef::Resource::File.new('crab')
+      resource.group(2342)
+      @current_resource.group(815)
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.describe_changes.should == ["change group from '815' to '2342'"]
+    end
+
+    it "sets the file's group as specified in the resource when the group is not correct" do
+      @resource.group(2342)
+      @current_resource.group(815)
+
+      File.should_receive(:chown).with(nil, 2342, '/tmp/different_file.txt')
+      @fac.set_group
+      @fac.should be_modified
+    end
+
+    it "doesn't want to modify the file's group when the current group is correct" do
+      @resource.group(2342)
+      @current_resource.group(2342)
+      @fac.should_update_group?.should be_false
+    end
+
+    it "doesnt set the file's group if it is already correct" do
+      @resource.group(2342)
+      @current_resource.group(2342)
+
+      # @fac.stub!(:stat).and_return(OpenStruct.new(:gid => 2342))
+      File.should_not_receive(:chown)
+      @fac.set_group
+      @fac.should_not be_modified
+    end
+
+    it "uses the supplied mode as octal when it's a string" do
+      @resource.mode('444')
+      @fac.target_mode.should == 292 # octal 444 => decimal 292
+    end
+
+    it "uses the supplied mode verbatim when it's an integer" do
+      @resource.mode(00444)
+      @fac.target_mode.should == 292
+    end
+
+    it "does not try to determine the mode when none is given" do
+      resource = Chef::Resource::File.new('blahblah')
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.target_mode.should be_nil
+    end
+
+    it "doesn't want to update the mode when no target mode is given" do
+      resource = Chef::Resource::File.new('blahblah')
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.should_update_mode?.should be_false
+    end
+
+    it "wants to update the mode when the current mode is nil (creating a file)" do
+      @resource.mode("0400")
+      @current_resource.mode(nil)
+      @fac.should_update_mode?.should be_true
+    end
+
+    it "wants to update the mode when the desired mode does not match the current mode" do
+      @resource.mode("0400")
+      @current_resource.mode("0644")
+      @fac.should_update_mode?.should be_true
+    end
+
+    it "includes changing the mode in the list of desired changes" do
+      resource = Chef::Resource::File.new('blahblah')
+      resource.mode("0750")
+      @current_resource.mode("0444")
+      fac = Chef::FileAccessControl.new(@current_resource, resource, @provider)
+      fac.describe_changes.should == ["change mode from '0444' to '0750'"]
+    end
+
+    it "sets the file's mode as specified in the resource when the current modes are incorrect" do
+      # stat returns modes like 0100644 (octal) => 33188 (decimal)
+      #@fac.stub!(:stat).and_return(OpenStruct.new(:mode => 33188))
+      @current_resource.mode("0644")
+      File.should_receive(:chmod).with(256, '/tmp/different_file.txt')
+      @fac.set_mode
+      @fac.should be_modified
+    end
+
+    it "does not want to update the mode when the current mode is correct" do
+      @current_resource.mode("0400")
+      @fac.should_update_mode?.should be_false
+    end
+
+    it "does not set the file's mode when the current modes are correct" do
+      #@fac.stub!(:stat).and_return(OpenStruct.new(:mode => 0100400))
+      @current_resource.mode("0400")
+      File.should_not_receive(:chmod)
+      @fac.set_mode
+      @fac.should_not be_modified
+    end
+
+    it "sets all access controls on a file" do
+      @fac.stub!(:stat).and_return(OpenStruct.new(:owner => 99, :group => 99, :mode => 0100444))
+      @resource.mode(0400)
+      @resource.owner(0)
+      @resource.group(0)
+      File.should_receive(:chmod).with(0400, '/tmp/different_file.txt')
+      File.should_receive(:chown).with(0, nil, '/tmp/different_file.txt')
+      File.should_receive(:chown).with(nil, 0, '/tmp/different_file.txt')
+      @fac.set_all
+      @fac.should be_modified
+    end
+  end
+end
diff --git a/spec/unit/file_cache_spec.rb b/spec/unit/file_cache_spec.rb
new file mode 100644
index 0000000..7680852
--- /dev/null
+++ b/spec/unit/file_cache_spec.rb
@@ -0,0 +1,114 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::FileCache do
+  before do
+    @file_cache_path = Dir.mktmpdir
+    Chef::Config[:file_cache_path] = @file_cache_path
+    @io = StringIO.new
+  end
+
+  after do
+    FileUtils.rm_rf(Chef::Config[:file_cache_path])
+  end
+
+  describe "when the relative path to the cache file doesn't exist" do
+    it "creates intermediate directories as needed" do
+      Chef::FileCache.store("whiz/bang", "I found a poop")
+      File.should exist(File.join(@file_cache_path, 'whiz'))
+    end
+
+    it "creates the cached file at the correct relative path" do
+      File.should_receive(:open).with(File.join(@file_cache_path, 'whiz', 'bang'), "w",416).and_yield(@io)
+      Chef::FileCache.store("whiz/bang", "borkborkbork")
+    end
+
+  end
+
+  describe "when storing a file" do
+    before do
+      File.stub!(:open).and_yield(@io)
+    end
+
+    it "should print the contents to the file" do
+      Chef::FileCache.store("whiz/bang", "borkborkbork")
+      @io.string.should == "borkborkbork"
+    end
+
+  end
+
+  describe "when loading cached files" do
+    it "finds and reads the cached file" do
+      FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+      File.open(File.join(@file_cache_path, 'whiz', 'bang'), 'w') { |f| f.print("borkborkbork") }
+      Chef::FileCache.load('whiz/bang').should == 'borkborkbork'
+    end
+
+    it "should raise a Chef::Exceptions::FileNotFound if the file doesn't exist" do
+      lambda { Chef::FileCache.load('whiz/bang') }.should raise_error(Chef::Exceptions::FileNotFound)
+    end
+  end
+
+  describe "when deleting cached files" do
+    before(:each) do
+      FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+      File.open(File.join(@file_cache_path, 'whiz', 'bang'), 'w') { |f| f.print("borkborkbork") }
+    end
+
+    it "unlinks the file" do
+      Chef::FileCache.delete("whiz/bang")
+      File.should_not exist(File.join(@file_cache_path, 'whiz', 'bang'))
+    end
+
+  end
+
+  describe "when listing files in the cache" do
+    before(:each) do
+      FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+      FileUtils.touch(File.join(@file_cache_path, 'whiz', 'bang'))
+      FileUtils.mkdir_p(File.join(@file_cache_path, 'snappy'))
+      FileUtils.touch(File.join(@file_cache_path, 'snappy', 'patter'))
+    end
+
+    it "should return the relative paths" do
+      Chef::FileCache.list.sort.should == %w{snappy/patter whiz/bang}
+    end
+
+    it "searches for cached files by globbing" do
+      Chef::FileCache.find('snappy/**/*').should == %w{snappy/patter}
+    end
+
+  end
+
+  describe "when checking for the existence of a file" do
+    before do
+      FileUtils.mkdir_p(File.join(@file_cache_path, 'whiz'))
+    end
+
+    it "has a key if the corresponding cache file exists" do
+      FileUtils.touch(File.join(@file_cache_path, 'whiz', 'bang'))
+      Chef::FileCache.should have_key("whiz/bang")
+    end
+
+    it "doesn't have a key if the corresponding cache file doesn't exist" do
+      Chef::FileCache.should_not have_key("whiz/bang")
+    end
+  end
+end
diff --git a/spec/unit/file_content_management/deploy/cp_spec.rb b/spec/unit/file_content_management/deploy/cp_spec.rb
new file mode 100644
index 0000000..dacf392
--- /dev/null
+++ b/spec/unit/file_content_management/deploy/cp_spec.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::FileContentManagement::Deploy::Cp do
+
+  let(:content_deployer) { described_class.new }
+  let(:target_file_path) { "/etc/my_app.conf" }
+
+  describe "creating the file" do
+
+    it "touches the file to create it" do
+      FileUtils.should_receive(:touch).with(target_file_path)
+      content_deployer.create(target_file_path)
+    end
+  end
+
+  describe "updating the file" do
+
+    let(:staging_file_path) { "/tmp/random-dir/staging-file.tmp" }
+
+    it "copies the staging file's content" do
+      FileUtils.should_receive(:cp).with(staging_file_path, target_file_path)
+      content_deployer.deploy(staging_file_path, target_file_path)
+    end
+
+  end
+end
+
+
diff --git a/spec/unit/file_content_management/deploy/mv_unix_spec.rb b/spec/unit/file_content_management/deploy/mv_unix_spec.rb
new file mode 100644
index 0000000..2be0abe
--- /dev/null
+++ b/spec/unit/file_content_management/deploy/mv_unix_spec.rb
@@ -0,0 +1,103 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::FileContentManagement::Deploy::MvUnix do
+
+  let(:content_deployer) { described_class.new }
+  let(:target_file_path) { "/etc/my_app.conf" }
+
+  describe "creating the file" do
+
+    it "touches the file to create it" do
+      FileUtils.should_receive(:touch).with(target_file_path)
+      content_deployer.create(target_file_path)
+    end
+  end
+
+  describe "updating the file" do
+
+    let(:staging_file_path) { "/tmp/random-dir/staging-file.tmp" }
+
+    let(:target_file_mode) { 0644 }
+    let(:target_file_stat) do
+      mock "File::Stat struct for target file",
+           :mode => target_file_mode,
+           :uid => target_file_uid,
+           :gid => target_file_gid
+    end
+
+    before do
+      File.should_receive(:stat).with(target_file_path).and_return(target_file_stat)
+      File.should_receive(:chmod).with(target_file_mode, staging_file_path).and_return(1)
+      FileUtils.should_receive(:mv).with(staging_file_path, target_file_path)
+    end
+
+    # This context represents the case where:
+    # * Chef runs as root
+    # * The owner and group of the target file match the owner and group of the
+    #   staging file.
+    context "when the user has permissions to set file ownership" do
+
+      # For the purposes of this test, the uid/gid can be anything. These
+      # values are just chosen because (assuming chef-client's euid == 1001 and
+      # egid == 1001), the `chown` call is allowed by the OS. See the
+      # description of `EPERM` in `man 2 chown` for reference.
+      let(:target_file_uid) { 1001 }
+      let(:target_file_gid) { 1001 }
+
+      before do
+        File.should_receive(:chown).with(target_file_uid, nil, staging_file_path).and_return(1)
+        File.should_receive(:chown).with(nil, target_file_gid, staging_file_path).and_return(1)
+      end
+
+      it "fixes up permissions and moves the file into place" do
+        content_deployer.deploy(staging_file_path, target_file_path)
+      end
+
+    end
+
+    context "when the user does not have permissions to set file ownership" do
+
+      # The test code does not care what these values are. These values are
+      # chosen because they're representitive of the case that chef-client is
+      # running as non-root and is managing a file that got ownership set to
+      # root somehow. In this example, gid==20 is something like "staff" which
+      # the user running chef-client is a member of (but it's not that user's
+      # primary group).
+      let(:target_file_uid) { 0 }
+      let(:target_file_gid) { 20 }
+
+      before do
+        File.should_receive(:chown).with(target_file_uid, nil, staging_file_path).and_raise(Errno::EPERM)
+        File.should_receive(:chown).with(nil, target_file_gid, staging_file_path).and_raise(Errno::EPERM)
+
+        Chef::Log.should_receive(:warn).with(/^Could not set uid/)
+        Chef::Log.should_receive(:warn).with(/^Could not set gid/)
+      end
+
+      it "fixes up permissions and moves the file into place" do
+        content_deployer.deploy(staging_file_path, target_file_path)
+      end
+    end
+
+  end
+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
new file mode 100644
index 0000000..0a0b1f8
--- /dev/null
+++ b/spec/unit/file_content_management/deploy/mv_windows_spec.rb
@@ -0,0 +1,179 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+unless Chef::Platform.windows?
+  class Chef
+    module ReservedNames
+      module Win32
+        module Security
+          ACL = Object.new
+          SecurableObject = Object.new
+        end
+      end
+    end
+  end
+end
+
+require 'chef/file_content_management/deploy/mv_windows'
+
+describe Chef::FileContentManagement::Deploy::MvWindows do
+
+  let(:content_deployer) { described_class.new }
+  let(:target_file_path) { "/etc/my_app.conf" }
+
+  describe "creating the file" do
+
+    it "touches the file to create it" do
+      FileUtils.should_receive(:touch).with(target_file_path)
+      content_deployer.create(target_file_path)
+    end
+  end
+
+  describe "updating the file" do
+
+    let(:staging_file_path) { "/tmp/random-dir/staging-file.tmp" }
+
+    let(:target_file_security_object) do
+      mock "Securable Object for target file"
+    end
+
+    let(:updated_target_security_object) do
+      mock "Securable Object for target file after staging file deploy"
+    end
+
+    before do
+      Chef::ReservedNames::Win32::Security::SecurableObject.
+        stub(:new).
+        with(target_file_path).
+        and_return(target_file_security_object, updated_target_security_object)
+
+    end
+
+    context "when run without adminstrator privileges" do
+      before do
+        target_file_security_object.should_receive(:security_descriptor).and_raise(Chef::Exceptions::Win32APIError)
+      end
+
+      it "errors out with a WindowsNotAdmin error" do
+        lambda { content_deployer.deploy(staging_file_path, target_file_path)}.should raise_error(Chef::Exceptions::WindowsNotAdmin)
+      end
+
+    end
+
+    context "when run with administrator privileges" do
+
+      let(:original_target_file_owner) { mock("original target file owner") }
+      let(:original_target_file_group) { mock("original target file group") }
+
+      let(:target_file_security_descriptor) do
+        mock "security descriptor for target file",
+             :group => original_target_file_group,
+             :owner => original_target_file_owner
+      end
+
+      let(:updated_target_security_descriptor) do
+        mock "security descriptor for target file"
+      end
+
+
+      before do
+        target_file_security_object.stub(:security_descriptor).and_return(target_file_security_descriptor)
+
+        FileUtils.should_receive(:mv).with(staging_file_path, target_file_path)
+
+        updated_target_security_object.should_receive(:group=).with(original_target_file_group)
+        updated_target_security_object.should_receive(:owner=).with(original_target_file_owner)
+      end
+
+      context "and the target file has no dacl or sacl" do
+
+        before do
+          target_file_security_descriptor.stub(:dacl_present?).and_return(false)
+          target_file_security_descriptor.stub(:sacl_present?).and_return(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) { mock("Windows dacl ace (inherited)", :inherited? => true) }
+        let(:not_inherited_dacl_ace) { mock("Windows dacl ace (not inherited)", :inherited? => false) }
+
+        let(:original_target_file_dacl) { [inherited_dacl_ace, not_inherited_dacl_ace] }
+
+        let(:inherited_sacl_ace) { mock("Windows sacl ace (inherited)", :inherited? => true) }
+        let(:not_inherited_sacl_ace) { mock("Windows sacl ace (not inherited)", :inherited? => false) }
+        let(:original_target_file_sacl) { [inherited_sacl_ace, not_inherited_sacl_ace] }
+
+        let(:custom_dacl) { mock("Windows ACL for non-inherited dacl aces") }
+        let(:custom_sacl) { mock("Windows ACL for non-inherited sacl aces") }
+
+        before do
+          target_file_security_descriptor.stub(:dacl_present?).and_return(true)
+          target_file_security_descriptor.stub(:dacl_inherits?).and_return(dacl_inherits?)
+
+          target_file_security_descriptor.stub(:dacl).and_return(original_target_file_dacl)
+          Chef::ReservedNames::Win32::Security::ACL.
+            should_receive(:create).
+            with([not_inherited_dacl_ace]).
+            and_return(custom_dacl)
+
+          target_file_security_descriptor.stub(:sacl_present?).and_return(true)
+          target_file_security_descriptor.stub(:sacl_inherits?).and_return(sacl_inherits?)
+
+          target_file_security_descriptor.stub(:sacl).and_return(original_target_file_sacl)
+          Chef::ReservedNames::Win32::Security::ACL.
+            should_receive(:create).
+            with([not_inherited_sacl_ace]).
+            and_return(custom_sacl)
+
+          updated_target_security_object.should_receive(:set_dacl).with(custom_dacl, dacl_inherits?)
+          updated_target_security_object.should_receive(:set_sacl).with(custom_sacl, sacl_inherits?)
+        end
+
+        context "and the dacl and sacl don't inherit" do
+          let(:dacl_inherits?) { false }
+          let(:sacl_inherits?) { false }
+
+          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 dacl and sacl inherit" do
+          let(:dacl_inherits?) { true }
+          let(:sacl_inherits?) { true }
+
+          it "fixes up permissions and moves the file into place" do
+            content_deployer.deploy(staging_file_path, target_file_path)
+          end
+        end
+
+      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
new file mode 100644
index 0000000..0b20b0e
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
@@ -0,0 +1,202 @@
+#
+# Author:: Daniel DeLeo (<dan 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'
+
+BAD_RECIPE=<<-E
+#
+# Cookbook Name:: syntax-err
+# Recipe:: default
+#
+# Copyright 2012, YOUR_COMPANY_NAME
+#
+# All rights reserved - Do Not Redistribute
+#
+
+
+file "/tmp/explode-me" do
+  mode 0655
+  owner "root"
+  this_is_not_a_valid_method
+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::Outputter.new(StringIO.new, STDERR)
+    #@outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+  end
+
+  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.
+      Chef::Config.stub!(: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)
+
+      @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'",
+      ]
+      @inspector.filtered_bt.should == @expected_filtered_trace
+    end
+  end
+
+  describe "when explaining an error in the compile phase" do
+    before do
+      Chef::Config.stub!(:cookbook_path).and_return([ "/var/chef/cache/cookbooks" ])
+      recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
+      IO.should_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
+
+    it "finds the line number of the error from the stacktrace" do
+      @inspector.culprit_line.should == 14
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+    end
+  end
+
+  describe "when explaining an error on windows" do
+    before do
+      Chef::Config.stub!(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
+      recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
+      IO.should_receive(:readlines).at_least(1).times.with(/:\/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
+
+
+    describe "and examining the stack trace for a recipe" do
+      it "find the culprit recipe name when the drive letter is upper case" do
+        @inspector.culprit_file.should == "C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb"
+      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)
+        @inspector.culprit_file.should == "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
+      @inspector.culprit_line.should == 14
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+    end
+  end
+
+  describe "when explaining an error on windows, and the backtrace lowercases the drive letter" do
+    before do
+      Chef::Config.stub!(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
+      recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
+      IO.should_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
+
+    it "finds the culprit recipe name from the stacktrace" do
+      @inspector.culprit_file.should == "c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb"
+    end
+
+    it "finds the line number of the error from the stack trace" do
+      @inspector.culprit_line.should == 14
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+    end
+  end
+
+end
diff --git a/spec/unit/formatters/error_inspectors/cookbook_resolve_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/cookbook_resolve_error_inspector_spec.rb
new file mode 100644
index 0000000..93129ea
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/cookbook_resolve_error_inspector_spec.rb
@@ -0,0 +1,129 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector do
+
+  before do
+    @expanded_run_list = Chef::RunList.new("recipe[annoyances]", "recipe[apache2]", "recipe[users]", "recipe[chef::client]")
+
+    @description = Chef::Formatters::ErrorDescription.new("Error Resolving Cookbooks for Run List:")
+    @outputter_output = StringIO.new
+    @outputter = Chef::Formatters::Outputter.new(@outputter_output, STDERR)
+    # @outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+  end
+
+  describe "when explaining a 403 error" do
+    before do
+
+      @response_body = %Q({"error": [{"message": "gtfo"}])
+      @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) forbidden", @response)
+
+      @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      lambda { @description.display(@outputter) }.should_not raise_error
+    end
+
+  end
+
+  describe "when explaining a PreconditionFailed (412) error with current error message style" do
+    # Chef currently returns error messages with some fields as JSON strings,
+    # which must be re-parsed to get the actual data.
+
+    before do
+
+      @response_body = "{\"error\":[\"{\\\"non_existent_cookbooks\\\":[\\\"apache2\\\"],\\\"cookbooks_with_no_versions\\\":[\\\"users\\\"],\\\"message\\\":\\\"Run list contains invalid items: no such cookbook nope.\\\"}\"]}"
+      @response = Net::HTTPPreconditionFailed.new("1.1", "412", "(response) unauthorized")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) precondition failed", @response)
+
+      @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+      @outputter_output.rewind
+      observed_output = @outputter_output.read
+      observed_output.should include("apache2")
+      observed_output.should include("users")
+      observed_output.should_not include("Run list contains invalid items: no such cookbook nope.")
+    end
+
+  end
+
+  describe "when explaining a PreconditionFailed (412) error with current error message style without cookbook details" do
+    # Chef currently returns error messages with some fields as JSON strings,
+    # which must be re-parsed to get the actual data.
+    # In some cases the error message doesn't contain any cookbook
+    # details. But we should still print a pretty error message.
+
+    before do
+
+      @response_body = "{\"error\":[{\"non_existent_cookbooks\":[],\"cookbooks_with_no_versions\":[],\"message\":\"unable to solve dependencies in alotted time.\"}]}"
+      @response = Net::HTTPPreconditionFailed.new("1.1", "412", "(response) unauthorized")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) precondition failed", @response)
+
+      @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+      @outputter_output.rewind
+      @outputter_output.read.should include("unable to solve dependencies in alotted time.")
+    end
+
+  end
+
+  describe "when explaining a PreconditionFailed (412) error with single encoded JSON" do
+    # Chef currently returns error messages with some fields as JSON strings,
+    # which must be re-parsed to get the actual data.
+
+    before do
+
+      @response_body = "{\"error\":[{\"non_existent_cookbooks\":[\"apache2\"],\"cookbooks_with_no_versions\":[\"users\"],\"message\":\"Run list contains invalid items: no such cookbook nope.\"}]}"
+      @response = Net::HTTPPreconditionFailed.new("1.1", "412", "(response) unauthorized")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) precondition failed", @response)
+
+      @inspector = Chef::Formatters::ErrorInspectors::CookbookResolveErrorInspector.new(@expanded_run_list, @exception)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+      @outputter_output.rewind
+      observed_output = @outputter_output.read
+      observed_output.should include("apache2")
+      observed_output.should include("users")
+      observed_output.should_not include("Run list contains invalid items: no such cookbook nope.")
+    end
+
+  end
+end
+
+
+
diff --git a/spec/unit/formatters/error_inspectors/cookbook_sync_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/cookbook_sync_error_inspector_spec.rb
new file mode 100644
index 0000000..eb8d302
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/cookbook_sync_error_inspector_spec.rb
@@ -0,0 +1,43 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Formatters::ErrorInspectors::CookbookSyncErrorInspector do
+  before do
+    @description = Chef::Formatters::ErrorDescription.new("Error Expanding RunList:")
+    @outputter = Chef::Formatters::Outputter.new(StringIO.new, STDERR)
+    #@outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+  end
+
+  describe "when explaining a 502 error" do
+    before do
+      @response_body = "sad trombone orchestra"
+      @response = Net::HTTPBadGateway.new("1.1", "502", "(response) bad gateway")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPFatalError.new("(exception) bad gateway", @response)
+      @inspector = described_class.new({}, @exception)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a nice message" do
+      @description.display(@outputter)
+    end
+
+  end
+end
diff --git a/spec/unit/formatters/error_inspectors/node_load_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/node_load_error_inspector_spec.rb
new file mode 100644
index 0000000..d2bbffa
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/node_load_error_inspector_spec.rb
@@ -0,0 +1,27 @@
+#
+# Author:: Daniel DeLeo (<dan 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'
+
+# spec_helper loads the shared examples already.
+#require 'support/shared/unit/api_error_inspector_spec'
+
+
+describe Chef::Formatters::ErrorInspectors::NodeLoadErrorInspector do
+  it_behaves_like "an api error inspector"
+end
diff --git a/spec/unit/formatters/error_inspectors/registration_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/registration_error_inspector_spec.rb
new file mode 100644
index 0000000..4c21dad
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/registration_error_inspector_spec.rb
@@ -0,0 +1,27 @@
+#
+# Author:: Daniel DeLeo (<dan 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'
+
+# spec_helper loads the shared examples already.
+#require 'support/shared/unit/api_error_inspector_spec'
+
+
+describe Chef::Formatters::ErrorInspectors::RegistrationErrorInspector do
+  it_behaves_like "an api error inspector"
+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
new file mode 100644
index 0000000..fdbb601
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
@@ -0,0 +1,183 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do
+  include Chef::DSL::Recipe
+
+  def run_context
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "ubuntu"
+    node.automatic_attrs[:platform_version] = "10.04"
+    Chef::RunContext.new(node, {}, nil)
+  end
+
+  def cookbook_name
+    "rspec-example"
+  end
+
+  before do
+    @description = Chef::Formatters::ErrorDescription.new("Error Converging Resource:")
+    @stdout = StringIO.new
+    @outputter = Chef::Formatters::Outputter.new(@stdout, STDERR)
+    #@outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+
+    Chef::Config.stub!(:cookbook_path).and_return([ "/var/chef/cache" ])
+  end
+
+  describe "when explaining an error converging a resource" do
+    before do
+      source_line = caller(0)[0]
+      @resource = package("non-existing-package") do
+
+        only_if do
+          true
+        end
+
+        not_if("/bin/false")
+        action :upgrade
+      end
+
+      @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 = Chef::Exceptions::Package.new("No such package 'non-existing-package'")
+      @exception.set_backtrace(@trace)
+      @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+      @inspector.add_explanation(@description)
+    end
+
+    it "filters chef core code from the backtrace" do
+      @expected_filtered_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'",
+      ]
+
+      @inspector.filtered_bt.should == @expected_filtered_trace
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+    end
+
+    describe "and the error is a template error" do
+      before do
+        @description = Chef::Formatters::ErrorDescription.new("Error Converging Resource:")
+        @template_class = Class.new { include Chef::Mixin::Template }
+        @template = @template_class.new
+        @context = Chef::Mixin::Template::TemplateContext.new({})
+        @context[:chef] = "cool"
+
+        @resource = template("/tmp/foo.txt") do
+          mode "0644"
+        end
+
+        @error = begin
+                   @context.render_template_from_string("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno")
+                 rescue  Chef::Mixin::Template::TemplateError => e
+                   e
+                 end
+
+        @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @error)
+        @inspector.add_explanation(@description)
+      end
+
+      it "includes contextual info from the template error in the output" do
+        @description.display(@outputter)
+        @stdout.string.should include(@error.source_listing)
+      end
+
+
+    end
+
+    describe "recipe_snippet" do
+      before do
+        # fake code to run through #recipe_snippet
+        source_file = [ "if true", "var = non_existant", "end" ]
+        IO.stub!(:readlines).and_return(source_file)
+        File.stub!(:exists?).and_return(true)
+      end
+
+      it "parses a Windows path" do
+        source_line = "C:/Users/btm/chef/chef/spec/unit/fake_file.rb:2: undefined local variable or method `non_existant' for main:Object (NameError)"
+        @resource.source_line = source_line
+        @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+        @inspector.recipe_snippet.should match(/^# In C:\/Users\/btm/)
+      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_existant' for main:Object (NameError)"
+        @resource.source_line = source_line
+        @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+        @inspector.recipe_snippet.should match(/^# In \/home\/btm/)
+      end
+
+      context "when the recipe file does not exist" do
+        before do
+          File.stub!(:exists?).and_return(false)
+          IO.stub!(:readlines).and_raise(Errno::ENOENT)
+        end
+
+        it "does not try to parse a recipe in chef-shell/irb (CHEF-3411)" do
+          @resource.source_line = "(irb#1):1:in `irb_binding'"
+          @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+          @inspector.recipe_snippet.should be_nil
+        end
+
+        it "does not raise an exception trying to load a non-existant file (CHEF-3411)" do
+          @resource.source_line = "/somewhere/in/space"
+          @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+          lambda { @inspector.recipe_snippet }.should_not raise_error
+        end
+      end
+    end
+
+    describe "when examining a resource that confuses the parser" do
+      before do
+        angry_bash_recipe = File.expand_path("cookbooks/angrybash/recipes/default.rb", CHEF_SPEC_DATA)
+        source_line = "#{angry_bash_recipe}:1:in `<main>'"
+
+        # source_line = caller(0)[0]; @resource = bash "go off the rails" do
+        #   code <<-END
+        #     for i in localhost 127.0.0.1 #{Socket.gethostname()}
+        #     do
+        #       echo "grant all on *.* to root@'$i' identified by 'a_password'; flush privileges;" | mysql -u root -h 127.0.0.1
+        #     done
+        #    END
+        # end
+        @resource = eval(IO.read(angry_bash_recipe))
+        @resource.source_line = source_line
+        @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+
+        @exception.set_backtrace(@trace)
+        @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+      end
+
+      it "does not generate an error" do
+        lambda { @inspector.add_explanation(@description) }.should_not raise_error(TypeError)
+        @description.display(@outputter)
+      end
+    end
+
+  end
+
+
+end
diff --git a/spec/unit/formatters/error_inspectors/run_list_expansion_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/run_list_expansion_error_inspector_spec.rb
new file mode 100644
index 0000000..706ff59
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/run_list_expansion_error_inspector_spec.rb
@@ -0,0 +1,93 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector do
+  before do
+    @node = Chef::Node.new.tap do |n|
+      n.name("unit-test.example.com")
+      n.run_list("role[base]")
+    end
+
+    @description = Chef::Formatters::ErrorDescription.new("Error Expanding RunList:")
+    @outputter = Chef::Formatters::Outputter.new(StringIO.new, STDERR)
+    #@outputter = Chef::Formatters::Outputter.new(STDOUT, STDERR)
+  end
+
+  describe "when explaining a missing role error" do
+
+    before do
+      @run_list_expansion = Chef::RunList::RunListExpansion.new("_default", @node.run_list)
+      @run_list_expansion.missing_roles_with_including_role << [ "role[missing-role]", "role[base]" ]
+      @run_list_expansion.missing_roles_with_including_role << [ "role[another-missing-role]", "role[base]" ]
+
+      @exception = Chef::Exceptions::MissingRole.new(@run_list_expansion)
+
+
+      @inspector = Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector.new(@node, @exception)
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining an HTTP 403 error" do
+    before do
+
+      @response_body = "forbidden"
+      @response = Net::HTTPForbidden.new("1.1", "403", "(response) forbidden")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) forbidden", @response)
+      @inspector = Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector.new(@node, @exception)
+      @inspector.stub!(:config).and_return(:node_name => "unit-test.example.com")
+
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+    end
+
+  end
+
+  describe "when explaining an HTTP 401 error" do
+    before do
+      @response_body = "check your key and node name"
+      @response = Net::HTTPUnauthorized.new("1.1", "401", "(response) unauthorized")
+      @response.stub!(:body).and_return(@response_body)
+      @exception = Net::HTTPServerException.new("(exception) unauthorized", @response)
+
+      @inspector = Chef::Formatters::ErrorInspectors::RunListExpansionErrorInspector.new(@node, @exception)
+      @inspector.stub!(:config).and_return(:node_name => "unit-test.example.com",
+                                           :client_key => "/etc/chef/client.pem",
+                                           :chef_server_url => "http://chef.example.com")
+
+      @inspector.add_explanation(@description)
+    end
+
+    it "prints a pretty message" do
+      @description.display(@outputter)
+    end
+  end
+
+end
+
diff --git a/spec/unit/handler/json_file_spec.rb b/spec/unit/handler/json_file_spec.rb
new file mode 100644
index 0000000..1f47c40
--- /dev/null
+++ b/spec/unit/handler/json_file_spec.rb
@@ -0,0 +1,64 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Handler::JsonFile do
+  before(:each) do
+    @handler = Chef::Handler::JsonFile.new(:the_sun => "will rise", :path => '/tmp/foobarbazqux')
+  end
+
+  it "accepts arbitrary config options" do
+    @handler.config[:the_sun].should == "will rise"
+  end
+
+  it "creates the directory where the reports will be saved" do
+    FileUtils.should_receive(:mkdir_p).with('/tmp/foobarbazqux')
+    File.should_receive(:chmod).with(00700, '/tmp/foobarbazqux')
+    @handler.build_report_dir
+  end
+
+  describe "when reporting success" do
+    before(:each) do
+      @node = Chef::Node.new
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_status = Chef::RunStatus.new(@node, @events)
+      @expected_time = Time.now
+      Time.stub(:now).and_return(@expected_time, @expected_time + 5)
+      @run_status.start_clock
+      @run_status.stop_clock
+      @run_context = Chef::RunContext.new(@node, {}, @events)
+      @run_status.run_context = @run_context
+      @run_status.exception = Exception.new("Boy howdy!")
+      @file_mock = StringIO.new
+      File.stub!(:open).and_yield(@file_mock)
+    end
+
+
+    it "saves run status data to a file as JSON" do
+      @handler.should_receive(:build_report_dir)
+      @handler.run_report_unsafe(@run_status)
+      reported_data = Chef::JSONCompat.from_json(@file_mock.string)
+      reported_data['exception'].should == "Exception: Boy howdy!"
+      reported_data['start_time'].should == @expected_time.to_s
+      reported_data['end_time'].should == (@expected_time + 5).to_s
+      reported_data['elapsed_time'].should == 5
+    end
+
+  end
+end
diff --git a/spec/unit/handler_spec.rb b/spec/unit/handler_spec.rb
new file mode 100644
index 0000000..eab98e7
--- /dev/null
+++ b/spec/unit/handler_spec.rb
@@ -0,0 +1,216 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Handler do
+  before(:each) do
+    @handler = Chef::Handler.new
+
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_status = Chef::RunStatus.new(@node, @events)
+
+    @handler.instance_variable_set(:@run_status, @run_status)
+  end
+
+  describe "when accessing the run status" do
+    before do
+      @backtrace = caller
+      @exception = Exception.new("epic_fail")
+      @exception.set_backtrace(@backtrace)
+      @run_status.exception = @exception
+      @run_context = Chef::RunContext.new(@node, {}, @events)
+      @all_resources = [Chef::Resource::Cat.new('lolz'), Chef::Resource::ZenMaster.new('tzu')]
+      @all_resources.first.updated = true
+      @run_context.resource_collection.all_resources.replace(@all_resources)
+      @run_status.run_context = @run_context
+      @start_time = Time.now
+      @end_time = @start_time + 4.2
+      Time.stub!(:now).and_return(@start_time, @end_time)
+      @run_status.start_clock
+      @run_status.stop_clock
+    end
+
+    it "has a shortcut for the exception" do
+      @handler.exception.should == @exception
+    end
+
+    it "has a shortcut for the backtrace" do
+      @handler.backtrace.should == @backtrace
+    end
+
+    it "has a shortcut for all resources" do
+      @handler.all_resources.should == @all_resources
+    end
+
+    it "has a shortcut for just the updated resources" do
+      @handler.updated_resources.should == [@all_resources.first]
+    end
+
+    it "has a shortcut for the start time" do
+      @handler.start_time.should == @start_time
+    end
+
+    it "has a shortcut for the end time" do
+      @handler.end_time.should == @end_time
+    end
+
+    it "has a shortcut for the elapsed time" do
+      @handler.elapsed_time.should == 4.2
+    end
+
+    it "has a shortcut for the node" do
+      @handler.node.should == @node
+    end
+
+    it "has a shortcut for the run context" do
+      @handler.run_context.should == @run_context
+    end
+
+    it "has a shortcut for the success? and failed? predicates" do
+      @handler.success?.should be_false # becuase there's an exception
+      @handler.failed?.should be_true
+    end
+
+    it "has a shortcut to the hash representation of the run status" do
+      @handler.data.should == @run_status.to_hash
+    end
+  end
+
+  describe "when running the report" do
+    it "does not fail if the report handler raises an exception" do
+      $report_ran = false
+      def @handler.report
+        $report_ran = true
+        raise Exception, "I died the deth"
+      end
+      lambda {@handler.run_report_safely(@run_status)}.should_not raise_error
+      $report_ran.should be_true
+    end
+    it "does not fail if the report handler does not raise an exception" do
+      $report_ran = false
+      def @handler.report
+        $report_ran = true
+        puts "I'm AOK here."
+      end
+      lambda {@handler.run_report_safely(@run_status)}.should_not raise_error
+      $report_ran.should be_true
+    end
+  end
+
+  # Hmm, no tests for report handlers, looks like
+  describe "when running a report handler" do
+    before do
+      @run_context = Chef::RunContext.new(@node, {}, @events)
+      @all_resources = [Chef::Resource::Cat.new('foo'), Chef::Resource::ZenMaster.new('moo')]
+      @all_resources.first.updated = true
+      @run_context.resource_collection.all_resources.replace(@all_resources)
+      @run_status.run_context = @run_context
+      @start_time = Time.now
+      @end_time = @start_time + 4.2
+      Time.stub!(:now).and_return(@start_time, @end_time)
+      @run_status.start_clock
+      @run_status.stop_clock
+    end
+
+    it "has a shortcut for all resources" do
+      @handler.all_resources.should == @all_resources
+    end
+
+    it "has a shortcut for just the updated resources" do
+      @handler.updated_resources.should == [@all_resources.first]
+    end
+
+    it "has a shortcut for the start time" do
+      @handler.start_time.should == @start_time
+    end
+
+    it "has a shortcut for the end time" do
+      @handler.end_time.should == @end_time
+    end
+
+    it "has a shortcut for the elapsed time" do
+      @handler.elapsed_time.should == 4.2
+    end
+
+    it "has a shortcut for the node" do
+      @handler.node.should == @node
+    end
+
+    it "has a shortcut for the run context" do
+      @handler.run_context.should == @run_context
+    end
+
+    it "has a shortcut for the success? and failed? predicates" do
+      @handler.success?.should be_true
+      @handler.failed?.should be_false
+    end
+
+    it "has a shortcut to the hash representation of the run status" do
+      @handler.data.should == @run_status.to_hash
+    end
+  end
+
+  # and this would test the start handler
+  describe "when running a start handler" do
+    before do
+      @start_time = Time.now
+      Time.stub!(:now).and_return(@start_time)
+      @run_status.start_clock
+    end
+
+    it "should not have all resources" do
+      @handler.all_resources.should be_false
+    end
+
+    it "should not have updated resources" do
+      @handler.updated_resources.should be_false
+    end
+
+    it "has a shortcut for the start time" do
+      @handler.start_time.should == @start_time
+    end
+
+    it "does not have a shortcut for the end time" do
+      @handler.end_time.should be_false
+    end
+
+    it "does not have a shortcut for the elapsed time" do
+      @handler.elapsed_time.should be_false
+    end
+
+    it "has a shortcut for the node" do
+      @handler.node.should == @node
+    end
+
+    it "does not have a shortcut for the run context" do
+      @handler.run_context.should be_false
+    end
+
+    it "has a shortcut for the success? and failed? predicates" do
+      @handler.success?.should be_true # for some reason this is true
+      @handler.failed?.should be_false
+    end
+
+    it "has a shortcut to the hash representation of the run status" do
+      @handler.data.should == @run_status.to_hash
+    end
+  end
+
+end
diff --git a/spec/unit/http/ssl_policies_spec.rb b/spec/unit/http/ssl_policies_spec.rb
new file mode 100644
index 0000000..b95e13a
--- /dev/null
+++ b/spec/unit/http/ssl_policies_spec.rb
@@ -0,0 +1,170 @@
+#--
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2009, 2010, 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/http/ssl_policies'
+
+describe "HTTP SSL Policy" do
+
+  before do
+    Chef::Config[:ssl_client_cert] = nil
+    Chef::Config[:ssl_client_key]  = nil
+    Chef::Config[:ssl_ca_path]     = nil
+    Chef::Config[:ssl_ca_file]     = nil
+  end
+
+  let(:unconfigured_http_client) { Net::HTTP.new("example.com", 443) }
+  let(:http_client) do
+    unconfigured_http_client.use_ssl = true
+    ssl_policy.apply
+    unconfigured_http_client
+  end
+
+  describe Chef::HTTP::DefaultSSLPolicy do
+
+    let(:ssl_policy) { Chef::HTTP::DefaultSSLPolicy.new(unconfigured_http_client) }
+
+    describe "when configured with :ssl_verify_mode set to :verify peer" do
+      before do
+        Chef::Config[:ssl_verify_mode] = :verify_peer
+      end
+
+      it "configures the HTTP client to use SSL when given a URL with the https protocol" do
+        http_client.use_ssl?.should be_true
+      end
+
+      it "sets the OpenSSL verify mode to verify_peer" do
+        http_client.verify_mode.should == OpenSSL::SSL::VERIFY_PEER
+      end
+
+      it "raises a ConfigurationError if :ssl_ca_path is set to a path that doesn't exist" do
+        Chef::Config[:ssl_ca_path] = "/dev/null/nothing_here"
+        lambda {http_client}.should raise_error(Chef::Exceptions::ConfigurationError)
+      end
+
+      it "should set the CA path if that is set in the configuration" do
+        Chef::Config[:ssl_ca_path] = File.join(CHEF_SPEC_DATA, "ssl")
+        http_client.ca_path.should == File.join(CHEF_SPEC_DATA, "ssl")
+      end
+
+      it "raises a ConfigurationError if :ssl_ca_file is set to a file that does not exist" do
+        Chef::Config[:ssl_ca_file] = "/dev/null/nothing_here"
+        lambda {http_client}.should raise_error(Chef::Exceptions::ConfigurationError)
+      end
+
+      it "should set the CA file if that is set in the configuration" do
+        Chef::Config[:ssl_ca_file] = CHEF_SPEC_DATA + '/ssl/5e707473.0'
+        http_client.ca_file.should == CHEF_SPEC_DATA + '/ssl/5e707473.0'
+      end
+    end
+
+    describe "when configured with :ssl_verify_mode set to :verify peer" do
+      before do
+        @url = URI.parse("https://chef.example.com:4443/")
+        Chef::Config[:ssl_verify_mode] = :verify_none
+      end
+
+      it "sets the OpenSSL verify mode to :verify_none" do
+        http_client.verify_mode.should == OpenSSL::SSL::VERIFY_NONE
+      end
+    end
+
+    describe "when configured with a client certificate" do
+      before {@url = URI.parse("https://chef.example.com:4443/")}
+
+      it "raises ConfigurationError if the certificate file doesn't exist" do
+        Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here"
+        Chef::Config[:ssl_client_key]  = CHEF_SPEC_DATA + '/ssl/chef-rspec.key'
+        lambda {http_client}.should raise_error(Chef::Exceptions::ConfigurationError)
+      end
+
+      it "raises ConfigurationError if the certificate file doesn't exist" do
+        Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + '/ssl/chef-rspec.cert'
+        Chef::Config[:ssl_client_key]  = "/dev/null/nothing_here"
+        lambda {http_client}.should raise_error(Chef::Exceptions::ConfigurationError)
+      end
+
+      it "raises a ConfigurationError if one of :ssl_client_cert and :ssl_client_key is set but not both" do
+        Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here"
+        Chef::Config[:ssl_client_key]  = nil
+        lambda {http_client}.should raise_error(Chef::Exceptions::ConfigurationError)
+      end
+
+      it "configures the HTTP client's cert and private key" do
+        Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + '/ssl/chef-rspec.cert'
+        Chef::Config[:ssl_client_key]  = CHEF_SPEC_DATA + '/ssl/chef-rspec.key'
+        http_client.cert.to_s.should == OpenSSL::X509::Certificate.new(IO.read(CHEF_SPEC_DATA + '/ssl/chef-rspec.cert')).to_s
+        http_client.key.to_s.should  == IO.read(CHEF_SPEC_DATA + '/ssl/chef-rspec.key')
+      end
+    end
+
+    context "when additional certs are located in the trusted_certs dir" do
+      let(:self_signed_crt_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "example.crt") }
+      let(:self_signed_crt) { OpenSSL::X509::Certificate.new(File.read(self_signed_crt_path)) }
+
+      let(:additional_pem_path) { File.join(CHEF_SPEC_DATA, "trusted_certs", "opscode.pem") }
+      let(:additional_pem) { OpenSSL::X509::Certificate.new(File.read(additional_pem_path)) }
+
+      before do
+        Chef::Config.trusted_certs_dir = File.join(CHEF_SPEC_DATA, "trusted_certs")
+      end
+
+      it "enables verification of self-signed certificates" do
+        http_client.cert_store.verify(self_signed_crt).should be_true
+      end
+
+      it "enables verification of cert chains" do
+        # This cert is signed by DigiCert so it would be valid in normal SSL usage.
+        # The chain goes:
+        # trusted root -> intermediate -> opscode.pem
+        # In this test, the intermediate has to be loaded and trusted in order
+        # for verification to work correctly.
+        # If the machine running the test doesn't have ruby SSL configured correctly,
+        # then the root cert also has to be loaded for the test to succeed.
+        # The system under test **SHOULD** do both of these things.
+        http_client.cert_store.verify(additional_pem).should be_true
+      end
+
+      context "and some certs are duplicates" do
+        it "skips duplicate certs" do
+          # For whatever reason, OpenSSL errors out when adding a
+          # cert you already have to the certificate store.
+          ssl_policy.set_custom_certs
+          ssl_policy.set_custom_certs #should not raise an error
+        end
+      end
+    end
+  end
+
+  describe Chef::HTTP::APISSLPolicy do
+
+    let(:ssl_policy) { Chef::HTTP::APISSLPolicy.new(unconfigured_http_client) }
+
+    context "when verify_api_cert is set" do
+      before do
+        Chef::Config[:verify_api_cert] = true
+      end
+
+      it "sets the OpenSSL verify mode to verify_peer" do
+        http_client.verify_mode.should == OpenSSL::SSL::VERIFY_PEER
+      end
+    end
+
+  end
+end
+
diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb
new file mode 100644
index 0000000..cce31b0
--- /dev/null
+++ b/spec/unit/json_compat_spec.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Juanje Ojeda (<juanje.ojeda at gmail.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 File.expand_path('../../spec_helper', __FILE__)
+require 'chef/json_compat'
+
+describe Chef::JSONCompat do
+
+  describe "with JSON containing an existing class" do
+    let(:json){'{"json_class": "Chef::Role"}'}
+    it "returns an instance of the class instead of a Hash" do
+      Chef::JSONCompat.from_json(json).class.should eq Chef::Role
+    end
+  end
+
+  describe 'with JSON containing "Chef::Sandbox" as a json_class value' do
+    require 'chef/sandbox' # Only needed for this test
+    let(:json){'{"json_class": "Chef::Sandbox", "arbitrary": "data"}'}
+    it "returns a Hash, because Chef::Sandbox is a dummy class" do
+      Chef::JSONCompat.from_json(json).should eq({"json_class" => "Chef::Sandbox", "arbitrary" => "data"})
+    end
+  end
+
+  describe "with a file with 300 or less nested entries" do
+    before(:all) do
+      @json = IO.read(File.join(CHEF_SPEC_DATA, 'big_json.json'))
+      @hash = Chef::JSONCompat.from_json(@json)
+    end
+
+    describe "when a big json file is loaded" do
+      it "should create a Hash from the file" do
+        @hash.should be_kind_of(Hash)
+      end
+      it "should has 'test' as a 300th nested value" do
+        @hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['k [...]
+      end
+    end
+  end
+  describe "with a file with more than 300 nested entries" do
+    before(:all) do
+      @json = IO.read(File.join(CHEF_SPEC_DATA, 'big_json_plus_one.json'))
+      @hash = Chef::JSONCompat.from_json(@json, {:max_nesting => 301})
+    end
+
+    describe "when a big json file is loaded" do
+      it "should create a Hash from the file" do
+        @hash.should be_kind_of(Hash)
+      end
+      it "should has 'test' as a 301st nested value" do
+        @hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['k [...]
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
new file mode 100644
index 0000000..cc0336d
--- /dev/null
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -0,0 +1,383 @@
+#
+# Author:: Ian Meyer (<ianmmeyer at gmail.com>)
+# Copyright:: Copyright (c) 2010 Ian Meyer
+# 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'
+
+Chef::Knife::Bootstrap.load_deps
+require 'net/ssh'
+
+describe Chef::Knife::Bootstrap do
+  before(:each) do
+    Chef::Log.logger = Logger.new(StringIO.new)
+    @knife = Chef::Knife::Bootstrap.new
+    # Merge default settings in.
+    @knife.merge_configs
+    @knife.config[:template_file] = File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb"))
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @stderr = StringIO.new
+    @knife.ui.stub!(:stderr).and_return(@stderr)
+  end
+
+  it "should return a name of default bootstrap template" do
+    @knife.find_template.should be_a_kind_of(String)
+  end
+
+  it "should error if template can not be found" do
+    @knife.config[:template_file] = false
+    @knife.config[:distro] = 'penultimate'
+    lambda { @knife.find_template }.should raise_error
+  end
+
+  it "should look for templates early in the run" do
+    File.stub(:exists?).and_return(true)
+    @knife.name_args = ['shatner']
+    @knife.stub!(:read_template).and_return("")
+    @knife.stub!(:knife_ssh).and_return(true)
+    @knife_ssh = @knife.knife_ssh
+    @knife.should_receive(:find_template).ordered
+    @knife.should_receive(:knife_ssh).ordered
+    @knife_ssh.should_receive(:run) # rspec appears to keep order per object
+    @knife.run
+  end
+
+  it "should load the specified template" do
+    @knife.config[:distro] = 'fedora13-gems'
+    lambda { @knife.find_template }.should_not raise_error
+  end
+
+  it "should load the specified template from a Ruby gem" do
+    @knife.config[:template_file] = false
+    Gem.stub(:find_files).and_return(["/Users/schisamo/.rvm/gems/ruby-1.9.2-p180 at chef-0.10/gems/knife-windows-0.5.4/lib/chef/knife/bootstrap/fake-bootstrap-template.erb"])
+    File.stub(:exists?).and_return(true)
+    IO.stub(:read).and_return('random content')
+    @knife.config[:distro] = 'fake-bootstrap-template'
+    lambda { @knife.find_template }.should_not raise_error
+  end
+
+  it "should return an empty run_list" do
+    @knife.instance_variable_set("@template_file", @knife.config[:template_file])
+    template_string = @knife.read_template
+    @knife.render_template(template_string).should == '{"run_list":[]}'
+  end
+
+  it "should have role[base] in the run_list" do
+    @knife.instance_variable_set("@template_file", @knife.config[:template_file])
+    template_string = @knife.read_template
+    @knife.parse_options(["-r","role[base]"])
+    @knife.render_template(template_string).should == '{"run_list":["role[base]"]}'
+  end
+
+  it "should have role[base] and recipe[cupcakes] in the run_list" do
+    @knife.instance_variable_set("@template_file", @knife.config[:template_file])
+    template_string = @knife.read_template
+    @knife.parse_options(["-r", "role[base],recipe[cupcakes]"])
+    @knife.render_template(template_string).should == '{"run_list":["role[base]","recipe[cupcakes]"]}'
+  end
+
+  it "should have foo => {bar => baz} in the first_boot" do
+    @knife.instance_variable_set("@template_file", @knife.config[:template_file])
+    template_string = @knife.read_template
+    @knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
+    expected_hash = Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
+    actual_hash = Yajl::Parser.new.parse(@knife.render_template(template_string))
+    actual_hash.should == expected_hash
+  end
+
+  it "should create a hint file when told to" do
+    @knife.config[:template_file] = File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb"))
+    @knife.instance_variable_set("@template_file", @knife.config[:template_file])
+    template_string = @knife.read_template
+    @knife.parse_options(["--hint", "openstack"])
+    @knife.render_template(template_string).should match /\/etc\/chef\/ohai\/hints\/openstack.json/
+  end
+
+  it "should populate a hint file with JSON when given a file to read" do
+    @knife.stub(:find_template).and_return(true)
+    @knife.config[:template_file] = File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb"))
+    ::File.stub!(:read).and_return('{ "foo" : "bar" }')
+    @knife.instance_variable_set("@template_file", @knife.config[:template_file])
+    template_string = @knife.read_template
+    @knife.stub!(:read_template).and_return('{ "foo" : "bar" }')
+    @knife.parse_options(["--hint", "openstack=hints/openstack.json"])
+    @knife.render_template(template_string).should match /\{\"foo\":\"bar\"\}/
+  end
+
+  it "should take the node name from ARGV" do
+    @knife.name_args = ['barf']
+    @knife.name_args.first.should == "barf"
+  end
+
+  describe "specifying no_proxy with various entries" do
+    subject(:knife) { described_class.new }
+    let(:options){ ["--bootstrap-no-proxy", setting] }
+    let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
+    let(:rendered_template) do
+      knife.instance_variable_set("@template_file", template_file)
+      knife.parse_options(options)
+      template_string = knife.read_template
+      knife.render_template(template_string)
+    end
+
+    context "via --bootstrap-no-proxy" do
+      let(:setting) { "api.opscode.com" }
+
+      it "renders the client.rb with a single FQDN no_proxy entry" do
+        rendered_template.should match(%r{.*no_proxy\s*"api.opscode.com".*})
+      end
+    end
+
+    context "via --bootstrap-no-proxy multiple" do
+      let(:setting) { "api.opscode.com,172.16.10.*" }
+
+      it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
+        rendered_template.should match(%r{.*no_proxy\s*"api.opscode.com,172.16.10.\*".*})
+      end
+    end
+  end
+
+  describe "specifying the encrypted data bag secret key" do
+    subject(:knife) { described_class.new }
+    let(:secret) { "supersekret" }
+    let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') }
+    let(:options) { [] }
+    let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) }
+    let(:rendered_template) do
+      knife.instance_variable_set("@template_file", template_file)
+      knife.parse_options(options)
+      template_string = knife.read_template
+      knife.render_template(template_string)
+    end
+
+    context "via --secret" do
+      let(:options){ ["--secret", secret] }
+
+      it "creates a secret file" do
+        rendered_template.should match(%r{#{secret}})
+      end
+
+      it "renders the client.rb with an encrypted_data_bag_secret entry" do
+        rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
+      end
+    end
+
+    context "via --secret-file" do
+      let(:options) { ["--secret-file", secret_file] }
+      let(:secret) { IO.read(secret_file) }
+
+      it "creates a secret file" do
+        rendered_template.should match(%r{#{secret}})
+      end
+
+      it "renders the client.rb with an encrypted_data_bag_secret entry" do
+        rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
+      end
+    end
+
+    context "via Chef::Config[:encrypted_data_bag_secret]" do
+      before(:each) { Chef::Config[:encrypted_data_bag_secret] = secret_file }
+      let(:secret) { IO.read(secret_file) }
+
+      it "creates a secret file" do
+        rendered_template.should match(%r{#{secret}})
+      end
+
+      it "renders the client.rb with an encrypted_data_bag_secret entry" do
+        rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
+      end
+    end
+  end
+
+  describe "when configuring the underlying knife ssh command" do
+    context "from the command line" do
+      before do
+        @knife.name_args = ["foo.example.com"]
+        @knife.config[:ssh_user]      = "rooty"
+        @knife.config[:ssh_port]      = "4001"
+        @knife.config[:ssh_password]  = "open_sesame"
+        Chef::Config[:knife][:ssh_user] = nil
+        Chef::Config[:knife][:ssh_port] = nil
+        @knife.config[:forward_agent] = true
+        @knife.config[:identity_file] = "~/.ssh/me.rsa"
+        @knife.stub!(:read_template).and_return("")
+        @knife_ssh = @knife.knife_ssh
+      end
+
+      it "configures the hostname" do
+        @knife_ssh.name_args.first.should == "foo.example.com"
+      end
+
+      it "configures the ssh user" do
+        @knife_ssh.config[:ssh_user].should == 'rooty'
+      end
+
+      it "configures the ssh password" do
+        @knife_ssh.config[:ssh_password].should == 'open_sesame'
+      end
+
+      it "configures the ssh port" do
+        @knife_ssh.config[:ssh_port].should == '4001'
+      end
+
+      it "configures the ssh agent forwarding" do
+        @knife_ssh.config[:forward_agent].should == true
+      end
+
+      it "configures the ssh identity file" do
+        @knife_ssh.config[:identity_file].should == '~/.ssh/me.rsa'
+      end
+    end
+    context "validating use_sudo_password" do
+      before do
+        @knife.config[:distro] = "ubuntu"
+        @knife.config[:ssh_password] = "password"
+        @knife.stub(:read_template).and_return(IO.read(@knife.find_template).chomp)
+      end
+
+      it "use_sudo_password contains description and long params for help" do
+        @knife.options.should have_key(:use_sudo_password) \
+          and @knife.options[:use_sudo_password][:description].to_s.should_not == ''\
+          and @knife.options[:use_sudo_password][:long].to_s.should_not == ''
+      end
+
+      it "uses the password from --ssh-password for sudo when --use-sudo-password is set" do
+        @knife.config[:use_sudo] = true
+        @knife.config[:use_sudo_password] = true
+        @knife.ssh_command.should include("echo \'#{@knife.config[:ssh_password]}\' | sudo -S")
+      end
+
+      it "should not honor --use-sudo-password when --use-sudo is not set" do
+        @knife.config[:use_sudo] = false
+        @knife.config[:use_sudo_password] = true
+        @knife.ssh_command.should_not include("echo #{@knife.config[:ssh_password]} | sudo -S")
+      end
+    end
+    context "from the knife config file" do
+      before do
+        @knife.name_args = ["config.example.com"]
+        @knife.config[:ssh_user] = nil
+        @knife.config[:ssh_port] = nil
+        @knife.config[:ssh_gateway] = nil
+        @knife.config[:forward_agent] = nil
+        @knife.config[:identity_file] = nil
+        @knife.config[:host_key_verify] = nil
+        Chef::Config[:knife][:ssh_user] = "curiosity"
+        Chef::Config[:knife][:ssh_port] = "2430"
+        Chef::Config[:knife][:forward_agent] = true
+        Chef::Config[:knife][:identity_file] = "~/.ssh/you.rsa"
+        Chef::Config[:knife][:ssh_gateway] = "towel.blinkenlights.nl"
+        Chef::Config[:knife][:host_key_verify] = true
+        @knife.stub!(:read_template).and_return("")
+        @knife_ssh = @knife.knife_ssh
+      end
+
+      it "configures the ssh user" do
+        @knife_ssh.config[:ssh_user].should == 'curiosity'
+      end
+
+      it "configures the ssh port" do
+        @knife_ssh.config[:ssh_port].should == '2430'
+      end
+
+      it "configures the ssh agent forwarding" do
+        @knife_ssh.config[:forward_agent].should == true
+      end
+
+      it "configures the ssh identity file" do
+        @knife_ssh.config[:identity_file].should == '~/.ssh/you.rsa'
+      end
+
+      it "configures the ssh gateway" do
+        @knife_ssh.config[:ssh_gateway].should == 'towel.blinkenlights.nl'
+      end
+
+      it "configures the host key verify mode" do
+        @knife_ssh.config[:host_key_verify].should == true
+      end
+    end
+
+    describe "when falling back to password auth when host key auth fails" do
+      before do
+        @knife.name_args = ["foo.example.com"]
+        @knife.config[:ssh_user]      = "rooty"
+        @knife.config[:identity_file] = "~/.ssh/me.rsa"
+        @knife.stub!(:read_template).and_return("")
+        @knife_ssh = @knife.knife_ssh
+      end
+
+      it "prompts the user for a password " do
+        @knife.stub!(:knife_ssh).and_return(@knife_ssh)
+        @knife_ssh.stub!(:get_password).and_return('typed_in_password')
+        alternate_knife_ssh = @knife.knife_ssh_with_password_auth
+        alternate_knife_ssh.config[:ssh_password].should == 'typed_in_password'
+      end
+
+      it "configures knife not to use the identity file that didn't work previously" do
+        @knife.stub!(:knife_ssh).and_return(@knife_ssh)
+        @knife_ssh.stub!(:get_password).and_return('typed_in_password')
+        alternate_knife_ssh = @knife.knife_ssh_with_password_auth
+        alternate_knife_ssh.config[:identity_file].should be_nil
+      end
+    end
+  end
+
+  describe "when running the bootstrap" do
+    before do
+      @knife.name_args = ["foo.example.com"]
+      @knife.config[:ssh_user]      = "rooty"
+      @knife.config[:identity_file] = "~/.ssh/me.rsa"
+      @knife.stub!(:read_template).and_return("")
+      @knife_ssh = @knife.knife_ssh
+      @knife.stub!(:knife_ssh).and_return(@knife_ssh)
+    end
+
+    it "verifies that a server to bootstrap was given as a command line arg" do
+      @knife.name_args = nil
+      lambda { @knife.run }.should raise_error(SystemExit)
+      @stderr.string.should match /ERROR:.+FQDN or ip/
+    end
+
+    it "configures the underlying ssh command and then runs it" do
+      @knife_ssh.should_receive(:run)
+      @knife.run
+    end
+
+    it "falls back to password based auth when auth fails the first time" do
+      @knife.stub!(:puts)
+
+      @fallback_knife_ssh = @knife_ssh.dup
+      @knife_ssh.should_receive(:run).and_raise(Net::SSH::AuthenticationFailed.new("no ssh for you"))
+      @knife.stub!(:knife_ssh_with_password_auth).and_return(@fallback_knife_ssh)
+      @fallback_knife_ssh.should_receive(:run)
+      @knife.run
+    end
+
+    context "Chef::Config[:encrypted_data_bag_secret] is set" do
+      let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') }
+      before { Chef::Config[:encrypted_data_bag_secret] = secret_file }
+
+      it "warns the configuration option is deprecated" do
+        @knife_ssh.should_receive(:run)
+        @knife.ui.should_receive(:warn).at_least(3).times
+        @knife.run
+      end
+    end
+
+  end
+
+end
diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb
new file mode 100644
index 0000000..b7864ed
--- /dev/null
+++ b/spec/unit/knife/client_bulk_delete_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Stephen Delano (<stephen at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::ClientBulkDelete do
+  before(:each) do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::ClientBulkDelete.new
+    @knife.name_args = ["."]
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @knife.ui.stub!(:confirm).and_return(true)
+    @clients = Hash.new
+    %w{tim dan stephen}.each do |client_name|
+      client = Chef::ApiClient.new()
+      client.name(client_name)
+      client.stub!(:destroy).and_return(true)
+      @clients[client_name] = client
+    end
+    Chef::ApiClient.stub!(:list).and_return(@clients)
+  end
+
+  describe "run" do
+
+    it "should get the list of the clients" do
+      Chef::ApiClient.should_receive(:list).and_return(@clients)
+      @knife.run
+    end
+
+    it "should print the clients you are about to delete" do
+      @knife.run
+      @stdout.string.should match(/#{@knife.ui.list(@clients.keys.sort, :columns_down)}/)
+    end
+
+    it "should confirm you really want to delete them" do
+      @knife.ui.should_receive(:confirm)
+      @knife.run
+    end
+
+    it "should delete each client" do
+      @clients.each_value do |c|
+        c.should_receive(:destroy)
+      end
+      @knife.run
+    end
+
+    it "should only delete clients that match the regex" do
+      @knife.name_args = ["tim"]
+      @clients["tim"].should_receive(:destroy)
+      @clients["stephen"].should_not_receive(:destroy)
+      @clients["dan"].should_not_receive(:destroy)
+      @knife.run
+    end
+
+    it "should exit if the regex is not provided" do
+      @knife.name_args = []
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+  end
+end
diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb
new file mode 100644
index 0000000..d07b6ce
--- /dev/null
+++ b/spec/unit/knife/client_create_spec.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+Chef::Knife::ClientCreate.load_deps
+
+describe Chef::Knife::ClientCreate do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::ClientCreate.new
+    @knife.config = {
+      :file => nil
+    }
+    @knife.name_args = [ "adam" ]
+    @client = Chef::ApiClient.new
+    @client.stub!(:save).and_return({ 'private_key' => '' })
+    @knife.stub!(:edit_data).and_return(@client)
+    @knife.stub!(:puts)
+    Chef::ApiClient.stub!(:new).and_return(@client)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "should create a new Client" do
+      Chef::ApiClient.should_receive(:new).and_return(@client)
+      @knife.run
+      @stdout.string.should match /created client.+adam/i
+    end
+
+    it "should set the Client name" do
+      @client.should_receive(:name).with("adam")
+      @knife.run
+    end
+
+    it "should allow you to edit the data" do
+      @knife.should_receive(:edit_data).with(@client)
+      @knife.run
+    end
+
+    it "should save the Client" do
+      @client.should_receive(:save)
+      @knife.run
+    end
+
+    describe "with -f or --file" do
+      it "should write the private key to a file" do
+        @knife.config[:file] = "/tmp/monkeypants"
+        @client.stub!(:save).and_return({ 'private_key' => "woot" })
+        filehandle = mock("Filehandle")
+        filehandle.should_receive(:print).with('woot')
+        File.should_receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+        @knife.run
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb
new file mode 100644
index 0000000..9ebccba
--- /dev/null
+++ b/spec/unit/knife/client_delete_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::ClientDelete do
+  before(:each) do
+    @knife = Chef::Knife::ClientDelete.new
+    @knife.name_args = [ 'adam' ]
+  end
+
+  describe 'run' do
+    it 'should delete the client' do
+      @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam')
+      @knife.run
+    end
+
+    it 'should print usage and exit when a client name is not provided' do
+      @knife.name_args = []
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+  end
+end
diff --git a/spec/unit/knife/client_edit_spec.rb b/spec/unit/knife/client_edit_spec.rb
new file mode 100644
index 0000000..1d7049b
--- /dev/null
+++ b/spec/unit/knife/client_edit_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::ClientEdit do
+  before(:each) do
+    @knife = Chef::Knife::ClientEdit.new
+    @knife.name_args = [ 'adam' ]
+  end
+
+  describe 'run' do
+    it 'should edit the client' do
+      @knife.should_receive(:edit_object).with(Chef::ApiClient, 'adam')
+      @knife.run
+    end
+
+    it 'should print usage and exit when a client name is not provided' do
+      @knife.name_args = []
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+  end
+end
diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/knife/client_list_spec.rb
new file mode 100644
index 0000000..c4834ad
--- /dev/null
+++ b/spec/unit/knife/client_list_spec.rb
@@ -0,0 +1,34 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::ClientList do
+  before(:each) do
+    @knife = Chef::Knife::ClientList.new
+    @knife.name_args = [ 'adam' ]
+  end
+
+  describe 'run' do
+    it 'should list the clients' do
+      Chef::ApiClient.should_receive(:list)
+      @knife.should_receive(:format_list_for_display)
+      @knife.run
+    end
+  end
+end
diff --git a/spec/unit/knife/client_reregister_spec.rb b/spec/unit/knife/client_reregister_spec.rb
new file mode 100644
index 0000000..d84978c
--- /dev/null
+++ b/spec/unit/knife/client_reregister_spec.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::ClientReregister do
+  before(:each) do
+    @knife = Chef::Knife::ClientReregister.new
+    @knife.name_args = [ 'adam' ]
+    @client_mock = mock('client_mock', :private_key => "foo_key")
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  context "when no client name is given on the command line" do
+    before do
+      @knife.name_args = []
+    end
+
+    it 'should print usage and exit when a client name is not provided' do
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+  end
+
+  context 'when not configured for file output' do
+    it 'reregisters the client and prints the key' do
+      Chef::ApiClient.should_receive(:reregister).with('adam').and_return(@client_mock)
+      @knife.run
+      @stdout.string.should match( /foo_key/ )
+    end
+  end
+
+  context 'when configured for file output' do
+    it 'should write the private key to a file' do
+      Chef::ApiClient.should_receive(:reregister).with('adam').and_return(@client_mock)
+
+      @knife.config[:file] = '/tmp/monkeypants'
+      filehandle = StringIO.new
+      File.should_receive(:open).with('/tmp/monkeypants', 'w').and_yield(filehandle)
+      @knife.run
+      filehandle.string.should == "foo_key"
+    end
+  end
+
+end
diff --git a/spec/unit/knife/client_show_spec.rb b/spec/unit/knife/client_show_spec.rb
new file mode 100644
index 0000000..ce5f93a
--- /dev/null
+++ b/spec/unit/knife/client_show_spec.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::ClientShow do
+  before(:each) do
+    @knife = Chef::Knife::ClientShow.new
+    @knife.name_args = [ 'adam' ]
+    @client_mock = mock('client_mock')
+  end
+
+  describe 'run' do
+    it 'should list the client' do
+      Chef::ApiClient.should_receive(:load).with('adam').and_return(@client_mock)
+      @knife.should_receive(:format_for_display).with(@client_mock)
+      @knife.run
+    end
+
+    it 'should print usage and exit when a client name is not provided' do
+      @knife.name_args = []
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+  end
+end
diff --git a/spec/unit/knife/config_file_selection_spec.rb b/spec/unit/knife/config_file_selection_spec.rb
new file mode 100644
index 0000000..127e89b
--- /dev/null
+++ b/spec/unit/knife/config_file_selection_spec.rb
@@ -0,0 +1,135 @@
+#
+# Author:: Nicolas Vinot (<aeris at imirhil.fr>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+require 'tmpdir'
+
+describe Chef::Knife do
+
+  let(:missing_config_fetcher) do
+    double(Chef::ConfigFetcher, :config_missing? => true)
+  end
+
+  let(:available_config_fetcher) do
+    double(Chef::ConfigFetcher, :config_missing? => false,
+                                :read_config => "")
+  end
+
+  def have_config_file(path)
+    Chef::ConfigFetcher.should_receive(:new).at_least(1).times.with(path, nil).and_return(available_config_fetcher)
+  end
+
+  before do
+    # Make sure tests can run when HOME is not set...
+    @original_home = ENV["HOME"]
+    ENV["HOME"] = Dir.tmpdir
+  end
+
+  after do
+    ENV["HOME"] = @original_home
+  end
+
+  before :each do
+    Chef::Config.stub!(:from_file).and_return(true)
+    Chef::ConfigFetcher.stub(:new).and_return(missing_config_fetcher)
+  end
+
+  it "configure knife from KNIFE_HOME env variable" do
+    env_config = File.expand_path(File.join(Dir.tmpdir, 'knife.rb'))
+    have_config_file(env_config)
+
+    ENV['KNIFE_HOME'] = Dir.tmpdir
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == env_config
+  end
+
+   it "configure knife from PWD" do
+    pwd_config = "#{Dir.pwd}/knife.rb"
+    have_config_file(pwd_config)
+
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == pwd_config
+  end
+
+  it "configure knife from UPWARD" do
+    upward_dir = File.expand_path "#{Dir.pwd}/.chef"
+    upward_config = File.expand_path "#{upward_dir}/knife.rb"
+    have_config_file(upward_config)
+    Chef::Knife.stub!(:chef_config_dir).and_return(upward_dir)
+
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == upward_config
+  end
+
+  it "configure knife from HOME" do
+    home_config = File.expand_path(File.join("#{ENV['HOME']}", "/.chef/knife.rb"))
+    have_config_file(home_config)
+
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == home_config
+  end
+
+  it "configure knife from nothing" do
+    ::File.stub!(:exist?).and_return(false)
+    @knife = Chef::Knife.new
+    @knife.ui.should_receive(:warn).with("No knife configuration file found")
+    @knife.configure_chef
+    @knife.config[:config_file].should be_nil
+  end
+
+  it "configure knife precedence" do
+    env_config = File.join(Dir.tmpdir, 'knife.rb')
+    pwd_config = "#{Dir.pwd}/knife.rb"
+    upward_dir = File.expand_path "#{Dir.pwd}/.chef"
+    upward_config = File.expand_path "#{upward_dir}/knife.rb"
+    home_config = File.expand_path(File.join("#{ENV['HOME']}", "/.chef/knife.rb"))
+    configs = [ env_config, pwd_config, upward_config, home_config ]
+
+    Chef::Knife.stub!(:chef_config_dir).and_return(upward_dir)
+    ENV['KNIFE_HOME'] = Dir.tmpdir
+
+    @knife = Chef::Knife.new
+
+    @knife.configure_chef
+    @knife.config[:config_file].should be_nil
+
+    have_config_file(home_config)
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == home_config
+
+    have_config_file(upward_config)
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == upward_config
+
+    have_config_file(pwd_config)
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == pwd_config
+
+    have_config_file(env_config)
+    @knife = Chef::Knife.new
+    @knife.configure_chef
+    @knife.config[:config_file].should == env_config
+  end
+end
diff --git a/spec/unit/knife/configure_client_spec.rb b/spec/unit/knife/configure_client_spec.rb
new file mode 100644
index 0000000..ba83210
--- /dev/null
+++ b/spec/unit/knife/configure_client_spec.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::ConfigureClient do
+  before do
+    @knife = Chef::Knife::ConfigureClient.new
+    Chef::Config[:chef_server_url] = 'https://chef.example.com'
+    Chef::Config[:validation_client_name] = 'chef-validator'
+    Chef::Config[:validation_key] = '/etc/chef/validation.pem'
+
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe 'run' do
+    it 'should print usage and exit when a directory is not provided' do
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal).with(/must provide the directory/)
+      lambda {
+        @knife.run
+      }.should raise_error SystemExit
+    end
+
+    describe 'when specifing a directory' do
+      before do
+        @knife.name_args = ['/home/bob/.chef']
+        @client_file = StringIO.new
+        @validation_file = StringIO.new
+        File.should_receive(:open).with('/home/bob/.chef/client.rb', 'w').
+                                   and_yield(@client_file)
+        File.should_receive(:open).with('/home/bob/.chef/validation.pem', 'w').
+                                   and_yield(@validation_file)
+        IO.should_receive(:read).and_return('foo_bar_baz')
+      end
+
+      it 'should recursively create the directory' do
+        FileUtils.should_receive(:mkdir_p).with('/home/bob/.chef')
+        @knife.run
+      end
+
+      it 'should write out the config file' do
+        FileUtils.stub!(:mkdir_p)
+        @knife.run
+        @client_file.string.should match /log_level\s+\:info/
+        @client_file.string.should match /log_location\s+STDOUT/
+        @client_file.string.should match /chef_server_url\s+'https\:\/\/chef\.example\.com'/
+        @client_file.string.should match /validation_client_name\s+'chef-validator'/
+      end
+
+      it 'should write out the validation.pem file' do
+        FileUtils.stub!(:mkdir_p)
+        @knife.run
+        @validation_file.string.should match /foo_bar_baz/
+      end
+
+      it 'should print information on what is being configured' do
+        FileUtils.stub!(:mkdir_p)
+        @knife.run
+        @stdout.string.should match /creating client configuration/i
+        @stdout.string.should match /writing client\.rb/i
+        @stdout.string.should match /writing validation\.pem/i
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/knife/configure_spec.rb b/spec/unit/knife/configure_spec.rb
new file mode 100644
index 0000000..7c48be6
--- /dev/null
+++ b/spec/unit/knife/configure_spec.rb
@@ -0,0 +1,242 @@
+require 'spec_helper'
+
+describe Chef::Knife::Configure do
+  before do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::Configure.new
+    @rest_client = mock("null rest client", :post_rest => { :result => :true })
+    @knife.stub!(:rest).and_return(@rest_client)
+
+    @out = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@out)
+    @knife.config[:config_file] = '/home/you/.chef/knife.rb'
+
+    @in = StringIO.new("\n" * 7)
+    @knife.ui.stub!(:stdin).and_return(@in)
+
+    @err = StringIO.new
+    @knife.ui.stub!(:stderr).and_return(@err)
+
+    Ohai::System.stub!(:new).and_return(ohai)
+  end
+
+
+  let(:fqdn) { "foo.example.org" }
+
+  let(:ohai) do
+    o = {}
+    o.stub(:require_plugin)
+    o[:fqdn] = fqdn
+    o
+  end
+
+  let(:default_admin_key) { "/etc/chef-server/admin.pem" }
+  let(:default_admin_key_win32) { File.expand_path(default_admin_key) }
+
+  let(:default_validator_key) { "/etc/chef-server/chef-validator.pem" }
+  let(:default_validator_key_win32) { File.expand_path(default_validator_key) }
+
+  let(:default_server_url) { "https://#{fqdn}:443" }
+
+
+  it "asks the user for the URL of the chef server" do
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter the chef server URL: [#{default_server_url}]"))
+    @knife.chef_server.should == default_server_url
+  end
+
+  it "asks the user for the clientname they want for the new client if -i is specified" do
+    @knife.config[:initial] = true
+    Etc.stub!(:getlogin).and_return("a-new-user")
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter a name for the new user: [a-new-user]"))
+    @knife.new_client_name.should == Etc.getlogin
+  end
+
+  it "should not ask the user for the clientname they want for the new client if -i and --node_name are specified" do
+    @knife.config[:initial] = true
+    @knife.config[:node_name] = 'testnode'
+    Etc.stub!(:getlogin).and_return("a-new-user")
+    @knife.ask_user_for_config
+    @out.string.should_not match(Regexp.escape("Please enter a name for the new user"))
+    @knife.new_client_name.should == 'testnode'
+  end
+
+  it "asks the user for the existing API username or clientname if -i is not specified" do
+    Etc.stub!(:getlogin).and_return("a-new-user")
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter an existing username or clientname for the API: [a-new-user]"))
+    @knife.new_client_name.should == Etc.getlogin
+  end
+
+  it "asks the user for the existing admin client's name if -i is specified" do
+    @knife.config[:initial] = true
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter the existing admin name: [admin]"))
+    @knife.admin_client_name.should == 'admin'
+  end
+
+  it "should not ask the user for the existing admin client's name if -i and --admin-client_name are specified" do
+    @knife.config[:initial] = true
+    @knife.config[:admin_client_name] = 'my-webui'
+    @knife.ask_user_for_config
+    @out.string.should_not match(Regexp.escape("Please enter the existing admin:"))
+    @knife.admin_client_name.should == 'my-webui'
+  end
+
+  it "should not ask the user for the existing admin client's name if -i is not specified" do
+    @knife.ask_user_for_config
+    @out.string.should_not match(Regexp.escape("Please enter the existing admin: [admin]"))
+    @knife.admin_client_name.should_not == 'admin'
+  end
+
+  it "asks the user for the location of the existing admin key if -i is specified" do
+    @knife.config[:initial] = true
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter the location of the existing admin's private key: [#{default_admin_key}]"))
+    if windows?
+      @knife.admin_client_key.capitalize.should == default_admin_key_win32.capitalize
+    else
+      @knife.admin_client_key.should == default_admin_key
+    end
+  end
+
+  it "should not ask the user for the location of the existing admin key if -i and --admin_client_key are specified" do
+    @knife.config[:initial] = true
+    @knife.config[:admin_client_key] = '/home/you/.chef/my-webui.pem'
+    @knife.ask_user_for_config
+    @out.string.should_not match(Regexp.escape("Please enter the location of the existing admin client's private key:"))
+    if windows?
+      @knife.admin_client_key.should match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$}
+    else
+      @knife.admin_client_key.should == '/home/you/.chef/my-webui.pem'
+    end
+  end
+
+  it "should not ask the user for the location of the existing admin key if -i is not specified" do
+    @knife.ask_user_for_config
+    @out.string.should_not match(Regexp.escape("Please enter the location of the existing admin client's private key: [#{default_admin_key}]"))
+    if windows?
+      @knife.admin_client_key.should_not == default_admin_key_win32
+    else
+      @knife.admin_client_key.should_not == default_admin_key
+    end
+  end
+
+  it "asks the user for the location of a chef repo" do
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter the path to a chef repository (or leave blank):"))
+    @knife.chef_repo.should == ''
+  end
+
+  it "asks the users for the name of the validation client" do
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter the validation clientname: [chef-validator]"))
+    @knife.validation_client_name.should == 'chef-validator'
+  end
+
+  it "should not ask the users for the name of the validation client if --validation_client_name is specified" do
+    @knife.config[:validation_client_name] = 'my-validator'
+    @knife.ask_user_for_config
+    @out.string.should_not match(Regexp.escape("Please enter the validation clientname:"))
+    @knife.validation_client_name.should == 'my-validator'
+  end
+
+  it "asks the users for the location of the validation key" do
+    @knife.ask_user_for_config
+    @out.string.should match(Regexp.escape("Please enter the location of the validation key: [#{default_validator_key}]"))
+    if windows?
+      @knife.validation_key.capitalize.should == default_validator_key_win32.capitalize
+    else
+      @knife.validation_key.should == default_validator_key
+    end
+  end
+
+  it "should not ask the users for the location of the validation key if --validation_key is specified" do
+    @knife.config[:validation_key] = '/home/you/.chef/my-validation.pem'
+    @knife.ask_user_for_config
+    @out.string.should_not match(Regexp.escape("Please enter the location of the validation key:"))
+    if windows?
+      @knife.validation_key.should match %r{^[A-Za-z]:/home/you/\.chef/my-validation\.pem$}
+    else
+      @knife.validation_key.should == '/home/you/.chef/my-validation.pem'
+    end
+  end
+
+  it "should not ask the user for anything if -i and all other properties are specified" do
+    @knife.config[:initial] = true
+    @knife.config[:chef_server_url] = 'http://localhost:5000'
+    @knife.config[:node_name] = 'testnode'
+    @knife.config[:admin_client_name] = 'my-webui'
+    @knife.config[:admin_client_key] = '/home/you/.chef/my-webui.pem'
+    @knife.config[:validation_client_name] = 'my-validator'
+    @knife.config[:validation_key] = '/home/you/.chef/my-validation.pem'
+    @knife.config[:repository] = ''
+    @knife.config[:client_key] = '/home/you/a-new-user.pem'
+    Etc.stub!(:getlogin).and_return('a-new-user')
+
+    @knife.ask_user_for_config
+    @out.string.should match(/\s*/)
+
+    @knife.new_client_name.should == 'testnode'
+    @knife.chef_server.should == 'http://localhost:5000'
+    @knife.admin_client_name.should == 'my-webui'
+    if windows?
+      @knife.admin_client_key.should match %r{^[A-Za-z]:/home/you/\.chef/my-webui\.pem$}
+      @knife.validation_key.should match %r{^[A-Za-z]:/home/you/\.chef/my-validation\.pem$}
+      @knife.new_client_key.should match %r{^[A-Za-z]:/home/you/a-new-user\.pem$}
+    else
+      @knife.admin_client_key.should == '/home/you/.chef/my-webui.pem'
+      @knife.validation_key.should == '/home/you/.chef/my-validation.pem'
+      @knife.new_client_key.should == '/home/you/a-new-user.pem'
+    end
+    @knife.validation_client_name.should == 'my-validator'
+    @knife.chef_repo.should == ''
+  end
+
+  it "writes the new data to a config file" do
+    File.stub!(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb")
+    File.stub!(:expand_path).with("/home/you/.chef/#{Etc.getlogin}.pem").and_return("/home/you/.chef/#{Etc.getlogin}.pem")
+    File.stub!(:expand_path).with(default_validator_key).and_return(default_validator_key)
+    File.stub!(:expand_path).with(default_admin_key).and_return(default_admin_key)
+    FileUtils.should_receive(:mkdir_p).with("/home/you/.chef")
+    config_file = StringIO.new
+    ::File.should_receive(:open).with("/home/you/.chef/knife.rb", "w").and_yield config_file
+    @knife.config[:repository] = '/home/you/chef-repo'
+    @knife.run
+    config_file.string.should match(/^node_name[\s]+'#{Etc.getlogin}'$/)
+    config_file.string.should match(%r{^client_key[\s]+'/home/you/.chef/#{Etc.getlogin}.pem'$})
+    config_file.string.should match(/^validation_client_name\s+'chef-validator'$/)
+    config_file.string.should match(%r{^validation_key\s+'#{default_validator_key}'$})
+    config_file.string.should match(%r{^chef_server_url\s+'#{default_server_url}'$})
+    config_file.string.should match(%r{cookbook_path\s+\[ '/home/you/chef-repo/cookbooks' \]})
+  end
+
+  it "creates a new client when given the --initial option" do
+    File.should_receive(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb")
+    File.should_receive(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem")
+    File.should_receive(:expand_path).with(default_validator_key).and_return(default_validator_key)
+    File.should_receive(:expand_path).with(default_admin_key).and_return(default_admin_key)
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+
+    user_command = Chef::Knife::UserCreate.new
+    user_command.should_receive(:run)
+
+    Etc.stub!(:getlogin).and_return("a-new-user")
+
+    Chef::Knife::UserCreate.stub!(:new).and_return(user_command)
+    FileUtils.should_receive(:mkdir_p).with("/home/you/.chef")
+    ::File.should_receive(:open).with("/home/you/.chef/knife.rb", "w")
+    @knife.config[:initial] = true
+    @knife.config[:user_password] = "blah"
+    @knife.run
+    user_command.name_args.should == Array("a-new-user")
+    user_command.config[:user_password].should == "blah"
+    user_command.config[:admin].should be_true
+    user_command.config[:file].should == "/home/you/.chef/a-new-user.pem"
+    user_command.config[:yes].should be_true
+    user_command.config[:disable_editing].should be_true
+  end
+end
diff --git a/spec/unit/knife/cookbook_bulk_delete_spec.rb b/spec/unit/knife/cookbook_bulk_delete_spec.rb
new file mode 100644
index 0000000..ced2a9a
--- /dev/null
+++ b/spec/unit/knife/cookbook_bulk_delete_spec.rb
@@ -0,0 +1,87 @@
+#
+# Author:: Stephen Delano (<stephen at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::CookbookBulkDelete do
+  before(:each) do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::CookbookBulkDelete.new
+    @knife.config = {:print_after => nil}
+    @knife.name_args = ["."]
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @knife.ui.stub!(:confirm).and_return(true)
+    @cookbooks = Hash.new
+    %w{cheezburger pizza lasagna}.each do |cookbook_name|
+      cookbook = Chef::CookbookVersion.new(cookbook_name)
+      @cookbooks[cookbook_name] = cookbook
+    end
+    @rest = mock("Chef::REST")
+    @rest.stub!(:get_rest).and_return(@cookbooks)
+    @rest.stub!(:delete_rest).and_return(true)
+    @knife.stub!(:rest).and_return(@rest)
+    Chef::CookbookVersion.stub!(:list).and_return(@cookbooks)
+
+  end
+
+
+
+  describe "when there are several cookbooks on the server" do
+    before do
+      @cheezburger = {'cheezburger' => {"url" => "file:///dev/null", "versions" => [{"url" => "file:///dev/null-cheez", "version" => "1.0.0"}]}}
+      @rest.stub!(:get_rest).with('cookbooks/cheezburger').and_return(@cheezburger)
+      @pizza = {'pizza' => {"url" => "file:///dev/null", "versions" => [{"url" => "file:///dev/null-pizza", "version" => "2.0.0"}]}}
+      @rest.stub!(:get_rest).with('cookbooks/pizza').and_return(@pizza)
+      @lasagna = {'lasagna' => {"url" => "file:///dev/null", "versions" => [{"url" => "file:///dev/null-lasagna", "version" => "3.0.0"}]}}
+      @rest.stub!(:get_rest).with('cookbooks/lasagna').and_return(@lasagna)
+    end
+
+    it "should print the cookbooks you are about to delete" do
+      expected = @knife.ui.list(@cookbooks.keys.sort, :columns_down)
+      @knife.run
+      @stdout.string.should match(/#{expected}/)
+    end
+
+    it "should confirm you really want to delete them" do
+      @knife.ui.should_receive(:confirm)
+      @knife.run
+    end
+
+    it "should delete each cookbook" do
+      {"cheezburger" => "1.0.0", "pizza" => "2.0.0", "lasagna" => '3.0.0'}.each do |cookbook_name, version|
+        @rest.should_receive(:delete_rest).with("cookbooks/#{cookbook_name}/#{version}")
+      end
+      @knife.run
+    end
+
+    it "should only delete cookbooks that match the regex" do
+      @knife.name_args = ["cheezburger"]
+      @rest.should_receive(:delete_rest).with('cookbooks/cheezburger/1.0.0')
+      @knife.run
+    end
+  end
+
+  it "should exit if the regex is not provided" do
+    @knife.name_args = []
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+
+end
diff --git a/spec/unit/knife/cookbook_create_spec.rb b/spec/unit/knife/cookbook_create_spec.rb
new file mode 100644
index 0000000..7891115
--- /dev/null
+++ b/spec/unit/knife/cookbook_create_spec.rb
@@ -0,0 +1,260 @@
+#
+# Author:: Nuo Yan (<nuo at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tmpdir'
+
+describe Chef::Knife::CookbookCreate do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::CookbookCreate.new
+    @knife.config = {}
+    @knife.name_args = ["foobar"]
+    @stdout = StringIO.new
+    @knife.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+
+    # Fixes CHEF-2579
+    it "should expand the path of the cookbook directory" do
+      File.should_receive(:expand_path).with("~/tmp/monkeypants")
+      @knife.config = {:cookbook_path => "~/tmp/monkeypants"}
+      @knife.stub!(:create_cookbook)
+      @knife.stub!(:create_readme)
+      @knife.stub!(:create_changelog)
+      @knife.stub!(:create_metadata)
+      @knife.run
+    end
+
+    it "should create a new cookbook with default values to copyright name, email, readme format and license if those are not supplied" do
+      @dir = Dir.tmpdir
+      @knife.config = {:cookbook_path => @dir}
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "none")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "YOUR_EMAIL", "none", "md")
+      @knife.run
+    end
+
+    it "should create a new cookbook with specified company name in the copyright section if one is specified" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "YOUR_EMAIL", "none", "md")
+      @knife.run
+    end
+
+    it "should create a new cookbook with specified copyright name and email if they are specified" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "none", "md")
+      @knife.run
+    end
+
+    it "should create a new cookbook with specified copyright name and email and license information (true) if they are specified" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "apachev2"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "apachev2")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "apachev2", "md")
+      @knife.run
+    end
+
+    it "should create a new cookbook with specified copyright name and email and license information (false) if they are specified" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => false
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "none", "md")
+      @knife.run
+    end
+
+    it "should create a new cookbook with specified copyright name and email and license information ('false' as string) if they are specified" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "false"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "none", "md")
+      @knife.run
+    end
+
+    it "should allow specifying a gpl2 license" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "gplv2"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "gplv2")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "gplv2", "md")
+      @knife.run
+    end
+
+    it "should allow specifying a gplv3 license" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "gplv3"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "gplv3")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "gplv3", "md")
+      @knife.run
+    end
+
+    it "should allow specifying the mit license" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "mit"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "mit", "md")
+      @knife.run
+    end
+
+    it "should allow specifying the rdoc readme format" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "mit",
+        :readme_format => "rdoc"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "rdoc")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "mit", "rdoc")
+      @knife.run
+    end
+
+    it "should allow specifying the mkd readme format" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "mit",
+        :readme_format => "mkd"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "mkd")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "mit", "mkd")
+      @knife.run
+    end
+
+    it "should allow specifying the txt readme format" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "mit",
+        :readme_format => "txt"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "txt")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "mit", "txt")
+      @knife.run
+    end
+
+    it "should allow specifying an arbitrary readme format" do
+      @dir = Dir.tmpdir
+      @knife.config = {
+        :cookbook_path => @dir,
+        :cookbook_copyright => "Opscode, Inc",
+        :cookbook_email => "nuo at opscode.com",
+        :cookbook_license => "mit",
+        :readme_format => "foo"
+      }
+      @knife.name_args=["foobar"]
+      @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit")
+      @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "foo")
+      @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first)
+      @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo at opscode.com", "mit", "foo")
+      @knife.run
+    end
+
+    context "when the cookbooks path is set to nil" do
+      before do
+        Chef::Config[:cookbook_path] = nil
+      end
+
+      it "should throw an argument error" do
+        @dir = Dir.tmpdir
+        lambda{@knife.run}.should raise_error(ArgumentError)
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/knife/cookbook_delete_spec.rb b/spec/unit/knife/cookbook_delete_spec.rb
new file mode 100644
index 0000000..afaa3b6
--- /dev/null
+++ b/spec/unit/knife/cookbook_delete_spec.rb
@@ -0,0 +1,239 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::CookbookDelete do
+  before(:each) do
+    @knife = Chef::Knife::CookbookDelete.new
+    @knife.name_args = ['foobar']
+    @knife.cookbook_name = 'foobar'
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @stderr = StringIO.new
+    @knife.ui.stub!(:stderr).and_return(@stderr)
+  end
+
+  describe 'run' do
+    it 'should print usage and exit when a cookbook name is not provided' do
+      @knife.name_args = []
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    describe 'when specifying a cookbook name' do
+      it 'should delete the cookbook without a specific version' do
+        @knife.should_receive(:delete_without_explicit_version)
+        @knife.run
+      end
+
+      describe 'and a version' do
+        it 'should delete the specific version of the cookbook' do
+          @knife.name_args << '1.0.0'
+          @knife.should_receive(:delete_explicit_version)
+          @knife.run
+        end
+      end
+
+      describe 'with -a or --all' do
+        it 'should delete all versions of the cookbook' do
+          @knife.config[:all] = true
+          @knife.should_receive(:delete_all_versions)
+          @knife.run
+        end
+      end
+
+      describe 'with -p or --purge' do
+        it 'should prompt to purge the files' do
+          @knife.config[:purge] = true
+          @knife.should_receive(:confirm).
+                 with(/.+Are you sure you want to purge files.+/)
+          @knife.should_receive(:delete_without_explicit_version)
+          @knife.run
+        end
+      end
+    end
+  end
+
+  describe 'delete_explicit_version' do
+    it 'should delete the specific cookbook version' do
+      @knife.cookbook_name = 'foobar'
+      @knife.version = '1.0.0'
+      @knife.should_receive(:delete_object).with(Chef::CookbookVersion,
+                                                 'foobar version 1.0.0',
+                                                 'cookbook').and_yield()
+      @knife.should_receive(:delete_request).with('cookbooks/foobar/1.0.0')
+      @knife.delete_explicit_version
+    end
+  end
+
+  describe 'delete_all_versions' do
+    it 'should prompt to delete all versions of the cookbook' do
+      @knife.cookbook_name = 'foobar'
+      @knife.should_receive(:confirm).with('Do you really want to delete all versions of foobar')
+      @knife.should_receive(:delete_all_without_confirmation)
+      @knife.delete_all_versions
+    end
+  end
+
+  describe 'delete_all_without_confirmation' do
+    it 'should delete all versions without confirmation' do
+      versions = ['1.0.0', '1.1.0']
+      @knife.should_receive(:available_versions).and_return(versions)
+      versions.each do |v|
+        @knife.should_receive(:delete_version_without_confirmation).with(v)
+      end
+      @knife.delete_all_without_confirmation
+    end
+  end
+
+  describe 'delete_without_explicit_version' do
+    it 'should exit if there are no available versions' do
+      @knife.should_receive(:available_versions).and_return(nil)
+      lambda { @knife.delete_without_explicit_version }.should raise_error(SystemExit)
+    end
+
+    it 'should delete the version if only one is found' do
+      @knife.should_receive(:available_versions).at_least(:once).and_return(['1.0.0'])
+      @knife.should_receive(:delete_explicit_version)
+      @knife.delete_without_explicit_version
+    end
+
+    it 'should ask which version(s) to delete if multiple are found' do
+      @knife.should_receive(:available_versions).at_least(:once).and_return(['1.0.0', '1.1.0'])
+      @knife.should_receive(:ask_which_versions_to_delete).and_return(['1.0.0', '1.1.0'])
+      @knife.should_receive(:delete_versions_without_confirmation).with(['1.0.0', '1.1.0'])
+      @knife.delete_without_explicit_version
+    end
+  end
+
+  describe 'available_versions' do
+    before(:each) do
+      @rest_mock = mock('rest')
+      @knife.should_receive(:rest).and_return(@rest_mock)
+      @cookbook_data = { 'foobar' => { 'versions' => [{'version' => '1.0.0'},
+                                                      {'version' => '1.1.0'},
+                                                      {'version' => '2.0.0'} ]}
+      }
+    end
+
+    it 'should return the list of versions of the cookbook' do
+      @rest_mock.should_receive(:get_rest).with('cookbooks/foobar').and_return(@cookbook_data)
+      @knife.available_versions.should == ['1.0.0', '1.1.0', '2.0.0']
+    end
+
+    it 'should raise if an error other than HTTP 404 is returned' do
+      exception = Net::HTTPServerException.new('500 Internal Server Error', '500')
+      @rest_mock.should_receive(:get_rest).and_raise(exception)
+      lambda { @knife.available_versions }.should raise_error Net::HTTPServerException
+    end
+
+    describe "if the cookbook can't be found" do
+      before(:each) do
+        @rest_mock.should_receive(:get_rest).
+          and_raise(Net::HTTPServerException.new('404 Not Found', '404'))
+      end
+
+      it 'should print an error' do
+        @knife.available_versions
+        @stderr.string.should match /error.+cannot find a cookbook named foobar/i
+      end
+
+      it 'should return nil' do
+        @knife.available_versions.should == nil
+      end
+    end
+  end
+
+  describe 'ask_which_version_to_delete' do
+    before(:each) do
+      @knife.stub!(:available_versions).and_return(['1.0.0', '1.1.0', '2.0.0'])
+    end
+
+    it 'should prompt the user to select a version' do
+      prompt = /Which version\(s\) do you want to delete\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+4\. All versions.+/m
+      @knife.should_receive(:ask_question).with(prompt).and_return('1')
+      @knife.ask_which_versions_to_delete
+    end
+
+    it "should print an error and exit if a version wasn't specified" do
+      @knife.should_receive(:ask_question).and_return('')
+      @knife.ui.should_receive(:error).with(/no versions specified/i)
+      lambda { @knife.ask_which_versions_to_delete }.should raise_error(SystemExit)
+    end
+
+    it 'should print an error if an invalid choice was selected' do
+      @knife.should_receive(:ask_question).and_return('100')
+      @knife.ui.should_receive(:error).with(/100 is not a valid choice/i)
+      @knife.ask_which_versions_to_delete
+    end
+
+    it 'should return the selected versions' do
+      @knife.should_receive(:ask_question).and_return('1, 3')
+      @knife.ask_which_versions_to_delete.should == ['1.0.0', '2.0.0']
+    end
+
+    it "should return all of the versions if 'all' was selected" do
+      @knife.should_receive(:ask_question).and_return('4')
+      @knife.ask_which_versions_to_delete.should == [:all]
+    end
+  end
+
+  describe 'delete_version_without_confirmation' do
+    it 'should delete the cookbook version' do
+      @knife.should_receive(:delete_request).with('cookbooks/foobar/1.0.0')
+      @knife.delete_version_without_confirmation('1.0.0')
+    end
+
+    it 'should output that the cookbook was deleted' do
+      @knife.stub!(:delete_request)
+      @knife.delete_version_without_confirmation('1.0.0')
+      @stdout.string.should match /deleted cookbook\[foobar\]\[1.0.0\]/im
+    end
+
+    describe 'with --print-after' do
+      it 'should display the cookbook data' do
+        object = ''
+        @knife.config[:print_after] = true
+        @knife.stub!(:delete_request).and_return(object)
+        @knife.should_receive(:format_for_display).with(object)
+        @knife.delete_version_without_confirmation('1.0.0')
+      end
+    end
+  end
+
+  describe 'delete_versions_without_confirmation' do
+    it 'should delete each version without confirmation' do
+      versions = ['1.0.0', '1.1.0']
+      versions.each do |v|
+        @knife.should_receive(:delete_version_without_confirmation).with(v)
+      end
+      @knife.delete_versions_without_confirmation(versions)
+    end
+
+    describe 'with -a or --all' do
+      it 'should delete all versions without confirmation' do
+        versions = [:all]
+        @knife.should_receive(:delete_all_without_confirmation)
+        @knife.delete_versions_without_confirmation(versions)
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/knife/cookbook_download_spec.rb b/spec/unit/knife/cookbook_download_spec.rb
new file mode 100644
index 0000000..cc3c300
--- /dev/null
+++ b/spec/unit/knife/cookbook_download_spec.rb
@@ -0,0 +1,238 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::CookbookDownload do
+  before(:each) do
+    @knife = Chef::Knife::CookbookDownload.new
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe 'run' do
+    it 'should print usage and exit when a cookbook name is not provided' do
+      @knife.name_args = []
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal).with(/must specify a cookbook name/)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should exit with a fatal error when there is no cookbook on the server' do
+      @knife.name_args = ['foobar', nil]
+      @knife.should_receive(:determine_version).and_return(nil)
+      @knife.ui.should_receive(:fatal).with('No such cookbook found')
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    describe 'with a cookbook name' do
+      before(:each) do
+        @knife.name_args = ['foobar']
+        @knife.config[:download_directory] = '/var/tmp/chef'
+        @rest_mock = mock('rest')
+        @knife.stub(:rest).and_return(@rest_mock)
+
+        @manifest_data = {
+          :recipes => [
+            {'path' => 'recipes/foo.rb',
+             'url' => 'http://example.org/files/foo.rb'},
+            {'path' => 'recipes/bar.rb',
+             'url' => 'http://example.org/files/bar.rb'}
+          ],
+          :templates => [
+            {'path' => 'templates/default/foo.erb',
+             'url' => 'http://example.org/files/foo.erb'},
+            {'path' => 'templates/default/bar.erb',
+             'url' => 'http://example.org/files/bar.erb'}
+          ],
+          :attributes => [
+            {'path' => 'attributes/default.rb',
+             'url' => 'http://example.org/files/default.rb'}
+          ]
+        }
+
+        @cookbook_mock = mock('cookbook')
+        @cookbook_mock.stub!(:version).and_return('1.0.0')
+        @cookbook_mock.stub!(:manifest).and_return(@manifest_data)
+        @rest_mock.should_receive(:get_rest).with('cookbooks/foobar/1.0.0').
+                                             and_return(@cookbook_mock)
+      end
+
+      it 'should determine which version if one was not explicitly specified'do
+        @cookbook_mock.stub!(:manifest).and_return({})
+        @knife.should_receive(:determine_version).and_return('1.0.0')
+        File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(false)
+        Chef::CookbookVersion.stub!(:COOKBOOK_SEGEMENTS).and_return([])
+        @knife.run
+      end
+
+      describe 'and a version' do
+        before(:each) do
+          @knife.name_args << '1.0.0'
+          @files = @manifest_data.values.map { |v| v.map { |i| i['path'] } }.flatten.uniq
+          @files_mocks = {}
+          @files.map { |f| File.basename(f) }.flatten.uniq.each do |f|
+            @files_mocks[f] = mock("#{f}_mock")
+            @files_mocks[f].stub!(:path).and_return("/var/tmp/#{f}")
+          end
+        end
+
+        it 'should print an error and exit if the cookbook download directory already exists' do
+          File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(true)
+          @knife.ui.should_receive(:fatal).with(/\/var\/tmp\/chef\/foobar-1\.0\.0 exists/i)
+          lambda { @knife.run }.should raise_error(SystemExit)
+        end
+
+        describe 'when downloading the cookbook' do
+          before(:each) do
+            @files.map { |f| File.dirname(f) }.flatten.uniq.each do |dir|
+              FileUtils.should_receive(:mkdir_p).with("/var/tmp/chef/foobar-1.0.0/#{dir}").
+              at_least(:once)
+            end
+
+            @files_mocks.each_pair do |file, mock|
+              @rest_mock.should_receive(:get_rest).with("http://example.org/files/#{file}", true).
+              and_return(mock)
+            end
+
+            @rest_mock.should_receive(:sign_on_redirect=).with(false).at_least(:once)
+            @files.each do |f|
+              FileUtils.should_receive(:mv).
+                        with("/var/tmp/#{File.basename(f)}", "/var/tmp/chef/foobar-1.0.0/#{f}")
+            end
+          end
+
+          it "should download the cookbook when the cookbook download directory doesn't exist" do
+            File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(false)
+            @knife.run
+            ['attributes', 'recipes', 'templates'].each do |segment|
+              @stdout.string.should match /downloading #{segment}/im
+            end
+            @stdout.string.should match /downloading foobar cookbook version 1\.0\.0/im
+            @stdout.string.should match /cookbook downloaded to \/var\/tmp\/chef\/foobar-1\.0\.0/im
+          end
+
+          describe 'with -f or --force' do
+            it 'should remove the existing the cookbook download directory if it exists' do
+              @knife.config[:force] = true
+              File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(true)
+              FileUtils.should_receive(:rm_rf).with('/var/tmp/chef/foobar-1.0.0')
+              @knife.run
+            end
+          end
+        end
+
+      end
+    end
+
+  end
+
+  describe 'determine_version' do
+
+    it 'should return nil if there are no versions' do
+      @knife.should_receive(:available_versions).and_return(nil)
+      @knife.determine_version.should == nil
+      @knife.version.should == nil
+    end
+
+    it 'should return and set the version if there is only one version' do
+      @knife.should_receive(:available_versions).at_least(:once).and_return(['1.0.0'])
+      @knife.determine_version.should == '1.0.0'
+      @knife.version.should == '1.0.0'
+    end
+
+    it 'should ask which version to download and return it if there is more than one' do
+      @knife.should_receive(:available_versions).at_least(:once).and_return(['1.0.0', '2.0.0'])
+      @knife.should_receive(:ask_which_version).and_return('1.0.0')
+      @knife.determine_version.should == '1.0.0'
+    end
+
+    describe 'with -N or --latest' do
+      it 'should return and set the version to the latest version' do
+        @knife.config[:latest] = true
+        @knife.should_receive(:available_versions).at_least(:once).
+                                                   and_return(['1.0.0', '1.1.0', '2.0.0'])
+        @knife.determine_version
+        @knife.version.to_s.should == '2.0.0'
+      end
+    end
+  end
+
+  describe 'available_versions' do
+    before(:each) do
+      @knife.cookbook_name = 'foobar'
+    end
+
+    it 'should return nil if there are no versions' do
+      Chef::CookbookVersion.should_receive(:available_versions).
+                            with('foobar').
+                            and_return(nil)
+      @knife.available_versions.should == nil
+    end
+
+    it 'should return the available versions' do
+      Chef::CookbookVersion.should_receive(:available_versions).
+                            with('foobar').
+                            and_return(['1.1.0', '2.0.0', '1.0.0'])
+      @knife.available_versions.should == [Chef::Version.new('1.0.0'),
+                                           Chef::Version.new('1.1.0'),
+                                           Chef::Version.new('2.0.0')]
+    end
+
+    it 'should avoid multiple API calls to the server' do
+      Chef::CookbookVersion.should_receive(:available_versions).
+                            once.
+                            with('foobar').
+                            and_return(['1.1.0', '2.0.0', '1.0.0'])
+      @knife.available_versions
+      @knife.available_versions
+    end
+  end
+
+  describe 'ask_which_version' do
+    before(:each) do
+      @knife.cookbook_name = 'foobar'
+      @knife.stub!(:available_versions).and_return(['1.0.0', '1.1.0', '2.0.0'])
+    end
+
+    it 'should prompt the user to select a version' do
+      prompt = /Which version do you want to download\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+/m
+      @knife.should_receive(:ask_question).with(prompt).and_return('1')
+      @knife.ask_which_version
+    end
+
+    it "should set the version to the user's selection" do
+      @knife.should_receive(:ask_question).and_return('1')
+      @knife.ask_which_version
+      @knife.version.should == '1.0.0'
+    end
+
+    it "should print an error and exit if a version wasn't specified" do
+      @knife.should_receive(:ask_question).and_return('')
+      @knife.ui.should_receive(:error).with(/is not a valid value/i)
+      lambda { @knife.ask_which_version }.should raise_error(SystemExit)
+    end
+
+    it 'should print an error if an invalid choice was selected' do
+      @knife.should_receive(:ask_question).and_return('100')
+      @knife.ui.should_receive(:error).with(/'100' is not a valid value/i)
+      lambda { @knife.ask_which_version }.should raise_error(SystemExit)
+    end
+  end
+
+end
diff --git a/spec/unit/knife/cookbook_list_spec.rb b/spec/unit/knife/cookbook_list_spec.rb
new file mode 100644
index 0000000..db6f061
--- /dev/null
+++ b/spec/unit/knife/cookbook_list_spec.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::CookbookList do
+  before do
+    @knife = Chef::Knife::CookbookList.new
+    @rest_mock = mock('rest')
+    @knife.stub!(:rest).and_return(@rest_mock)
+    @cookbook_names = ['apache2', 'mysql']
+    @base_url = 'https://server.example.com/cookbooks'
+    @cookbook_data = {}
+    @cookbook_names.each do |item|
+      @cookbook_data[item] = {'url' => "#{@base_url}/#{item}",
+                              'versions' => [{'version' => '1.0.1',
+                                              'url' => "#{@base_url}/#{item}/1.0.1"}]}
+    end
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe 'run' do
+    it 'should display the latest version of the cookbooks' do
+      @rest_mock.should_receive(:get_rest).with('/cookbooks?num_versions=1').
+                                           and_return(@cookbook_data)
+      @knife.run
+      @cookbook_names.each do |item|
+        @stdout.string.should match /#{item}\s+1\.0\.1/
+      end
+    end
+
+    it 'should query cookbooks for the configured environment' do
+      @knife.config[:environment] = 'production'
+      @rest_mock.should_receive(:get_rest).
+                 with('/environments/production/cookbooks?num_versions=1').
+                 and_return(@cookbook_data)
+      @knife.run
+    end
+
+    describe 'with -w or --with-uri' do
+      it 'should display the cookbook uris' do
+        @knife.config[:with_uri] = true
+        @rest_mock.stub(:get_rest).and_return(@cookbook_data)
+        @knife.run
+        @cookbook_names.each do |item|
+          pattern = /#{Regexp.escape(@cookbook_data[item]['versions'].first['url'])}/
+          @stdout.string.should match pattern
+        end
+      end
+    end
+
+    describe 'with -a or --all' do
+      before do
+        @cookbook_names.each do |item|
+          @cookbook_data[item]['versions'] << {'version' => '1.0.0',
+                                               'url' => "#{@base_url}/#{item}/1.0.0"}
+        end
+      end
+
+      it 'should display all versions of the cookbooks' do
+        @knife.config[:all_versions] = true
+        @rest_mock.should_receive(:get_rest).with('/cookbooks?num_versions=all').
+                                             and_return(@cookbook_data)
+        @knife.run
+        @cookbook_names.each do |item|
+          @stdout.string.should match /#{item}\s+1\.0\.1\s+1\.0\.0/
+        end
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/knife/cookbook_metadata_from_file_spec.rb b/spec/unit/knife/cookbook_metadata_from_file_spec.rb
new file mode 100644
index 0000000..6f406de
--- /dev/null
+++ b/spec/unit/knife/cookbook_metadata_from_file_spec.rb
@@ -0,0 +1,65 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Matthew Kent (<mkent at magoazul.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::CookbookMetadataFromFile do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @src = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.rb"))
+    @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json"))
+    @knife = Chef::Knife::CookbookMetadataFromFile.new
+    @knife.name_args = [ @src ]
+    @knife.stub!(:to_json_pretty).and_return(true)
+    @md = Chef::Cookbook::Metadata.new
+    Chef::Cookbook::Metadata.stub(:new).and_return(@md)
+    $stdout.stub!(:write)
+  end
+
+  after do
+    if File.exists?(@tgt)
+      File.unlink(@tgt)
+    end
+  end
+
+  describe "run" do
+    it "should determine cookbook name from path" do
+      @md.should_receive(:name).with()
+      @md.should_receive(:name).with("quick_start")
+      @knife.run
+    end
+
+    it "should load the metadata source" do
+      @md.should_receive(:from_file).with(@src)
+      @knife.run
+    end
+
+    it "should write out the metadata to the correct location" do
+      File.should_receive(:open).with(@tgt, "w")
+      @knife.run
+    end
+
+    it "should generate json from the metadata" do
+      Chef::JSONCompat.should_receive(:to_json_pretty).with(@md)
+      @knife.run
+    end
+
+  end
+end
diff --git a/spec/unit/knife/cookbook_metadata_spec.rb b/spec/unit/knife/cookbook_metadata_spec.rb
new file mode 100644
index 0000000..c664326
--- /dev/null
+++ b/spec/unit/knife/cookbook_metadata_spec.rb
@@ -0,0 +1,179 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2011 Thomas Bishop
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::CookbookMetadata do
+  before(:each) do
+    @knife = Chef::Knife::CookbookMetadata.new
+    @knife.name_args = ['foobar']
+    @cookbook_dir = Dir.mktmpdir
+    @json_data = '{ "version": "1.0.0" }'
+    @stdout = StringIO.new
+    @stderr = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @knife.ui.stub!(:stderr).and_return(@stderr)
+  end
+
+  describe 'run' do
+    it 'should print an error and exit if a cookbook name was not provided' do
+      @knife.name_args = []
+      @knife.ui.should_receive(:error).with(/you must specify the cookbook.+use the --all/i)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should print an error and exit if an empty cookbook name was provided' do
+      @knife.name_args = ['']
+      @knife.ui.should_receive(:error).with(/you must specify the cookbook.+use the --all/i)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should generate the metadata for the cookbook' do
+      @knife.should_receive(:generate_metadata).with('foobar')
+      @knife.run
+    end
+
+    describe 'with -a or --all' do
+      before(:each) do
+        @knife.config[:all] = true
+        @foo = Chef::CookbookVersion.new('foo')
+        @foo.version = '1.0.0'
+        @bar = Chef::CookbookVersion.new('bar')
+        @bar.version = '2.0.0'
+        @cookbook_loader = {
+          "foo" => @foo,
+          "bar" => @bar
+        }
+        @cookbook_loader.should_receive(:load_cookbooks).and_return(@cookbook_loader)
+        @knife.should_receive(:generate_metadata).with('foo')
+        @knife.should_receive(:generate_metadata).with('bar')
+      end
+
+      it 'should generate the metadata for each cookbook' do
+        Chef::Config[:cookbook_path] = @cookbook_dir
+        Chef::CookbookLoader.should_receive(:new).with(@cookbook_dir).and_return(@cookbook_loader)
+        @knife.run
+      end
+
+      describe 'and with -o or --cookbook-path' do
+        it 'should look in the provided path and generate cookbook metadata' do
+          @knife.config[:cookbook_path] = '/opt/chef/cookbooks'
+          Chef::CookbookLoader.should_receive(:new).with('/opt/chef/cookbooks').and_return(@cookbook_loader)
+          @knife.run
+        end
+      end
+    end
+
+  end
+
+  describe 'generate_metadata' do
+    before(:each) do
+      @knife.config[:cookbook_path] = @cookbook_dir
+      File.stub!(:expand_path).with("#{@cookbook_dir}/foobar/metadata.rb").
+                                    and_return("#{@cookbook_dir}/foobar/metadata.rb")
+    end
+
+    it 'should generate the metadata from metadata.rb if it exists' do
+      File.should_receive(:exists?).with("#{@cookbook_dir}/foobar/metadata.rb").
+                                    and_return(true)
+      @knife.should_receive(:generate_metadata_from_file).with('foobar', "#{@cookbook_dir}/foobar/metadata.rb")
+      @knife.run
+    end
+
+    it 'should validate the metadata json if metadata.rb does not exist' do
+      File.should_receive(:exists?).with("#{@cookbook_dir}/foobar/metadata.rb").
+                                    and_return(false)
+      @knife.should_receive(:validate_metadata_json).with(@cookbook_dir, 'foobar')
+      @knife.run
+    end
+  end
+
+  describe 'generate_metadata_from_file' do
+    before(:each) do
+      @metadata_mock = mock('metadata')
+      @json_file_mock = mock('json_file')
+    end
+
+    it 'should generate the metatdata json from metatdata.rb' do
+      Chef::Cookbook::Metadata.stub!(:new).and_return(@metadata_mock)
+      @metadata_mock.should_receive(:name).with('foobar')
+      @metadata_mock.should_receive(:from_file).with("#{@cookbook_dir}/foobar/metadata.rb")
+      File.should_receive(:open).with("#{@cookbook_dir}/foobar/metadata.json", 'w').
+                                 and_yield(@json_file_mock)
+      @json_file_mock.should_receive(:write).with(@json_data)
+      Chef::JSONCompat.should_receive(:to_json_pretty).with(@metadata_mock).
+                                                       and_return(@json_data)
+      @knife.generate_metadata_from_file('foobar', "#{@cookbook_dir}/foobar/metadata.rb")
+      @stdout.string.should match /generating metadata for foobar from #{@cookbook_dir}\/foobar\/metadata\.rb/im
+    end
+
+    { Chef::Exceptions::ObsoleteDependencySyntax => 'obsolote dependency',
+      Chef::Exceptions::InvalidVersionConstraint => 'invalid version constraint'
+    }.each_pair do |klass, description|
+      it "should print an error and exit when an #{description} syntax exception is encountered" do
+        exception = klass.new("#{description} blah")
+        Chef::Cookbook::Metadata.stub!(:new).and_raise(exception)
+        lambda {
+          @knife.generate_metadata_from_file('foobar', "#{@cookbook_dir}/foobar/metadata.rb")
+        }.should raise_error(SystemExit)
+        @stderr.string.should match /error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im
+        @stderr.string.should match /in #{@cookbook_dir}\/foobar\/metadata\.rb/im
+        @stderr.string.should match /#{description} blah/im
+      end
+    end
+  end
+
+  describe 'validate_metadata_json' do
+    it 'should validate the metadata json' do
+      File.should_receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json").
+                                   and_return(true)
+      IO.should_receive(:read).with("#{@cookbook_dir}/foobar/metadata.json").
+                               and_return(@json_data)
+      Chef::Cookbook::Metadata.should_receive(:validate_json).with(@json_data)
+      @knife.validate_metadata_json(@cookbook_dir, 'foobar')
+    end
+
+    it 'should not try to validate the metadata json if the file does not exist' do
+      File.should_receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json").
+                                   and_return(false)
+      IO.should_not_receive(:read)
+      Chef::Cookbook::Metadata.should_not_receive(:validate_json)
+      @knife.validate_metadata_json(@cookbook_dir, 'foobar')
+    end
+
+    { Chef::Exceptions::ObsoleteDependencySyntax => 'obsolote dependency',
+      Chef::Exceptions::InvalidVersionConstraint => 'invalid version constraint'
+    }.each_pair do |klass, description|
+      it "should print an error and exit when an #{description} syntax exception is encountered" do
+        File.should_receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json").
+                                     and_return(true)
+        IO.should_receive(:read).with("#{@cookbook_dir}/foobar/metadata.json").
+                                 and_return(@json_data)
+        exception = klass.new("#{description} blah")
+        Chef::Cookbook::Metadata.stub!(:validate_json).and_raise(exception)
+        lambda {
+          @knife.validate_metadata_json(@cookbook_dir, 'foobar')
+        }.should raise_error(SystemExit)
+        @stderr.string.should match /error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im
+        @stderr.string.should match /in #{@cookbook_dir}\/foobar\/metadata\.json/im
+        @stderr.string.should match /#{description} blah/im
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/knife/cookbook_show_spec.rb b/spec/unit/knife/cookbook_show_spec.rb
new file mode 100644
index 0000000..96fe738
--- /dev/null
+++ b/spec/unit/knife/cookbook_show_spec.rb
@@ -0,0 +1,223 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, eersion 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.
+#
+
+# rename to cookbook not coookbook
+require 'spec_helper'
+
+describe Chef::Knife::CookbookShow do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::CookbookShow.new
+    @knife.config = { }
+    @knife.name_args = [ "cookbook_name" ]
+    @rest = mock(Chef::REST)
+    @knife.stub!(:rest).and_return(@rest)
+    @knife.stub!(:pretty_print).and_return(true)
+    @knife.stub!(:output).and_return(true)
+  end
+
+  describe "run" do
+    describe "with 0 arguments: help" do
+      it 'should should print usage and exit when given no arguments' do
+        @knife.name_args = []
+        @knife.should_receive(:show_usage)
+        @knife.ui.should_receive(:fatal)
+        lambda { @knife.run }.should raise_error(SystemExit)
+      end
+    end
+
+    describe "with 1 argument: versions" do
+      before(:each) do
+        @response = {
+          "cookbook_name" => {
+            "url" => "http://url/cookbooks/cookbook_name",
+            "versions" => [
+              { "version" => "0.10.0", "url" => "http://url/cookbooks/cookbook_name/0.10.0" },
+              { "version" => "0.9.0", "url" => "http://url/cookbookx/cookbook_name/0.9.0" },
+              { "version" => "0.8.0", "url" => "http://url/cookbooks/cookbook_name/0.8.0" }
+            ]
+          }
+        }
+      end
+
+      it "should show the raw cookbook data" do
+        @rest.should_receive(:get_rest).with("cookbooks/cookbook_name").and_return(@response)
+        @knife.should_receive(:format_cookbook_list_for_display).with(@response)
+        @knife.run
+      end
+
+      it "should respect the user-supplied environment" do
+        @knife.config[:environment] = "foo"
+        @rest.should_receive(:get_rest).with("environments/foo/cookbooks/cookbook_name").and_return(@response)
+        @knife.should_receive(:format_cookbook_list_for_display).with(@response)
+        @knife.run
+      end
+    end
+
+    describe "with 2 arguments: name and version" do
+      before(:each) do
+        @knife.name_args << "0.1.0"
+        @response = { "0.1.0" => { "recipes" => {"default.rb" => ""} } }
+      end
+
+      it "should show the specific part of a cookbook" do
+        @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@response)
+        @knife.should_receive(:output).with(@response)
+        @knife.run
+      end
+    end
+
+    describe "with 3 arguments: name, version, and segment" do
+      before(:each) do
+        @knife.name_args = [ "cookbook_name", "0.1.0", "recipes" ]
+        @cookbook_response = Chef::CookbookVersion.new("cookbook_name")
+        @manifest = {
+          "recipes" => [
+            {
+              :name => "default.rb",
+              :path => "recipes/default.rb",
+              :checksum => "1234",
+              :url => "http://example.org/files/default.rb"
+            }
+          ]
+        }
+        @cookbook_response.manifest = @manifest
+        @response = {"name"=>"default.rb", "url"=>"http://example.org/files/default.rb", "checksum"=>"1234", "path"=>"recipes/default.rb"}
+      end
+
+      it "should print the json of the part" do
+        @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response)
+        @knife.should_receive(:output).with(@cookbook_response.manifest["recipes"])
+        @knife.run
+      end
+    end
+
+    describe "with 4 arguments: name, version, segment and filename" do
+      before(:each) do
+        @knife.name_args = [ "cookbook_name", "0.1.0", "recipes", "default.rb" ]
+        @cookbook_response = Chef::CookbookVersion.new("cookbook_name")
+        @cookbook_response.manifest = {
+          "recipes" => [
+            {
+              :name => "default.rb",
+              :path => "recipes/default.rb",
+              :checksum => "1234",
+              :url => "http://example.org/files/default.rb"
+            }
+          ]
+        }
+        @response = "Example recipe text"
+      end
+
+      it "should print the raw result of the request (likely a file!)" do
+        @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response)
+        @rest.should_receive(:get_rest).with("http://example.org/files/default.rb", true).and_return(StringIO.new(@response))
+        @knife.should_receive(:pretty_print).with(@response)
+        @knife.run
+      end
+    end
+
+    describe "with 4 arguments: name, version, segment and filename -- with specificity" do
+      before(:each) do
+        @knife.name_args = [ "cookbook_name", "0.1.0", "files", "afile.rb" ]
+        @cookbook_response = Chef::CookbookVersion.new("cookbook_name")
+        @cookbook_response.manifest = {
+          "files" => [
+            {
+              :name => "afile.rb",
+              :path => "files/host-examplehost.example.org/afile.rb",
+              :checksum => "1111",
+              :specificity => "host-examplehost.example.org",
+              :url => "http://example.org/files/1111"
+            },
+            {
+              :name => "afile.rb",
+              :path => "files/ubuntu-9.10/afile.rb",
+              :checksum => "2222",
+              :specificity => "ubuntu-9.10",
+              :url => "http://example.org/files/2222"
+            },
+            {
+              :name => "afile.rb",
+              :path => "files/ubuntu/afile.rb",
+              :checksum => "3333",
+              :specificity => "ubuntu",
+              :url => "http://example.org/files/3333"
+            },
+            {
+              :name => "afile.rb",
+              :path => "files/default/afile.rb",
+              :checksum => "4444",
+              :specificity => "default",
+              :url => "http://example.org/files/4444"
+            },
+          ]
+        }
+
+        @response = "Example recipe text"
+      end
+
+      describe "with --fqdn" do
+        it "should pass the fqdn" do
+          @knife.config[:platform] = "example_platform"
+          @knife.config[:platform_version] = "1.0"
+          @knife.config[:fqdn] = "examplehost.example.org"
+          @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response)
+          @rest.should_receive(:get_rest).with("http://example.org/files/1111", true).and_return(StringIO.new(@response))
+          @knife.should_receive(:pretty_print).with(@response)
+          @knife.run
+        end
+      end
+
+      describe "and --platform" do
+        it "should pass the platform" do
+          @knife.config[:platform] = "ubuntu"
+          @knife.config[:platform_version] = "1.0"
+          @knife.config[:fqdn] = "differenthost.example.org"
+          @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response)
+          @rest.should_receive(:get_rest).with("http://example.org/files/3333", true).and_return(StringIO.new(@response))
+          @knife.should_receive(:pretty_print).with(@response)
+          @knife.run
+        end
+      end
+
+      describe "and --platform-version" do
+        it "should pass the platform" do
+          @knife.config[:platform] = "ubuntu"
+          @knife.config[:platform_version] = "9.10"
+          @knife.config[:fqdn] = "differenthost.example.org"
+          @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response)
+          @rest.should_receive(:get_rest).with("http://example.org/files/2222", true).and_return(StringIO.new(@response))
+          @knife.should_receive(:pretty_print).with(@response)
+          @knife.run
+        end
+      end
+
+      describe "with none of the arguments, it should use the default" do
+        it "should pass them all" do
+          @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response)
+          @rest.should_receive(:get_rest).with("http://example.org/files/4444", true).and_return(StringIO.new(@response))
+          @knife.should_receive(:pretty_print).with(@response)
+          @knife.run
+        end
+      end
+
+    end
+  end
+end
+
diff --git a/spec/unit/knife/cookbook_site_download_spec.rb b/spec/unit/knife/cookbook_site_download_spec.rb
new file mode 100644
index 0000000..a3d43c5
--- /dev/null
+++ b/spec/unit/knife/cookbook_site_download_spec.rb
@@ -0,0 +1,151 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2012 Thomas Bishop
+# 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 File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe Chef::Knife::CookbookSiteDownload do
+
+  describe 'run' do
+    before do
+      @knife            = Chef::Knife::CookbookSiteDownload.new
+      @knife.name_args  = ['apache2']
+      @noauth_rest      = mock 'no auth rest'
+      @stdout           = StringIO.new
+      @cookbook_api_url = 'http://cookbooks.opscode.com/api/v1/cookbooks'
+      @version          = '1.0.2'
+      @version_us       = @version.gsub '.', '_'
+      @current_data     = { 'deprecated'       => false,
+                            'latest_version'   => "#{@cookbook_api_url}/apache2/versions/#{@version_us}",
+                            'replacement' => 'other_apache2' }
+
+      @knife.ui.stub(:stdout).and_return(@stdout)
+      @knife.stub(:noauth_rest).and_return(@noauth_rest)
+      @noauth_rest.should_receive(:get_rest).
+                   with("#{@cookbook_api_url}/apache2").
+                   and_return(@current_data)
+    end
+
+    context 'when the cookbook is deprecated and not forced' do
+      before do
+        @current_data['deprecated'] = true
+      end
+
+      it 'should warn with info about the replacement' do
+        @knife.ui.should_receive(:warn).
+                  with(/.+deprecated.+replaced by other_apache2.+/i)
+        @knife.ui.should_receive(:warn).
+                  with(/use --force.+download.+/i)
+        @knife.run
+      end
+    end
+
+    context 'when' do
+      before do
+        @cookbook_data = { 'version' => @version,
+                           'file'    => "http://example.com/apache2_#{@version_us}.tgz" }
+        @temp_file     =  stub :path => "/tmp/apache2_#{@version_us}.tgz"
+        @file          = File.join(Dir.pwd, "apache2-#{@version}.tar.gz")
+
+        @noauth_rest.should_receive(:sign_on_redirect=).with(false)
+      end
+
+      context 'downloading the latest version' do
+        before do
+          @noauth_rest.should_receive(:get_rest).
+                       with(@current_data['latest_version']).
+                       and_return(@cookbook_data)
+          @noauth_rest.should_receive(:get_rest).
+                       with(@cookbook_data['file'], true).
+                       and_return(@temp_file)
+        end
+
+        context 'and it is deprecated and with --force' do
+          before do
+            @current_data['deprecated'] = true
+            @knife.config[:force] = true
+          end
+
+          it 'should download the latest version' do
+            @knife.ui.should_receive(:warn).
+                      with(/.+deprecated.+replaced by other_apache2.+/i)
+            FileUtils.should_receive(:cp).with(@temp_file.path, @file)
+            @knife.run
+            @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i
+            @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i
+          end
+
+        end
+
+        it 'should download the latest version' do
+          FileUtils.should_receive(:cp).with(@temp_file.path, @file)
+          @knife.run
+          @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i
+          @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i
+        end
+
+        context 'with -f or --file' do
+          before do
+            @file = '/opt/chef/cookbooks/apache2.tar.gz'
+            @knife.config[:file] = @file
+            FileUtils.should_receive(:cp).with(@temp_file.path, @file)
+          end
+
+          it 'should download the cookbook to the desired file' do
+            @knife.run
+            @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i
+            @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i
+          end
+        end
+
+        it 'should provide an accessor to the version' do
+          FileUtils.stub(:cp).and_return(true)
+          @knife.version.should == @version
+          @knife.run
+        end
+      end
+
+      context 'downloading a cookbook of a specific version' do
+        before do
+          @version         = '1.0.1'
+          @version_us      = @version.gsub '.', '_'
+          @cookbook_data   = { 'version' => @version,
+                               'file'    => "http://example.com/apache2_#{@version_us}.tgz" }
+          @temp_file       = stub :path => "/tmp/apache2_#{@version_us}.tgz"
+          @file            = File.join(Dir.pwd, "apache2-#{@version}.tar.gz")
+          @knife.name_args << @version
+        end
+
+        it 'should download the desired version' do
+          @noauth_rest.should_receive(:get_rest).
+                       with("#{@cookbook_api_url}/apache2/versions/#{@version_us}").
+                       and_return(@cookbook_data)
+          @noauth_rest.should_receive(:get_rest).
+                       with(@cookbook_data['file'], true).
+                       and_return(@temp_file)
+          FileUtils.should_receive(:cp).with(@temp_file.path, @file)
+          @knife.run
+          @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i
+          @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i
+        end
+      end
+
+    end
+
+  end
+
+end
diff --git a/spec/unit/knife/cookbook_site_install_spec.rb b/spec/unit/knife/cookbook_site_install_spec.rb
new file mode 100644
index 0000000..f7d7a39
--- /dev/null
+++ b/spec/unit/knife/cookbook_site_install_spec.rb
@@ -0,0 +1,148 @@
+#
+# Author:: Steven Danna (<steve at opscode.com>)
+# Copyright:: Copyright (c) 2011 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Knife::CookbookSiteInstall do
+  before(:each) do
+    require 'chef/knife/core/cookbook_scm_repo'
+    @knife = Chef::Knife::CookbookSiteInstall.new
+    @knife.config = {}
+    if Chef::Platform.windows?
+      @install_path = 'C:/tmp/chef'
+    else
+      @install_path = '/var/tmp/chef'
+    end
+    @knife.config[:cookbook_path] = [ @install_path ]
+
+    @stdout = StringIO.new
+    @stderr = StringIO.new
+    @knife.stub!(:stderr).and_return(@stdout)
+    @knife.stub!(:stdout).and_return(@stdout)
+
+    #Assume all external commands would have succeed. :(
+    File.stub!(:unlink)
+    File.stub!(:rmtree)
+    @knife.stub!(:shell_out!).and_return(true)
+
+    #CookbookSiteDownload Stup
+    @downloader = {}
+    @knife.stub!(:download_cookbook_to).and_return(@downloader)
+    @downloader.stub!(:version).and_return do
+      if @knife.name_args.size == 2
+        @knife.name_args[1]
+      else
+        "0.3.0"
+      end
+    end
+
+    #Stubs for CookbookSCMRepo
+    @repo = stub(:sanity_check => true, :reset_to_default_state => true,
+                 :prepare_to_import => true, :finalize_updates_to => true,
+                 :merge_updates_from => true)
+    Chef::Knife::CookbookSCMRepo.stub!(:new).and_return(@repo)
+  end
+
+
+  describe "run" do
+    it "should return an error if a cookbook name is not provided" do
+      @knife.name_args = []
+      @knife.ui.should_receive(:error).with("Please specify a cookbook to download and install.")
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it "should return an error if more than two arguments are given" do
+      @knife.name_args = ["foo", "bar", "baz"]
+      @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it "should return an error if the second argument is not a version" do
+      @knife.name_args = ["getting-started", "1pass"]
+      @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it "should return an error if the second argument is a four-digit version" do
+      @knife.name_args = ["getting-started", "0.0.0.1"]
+      @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it "should return an error if the second argument is a one-digit version" do
+      @knife.name_args = ["getting-started", "1"]
+      @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.")
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it "should install the specified version if second argument is a three-digit version" do
+      @knife.name_args = ["getting-started", "0.1.0"]
+      @knife.config[:no_deps] = true
+      upstream_file = File.join(@install_path, "getting-started.tar.gz")
+      @knife.should_receive(:download_cookbook_to).with(upstream_file)
+      @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1.0")
+      @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
+      @repo.should_receive(:merge_updates_from).with("getting-started", "0.1.0")
+      @knife.run
+    end
+
+    it "should install the specified version if second argument is a two-digit version" do
+      @knife.name_args = ["getting-started", "0.1"]
+      @knife.config[:no_deps] = true
+      upstream_file = File.join(@install_path, "getting-started.tar.gz")
+      @knife.should_receive(:download_cookbook_to).with(upstream_file)
+      @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1")
+      @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
+      @repo.should_receive(:merge_updates_from).with("getting-started", "0.1")
+      @knife.run
+    end
+
+    it "should install the latest version if only a cookbook name is given" do
+      @knife.name_args = ["getting-started"]
+      @knife.config[:no_deps] = true
+      upstream_file = File.join(@install_path, "getting-started.tar.gz")
+      @knife.should_receive(:download_cookbook_to).with(upstream_file)
+      @knife.should_receive(:extract_cookbook).with(upstream_file, "0.3.0")
+      @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
+      @repo.should_receive(:merge_updates_from).with("getting-started", "0.3.0")
+      @knife.run
+    end
+
+    it "should not create/reset git branches if use_current_branch is set" do
+      @knife.name_args = ["getting-started"]
+      @knife.config[:use_current_branch] = true
+      @knife.config[:no_deps] = true
+      upstream_file = File.join(@install_path, "getting-started.tar.gz")
+      @repo.should_not_receive(:prepare_to_import)
+      @repo.should_not_receive(:reset_to_default_state)
+      @knife.run
+    end
+
+    it "should not raise an error if cookbook_path is a string" do
+      @knife.config[:cookbook_path] = @install_path
+      @knife.config[:no_deps] = true
+      @knife.name_args = ["getting-started"]
+      upstream_file = File.join(@install_path, "getting-started.tar.gz")
+      @knife.should_receive(:download_cookbook_to).with(upstream_file)
+      @knife.should_receive(:extract_cookbook).with(upstream_file, "0.3.0")
+      @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started"))
+      @repo.should_receive(:merge_updates_from).with("getting-started", "0.3.0")
+      lambda { @knife.run }.should_not raise_error
+    end
+  end
+end
diff --git a/spec/unit/knife/cookbook_site_share_spec.rb b/spec/unit/knife/cookbook_site_share_spec.rb
new file mode 100644
index 0000000..3b912af
--- /dev/null
+++ b/spec/unit/knife/cookbook_site_share_spec.rb
@@ -0,0 +1,146 @@
+#
+# Author:: Stephen Delano (<stephen at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'chef/cookbook_uploader'
+require 'chef/cookbook_site_streaming_uploader'
+
+describe Chef::Knife::CookbookSiteShare do
+
+  before(:each) do
+    @knife = Chef::Knife::CookbookSiteShare.new
+    @knife.name_args = ['cookbook_name', 'AwesomeSausage']
+
+    @cookbook = Chef::CookbookVersion.new('cookbook_name')
+
+    @cookbook_loader = mock('Chef::CookbookLoader')
+    @cookbook_loader.stub!(:cookbook_exists?).and_return(true)
+    @cookbook_loader.stub!(:[]).and_return(@cookbook)
+    Chef::CookbookLoader.stub!(:new).and_return(@cookbook_loader)
+
+    @cookbook_uploader = Chef::CookbookUploader.new('herpderp', File.join(CHEF_SPEC_DATA, 'cookbooks'), :rest => "norest")
+    Chef::CookbookUploader.stub!(:new).and_return(@cookbook_uploader)
+    @cookbook_uploader.stub!(:validate_cookbooks).and_return(true)
+    Chef::CookbookSiteStreamingUploader.stub!(:create_build_dir).and_return(Dir.mktmpdir)
+
+    Chef::Mixin::Command.stub(:run_command).and_return(true)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe 'run' do
+
+    before(:each) do
+      @knife.stub!(:do_upload).and_return(true)
+    end
+
+    it 'should should print usage and exit when given no arguments' do
+      @knife.name_args = []
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should print usage and exit when given only 1 argument' do
+      @knife.name_args = ['cookbook_name']
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should check if the cookbook exists' do
+      @cookbook_loader.should_receive(:cookbook_exists?)
+      @knife.run
+    end
+
+    it "should exit and log to error if the cookbook doesn't exist" do
+      @cookbook_loader.stub(:cookbook_exists?).and_return(false)
+      @knife.ui.should_receive(:error)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should make a tarball of the cookbook' do
+      Chef::Mixin::Command.should_receive(:run_command) { |args|
+        args[:command].should match /tar -czf/
+      }
+      @knife.run
+    end
+
+    it 'should exit and log to error when the tarball creation fails' do
+      Chef::Mixin::Command.stub!(:run_command).and_raise(Chef::Exceptions::Exec)
+      @knife.ui.should_receive(:error)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should upload the cookbook and clean up the tarball' do
+      @knife.should_receive(:do_upload)
+      FileUtils.should_receive(:rm_rf)
+      @knife.run
+    end
+  end
+
+  describe 'do_upload' do
+
+    before(:each) do
+      @upload_response = mock('Net::HTTPResponse')
+      Chef::CookbookSiteStreamingUploader.stub!(:post).and_return(@upload_response)
+
+      @stdout = StringIO.new
+      @stderr = StringIO.new
+      @knife.ui.stub!(:stdout).and_return(@stdout)
+      @knife.ui.stub!(:stderr).and_return(@stderr)
+      File.stub(:open).and_return(true)
+    end
+
+    it 'should post the cookbook to "http://cookbooks.opscode.com"' do
+      response_text = {:uri => 'http://cookbooks.opscode.com/cookbooks/cookbook_name'}.to_json
+      @upload_response.stub!(:body).and_return(response_text)
+      @upload_response.stub!(:code).and_return(201)
+      Chef::CookbookSiteStreamingUploader.should_receive(:post).with(/cookbooks\.opscode\.com/, anything(), anything(), anything())
+      @knife.run
+    end
+
+    it 'should alert the user when a version already exists' do
+      response_text = {:error_messages => ['Version already exists']}.to_json
+      @upload_response.stub!(:body).and_return(response_text)
+      @upload_response.stub!(:code).and_return(409)
+      lambda { @knife.run }.should raise_error(SystemExit)
+      @stderr.string.should match(/ERROR(.+)cookbook already exists/)
+    end
+
+    it 'should pass any errors on to the user' do
+      response_text = {:error_messages => ["You're holding it wrong"]}.to_json
+      @upload_response.stub!(:body).and_return(response_text)
+      @upload_response.stub!(:code).and_return(403)
+      lambda { @knife.run }.should raise_error(SystemExit)
+      @stderr.string.should match("ERROR(.*)You're holding it wrong")
+    end
+
+    it 'should print the body if no errors are exposed on failure' do
+      response_text = {:system_error => "Your call was dropped", :reason => "There's a map for that"}.to_json
+      @upload_response.stub!(:body).and_return(response_text)
+      @upload_response.stub!(:code).and_return(500)
+      @knife.ui.should_receive(:error).with(/#{Regexp.escape(response_text)}/)#.ordered
+      @knife.ui.should_receive(:error).with(/Unknown error/)#.ordered
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+  end
+
+end
diff --git a/spec/unit/knife/cookbook_site_unshare_spec.rb b/spec/unit/knife/cookbook_site_unshare_spec.rb
new file mode 100644
index 0000000..ffba2ec
--- /dev/null
+++ b/spec/unit/knife/cookbook_site_unshare_spec.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Stephen Delano (<stephen at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::CookbookSiteUnshare do
+
+  before(:each) do
+    @knife = Chef::Knife::CookbookSiteUnshare.new
+    @knife.name_args = ['cookbook_name']
+    @knife.stub!(:confirm).and_return(true)
+
+    @rest = mock('Chef::REST')
+    @rest.stub!(:delete_rest).and_return(true)
+    @knife.stub!(:rest).and_return(@rest)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe 'run' do
+
+    describe 'with no cookbook argument' do
+      it 'should print the usage and exit' do
+        @knife.name_args = []
+        @knife.ui.should_receive(:fatal)
+        @knife.should_receive(:show_usage)
+        lambda { @knife.run }.should raise_error(SystemExit)
+      end
+    end
+
+    it 'should confirm you want to unshare the cookbook' do
+      @knife.should_receive(:confirm)
+      @knife.run
+    end
+
+    it 'should send a delete request to the cookbook site' do
+      @rest.should_receive(:delete_rest)
+      @knife.run
+    end
+
+    it 'should log an error and exit when forbidden' do
+      exception = mock('403 "Forbidden"', :code => '403')
+      @rest.stub!(:delete_rest).and_raise(Net::HTTPServerException.new('403 "Forbidden"', exception))
+      @knife.ui.should_receive(:error)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    it 'should re-raise any non-forbidden errors on delete_rest' do
+      exception = mock('500 "Application Error"', :code => '500')
+      @rest.stub(:delete_rest).and_raise(Net::HTTPServerException.new('500 "Application Error"', exception))
+      lambda { @knife.run }.should raise_error(Net::HTTPServerException)
+    end
+
+    it 'should log a success message' do
+      @knife.ui.should_receive(:info)
+      @knife.run
+    end
+
+  end
+
+end
diff --git a/spec/unit/knife/cookbook_test_spec.rb b/spec/unit/knife/cookbook_test_spec.rb
new file mode 100644
index 0000000..24c658d
--- /dev/null
+++ b/spec/unit/knife/cookbook_test_spec.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Stephen Delano (<stephen at opscode.com>)$
+# Author:: Matthew Kent (<mkent at magoazul.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.$
+# Copyright:: Copyright (c) 2010 Matthew Kent
+# 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'
+Chef::Knife::CookbookTest.load_deps
+
+describe Chef::Knife::CookbookTest do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::CookbookTest.new
+    @knife.config[:cookbook_path] = File.join(CHEF_SPEC_DATA,'cookbooks')
+    @knife.cookbook_loader.stub!(:cookbook_exists?).and_return(true)
+    @cookbooks = []
+    %w{tats central_market jimmy_johns pho}.each do |cookbook_name|
+      @cookbooks << Chef::CookbookVersion.new(cookbook_name)
+    end
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "should test the cookbook" do
+      @knife.stub!(:test_cookbook).and_return(true)
+      @knife.name_args = ["italian"]
+      @knife.should_receive(:test_cookbook).with("italian")
+      @knife.run
+    end
+
+    it "should test multiple cookbooks when provided" do
+      @knife.stub!(:test_cookbook).and_return(true)
+      @knife.name_args = ["tats", "jimmy_johns"]
+      @knife.should_receive(:test_cookbook).with("tats")
+      @knife.should_receive(:test_cookbook).with("jimmy_johns")
+      @knife.should_not_receive(:test_cookbook).with("central_market")
+      @knife.should_not_receive(:test_cookbook).with("pho")
+      @knife.run
+    end
+
+    it "should test both ruby and templates" do
+      @knife.name_args = ["example"]
+      @knife.config[:cookbook_path].should_not be_empty
+      Array(@knife.config[:cookbook_path]).reverse.each do |path|
+        @knife.should_receive(:test_ruby).with(an_instance_of(Chef::Cookbook::SyntaxCheck))
+        @knife.should_receive(:test_templates).with(an_instance_of(Chef::Cookbook::SyntaxCheck))
+      end
+      @knife.run
+    end
+
+    describe "with -a or --all" do
+      it "should test all of the cookbooks" do
+        @knife.stub!(:test_cookbook).and_return(true)
+        @knife.config[:all] = true
+        @loader = {}
+        @loader.stub!(:load_cookbooks).and_return(@loader)
+        @cookbooks.each do |cookbook|
+          @loader[cookbook.name] = cookbook
+        end
+        @knife.stub!(:cookbook_loader).and_return(@loader)
+        @loader.each do |key, cookbook|
+          @knife.should_receive(:test_cookbook).with(cookbook.name)
+        end
+        @knife.run
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/knife/cookbook_upload_spec.rb b/spec/unit/knife/cookbook_upload_spec.rb
new file mode 100644
index 0000000..d9f1b8f
--- /dev/null
+++ b/spec/unit/knife/cookbook_upload_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Matthew Kent (<mkent at magoazul.com>)
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+require 'chef/cookbook_uploader'
+require 'timeout'
+
+describe Chef::Knife::CookbookUpload do
+  before(:each) do
+    @knife = Chef::Knife::CookbookUpload.new
+    @knife.name_args = ['test_cookbook']
+
+    @cookbook = Chef::CookbookVersion.new('test_cookbook')
+
+    @cookbook_loader = {}
+    @cookbook_loader.stub!(:[]).and_return(@cookbook)
+    @cookbook_loader.stub!(:merged_cookbooks).and_return([])
+    @cookbook_loader.stub!(:load_cookbooks).and_return(@cookbook_loader)
+    Chef::CookbookLoader.stub!(:new).and_return(@cookbook_loader)
+
+    @output = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@output)
+    @knife.ui.stub!(:stderr).and_return(@output)
+  end
+
+  describe 'run' do
+    before(:each) do
+      @cookbook_uploader = stub(:upload_cookbooks => nil)
+      Chef::CookbookUploader.stub(:new => @cookbook_uploader)
+      Chef::CookbookVersion.stub(:list_all_versions).and_return({})
+    end
+
+    it 'should print usage and exit when a cookbook name is not provided' do
+      @knife.name_args = []
+      @knife.should_receive(:show_usage)
+      @knife.ui.should_receive(:fatal)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    describe 'when specifying a cookbook name' do
+      it 'should upload the cookbook' do
+        @knife.should_receive(:upload).once
+        @knife.run
+      end
+
+      it 'should report on success' do
+        @knife.should_receive(:upload).once
+        @knife.ui.should_receive(:info).with(/Uploaded 1 cookbook/)
+        @knife.run
+      end
+    end
+
+    describe 'when specifying the same cookbook name twice' do
+      it 'should upload the cookbook only once' do
+        @knife.name_args = ['test_cookbook', 'test_cookbook']
+        @knife.should_receive(:upload).once
+        @knife.run
+      end
+    end
+
+    describe 'when specifying a cookbook name among many' do
+      before(:each) do
+        @knife.name_args = ['test_cookbook1']
+        @cookbooks = {
+          'test_cookbook1' => Chef::CookbookVersion.new('test_cookbook1'),
+          'test_cookbook2' => Chef::CookbookVersion.new('test_cookbook2'),
+          'test_cookbook3' => Chef::CookbookVersion.new('test_cookbook3')
+        }
+        @cookbook_loader = {}
+        @cookbook_loader.stub!(:merged_cookbooks).and_return([])
+        @cookbook_loader.stub(:[]) { |ckbk| @cookbooks[ckbk] }
+        Chef::CookbookLoader.stub!(:new).and_return(@cookbook_loader)
+      end
+
+      it "should read only one cookbook" do
+        @cookbook_loader.should_receive(:[]).once.with('test_cookbook1')
+        @knife.run
+      end
+
+      it "should not read all cookbooks" do
+        @cookbook_loader.should_not_receive(:load_cookbooks)
+        @knife.run
+      end
+
+      it "should upload only one cookbook" do
+        @knife.should_receive(:upload).exactly(1).times
+        @knife.run
+      end
+    end
+
+    # This is testing too much.  We should break it up.
+    describe 'when specifying a cookbook name with dependencies' do
+      it "should upload all dependencies once" do
+        @knife.name_args = ["test_cookbook2"]
+        @knife.config[:depends] = true
+        @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1')
+        @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2')
+        @test_cookbook3 = Chef::CookbookVersion.new('test_cookbook3')
+        @test_cookbook2.metadata.depends("test_cookbook3")
+        @test_cookbook3.metadata.depends("test_cookbook1")
+        @test_cookbook3.metadata.depends("test_cookbook2")
+        @cookbook_loader.stub!(:[])  do |ckbk|
+          { "test_cookbook1" =>  @test_cookbook1,
+            "test_cookbook2" =>  @test_cookbook2,
+            "test_cookbook3" => @test_cookbook3 }[ckbk]
+        end
+        @knife.stub!(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2", "test_cookbook3"])
+        @knife.should_receive(:upload).exactly(3).times
+        Timeout::timeout(5) do
+          @knife.run
+        end.should_not raise_error(Timeout::Error)
+      end
+    end
+
+    it "should freeze the version of the cookbooks if --freeze is specified" do
+      @knife.config[:freeze] = true
+      @cookbook.should_receive(:freeze_version).once
+      @knife.run
+    end
+
+    describe 'with -a or --all' do
+      before(:each) do
+        @knife.config[:all] = true
+        @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1')
+        @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2')
+        @cookbook_loader.stub!(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2)
+        @cookbook_loader.stub!(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2"])
+      end
+
+      it 'should upload all cookbooks' do
+        @knife.should_receive(:upload).once
+        @knife.run
+      end
+
+      it 'should report on success' do
+        @knife.should_receive(:upload).once
+        @knife.ui.should_receive(:info).with(/Uploaded all cookbooks/)
+        @knife.run
+      end
+
+      it 'should update the version constraints for an environment' do
+        @knife.stub!(:assert_environment_valid!).and_return(true)
+        @knife.config[:environment] = "production"
+        @knife.should_receive(:update_version_constraints).once
+        @knife.run
+      end
+    end
+
+    describe 'when a frozen cookbook exists on the server' do
+      it 'should fail to replace it' do
+        exception = Chef::Exceptions::CookbookFrozen.new
+        @cookbook_uploader.should_receive(:upload_cookbooks).
+          and_raise(exception)
+        @knife.ui.stub(:error)
+        @knife.ui.should_receive(:error).with(exception)
+        lambda { @knife.run }.should raise_error(SystemExit)
+      end
+
+      it 'should not update the version constraints for an environment' do
+        @knife.stub!(:assert_environment_valid!).and_return(true)
+        @knife.config[:environment] = "production"
+        @knife.stub!(:upload).and_raise(Chef::Exceptions::CookbookFrozen)
+        @knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/)
+        @knife.ui.should_receive(:warn).with(/Not updating version constraints/)
+        @knife.should_not_receive(:update_version_constraints)
+        lambda { @knife.run }.should raise_error(SystemExit)
+      end
+    end
+  end # run
+end
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
new file mode 100644
index 0000000..47261e2
--- /dev/null
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -0,0 +1,160 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2011 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/knife/core/bootstrap_context'
+
+describe Chef::Knife::Core::BootstrapContext do
+  let(:config) { {:foo => :bar} }
+  let(:run_list) { Chef::RunList.new('recipe[tmux]', 'role[base]') }
+  let(:chef_config) do
+    {
+      :validation_key => File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem'),
+      :chef_server_url => 'http://chef.example.com:4444',
+      :validation_client_name => 'chef-validator-testing'
+    }
+  end
+  let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') }
+
+  subject(:bootstrap_context) { described_class.new(config, run_list, chef_config) }
+
+  it "installs the same version of chef on the remote host" do
+    bootstrap_context.bootstrap_version_string.should eq "--version #{Chef::VERSION}"
+  end
+
+  it "runs chef with the first-boot.json in the _default environment" do
+    bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default"
+  end
+
+  it "reads the validation key" do
+    bootstrap_context.validation_key.should eq IO.read(File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem'))
+  end
+
+  it "generates the config file data" do
+    expected=<<-EXPECTED
+log_level        :auto
+log_location     STDOUT
+chef_server_url  "http://chef.example.com:4444"
+validation_client_name "chef-validator-testing"
+# Using default node name (fqdn)
+EXPECTED
+    bootstrap_context.config_content.should eq expected
+  end
+
+  describe "alternate chef-client path" do
+    let(:chef_config){ {:chef_client_path => '/usr/local/bin/chef-client'} }
+    it "runs chef-client from another path when specified" do
+      bootstrap_context.start_chef.should eq "/usr/local/bin/chef-client -j /etc/chef/first-boot.json -E _default"
+    end
+  end
+
+  describe "validation key path that contains a ~" do
+    let(:chef_config){ {:validation_key => '~/my.key'} }
+    it "reads the validation key when it contains a ~" do
+      IO.should_receive(:read).with(File.expand_path("my.key", ENV['HOME']))
+      bootstrap_context.validation_key
+    end
+  end
+
+  describe "when an explicit node name is given" do
+    let(:config){ {:chef_node_name => 'foobar.example.com' }}
+    it "sets the node name in the client.rb" do
+      bootstrap_context.config_content.should match(/node_name "foobar\.example\.com"/)
+    end
+  end
+
+  describe "when bootstrapping into a specific environment" do
+    let(:chef_config){ {:environment => "prodtastic"} }
+    it "starts chef in the configured environment" do
+      bootstrap_context.start_chef.should == 'chef-client -j /etc/chef/first-boot.json -E prodtastic'
+    end
+  end
+
+  describe "when installing a prerelease version of chef" do
+    let(:config){ {:prerelease => true }}
+    it "supplies --prerelease as the version string" do
+      bootstrap_context.bootstrap_version_string.should eq '--prerelease'
+    end
+  end
+
+  describe "when installing an explicit version of chef" do
+    let(:chef_config) do
+      {
+        :knife => { :bootstrap_version => '123.45.678' }
+      }
+    end
+    it "gives --version $VERSION as the version string" do
+      bootstrap_context.bootstrap_version_string.should eq '--version 123.45.678'
+    end
+  end
+
+  describe "when JSON attributes are given" do
+    let(:config) { {:first_boot_attributes => {:baz => :quux}} }
+    it "adds the attributes to first_boot" do
+      bootstrap_context.first_boot.to_json.should eq({:baz => :quux, :run_list => run_list}.to_json)
+    end
+  end
+
+  describe "when JSON attributes are NOT given" do
+    it "sets first_boot equal to run_list" do
+      bootstrap_context.first_boot.to_json.should eq({:run_list => run_list}.to_json)
+    end
+  end
+
+  describe "when an encrypted_data_bag_secret is provided" do
+    context "via config[:secret]" do
+      let(:chef_config) do
+        {
+          :knife => {:secret => "supersekret" }
+        }
+      end
+      it "reads the encrypted_data_bag_secret" do
+        bootstrap_context.encrypted_data_bag_secret.should eq "supersekret"
+      end
+    end
+
+    context "via config[:secret_file]" do
+      let(:chef_config) do
+        {
+          :knife => {:secret_file =>  secret_file}
+        }
+      end
+      it "reads the encrypted_data_bag_secret" do
+        bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file)
+      end
+    end
+  end
+
+  describe "to support compatibility with existing templates" do
+    it "sets the @config instance variable" do
+      bootstrap_context.instance_variable_get(:@config).should eq config
+    end
+
+    it "sets the @run_list instance variable" do
+      bootstrap_context.instance_variable_get(:@run_list).should eq run_list
+    end
+
+    describe "accepts encrypted_data_bag_secret via Chef::Config" do
+      let(:chef_config) { {:encrypted_data_bag_secret => secret_file }}
+      it "reads the encrypted_data_bag_secret" do
+        bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file)
+      end
+    end
+  end
+end
+
diff --git a/spec/unit/knife/core/cookbook_scm_repo_spec.rb b/spec/unit/knife/core/cookbook_scm_repo_spec.rb
new file mode 100644
index 0000000..629164a
--- /dev/null
+++ b/spec/unit/knife/core/cookbook_scm_repo_spec.rb
@@ -0,0 +1,187 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2011 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/knife/core/cookbook_scm_repo'
+
+describe Chef::Knife::CookbookSCMRepo do
+  before do
+    @repo_path = File.join(CHEF_SPEC_DATA, 'cookbooks')
+    @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
+    @ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
+    @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, :default_branch => 'master')
+
+    @branch_list = Mixlib::ShellOut.new
+    @branch_list.stdout.replace(<<-BRANCHES)
+  chef-vendor-apache2
+  chef-vendor-build-essential
+  chef-vendor-dynomite
+  chef-vendor-ganglia
+  chef-vendor-graphite
+  chef-vendor-python
+  chef-vendor-absent-new
+BRANCHES
+  end
+
+  it "has a path to the cookbook repo" do
+    @cookbook_repo.repo_path.should == @repo_path
+  end
+
+  it "has a default branch" do
+    @cookbook_repo.default_branch.should == 'master'
+  end
+
+  describe "when sanity checking the repo" do
+    it "exits when the directory does not exist" do
+      ::File.should_receive(:directory?).with(@repo_path).and_return(false)
+      lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit)
+    end
+
+    describe "and the repo dir exists" do
+      before do
+        ::File.stub!(:directory?).with(@repo_path).and_return(true)
+      end
+
+      it "exits when there is no git repo" do
+        ::File.stub!(:directory?).with(/.*\.git/).and_return(false)
+        lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit)
+      end
+
+      describe "and the repo is a git repo" do
+        before do
+          ::File.stub!(:directory?).with(File.join(@repo_path, '.git')).and_return(true)
+        end
+
+        it "exits when the default branch doesn't exist" do
+          @nobranches = Mixlib::ShellOut.new.tap {|s|s.stdout.replace "\n"}
+          @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@nobranches)
+          lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit)
+        end
+
+        describe "and the default branch exists" do
+          before do
+            @master_branch = Mixlib::ShellOut.new
+            @master_branch.stdout.replace "* master\n"
+            @cookbook_repo.should_receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@master_branch)
+          end
+
+          it "exits when the git repo is dirty" do
+            @dirty_status = Mixlib::ShellOut.new
+            @dirty_status.stdout.replace(<<-DIRTY)
+ M chef/lib/chef/knife/cookbook_site_vendor.rb
+DIRTY
+            @cookbook_repo.should_receive(:shell_out!).with('git status --porcelain', :cwd => @repo_path).and_return(@dirty_status)
+            lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit)
+          end
+
+          describe "and the repo is clean" do
+            before do
+              @clean_status = Mixlib::ShellOut.new.tap {|s| s.stdout.replace("\n")}
+              @cookbook_repo.stub!(:shell_out!).with('git status --porcelain', :cwd => @repo_path).and_return(@clean_status)
+            end
+
+            it "passes the sanity check" do
+              @cookbook_repo.sanity_check
+            end
+
+          end
+        end
+      end
+    end
+  end
+
+  it "resets to default state by checking out the default branch" do
+    @cookbook_repo.should_receive(:shell_out!).with('git checkout master', :cwd => @repo_path)
+    @cookbook_repo.reset_to_default_state
+  end
+
+  it "determines if a the pristine copy branch exists" do
+    @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list)
+    @cookbook_repo.branch_exists?("chef-vendor-apache2").should be_true
+    @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list)
+    @cookbook_repo.branch_exists?("chef-vendor-nginx").should be_false
+  end
+
+  it "determines if a the branch not exists correctly without substring search" do
+    @cookbook_repo.should_receive(:shell_out!).twice.with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list)
+    @cookbook_repo.should_not be_branch_exists("chef-vendor-absent")
+    @cookbook_repo.should be_branch_exists("chef-vendor-absent-new")
+  end
+
+  describe "when the pristine copy branch does not exist" do
+    it "prepares for import by creating the pristine copy branch" do
+      @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list)
+      @cookbook_repo.should_receive(:shell_out!).with('git checkout -b chef-vendor-nginx', :cwd => @repo_path)
+      @cookbook_repo.prepare_to_import("nginx")
+    end
+  end
+
+  describe "when the pristine copy branch does exist" do
+    it "prepares for import by checking out the pristine copy branch" do
+      @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list)
+      @cookbook_repo.should_receive(:shell_out!).with('git checkout chef-vendor-apache2', :cwd => @repo_path)
+      @cookbook_repo.prepare_to_import("apache2")
+    end
+  end
+
+  describe "when the pristine copy branch was not updated by the changes" do
+    before do
+      @updates = Mixlib::ShellOut.new
+      @updates.stdout.replace("\n")
+      @cookbook_repo.stub!(:shell_out!).with('git status --porcelain -- apache2', :cwd => @repo_path).and_return(@updates)
+    end
+
+    it "shows no changes in the pristine copy" do
+      @cookbook_repo.updated?('apache2').should be_false
+    end
+
+    it "does nothing to finalize the updates" do
+      @cookbook_repo.finalize_updates_to('apache2', '1.2.3').should be_false
+    end
+  end
+
+  describe "when the pristine copy branch was updated by the changes" do
+    before do
+      @updates = Mixlib::ShellOut.new
+      @updates.stdout.replace(" M cookbooks/apache2/recipes/default.rb\n")
+      @cookbook_repo.stub!(:shell_out!).with('git status --porcelain -- apache2', :cwd => @repo_path).and_return(@updates)
+    end
+
+    it "shows changes in the pristine copy" do
+      @cookbook_repo.updated?('apache2').should be_true
+    end
+
+    it "commits the changes to the repo and tags the commit" do
+      @cookbook_repo.should_receive(:shell_out!).with("git add apache2", :cwd => @repo_path)
+      @cookbook_repo.should_receive(:shell_out!).with("git commit -m \"Import apache2 version 1.2.3\" -- apache2", :cwd => @repo_path)
+      @cookbook_repo.should_receive(:shell_out!).with("git tag -f cookbook-site-imported-apache2-1.2.3", :cwd => @repo_path)
+      @cookbook_repo.finalize_updates_to("apache2", "1.2.3").should be_true
+    end
+  end
+
+  describe "when a custom default branch is specified" do
+    before do
+      @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, :default_branch => 'develop')
+    end
+
+    it "resets to default state by checking out the default branch" do
+      @cookbook_repo.should_receive(:shell_out!).with('git checkout develop', :cwd => @repo_path)
+      @cookbook_repo.reset_to_default_state
+    end
+  end
+end
diff --git a/spec/unit/knife/core/object_loader_spec.rb b/spec/unit/knife/core/object_loader_spec.rb
new file mode 100644
index 0000000..b3456e2
--- /dev/null
+++ b/spec/unit/knife/core/object_loader_spec.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Juanje Ojeda (<juanje.ojeda at gmail.com>)
+# Copyright:: Copyright (c) 2011-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/knife/core/object_loader'
+
+describe Chef::Knife::Core::ObjectLoader do
+  before(:each) do
+    @knife = Chef::Knife.new
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    Dir.chdir(File.join(CHEF_SPEC_DATA, 'object_loader'))
+  end
+
+  shared_examples_for "Chef object" do |chef_class|
+    it "should create a #{chef_class} object" do
+      @object.should be_a_kind_of(chef_class)
+    end
+
+    it "should has a attribute 'name'" do
+      @object.name.should eql('test')
+    end
+  end
+
+  {
+    'nodes' => Chef::Node,
+    'roles' => Chef::Role,
+    'environments' => Chef::Environment
+  }.each do |repo_location, chef_class|
+
+    describe "when the file is a #{chef_class}" do
+      before do
+        @loader = Chef::Knife::Core::ObjectLoader.new(chef_class, @knife.ui)
+      end
+
+      describe "when the file is a Ruby" do
+        before do
+          @object = @loader.load_from(repo_location, 'test.rb')
+        end
+
+        it_behaves_like "Chef object", chef_class
+      end
+
+      #NOTE: This is check for the bug described at CHEF-2352
+      describe "when the file is a JSON" do
+        describe "and it has defined 'json_class'" do
+          before do
+            @object = @loader.load_from(repo_location, 'test_json_class.json')
+          end
+
+          it_behaves_like "Chef object", chef_class
+        end
+
+        describe "and it has not defined 'json_class'" do
+          before do
+            @object = @loader.load_from(repo_location, 'test.json')
+          end
+
+          it_behaves_like "Chef object", chef_class
+        end
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb
new file mode 100644
index 0000000..3af1a8c
--- /dev/null
+++ b/spec/unit/knife/core/subcommand_loader_spec.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::SubcommandLoader do
+  before do
+    @home = File.join(CHEF_SPEC_DATA, 'knife-home')
+    @env = {'HOME' => @home}
+    @loader = Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'), @env)
+  end
+
+  it "builds a list of the core subcommand file require paths" do
+    @loader.subcommand_files.should_not be_empty
+    @loader.subcommand_files.each do |require_path|
+      require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/)
+    end
+  end
+
+  it "finds files installed via rubygems" do
+    @loader.find_subcommands_via_rubygems.should include('chef/knife/node_create')
+    @loader.find_subcommands_via_rubygems.each {|rel_path, abs_path| abs_path.should match(%r[chef/knife/.+])}
+  end
+
+  it "finds files from latest version of installed gems" do
+    gems = [ double('knife-ec2-0.5.12') ]
+    gem_files = [
+      '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb',
+      '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb'
+    ]
+    $LOAD_PATH.should_receive(:map).and_return([])
+    if Gem::Specification.respond_to? :latest_specs
+      Gem::Specification.should_receive(:latest_specs).and_return(gems)
+      gems[0].should_receive(:matches_for_glob).with(/chef\/knife\/\*\.rb{(.*),\.rb,(.*)}/).and_return(gem_files)
+    else
+      Gem.source_index.should_receive(:latest_specs).and_return(gems)
+      gems[0].should_receive(:require_paths).twice.and_return(['lib'])
+      gems[0].should_receive(:full_gem_path).and_return('/usr/lib/ruby/gems/knife-ec2-0.5.12')
+      Dir.should_receive(:[]).with('/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb').and_return(gem_files)
+    end
+    @loader.should_receive(:find_subcommands_via_dirglob).and_return({})
+    @loader.find_subcommands_via_rubygems.values.select { |file| file =~ /knife-ec2/ }.sort.should == gem_files
+  end
+
+  it "finds files using a dirglob when rubygems is not available" do
+    @loader.find_subcommands_via_dirglob.should include('chef/knife/node_create')
+    @loader.find_subcommands_via_dirglob.each {|rel_path, abs_path| abs_path.should match(%r[chef/knife/.+])}
+  end
+
+  it "finds user-specific subcommands in the user's ~/.chef directory" do
+    expected_command = File.join(@home, '.chef', 'plugins', 'knife', 'example_home_subcommand.rb')
+    @loader.site_subcommands.should include(expected_command)
+  end
+
+  it "finds repo specific subcommands by searching for a .chef directory" do
+    expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb')
+    @loader.site_subcommands.should include(expected_command)
+  end
+end
diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb
new file mode 100644
index 0000000..18e88b6
--- /dev/null
+++ b/spec/unit/knife/core/ui_spec.rb
@@ -0,0 +1,468 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: John Keiser (<jkeiser at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2011, 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::UI do
+  before do
+    @out, @err, @in = StringIO.new, StringIO.new, StringIO.new
+    @config = {}
+    @ui = Chef::Knife::UI.new(@out, @err, @in, @config)
+  end
+
+  describe "edit" do
+    ruby_for_json = { 'foo' => 'bar' }
+    json_from_ruby = "{\n  \"foo\": \"bar\"\n}"
+    json_from_editor = "{\n  \"bar\": \"foo\"\n}"
+    ruby_from_editor = { 'bar' => 'foo' }
+    my_editor = "veeeye"
+    temp_path = "/tmp/bar/baz"
+
+    let(:subject) { @ui.edit_data(ruby_for_json, parse_output) }
+    let(:parse_output) { false }
+
+    context "when editing is disabled" do
+      before do
+        @ui.config[:disable_editing] = true
+        stub_const("Tempfile", double)  # Tempfiles should never be invoked
+      end
+      context "when parse_output is false" do
+        it "returns pretty json string" do
+          expect(subject).to eql(json_from_ruby)
+        end
+      end
+      context "when parse_output is true" do
+        let(:parse_output) { true }
+        it "returns a ruby object" do
+          expect(subject).to eql(ruby_for_json)
+        end
+      end
+
+    end
+
+    context "when editing is enabled" do
+      before do
+        @ui.config[:disable_editing] = false
+        @ui.config[:editor] = my_editor
+        @mock = mock('Tempfile')
+        @mock.should_receive(:sync=).with(true)
+        @mock.should_receive(:puts).with(json_from_ruby)
+        @mock.should_receive(:close)
+        @mock.should_receive(:path).at_least(:once).and_return(temp_path)
+        Tempfile.should_receive(:open).with([ 'knife-edit-', '.json' ]).and_yield(@mock)
+      end
+      context "and the editor works" do
+        before do
+          @ui.should_receive(:system).with("#{my_editor} #{temp_path}").and_return(true)
+          IO.should_receive(:read).with(temp_path).and_return(json_from_editor)
+        end
+
+        context "when parse_output is false" do
+          it "returns an edited pretty json string" do
+            expect(subject).to eql(json_from_editor)
+          end
+        end
+        context "when parse_output is true" do
+          let(:parse_output) { true }
+          it "returns an edited ruby object" do
+            expect(subject).to eql(ruby_from_editor)
+          end
+        end
+      end
+      context "when running the editor fails with nil" do
+        before do
+          @ui.should_receive(:system).with("#{my_editor} #{temp_path}").and_return(nil)
+          IO.should_not_receive(:read)
+        end
+        it "throws an exception" do
+          expect{ subject }.to raise_error(RuntimeError)
+        end
+      end
+      context "when running the editor fails with false" do
+        before do
+          @ui.should_receive(:system).with("#{my_editor} #{temp_path}").and_return(false)
+          IO.should_not_receive(:read)
+        end
+        it "throws an exception" do
+          expect{ subject }.to raise_error(RuntimeError)
+        end
+      end
+    end
+    context "when editing and not stubbing Tempfile (semi-functional test)" do
+      before do
+        @ui.config[:disable_editing] = false
+        @ui.config[:editor] = my_editor
+        @tempfile = Tempfile.new([ 'knife-edit-', '.json' ])
+        Tempfile.should_receive(:open).with([ 'knife-edit-', '.json' ]).and_yield(@tempfile)
+      end
+
+      context "and the editor works" do
+        before do
+          @ui.should_receive(:system).with("#{my_editor} #{@tempfile.path}").and_return(true)
+          IO.should_receive(:read).with(@tempfile.path).and_return(json_from_editor)
+        end
+
+        context "when parse_output is false" do
+          it "returns an edited pretty json string" do
+            expect(subject).to eql(json_from_editor)
+          end
+          it "the tempfile should have mode 0600", :unix_only do
+            # XXX: this looks odd because we're really testing Tempfile.new here
+            expect(File.stat(@tempfile.path).mode & 0777).to eql(0600)
+            expect(subject).to eql(json_from_editor)
+          end
+        end
+
+        context "when parse_output is true" do
+          let(:parse_output) { true }
+          it "returns an edited ruby object" do
+            expect(subject).to eql(ruby_from_editor)
+          end
+          it "the tempfile should have mode 0600", :unix_only do
+            # XXX: this looks odd because we're really testing Tempfile.new here
+            expect(File.stat(@tempfile.path).mode & 0777).to eql(0600)
+            expect(subject).to eql(ruby_from_editor)
+          end
+        end
+      end
+    end
+  end
+
+  describe "format_list_for_display" do
+    it "should print the full hash if --with-uri is true" do
+      @ui.config[:with_uri] = true
+      @ui.format_list_for_display({ :marcy => :playground }).should == { :marcy => :playground }
+    end
+
+    it "should print only the keys if --with-uri is false" do
+      @ui.config[:with_uri] = false
+      @ui.format_list_for_display({ :marcy => :playground }).should == [ :marcy ]
+    end
+  end
+
+  shared_examples "an output mehthod handling IO exceptions" do |method|
+    it "should throw Errno::EIO exceptions" do
+      @out.stub(:puts).and_raise(Errno::EIO)
+      @err.stub(:puts).and_raise(Errno::EIO)
+      lambda {@ui.send(method, "hi")}.should raise_error(Errno::EIO)
+    end
+
+    it "should ignore Errno::EPIPE exceptions (CHEF-3516)" do
+      @out.stub(:puts).and_raise(Errno::EPIPE)
+      @err.stub(:puts).and_raise(Errno::EPIPE)
+      lambda {@ui.send(method, "hi")}.should_not raise_error(Errno::EPIPE)
+    end
+
+    it "should throw Errno::EPIPE exceptions with -VV (CHEF-3516)" do
+      @config[:verbosity] = 2
+      @out.stub(:puts).and_raise(Errno::EPIPE)
+      @err.stub(:puts).and_raise(Errno::EPIPE)
+      lambda {@ui.send(method, "hi")}.should raise_error(Errno::EPIPE)
+    end
+  end
+
+  describe "output" do
+    it_behaves_like "an output mehthod handling IO exceptions", :output
+
+    it "formats strings appropriately" do
+      @ui.output("hi")
+      @out.string.should == "hi\n"
+    end
+
+    it "formats hashes appropriately" do
+      @ui.output({'hi' => 'a', 'lo' => 'b' })
+      @out.string.should == <<EOM
+hi: a
+lo: b
+EOM
+    end
+
+    it "formats empty hashes appropriately" do
+      @ui.output({})
+      @out.string.should == "\n"
+    end
+
+    it "formats arrays appropriately" do
+      @ui.output([ 'a', 'b' ])
+      @out.string.should == <<EOM
+a
+b
+EOM
+    end
+
+    it "formats empty arrays appropriately" do
+      @ui.output([ ])
+      @out.string.should == "\n"
+    end
+
+    it "formats single-member arrays appropriately" do
+      @ui.output([ 'a' ])
+      @out.string.should == "a\n"
+    end
+
+    it "formats nested single-member arrays appropriately" do
+      @ui.output([ [ 'a' ] ])
+      @out.string.should == "a\n"
+    end
+
+    it "formats nested arrays appropriately" do
+      @ui.output([ [ 'a', 'b' ], [ 'c', 'd' ]])
+      @out.string.should == <<EOM
+a
+b
+
+c
+d
+EOM
+    end
+
+    it "formats nested arrays with single- and empty subarrays appropriately" do
+      @ui.output([ [ 'a', 'b' ], [ 'c' ], [], [ 'd', 'e' ]])
+      @out.string.should == <<EOM
+a
+b
+
+c
+
+
+d
+e
+EOM
+    end
+
+    it "formats arrays of hashes with extra lines in between for readability" do
+      @ui.output([ { 'a' => 'b', 'c' => 'd' }, { 'x' => 'y' }, { 'm' => 'n', 'o' => 'p' }])
+      @out.string.should == <<EOM
+a: b
+c: d
+
+x: y
+
+m: n
+o: p
+EOM
+    end
+
+    it "formats hashes with empty array members appropriately" do
+      @ui.output({ 'a' => [], 'b' => 'c' })
+      @out.string.should == <<EOM
+a:
+b: c
+EOM
+    end
+
+    it "formats hashes with single-member array values appropriately" do
+      @ui.output({ 'a' => [ 'foo' ], 'b' => 'c' })
+      @out.string.should == <<EOM
+a: foo
+b: c
+EOM
+    end
+
+    it "formats hashes with array members appropriately" do
+      @ui.output({ 'a' => [ 'foo', 'bar' ], 'b' => 'c' })
+      @out.string.should == <<EOM
+a:
+  foo
+  bar
+b: c
+EOM
+    end
+
+    it "formats hashes with single-member nested array values appropriately" do
+      @ui.output({ 'a' => [ [ 'foo' ] ], 'b' => 'c' })
+      @out.string.should == <<EOM
+a:
+  foo
+b: c
+EOM
+    end
+
+    it "formats hashes with nested array values appropriately" do
+      @ui.output({ 'a' => [ [ 'foo', 'bar' ], [ 'baz', 'bjork' ] ], 'b' => 'c' })
+      @out.string.should == <<EOM
+a:
+  foo
+  bar
+  
+  baz
+  bjork
+b: c
+EOM
+    end
+
+    it "formats hashes with hash values appropriately" do
+      @ui.output({ 'a' => { 'aa' => 'bb', 'cc' => 'dd' }, 'b' => 'c' })
+      @out.string.should == <<EOM
+a:
+  aa: bb
+  cc: dd
+b: c
+EOM
+    end
+
+    it "formats hashes with empty hash values appropriately" do
+      @ui.output({ 'a' => { }, 'b' => 'c' })
+      @out.string.should == <<EOM
+a:
+b: c
+EOM
+    end
+  end
+
+  describe "warn" do
+    it_behaves_like "an output mehthod handling IO exceptions", :warn
+  end
+
+  describe "error" do
+    it_behaves_like "an output mehthod handling IO exceptions", :warn
+  end
+
+  describe "fatal" do
+    it_behaves_like "an output mehthod handling IO exceptions", :warn
+  end
+
+  describe "format_for_display" do
+    it "should return the raw data" do
+      input = { :gi => :go }
+      @ui.format_for_display(input).should == input
+    end
+
+    describe "with --attribute passed" do
+      it "should return the deeply nested attribute" do
+        input = { "gi" => { "go" => "ge" }, "id" => "sample-data-bag-item" }
+        @ui.config[:attribute] = "gi.go"
+        @ui.format_for_display(input).should == { "sample-data-bag-item" => { "gi.go" => "ge" } }
+      end
+
+      it "should return multiple attributes" do
+        input = { "gi" =>  "go", "hi" => "ho", "id" => "sample-data-bag-item" }
+        @ui.config[:attribute] = ["gi", "hi"]
+        @ui.format_for_display(input).should == { "sample-data-bag-item" => { "gi" => "go", "hi"=> "ho" } }
+      end
+    end
+
+    describe "with --run-list passed" do
+      it "should return the run list" do
+        input = Chef::Node.new
+        input.name("sample-node")
+        input.run_list("role[monkey]", "role[churchmouse]")
+        @ui.config[:run_list] = true
+        response = @ui.format_for_display(input)
+        response["sample-node"]["run_list"][0].should == "role[monkey]"
+        response["sample-node"]["run_list"][1].should == "role[churchmouse]"
+      end
+    end
+  end
+
+  describe "format_cookbook_list_for_display" do
+    before(:each) do
+      @item = {
+        "cookbook_name" => {
+          "url" => "http://url/cookbooks/cookbook",
+          "versions" => [
+            { "version" => "3.0.0", "url" => "http://url/cookbooks/3.0.0" },
+            { "version" => "2.0.0", "url" => "http://url/cookbooks/2.0.0" },
+            { "version" => "1.0.0", "url" => "http://url/cookbooks/1.0.0" }
+          ]
+        }
+      }
+    end
+
+    it "should return an array of the cookbooks with versions" do
+      expected_response = [ "cookbook_name   3.0.0  2.0.0  1.0.0" ]
+      response = @ui.format_cookbook_list_for_display(@item)
+      response.should == expected_response
+    end
+
+    describe "with --with-uri" do
+      it "should return the URIs" do
+        response = {
+          "cookbook_name"=>{
+            "1.0.0" => "http://url/cookbooks/1.0.0",
+            "2.0.0" => "http://url/cookbooks/2.0.0",
+            "3.0.0" => "http://url/cookbooks/3.0.0"}
+        }
+        @ui.config[:with_uri] = true
+        @ui.format_cookbook_list_for_display(@item).should == response
+      end
+    end
+  end
+
+  describe "confirm" do
+    before(:each) do
+      @question = "monkeys rule"
+      @stdout = StringIO.new
+      @ui.stub(:stdout).and_return(@stdout)
+      @ui.stdin.stub!(:readline).and_return("y")
+    end
+
+    it "should return true if you answer Y" do
+      @ui.stdin.stub!(:readline).and_return("Y")
+      @ui.confirm(@question).should == true
+    end
+
+    it "should return true if you answer y" do
+      @ui.stdin.stub!(:readline).and_return("y")
+      @ui.confirm(@question).should == true
+    end
+
+    it "should exit 3 if you answer N" do
+      @ui.stdin.stub!(:readline).and_return("N")
+      lambda {
+        @ui.confirm(@question)
+      }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+    end
+
+    it "should exit 3 if you answer n" do
+      @ui.stdin.stub!(:readline).and_return("n")
+      lambda {
+        @ui.confirm(@question)
+      }.should raise_error(SystemExit) { |e| e.status.should == 3 }
+    end
+
+    describe "with --y or --yes passed" do
+      it "should return true" do
+        @ui.config[:yes] = true
+        @ui.confirm(@question).should == true
+      end
+    end
+
+    describe "when asking for free-form user input" do
+      it "asks a question and returns the answer provided by the user" do
+        out = StringIO.new
+        @ui.stub!(:stdout).and_return(out)
+        @ui.stub!(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n"))
+        @ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com"
+        out.string.should == "your chef server URL?"
+      end
+
+      it "suggests a default setting and returns the default when the user's response only contains whitespace" do
+        out = StringIO.new
+        @ui.stub!(:stdout).and_return(out)
+        @ui.stub!(:stdin).and_return(StringIO.new(" \n"))
+        @ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000"
+        out.string.should == "your chef server URL? [http://localhost:4000] "
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb
new file mode 100644
index 0000000..0ac9b6f
--- /dev/null
+++ b/spec/unit/knife/data_bag_create_spec.rb
@@ -0,0 +1,110 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright (c) 2009-2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tempfile'
+
+module ChefSpecs
+  class ChefRest
+    attr_reader :args_received
+    def initialize
+      @args_received = []
+    end
+
+    def post_rest(*args)
+      @args_received << args
+    end
+  end
+end
+
+
+describe Chef::Knife::DataBagCreate do
+  before do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::DataBagCreate.new
+    @rest = ChefSpecs::ChefRest.new
+    @knife.stub!(:rest).and_return(@rest)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+
+  it "creates a data bag when given one argument" do
+    @knife.name_args = ['sudoing_admins']
+    @rest.should_receive(:post_rest).with("data", {"name" => "sudoing_admins"})
+    @knife.ui.should_receive(:info).with("Created data_bag[sudoing_admins]")
+
+    @knife.run
+  end
+
+  it "creates a data bag item when given two arguments" do
+    @knife.name_args = ['sudoing_admins', 'ME']
+    user_supplied_hash = {"login_name" => "alphaomega", "id" => "ME"}
+    data_bag_item = Chef::DataBagItem.from_hash(user_supplied_hash)
+    data_bag_item.data_bag("sudoing_admins")
+    @knife.should_receive(:create_object).and_yield(user_supplied_hash)
+    @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered
+    @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered
+
+    @knife.run
+  end
+
+  describe "encrypted data bag items" do
+    before(:each) do
+      @secret = "abc123SECRET"
+      @plain_data = {"login_name" => "alphaomega", "id" => "ME"}
+      @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
+                                                                   @secret)
+      @knife.name_args = ['sudoing_admins', 'ME']
+      @knife.should_receive(:create_object).and_yield(@plain_data)
+      data_bag_item = Chef::DataBagItem.from_hash(@enc_data)
+      data_bag_item.data_bag("sudoing_admins")
+
+      # Random IV is used each time the data bag item is encrypted, so values
+      # will not be equal if we re-encrypt.
+      Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data)
+
+      @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered
+      @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered
+
+      @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
+      @secret_file.puts(@secret)
+      @secret_file.flush
+    end
+
+    after do
+      @secret_file.close
+      @secret_file.unlink
+    end
+
+    it "creates an encrypted data bag item via --secret" do
+      @knife.stub!(:config).and_return({:secret => @secret})
+      @knife.run
+    end
+
+    it "creates an encrypted data bag item via --secret_file" do
+      secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
+      secret_file.puts(@secret)
+      secret_file.flush
+      @knife.stub!(:config).and_return({:secret_file => secret_file.path})
+      @knife.run
+    end
+  end
+
+end
diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb
new file mode 100644
index 0000000..412a237
--- /dev/null
+++ b/spec/unit/knife/data_bag_edit_spec.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'tempfile'
+
+describe Chef::Knife::DataBagEdit do
+  before do
+    @plain_data = {"login_name" => "alphaomega", "id" => "item_name"}
+    @edited_data = {
+      "login_name" => "rho", "id" => "item_name",
+      "new_key" => "new_value" }
+
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+
+    @knife = Chef::Knife::DataBagEdit.new
+    @rest = mock('chef-rest-mock')
+    @knife.stub!(:rest).and_return(@rest)
+
+    @stdout = StringIO.new
+    @knife.stub!(:stdout).and_return(@stdout)
+    @log = Chef::Log
+    @knife.name_args = ['bag_name', 'item_name']
+  end
+
+  it "requires data bag and item arguments" do
+    @knife.name_args = []
+    lambda { @knife.run }.should raise_error(SystemExit)
+    @stdout.string.should match(/^You must supply the data bag and an item to edit/)
+  end
+
+  it "saves edits on a data bag item" do
+    Chef::DataBagItem.stub!(:load).with('bag_name', 'item_name').and_return(@plain_data)
+    @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
+    @rest.should_receive(:put_rest).with("data/bag_name/item_name", @edited_data).ordered
+    @knife.run
+  end
+
+  describe "encrypted data bag items" do
+    before(:each) do
+      @secret = "abc123SECRET"
+      @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
+                                                                   @secret)
+      @enc_edited_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@edited_data,
+                                                                          @secret)
+      Chef::DataBagItem.stub!(:load).with('bag_name', 'item_name').and_return(@enc_data)
+
+      # Random IV is used each time the data bag item is encrypted, so values
+      # will not be equal if we encrypt same value twice.
+      Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_edited_data)
+
+      @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
+      @secret_file.puts(@secret)
+      @secret_file.flush
+    end
+
+    after do
+      @secret_file.close
+      @secret_file.unlink
+    end
+
+    it "decrypts and encrypts via --secret" do
+      @knife.stub!(:config).and_return({:secret => @secret})
+      @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
+      @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered
+
+      @knife.run
+    end
+
+    it "decrypts and encrypts via --secret_file" do
+      @knife.stub!(:config).and_return({:secret_file => @secret_file.path})
+      @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data)
+      @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered
+
+      @knife.run
+    end
+  end
+end
diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb
new file mode 100644
index 0000000..cef1bab
--- /dev/null
+++ b/spec/unit/knife/data_bag_from_file_spec.rb
@@ -0,0 +1,196 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'chef/data_bag_item'
+require 'chef/encrypted_data_bag_item'
+require 'tempfile'
+require 'json'
+
+Chef::Knife::DataBagFromFile.load_deps
+
+describe Chef::Knife::DataBagFromFile do
+  before :each do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::DataBagFromFile.new
+    @rest = mock("Chef::REST")
+    @knife.stub!(:rest).and_return(@rest)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @tmp_dir = Dir.mktmpdir
+    @db_folder = File.join(@tmp_dir, 'data_bags', 'bag_name')
+    FileUtils.mkdir_p(@db_folder)
+    @db_file = Tempfile.new(["data_bag_from_file_test", ".json"], @db_folder)
+    @db_file2 = Tempfile.new(["data_bag_from_file_test2", ".json"], @db_folder)
+    @db_folder2 = File.join(@tmp_dir, 'data_bags', 'bag_name2')
+    FileUtils.mkdir_p(@db_folder2)
+    @db_file3 = Tempfile.new(["data_bag_from_file_test3", ".json"], @db_folder2)
+    @plain_data = {
+        "id" => "item_name",
+        "greeting" => "hello",
+        "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
+    }
+    @db_file.write(@plain_data.to_json)
+    @db_file.flush
+    @knife.instance_variable_set(:@name_args, ['bag_name', @db_file.path])
+  end
+
+  # We have to explicitly clean up Tempfile on Windows because it said so.
+  after :each do
+    @db_file.close
+    @db_file2.close
+    @db_file3.close
+    FileUtils.rm_rf(@db_folder)
+    FileUtils.rm_rf(@db_folder2)
+    FileUtils.remove_entry_secure @tmp_dir
+  end
+
+  it "loads from a file and saves" do
+    @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
+    dbag = Chef::DataBagItem.new
+    Chef::DataBagItem.stub!(:new).and_return(dbag)
+    dbag.should_receive(:save)
+    @knife.run
+
+    dbag.data_bag.should == 'bag_name'
+    dbag.raw_data.should == @plain_data
+  end
+
+  it "loads all from a mutiple files and saves" do
+    @knife.name_args = [ 'bag_name', @db_file.path, @db_file2.path ]
+    @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
+    @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data)
+    dbag = Chef::DataBagItem.new
+    Chef::DataBagItem.stub!(:new).and_return(dbag)
+    dbag.should_receive(:save).twice
+    @knife.run
+
+    dbag.data_bag.should == 'bag_name'
+    dbag.raw_data.should == @plain_data
+  end
+
+  it "loads all from a folder and saves" do
+    dir = File.dirname(@db_file.path)
+    @knife.name_args = [ 'bag_name', @db_folder ]
+    @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
+    @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data)
+    dbag = Chef::DataBagItem.new
+    Chef::DataBagItem.stub!(:new).and_return(dbag)
+    dbag.should_receive(:save).twice
+    @knife.run
+  end
+
+  describe "loading all data bags" do
+
+    before do
+      @pwd = Dir.pwd
+      Dir.chdir(@tmp_dir)
+    end
+
+    after do
+      Dir.chdir(@pwd)
+    end
+
+    it "loads all data bags when -a or --all options is provided" do
+      @knife.name_args = []
+      @knife.stub!(:config).and_return({:all => true})
+      @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file.path)).
+        and_return(@plain_data)
+      @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file2.path)).
+        and_return(@plain_data)
+      @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)).
+        and_return(@plain_data)
+      dbag = Chef::DataBagItem.new
+      Chef::DataBagItem.stub!(:new).and_return(dbag)
+      dbag.should_receive(:save).exactly(3).times
+      @knife.run
+    end
+
+    it "loads all data bags items when -a or --all options is provided" do
+      @knife.name_args = ["bag_name2"]
+      @knife.stub!(:config).and_return({:all => true})
+      @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)).
+        and_return(@plain_data)
+      dbag = Chef::DataBagItem.new
+      Chef::DataBagItem.stub!(:new).and_return(dbag)
+      dbag.should_receive(:save)
+      @knife.run
+      dbag.data_bag.should == 'bag_name2'
+      dbag.raw_data.should == @plain_data
+    end
+
+  end
+
+  describe "encrypted data bag items" do
+    before(:each) do
+      @secret = "abc123SECRET"
+      @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
+                                                                   @secret)
+
+      # Random IV is used each time the data bag item is encrypted, so values
+      # will not be equal if we re-encrypt.
+      Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data)
+
+      @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
+      @secret_file.puts(@secret)
+      @secret_file.flush
+    end
+
+    after do
+      @secret_file.close
+      @secret_file.unlink
+    end
+
+    it "encrypts values when given --secret" do
+      @knife.stub!(:config).and_return({:secret => @secret})
+
+      @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", @db_file.path).and_return(@plain_data)
+      dbag = Chef::DataBagItem.new
+      Chef::DataBagItem.stub!(:new).and_return(dbag)
+      dbag.should_receive(:save)
+      @knife.run
+      dbag.data_bag.should == 'bag_name'
+      dbag.raw_data.should == @enc_data
+    end
+
+    it "encrypts values when given --secret_file" do
+      @knife.stub!(:config).and_return({:secret_file => @secret_file.path})
+
+      @knife.loader.stub!(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data)
+      dbag = Chef::DataBagItem.new
+      Chef::DataBagItem.stub!(:new).and_return(dbag)
+      dbag.should_receive(:save)
+      @knife.run
+      dbag.data_bag.should == 'bag_name'
+      dbag.raw_data.should == @enc_data
+    end
+
+  end
+
+  describe "command line parsing" do
+    it "prints help if given no arguments" do
+      @knife.instance_variable_set(:@name_args, [])
+      lambda { @knife.run }.should raise_error(SystemExit)
+      help_text = "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
+      help_text_regex = Regexp.new("^#{Regexp.escape(help_text)}")
+      @stdout.string.should match(help_text_regex)
+    end
+  end
+
+end
diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb
new file mode 100644
index 0000000..d64b4f2
--- /dev/null
+++ b/spec/unit/knife/data_bag_show_spec.rb
@@ -0,0 +1,112 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright (c) 2008-2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'chef/data_bag_item'
+require 'chef/encrypted_data_bag_item'
+require 'chef/json_compat'
+require 'tempfile'
+
+describe Chef::Knife::DataBagShow do
+  before do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::DataBagShow.new
+    @knife.config[:format] = 'json'
+    @rest = mock("Chef::REST")
+    @knife.stub!(:rest).and_return(@rest)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+
+  it "prints the ids of the data bag items when given a bag name" do
+    @knife.instance_variable_set(:@name_args, ['bag_o_data'])
+    data_bag_contents = { "baz"=>"http://localhost:4000/data/bag_o_data/baz",
+      "qux"=>"http://localhost:4000/data/bag_o_data/qux"}
+    Chef::DataBag.should_receive(:load).and_return(data_bag_contents)
+    expected = %q|[
+  "baz",
+  "qux"
+]|
+    @knife.run
+    @stdout.string.strip.should == expected
+  end
+
+  it "prints the contents of the data bag item when given a bag and item name" do
+    @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item'])
+    data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}}
+
+    Chef::DataBagItem.should_receive(:load).with('bag_o_data', 'an_item').and_return(data_item)
+
+    @knife.run
+    Chef::JSONCompat.from_json(@stdout.string).should == data_item.raw_data
+
+  end
+
+  describe "encrypted data bag items" do
+    before(:each) do
+      @secret = "abc123SECRET"
+      @plain_data = {
+        "id" => "item_name",
+        "greeting" => "hello",
+        "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }}
+      }
+      @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data,
+                                                                   @secret)
+      @knife.instance_variable_set(:@name_args, ['bag_name', 'item_name'])
+
+      @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test")
+      @secret_file.puts(@secret)
+      @secret_file.flush
+    end
+
+    after do
+      @secret_file.close
+      @secret_file.unlink
+    end
+
+    it "prints the decrypted contents of an item when given --secret" do
+      @knife.stub!(:config).and_return({:secret => @secret})
+      Chef::EncryptedDataBagItem.should_receive(:load).
+        with('bag_name', 'item_name', @secret).
+        and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret))
+      @knife.run
+      Chef::JSONCompat.from_json(@stdout.string).should == @plain_data
+    end
+
+    it "prints the decrypted contents of an item when given --secret_file" do
+      @knife.stub!(:config).and_return({:secret_file => @secret_file.path})
+      Chef::EncryptedDataBagItem.should_receive(:load).
+        with('bag_name', 'item_name', @secret).
+        and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret))
+      @knife.run
+      Chef::JSONCompat.from_json(@stdout.string).should == @plain_data
+    end
+  end
+
+  describe "command line parsing" do
+    it "prints help if given no arguments" do
+      @knife.instance_variable_set(:@name_args, [])
+      lambda { @knife.run }.should raise_error(SystemExit)
+      @stdout.string.should match(/^knife data bag show BAG \[ITEM\] \(options\)/)
+    end
+  end
+
+end
diff --git a/spec/unit/knife/environment_create_spec.rb b/spec/unit/knife/environment_create_spec.rb
new file mode 100644
index 0000000..36f6556
--- /dev/null
+++ b/spec/unit/knife/environment_create_spec.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Stephen Delano (<stephen at ospcode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::EnvironmentCreate do
+  before(:each) do
+    @knife = Chef::Knife::EnvironmentCreate.new
+    @knife.stub!(:msg).and_return true
+    @knife.stub!(:output).and_return true
+    @knife.stub!(:show_usage).and_return true
+    @knife.name_args = [ "production" ]
+
+    @environment = Chef::Environment.new
+    @environment.stub!(:save)
+
+    Chef::Environment.stub!(:new).and_return @environment
+    @knife.stub!(:edit_data).and_return @environment
+  end
+
+  describe "run" do
+    it "should create a new environment" do
+      Chef::Environment.should_receive(:new)
+      @knife.run
+    end
+
+    it "should set the environment name" do
+      @environment.should_receive(:name).with("production")
+      @knife.run
+    end
+
+    it "should not print the environment" do
+      @knife.should_not_receive(:output)
+      @knife.run
+    end
+
+    it "should prompt you to edit the data" do
+      @knife.should_receive(:edit_data).with(@environment)
+      @knife.run
+    end
+
+    it "should save the environment" do
+      @environment.should_receive(:save)
+      @knife.run
+    end
+
+    it "should show usage and exit when no environment name is provided" do
+      @knife.name_args = [ ]
+      @knife.ui.should_receive(:fatal)
+      @knife.should_receive(:show_usage)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    describe "with --description" do
+      before(:each) do
+        @knife.config[:description] = "This is production"
+      end
+
+      it "should set the description" do
+        @environment.should_receive(:description).with("This is production")
+        @knife.run
+      end
+    end
+
+    describe "with --print-after" do
+      before(:each) do
+        @knife.config[:print_after] = true
+      end
+
+      it "should pretty print the environment, formatted for display" do
+        @knife.should_receive(:output).with(@environment)
+        @knife.run
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/environment_delete_spec.rb b/spec/unit/knife/environment_delete_spec.rb
new file mode 100644
index 0000000..219ae4a
--- /dev/null
+++ b/spec/unit/knife/environment_delete_spec.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Stephen Delano (<stephen at ospcode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::EnvironmentDelete do
+  before(:each) do
+    @knife = Chef::Knife::EnvironmentDelete.new
+    @knife.stub!(:msg).and_return true
+    @knife.stub!(:output).and_return true
+    @knife.stub!(:show_usage).and_return true
+    @knife.stub!(:confirm).and_return true
+    @knife.name_args = [ "production" ]
+
+    @environment = Chef::Environment.new
+    @environment.name("production")
+    @environment.description("Please delete me")
+    @environment.stub!(:destroy).and_return true
+    Chef::Environment.stub!(:load).and_return @environment
+  end
+
+  it "should confirm that you want to delete" do
+    @knife.should_receive(:confirm)
+    @knife.run
+  end
+
+  it "should load the environment" do
+    Chef::Environment.should_receive(:load).with("production")
+    @knife.run
+  end
+
+  it "should delete the environment" do
+    @environment.should_receive(:destroy)
+    @knife.run
+  end
+
+  it "should not print the environment" do
+    @knife.should_not_receive(:output)
+    @knife.run
+  end
+
+  it "should show usage and exit when no environment name is provided" do
+    @knife.name_args = []
+    @knife.ui.should_receive(:fatal)
+    @knife.should_receive(:show_usage)
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+
+  describe "with --print-after" do
+    it "should pretty print the environment, formatted for display" do
+      @knife.config[:print_after] = true
+      @knife.should_receive(:output).with(@environment)
+      @knife.run
+    end
+  end
+end
diff --git a/spec/unit/knife/environment_edit_spec.rb b/spec/unit/knife/environment_edit_spec.rb
new file mode 100644
index 0000000..91f9f5d
--- /dev/null
+++ b/spec/unit/knife/environment_edit_spec.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Stephen Delano (<stephen at ospcode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::EnvironmentEdit do
+  before(:each) do
+    @knife = Chef::Knife::EnvironmentEdit.new
+    @knife.ui.stub!(:msg).and_return true
+    @knife.ui.stub!(:output).and_return true
+    @knife.ui.stub!(:show_usage).and_return true
+    @knife.name_args = [ "production" ]
+
+    @environment = Chef::Environment.new
+    @environment.name("production")
+    @environment.description("Please edit me")
+    @environment.stub!(:save).and_return true
+    Chef::Environment.stub!(:load).and_return @environment
+    @knife.ui.stub(:edit_data).and_return @environment
+  end
+
+  it "should load the environment" do
+    Chef::Environment.should_receive(:load).with("production")
+    @knife.run
+  end
+
+  it "should let you edit the environment" do
+    @knife.ui.should_receive(:edit_data).with(@environment)
+    @knife.run
+  end
+
+  it "should save the edited environment data" do
+    pansy = Chef::Environment.new
+
+    @environment.name("new_environment_name")
+    @knife.ui.should_receive(:edit_data).with(@environment).and_return(pansy)
+    pansy.should_receive(:save)
+    @knife.run
+  end
+
+  it "should not save the unedited environment data" do
+    @environment.should_not_receive(:save)
+    @knife.run
+  end
+
+  it "should not print the environment" do
+    @knife.should_not_receive(:output)
+    @knife.run
+  end
+
+  it "shoud show usage and exit when no environment name is provided" do
+    @knife.name_args = []
+    @knife.should_receive(:show_usage)
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+
+  describe "with --print-after" do
+    it "should pretty print the environment, formatted for display" do
+      @knife.config[:print_after] = true
+      @knife.ui.should_receive(:output).with(@environment)
+      @knife.run
+    end
+  end
+end
diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb
new file mode 100644
index 0000000..7d028ae
--- /dev/null
+++ b/spec/unit/knife/environment_from_file_spec.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Stephen Delano (<stephen at ospcode.com>)
+# Author:: Seth Falcon (<seth at ospcode.com>)
+# Copyright:: Copyright 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+Chef::Knife::EnvironmentFromFile.load_deps
+
+describe Chef::Knife::EnvironmentFromFile do
+  before(:each) do
+    @knife = Chef::Knife::EnvironmentFromFile.new
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @knife.name_args = [ "spec.rb" ]
+
+    @environment = Chef::Environment.new
+    @environment.name("spec")
+    @environment.description("runs the unit tests")
+    @environment.cookbook_versions({"apt" => "= 1.2.3"})
+    @environment.stub!(:save).and_return true
+    @knife.loader.stub!(:load_from).and_return @environment
+  end
+
+  describe "run" do
+    it "loads the environment data from a file and saves it" do
+      @knife.loader.should_receive(:load_from).with('environments', 'spec.rb').and_return(@environment)
+      @environment.should_receive(:save)
+      @knife.run
+    end
+
+    context "when handling multiple environments" do
+      before(:each) do
+        @env_apple = @environment.dup
+        @env_apple.name("apple")
+        @knife.loader.stub!(:load_from).with("apple.rb").and_return @env_apple
+      end
+
+      it "loads multiple environments if given" do
+        @knife.name_args = [ "spec.rb", "apple.rb" ]
+        @environment.should_receive(:save).twice
+        @knife.run
+      end
+
+      it "loads all environments with -a" do
+        File.stub!(:expand_path).with("./environments/*.{json,rb}").and_return("/tmp/environments")
+        Dir.stub!(:glob).with("/tmp/environments").and_return(["spec.rb", "apple.rb"])
+        @knife.name_args = []
+        @knife.stub!(:config).and_return({:all => true})
+        @environment.should_receive(:save).twice
+        @knife.run
+      end
+    end
+
+    it "should not print the environment" do
+      @knife.should_not_receive(:output)
+      @knife.run
+    end
+
+    it "should show usage and exit if not filename is provided" do
+      @knife.name_args = []
+      @knife.ui.should_receive(:fatal)
+      @knife.should_receive(:show_usage)
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+    describe "with --print-after" do
+      it "should pretty print the environment, formatted for display" do
+        @knife.config[:print_after] = true
+        @knife.should_receive(:output)
+        @knife.run
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/environment_list_spec.rb b/spec/unit/knife/environment_list_spec.rb
new file mode 100644
index 0000000..05a3ae7
--- /dev/null
+++ b/spec/unit/knife/environment_list_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Stephen Delano (<stephen at ospcode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::EnvironmentList do
+  before(:each) do
+    @knife = Chef::Knife::EnvironmentList.new
+    @knife.stub!(:msg).and_return true
+    @knife.stub!(:output).and_return true
+    @knife.stub!(:show_usage).and_return true
+
+    @environments = {
+      "production" => "http://localhost:4000/environments/production",
+      "development" => "http://localhost:4000/environments/development",
+      "testing" => "http://localhost:4000/environments/testing"
+    }
+    Chef::Environment.stub!(:list).and_return @environments
+  end
+
+  it "should make an api call to list the environments" do
+    Chef::Environment.should_receive(:list)
+    @knife.run
+  end
+
+  it "should print the environment names in a sorted list" do
+    names = @environments.keys.sort { |a,b| a <=> b }
+    @knife.should_receive(:output).with(names)
+    @knife.run
+  end
+
+  describe "with --with-uri" do
+    it "should print and unsorted list of the environments and their URIs" do
+      @knife.config[:with_uri] = true
+      @knife.should_receive(:output).with(@environments)
+      @knife.run
+    end
+  end
+end
diff --git a/spec/unit/knife/environment_show_spec.rb b/spec/unit/knife/environment_show_spec.rb
new file mode 100644
index 0000000..1e1556f
--- /dev/null
+++ b/spec/unit/knife/environment_show_spec.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Stephen Delano (<stephen at ospcode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::EnvironmentShow do
+  before(:each) do
+    @knife = Chef::Knife::EnvironmentShow.new
+    @knife.stub!(:msg).and_return true
+    @knife.stub!(:output).and_return true
+    @knife.stub!(:show_usage).and_return true
+    @knife.name_args = [ "production" ]
+
+    @environment = Chef::Environment.new
+    @environment.name("production")
+    @environment.description("Look at me!")
+    Chef::Environment.stub!(:load).and_return @environment
+  end
+
+  it "should load the environment" do
+    Chef::Environment.should_receive(:load).with("production")
+    @knife.run
+  end
+
+  it "should pretty print the environment, formatted for display" do
+    @knife.should_receive(:format_for_display).with(@environment)
+    @knife.should_receive(:output)
+    @knife.run
+  end
+
+  it "should show usage and exit when no environment name is provided" do
+    @knife.name_args = []
+    @knife.ui.should_receive(:fatal)
+    @knife.should_receive(:show_usage)
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+end
diff --git a/spec/unit/knife/index_rebuild_spec.rb b/spec/unit/knife/index_rebuild_spec.rb
new file mode 100644
index 0000000..eb4e802
--- /dev/null
+++ b/spec/unit/knife/index_rebuild_spec.rb
@@ -0,0 +1,128 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::IndexRebuild do
+
+  let(:knife){Chef::Knife::IndexRebuild.new}
+  let(:rest_client){mock(Chef::REST)}
+
+  let(:stub_rest!) do
+    knife.should_receive(:rest).and_return(rest_client)
+  end
+
+  before :each do
+    # This keeps the test output clean
+    knife.ui.stub!(:stdout).and_return(StringIO.new)
+  end
+
+  context "#grab_api_info" do
+    let(:http_not_found_response) do
+      e = Net::HTTPNotFound.new("1.1", 404, "blah")
+      e.stub(:[]).with("x-ops-api-info").and_return(api_header_value)
+      e
+    end
+
+    let(:http_server_exception) do
+      Net::HTTPServerException.new("404: Not Found", http_not_found_response)
+    end
+
+    before(:each) do
+      stub_rest!
+      rest_client.stub(:get_rest).and_raise(http_server_exception)
+    end
+
+    context "against a Chef 11 server" do
+      let(:api_header_value){"flavor=osc;version=11.0.0;erchef=1.2.3"}
+      it "retrieves API information" do
+        knife.grab_api_info.should == {"flavor" => "osc", "version" => "11.0.0", "erchef" => "1.2.3"}
+      end
+    end # Chef 11
+
+    context "against a Chef 10 server" do
+      let(:api_header_value){nil}
+      it "finds no API information" do
+        knife.grab_api_info.should == {}
+      end
+    end # Chef 10
+  end # grab_api_info
+
+  context "#unsupported_version?" do
+    context "with Chef 11 API metadata" do
+      it "is unsupported" do
+        knife.unsupported_version?({"version" => "11.0.0", "flavor" => "osc", "erchef" => "1.2.3"}).should be_true
+      end
+
+      it "only truly relies on the version being non-nil" do
+        knife.unsupported_version?({"version" => "1", "flavor" => "osc", "erchef" => "1.2.3"}).should be_true
+      end
+    end
+
+    context "with Chef 10 API metadata" do
+      it "is supported" do
+        # Chef 10 will have no metadata
+        knife.unsupported_version?({}).should be_false
+      end
+    end
+  end # unsupported_version?
+
+  context "Simulating a 'knife index rebuild' run" do
+
+    before :each do
+      knife.should_receive(:grab_api_info).and_return(api_info)
+      server_specific_stubs!
+    end
+
+    context "against a Chef 11 server" do
+      let(:api_info) do
+        {"flavor" => "osc",
+          "version" => "11.0.0",
+          "erchef" => "1.2.3"
+        }
+      end
+      let(:server_specific_stubs!) do
+        knife.should_receive(:unsupported_server_message).with(api_info)
+        knife.should_receive(:exit).with(1)
+      end
+
+      it "should not be allowed" do
+        knife.run
+      end
+    end
+
+    context "against a Chef 10 server" do
+      let(:api_info){ {} }
+      let(:server_specific_stubs!) do
+        stub_rest!
+        rest_client.should_receive(:post_rest).with("/search/reindex", {}).and_return("representative output")
+        knife.should_not_receive(:unsupported_server_message)
+        knife.should_receive(:deprecated_server_message)
+        knife.should_receive(:nag)
+        knife.should_receive(:output).with("representative output")
+      end
+      it "should be allowed" do
+        knife.run
+      end
+    end
+  end
+
+end
+
+
+
diff --git a/spec/unit/knife/knife_help.rb b/spec/unit/knife/knife_help.rb
new file mode 100644
index 0000000..f5753e3
--- /dev/null
+++ b/spec/unit/knife/knife_help.rb
@@ -0,0 +1,92 @@
+#
+# Author:: Bryan McLellan <btm at loftninjas.org>
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::Help do
+  before(:each) do
+    # Perilously use the build in list even though it is dynamic so we don't get warnings about the constant
+    # HELP_TOPICS = [ "foo", "bar", "knife-kittens", "ceiling-cat", "shell" ]
+    @knife = Chef::Knife::Help.new
+  end
+
+  it "should return a list of help topics" do
+    @knife.help_topics.should include("knife-status")
+  end
+
+  it "should run man for you" do
+    @knife.name_args = [ "shell" ]
+    @knife.should_receive(:exec).with(/^man \/.*\/shell.1$/)
+    @knife.run
+  end
+
+  it "should suggest topics" do
+    @knife.name_args = [ "list" ]
+    @knife.ui.stub!(:msg)
+    @knife.ui.should_receive(:info).with("Available help topics are: ")
+    @knife.ui.should_receive(:msg).with(/knife/)
+    @knife.stub!(:exec)
+    @knife.should_receive(:exit).with(1)
+    @knife.run
+  end
+
+  describe "find_manpage_path" do
+    it "should find the man page in the gem" do
+      @knife.find_manpage_path("shell").should =~ /distro\/common\/man\/man1\/chef-shell.1$/
+    end
+
+    it "should provide the man page name if not in the gem" do
+      @knife.find_manpage_path("foo").should == "foo"
+    end
+  end
+
+  describe "find_manpages_for_query" do
+    it "should error if it does not find a match" do
+      @knife.ui.stub!(:error)
+      @knife.ui.stub!(:info)
+      @knife.ui.stub!(:msg)
+      @knife.should_receive(:exit).with(1)
+      @knife.ui.should_receive(:error).with("No help found for 'chickens'")
+      @knife.ui.should_receive(:msg).with(/knife/)
+      @knife.find_manpages_for_query("chickens")
+    end
+  end
+
+  describe "print_help_topics" do
+    it "should print the known help topics" do
+      @knife.ui.stub!(:msg)
+      @knife.ui.stub!(:info)
+      @knife.ui.should_receive(:msg).with(/knife/)
+      @knife.print_help_topics
+    end
+
+    it "should shorten topics prefixed by knife-" do
+      @knife.ui.stub!(:msg)
+      @knife.ui.stub!(:info)
+      @knife.ui.should_receive(:msg).with(/node/)
+      @knife.print_help_topics
+    end
+
+    it "should not leave topics prefixed by knife-" do
+      @knife.ui.stub!(:msg)
+      @knife.ui.stub!(:info)
+      @knife.ui.should_not_receive(:msg).with(/knife-node/)
+      @knife.print_help_topics
+    end
+  end
+end
diff --git a/spec/unit/knife/node_bulk_delete_spec.rb b/spec/unit/knife/node_bulk_delete_spec.rb
new file mode 100644
index 0000000..51f707d
--- /dev/null
+++ b/spec/unit/knife/node_bulk_delete_spec.rb
@@ -0,0 +1,97 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::NodeBulkDelete do
+  before(:each) do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeBulkDelete.new
+    @knife.name_args = ["."]
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @knife.ui.stub!(:confirm).and_return(true)
+    @nodes = Hash.new
+    %w{adam brent jacob}.each do |node_name|
+      @nodes[node_name] = "http://localhost:4000/nodes/#{node_name}"
+    end
+  end
+
+  describe "when creating the list of nodes" do
+    it "fetches the node list" do
+      expected = @nodes.inject({}) do |inflatedish, (name, uri)|
+        inflatedish[name] = Chef::Node.new.tap {|n| n.name(name)}
+        inflatedish
+      end
+      Chef::Node.should_receive(:list).and_return(@nodes)
+      # I hate not having == defined for anything :(
+      actual = @knife.all_nodes
+      actual.keys.should =~ expected.keys
+      actual.values.map {|n| n.name }.should =~ %w[adam brent jacob]
+    end
+  end
+
+  describe "run" do
+    before do
+      @inflatedish_list = @nodes.keys.inject({}) do |nodes_by_name, name|
+        node = Chef::Node.new()
+        node.name(name)
+        node.stub!(:destroy).and_return(true)
+        nodes_by_name[name] = node
+        nodes_by_name
+      end
+      @knife.stub!(:all_nodes).and_return(@inflatedish_list)
+    end
+
+    it "should print the nodes you are about to delete" do
+      @knife.run
+      @stdout.string.should match(/#{@knife.ui.list(@nodes.keys.sort, :columns_down)}/)
+    end
+
+    it "should confirm you really want to delete them" do
+      @knife.ui.should_receive(:confirm)
+      @knife.run
+    end
+
+    it "should delete each node" do
+      @inflatedish_list.each_value do |n|
+        n.should_receive(:destroy)
+      end
+      @knife.run
+    end
+
+    it "should only delete nodes that match the regex" do
+      @knife.name_args = ['adam']
+      @inflatedish_list['adam'].should_receive(:destroy)
+      @inflatedish_list['brent'].should_not_receive(:destroy)
+      @inflatedish_list['jacob'].should_not_receive(:destroy)
+      @knife.run
+    end
+
+    it "should exit if the regex is not provided" do
+      @knife.name_args = []
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+  end
+end
+
+
+
diff --git a/spec/unit/knife/node_delete_spec.rb b/spec/unit/knife/node_delete_spec.rb
new file mode 100644
index 0000000..bc30ae7
--- /dev/null
+++ b/spec/unit/knife/node_delete_spec.rb
@@ -0,0 +1,68 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::NodeDelete do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeDelete.new
+    @knife.config = {
+      :print_after => nil
+    }
+    @knife.name_args = [ "adam" ]
+    @knife.stub!(:output).and_return(true)
+    @knife.stub!(:confirm).and_return(true)
+    @node = Chef::Node.new()
+    @node.stub!(:destroy).and_return(true)
+    Chef::Node.stub!(:load).and_return(@node)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "should confirm that you want to delete" do
+      @knife.should_receive(:confirm)
+      @knife.run
+    end
+
+    it "should load the node" do
+      Chef::Node.should_receive(:load).with("adam").and_return(@node)
+      @knife.run
+    end
+
+    it "should delete the node" do
+      @node.should_receive(:destroy).and_return(@node)
+      @knife.run
+    end
+
+    it "should not print the node" do
+      @knife.should_not_receive(:output).with("poop")
+      @knife.run
+    end
+
+    describe "with -p or --print-after" do
+      it "should pretty print the node, formatted for display" do
+        @knife.config[:print_after] = true
+        @knife.should_receive(:format_for_display).with(@node).and_return("poop")
+        @knife.should_receive(:output).with("poop")
+        @knife.run
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/node_edit_spec.rb b/spec/unit/knife/node_edit_spec.rb
new file mode 100644
index 0000000..7fbd819
--- /dev/null
+++ b/spec/unit/knife/node_edit_spec.rb
@@ -0,0 +1,115 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+Chef::Knife::NodeEdit.load_deps
+
+describe Chef::Knife::NodeEdit do
+
+  # helper to convert the view from Chef objects into Ruby objects representing JSON
+  def deserialized_json_view
+    actual = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json_pretty(@knife.node_editor.send(:view)))
+  end
+
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeEdit.new
+    @knife.config = {
+      :editor => 'cat',
+      :attribute => nil,
+      :print_after => nil
+    }
+    @knife.name_args = [ "adam" ]
+    @node = Chef::Node.new()
+  end
+
+  it "should load the node" do
+    Chef::Node.should_receive(:load).with("adam").and_return(@node)
+    @knife.node
+  end
+
+  describe "after loading the node" do
+    before do
+      @knife.stub!(:node).and_return(@node)
+      @node.automatic_attrs = {:go => :away}
+      @node.default_attrs = {:hide => :me}
+      @node.override_attrs = {:dont => :show}
+      @node.normal_attrs = {:do_show => :these}
+      @node.chef_environment("prod")
+      @node.run_list("recipe[foo]")
+    end
+
+    it "creates a view of the node without attributes from roles or ohai" do
+      actual = deserialized_json_view
+      actual.should_not have_key("automatic")
+      actual.should_not have_key("override")
+      actual.should_not have_key("default")
+      actual["normal"].should == {"do_show" => "these"}
+      actual["run_list"].should == ["recipe[foo]"]
+      actual["chef_environment"].should == "prod"
+    end
+
+    it "shows the extra attributes when given the --all option" do
+      @knife.config[:all_attributes] = true
+
+      actual = deserialized_json_view
+      actual["automatic"].should == {"go" => "away"}
+      actual["override"].should == {"dont" => "show"}
+      actual["default"].should == {"hide" => "me"}
+      actual["normal"].should == {"do_show" => "these"}
+      actual["run_list"].should == ["recipe[foo]"]
+      actual["chef_environment"].should == "prod"
+    end
+
+    it "does not consider unedited data updated" do
+      view = deserialized_json_view
+      @knife.node_editor.send(:apply_updates, view)
+      @knife.node_editor.should_not be_updated
+    end
+
+    it "considers edited data updated" do
+      view = deserialized_json_view
+      view["run_list"] << "role[fuuu]"
+      @knife.node_editor.send(:apply_updates, view)
+      @knife.node_editor.should be_updated
+    end
+
+  end
+
+  describe "edit_node" do
+
+    before do
+      @knife.stub!(:node).and_return(@node)
+    end
+
+    let(:subject) { @knife.node_editor.edit_node }
+
+    it "raises an exception when editing is disabled" do
+      @knife.config[:disable_editing] = true
+      expect{ subject }.to raise_error(SystemExit)
+    end
+
+    it "raises an exception when the editor is not set" do
+      @knife.config[:editor] = nil
+      expect{ subject }.to raise_error(SystemExit)
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/knife/node_from_file_spec.rb b/spec/unit/knife/node_from_file_spec.rb
new file mode 100644
index 0000000..c6b9610
--- /dev/null
+++ b/spec/unit/knife/node_from_file_spec.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+Chef::Knife::NodeFromFile.load_deps
+
+describe Chef::Knife::NodeFromFile do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeFromFile.new
+    @knife.config = {
+      :print_after => nil
+    }
+    @knife.name_args = [ "adam.rb" ]
+    @knife.stub!(:output).and_return(true)
+    @knife.stub!(:confirm).and_return(true)
+    @node = Chef::Node.new()
+    @node.stub!(:save)
+    @knife.loader.stub!(:load_from).and_return(@node)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "should load from a file" do
+      @knife.loader.should_receive(:load_from).with('nodes', 'adam.rb').and_return(@node)
+      @knife.run
+    end
+
+    it "should not print the Node" do
+      @knife.should_not_receive(:output)
+      @knife.run
+    end
+
+    describe "with -p or --print-after" do
+      it "should print the Node" do
+        @knife.config[:print_after] = true
+        @knife.should_receive(:output)
+        @knife.run
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/node_list_spec.rb b/spec/unit/knife/node_list_spec.rb
new file mode 100644
index 0000000..3482eab
--- /dev/null
+++ b/spec/unit/knife/node_list_spec.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::NodeList do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    Chef::Config[:environment] = nil # reset this value each time, as it is not reloaded
+    @knife = Chef::Knife::NodeList.new
+    @knife.stub!(:output).and_return(true)
+    @list = {
+      "foo" => "http://example.com/foo",
+      "bar" => "http://example.com/foo"
+    }
+    Chef::Node.stub!(:list).and_return(@list)
+    Chef::Node.stub!(:list_by_environment).and_return(@list)
+  end
+
+  describe "run" do
+    it "should list all of the nodes if -E is not specified" do
+      Chef::Node.should_receive(:list).and_return(@list)
+      @knife.run
+    end
+
+    it "should pretty print the list" do
+      Chef::Node.should_receive(:list).and_return(@list)
+      @knife.should_receive(:output).with([ "bar", "foo" ])
+      @knife.run
+    end
+
+    it "should list nodes in the specific environment if -E ENVIRONMENT is specified" do
+      Chef::Config[:environment] = "prod"
+      Chef::Node.should_receive(:list_by_environment).with("prod").and_return(@list)
+      @knife.run
+    end
+
+    describe "with -w or --with-uri" do
+      it "should pretty print the hash" do
+        @knife.config[:with_uri] = true
+        Chef::Node.should_receive(:list).and_return(@list)
+        @knife.should_receive(:output).with(@list)
+        @knife.run
+      end
+    end
+  end
+end
+
diff --git a/spec/unit/knife/node_run_list_add_spec.rb b/spec/unit/knife/node_run_list_add_spec.rb
new file mode 100644
index 0000000..80e372c
--- /dev/null
+++ b/spec/unit/knife/node_run_list_add_spec.rb
@@ -0,0 +1,125 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::NodeRunListAdd do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeRunListAdd.new
+    @knife.config = {
+      :after => nil
+    }
+    @knife.name_args = [ "adam", "role[monkey]" ]
+    @knife.stub!(:output).and_return(true)
+    @node = Chef::Node.new()
+    @node.stub!(:save).and_return(true)
+    Chef::Node.stub!(:load).and_return(@node)
+  end
+
+  describe "run" do
+    it "should load the node" do
+      Chef::Node.should_receive(:load).with("adam")
+      @knife.run
+    end
+
+    it "should add to the run list" do
+      @knife.run
+      @node.run_list[0].should == 'role[monkey]'
+    end
+
+    it "should save the node" do
+      @node.should_receive(:save)
+      @knife.run
+    end
+
+    it "should print the run list" do
+      @knife.should_receive(:output).and_return(true)
+      @knife.run
+    end
+
+    describe "with -a or --after specified" do
+      it "should add to the run list after the specified entry" do
+        @node.run_list << "role[acorns]"
+        @node.run_list << "role[barn]"
+        @knife.config[:after] = "role[acorns]"
+        @knife.run
+        @node.run_list[0].should == "role[acorns]"
+        @node.run_list[1].should == "role[monkey]"
+        @node.run_list[2].should == "role[barn]"
+      end
+    end
+
+    describe "with more than one role or recipe" do
+      it "should add to the run list all the entries" do
+        @knife.name_args = [ "adam", "role[monkey],role[duck]" ]
+        @node.run_list << "role[acorns]"
+        @knife.run
+        @node.run_list[0].should == "role[acorns]"
+        @node.run_list[1].should == "role[monkey]"
+        @node.run_list[2].should == "role[duck]"
+      end
+    end
+
+    describe "with more than one role or recipe with space between items" do
+      it "should add to the run list all the entries" do
+        @knife.name_args = [ "adam", "role[monkey], role[duck]" ]
+        @node.run_list << "role[acorns]"
+        @knife.run
+        @node.run_list[0].should == "role[acorns]"
+        @node.run_list[1].should == "role[monkey]"
+        @node.run_list[2].should == "role[duck]"
+      end
+    end
+
+    describe "with more than one role or recipe as different arguments" do
+      it "should add to the run list all the entries" do
+        @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
+        @node.run_list << "role[acorns]"
+        @knife.run
+        @node.run_list[0].should == "role[acorns]"
+        @node.run_list[1].should == "role[monkey]"
+        @node.run_list[2].should == "role[duck]"
+      end
+    end
+
+    describe "with more than one role or recipe as different arguments and list separated by comas" do
+      it "should add to the run list all the entries" do
+        @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
+        @node.run_list << "role[acorns]"
+        @knife.run
+        @node.run_list[0].should == "role[acorns]"
+        @node.run_list[1].should == "role[monkey]"
+        @node.run_list[2].should == "role[duck]"
+      end
+    end
+
+    describe "with one role or recipe but with an extraneous comma" do
+      it "should add to the run list one item" do
+        @knife.name_args = [ "adam", "role[monkey]," ]
+        @node.run_list << "role[acorns]"
+        @knife.run
+        @node.run_list[0].should == "role[acorns]"
+        @node.run_list[1].should == "role[monkey]"
+      end
+    end
+  end
+end
+
+
+
diff --git a/spec/unit/knife/node_run_list_remove_spec.rb b/spec/unit/knife/node_run_list_remove_spec.rb
new file mode 100644
index 0000000..90869e8
--- /dev/null
+++ b/spec/unit/knife/node_run_list_remove_spec.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::NodeRunListRemove do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeRunListRemove.new
+    @knife.config[:print_after] = nil
+    @knife.name_args = [ "adam", "role[monkey]" ]
+    @node = Chef::Node.new()
+    @node.name("knifetest-node")
+    @node.run_list << "role[monkey]"
+    @node.stub!(:save).and_return(true)
+
+    @knife.ui.stub!(:output).and_return(true)
+    @knife.ui.stub!(:confirm).and_return(true)
+
+    Chef::Node.stub!(:load).and_return(@node)
+  end
+
+  describe "run" do
+    it "should load the node" do
+      Chef::Node.should_receive(:load).with("adam").and_return(@node)
+      @knife.run
+    end
+
+    it "should remove the item from the run list" do
+      @knife.run
+      @node.run_list[0].should_not == 'role[monkey]'
+    end
+
+    it "should save the node" do
+      @node.should_receive(:save).and_return(true)
+      @knife.run
+    end
+
+    it "should print the run list" do
+      @knife.config[:print_after] = true
+      @knife.ui.should_receive(:output).with({ "knifetest-node" => { 'run_list' => [] } })
+      @knife.run
+    end
+
+    describe "run with a list of roles and recipes" do
+      it "should remove the items from the run list" do
+        @node.run_list << 'role[monkey]'
+        @node.run_list << 'recipe[duck::type]'
+        @knife.name_args = [ 'adam', 'role[monkey],recipe[duck::type]' ]
+        @knife.run
+        @node.run_list.should_not include('role[monkey]')
+        @node.run_list.should_not include('recipe[duck::type]')
+      end
+    end
+  end
+end
+
+
+
diff --git a/spec/unit/knife/node_run_list_set_spec.rb b/spec/unit/knife/node_run_list_set_spec.rb
new file mode 100644
index 0000000..bc10269
--- /dev/null
+++ b/spec/unit/knife/node_run_list_set_spec.rb
@@ -0,0 +1,140 @@
+#
+# Author:: Mike Fiedler (<miketheman at gmail.com>)
+# Copyright:: Copyright (c) 2013 Mike Fiedler
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::NodeRunListSet do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeRunListSet.new
+    @knife.config = {}
+    @knife.name_args = [ "adam", "role[monkey]" ]
+    @knife.stub!(:output).and_return(true)
+    @node = Chef::Node.new()
+    @node.stub!(:save).and_return(true)
+    Chef::Node.stub!(:load).and_return(@node)
+  end
+
+  describe "run" do
+    it "should load the node" do
+      Chef::Node.should_receive(:load).with("adam")
+      @knife.run
+    end
+
+    it "should set the run list" do
+      @knife.run
+      @node.run_list[0].should == 'role[monkey]'
+    end
+
+    it "should save the node" do
+      @node.should_receive(:save)
+      @knife.run
+    end
+
+    it "should print the run list" do
+      @knife.should_receive(:output).and_return(true)
+      @knife.run
+    end
+
+    describe "with more than one role or recipe" do
+      it "should set the run list to all the entries" do
+        @knife.name_args = [ "adam", "role[monkey],role[duck]" ]
+        @knife.run
+        @node.run_list[0].should == "role[monkey]"
+        @node.run_list[1].should == "role[duck]"
+      end
+    end
+
+    describe "with more than one role or recipe with space between items" do
+      it "should set the run list to all the entries" do
+        @knife.name_args = [ "adam", "role[monkey], role[duck]" ]
+        @knife.run
+        @node.run_list[0].should == "role[monkey]"
+        @node.run_list[1].should == "role[duck]"
+      end
+    end
+
+    describe "with more than one role or recipe as different arguments" do
+      it "should set the run list to all the entries" do
+        @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
+        @knife.run
+        @node.run_list[0].should == "role[monkey]"
+        @node.run_list[1].should == "role[duck]"
+      end
+    end
+
+    describe "with more than one role or recipe as different arguments and list separated by comas" do
+      it "should add to the run list all the entries" do
+        @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ]
+        @knife.run
+        @node.run_list[0].should == "role[monkey]"
+        @node.run_list[1].should == "role[duck]"
+      end
+    end
+
+    describe "with one role or recipe but with an extraneous comma" do
+      it "should add to the run list one item" do
+        @knife.name_args = [ "adam", "role[monkey]," ]
+        @knife.run
+        @node.run_list[0].should == "role[monkey]"
+      end
+    end
+
+    describe "with an existing run list" do
+      it "should overwrite any existing run list items" do
+        @node.run_list << "role[acorns]"
+        @node.run_list << "role[zebras]"
+        @node.run_list[0].should == "role[acorns]"
+        @node.run_list[1].should == "role[zebras]"
+        @node.run_list.run_list_items.size.should == 2
+
+        @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ]
+        @knife.run
+        @node.run_list[0].should == "role[monkey]"
+        @node.run_list[1].should == "role[duck]"
+        @node.run_list.run_list_items.size.should == 2
+      end
+    end
+
+    describe "with no role or recipe" do
+      # Set up outputs for inspection later
+      before(:each) do
+        @stdout = StringIO.new
+        @stderr = StringIO.new
+
+        @knife.ui.stub!(:stdout).and_return(@stdout)
+        @knife.ui.stub!(:stderr).and_return(@stderr)
+      end
+
+      it "should exit" do
+        @knife.name_args = [ "adam" ]
+        lambda { @knife.run }.should raise_error SystemExit
+      end
+
+      it "should show the user" do
+        @knife.name_args = [ "adam" ]
+
+        begin ; @knife.run ; rescue SystemExit ; end
+
+        @stdout.string.should eq "USAGE: knife node run_list set NODE ENTRIES (options)\n"
+        @stderr.string.should eq "FATAL: You must supply both a node name and a run list.\n"
+      end
+    end
+
+  end
+end
diff --git a/spec/unit/knife/node_show_spec.rb b/spec/unit/knife/node_show_spec.rb
new file mode 100644
index 0000000..eb3593f
--- /dev/null
+++ b/spec/unit/knife/node_show_spec.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::NodeShow do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::NodeShow.new
+    @knife.config = {
+      :attribute => nil,
+      :run_list => nil,
+      :environment => nil
+    }
+    @knife.name_args = [ "adam" ]
+    @knife.stub!(:output).and_return(true)
+    @node = Chef::Node.new()
+    Chef::Node.stub!(:load).and_return(@node)
+  end
+
+  describe "run" do
+    it "should load the node" do
+      Chef::Node.should_receive(:load).with("adam").and_return(@node)
+      @knife.run
+    end
+
+    it "should pretty print the node, formatted for display" do
+      @knife.should_receive(:format_for_display).with(@node).and_return("poop")
+      @knife.should_receive(:output).with("poop")
+      @knife.run
+    end
+  end
+end
diff --git a/spec/unit/knife/role_bulk_delete_spec.rb b/spec/unit/knife/role_bulk_delete_spec.rb
new file mode 100644
index 0000000..0ee84f6
--- /dev/null
+++ b/spec/unit/knife/role_bulk_delete_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Stephen Delano (<stephen at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::RoleBulkDelete do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::RoleBulkDelete.new
+    @knife.config = {
+      :print_after => nil
+    }
+    @knife.name_args = ["."]
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @knife.ui.stub!(:confirm).and_return(true)
+    @roles = Hash.new
+    %w{dev staging production}.each do |role_name|
+      role = Chef::Role.new()
+      role.name(role_name)
+      role.stub!(:destroy).and_return(true)
+      @roles[role_name] = role
+    end
+    Chef::Role.stub!(:list).and_return(@roles)
+  end
+
+  describe "run" do
+
+    it "should get the list of the roles" do
+      Chef::Role.should_receive(:list).and_return(@roles)
+      @knife.run
+    end
+
+    it "should print the roles you are about to delete" do
+      @knife.run
+      @stdout.string.should match(/#{@knife.ui.list(@roles.keys.sort, :columns_down)}/)
+    end
+
+    it "should confirm you really want to delete them" do
+      @knife.ui.should_receive(:confirm)
+      @knife.run
+    end
+
+    it "should delete each role" do
+      @roles.each_value do |r|
+        r.should_receive(:destroy)
+      end
+      @knife.run
+    end
+
+    it "should only delete roles that match the regex" do
+      @knife.name_args = ["dev"]
+      @roles["dev"].should_receive(:destroy)
+      @roles["staging"].should_not_receive(:destroy)
+      @roles["production"].should_not_receive(:destroy)
+      @knife.run
+    end
+
+    it "should exit if the regex is not provided" do
+      @knife.name_args = []
+      lambda { @knife.run }.should raise_error(SystemExit)
+    end
+
+  end
+end
diff --git a/spec/unit/knife/role_create_spec.rb b/spec/unit/knife/role_create_spec.rb
new file mode 100644
index 0000000..39fed8f
--- /dev/null
+++ b/spec/unit/knife/role_create_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::RoleCreate do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::RoleCreate.new
+    @knife.config = {
+      :description => nil
+    }
+    @knife.name_args = [ "adam" ]
+    @knife.stub!(:output).and_return(true)
+    @role = Chef::Role.new()
+    @role.stub!(:save)
+    Chef::Role.stub!(:new).and_return(@role)
+    @knife.stub!(:edit_data).and_return(@role)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "should create a new role" do
+      Chef::Role.should_receive(:new).and_return(@role)
+      @knife.run
+    end
+
+    it "should set the role name" do
+      @role.should_receive(:name).with("adam")
+      @knife.run
+    end
+
+    it "should not print the role" do
+      @knife.should_not_receive(:output)
+      @knife.run
+    end
+
+    it "should allow you to edit the data" do
+      @knife.should_receive(:edit_data).with(@role)
+      @knife.run
+    end
+
+    it "should save the role" do
+      @role.should_receive(:save)
+      @knife.run
+    end
+
+    describe "with -d or --description" do
+      it "should set the description" do
+        @knife.config[:description] = "All is bob"
+        @role.should_receive(:description).with("All is bob")
+        @knife.run
+      end
+    end
+
+    describe "with -p or --print-after" do
+      it "should pretty print the node, formatted for display" do
+        @knife.config[:print_after] = true
+        @knife.should_receive(:output).with(@role)
+        @knife.run
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/role_delete_spec.rb b/spec/unit/knife/role_delete_spec.rb
new file mode 100644
index 0000000..37b16de
--- /dev/null
+++ b/spec/unit/knife/role_delete_spec.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::RoleDelete do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::RoleDelete.new
+    @knife.config = {
+      :print_after => nil
+    }
+    @knife.name_args = [ "adam" ]
+    @knife.stub!(:output).and_return(true)
+    @knife.stub!(:confirm).and_return(true)
+    @role = Chef::Role.new()
+    @role.stub!(:destroy).and_return(true)
+    Chef::Role.stub!(:load).and_return(@role)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "should confirm that you want to delete" do
+      @knife.should_receive(:confirm)
+      @knife.run
+    end
+
+    it "should load the Role" do
+      Chef::Role.should_receive(:load).with("adam").and_return(@role)
+      @knife.run
+    end
+
+    it "should delete the Role" do
+      @role.should_receive(:destroy).and_return(@role)
+      @knife.run
+    end
+
+    it "should not print the Role" do
+      @knife.should_not_receive(:output)
+      @knife.run
+    end
+
+    describe "with -p or --print-after" do
+      it "should pretty print the Role, formatted for display" do
+        @knife.config[:print_after] = true
+        @knife.should_receive(:output)
+        @knife.run
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/role_edit_spec.rb b/spec/unit/knife/role_edit_spec.rb
new file mode 100644
index 0000000..3a002f3
--- /dev/null
+++ b/spec/unit/knife/role_edit_spec.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::RoleEdit do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::RoleEdit.new
+    @knife.config[:print_after] = nil
+    @knife.name_args = [ "adam" ]
+    @knife.ui.stub!(:output).and_return(true)
+    @role = Chef::Role.new()
+    @role.stub!(:save)
+    Chef::Role.stub!(:load).and_return(@role)
+    @knife.ui.stub!(:edit_data).and_return(@role)
+    @knife.ui.stub!(:msg)
+  end
+
+  describe "run" do
+    it "should load the role" do
+      Chef::Role.should_receive(:load).with("adam").and_return(@role)
+      @knife.run
+    end
+
+    it "should edit the role data" do
+      @knife.ui.should_receive(:edit_data).with(@role)
+      @knife.run
+    end
+
+    it "should save the edited role data" do
+      pansy = Chef::Role.new
+
+      @role.name("new_role_name")
+      @knife.ui.should_receive(:edit_data).with(@role).and_return(pansy)
+      pansy.should_receive(:save)
+      @knife.run
+    end
+
+    it "should not save the unedited role data" do
+      pansy = Chef::Role.new
+
+      @knife.ui.should_receive(:edit_data).with(@role).and_return(pansy)
+      pansy.should_not_receive(:save)
+      @knife.run
+
+    end
+
+    it "should not print the role" do
+      @knife.ui.should_not_receive(:output)
+      @knife.run
+    end
+
+    describe "with -p or --print-after" do
+      it "should pretty print the role, formatted for display" do
+        @knife.config[:print_after] = true
+        @knife.ui.should_receive(:output).with(@role)
+        @knife.run
+      end
+    end
+  end
+end
+
+
diff --git a/spec/unit/knife/role_from_file_spec.rb b/spec/unit/knife/role_from_file_spec.rb
new file mode 100644
index 0000000..9b81bb1
--- /dev/null
+++ b/spec/unit/knife/role_from_file_spec.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+Chef::Knife::RoleFromFile.load_deps
+
+describe Chef::Knife::RoleFromFile do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::RoleFromFile.new
+    @knife.config = {
+      :print_after => nil
+    }
+    @knife.name_args = [ "adam.rb" ]
+    @knife.stub!(:output).and_return(true)
+    @knife.stub!(:confirm).and_return(true)
+    @role = Chef::Role.new()
+    @role.stub!(:save)
+    @knife.loader.stub!(:load_from).and_return(@role)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "should load from a file" do
+      @knife.loader.should_receive(:load_from).with('roles', 'adam.rb').and_return(@role)
+      @knife.run
+    end
+
+    it "should not print the role" do
+      @knife.should_not_receive(:output)
+      @knife.run
+    end
+
+    describe "with -p or --print-after" do
+      it "should print the role" do
+        @knife.config[:print_after] = true
+        @knife.should_receive(:output)
+        @knife.run
+      end
+    end
+  end
+
+  describe "run with multiple arguments" do
+    it "should load each file" do
+      @knife.name_args = [ "adam.rb", "caleb.rb" ]
+      @knife.loader.should_receive(:load_from).with('roles', 'adam.rb').and_return(@role)
+      @knife.loader.should_receive(:load_from).with('roles', 'caleb.rb').and_return(@role)
+      @knife.run
+    end
+  end
+
+end
diff --git a/spec/unit/knife/role_list_spec.rb b/spec/unit/knife/role_list_spec.rb
new file mode 100644
index 0000000..d0cd3b4
--- /dev/null
+++ b/spec/unit/knife/role_list_spec.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Knife::RoleList do
+  before(:each) do
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife::RoleList.new
+    @knife.stub!(:output).and_return(true)
+    @list = {
+      "foo" => "http://example.com/foo",
+      "bar" => "http://example.com/foo"
+    }
+    Chef::Role.stub!(:list).and_return(@list)
+  end
+
+  describe "run" do
+    it "should list the roles" do
+      Chef::Role.should_receive(:list).and_return(@list)
+      @knife.run
+    end
+
+    it "should pretty print the list" do
+      Chef::Role.should_receive(:list).and_return(@list)
+      @knife.should_receive(:output).with([ "bar", "foo" ])
+      @knife.run
+    end
+
+    describe "with -w or --with-uri" do
+      it "should pretty print the hash" do
+        @knife.config[:with_uri] = true
+        Chef::Role.should_receive(:list).and_return(@list)
+        @knife.should_receive(:output).with(@list)
+        @knife.run
+      end
+    end
+  end
+end
+
+
diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb
new file mode 100644
index 0000000..0d67f33
--- /dev/null
+++ b/spec/unit/knife/ssh_spec.rb
@@ -0,0 +1,283 @@
+#
+# Author:: Bryan McLellan <btm 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 'net/ssh'
+require 'net/ssh/multi'
+
+describe Chef::Knife::Ssh do
+  before(:each) do
+    Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
+  end
+
+  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"
+  end
+
+  describe "#configure_session" do
+    context "manual is set to false (default)" do
+      before do
+        @knife.config[:manual] = false
+        @query = Chef::Search::Query.new
+      end
+
+      def configure_query(node_array)
+        @query.stub!(:search).and_return([node_array])
+        Chef::Search::Query.stub!(:new).and_return(@query)
+      end
+
+      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[:override_attribute] = "ipaddress"
+          configure_query([@node_foo, @node_bar])
+          @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
+          @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[:override_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
+          configure_query([@node_foo, @node_bar])
+          @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2'])
+          @knife.configure_session
+        end
+      end
+
+      it "searchs for and returns an array of fqdns" do
+        configure_query([@node_foo, @node_bar])
+        @knife.should_receive(:session_from_list).with(['foo.example.org', 'bar.example.org'])
+        @knife.configure_session
+      end
+
+      should_return_specified_attributes
+
+      context "when cloud hostnames are available" do
+        before 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])
+          @knife.should_receive(:session_from_list).with(['ec2-10-0-0-1.compute-1.amazonaws.com', 'ec2-10-0-0-2.compute-1.amazonaws.com'])
+          @knife.configure_session
+        end
+
+        should_return_specified_attributes
+      end
+
+      it "should raise an error if no host are found" do
+          configure_query([ ])
+          @knife.ui.should_receive(:fatal)
+          @knife.should_receive(:exit).with(10)
+          @knife.configure_session
+      end
+
+      context "when there are some hosts found but they do not have an attribute to connect with" do
+        before do
+          @query.stub!(:search).and_return([[@node_foo, @node_bar]])
+          @node_foo.automatic_attrs[:fqdn] = nil
+          @node_bar.automatic_attrs[:fqdn] = nil
+          Chef::Search::Query.stub!(:new).and_return(@query)
+        end
+
+        it "should raise a specific error (CHEF-3402)" do
+          @knife.ui.should_receive(:fatal).with(/^2 nodes found/)
+          @knife.should_receive(:exit).with(10)
+          @knife.configure_session
+        end
+      end
+    end
+
+    context "manual is set to true" do
+      before do
+        @knife.config[:manual] = true
+      end
+
+      it "returns an array of provided values" do
+        @knife.instance_variable_set(:@name_args, ["foo.example.org bar.example.org"])
+        @knife.should_receive(:session_from_list).with(['foo.example.org', 'bar.example.org'])
+        @knife.configure_session
+      end
+    end
+  end
+
+  describe "#configure_attribute" do
+    before do
+      Chef::Config[:knife][:ssh_attribute] = nil
+      @knife.config[:attribute] = nil
+    end
+
+    it "should return fqdn by default" do
+      @knife.configure_attribute
+      @knife.config[:attribute].should == "fqdn"
+    end
+
+    it "should return the value set in the configuration file" do
+      Chef::Config[:knife][:ssh_attribute] = "config_file"
+      @knife.configure_attribute
+      @knife.config[:attribute].should == "config_file"
+    end
+
+    it "should return the value set on the command line" do
+      @knife.config[:attribute] = "command_line"
+      @knife.configure_attribute
+      @knife.config[:attribute].should == "command_line"
+    end
+
+    it "should set override_attribute to the value of attribute from the command line" do
+      @knife.config[:attribute] = "command_line"
+      @knife.configure_attribute
+      @knife.config[:attribute].should == "command_line"
+      @knife.config[:override_attribute].should == "command_line"
+    end
+
+    it "should set override_attribute to the value of attribute from the config file" do
+      Chef::Config[:knife][:ssh_attribute] = "config_file"
+      @knife.configure_attribute
+      @knife.config[:attribute].should == "config_file"
+      @knife.config[:override_attribute].should == "config_file"
+    end
+
+    it "should prefer the command line over the config file for the value of override_attribute" do
+      Chef::Config[:knife][:ssh_attribute] = "config_file"
+      @knife.config[:attribute] = "command_line"
+      @knife.configure_attribute
+      @knife.config[:override_attribute].should == "command_line"
+    end
+  end
+
+  describe "#session_from_list" do
+    before :each do
+      @knife.instance_variable_set(:@longest, 0)
+      ssh_config = {:timeout => 50, :user => "locutus", :port => 23 }
+      Net::SSH.stub!(:configuration_for).with('the.b.org').and_return(ssh_config)
+    end
+
+    it "uses the port from an ssh config file" do
+      @knife.session_from_list(['the.b.org'])
+      @knife.session.servers[0].port.should == 23
+    end
+
+    it "uses the user from an ssh config file" do
+      @knife.session_from_list(['the.b.org'])
+      @knife.session.servers[0].user.should == "locutus"
+    end
+  end
+
+  describe "#ssh_command" do
+    let(:execution_channel) { double(:execution_channel, :on_data => nil) }
+    let(:session_channel) { double(:session_channel, :request_pty => nil)}
+
+    let(:execution_channel2) { double(:execution_channel, :on_data => nil) }
+    let(:session_channel2) { double(:session_channel, :request_pty => nil)}
+
+    let(:session) { double(:session, :loop => nil) }
+
+    let(:command) { "false" }
+
+    before do
+      execution_channel.
+        should_receive(:on_request).
+        and_yield(nil, double(:data_stream, :read_long => exit_status))
+
+      session_channel.
+        should_receive(:exec).
+        with(command).
+        and_yield(execution_channel, true)
+
+      execution_channel2.
+        should_receive(:on_request).
+        and_yield(nil, double(:data_stream, :read_long => exit_status2))
+
+      session_channel2.
+        should_receive(:exec).
+        with(command).
+        and_yield(execution_channel2, true)
+
+      session.
+        should_receive(:open_channel).
+        and_yield(session_channel).
+        and_yield(session_channel2)
+    end
+
+    context "both connections return 0" do
+      let(:exit_status) { 0 }
+      let(:exit_status2) { 0 }
+
+      it "returns a 0 exit code" do
+        @knife.ssh_command(command, session).should == 0
+      end
+    end
+
+    context "the first connection returns 1 and the second returns 0" do
+      let(:exit_status) { 1 }
+      let(:exit_status2) { 0 }
+
+      it "returns a non-zero exit code" do
+        @knife.ssh_command(command, session).should == 1
+      end
+    end
+
+    context "the first connection returns 1 and the second returns 2" do
+      let(:exit_status) { 1 }
+      let(:exit_status2) { 2 }
+
+      it "returns a non-zero exit code" do
+        @knife.ssh_command(command, session).should == 2
+      end
+    end
+  end
+
+  describe "#run" do
+    before do
+      @query = Chef::Search::Query.new
+      @query.should_receive(:search).and_return([[@node_foo]])
+      Chef::Search::Query.stub!(:new).and_return(@query)
+      @knife.stub(:ssh_command).and_return(exit_code)
+      @knife.name_args = ['*:*', 'false']
+    end
+
+    context "with an error" do
+      let(:exit_code) { 1 }
+
+      it "should exit with a non-zero exit code" do
+        @knife.should_receive(:exit).with(exit_code)
+        @knife.run
+      end
+    end
+
+    context "with no error" do
+      let(:exit_code) { 0 }
+
+      it "should not exit" do
+        @knife.should_not_receive(:exit)
+        @knife.run
+      end
+    end
+  end
+end
diff --git a/spec/unit/knife/status_spec.rb b/spec/unit/knife/status_spec.rb
new file mode 100644
index 0000000..b009997
--- /dev/null
+++ b/spec/unit/knife/status_spec.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Sahil Muthoo (<sahil.muthoo at gmail.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 'highline'
+
+describe Chef::Knife::Status do
+  before(:each) do
+    node = Chef::Node.new.tap do |n|
+      n.automatic_attrs["fqdn"] = "foobar"
+      n.automatic_attrs["ohai_time"] = 1343845969
+    end
+    query = mock("Chef::Search::Query")
+    query.stub!(:search).and_yield(node)
+    Chef::Search::Query.stub!(:new).and_return(query)
+    @knife  = Chef::Knife::Status.new
+    @stdout = StringIO.new
+    @knife.stub!(:highline).and_return(HighLine.new(StringIO.new, @stdout))
+  end
+
+  describe "run" do
+    it "should not colorize output unless it's writing to a tty" do
+      @knife.run
+      @stdout.string.match(/foobar/).should_not be_nil
+      @stdout.string.match(/\e.*ago/).should be_nil
+    end
+  end
+end
diff --git a/spec/unit/knife/tag_create_spec.rb b/spec/unit/knife/tag_create_spec.rb
new file mode 100644
index 0000000..925d060
--- /dev/null
+++ b/spec/unit/knife/tag_create_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Chef::Knife::TagCreate do
+  before(:each) do
+    Chef::Config[:node_name] = "webmonkey.example.com"
+    @knife = Chef::Knife::TagCreate.new
+    @knife.name_args = [ Chef::Config[:node_name], "happytag" ]
+
+    @node = Chef::Node.new
+    @node.stub! :save
+    Chef::Node.stub!(:load).and_return @node
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "can create tags on a node" do
+      @knife.run
+      @node.tags.should == ["happytag"]
+      @stdout.string.should match /created tags happytag.+node webmonkey.example.com/i
+    end
+  end
+end
diff --git a/spec/unit/knife/tag_delete_spec.rb b/spec/unit/knife/tag_delete_spec.rb
new file mode 100644
index 0000000..ca27903
--- /dev/null
+++ b/spec/unit/knife/tag_delete_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Chef::Knife::TagDelete do
+  before(:each) do
+    Chef::Config[:node_name] = "webmonkey.example.com"
+    @knife = Chef::Knife::TagDelete.new
+    @knife.name_args = [ Chef::Config[:node_name], "sadtag" ]
+
+    @node = Chef::Node.new
+    @node.stub! :save
+    @node.tags << "sadtag" << "happytag"
+    Chef::Node.stub!(:load).and_return @node
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  describe "run" do
+    it "can delete tags on a node" do
+      @node.tags.should == ["sadtag", "happytag"]
+      @knife.run
+      @node.tags.should == ["happytag"]
+      @stdout.string.should match /deleted.+sadtag/i
+    end
+  end
+end
diff --git a/spec/unit/knife/tag_list_spec.rb b/spec/unit/knife/tag_list_spec.rb
new file mode 100644
index 0000000..0de5d5e
--- /dev/null
+++ b/spec/unit/knife/tag_list_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Chef::Knife::TagList do
+  before(:each) do
+    Chef::Config[:node_name] = "webmonkey.example.com"
+    @knife = Chef::Knife::TagList.new
+    @knife.name_args = [ Chef::Config[:node_name], "sadtag" ]
+
+    @node = Chef::Node.new
+    @node.stub! :save
+    @node.tags << "sadtag" << "happytag"
+    Chef::Node.stub!(:load).and_return @node
+  end
+
+  describe "run" do
+    it "can list tags on a node" do
+      expected = %w(sadtag happytag)
+      @node.tags.should == expected
+      @knife.should_receive(:output).with(expected)
+      @knife.run
+    end
+  end
+end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
new file mode 100644
index 0000000..0e36e14
--- /dev/null
+++ b/spec/unit/knife/user_create_spec.rb
@@ -0,0 +1,86 @@
+#
+# 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'
+
+Chef::Knife::UserCreate.load_deps
+
+describe Chef::Knife::UserCreate do
+  before(:each) do
+    @knife = Chef::Knife::UserCreate.new
+    @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'
+    @user.stub!(:create).and_return(@user_with_private_key)
+    Chef::User.stub!(:new).and_return(@user)
+    Chef::User.stub!(:from_hash).and_return(@user)
+    @knife.stub!(:edit_data).and_return(@user.to_hash)
+    @stdout = StringIO.new
+    @stderr = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+    @knife.ui.stub!(:stderr).and_return(@stderr)
+  end
+
+  it "creates a new user" do
+    Chef::User.should_receive(:new).and_return(@user)
+    @user.should_receive(:create)
+    @knife.run
+    @stdout.string.should match /created user.+a_user/i
+  end
+
+  it "sets the password" do
+    @knife.config[:user_password] = "a_password"
+    @user.should_receive(:password).with("a_password")
+    @knife.run
+  end
+
+  it "exits with an error if password is blank" do
+    @knife.config[:user_password] = ''
+    lambda { @knife.run }.should raise_error SystemExit
+    @stderr.string.should match /You must specify a non-blank password/
+  end
+
+  it "sets the user name" do
+    @user.should_receive(:name).with("a_user")
+    @knife.run
+  end
+
+  it "sets the public key if given" do
+    @knife.config[:user_key] = "/a/filename"
+    File.stub(:read).with(File.expand_path("/a/filename")).and_return("a_key")
+    @user.should_receive(:public_key).with("a_key")
+    @knife.run
+  end
+
+  it "allows you to edit the data" do
+    @knife.should_receive(:edit_data).with(@user)
+    @knife.run
+  end
+
+  it "writes the private key to a file when --file is specified" do
+    @knife.config[:file] = "/tmp/a_file"
+    filehandle = mock("filehandle")
+    filehandle.should_receive(:print).with('private_key')
+    File.should_receive(:open).with("/tmp/a_file", "w").and_yield(filehandle)
+    @knife.run
+  end
+end
diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb
new file mode 100644
index 0000000..be027e5
--- /dev/null
+++ b/spec/unit/knife/user_delete_spec.rb
@@ -0,0 +1,39 @@
+#
+# 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'
+
+describe Chef::Knife::UserDelete do
+  before(:each) do
+    Chef::Knife::UserDelete.load_deps
+    @knife = Chef::Knife::UserDelete.new
+    @knife.name_args = [ 'my_user' ]
+  end
+
+  it 'deletes the user' do
+    @knife.should_receive(:delete_object).with(Chef::User, 'my_user')
+    @knife.run
+  end
+
+  it 'prints usage and exits when a user name is not provided' do
+    @knife.name_args = []
+    @knife.should_receive(:show_usage)
+    @knife.ui.should_receive(:fatal)
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+end
diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb
new file mode 100644
index 0000000..d5b380a
--- /dev/null
+++ b/spec/unit/knife/user_edit_spec.rb
@@ -0,0 +1,42 @@
+#
+# 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'
+
+describe Chef::Knife::UserEdit do
+  before(:each) do
+    Chef::Knife::UserEdit.load_deps
+    @knife = Chef::Knife::UserEdit.new
+    @knife.name_args = [ 'my_user' ]
+    @knife.config[:disable_editing] = true
+  end
+
+  it 'loads and edits the user' do
+    data = { :name => "my_user" }
+    Chef::User.stub(:load).with("my_user").and_return(data)
+    @knife.should_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 = []
+    @knife.should_receive(:show_usage)
+    @knife.ui.should_receive(:fatal)
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+end
diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb
new file mode 100644
index 0000000..7a47f9d
--- /dev/null
+++ b/spec/unit/knife/user_list_spec.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Steven Danna
+# Copyright:: Copyright (c) 2012 Opscode, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::UserList do
+  before(:each) do
+    Chef::Knife::UserList.load_deps
+    @knife = Chef::Knife::UserList.new
+  end
+
+  it 'lists the users' do
+    Chef::User.should_receive(:list)
+    @knife.should_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
new file mode 100644
index 0000000..fddab57
--- /dev/null
+++ b/spec/unit/knife/user_reregister_spec.rb
@@ -0,0 +1,53 @@
+#
+# 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'
+
+describe Chef::Knife::UserReregister do
+  before(:each) do
+    Chef::Knife::UserReregister.load_deps
+    @knife = Chef::Knife::UserReregister.new
+    @knife.name_args = [ 'a_user' ]
+    @user_mock = mock('user_mock', :private_key => "private_key")
+    Chef::User.stub!(:load).and_return(@user_mock)
+    @stdout = StringIO.new
+    @knife.ui.stub!(:stdout).and_return(@stdout)
+  end
+
+  it 'prints usage and exits when a user name is not provided' do
+    @knife.name_args = []
+    @knife.should_receive(:show_usage)
+    @knife.ui.should_receive(:fatal)
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+
+  it 'reregisters the user and prints the key' do
+    @user_mock.should_receive(:reregister).and_return(@user_mock)
+    @knife.run
+    @stdout.string.should match( /private_key/ )
+  end
+
+  it 'writes the private key to a file when --file is specified' do
+    @user_mock.should_receive(:reregister).and_return(@user_mock)
+    @knife.config[:file] = '/tmp/a_file'
+    filehandle = StringIO.new
+    File.should_receive(:open).with('/tmp/a_file', 'w').and_yield(filehandle)
+    @knife.run
+    filehandle.string.should == "private_key"
+  end
+end
diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb
new file mode 100644
index 0000000..f2bd959
--- /dev/null
+++ b/spec/unit/knife/user_show_spec.rb
@@ -0,0 +1,41 @@
+#
+# 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'
+
+describe Chef::Knife::UserShow do
+  before(:each) do
+    Chef::Knife::UserShow.load_deps
+    @knife = Chef::Knife::UserShow.new
+    @knife.name_args = [ 'my_user' ]
+    @user_mock = mock('user_mock')
+  end
+
+  it 'loads and displays the user' do
+    Chef::User.should_receive(:load).with('my_user').and_return(@user_mock)
+    @knife.should_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 = []
+    @knife.should_receive(:show_usage)
+    @knife.ui.should_receive(:fatal)
+    lambda { @knife.run }.should raise_error(SystemExit)
+  end
+end
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
new file mode 100644
index 0000000..3edddec
--- /dev/null
+++ b/spec/unit/knife_spec.rb
@@ -0,0 +1,367 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2011 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.
+#
+
+# Fixtures for subcommand loading live in this namespace
+module KnifeSpecs
+end
+
+require 'spec_helper'
+
+describe Chef::Knife do
+  before(:each) do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    Chef::Config[:node_name]  = "webmonkey.example.com"
+    @knife = Chef::Knife.new
+    @knife.ui.stub!(:puts)
+    @knife.ui.stub!(:print)
+    Chef::Log.stub!(:init)
+    Chef::Log.stub!(:level)
+    [:debug, :info, :warn, :error, :crit].each do |level_sym|
+      Chef::Log.stub!(level_sym)
+    end
+    Chef::Knife.stub!(:puts)
+    @stdout = StringIO.new
+  end
+
+  describe "selecting a config file" do
+    context "when the current working dir is inside a symlinked directory" do
+      before do
+        Chef::Knife.reset_config_path!
+        # pwd according to your shell is /home/someuser/prod/chef-repo, but
+        # chef-repo is a symlink to /home/someuser/codes/chef-repo
+        ENV.stub!(:[]).with("PWD").and_return("/home/someuser/prod/chef-repo")
+        Dir.stub!(:pwd).and_return("/home/someuser/codes/chef-repo")
+      end
+
+      after do
+        Chef::Knife.reset_config_path!
+      end
+
+      it "loads the config from the non-dereferenced directory path" do
+        File.should_receive(:exist?).with("/home/someuser/prod/chef-repo/.chef").and_return(false)
+        File.should_receive(:exist?).with("/home/someuser/prod/.chef").and_return(true)
+        File.should_receive(:directory?).with("/home/someuser/prod/.chef").and_return(true)
+        Chef::Knife.chef_config_dir.should == "/home/someuser/prod/.chef"
+      end
+    end
+  end
+
+  describe "after loading a subcommand" do
+    before do
+      Chef::Knife.reset_subcommands!
+
+      if KnifeSpecs.const_defined?(:TestNameMapping)
+        KnifeSpecs.send(:remove_const, :TestNameMapping)
+      end
+
+      if KnifeSpecs.const_defined?(:TestExplicitCategory)
+        KnifeSpecs.send(:remove_const, :TestExplicitCategory)
+      end
+
+      Kernel.load(File.join(CHEF_SPEC_DATA, 'knife_subcommand', 'test_name_mapping.rb'))
+      Kernel.load(File.join(CHEF_SPEC_DATA, 'knife_subcommand', 'test_explicit_category.rb'))
+    end
+
+    it "has a category based on its name" do
+      KnifeSpecs::TestNameMapping.subcommand_category.should == 'test'
+    end
+
+    it "has an explictly defined category if set" do
+      KnifeSpecs::TestExplicitCategory.subcommand_category.should == 'cookbook site'
+    end
+
+    it "can reference the subcommand by its snake cased name" do
+      Chef::Knife.subcommands['test_name_mapping'].should equal(KnifeSpecs::TestNameMapping)
+    end
+
+    it "lists subcommands by category" do
+      Chef::Knife.subcommands_by_category['test'].should include('test_name_mapping')
+    end
+
+    it "lists subcommands by category when the subcommands have explicit categories" do
+      Chef::Knife.subcommands_by_category['cookbook site'].should include('test_explicit_category')
+    end
+
+    it "has empty dependency_loader list by default" do
+      KnifeSpecs::TestNameMapping.dependency_loaders.should be_empty
+    end
+  end
+
+  describe "after loading all subcommands" do
+    before do
+      Chef::Knife.reset_subcommands!
+      Chef::Knife.load_commands
+    end
+
+    it "references a subcommand class by its snake cased name" do
+      class SuperAwesomeCommand < Chef::Knife
+      end
+
+      Chef::Knife.load_commands
+
+      Chef::Knife.subcommands.should have_key("super_awesome_command")
+      Chef::Knife.subcommands["super_awesome_command"].should == SuperAwesomeCommand
+    end
+
+    it "guesses a category from a given ARGV" do
+      Chef::Knife.subcommands_by_category["cookbook"] << :cookbook
+      Chef::Knife.subcommands_by_category["cookbook site"] << :cookbook_site
+      Chef::Knife.guess_category(%w{cookbook foo bar baz}).should == 'cookbook'
+      Chef::Knife.guess_category(%w{cookbook site foo bar baz}).should == 'cookbook site'
+      Chef::Knife.guess_category(%w{cookbook site --help}).should == 'cookbook site'
+    end
+
+    it "finds a subcommand class based on ARGV" do
+      Chef::Knife.subcommands["cookbook_site_vendor"] = :CookbookSiteVendor
+      Chef::Knife.subcommands["cookbook"] = :Cookbook
+      Chef::Knife.subcommand_class_from(%w{cookbook site vendor --help foo bar baz}).should == :CookbookSiteVendor
+    end
+
+  end
+
+  describe "when running a command" do
+    before(:each) do
+      if KnifeSpecs.const_defined?(:TestYourself)
+        KnifeSpecs.send :remove_const, :TestYourself
+      end
+      Kernel.load(File.join(CHEF_SPEC_DATA, 'knife_subcommand', 'test_yourself.rb'))
+      Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.kind_of?(Class) }
+    end
+
+    it "merges the global knife CLI options" do
+      extra_opts = {}
+      extra_opts[:editor] = {:long=>"--editor EDITOR",
+                             :description=>"Set the editor to use for interactive commands",
+                             :short=>"-e EDITOR",
+                             :default=>"/usr/bin/vim"}
+
+      # there is special hackery to return the subcommand instance going on here.
+      command = Chef::Knife.run(%w{test yourself}, extra_opts)
+      editor_opts = command.options[:editor]
+      editor_opts[:long].should         == "--editor EDITOR"
+      editor_opts[:description].should  == "Set the editor to use for interactive commands"
+      editor_opts[:short].should        == "-e EDITOR"
+      editor_opts[:default].should      == "/usr/bin/vim"
+    end
+
+    it "creates an instance of the subcommand and runs it" do
+      command = Chef::Knife.run(%w{test yourself})
+      command.should be_an_instance_of(KnifeSpecs::TestYourself)
+      command.ran.should be_true
+    end
+
+    it "passes the command specific args to the subcommand" do
+      command = Chef::Knife.run(%w{test yourself with some args})
+      command.name_args.should == %w{with some args}
+    end
+
+    it "excludes the command name from the name args when parts are joined with underscores" do
+      command = Chef::Knife.run(%w{test_yourself with some args})
+      command.name_args.should == %w{with some args}
+    end
+
+    it "exits if no subcommand matches the CLI args" do
+      Chef::Knife.ui.stub!(:stdout).and_return(@stdout)
+      Chef::Knife.ui.should_receive(:fatal)
+      lambda {Chef::Knife.run(%w{fuuu uuuu fuuuu})}.should raise_error(SystemExit) { |e| e.status.should_not == 0 }
+    end
+
+    it "loads lazy dependencies" do
+      command = Chef::Knife.run(%w{test yourself})
+      KnifeSpecs::TestYourself.test_deps_loaded.should be_true
+    end
+
+    it "loads lazy dependencies from multiple deps calls" do
+      other_deps_loaded = false
+      KnifeSpecs::TestYourself.class_eval do
+        deps { other_deps_loaded = true }
+      end
+      command = Chef::Knife.run(%w{test yourself})
+      KnifeSpecs::TestYourself.test_deps_loaded.should be_true
+      other_deps_loaded.should be_true
+    end
+
+    describe "merging configuration options" do
+      before do
+        KnifeSpecs::TestYourself.option(:opt_with_default,
+                                        :short => "-D VALUE",
+                                        :default => "default-value")
+      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
+        knife_command.config[:opt_with_default].should == "default-value"
+      end
+
+      it "prefers a value in Chef::Config[:knife] to the default" do
+        Chef::Config[:knife][:opt_with_default] = "from-knife-config"
+        knife_command = KnifeSpecs::TestYourself.new([]) #empty argv
+        knife_command.configure_chef
+        knife_command.config[:opt_with_default].should == "from-knife-config"
+      end
+
+      it "prefers a value from command line over Chef::Config and the default" do
+        Chef::Config[:knife][:opt_with_default] = "from-knife-config"
+        knife_command = KnifeSpecs::TestYourself.new(["-D", "from-cli"])
+        knife_command.configure_chef
+        knife_command.config[:opt_with_default].should == "from-cli"
+      end
+    end
+
+  end
+
+  describe "when first created" do
+    before do
+      unless KnifeSpecs.const_defined?(:TestYourself)
+        Kernel.load(File.join(CHEF_SPEC_DATA, 'knife_subcommand', 'test_yourself.rb'))
+      end
+      @knife = KnifeSpecs::TestYourself.new(%w{with some args -s scrogramming})
+    end
+
+    it "it parses the options passed to it" do
+      @knife.config[:scro].should == 'scrogramming'
+    end
+
+    it "extracts its command specific args from the full arg list" do
+      @knife.name_args.should == %w{with some args}
+    end
+
+    it "does not have lazy dependencies loaded" do
+      @knife.class.test_deps_loaded.should_not be_true
+    end
+  end
+
+  describe "when formatting exceptions" do
+    before do
+      @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new
+      @knife.ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {})
+      @knife.should_receive(:exit).with(100)
+    end
+
+    it "formats 401s nicely" do
+      response = Net::HTTPUnauthorized.new("1.1", "401", "Unauthorized")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "y u no syncronize your clock?"))
+      @knife.stub!(:run).and_raise(Net::HTTPServerException.new("401 Unauthorized", response))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(/ERROR: Failed to authenticate to/)
+      @stdout.string.should match(/Response:  y u no syncronize your clock\?/)
+    end
+
+    it "formats 403s nicely" do
+      response = Net::HTTPForbidden.new("1.1", "403", "Forbidden")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "y u no administrator"))
+      @knife.stub!(:run).and_raise(Net::HTTPServerException.new("403 Forbidden", response))
+      @knife.stub!(:username).and_return("sadpanda")
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: You authenticated successfully to http.+ as sadpanda but you are not authorized for this action])
+      @stdout.string.should match(%r[Response:  y u no administrator])
+    end
+
+    it "formats 400s nicely" do
+      response = Net::HTTPBadRequest.new("1.1", "400", "Bad Request")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "y u search wrong"))
+      @knife.stub!(:run).and_raise(Net::HTTPServerException.new("400 Bad Request", response))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: The data in your request was invalid])
+      @stdout.string.should match(%r[Response: y u search wrong])
+    end
+
+    it "formats 404s nicely" do
+      response = Net::HTTPNotFound.new("1.1", "404", "Not Found")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "nothing to see here"))
+      @knife.stub!(:run).and_raise(Net::HTTPServerException.new("404 Not Found", response))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: The object you are looking for could not be found])
+      @stdout.string.should match(%r[Response: nothing to see here])
+    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.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone"))
+      @knife.stub!(:run).and_raise(Net::HTTPFatalError.new("500 Internal Server Error", response))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: internal server error])
+      @stdout.string.should match(%r[Response: sad trombone])
+    end
+
+    it "formats 502s nicely" do
+      response = Net::HTTPBadGateway.new("1.1", "502", "Bad Gateway")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "sadder trombone"))
+      @knife.stub!(:run).and_raise(Net::HTTPFatalError.new("502 Bad Gateway", response))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: bad gateway])
+      @stdout.string.should match(%r[Response: sadder trombone])
+    end
+
+    it "formats 503s nicely" do
+      response = Net::HTTPServiceUnavailable.new("1.1", "503", "Service Unavailable")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "saddest trombone"))
+      @knife.stub!(:run).and_raise(Net::HTTPFatalError.new("503 Service Unavailable", response))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: Service temporarily unavailable])
+      @stdout.string.should match(%r[Response: saddest trombone])
+    end
+
+    it "formats other HTTP errors nicely" do
+      response = Net::HTTPPaymentRequired.new("1.1", "402", "Payment Required")
+      response.instance_variable_set(:@read, true) # I hate you, net/http.
+      response.stub!(:body).and_return(Chef::JSONCompat.to_json(:error => "nobugfixtillyoubuy"))
+      @knife.stub!(:run).and_raise(Net::HTTPServerException.new("402 Payment Required", response))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: Payment Required])
+      @stdout.string.should match(%r[Response: nobugfixtillyoubuy])
+    end
+
+    it "formats NameError and NoMethodError nicely" do
+      @knife.stub!(:run).and_raise(NameError.new("Undefined constant FUUU"))
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: knife encountered an unexpected error])
+      @stdout.string.should match(%r[This may be a bug in the 'knife' knife command or plugin])
+      @stdout.string.should match(%r[Exception: NameError: Undefined constant FUUU])
+    end
+
+    it "formats missing private key errors nicely" do
+      @knife.stub!(:run).and_raise(Chef::Exceptions::PrivateKeyMissing.new('key not there'))
+      @knife.stub!(:api_key).and_return("/home/root/.chef/no-key-here.pem")
+      @knife.run_with_pretty_exceptions
+      @stderr.string.should match(%r[ERROR: Your private key could not be loaded from /home/root/.chef/no-key-here.pem])
+      @stdout.string.should match(%r[Check your configuration file and ensure that your private key is readable])
+    end
+
+    it "formats connection refused errors nicely" do
+      @knife.stub!(:run).and_raise(Errno::ECONNREFUSED.new('y u no shut up'))
+      @knife.run_with_pretty_exceptions
+      # Errno::ECONNREFUSED message differs by platform
+      # *nix = Errno::ECONNREFUSED: Connection refused
+      # win32: Errno::ECONNREFUSED: No connection could be made because the target machine actively refused it.
+      @stderr.string.should match(%r[ERROR: Network Error: .* - y u no shut up])
+      @stdout.string.should match(%r[Check your knife configuration and network settings])
+    end
+  end
+
+end
diff --git a/spec/unit/log_spec.rb b/spec/unit/log_spec.rb
new file mode 100644
index 0000000..7be40f7
--- /dev/null
+++ b/spec/unit/log_spec.rb
@@ -0,0 +1,24 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 'tempfile'
+require 'logger'
+require 'spec_helper'
+
+describe Chef::Log do
+end
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
new file mode 100644
index 0000000..19d3504
--- /dev/null
+++ b/spec/unit/lwrp_spec.rb
@@ -0,0 +1,298 @@
+#
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright (c) 2009 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'
+
+module LwrpConstScopingConflict
+end
+
+describe "LWRP" do
+  before do
+    @original_VERBOSE = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after do
+    $VERBOSE = @original_VERBOSE
+  end
+
+  describe "when overriding an existing class" do
+    before :each do
+      $stderr.stub!(:write)
+    end
+
+    it "should log if attempting to load resource of same name" do
+      Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
+        Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
+      end
+
+      Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
+        Chef::Log.should_receive(:info).with(/overriding/)
+        Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
+      end
+    end
+
+    it "should log if attempting to load provider of same name" do
+      Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file|
+        Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil)
+      end
+
+      Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file|
+        Chef::Log.should_receive(:info).with(/overriding/)
+        Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil)
+      end
+    end
+
+    it "removes the old LRWP resource class from the list of resource subclasses [CHEF-3432]" do
+      # CHEF-3432 regression test:
+      # Chef::Resource keeps a list of all subclasses to assist class inflation
+      # for json parsing (see Chef::JSONCompat). When replacing LWRP resources,
+      # we need to ensure the old resource class is remove from that list.
+      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
+      Chef::Resource.resource_classes.should 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)
+      end
+      Chef::Resource.resource_classes.should_not include(first_lwr_foo_class)
+    end
+
+    it "does not attempt to remove classes from higher up namespaces [CHEF-4117]" do
+      conflicting_lwrp_file = File.expand_path( "lwrp_const_scoping/resources/conflict.rb", CHEF_SPEC_DATA)
+      # The test is that this should not raise an error:
+      Chef::Resource::LWRPBase.build_from_file("lwrp_const_scoping", conflicting_lwrp_file, nil)
+    end
+
+  end
+
+  describe "Lightweight Chef::Resource" 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)
+      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, nil)
+      end
+    end
+
+    it "should load the resource into a properly-named class" do
+      Chef::Resource.const_get("LwrpFoo").should be_kind_of(Class)
+    end
+
+    it "should set resource_name" do
+      Chef::Resource::LwrpFoo.new("blah").resource_name.should eql(:lwrp_foo)
+    end
+
+    it "should add the specified actions to the allowed_actions array" do
+      Chef::Resource::LwrpFoo.new("blah").allowed_actions.should include(:pass_buck, :twiddle_thumbs)
+    end
+
+    it "should set the specified action as the default action" do
+      Chef::Resource::LwrpFoo.new("blah").action.should == :pass_buck
+    end
+
+    it "should create a method for each attribute" do
+      Chef::Resource::LwrpFoo.new("blah").methods.map{ |m| m.to_sym}.should include(:monkey)
+    end
+
+    it "should build attribute methods that respect validation rules" do
+      lambda { Chef::Resource::LwrpFoo.new("blah").monkey(42) }.should raise_error(ArgumentError)
+    end
+
+    it "should have access to the run context and node during class definition" do
+      node = Chef::Node.new
+      node.normal[:penguin_name] = "jackass"
+      run_context = Chef::RunContext.new(node, Chef::CookbookCollection.new, @events)
+
+      Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources_with_default_attributes", "*"))].each do |file|
+        Chef::Resource::LWRPBase.build_from_file("lwrp", file, run_context)
+      end
+
+      cls = Chef::Resource.const_get("LwrpNodeattr")
+      cls.node.should be_kind_of(Chef::Node)
+      cls.run_context.should be_kind_of(Chef::RunContext)
+      cls.node[:penguin_name].should eql("jackass")
+    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)
+      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
+
+      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
+
+      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)
+      end
+
+    end
+
+    it "should properly handle a new_resource reference" do
+      resource = Chef::Resource::LwrpFoo.new("morpheus")
+      resource.monkey("bob")
+      resource.provider(:lwrp_monkey_name_printer)
+      resource.run_context = @run_context
+
+      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
+      Chef::Provider.const_get("LwrpBuckPasser").should be_kind_of(Class)
+    end
+
+    it "should create a method for each attribute" do
+      new_resource = mock("new resource", :null_object=>true)
+      Chef::Provider::LwrpBuckPasser.new(nil, new_resource).methods.map{|m|m.to_sym}.should include(:action_pass_buck)
+      Chef::Provider::LwrpThumbTwiddler.new(nil, new_resource).methods.map{|m|m.to_sym}.should include(:action_twiddle_thumbs)
+    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.action(:pass_buck)
+      injector.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)
+
+      Chef::Runner.new(@run_context).converge
+
+      @run_context.resource_collection[0].should eql(injector)
+      @run_context.resource_collection[1].name.should eql(:prepared_thumbs)
+      @run_context.resource_collection[2].name.should eql(:twiddled_thumbs)
+      @run_context.resource_collection[3].should 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.action(:pass_buck)
+      injector.provider(:lwrp_buck_passer)
+
+      injector2 = Chef::Resource::LwrpBar.new("tank", @run_context)
+      injector2.action(:pass_buck)
+      injector2.provider(:lwrp_buck_passer_2)
+
+      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)
+
+      Chef::Runner.new(@run_context).converge
+
+      @run_context.resource_collection[0].should eql(injector)
+      @run_context.resource_collection[1].name.should eql(:prepared_thumbs)
+      @run_context.resource_collection[2].name.should eql(:twiddled_thumbs)
+      @run_context.resource_collection[3].should eql(dummy)
+      @run_context.resource_collection[4].should eql(injector2)
+      @run_context.resource_collection[5].name.should eql(:prepared_eyes)
+      @run_context.resource_collection[6].name.should eql(:dried_paint_watched)
+    end
+
+    it "should properly handle a new_resource reference" do
+      resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+      resource.monkey("bob")
+      resource.provider(:lwrp_monkey_name_printer)
+
+      provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
+      provider.action_twiddle_thumbs
+
+      provider.monkey_name.should == "my monkey's name is 'bob'"
+    end
+
+    it "should properly handle an embedded Resource accessing the enclosing Provider's scope" do
+      resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+      resource.monkey("bob")
+      resource.provider(:lwrp_embedded_resource_accesses_providers_scope)
+
+      provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
+      #provider = @runner.build_provider(resource)
+      provider.action_twiddle_thumbs
+
+      provider.enclosed_resource.monkey.should == 'bob, the monkey'
+    end
+
+    describe "when using inline compilation" do
+      before do
+        # Behavior in these examples depends on implementation of fixture provider.
+        # See spec/data/lwrp/providers/inline_compiler
+
+        # 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.allowed_actions << :test
+        @resource.action(:test)
+        @resource.provider(:lwrp_inline_compiler)
+      end
+
+      it "does not add interior resources to the exterior resource collection" do
+        @resource.run_action(:test)
+        @run_context.resource_collection.should be_empty
+      end
+
+      context "when interior resources are updated" do
+        it "processes notifications within the LWRP provider's action" do
+          @resource.run_action(:test)
+          $interior_ruby_block_2.should == "executed"
+        end
+
+        it "marks the parent resource updated" do
+          @resource.run_action(:test)
+          @resource.should be_updated
+          @resource.should be_updated_by_last_action
+        end
+      end
+
+      context "when interior resources are not updated" do
+        it "does not mark the parent resource updated" do
+          @resource.run_action(:no_updates)
+          @resource.should_not be_updated
+          @resource.should_not be_updated_by_last_action
+        end
+      end
+
+    end
+
+  end
+
+end
diff --git a/spec/unit/mash_spec.rb b/spec/unit/mash_spec.rb
new file mode 100644
index 0000000..7358781
--- /dev/null
+++ b/spec/unit/mash_spec.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Matthew Kent (<mkent at magoazul.com>)
+# Copyright:: Copyright (c) 2011 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/mash'
+
+describe Mash do
+  it "should duplicate a simple key/value mash to a new mash" do
+    data = {:x=>"one", :y=>"two", :z=>"three"}
+    @orig = Mash.new(data)
+    @copy = @orig.dup
+    @copy.to_hash.should == Mash.new(data).to_hash
+    @copy[:x] = "four"
+    @orig[:x].should == "one"
+  end
+
+  it "should duplicate a mash with an array to a new mash" do
+    data = {:x=>"one", :y=>"two", :z=>[1,2,3]}
+    @orig = Mash.new(data)
+    @copy = @orig.dup
+    @copy.to_hash.should == Mash.new(data).to_hash
+    @copy[:z] << 4
+    @orig[:z].should == [1,2,3]
+  end
+
+  it "should duplicate a nested mash to a new mash" do
+    data = {:x=>"one", :y=>"two", :z=>Mash.new({:a=>[1,2,3]})}
+    @orig = Mash.new(data)
+    @copy = @orig.dup
+    @copy.to_hash.should == Mash.new(data).to_hash
+    @copy[:z][:a] << 4
+    @orig[:z][:a].should == [1,2,3]
+  end
+
+  # add more!
+end
diff --git a/spec/unit/mixin/checksum_spec.rb b/spec/unit/mixin/checksum_spec.rb
new file mode 100644
index 0000000..c0f0acd
--- /dev/null
+++ b/spec/unit/mixin/checksum_spec.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2009 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/mixin/checksum'
+require 'stringio'
+
+class Chef::CMCCheck
+  include Chef::Mixin::Checksum
+end
+
+describe Chef::Mixin::Checksum do
+  before(:each) do
+    @checksum_user = Chef::CMCCheck.new
+    @cache = Chef::Digester.instance
+    @file = CHEF_SPEC_DATA + "/checksum/random.txt"
+    @stat = mock("File::Stat", { :mtime => Time.at(0) })
+    File.stub!(:stat).and_return(@stat)
+  end
+
+  it "gets the checksum of a file" do
+    @checksum_user.checksum(@file).should == "09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394"
+  end
+
+end
+
diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb
new file mode 100644
index 0000000..a2a2fef
--- /dev/null
+++ b/spec/unit/mixin/command_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Hongli Lai (hongli at phusion.nl)
+# Copyright:: Copyright (c) 2009 Phusion
+# 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::Command, :volatile do
+
+  if windows?
+
+    pending("TODO MOVE: this is a platform specific integration test.")
+
+  else
+
+    describe "popen4" do
+      include Chef::Mixin::Command
+
+      it "should be possible to read the child process's stdout and stderr" do
+        popen4("sh -c 'echo hello && echo world >&2'") do |pid, stdin, stdout, stderr|
+          stdout.read.should == "hello\n"
+          stderr.read.should == "world\n"
+        end
+      end
+
+      it "should default all commands to be run in the POSIX standard C locale" do
+        popen4("echo $LC_ALL") do |pid, stdin, stdout, stderr|
+          stdout.read.strip.should == "C"
+        end
+      end
+
+      it "should respect locale when specified explicitly" do
+        popen4("echo $LC_ALL", :environment => {"LC_ALL" => "es"}) do |pid, stdin, stdout, stderr|
+          stdout.read.strip.should == "es"
+        end
+      end
+
+      it "should end when the child process reads from STDIN and a block is given" do
+        lambda {Timeout.timeout(10) do
+            popen4("ruby -e 'while gets; end'", :waitlast => true) do |pid, stdin, stdout, stderr|
+              (1..5).each { |i| stdin.puts "#{i}" }
+            end
+          end
+        }.should_not raise_error
+      end
+
+      describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do
+
+        it "returns immediately after the first child process exits" do
+          lambda {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
+          end}.should_not raise_error
+        end
+
+      end
+
+    end
+
+    describe "run_command" do
+      include Chef::Mixin::Command
+
+      it "logs the command's stderr and stdout output if the command failed" do
+        Chef::Log.stub!(:level).and_return(:debug)
+        begin
+          run_command(:command => "sh -c 'echo hello; echo world >&2; false'")
+          violated "Exception expected, but nothing raised."
+        rescue => e
+          e.message.should =~ /STDOUT: hello/
+          e.message.should =~ /STDERR: world/
+        end
+      end
+
+      describe "when a process detaches but doesn't close STDOUT and STDERR [CHEF-584]" do
+        it "returns successfully" do
+          # CHEF-2916 might have added a slight delay here, or our CI
+          # infrastructure is burdened. Bumping timeout from 2 => 4 --
+          # btm
+          # Serdar - During Solaris tests, we've seen that processes
+          # are taking a long time to exit. Bumping timeout now to 10.
+          lambda {Timeout.timeout(10) do
+            evil_forker="exit if fork; 10.times { sleep 1}"
+            run_command(:command => "ruby -e '#{evil_forker}'")
+          end}.should_not raise_error
+        end
+
+      end
+    end
+  end
+end
diff --git a/spec/unit/mixin/convert_to_class_name_spec.rb b/spec/unit/mixin/convert_to_class_name_spec.rb
new file mode 100644
index 0000000..240decc
--- /dev/null
+++ b/spec/unit/mixin/convert_to_class_name_spec.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class ConvertToClassTestHarness
+  include Chef::Mixin::ConvertToClassName
+end
+
+describe Chef::Mixin::ConvertToClassName do
+
+  before do
+    @convert = ConvertToClassTestHarness.new
+  end
+
+  it "converts a_snake_case_word to a CamelCaseWord" do
+    @convert.convert_to_class_name("now_camelized").should == "NowCamelized"
+  end
+
+  it "converts a CamelCaseWord to a snake_case_word" do
+    @convert.convert_to_snake_case("NowImASnake").should == "now_im_a_snake"
+  end
+
+  it "removes the base classes before snake casing" do
+    @convert.convert_to_snake_case("NameSpaced::Class::ThisIsWin", "NameSpaced::Class").should == "this_is_win"
+  end
+
+  it "removes the base classes without explicitly naming them and returns snake case" do
+    @convert.snake_case_basename("NameSpaced::Class::ExtraWin").should == "extra_win"
+  end
+
+  it "interprets non-alphanumeric characters in snake case as word boundaries" do
+    @convert.convert_to_class_name("now_camelized_without-hyphen").should == "NowCamelizedWithoutHyphen"
+  end
+end
diff --git a/spec/unit/mixin/deep_merge_spec.rb b/spec/unit/mixin/deep_merge_spec.rb
new file mode 100644
index 0000000..0a7bbff
--- /dev/null
+++ b/spec/unit/mixin/deep_merge_spec.rb
@@ -0,0 +1,351 @@
+#
+# Author:: Matthew Kent (<mkent at magoazul.com>)
+# Author:: Steve Midgley (http://www.misuse.org/science)
+# Copyright:: Copyright (c) 2010 Matthew Kent
+# Copyright:: Copyright (c) 2008 Steve Midgley
+# 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.
+
+# Notice:
+# This code is imported from deep_merge by Steve Midgley. deep_merge is
+# available under the MIT license from
+# http://trac.misuse.org/science/wiki/DeepMerge
+
+require 'spec_helper'
+
+# Test coverage from the original author converted to rspec
+describe Chef::Mixin::DeepMerge, "deep_merge!" do
+  before do
+    @dm = Chef::Mixin::DeepMerge
+    @field_ko_prefix = '!merge'
+  end
+
+  # deep_merge core tests - moving from basic to more complex
+
+  it "tests merging an hash w/array into blank hash" do
+    hash_src = {'id' => '2'}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src.dup, hash_dst)
+    hash_dst.should == hash_src
+  end
+
+  it "tests merging an hash w/array into blank hash" do
+    hash_src = {'region' => {'id' => ['227', '2']}}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == hash_src
+  end
+
+  it "tests merge from empty hash" do
+    hash_src = {}
+    hash_dst = {"property" => ["2","4"]}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => ["2","4"]}
+  end
+
+  it "tests merge to empty hash" do
+    hash_src = {"property" => ["2","4"]}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => ["2","4"]}
+  end
+
+  it "tests simple string overwrite" do
+    hash_src = {"name" => "value"}
+    hash_dst = {"name" => "value1"}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"name" => "value"}
+  end
+
+  it "tests simple string overwrite of empty hash" do
+    hash_src = {"name" => "value"}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == hash_src
+  end
+
+  it "tests hashes holding array" do
+    hash_src = {"property" => ["1","3"]}
+    hash_dst = {"property" => ["2","4"]}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => ["2","4","1","3"]}
+  end
+
+  it "tests hashes holding hashes holding arrays (array with duplicate elements is merged with dest then src" do
+    hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}}
+    hash_dst = {"property" => {"bedroom_count" => ["3", "2"], "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => ["3","2","1"], "bathroom_count" => ["2", "1", "4+"]}}
+  end
+
+  it "tests hash holding hash holding array v string (string is overwritten by array)" do
+    hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}}
+    hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}
+  end
+
+  it "tests hash holding hash holding string v array (array is overwritten by string)" do
+    hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}}
+    hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}}
+  end
+
+  it "tests hash holding hash holding hash v array (array is overwritten by hash)" do
+    hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}}
+    hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}
+  end
+
+  it "tests 3 hash layers holding integers (integers are overwritten by source)" do
+    hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => 2, "queen_bed" => 4}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}
+  end
+
+  it "tests 3 hash layers holding arrays of int (arrays are merged)" do
+    hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => ["1", "4+"]}}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2","1","4+"]}}
+  end
+
+  it "tests 1 hash overwriting 3 hash layers holding arrays of int" do
+    hash_src = {"property" => "1"}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => "1"}
+  end
+
+  it "tests 3 hash layers holding arrays of int (arrays are merged) but second hash's array is overwritten" do
+    hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => "1"}}
+  end
+
+  it "tests 3 hash layers holding arrays of int, but one holds int. This one overwrites, but the rest merge" do
+    hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [1]}, "bathroom_count" => ["1"]}}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [4,1]}, "bathroom_count" => ["2","1"]}}
+  end
+
+  it "tests 3 hash layers holding arrays of int, but source is incomplete." do
+    hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3]}, "bathroom_count" => ["1"]}}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}
+  end
+
+  it "tests 3 hash layers holding arrays of int, but source is shorter and has new 2nd level ints." do
+    hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}
+  end
+
+  it "tests 3 hash layers holding arrays of int, but source is empty" do
+    hash_src = {}
+    hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}
+  end
+
+  it "tests 3 hash layers holding arrays of int, but dest is empty" do
+    hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}
+  end
+
+  it "tests hash holding arrays of arrays" do
+    hash_src = {["1", "2", "3"] => ["1", "2"]}
+    hash_dst = {["4", "5"] => ["3"]}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {["1","2","3"] => ["1", "2"], ["4", "5"] => ["3"]}
+  end
+
+  it "tests merging of hash with blank hash, and make sure that source array split does not function when turned off" do
+    hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {'property' => {'bedroom_count' => ["1","2,3"]}}
+  end
+
+  it "tests merging into a blank hash" do
+    hash_src = {"action"=>"browse", "controller"=>"results"}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == hash_src
+  end
+
+  it "tests are unmerged hashes passed unmodified w/out :unpack_arrays?" do
+    hash_src = {"amenity"=>{"id"=>["26,27"]}}
+    hash_dst = {}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"amenity"=>{"id"=>["26,27"]}}
+  end
+
+  it "tests hash of array of hashes" do
+    hash_src = {"item" => [{"1" => "3"}, {"2" => "4"}]}
+    hash_dst = {"item" => [{"3" => "5"}]}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"item" => [{"3" => "5"}, {"1" => "3"}, {"2" => "4"}]}
+  end
+
+  # Additions since import
+  it "should overwrite true with false when merging boolean values" do
+    hash_src = {"valid" => false}
+    hash_dst = {"valid" => true}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"valid" => false}
+  end
+
+  it "should overwrite false with true when merging boolean values" do
+    hash_src = {"valid" => true}
+    hash_dst = {"valid" => false}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"valid" => true}
+  end
+
+  it "should overwrite a string with an empty string when merging string values" do
+    hash_src = {"item" => " "}
+    hash_dst = {"item" => "orange"}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"item" => " "}
+  end
+
+  it "should overwrite an empty string with a string when merging string values" do
+    hash_src = {"item" => "orange"}
+    hash_dst = {"item" => " "}
+    @dm.deep_merge!(hash_src, hash_dst)
+    hash_dst.should == {"item" => "orange"}
+  end
+end # deep_merge!
+
+# Chef specific
+describe Chef::Mixin::DeepMerge do
+  before do
+    @dm = Chef::Mixin::DeepMerge
+  end
+
+  describe "merge" do
+    it "should merge a hash into an empty hash" do
+      hash_dst = {}
+      hash_src = {'id' => '2'}
+      @dm.merge(hash_dst, hash_src).should == hash_src
+    end
+
+    it "should merge a nested hash into an empty hash" do
+      hash_dst = {}
+      hash_src = {'region' => {'id' => ['227', '2']}}
+      @dm.merge(hash_dst, hash_src).should == hash_src
+    end
+
+    it "should overwrite as string value when merging hashes" do
+      hash_dst = {"name" => "value1"}
+      hash_src = {"name" => "value"}
+      @dm.merge(hash_dst, hash_src).should == {"name" => "value"}
+    end
+
+    it "should merge arrays within hashes" do
+      hash_dst = {"property" => ["2","4"]}
+      hash_src = {"property" => ["1","3"]}
+      @dm.merge(hash_dst, hash_src).should == {"property" => ["2","4","1","3"]}
+    end
+
+    it "should merge deeply nested hashes" do
+      hash_dst = {"property" => {"values" => {"are" => "falling", "can" => "change"}}}
+      hash_src = {"property" => {"values" => {"are" => "stable", "may" => "rise"}}}
+      @dm.merge(hash_dst, hash_src).should == {"property" => {"values" => {"are" => "stable", "can" => "change", "may" => "rise"}}}
+    end
+
+    it "should not modify the source or destination during the merge" do
+      hash_dst = {"property" => ["1","2","3"]}
+      hash_src = {"property" => ["4","5","6"]}
+      ret = @dm.merge(hash_dst, hash_src)
+      hash_dst.should == {"property" => ["1","2","3"]}
+      hash_src.should == {"property" => ["4","5","6"]}
+      ret.should == {"property" => ["1","2","3","4","5","6"]}
+    end
+
+  end
+
+  describe "role_merge" do
+    it "errors out if knockout merge use is detected in an array" do
+      hash_dst = {"property" => ["2","4"]}
+      hash_src = {"property" => ["1","!merge:4"]}
+      lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+    end
+
+    it "errors out if knockout merge use is detected in an array (reversed merge order)" do
+      hash_dst = {"property" => ["1","!merge:4"]}
+      hash_src = {"property" => ["2","4"]}
+      lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+    end
+
+    it "errors out if knockout merge use is detected in a string" do
+      hash_dst = {"property" => ["2","4"]}
+      hash_src = {"property" => "!merge"}
+      lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+    end
+
+    it "errors out if knockout merge use is detected in a string (reversed merge order)" do
+      hash_dst = {"property" => "!merge"}
+      hash_src= {"property" => ["2","4"]}
+      lambda {@dm.role_merge(hash_dst, hash_src)}.should raise_error(Chef::Mixin::DeepMerge::InvalidSubtractiveMerge)
+    end
+  end
+
+  describe "hash-only merging" do
+    it "merges Hashes like normal deep merge" do
+      merge_ee_hash = {"top_level_a" => {"1_deep_a" => "1-a-merge-ee", "1_deep_b" => "1-deep-b-merge-ee"}, "top_level_b" => "top-level-b-merge-ee"}
+      merge_with_hash = {"top_level_a" => {"1_deep_b" => "1-deep-b-merged-onto", "1_deep_c" => "1-deep-c-merged-onto"}, "top_level_b" => "top-level-b-merged-onto" }
+
+      merged_result = @dm.hash_only_merge(merge_ee_hash, merge_with_hash)
+
+      merged_result["top_level_b"].should == "top-level-b-merged-onto"
+      merged_result["top_level_a"]["1_deep_a"].should == "1-a-merge-ee"
+      merged_result["top_level_a"]["1_deep_b"].should == "1-deep-b-merged-onto"
+      merged_result["top_level_a"]["1_deep_c"].should == "1-deep-c-merged-onto"
+    end
+
+    it "replaces arrays rather than merging them" do
+      merge_ee_hash = {"top_level_a" => {"1_deep_a" => "1-a-merge-ee", "1_deep_b" => %w[A A A]}, "top_level_b" => "top-level-b-merge-ee"}
+      merge_with_hash = {"top_level_a" => {"1_deep_b" => %w[B B B], "1_deep_c" => "1-deep-c-merged-onto"}, "top_level_b" => "top-level-b-merged-onto" }
+
+      merged_result = @dm.hash_only_merge(merge_ee_hash, merge_with_hash)
+
+      merged_result["top_level_b"].should == "top-level-b-merged-onto"
+      merged_result["top_level_a"]["1_deep_a"].should == "1-a-merge-ee"
+      merged_result["top_level_a"]["1_deep_b"].should == %w[B B B]
+    end
+
+    it "replaces non-hash items with hashes when there's a conflict" do
+      merge_ee_hash = {"top_level_a" => "top-level-a-mergee", "top_level_b" => "top-level-b-merge-ee"}
+      merge_with_hash = {"top_level_a" => {"1_deep_b" => %w[B B B], "1_deep_c" => "1-deep-c-merged-onto"}, "top_level_b" => "top-level-b-merged-onto" }
+
+      merged_result = @dm.hash_only_merge(merge_ee_hash, merge_with_hash)
+
+      merged_result["top_level_a"].should be_a(Hash)
+      merged_result["top_level_a"]["1_deep_a"].should be_nil
+      merged_result["top_level_a"]["1_deep_b"].should == %w[B B B]
+    end
+
+  end
+end
diff --git a/spec/unit/mixin/deprecation_spec.rb b/spec/unit/mixin/deprecation_spec.rb
new file mode 100644
index 0000000..3ebf06e
--- /dev/null
+++ b/spec/unit/mixin/deprecation_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/deprecation'
+
+describe Chef::Mixin do
+  describe "deprecating constants (Class/Module)" do
+    before do
+      Chef::Mixin.deprecate_constant(:DeprecatedClass, Chef::Node, "This is a test deprecation")
+      @log_io = StringIO.new
+      Chef::Log.init(@log_io)
+    end
+
+    it "has a list of deprecated constants" do
+      Chef::Mixin.deprecated_constants.should have_key(:DeprecatedClass)
+    end
+
+    it "returns the replacement when accessing the deprecated constant" do
+      Chef::Mixin::DeprecatedClass.should == Chef::Node
+    end
+
+    it "warns when accessing the deprecated constant" do
+      Chef::Mixin::DeprecatedClass
+      @log_io.string.should include("This is a test deprecation")
+    end
+  end
+end
+
+describe Chef::Mixin::Deprecation::DeprecatedInstanceVariable do
+  before do
+    Chef::Log.logger = Logger.new(StringIO.new)
+
+    @deprecated_ivar = Chef::Mixin::Deprecation::DeprecatedInstanceVariable.new('value', 'an_ivar')
+  end
+
+  it "forward method calls to the target object" do
+    @deprecated_ivar.length.should == 5
+    @deprecated_ivar.to_sym.should == :value
+  end
+
+end
diff --git a/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
new file mode 100644
index 0000000..cffb1b9
--- /dev/null
+++ b/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb
@@ -0,0 +1,96 @@
+#
+# Author:: Mark Mzyk (<mmzyk at opscode.com>)
+# Copyright:: Copyright (c) 2011 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 'etc'
+require 'ostruct'
+
+describe Chef::Mixin::EnforceOwnershipAndPermissions do
+
+  before(:each) do
+    @node = Chef::Node.new
+    @node.name "make_believe"
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @tmpdir = Dir.mktmpdir
+    @resource = Chef::Resource::File.new("#{@tmpdir}/madeup.txt")
+    FileUtils.touch @resource.path
+    @resource.owner "adam"
+    @provider = Chef::Provider::File.new(@resource, @run_context)
+    @provider.current_resource = @resource
+  end
+
+  after(:each) do
+    FileUtils.rm_rf(@tmpdir)
+  end
+
+  it "should call set_all on the file access control object" do
+    Chef::FileAccessControl.any_instance.should_receive(:set_all)
+    @provider.enforce_ownership_and_permissions
+  end
+
+  context "when nothing was updated" do
+    before do
+      Chef::FileAccessControl.any_instance.stub(:uid_from_resource).and_return(0)
+      Chef::FileAccessControl.any_instance.stub(:requires_changes?).and_return(false)
+      Chef::FileAccessControl.any_instance.stub(:define_resource_requirements)
+
+      passwd_struct = if windows?
+                        Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash")
+                      else
+                        Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash")
+                      end
+      group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0)
+      Etc.stub!(:getpwuid).and_return(passwd_struct)
+      Etc.stub!(:getgrgid).and_return(group_struct)
+    end
+
+    it "does not set updated_by_last_action on the new resource" do
+      @provider.new_resource.should_not_receive(:updated_by_last_action)
+
+      Chef::FileAccessControl.any_instance.stub(:set_all)
+      @provider.run_action(:create)
+    end
+
+  end
+
+  context "when something was modified" do
+    before do
+      Chef::FileAccessControl.any_instance.stub(:requires_changes?).and_return(true)
+      Chef::FileAccessControl.any_instance.stub(:uid_from_resource).and_return(0)
+
+      passwd_struct = if windows?
+                        Struct::Passwd.new("root", "x", 0, 0, "/root", "/bin/bash")
+                      else
+                        Struct::Passwd.new("root", "x", 0, 0, "root", "/root", "/bin/bash")
+                      end
+      group_struct = OpenStruct.new(:name => "root", :passwd => "x", :gid => 0)
+      Etc.stub!(:getpwuid).and_return(passwd_struct)
+      Etc.stub!(:getgrgid).and_return(group_struct)
+    end
+
+    it "sets updated_by_last_action on the new resource" do
+      @provider.new_resource.owner(0) # CHEF-3557 hack - Set these because we don't for windows
+      @provider.new_resource.group(0) # CHEF-3557 hack - Set these because we don't for windows
+      @provider.new_resource.should_receive(:updated_by_last_action)
+      Chef::FileAccessControl.any_instance.stub(:set_all)
+      @provider.run_action(:create)
+    end
+  end
+
+end
diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb
new file mode 100644
index 0000000..2947ebe
--- /dev/null
+++ b/spec/unit/mixin/params_validate_spec.rb
@@ -0,0 +1,407 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+class TinyClass
+  include Chef::Mixin::ParamsValidate
+
+  def music(is_good=true)
+    is_good
+  end
+end
+
+describe Chef::Mixin::ParamsValidate do
+  before(:each) do
+     @vo = TinyClass.new()
+  end
+
+  it "should allow a hash and a hash as arguments to validate" do
+    lambda { @vo.validate({:one => "two"}, {}) }.should_not raise_error(ArgumentError)
+  end
+
+  it "should raise an argument error if validate is called incorrectly" do
+    lambda { @vo.validate("one", "two") }.should raise_error(ArgumentError)
+  end
+
+  it "should require validation map keys to be symbols or strings" do
+    lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError)
+    lambda { @vo.validate({:one => "two"}, { "one" => true }) }.should_not raise_error(ArgumentError)
+    lambda { @vo.validate({:one => "two"}, { Hash.new => true }) }.should raise_error(ArgumentError)
+  end
+
+  it "should allow options to be required with true" do
+    lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError)
+  end
+
+  it "should allow options to be optional with false" do
+    lambda { @vo.validate({}, {:one => false})}.should_not raise_error(ArgumentError)
+  end
+
+  it "should allow you to check what kind_of? thing an argument is with kind_of" do
+    lambda {
+      @vo.validate(
+        {:one => "string"},
+        {
+          :one => {
+            :kind_of => String
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+
+    lambda {
+      @vo.validate(
+        {:one => "string"},
+        {
+          :one => {
+            :kind_of => Array
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should allow you to specify an argument is required with required" do
+    lambda {
+      @vo.validate(
+        {:one => "string"},
+        {
+          :one => {
+            :required => true
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+
+    lambda {
+      @vo.validate(
+        {:two => "string"},
+        {
+          :one => {
+            :required => true
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+
+    lambda {
+      @vo.validate(
+        {:two => "string"},
+        {
+          :one => {
+            :required => false
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+  end
+
+  it "should allow you to specify whether an object has a method with respond_to" do
+    lambda {
+      @vo.validate(
+        {:one => @vo},
+        {
+          :one => {
+            :respond_to => "validate"
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+
+    lambda {
+      @vo.validate(
+        {:one => @vo},
+        {
+          :one => {
+            :respond_to => "monkey"
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should allow you to specify whether an object has all the given methods with respond_to and an array" do
+    lambda {
+      @vo.validate(
+        {:one => @vo},
+        {
+          :one => {
+            :respond_to => ["validate", "music"]
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+
+    lambda {
+      @vo.validate(
+        {:one => @vo},
+        {
+          :one => {
+            :respond_to => ["monkey", "validate"]
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should let you set a default value with default => value" do
+    arguments = Hash.new
+    @vo.validate(arguments, {
+      :one => {
+        :default => "is the loneliest number"
+      }
+    })
+    arguments[:one].should == "is the loneliest number"
+  end
+
+  it "should let you check regular expressions" do
+    lambda {
+      @vo.validate(
+        { :one => "is good" },
+        {
+          :one => {
+            :regex => /^is good$/
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+
+    lambda {
+      @vo.validate(
+        { :one => "is good" },
+        {
+          :one => {
+            :regex => /^is bad$/
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should let you specify your own callbacks" do
+    lambda {
+      @vo.validate(
+        { :one => "is good" },
+        {
+          :one => {
+            :callbacks => {
+              "should be equal to is good" => lambda { |a|
+                a == "is good"
+              },
+            }
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+
+    lambda {
+      @vo.validate(
+        { :one => "is bad" },
+        {
+          :one => {
+            :callbacks => {
+              "should be equal to 'is good'" => lambda { |a|
+                a == "is good"
+              },
+            }
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should let you combine checks" do
+    args = { :one => "is good", :two => "is bad" }
+    lambda {
+      @vo.validate(
+        args,
+        {
+          :one => {
+            :kind_of => String,
+            :respond_to => [ :to_s, :upcase ],
+            :regex => /^is good/,
+            :callbacks => {
+              "should be your friend" => lambda { |a|
+                a == "is good"
+              }
+            },
+            :required => true
+          },
+          :two => {
+            :kind_of => String,
+            :required => false
+          },
+          :three => { :default => "neato mosquito" }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+    args[:three].should == "neato mosquito"
+    lambda {
+      @vo.validate(
+        args,
+        {
+          :one => {
+            :kind_of => String,
+            :respond_to => [ :to_s, :upcase ],
+            :regex => /^is good/,
+            :callbacks => {
+              "should be your friend" => lambda { |a|
+                a == "is good"
+              }
+            },
+            :required => true
+          },
+          :two => {
+            :kind_of => Hash,
+            :required => false
+          },
+          :three => { :default => "neato mosquito" }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should raise an ArgumentError if the validation map has an unknown check" do
+    lambda { @vo.validate(
+        { :one => "two" },
+        {
+          :one => {
+            :busted => "check"
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should accept keys that are strings in the options" do
+    lambda {
+      @vo.validate({ "one" => "two" }, { :one => { :regex => /^two$/ }})
+    }.should_not raise_error(ArgumentError)
+  end
+
+  it "should allow an array to kind_of" do
+    lambda {
+      @vo.validate(
+        {:one => "string"},
+        {
+          :one => {
+            :kind_of => [ String, Array ]
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+    lambda {
+      @vo.validate(
+        {:one => ["string"]},
+        {
+          :one => {
+            :kind_of => [ String, Array ]
+          }
+        }
+      )
+    }.should_not raise_error(ArgumentError)
+    lambda {
+      @vo.validate(
+        {:one => Hash.new},
+        {
+          :one => {
+            :kind_of => [ String, Array ]
+          }
+        }
+      )
+    }.should raise_error(ArgumentError)
+  end
+
+  it "asserts that a value returns false from a predicate method" do
+    lambda do
+      @vo.validate({:not_blank => "should pass"},
+                   {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+    end.should_not raise_error
+    lambda do
+      @vo.validate({:not_blank => ""},
+                   {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+    end.should raise_error(Chef::Exceptions::ValidationFailed)
+  end
+
+  it "should set and return a value, then return the same value" do
+    value = "meow"
+    @vo.set_or_return(:test, value, {}).object_id.should == value.object_id
+    @vo.set_or_return(:test, nil, {}).object_id.should == value.object_id
+  end
+
+  it "should set and return a default value when the argument is nil, then return the same value" do
+    value = "meow"
+    @vo.set_or_return(:test, nil, { :default => value }).object_id.should == value.object_id
+    @vo.set_or_return(:test, nil, {}).object_id.should == value.object_id
+  end
+
+  it "should raise an ArgumentError when argument is nil and required is true" do
+    lambda {
+      @vo.set_or_return(:test, nil, { :required => true })
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should not raise an error when argument is nil and required is false" do
+    lambda {
+      @vo.set_or_return(:test, nil, { :required => false })
+    }.should_not raise_error(ArgumentError)
+  end
+
+  it "should set and return @name, then return @name for foo when argument is nil" do
+    value = "meow"
+    @vo.set_or_return(:name, value, { }).object_id.should == value.object_id
+    @vo.set_or_return(:foo, nil, { :name_attribute => true }).object_id.should == value.object_id
+  end
+
+  it "should allow DelayedEvaluator instance to be set for value regardless of restriction" do
+    value = Chef::DelayedEvaluator.new{ 'test' }
+    @vo.set_or_return(:test, value, {:kind_of => Numeric})
+  end
+
+  it "should raise an error when delayed evaluated attribute is not valid" do
+    value = Chef::DelayedEvaluator.new{ 'test' }
+    @vo.set_or_return(:test, value, {:kind_of => Numeric})
+    lambda do
+      @vo.set_or_return(:test, nil, {:kind_of => Numeric})
+    end.should raise_error(Chef::Exceptions::ValidationFailed)
+  end
+
+  it "should create DelayedEvaluator instance when #lazy is used" do
+    @vo.set_or_return(:delayed, @vo.lazy{ 'test' }, {})
+    @vo.instance_variable_get(:@delayed).should be_a(Chef::DelayedEvaluator)
+  end
+
+  it "should execute block on each call when DelayedEvaluator" do
+    value = 'fubar'
+    @vo.set_or_return(:test, @vo.lazy{ value }, {})
+    @vo.set_or_return(:test, nil, {}).should == 'fubar'
+    value = 'foobar'
+    @vo.set_or_return(:test, nil, {}).should == 'foobar'
+    value = 'fauxbar'
+    @vo.set_or_return(:test, nil, {}).should == 'fauxbar'
+  end
+
+  it "should not evaluate non DelayedEvaluator instances" do
+    value = lambda{ 'test' }
+    @vo.set_or_return(:test, value, {})
+    @vo.set_or_return(:test, nil, {}).object_id.should == value.object_id
+    @vo.set_or_return(:test, nil, {}).should be_a(Proc)
+  end
+
+end
diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb
new file mode 100644
index 0000000..e38ee7d
--- /dev/null
+++ b/spec/unit/mixin/path_sanity_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 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'
+
+class PathSanityTestHarness
+  include Chef::Mixin::PathSanity
+end
+
+describe Chef::Mixin::PathSanity do
+
+  before do
+    @sanity = PathSanityTestHarness.new
+  end
+
+  describe "when enforcing path sanity" do
+    before do
+      Chef::Config[:enforce_path_sanity] = true
+      @ruby_bindir = '/some/ruby/bin'
+      @gem_bindir = '/some/gem/bin'
+      Gem.stub!(:bindir).and_return(@gem_bindir)
+      RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(@ruby_bindir)
+      Chef::Platform.stub!(:windows?).and_return(false)
+    end
+
+    it "adds all useful PATHs that are not yet in PATH to PATH" do
+      env = {"PATH" => ""}
+      @sanity.enforce_path_sanity(env)
+      env["PATH"].should == "#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+    end
+
+    it "does not re-add paths that already exist in PATH" do
+      env = {"PATH" => "/usr/bin:/sbin:/bin"}
+      @sanity.enforce_path_sanity(env)
+      env["PATH"].should == "/usr/bin:/sbin:/bin:#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin"
+    end
+
+    it "adds the current executing Ruby's bindir and Gem bindir to the PATH" do
+      env = {"PATH" => ""}
+      @sanity.enforce_path_sanity(env)
+      env["PATH"].should == "#{@ruby_bindir}:#{@gem_bindir}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+    end
+
+    it "does not create entries for Ruby/Gem bindirs if they exist in SANE_PATH or PATH" do
+      ruby_bindir = '/usr/bin'
+      gem_bindir = '/yo/gabba/gabba'
+      Gem.stub!(:bindir).and_return(gem_bindir)
+      RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(ruby_bindir)
+      env = {"PATH" => gem_bindir}
+      @sanity.enforce_path_sanity(env)
+      env["PATH"].should == "/yo/gabba/gabba:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+    end
+
+    it "builds a valid windows path" do
+      ruby_bindir = 'C:\ruby\bin'
+      gem_bindir = 'C:\gems\bin'
+      Gem.stub!(:bindir).and_return(gem_bindir)
+      RbConfig::CONFIG.stub!(:[]).with('bindir').and_return(ruby_bindir)
+      Chef::Platform.stub!(:windows?).and_return(true)
+      env = {"PATH" => 'C:\Windows\system32;C:\mr\softie'}
+      @sanity.enforce_path_sanity(env)
+      env["PATH"].should == "C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}"
+    end
+  end
+end
diff --git a/spec/unit/mixin/securable_spec.rb b/spec/unit/mixin/securable_spec.rb
new file mode 100644
index 0000000..fe21393
--- /dev/null
+++ b/spec/unit/mixin/securable_spec.rb
@@ -0,0 +1,242 @@
+#
+# Author:: Mark Mzyk (<mmzyk at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Mixin::Securable do
+
+  before(:each) do
+    @securable = Object.new
+    @securable.send(:extend, Chef::Mixin::Securable)
+    @securable.send(:extend, Chef::Mixin::ParamsValidate)
+  end
+
+  it "should accept a group name or id for group" do
+    lambda { @securable.group "root" }.should_not raise_error(ArgumentError)
+    lambda { @securable.group 123 }.should_not raise_error(ArgumentError)
+    lambda { @securable.group "root*goo" }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a user name or id for owner" do
+    lambda { @securable.owner "root" }.should_not raise_error(ArgumentError)
+    lambda { @securable.owner 123 }.should_not raise_error(ArgumentError)
+    lambda { @securable.owner "root*goo" }.should raise_error(ArgumentError)
+  end
+
+  it "allows the owner to be specified as #user" do
+    @securable.should respond_to(:user)
+  end
+
+  describe "unix-specific behavior" do
+    before(:each) do
+      platform_mock :unix do
+        load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "config.rb")
+        load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb")
+        @securable = Object.new
+        @securable.send(:extend, Chef::Mixin::Securable)
+        @securable.send(:extend, Chef::Mixin::ParamsValidate)
+      end
+    end
+
+    it "should accept a group name or id for group with spaces and backslashes" do
+      lambda { @securable.group 'test\ group' }.should_not raise_error(ArgumentError)
+    end
+
+    it "should accept a unix file mode in string form as an octal number" do
+      lambda { @securable.mode "0" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "0000" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "0111" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "0444" }.should_not raise_error(ArgumentError)
+
+      lambda { @securable.mode "111" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "444" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "7777" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "07777" }.should_not raise_error(ArgumentError)
+
+      lambda { @securable.mode "-01" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "010000" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "-1" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "10000" }.should raise_error(ArgumentError)
+
+      lambda { @securable.mode "07778" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "7778" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "4095" }.should raise_error(ArgumentError)
+
+      lambda { @securable.mode "0foo1234" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "foo1234" }.should raise_error(ArgumentError)
+    end
+
+    it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do
+      lambda { @securable.mode 0 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 0000 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 444 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 0444 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 07777 }.should_not raise_error(ArgumentError)
+
+      lambda { @securable.mode 292 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 4095 }.should_not raise_error(ArgumentError)
+
+      lambda { @securable.mode 0111 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 73 }.should_not raise_error(ArgumentError)
+
+      lambda { @securable.mode -01 }.should raise_error(ArgumentError)
+      lambda { @securable.mode 010000 }.should raise_error(ArgumentError)
+      lambda { @securable.mode -1 }.should raise_error(ArgumentError)
+      lambda { @securable.mode 4096 }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "windows-specific behavior" do
+    before(:each) do
+      platform_mock :windows do
+        load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "config.rb")
+        load File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "chef", "mixin", "securable.rb")
+        SECURABLE_CLASS = Class.new do
+          include Chef::Mixin::Securable
+          include Chef::Mixin::ParamsValidate
+        end
+        @securable = SECURABLE_CLASS.new
+      end
+    end
+
+    it "should not accept a group name or id for group with spaces and multiple backslashes" do
+      lambda { @securable.group 'test\ \group' }.should raise_error(ArgumentError)
+    end
+
+    it "should accept a unix file mode in string form as an octal number" do
+      lambda { @securable.mode "0" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "0000" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "0111" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "0444" }.should_not raise_error(ArgumentError)
+
+      lambda { @securable.mode "111" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "444" }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode "7777" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "07777" }.should raise_error(ArgumentError)
+
+      lambda { @securable.mode "-01" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "010000" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "-1" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "10000" }.should raise_error(ArgumentError)
+
+      lambda { @securable.mode "07778" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "7778" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "4095" }.should raise_error(ArgumentError)
+
+      lambda { @securable.mode "0foo1234" }.should raise_error(ArgumentError)
+      lambda { @securable.mode "foo1234" }.should raise_error(ArgumentError)
+    end
+
+    it "should accept a unix file mode in numeric form as a ruby-interpreted integer" do
+      lambda { @securable.mode 0 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 0000 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 444 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 0444 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 07777 }.should raise_error(ArgumentError)
+
+      lambda { @securable.mode 292 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 4095 }.should raise_error(ArgumentError)
+
+      lambda { @securable.mode 0111 }.should_not raise_error(ArgumentError)
+      lambda { @securable.mode 73 }.should_not raise_error(ArgumentError)
+
+      lambda { @securable.mode -01 }.should raise_error(ArgumentError)
+      lambda { @securable.mode 010000 }.should raise_error(ArgumentError)
+      lambda { @securable.mode -1 }.should raise_error(ArgumentError)
+      lambda { @securable.mode 4096 }.should raise_error(ArgumentError)
+    end
+
+    it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write rights" do
+      lambda { @securable.rights :full_control, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :modify, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read_execute, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :write, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :to_party, "The Dude" }.should raise_error(ArgumentError)
+    end
+
+    it "should allow you to specify :full_control, :modify, :read_execute, :read, and :write deny_rights" do
+      lambda { @securable.deny_rights :full_control, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.deny_rights :modify, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.deny_rights :read_execute, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.deny_rights :read, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.deny_rights :write, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.deny_rights :to_party, "The Dude" }.should raise_error(ArgumentError)
+    end
+
+    it "should accept a principal as a string or an array" do
+      lambda { @securable.rights :read, "The Dude" }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, ["The Dude","Donny"] }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, 3 }.should raise_error(ArgumentError)
+    end
+
+    it "should allow you to specify whether the permissions applies_to_children with true/false/:containers_only/:objects_only" do
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => false }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => :containers_only }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => :objects_only }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => 'poop' }.should raise_error(ArgumentError)
+    end
+
+    it "should allow you to specify whether the permissions applies_to_self with true/false" do
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_self => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_self => 'poop' }.should raise_error(ArgumentError)
+    end
+
+    it "should allow you to specify whether the permissions applies one_level_deep with true/false" do
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => 'poop' }.should raise_error(ArgumentError)
+    end
+
+    it "should allow multiple rights and deny_rights declarations" do
+      @securable.rights :read, "The Dude"
+      @securable.deny_rights :full_control, "The Dude"
+      @securable.rights :full_control, "The Dude"
+      @securable.rights :write, "The Dude"
+      @securable.deny_rights :read, "The Dude"
+      @securable.rights.size.should == 3
+      @securable.deny_rights.size.should == 2
+    end
+
+    it "should allow you to specify whether the permission applies_to_self only if you specified applies_to_children" do
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :applies_to_self => false }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :applies_to_self => false }.should raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_self => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_self => false }.should_not raise_error(ArgumentError)
+    end
+
+    it "should allow you to specify whether the permission applies one_level_deep only if you specified applies_to_children" do
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => true, :one_level_deep => false }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => true }.should raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :applies_to_children => false, :one_level_deep => false }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :one_level_deep => true }.should_not raise_error(ArgumentError)
+      lambda { @securable.rights :read, "The Dude", :one_level_deep => false }.should_not raise_error(ArgumentError)
+    end
+
+    it "should allow you to specify whether the permissions inherit with true/false" do
+      lambda { @securable.inherits true }.should_not raise_error(ArgumentError)
+      lambda { @securable.inherits false }.should_not raise_error(ArgumentError)
+      lambda { @securable.inherits "monkey" }.should raise_error(ArgumentError)
+    end
+  end
+end
diff --git a/spec/unit/mixin/shell_out_spec.rb b/spec/unit/mixin/shell_out_spec.rb
new file mode 100644
index 0000000..c7ca56f
--- /dev/null
+++ b/spec/unit/mixin/shell_out_spec.rb
@@ -0,0 +1,109 @@
+#
+# Author:: Ho-Sheng Hsiao (hosh at opscode.com)
+# Code derived from spec/unit/mixin/command_spec.rb
+#
+# Original header:
+# Author:: Hongli Lai (hongli at phusion.nl)
+# Copyright:: Copyright (c) 2009 Phusion
+# 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::ShellOut do
+  include Chef::Mixin::ShellOut
+
+  describe '#run_command_compatible_options' do
+    subject { run_command_compatible_options(command_args) }
+    let(:command_args) { [ cmd, options ] }
+    let(:cmd) { "echo '#{rand(1000)}'" }
+
+    let(:output) { StringIO.new }
+    let!(:capture_log_output) { Chef::Log.logger = Logger.new(output)  }
+    let(:assume_deprecation_log_level) { Chef::Log.stub!(:level).and_return(:warn) }
+
+    context 'without options' do
+      let(:command_args) { [ cmd ] }
+
+      it 'should not edit command args' do
+        should eql(command_args)
+      end
+    end
+
+    context 'without deprecated options' do
+      let(:options) { { :environment => environment } }
+      let(:environment) { { 'LC_ALL' => 'C' } }
+
+      it 'should not edit command args' do
+        should eql(command_args)
+      end
+    end
+
+    def self.should_emit_deprecation_warning_about(old_option, new_option)
+      it 'should emit a deprecation warning' do
+        assume_deprecation_log_level and capture_log_output
+        subject
+        output.string.should match /DEPRECATION:/
+        output.string.should match Regexp.escape(old_option.to_s)
+        output.string.should match Regexp.escape(new_option.to_s)
+      end
+    end
+
+    context 'with :command_log_level option' do
+      let(:options) { { :command_log_level => command_log_level } }
+      let(:command_log_level) { :warn }
+
+      it 'should convert :command_log_level to :log_level' do
+        should eql [ cmd, { :log_level => command_log_level } ]
+      end
+
+      should_emit_deprecation_warning_about :command_log_level, :log_level
+    end
+
+    context 'with :command_log_prepend option' do
+      let(:options) { { :command_log_prepend => command_log_prepend } }
+      let(:command_log_prepend) { 'PROVIDER:' }
+
+      it 'should convert :command_log_prepend to :log_tag' do
+        should eql [ cmd, { :log_tag => command_log_prepend } ]
+      end
+
+      should_emit_deprecation_warning_about :command_log_prepend, :log_tag
+    end
+
+    context "with 'command_log_level' option" do
+      let(:options) { { 'command_log_level' => command_log_level } }
+      let(:command_log_level) { :warn }
+
+      it "should convert 'command_log_level' to :log_level" do
+        should eql [ cmd, { :log_level => command_log_level } ]
+      end
+
+      should_emit_deprecation_warning_about :command_log_level, :log_level
+    end
+
+    context "with 'command_log_prepend' option" do
+      let(:options) { { 'command_log_prepend' => command_log_prepend } }
+      let(:command_log_prepend) { 'PROVIDER:' }
+
+      it "should convert 'command_log_prepend' to :log_tag" do
+        should eql [ cmd, { :log_tag => command_log_prepend } ]
+      end
+
+      should_emit_deprecation_warning_about :command_log_prepend, :log_tag
+    end
+
+  end
+end
diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb
new file mode 100644
index 0000000..ca2c3c7
--- /dev/null
+++ b/spec/unit/mixin/template_spec.rb
@@ -0,0 +1,269 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 'cgi'
+describe Chef::Mixin::Template, "render_template" do
+
+  let(:sep) { Chef::Platform.windows? ? "\r\n" : "\n" }
+
+  before :each do
+    @context = Chef::Mixin::Template::TemplateContext.new({})
+  end
+
+  it "should render the template evaluated in the given context" do
+    @context[:foo] = "bar"
+    output = @context.render_template_from_string("<%= @foo %>")
+    output.should == "bar"
+  end
+
+  template_contents = [ "Fancy\r\nTemplate\r\n\r\n",
+                        "Fancy\nTemplate\n\n",
+                        "Fancy\r\nTemplate\n\r\n"]
+
+  describe "when running on windows" do
+    before do
+      Chef::Platform.stub!(:windows?).and_return(true)
+    end
+
+    it "should render the templates with windows line endings" do
+      template_contents.each do |template_content|
+        output = @context.render_template_from_string(template_content)
+        output.each_line do |line|
+          line.should end_with("\r\n")
+        end
+      end
+    end
+  end
+
+  describe "when running on unix" do
+    before do
+      Chef::Platform.stub!(:windows?).and_return(false)
+    end
+
+    it "should render the templates with unix line endings" do
+      template_contents.each do |template_content|
+        output = @context.render_template_from_string(template_content)
+        output.each_line do |line|
+          line.should end_with("\n")
+        end
+      end
+    end
+  end
+
+  it "should provide a node method to access @node" do
+    @context[:node] = "tehShizzle"
+    output = @context.render_template_from_string("<%= @node %>")
+    output.should == "tehShizzle"
+  end
+
+  describe "with a template resource" do
+    before :each do
+      @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+      Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, @cookbook_repo) }
+
+      @node = Chef::Node.new
+      cl = Chef::CookbookLoader.new(@cookbook_repo)
+      cl.load_cookbooks
+      @cookbook_collection = Chef::CookbookCollection.new(cl)
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+      @rendered_file_location = Dir.tmpdir + '/openldap_stuff.conf'
+
+      @resource = Chef::Resource::Template.new(@rendered_file_location)
+      @resource.cookbook_name = 'openldap'
+      @current_resource = @resource.dup
+
+      @content_provider = Chef::Provider::Template::Content.new(@resource, @current_resource, @run_context)
+
+      @template_context = Chef::Mixin::Template::TemplateContext.new({})
+      @template_context[:node] = @node
+      @template_context[:template_finder] = Chef::Provider::TemplateFinder.new(@run_context, @resource.cookbook_name, @node)
+    end
+
+    it "should provide a render method" do
+      output = @template_context.render_template_from_string("before {<%= render('test.erb').strip -%>} after")
+      output.should == "before {We could be diving for pearls!} after"
+    end
+
+    it "should render local files" do
+      begin
+        tf = Tempfile.new("partial")
+        tf.write "test"
+        tf.rewind
+
+        output = @template_context.render_template_from_string("before {<%= render '#{tf.path}', :local => true %>} after")
+        output.should == "before {test} after"
+      ensure
+        tf.close
+      end
+    end
+
+    it "should render partials from a different cookbook" do
+      @template_context[:template_finder] = Chef::Provider::TemplateFinder.new(@run_context, 'apache2', @node)
+
+      output = @template_context.render_template_from_string("before {<%= render('test.erb', :cookbook => 'openldap').strip %>} after")
+      output.should == "before {We could be diving for pearls!} after"
+    end
+
+    it "should render using the source argument if provided" do
+      begin
+        tf = Tempfile.new("partial")
+        tf.write "test"
+        tf.rewind
+
+        output = @template_context.render_template_from_string("before {<%= render 'something', :local => true, :source => '#{tf.path}' %>} after")
+        output.should == "before {test} after"
+      ensure
+        tf.close
+      end
+    end
+
+    it "should pass the node to partials" do
+      @node.normal[:slappiness] = "happiness"
+
+      output = @template_context.render_template_from_string("before {<%= render 'openldap_stuff.conf.erb' %>} after")
+      output.should == "before {slappiness is happiness} after"
+    end
+
+    it "should pass the original variables to partials" do
+      @template_context[:secret] = 'candy'
+
+      output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb' %>} after")
+      output == "before {super secret is candy} after"
+    end
+
+    it "should pass variables to partials" do
+      output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'whatever' } %>} after")
+      output.should == "before {super secret is whatever} after"
+    end
+
+    it "should pass variables to partials even if they are named the same" do
+      @template_context[:secret] = 'one'
+
+      output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {:secret => 'two' } %>} after <%= @secret %>")
+      output.should == "before {super secret is two} after one"
+    end
+
+    it "should pass nil for missing variables in partials" do
+      output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb', :variables => {} %>} after")
+      output.should == "before {super secret is } after"
+
+      output = @template_context.render_template_from_string("before {<%= render 'openldap_variable_stuff.conf.erb' %>} after")
+    output.should == "before {super secret is } after"
+    end
+
+    it "should render nested partials" do
+      path = File.expand_path(File.join(CHEF_SPEC_DATA, "partial_one.erb"))
+
+      output = @template_context.render_template_from_string("before {<%= render('#{path}', :local => true).strip %>} after")
+      output.should == "before {partial one We could be diving for pearls! calling home} after"
+    end
+
+    describe "when customizing the template context" do
+
+      it "extends the context to include modules" do
+        mod = Module.new do
+          def hello
+            "ohai"
+          end
+        end
+        @template_context._extend_modules([mod])
+        output = @template_context.render_template_from_string("<%=hello%>")
+        output.should == "ohai"
+      end
+
+      it "emits a warning when overriding 'core' methods" do
+        mod = Module.new do
+          def render
+          end
+          def node
+          end
+          def render_template
+          end
+          def render_template_from_string
+          end
+        end
+        ['node', 'render', 'render_template', 'render_template_from_string'].each do |method_name|
+          Chef::Log.should_receive(:warn).with(/^Core template method `#{method_name}' overridden by extension module/)
+        end
+        @template_context._extend_modules([mod])
+      end
+    end
+
+  end
+
+  describe "when an exception is raised in the template" do
+    def do_raise
+      @context.render_template_from_string("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno")
+    end
+
+    it "should catch and re-raise the exception as a TemplateError" do
+      lambda { do_raise }.should raise_error(Chef::Mixin::Template::TemplateError)
+    end
+
+    it "should raise an error if an attempt is made to access node but it is nil" do
+      lambda {@context.render_template_from_string("<%= node %>") {|r| r}}.should raise_error(Chef::Mixin::Template::TemplateError)
+    end
+
+    describe "the raised TemplateError" do
+      before :each do
+        begin
+          do_raise
+        rescue Chef::Mixin::Template::TemplateError => e
+          @exception = e
+        end
+      end
+
+      it "should have the original exception" do
+        @exception.original_exception.should be
+        @exception.original_exception.message.should =~ /undefined local variable or method `this_is_not_defined'/
+      end
+
+      it "should determine the line number of the exception" do
+        @exception.line_number.should == 4
+      end
+
+      it "should provide a source listing of the template around the exception" do
+        @exception.source_listing.should == "  2: bar\n  3: baz\n  4: <%= this_is_not_defined %>\n  5: quin\n  6: qunx"
+      end
+
+      it "should provide the evaluation context of the template" do
+        @exception.context.should == @context
+      end
+
+      it "should defer the message to the original exception" do
+        @exception.message.should =~ /undefined local variable or method `this_is_not_defined'/
+      end
+
+      it "should provide a nice source location" do
+        @exception.source_location.should == "on line #4"
+      end
+
+      it "should create a pretty output for the terminal" do
+        @exception.to_s.should =~ /Chef::Mixin::Template::TemplateError/
+        @exception.to_s.should =~ /undefined local variable or method `this_is_not_defined'/
+        @exception.to_s.should include("  2: bar\n  3: baz\n  4: <%= this_is_not_defined %>\n  5: quin\n  6: qunx")
+        @exception.to_s.should include(@exception.original_exception.backtrace.first)
+      end
+    end
+  end
+end
+
diff --git a/spec/unit/mixin/windows_architecture_helper_spec.rb b/spec/unit/mixin/windows_architecture_helper_spec.rb
new file mode 100644
index 0000000..f59602d
--- /dev/null
+++ b/spec/unit/mixin/windows_architecture_helper_spec.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+require 'spec_helper'
+require 'chef/mixin/windows_architecture_helper'
+
+
+
+describe Chef::Mixin::WindowsArchitectureHelper do
+  include Chef::Mixin::WindowsArchitectureHelper
+
+  before do
+    @valid_architectures = [ :i386, :x86_64 ]
+    @invalid_architectures = [ "i386", "x86_64", :x64, :x86, :arm ]
+
+    @node_i386 = Chef::Node.new
+    @node_x86_64 = Chef::Node.new
+  end
+
+  it "returns true when valid architectures are passed to valid_windows_architecture?" do
+    @valid_architectures.each do | architecture |
+      valid_windows_architecture?(architecture).should == true
+    end
+  end
+
+  it "returns false when invalid architectures are passed to valid_windows_architecture?" do
+    @invalid_architectures.each do | architecture |
+      valid_windows_architecture?(architecture).should == false
+    end
+  end
+
+  it "does not raise an exception when a valid architecture is passed to assert_valid_windows_architecture!" do
+    @valid_architectures.each do | architecture |
+      assert_valid_windows_architecture!(architecture)
+    end
+  end
+
+  it "raises an error if an invalid architecture is passed to assert_valid_windows_architecture!" do
+    @invalid_architectures.each do | architecture |
+      begin
+        assert_valid_windows_architecture!(architecture).should raise_error Chef::Exceptions::Win32ArchitectureIncorrect
+      rescue Chef::Exceptions::Win32ArchitectureIncorrect
+      end
+    end
+  end
+
+  it "returns true for each supported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture" do
+    enumerate_architecture_node_combinations(true)
+  end
+
+  it "returns false for each unsupported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture?" do
+    enumerate_architecture_node_combinations(true)
+  end
+
+  def enumerate_architecture_node_combinations(only_valid_combinations)
+    @valid_architectures.each do | node_architecture |
+      new_node = Chef::Node.new
+      new_node.default["kernel"] = Hash.new
+      new_node.default["kernel"][:machine] = node_architecture.to_s
+
+      @valid_architectures.each do | supported_architecture |
+        node_supports_windows_architecture?(new_node, supported_architecture).should == true if only_valid_combinations && (supported_architecture != :x86_64 && node_architecture != :i386 )
+        node_supports_windows_architecture?(new_node, supported_architecture).should == false if ! only_valid_combinations && (supported_architecture == :x86_64 && node_architecture == :i386 )
+      end
+    end
+  end
+end
diff --git a/spec/unit/mixin/xml_escape_spec.rb b/spec/unit/mixin/xml_escape_spec.rb
new file mode 100644
index 0000000..83debb5
--- /dev/null
+++ b/spec/unit/mixin/xml_escape_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+class XMLEscapingTestHarness
+  include Chef::Mixin::XMLEscape
+end
+
+describe Chef::Mixin::XMLEscape do
+  before do
+    @escaper = XMLEscapingTestHarness.new
+  end
+
+  it "escapes ampersands to '&'" do
+    @escaper.xml_escape("&").should == "&"
+  end
+
+  it "escapes angle brackets to < or >" do
+    @escaper.xml_escape("<").should == "<"
+    @escaper.xml_escape(">").should == ">"
+  end
+
+  it "does not modify ASCII strings" do
+    @escaper.xml_escape('foobarbaz!@#$%^*()').should == 'foobarbaz!@#$%^*()'
+  end
+
+  it "converts invalid bytes to asterisks" do
+    @escaper.xml_escape("\x00").should == "*"
+  end
+
+  it "converts UTF-8 correctly" do
+    @escaper.xml_escape("\xC2\xA9").should == '©'
+  end
+
+  it "converts win 1252 characters correctly" do
+    @escaper.xml_escape("\x80").should == '€'
+  end
+end
diff --git a/spec/unit/monkey_patches/string_spec.rb b/spec/unit/monkey_patches/string_spec.rb
new file mode 100644
index 0000000..8c6710b
--- /dev/null
+++ b/spec/unit/monkey_patches/string_spec.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Devin Ben-Hur <dbenhur at whitepages.com>
+# Copyright:: Copyright (c) 2008, 2011 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/monkey_patches/string'
+
+describe String do
+
+  describe "#ord" do
+    it "converts each ASCII-8BIT character to corresponding positive Fixnum" do
+      (0..0xff).each do |num|
+        ch = num.chr
+        ch.force_encoding('ASCII-8BIT') if ch.respond_to? :force_encoding
+
+        ch.ord.should be_a_kind_of(Fixnum)
+        ch.ord.should == num
+      end
+    end
+
+  end
+
+end
diff --git a/spec/unit/node/attribute_spec.rb b/spec/unit/node/attribute_spec.rb
new file mode 100644
index 0000000..70905d3
--- /dev/null
+++ b/spec/unit/node/attribute_spec.rb
@@ -0,0 +1,1168 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: AJ Christensen (<aj at opscode.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/node/attribute'
+
+describe Chef::Node::Attribute do
+  before(:each) do
+    @attribute_hash =
+      {"dmi"=>{},
+        "command"=>{"ps"=>"ps -ef"},
+        "platform_version"=>"10.5.7",
+        "platform"=>"mac_os_x",
+        "ipaddress"=>"192.168.0.117",
+        "network"=>
+    {"default_interface"=>"en1",
+      "interfaces"=>
+    {"vmnet1"=>
+      {"flags"=>
+        ["UP", "BROADCAST", "SMART", "RUNNING", "SIMPLEX", "MULTICAST"],
+          "number"=>"1",
+          "addresses"=>
+        {"00:50:56:c0:00:01"=>{"family"=>"lladdr"},
+          "192.168.110.1"=>
+        {"broadcast"=>"192.168.110.255",
+          "netmask"=>"255.255.255.0",
+          "family"=>"inet"}},
+          "mtu"=>"1500",
+          "type"=>"vmnet",
+          "arp"=>{"192.168.110.255"=>"ff:ff:ff:ff:ff:ff"},
+          "encapsulation"=>"Ethernet"},
+          "stf0"=>
+        {"flags"=>[],
+          "number"=>"0",
+          "addresses"=>{},
+          "mtu"=>"1280",
+          "type"=>"stf",
+          "encapsulation"=>"6to4"},
+          "lo0"=>
+        {"flags"=>["UP", "LOOPBACK", "RUNNING", "MULTICAST"],
+          "number"=>"0",
+          "addresses"=>
+        {"::1"=>{"scope"=>"Node", "prefixlen"=>"128", "family"=>"inet6"},
+          "127.0.0.1"=>{"netmask"=>"255.0.0.0", "family"=>"inet"},
+          "fe80::1"=>{"scope"=>"Link", "prefixlen"=>"64", "family"=>"inet6"}},
+          "mtu"=>"16384",
+          "type"=>"lo",
+          "encapsulation"=>"Loopback"},
+          "gif0"=>
+        {"flags"=>["POINTOPOINT", "MULTICAST"],
+          "number"=>"0",
+          "addresses"=>{},
+          "mtu"=>"1280",
+          "type"=>"gif",
+          "encapsulation"=>"IPIP"},
+          "vmnet8"=>
+        {"flags"=>
+          ["UP", "BROADCAST", "SMART", "RUNNING", "SIMPLEX", "MULTICAST"],
+            "number"=>"8",
+            "addresses"=>
+          {"192.168.4.1"=>
+            {"broadcast"=>"192.168.4.255",
+              "netmask"=>"255.255.255.0",
+              "family"=>"inet"},
+              "00:50:56:c0:00:08"=>{"family"=>"lladdr"}},
+              "mtu"=>"1500",
+              "type"=>"vmnet",
+              "arp"=>{"192.168.4.255"=>"ff:ff:ff:ff:ff:ff"},
+              "encapsulation"=>"Ethernet"},
+              "en0"=>
+            {"status"=>"inactive",
+              "flags"=>
+            ["UP", "BROADCAST", "SMART", "RUNNING", "SIMPLEX", "MULTICAST"],
+              "number"=>"0",
+              "addresses"=>{"00:23:32:b0:32:f2"=>{"family"=>"lladdr"}},
+              "mtu"=>"1500",
+              "media"=>
+            {"supported"=>
+              {"autoselect"=>{"options"=>[]},
+                "none"=>{"options"=>[]},
+                "1000baseT"=>
+              {"options"=>["full-duplex", "flow-control", "hw-loopback"]},
+                "10baseT/UTP"=>
+              {"options"=>
+                ["half-duplex", "full-duplex", "flow-control", "hw-loopback"]},
+                  "100baseTX"=>
+                {"options"=>
+                  ["half-duplex", "full-duplex", "flow-control", "hw-loopback"]}},
+                    "selected"=>{"autoselect"=>{"options"=>[]}}},
+                    "type"=>"en",
+                    "encapsulation"=>"Ethernet"},
+                    "en1"=>
+                  {"status"=>"active",
+                    "flags"=>
+                  ["UP", "BROADCAST", "SMART", "RUNNING", "SIMPLEX", "MULTICAST"],
+                    "number"=>"1",
+                    "addresses"=>
+                  {"fe80::223:6cff:fe7f:676c"=>
+                    {"scope"=>"Link", "prefixlen"=>"64", "family"=>"inet6"},
+                      "00:23:6c:7f:67:6c"=>{"family"=>"lladdr"},
+                      "192.168.0.117"=>
+                    {"broadcast"=>"192.168.0.255",
+                      "netmask"=>"255.255.255.0",
+                      "family"=>"inet"}},
+                      "mtu"=>"1500",
+                      "media"=>
+                    {"supported"=>{"autoselect"=>{"options"=>[]}},
+                      "selected"=>{"autoselect"=>{"options"=>[]}}},
+                      "type"=>"en",
+                      "arp"=>
+                    {"192.168.0.72"=>"0:f:ea:39:fa:d5",
+                      "192.168.0.1"=>"0:1c:fb:fc:6f:20",
+                      "192.168.0.255"=>"ff:ff:ff:ff:ff:ff",
+                      "192.168.0.3"=>"0:1f:33:ea:26:9b",
+                      "192.168.0.77"=>"0:23:12:70:f8:cf",
+                      "192.168.0.152"=>"0:26:8:7d:2:4c"},
+                      "encapsulation"=>"Ethernet"},
+                      "en2"=>
+                    {"status"=>"active",
+                      "flags"=>
+                    ["UP", "BROADCAST", "SMART", "RUNNING", "SIMPLEX", "MULTICAST"],
+                      "number"=>"2",
+                      "addresses"=>
+                    {"169.254.206.152"=>
+                      {"broadcast"=>"169.254.255.255",
+                        "netmask"=>"255.255.0.0",
+                        "family"=>"inet"},
+                        "00:1c:42:00:00:01"=>{"family"=>"lladdr"},
+                        "fe80::21c:42ff:fe00:1"=>
+                      {"scope"=>"Link", "prefixlen"=>"64", "family"=>"inet6"}},
+                        "mtu"=>"1500",
+                        "media"=>
+                      {"supported"=>{"autoselect"=>{"options"=>[]}},
+                        "selected"=>{"autoselect"=>{"options"=>[]}}},
+                        "type"=>"en",
+                        "encapsulation"=>"Ethernet"},
+                        "fw0"=>
+                      {"status"=>"inactive",
+                        "flags"=>["BROADCAST", "SIMPLEX", "MULTICAST"],
+                        "number"=>"0",
+                        "addresses"=>{"00:23:32:ff:fe:b0:32:f2"=>{"family"=>"lladdr"}},
+                        "mtu"=>"4078",
+                        "media"=>
+                      {"supported"=>{"autoselect"=>{"options"=>["full-duplex"]}},
+                        "selected"=>{"autoselect"=>{"options"=>["full-duplex"]}}},
+                        "type"=>"fw",
+                        "encapsulation"=>"1394"},
+                        "en3"=>
+                      {"status"=>"active",
+                        "flags"=>
+                      ["UP", "BROADCAST", "SMART", "RUNNING", "SIMPLEX", "MULTICAST"],
+                        "number"=>"3",
+                        "addresses"=>
+                      {"169.254.206.152"=>
+                        {"broadcast"=>"169.254.255.255",
+                          "netmask"=>"255.255.0.0",
+                          "family"=>"inet"},
+                          "00:1c:42:00:00:00"=>{"family"=>"lladdr"},
+                          "fe80::21c:42ff:fe00:0"=>
+                        {"scope"=>"Link", "prefixlen"=>"64", "family"=>"inet6"}},
+                          "mtu"=>"1500",
+                          "media"=>
+                        {"supported"=>{"autoselect"=>{"options"=>[]}},
+                          "selected"=>{"autoselect"=>{"options"=>[]}}},
+                          "type"=>"en",
+                          "encapsulation"=>"Ethernet"}}},
+                          "fqdn"=>"latte.local",
+                          "ohai_time"=>1249065590.90391,
+                          "domain"=>"local",
+                          "os"=>"darwin",
+                          "platform_build"=>"9J61",
+                          "os_version"=>"9.7.0",
+                          "hostname"=>"latte",
+                          "macaddress"=>"00:23:6c:7f:67:6c",
+                          "music" => { "jimmy_eat_world" => "nice", "apophis" => false }
+    }
+    @default_hash = {
+      "domain" => "opscode.com",
+      "hot" => { "day" => "saturday" },
+      "music" => {
+        "jimmy_eat_world" => "is fun!",
+        "mastodon" => "rocks",
+        "mars_volta" => "is loud and nutty",
+        "deeper" => { "gates_of_ishtar" => nil },
+        "this" => {"apparatus" => {"must" => "be unearthed"}}
+      }
+    }
+    @override_hash = {
+      "macaddress" => "00:00:00:00:00:00",
+      "hot" => { "day" => "sunday" },
+      "fire" => "still burn",
+      "music" => {
+        "mars_volta" => "cicatriz"
+      }
+    }
+    @automatic_hash = {"week" => "friday"}
+    @attributes = Chef::Node::Attribute.new(@attribute_hash, @default_hash, @override_hash, @automatic_hash)
+  end
+
+  describe "initialize" do
+    it "should return a Chef::Node::Attribute" do
+      @attributes.should be_a_kind_of(Chef::Node::Attribute)
+    end
+
+    it "should take an Automatioc, Normal, Default and Override hash" do
+      lambda { Chef::Node::Attribute.new({}, {}, {}, {}) }.should_not raise_error
+    end
+
+    [ :normal, :default, :override, :automatic ].each do |accessor|
+      it "should set #{accessor}" do
+        na = Chef::Node::Attribute.new({ :normal => true }, { :default => true }, { :override => true }, { :automatic => true })
+        na.send(accessor).should == { accessor.to_s => true }
+      end
+    end
+
+    it "should be enumerable" do
+      @attributes.should be_is_a(Enumerable)
+    end
+  end
+
+  describe "when printing attribute components" do
+
+    it "does not cause a type error" do
+      # See CHEF-3799; IO#puts implicitly calls #to_ary on its argument. This
+      # is expected to raise a NoMethodError or return an Array. `to_ary` is
+      # the "strict" conversion method that should only be implemented by
+      # things that are truly Array-like, so NoMethodError is the right choice.
+      # (cf. there is no Hash#to_ary).
+      lambda { @attributes.default.to_ary }.should raise_error(NoMethodError)
+    end
+
+  end
+
+  describe "when debugging attributes" do
+    before do
+      @attributes.default[:foo][:bar] = "default"
+      @attributes.env_default[:foo][:bar] = "env_default"
+      @attributes.role_default[:foo][:bar] = "role_default"
+      @attributes.force_default[:foo][:bar] = "force_default"
+      @attributes.normal[:foo][:bar] = "normal"
+      @attributes.override[:foo][:bar] = "override"
+      @attributes.role_override[:foo][:bar] = "role_override"
+      @attributes.env_override[:foo][:bar] = "env_override"
+      @attributes.force_override[:foo][:bar] = "force_override"
+      @attributes.automatic[:foo][:bar] = "automatic"
+    end
+
+    it "gives the value at each level of precedence for a path spec" do
+      expected = [["set_unless_enabled?", false],
+        ["default", "default"],
+        ["env_default", "env_default"],
+        ["role_default", "role_default"],
+        ["force_default", "force_default"],
+        ["normal", "normal"],
+        ["override", "override"],
+        ["role_override", "role_override"],
+        ["env_override", "env_override"],
+        ["force_override", "force_override"],
+        ["automatic", "automatic"]
+      ]
+      @attributes.debug_value(:foo, :bar).should == expected
+    end
+  end
+
+  describe "when fetching values based on precedence" do
+    before do
+      @attributes.default["default"] = "cookbook default"
+      @attributes.override["override"] = "cookbook override"
+    end
+
+    it "prefers 'forced default' over any other default" do
+      @attributes.default!["default"] = "force default"
+      @attributes.role_default["default"] = "role default"
+      @attributes.env_default["default"] = "environment default"
+      @attributes["default"].should == "force default"
+    end
+
+    it "prefers role_default over environment or cookbook default" do
+      @attributes.role_default["default"] = "role default"
+      @attributes.env_default["default"] = "environment default"
+      @attributes["default"].should == "role default"
+    end
+
+    it "prefers environment default over cookbook default" do
+      @attributes.env_default["default"] = "environment default"
+      @attributes["default"].should == "environment default"
+    end
+
+    it "returns the cookbook default when no other default values are present" do
+      @attributes["default"].should == "cookbook default"
+    end
+
+    it "prefers 'forced overrides' over role or cookbook overrides" do
+      @attributes.override!["override"] = "force override"
+      @attributes.env_override["override"] = "environment override"
+      @attributes.role_override["override"] = "role override"
+      @attributes["override"].should == "force override"
+    end
+
+    it "prefers environment overrides over role or cookbook overrides" do
+      @attributes.env_override["override"] = "environment override"
+      @attributes.role_override["override"] = "role override"
+      @attributes["override"].should == "environment override"
+    end
+
+    it "prefers role overrides over cookbook overrides" do
+      @attributes.role_override["override"] = "role override"
+      @attributes["override"].should == "role override"
+    end
+
+    it "returns cookbook overrides when no other overrides are present" do
+      @attributes["override"].should == "cookbook override"
+    end
+
+    it "merges arrays within the default precedence" do
+      @attributes.role_default["array"] = %w{role}
+      @attributes.env_default["array"] = %w{env}
+      @attributes["array"].should == %w{env role}
+    end
+
+    it "merges arrays within the override precedence" do
+      @attributes.role_override["array"] = %w{role}
+      @attributes.env_override["array"] = %w{env}
+      @attributes["array"].should == %w{role env}
+    end
+
+    it "does not merge arrays between default and normal" do
+      @attributes.role_default["array"] = %w{role}
+      @attributes.normal["array"] = %w{normal}
+      @attributes["array"].should == %w{normal}
+    end
+
+    it "does not merge arrays between normal and override" do
+      @attributes.normal["array"] = %w{normal}
+      @attributes.role_override["array"] = %w{role}
+      @attributes["array"].should == %w{role}
+    end
+
+    it "merges nested hashes between precedence levels" do
+      @attributes = Chef::Node::Attribute.new({}, {}, {}, {})
+      @attributes.env_default = {"a" => {"b" => {"default" => "default"}}}
+      @attributes.normal = {"a" => {"b" => {"normal" => "normal"}}}
+      @attributes.override = {"a" => {"override" => "role"}}
+      @attributes.automatic = {"a" => {"automatic" => "auto"}}
+      @attributes["a"].should == {"b"=>{"default"=>"default", "normal"=>"normal"},
+                                  "override"=>"role",
+                                  "automatic"=>"auto"}
+    end
+  end
+
+  describe "when reading combined default or override values" do
+    before do
+      @attributes.default["cd"] = "cookbook default"
+      @attributes.role_default["rd"] = "role default"
+      @attributes.env_default["ed"] = "env default"
+      @attributes.default!["fd"] = "force default"
+      @attributes.override["co"] = "cookbook override"
+      @attributes.role_override["ro"] = "role override"
+      @attributes.env_override["eo"] = "env override"
+      @attributes.override!["fo"] = "force override"
+    end
+
+    it "merges all types of overrides into a combined override" do
+      @attributes.combined_override["co"].should == "cookbook override"
+      @attributes.combined_override["ro"].should == "role override"
+      @attributes.combined_override["eo"].should == "env override"
+      @attributes.combined_override["fo"].should == "force override"
+    end
+
+    it "merges all types of defaults into a combined default" do
+      @attributes.combined_default["cd"].should == "cookbook default"
+      @attributes.combined_default["rd"].should == "role default"
+      @attributes.combined_default["ed"].should == "env default"
+      @attributes.combined_default["fd"].should == "force default"
+    end
+
+  end
+
+  describe "[]" do
+    it "should return override data if it exists" do
+      @attributes["macaddress"].should == "00:00:00:00:00:00"
+    end
+
+    it "should return attribute data if it is not overridden" do
+      @attributes["platform"].should == "mac_os_x"
+    end
+
+    it "should return data that doesn't have corresponding keys in every hash" do
+      @attributes["command"]["ps"].should == "ps -ef"
+    end
+
+    it "should return default data if it is not overriden or in attribute data" do
+      @attributes["music"]["mastodon"].should == "rocks"
+    end
+
+    it "should prefer the override data over an available default" do
+      @attributes["music"]["mars_volta"].should == "cicatriz"
+    end
+
+    it "should prefer the attribute data over an available default" do
+      @attributes["music"]["jimmy_eat_world"].should == "nice"
+    end
+
+    it "should prefer override data over default data if there is no attribute data" do
+      @attributes["hot"]["day"].should == "sunday"
+    end
+
+    it "should return the merged hash if all three have values" do
+      result = @attributes["music"]
+      result["mars_volta"].should == "cicatriz"
+      result["jimmy_eat_world"].should == "nice"
+      result["mastodon"].should == "rocks"
+    end
+  end
+
+  describe "[]=" do
+    it "should error out when the type of attribute to set has not been specified" do
+      @attributes.normal["the_ghost"] = {  }
+      lambda { @attributes["the_ghost"]["exterminate"] = false }.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
+    end
+
+    it "should let you set an attribute value when another hash has an intermediate value" do
+      @attributes.normal["the_ghost"] = { "exterminate" => "the future" }
+      lambda { @attributes.normal["the_ghost"]["exterminate"]["tomorrow"] = false }.should_not raise_error(NoMethodError)
+    end
+
+    it "should set the attribute value" do
+      @attributes.normal["longboard"] = "surfing"
+      @attributes.normal["longboard"].should == "surfing"
+      @attributes.normal["longboard"].should == "surfing"
+    end
+
+    it "should set deeply nested attribute values when a precedence level is specified" do
+      @attributes.normal["deftones"]["hunters"]["nap"] = "surfing"
+      @attributes.normal["deftones"]["hunters"]["nap"].should == "surfing"
+    end
+
+    it "should die if you try and do nested attributes that do not exist without read vivification" do
+      lambda { @attributes["foo"]["bar"] = :baz }.should raise_error
+    end
+
+    it "should let you set attributes manually without vivification" do
+      @attributes.normal["foo"] = Mash.new
+      @attributes.normal["foo"]["bar"] = :baz
+      @attributes.normal["foo"]["bar"].should == :baz
+    end
+
+    it "should optionally skip setting the value if one already exists" do
+      @attributes.set_unless_value_present = true
+      @attributes.normal["hostname"] = "bar"
+      @attributes["hostname"].should == "latte"
+    end
+
+    it "does not support ||= when setting" do
+      # This is a limitation of auto-vivification.
+      # Users who need this behavior can use set_unless and friends
+      @attributes.normal["foo"] = Mash.new
+      @attributes.normal["foo"]["bar"] ||= "stop the world"
+      @attributes.normal["foo"]["bar"].should == {}
+    end
+  end
+
+  describe "to_hash" do
+    it "should convert to a hash" do
+      @attributes.to_hash.class.should == Hash
+    end
+
+    it "should convert to a hash based on current state" do
+      hash = @attributes["hot"].to_hash
+      hash.class.should == Hash
+      hash["day"].should == "sunday"
+    end
+  end
+
+  describe "has_key?" do
+    it "should return true if an attribute exists" do
+      @attributes.has_key?("music").should == true
+    end
+
+    it "should return false if an attribute does not exist" do
+      @attributes.has_key?("ninja").should == false
+    end
+
+    it "should return false if an attribute does not exist using dot notation" do
+      @attributes.has_key?("does_not_exist_at_all").should == false
+    end
+
+    it "should return true if an attribute exists but is set to nil using dot notation" do
+      @attributes.music.deeper.has_key?("gates_of_ishtar").should == true
+    end
+
+    it "should return true if an attribute exists but is set to false" do
+      @attributes.has_key?("music")
+      @attributes["music"].has_key?("apophis").should == true
+    end
+
+    it "does not find keys above the current nesting level" do
+      @attributes["music"]["this"]["apparatus"].should_not have_key("this")
+    end
+
+    it "does not find keys below the current nesting level" do
+      @attributes["music"]["this"].should_not have_key("must")
+    end
+
+    [:include?, :key?, :member?].each do |method|
+      it "should alias the method #{method} to itself" do
+        @attributes.should respond_to(method)
+      end
+
+      it "#{method} should behave like has_key?" do
+        @attributes.send(method, "music").should == true
+      end
+    end
+  end
+
+  describe "attribute?" do
+    it "should return true if an attribute exists" do
+      @attributes.attribute?("music").should == true
+    end
+
+    it "should return false if an attribute does not exist" do
+      @attributes.attribute?("ninja").should == false
+    end
+
+  end
+
+  describe "method_missing" do
+    it "should behave like a [] lookup" do
+      @attributes.music.mastodon.should == "rocks"
+    end
+
+    it "should allow the last method to set a value if it has an = sign on the end" do
+      @attributes.normal.music.mastodon = [ "dream", "still", "shining" ]
+      @attributes.reset
+      @attributes.normal.music.mastodon.should == [ "dream", "still", "shining" ]
+    end
+  end
+
+  describe "keys" do
+    before(:each) do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  { "two" => "three" },
+          "hut" =>  { "two" => "three" },
+          "place" => { }
+        },
+        {
+          "one" =>  { "four" => "five" },
+          "snakes" => "on a plane"
+        },
+        {
+          "one" =>  { "six" => "seven" },
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should yield each top level key" do
+      collect = Array.new
+      @attributes.keys.each do |k|
+        collect << k
+      end
+      collect.include?("one").should == true
+      collect.include?("hut").should == true
+      collect.include?("snakes").should == true
+      collect.include?("snack").should == true
+      collect.include?("place").should == true
+      collect.length.should == 5
+    end
+
+    it "should yield lower if we go deeper" do
+      collect = Array.new
+      @attributes.one.keys.each do |k|
+        collect << k
+      end
+      collect.include?("two").should == true
+      collect.include?("four").should == true
+      collect.include?("six").should == true
+      collect.length.should == 3
+    end
+
+    it "should not raise an exception if one of the hashes has a nil value on a deep lookup" do
+      lambda { @attributes.place.keys { |k| } }.should_not raise_error(NoMethodError)
+    end
+  end
+
+  describe "each" do
+    before(:each) do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should yield each top level key and value, post merge rules" do
+      collect = Hash.new
+      @attributes.each do |k, v|
+        collect[k] = v
+      end
+
+      collect["one"].should == "six"
+      collect["hut"].should == "three"
+      collect["snakes"].should == "on a plane"
+      collect["snack"].should == "cookies"
+    end
+
+    it "should yield as a two-element array" do
+      @attributes.each do |a|
+        a.should be_an_instance_of(Array)
+      end
+    end
+  end
+
+  describe "each_key" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to each_key" do
+      @attributes.should respond_to(:each_key)
+    end
+
+    it "should yield each top level key, post merge rules" do
+      collect = Array.new
+      @attributes.each_key do |k|
+        collect << k
+      end
+
+      collect.should include("one")
+      collect.should include("snack")
+      collect.should include("hut")
+      collect.should include("snakes")
+    end
+  end
+
+  describe "each_pair" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to each_pair" do
+      @attributes.should respond_to(:each_pair)
+    end
+
+    it "should yield each top level key and value pair, post merge rules" do
+      collect = Hash.new
+      @attributes.each_pair do |k, v|
+        collect[k] = v
+      end
+
+      collect["one"].should == "six"
+      collect["hut"].should == "three"
+      collect["snakes"].should == "on a plane"
+      collect["snack"].should == "cookies"
+    end
+  end
+
+  describe "each_value" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to each_value" do
+      @attributes.should respond_to(:each_value)
+    end
+
+    it "should yield each value, post merge rules" do
+      collect = Array.new
+      @attributes.each_value do |v|
+        collect << v
+      end
+
+      collect.should include("cookies")
+      collect.should include("three")
+      collect.should include("on a plane")
+    end
+
+    it "should yield four elements" do
+      collect = Array.new
+      @attributes.each_value do |v|
+        collect << v
+      end
+
+      collect.length.should == 4
+    end
+  end
+
+  describe "empty?" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+      @empty = Chef::Node::Attribute.new({}, {}, {}, {})
+    end
+
+    it "should respond to empty?" do
+      @attributes.should respond_to(:empty?)
+    end
+
+    it "should return true when there are no keys" do
+      @empty.empty?.should == true
+    end
+
+    it "should return false when there are keys" do
+      @attributes.empty?.should == false
+    end
+
+  end
+
+  describe "fetch" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to fetch" do
+      @attributes.should respond_to(:fetch)
+    end
+
+    describe "when the key exists" do
+      it "should return the value of the key, post merge (same result as each)" do
+        {
+          "one" => "six",
+          "hut" => "three",
+          "snakes" => "on a plane",
+          "snack" => "cookies"
+        }.each do |k,v|
+          @attributes.fetch(k).should == v
+        end
+      end
+    end
+
+    describe "when the key does not exist" do
+      describe "and no args are passed" do
+        it "should raise an indexerror" do
+          lambda { @attributes.fetch("lololol") }.should raise_error(IndexError)
+        end
+      end
+
+      describe "and a default arg is passed" do
+        it "should return the value of the default arg" do
+          @attributes.fetch("lol", "blah").should == "blah"
+        end
+      end
+
+      describe "and a block is passed" do
+        it "should run the block and return its value" do
+          @attributes.fetch("lol") { |x| "#{x}, blah" }.should == "lol, blah"
+        end
+      end
+    end
+  end
+
+  describe "has_value?" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to has_value?" do
+      @attributes.should respond_to(:has_value?)
+    end
+
+    it "should return true if any key has the value supplied" do
+      @attributes.has_value?("cookies").should == true
+    end
+
+    it "should return false no key has the value supplied" do
+      @attributes.has_value?("lololol").should == false
+    end
+
+    it "should alias value?" do
+      @attributes.should respond_to(:value?)
+    end
+  end
+
+  describe "index" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to index" do
+      @attributes.should respond_to(:index)
+    end
+
+    describe "when the value is indexed" do
+      it "should return the index" do
+        @attributes.index("six").should == "one"
+      end
+    end
+
+    describe "when the value is not indexed" do
+      it "should return nil" do
+        @attributes.index("lolol").should == nil
+      end
+    end
+
+  end
+
+
+  describe "values" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to values" do
+      @attributes.should respond_to(:values)
+    end
+
+    it "should return an array of values" do
+      @attributes.values.length.should == 4
+    end
+
+    it "should match the values output from each" do
+      @attributes.values.should include("six")
+      @attributes.values.should include("cookies")
+      @attributes.values.should include("three")
+      @attributes.values.should include("on a plane")
+    end
+
+  end
+
+  describe "select" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+    end
+
+    it "should respond to select" do
+      @attributes.should respond_to(:select)
+    end
+
+    if RUBY_VERSION >= "1.8.7"
+      it "should not raise a LocalJumpError if no block is given" do
+        lambda { @attributes.select }.should_not raise_error(LocalJumpError)
+      end
+    else
+      it "should raise a LocalJumpError if no block is given" do
+        lambda{ @attributes.select }.should raise_error(LocalJumpError)
+      end
+    end
+
+    it "should return an empty hash/array (ruby-version-dependent) for a block containing nil" do
+      @attributes.select { nil }.should == {}.select { nil }
+    end
+
+    # sorted for spec clarity
+    it "should return a new array of k,v pairs for which the block returns true" do
+      @attributes.select { true }.sort.should == (
+        [
+          ["hut", "three"],
+          ["one", "six"],
+          ["snack", "cookies"],
+          ["snakes", "on a plane"]
+        ]
+      )
+    end
+  end
+
+  describe "size" do
+    before do
+      @attributes = Chef::Node::Attribute.new(
+        {
+          "one" =>  "two",
+          "hut" =>  "three",
+        },
+        {
+          "one" =>  "four",
+          "snakes" => "on a plane"
+        },
+        {
+          "one" => "six",
+          "snack" => "cookies"
+        },
+        {}
+      )
+
+      @empty = Chef::Node::Attribute.new({},{},{},{})
+    end
+
+    it "should respond to size" do
+      @attributes.should respond_to(:size)
+    end
+
+    it "should alias length to size" do
+      @attributes.should respond_to(:length)
+    end
+
+    it "should return 0 for an empty attribute" do
+      @empty.size.should == 0
+    end
+
+    it "should return the number of pairs" do
+      @attributes.size.should == 4
+    end
+  end
+
+  describe "kind_of?" do
+    it "should falsely inform you that it is a Hash" do
+      @attributes.should be_a_kind_of(Hash)
+    end
+
+    it "should falsely inform you that it is a Mash" do
+      @attributes.should be_a_kind_of(Mash)
+    end
+
+    it "should inform you that it is a Chef::Node::Attribute" do
+      @attributes.should be_a_kind_of(Chef::Node::Attribute)
+    end
+
+    it "should inform you that it is anything else" do
+      @attributes.should_not be_a_kind_of(Chef::Node)
+    end
+  end
+
+  describe "inspect" do
+    it "should be readable" do
+      # NOTE: previous implementation hid the values, showing @automatic={...}
+      # That is nice and compact, but hides a lot of info, which seems counter
+      # to the point of calling #inspect...
+      @attributes.inspect.should =~ /@automatic=\{.*\}/
+      @attributes.inspect.should =~ /@normal=\{.*\}/
+    end
+  end
+
+  # For expedience, this test is implementation-heavy.
+  describe "when a component attribute is mutated" do
+    [
+      :clear,
+      :shift
+    ].each do |mutator|
+      it "resets the cache when the mutator #{mutator} is called" do
+        @attributes.should_receive(:reset_cache)
+        @attributes.default.send(mutator)
+      end
+    end
+
+    it "resets the cache when the mutator delete is called" do
+      @attributes.should_receive(:reset_cache)
+      @attributes.default.delete(:music)
+    end
+
+    [
+      :merge!,
+      :update,
+      :replace
+    ].each do |mutator|
+      it "resets the cache when the mutator #{mutator} is called" do
+        # Implementation of Mash means that this could get called many times. That's okay.
+        @attributes.should_receive(:reset_cache).at_least(1).times
+        @attributes.default.send(mutator, {:foo => :bar})
+      end
+    end
+
+    [
+      :delete_if,
+      :keep_if,
+      :reject!,
+      :select!,
+    ].each do |mutator|
+      it "resets the cache when the mutator #{mutator} is called" do
+        # Implementation of Mash means that this could get called many times. That's okay.
+        @attributes.should_receive(:reset_cache).at_least(1).times
+        block = lambda {|k,v| true }
+        @attributes.default.send(mutator, &block)
+      end
+    end
+  end
+
+  describe "when not mutated" do
+
+    it "does not reset the cache when dup'd [CHEF-3680]" do
+      @attributes.default[:foo][:bar] = "set on original"
+      subtree = @attributes[:foo]
+      @attributes.default[:foo].dup[:bar] = "set on dup"
+      subtree[:bar].should == "set on original"
+    end
+
+  end
+
+  describe "when setting a component attribute to a new value" do
+    it "converts the input in to a VividMash tree (default)" do
+      @attributes.default = {}
+      @attributes.default.foo = "bar"
+      @attributes.merged_attributes[:foo].should == "bar"
+    end
+
+    it "converts the input in to a VividMash tree (normal)" do
+      @attributes.normal = {}
+      @attributes.normal.foo = "bar"
+      @attributes.merged_attributes[:foo].should == "bar"
+    end
+
+    it "converts the input in to a VividMash tree (override)" do
+      @attributes.override = {}
+      @attributes.override.foo = "bar"
+      @attributes.merged_attributes[:foo].should == "bar"
+    end
+
+    it "converts the input in to a VividMash tree (automatic)" do
+      @attributes.automatic = {}
+      @attributes.automatic.foo = "bar"
+      @attributes.merged_attributes[:foo].should == "bar"
+    end
+  end
+
+  describe "when attemping to write without specifying precedence" do
+    it "raises an error when using []=" do
+      lambda { @attributes[:new_key] = "new value" }.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
+    end
+
+    it "raises an error when using `attr=value`" do
+      lambda { @attributes.new_key = "new value" }.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/node/immutable_collections_spec.rb b/spec/unit/node/immutable_collections_spec.rb
new file mode 100644
index 0000000..0c2b878
--- /dev/null
+++ b/spec/unit/node/immutable_collections_spec.rb
@@ -0,0 +1,139 @@
+#
+# Author:: Daniel DeLeo (<dan 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/node/immutable_collections"
+
+describe Chef::Node::ImmutableMash do
+  before do
+    @data_in = {:top => {:second_level => "some value"},
+                "top_level_2" => %w[array of values],
+                :top_level_3 => [{:hash_array => 1, :hash_array_b => 2}],
+                :top_level_4 => {:level2 => {:key => "value"}}
+    }
+    @immutable_mash = Chef::Node::ImmutableMash.new(@data_in)
+  end
+
+  it "element references like regular hash" do
+    @immutable_mash[:top][:second_level].should == "some value"
+  end
+
+  it "elelment references like a regular Mash" do
+    @immutable_mash[:top_level_2].should == %w[array of values]
+  end
+
+  it "converts Hash-like inputs into ImmutableMash's" do
+    @immutable_mash[:top].should be_a(Chef::Node::ImmutableMash)
+  end
+
+  it "converts array inputs into ImmutableArray's" do
+    @immutable_mash[:top_level_2].should be_a(Chef::Node::ImmutableArray)
+  end
+
+  it "converts arrays of hashes to ImmutableArray's of ImmutableMashes" do
+    @immutable_mash[:top_level_3].first.should be_a(Chef::Node::ImmutableMash)
+  end
+
+  it "converts nested hashes to ImmutableMashes" do
+    @immutable_mash[:top_level_4].should be_a(Chef::Node::ImmutableMash)
+    @immutable_mash[:top_level_4][:level2].should be_a(Chef::Node::ImmutableMash)
+  end
+
+
+  [
+    :[]=,
+    :clear,
+    :default=,
+    :default_proc=,
+    :delete,
+    :delete_if,
+    :keep_if,
+    :merge!,
+    :update,
+    :reject!,
+    :replace,
+    :select!,
+    :shift
+  ].each do |mutator|
+    it "doesn't allow mutation via `#{mutator}'" do
+      lambda { @immutable_mash.send(mutator) }.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
+    end
+  end
+
+  it "returns a mutable version of itself when duped" do
+    mutable = @immutable_mash.dup
+    mutable[:new_key] = :value
+    mutable[:new_key].should == :value
+  end
+
+end
+
+describe Chef::Node::ImmutableArray do
+
+  before do
+    @immutable_array = Chef::Node::ImmutableArray.new(%w[foo bar baz])
+  end
+
+  ##
+  # Note: other behaviors, such as immutibilizing input data, are tested along
+  # with ImmutableMash, above
+  ###
+
+  [
+    :<<,
+    :[]=,
+    :clear,
+    :collect!,
+    :compact!,
+    :default=,
+    :default_proc=,
+    :delete,
+    :delete_at,
+    :delete_if,
+    :fill,
+    :flatten!,
+    :insert,
+    :keep_if,
+    :map!,
+    :merge!,
+    :pop,
+    :push,
+    :update,
+    :reject!,
+    :reverse!,
+    :replace,
+    :select!,
+    :shift,
+    :slice!,
+    :sort!,
+    :sort_by!,
+    :uniq!,
+    :unshift
+  ].each do |mutator|
+    it "does not allow mutation via `#{mutator}" do
+      lambda { @immutable_array.send(mutator)}.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
+    end
+  end
+
+  it "returns a mutable version of itself when duped" do
+    mutable = @immutable_array.dup
+    mutable[0] = :value
+    mutable[0].should == :value
+  end
+end
+
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
new file mode 100644
index 0000000..bb567db
--- /dev/null
+++ b/spec/unit/node_spec.rb
@@ -0,0 +1,828 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 'ostruct'
+
+describe Chef::Node do
+
+  let(:node) { Chef::Node.new() }
+  let(:platform_introspector) { node }
+
+  it_behaves_like "a platform introspector"
+
+  it "creates a node and assigns it a name" do
+    node = Chef::Node.build('solo-node')
+    node.name.should == 'solo-node'
+  end
+
+  it "should validate the name of the node" do
+    lambda{Chef::Node.build('solo node')}.should raise_error(Chef::Exceptions::ValidationFailed)
+  end
+
+  it "should be sortable" do
+    n1 = Chef::Node.build('alpha')
+    n2 = Chef::Node.build('beta')
+    n3 = Chef::Node.build('omega')
+    [n3, n1, n2].sort.should == [n1, n2, n3]
+  end
+
+  describe "when the node does not exist on the server" do
+    before do
+      response = OpenStruct.new(:code => '404')
+      exception = Net::HTTPServerException.new("404 not found", response)
+      Chef::Node.stub!(:load).and_raise(exception)
+      node.name("created-node")
+    end
+
+    it "creates a new node for find_or_create" do
+      Chef::Node.stub!(:new).and_return(node)
+      node.should_receive(:create).and_return(node)
+      node = Chef::Node.find_or_create("created-node")
+      node.name.should == 'created-node'
+      node.should equal(node)
+    end
+  end
+
+  describe "when the node exists on the server" do
+    before do
+      node.name('existing-node')
+      Chef::Node.stub!(:load).and_return(node)
+    end
+
+    it "loads the node via the REST API for find_or_create" do
+      Chef::Node.find_or_create('existing-node').should equal(node)
+    end
+  end
+
+  describe "run_state" do
+    it "is an empty hash" do
+      node.run_state.should respond_to(:keys)
+      node.run_state.should be_empty
+    end
+  end
+
+  describe "initialize" do
+    it "should default to the '_default' chef_environment" do
+      n = Chef::Node.new
+      n.chef_environment.should == '_default'
+    end
+  end
+
+  describe "name" do
+    it "should allow you to set a name with name(something)" do
+      lambda { node.name("latte") }.should_not raise_error
+    end
+
+    it "should return the name with name()" do
+      node.name("latte")
+      node.name.should eql("latte")
+    end
+
+    it "should always have a string for name" do
+      lambda { node.name(Hash.new) }.should raise_error(ArgumentError)
+    end
+
+    it "cannot be blank" do
+      lambda { node.name("")}.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "should not accept name doesn't match /^[\-[:alnum:]_:.]+$/" do
+      lambda { node.name("space in it")}.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+  end
+
+  describe "chef_environment" do
+    it "should set an environment with chef_environment(something)" do
+      lambda { node.chef_environment("latte") }.should_not raise_error
+    end
+
+    it "should return the chef_environment with chef_environment()" do
+      node.chef_environment("latte")
+      node.chef_environment.should == "latte"
+    end
+
+    it "should disallow non-strings" do
+      lambda { node.chef_environment(Hash.new) }.should raise_error(ArgumentError)
+      lambda { node.chef_environment(42) }.should raise_error(ArgumentError)
+    end
+
+    it "cannot be blank" do
+      lambda { node.chef_environment("")}.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+  end
+
+  describe "attributes" do
+    it "should have attributes" do
+      node.attribute.should be_a_kind_of(Hash)
+    end
+
+    it "should allow attributes to be accessed by name or symbol directly on node[]" do
+      node.default["locust"] = "something"
+      node[:locust].should eql("something")
+      node["locust"].should eql("something")
+    end
+
+    it "should return nil if it cannot find an attribute with node[]" do
+      node["secret"].should eql(nil)
+    end
+
+    it "does not allow you to set an attribute via node[]=" do
+      lambda  { node["secret"] = "shush" }.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
+    end
+
+    it "should allow you to query whether an attribute exists with attribute?" do
+      node.default["locust"] = "something"
+      node.attribute?("locust").should eql(true)
+      node.attribute?("no dice").should eql(false)
+    end
+
+    it "should let you go deep with attribute?" do
+      node.set["battles"]["people"]["wonkey"] = true
+      node["battles"]["people"].attribute?("wonkey").should == true
+      node["battles"]["people"].attribute?("snozzberry").should == false
+    end
+
+    it "does not allow you to set an attribute via method_missing" do
+      lambda { node.sunshine = "is bright"}.should raise_error(Chef::Exceptions::ImmutableAttributeModification)
+    end
+
+    it "should allow you get get an attribute via method_missing" do
+      node.default.sunshine = "is bright"
+      node.sunshine.should eql("is bright")
+    end
+
+    describe "normal attributes" do
+      it "should allow you to set an attribute with set, without pre-declaring a hash" do
+        node.set[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set an attribute with set_unless" do
+        node.set_unless[:snoopy][:is_a_puppy] = false
+        node[:snoopy][:is_a_puppy].should == false
+      end
+
+      it "should not allow you to set an attribute with set_unless if it already exists" do
+        node.set[:snoopy][:is_a_puppy] = true
+        node.set_unless[:snoopy][:is_a_puppy] = false
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set a value after a set_unless" do
+        # this tests for set_unless_present state bleeding between statements CHEF-3806
+        node.set_unless[:snoopy][:is_a_puppy] = false
+        node.set[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should let you set a value after a 'dangling' set_unless" do
+        # this tests for set_unless_present state bleeding between statements CHEF-3806
+        node.set[:snoopy][:is_a_puppy] = "what"
+        node.set_unless[:snoopy][:is_a_puppy]
+        node.set[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "auto-vivifies attributes created via method syntax" do
+        node.set.fuu.bahrr.baz = "qux"
+        node.fuu.bahrr.baz.should == "qux"
+      end
+
+      it "should let you use tag as a convience method for the tags attribute" do
+        node.normal['tags'] = ['one', 'two']
+        node.tag('three', 'four')
+        node['tags'].should == ['one', 'two', 'three', 'four']
+      end
+    end
+
+    describe "default attributes" do
+      it "should be set with default, without pre-declaring a hash" do
+        node.default[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set with default_unless without pre-declaring a hash" do
+        node.default_unless[:snoopy][:is_a_puppy] = false
+        node[:snoopy][:is_a_puppy].should == false
+      end
+
+      it "should not allow you to set an attribute with default_unless if it already exists" do
+        node.default[:snoopy][:is_a_puppy] = true
+        node.default_unless[:snoopy][:is_a_puppy] = false
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set a value after a default_unless" do
+        # this tests for set_unless_present state bleeding between statements CHEF-3806
+        node.default_unless[:snoopy][:is_a_puppy] = false
+        node.default[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set a value after a 'dangling' default_unless" do
+        # this tests for set_unless_present state bleeding between statements CHEF-3806
+        node.default[:snoopy][:is_a_puppy] = "what"
+        node.default_unless[:snoopy][:is_a_puppy]
+        node.default[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "auto-vivifies attributes created via method syntax" do
+        node.default.fuu.bahrr.baz = "qux"
+        node.fuu.bahrr.baz.should == "qux"
+      end
+
+      it "accesses force defaults via default!" do
+        node.default![:foo] = "wet bar"
+        node.default[:foo] = "bar"
+        node[:foo].should == "wet bar"
+      end
+
+    end
+
+    describe "override attributes" do
+      it "should be set with override, without pre-declaring a hash" do
+        node.override[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set with override_unless without pre-declaring a hash" do
+        node.override_unless[:snoopy][:is_a_puppy] = false
+        node[:snoopy][:is_a_puppy].should == false
+      end
+
+      it "should not allow you to set an attribute with override_unless if it already exists" do
+        node.override[:snoopy][:is_a_puppy] = true
+        node.override_unless[:snoopy][:is_a_puppy] = false
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set a value after an override_unless" do
+        # this tests for set_unless_present state bleeding between statements CHEF-3806
+        node.override_unless[:snoopy][:is_a_puppy] = false
+        node.override[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "should allow you to set a value after a 'dangling' override_unless" do
+        # this tests for set_unless_present state bleeding between statements CHEF-3806
+        node.override_unless[:snoopy][:is_a_puppy] = "what"
+        node.override_unless[:snoopy][:is_a_puppy]
+        node.override[:snoopy][:is_a_puppy] = true
+        node[:snoopy][:is_a_puppy].should == true
+      end
+
+      it "auto-vivifies attributes created via method syntax" do
+        node.override.fuu.bahrr.baz = "qux"
+        node.fuu.bahrr.baz.should == "qux"
+      end
+
+      it "sets force_overrides via override!" do
+        node.override![:foo] = "wet bar"
+        node.override[:foo] = "bar"
+        node[:foo].should == "wet bar"
+      end
+
+    end
+
+    it "should raise an ArgumentError if you ask for an attribute that doesn't exist via method_missing" do
+      lambda { node.sunshine }.should raise_error(NoMethodError)
+    end
+
+    it "should allow you to iterate over attributes with each_attribute" do
+      node.default.sunshine = "is bright"
+      node.default.canada = "is a nice place"
+      seen_attributes = Hash.new
+      node.each_attribute do |a,v|
+        seen_attributes[a] = v
+      end
+      seen_attributes.should have_key("sunshine")
+      seen_attributes.should have_key("canada")
+      seen_attributes["sunshine"].should == "is bright"
+      seen_attributes["canada"].should == "is a nice place"
+    end
+  end
+
+  describe "consuming json" do
+
+    before do
+      @ohai_data = {:platform => 'foo', :platform_version => 'bar'}
+    end
+
+    it "consumes the run list portion of a collection of attributes and returns the remainder" do
+      attrs = {"run_list" => [ "role[base]", "recipe[chef::server]" ], "foo" => "bar"}
+      node.consume_run_list(attrs).should == {"foo" => "bar"}
+      node.run_list.should == [ "role[base]", "recipe[chef::server]" ]
+    end
+
+    it "should overwrites the run list with the run list it consumes" do
+      node.consume_run_list "recipes" => [ "one", "two" ]
+      node.consume_run_list "recipes" => [ "three" ]
+      node.run_list.should == [ "three" ]
+    end
+
+    it "should not add duplicate recipes from the json attributes" do
+      node.run_list << "one"
+      node.consume_run_list "recipes" => [ "one", "two", "three" ]
+      node.run_list.should  == [ "one", "two", "three" ]
+    end
+
+    it "doesn't change the run list if no run_list is specified in the json" do
+      node.run_list << "role[database]"
+      node.consume_run_list "foo" => "bar"
+      node.run_list.should == ["role[database]"]
+    end
+
+    it "raises an exception if you provide both recipe and run_list attributes, since this is ambiguous" do
+      lambda { node.consume_run_list "recipes" => "stuff", "run_list" => "other_stuff" }.should raise_error(Chef::Exceptions::AmbiguousRunlistSpecification)
+    end
+
+    it "should add json attributes to the node" do
+      node.consume_external_attrs(@ohai_data, {"one" => "two", "three" => "four"})
+      node.one.should eql("two")
+      node.three.should eql("four")
+    end
+
+    it "should set the tags attribute to an empty array if it is not already defined" do
+      node.consume_external_attrs(@ohai_data, {})
+      node.tags.should eql([])
+    end
+
+    it "should not set the tags attribute to an empty array if it is already defined" do
+      node.normal[:tags] = [ "radiohead" ]
+      node.consume_external_attrs(@ohai_data, {})
+      node.tags.should eql([ "radiohead" ])
+    end
+
+    it "deep merges attributes instead of overwriting them" do
+      node.consume_external_attrs(@ohai_data, "one" => {"two" => {"three" => "four"}})
+      node.one.to_hash.should == {"two" => {"three" => "four"}}
+      node.consume_external_attrs(@ohai_data, "one" => {"abc" => "123"})
+      node.consume_external_attrs(@ohai_data, "one" => {"two" => {"foo" => "bar"}})
+      node.one.to_hash.should == {"two" => {"three" => "four", "foo" => "bar"}, "abc" => "123"}
+    end
+
+    it "gives attributes from JSON priority when deep merging" do
+      node.consume_external_attrs(@ohai_data, "one" => {"two" => {"three" => "four"}})
+      node.one.to_hash.should == {"two" => {"three" => "four"}}
+      node.consume_external_attrs(@ohai_data, "one" => {"two" => {"three" => "forty-two"}})
+      node.one.to_hash.should == {"two" => {"three" => "forty-two"}}
+    end
+
+  end
+
+  describe "preparing for a chef client run" do
+    before do
+      @ohai_data = {:platform => 'foobuntu', :platform_version => '23.42'}
+    end
+
+    it "sets its platform according to platform detection" do
+      node.consume_external_attrs(@ohai_data, {})
+      node.automatic_attrs[:platform].should == 'foobuntu'
+      node.automatic_attrs[:platform_version].should == '23.42'
+    end
+
+    it "consumes the run list from provided json attributes" do
+      node.consume_external_attrs(@ohai_data, {"run_list" => ['recipe[unicorn]']})
+      node.run_list.should == ['recipe[unicorn]']
+    end
+
+    it "saves non-runlist json attrs for later" do
+      expansion = Chef::RunList::RunListExpansion.new('_default', [])
+      node.run_list.stub!(:expand).and_return(expansion)
+      node.consume_external_attrs(@ohai_data, {"foo" => "bar"})
+      node.expand!
+      node.normal_attrs.should == {"foo" => "bar", "tags" => []}
+    end
+
+  end
+
+  describe "when expanding its run list and merging attributes" do
+    before do
+      @environment = Chef::Environment.new.tap do |e|
+        e.name('rspec_env')
+        e.default_attributes("env default key" => "env default value")
+        e.override_attributes("env override key" => "env override value")
+      end
+      Chef::Environment.should_receive(:load).with("rspec_env").and_return(@environment)
+      @expansion = Chef::RunList::RunListExpansion.new("rspec_env", [])
+      node.chef_environment("rspec_env")
+      node.run_list.stub!(:expand).and_return(@expansion)
+    end
+
+    it "sets the 'recipes' automatic attribute to the recipes in the expanded run_list" do
+      @expansion.recipes << 'recipe[chef::client]' << 'recipe[nginx::default]'
+      node.expand!
+      node.automatic_attrs[:recipes].should == ['recipe[chef::client]', 'recipe[nginx::default]']
+    end
+
+    it "sets the 'roles' automatic attribute to the expanded role list" do
+      @expansion.instance_variable_set(:@applied_roles, {'arf' => nil, 'countersnark' => nil})
+      node.expand!
+      node.automatic_attrs[:roles].sort.should == ['arf', 'countersnark']
+    end
+
+    it "applies default attributes from the environment as environment defaults" do
+      node.expand!
+      node.attributes.env_default["env default key"].should == "env default value"
+    end
+
+    it "applies override attributes from the environment as env overrides" do
+      node.expand!
+      node.attributes.env_override["env override key"].should == "env override value"
+    end
+
+    it "applies default attributes from roles as role defaults" do
+      @expansion.default_attrs["role default key"] = "role default value"
+      node.expand!
+      node.attributes.role_default["role default key"].should == "role default value"
+    end
+
+    it "applies override attributes from roles as role overrides" do
+      @expansion.override_attrs["role override key"] = "role override value"
+      node.expand!
+      node.attributes.role_override["role override key"].should == "role override value"
+    end
+  end
+
+  describe "when querying for recipes in the run list" do
+    context "when a recipe is in the top level run list" do
+      before do
+        node.run_list << "recipe[nginx::module]"
+      end
+
+      it "finds the recipe" do
+        node.recipe?("nginx::module").should be_true
+      end
+
+      it "does not find a recipe not in the run list" do
+        node.recipe?("nginx::other_module").should be_false
+      end
+    end
+    context "when a recipe is in the expanded run list only" do
+      before do
+        node.run_list << "role[base]"
+        node.automatic_attrs[:recipes] = [ "nginx::module" ]
+      end
+
+      it "finds a recipe in the expanded run list" do
+        node.recipe?("nginx::module").should be_true
+      end
+
+      it "does not find a recipe that's not in the run list" do
+        node.recipe?("nginx::other_module").should be_false
+      end
+    end
+  end
+
+  describe "when clearing computed state at the beginning of a run" do
+    before do
+      node.default[:foo] = "default"
+      node.normal[:foo] = "normal"
+      node.override[:foo] = "override"
+      node.reset_defaults_and_overrides
+    end
+
+    it "removes default attributes" do
+      node.default.should be_empty
+    end
+
+    it "removes override attributes" do
+      node.override.should be_empty
+    end
+
+    it "leaves normal level attributes untouched" do
+      node[:foo].should == "normal"
+    end
+
+  end
+
+  describe "when merging environment attributes" do
+    before do
+      node.chef_environment = "rspec"
+      @expansion = Chef::RunList::RunListExpansion.new("rspec", [])
+      @expansion.default_attrs.replace({:default => "from role", :d_role => "role only"})
+      @expansion.override_attrs.replace({:override => "from role", :o_role => "role only"})
+
+
+      @environment = Chef::Environment.new
+      @environment.default_attributes = {:default => "from env", :d_env => "env only" }
+      @environment.override_attributes = {:override => "from env", :o_env => "env only"}
+      Chef::Environment.stub!(:load).and_return(@environment)
+      node.apply_expansion_attributes(@expansion)
+    end
+
+    it "does not nuke role-only default attrs" do
+      node[:d_role].should == "role only"
+    end
+
+    it "does not nuke role-only override attrs" do
+      node[:o_role].should == "role only"
+    end
+
+    it "does not nuke env-only default attrs" do
+      node[:o_env].should == "env only"
+    end
+
+    it "does not nuke role-only override attrs" do
+      node[:o_env].should == "env only"
+    end
+
+    it "gives role defaults precedence over env defaults" do
+      node[:default].should == "from role"
+    end
+
+    it "gives env overrides precedence over role overrides" do
+      node[:override].should == "from env"
+    end
+  end
+
+  describe "when evaluating attributes files" do
+    before do
+      @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+      @cookbook_loader = Chef::CookbookLoader.new(@cookbook_repo)
+      @cookbook_loader.load_cookbooks
+
+      @cookbook_collection = Chef::CookbookCollection.new(@cookbook_loader.cookbooks_by_name)
+
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_context = Chef::RunContext.new(node, @cookbook_collection, @events)
+
+      node.include_attribute("openldap::default")
+      node.include_attribute("openldap::smokey")
+    end
+
+    it "sets attributes from the files" do
+      node.ldap_server.should eql("ops1prod")
+      node.ldap_basedn.should eql("dc=hjksolutions,dc=com")
+      node.ldap_replication_password.should eql("forsure")
+      node.smokey.should eql("robinson")
+    end
+
+    it "gives a sensible error when attempting to load a missing attributes file" do
+      lambda { node.include_attribute("nope-this::doesnt-exist") }.should raise_error(Chef::Exceptions::CookbookNotFound)
+    end
+  end
+
+  describe "roles" do
+    it "should allow you to query whether or not it has a recipe applied with role?" do
+      node.run_list << "role[sunrise]"
+      node.role?("sunrise").should eql(true)
+      node.role?("not at home").should eql(false)
+    end
+
+    it "should allow you to set roles with arguments" do
+      node.run_list << "role[one]"
+      node.run_list << "role[two]"
+      node.role?("one").should eql(true)
+      node.role?("two").should eql(true)
+    end
+  end
+
+  describe "run_list" do
+    it "should have a Chef::RunList of recipes and roles that should be applied" do
+      node.run_list.should be_a_kind_of(Chef::RunList)
+    end
+
+    it "should allow you to query the run list with arguments" do
+      node.run_list "recipe[baz]"
+      node.run_list?("recipe[baz]").should eql(true)
+    end
+
+    it "should allow you to set the run list with arguments" do
+      node.run_list "recipe[baz]", "role[foo]"
+      node.run_list?("recipe[baz]").should eql(true)
+      node.run_list?("role[foo]").should eql(true)
+    end
+  end
+
+  describe "from file" do
+    it "should load a node from a ruby file" do
+      node.from_file(File.expand_path(File.join(CHEF_SPEC_DATA, "nodes", "test.rb")))
+      node.name.should eql("test.example.com-short")
+      node.sunshine.should eql("in")
+      node.something.should eql("else")
+      node.run_list.should == ["operations-master", "operations-monitoring"]
+    end
+
+    it "should raise an exception if the file cannot be found or read" do
+      lambda { node.from_file("/tmp/monkeydiving") }.should raise_error(IOError)
+    end
+  end
+
+  describe "update_from!" do
+    before(:each) do
+      node.name("orig")
+      node.chef_environment("dev")
+      node.default_attrs = { "one" => { "two" => "three", "four" => "five", "eight" => "nine" } }
+      node.override_attrs = { "one" => { "two" => "three", "four" => "six" } }
+      node.normal_attrs = { "one" => { "two" => "seven" } }
+      node.run_list << "role[marxist]"
+      node.run_list << "role[leninist]"
+      node.run_list << "recipe[stalinist]"
+
+      @example = Chef::Node.new()
+      @example.name("newname")
+      @example.chef_environment("prod")
+      @example.default_attrs = { "alpha" => { "bravo" => "charlie", "delta" => "echo" } }
+      @example.override_attrs = { "alpha" => { "bravo" => "foxtrot", "delta" => "golf" } }
+      @example.normal_attrs = { "alpha" => { "bravo" => "hotel" } }
+      @example.run_list << "role[comedy]"
+      @example.run_list << "role[drama]"
+      @example.run_list << "recipe[mystery]"
+    end
+
+    it "allows update of everything except name" do
+      node.update_from!(@example)
+      node.name.should == "orig"
+      node.chef_environment.should == @example.chef_environment
+      node.default_attrs.should == @example.default_attrs
+      node.override_attrs.should == @example.override_attrs
+      node.normal_attrs.should == @example.normal_attrs
+      node.run_list.should == @example.run_list
+    end
+
+    it "should not update the name of the node" do
+      node.should_not_receive(:name).with(@example.name)
+      node.update_from!(@example)
+    end
+  end
+
+  describe "to_hash" do
+    it "should serialize itself as a hash" do
+      node.chef_environment("dev")
+      node.default_attrs = { "one" => { "two" => "three", "four" => "five", "eight" => "nine" } }
+      node.override_attrs = { "one" => { "two" => "three", "four" => "six" } }
+      node.normal_attrs = { "one" => { "two" => "seven" } }
+      node.run_list << "role[marxist]"
+      node.run_list << "role[leninist]"
+      node.run_list << "recipe[stalinist]"
+      h = node.to_hash
+      h["one"]["two"].should == "three"
+      h["one"]["four"].should == "six"
+      h["one"]["eight"].should == "nine"
+      h["role"].should be_include("marxist")
+      h["role"].should be_include("leninist")
+      h["run_list"].should be_include("role[marxist]")
+      h["run_list"].should be_include("role[leninist]")
+      h["run_list"].should be_include("recipe[stalinist]")
+      h["chef_environment"].should == "dev"
+    end
+  end
+
+  describe "converting to or from json" do
+    it "should serialize itself as json", :json => true do
+      node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA))
+      json = Chef::JSONCompat.to_json(node)
+      json.should =~ /json_class/
+      json.should =~ /name/
+      json.should =~ /chef_environment/
+      json.should =~ /normal/
+      json.should =~ /default/
+      json.should =~ /override/
+      json.should =~ /run_list/
+    end
+
+    it 'should serialize valid json with a run list', :json => true do
+      #This test came about because activesupport mucks with Chef json serialization
+      #Test should pass with and without Activesupport
+      node.run_list << {"type" => "role", "name" => 'Cthulu'}
+      node.run_list << {"type" => "role", "name" => 'Hastur'}
+      json = Chef::JSONCompat.to_json(node)
+      json.should =~ /\"run_list\":\[\"role\[Cthulu\]\",\"role\[Hastur\]\"\]/
+    end
+
+    it "merges the override components into a combined override object" do
+      node.attributes.role_override["role override"] = "role override"
+      node.attributes.env_override["env override"] = "env override"
+      node_for_json = node.for_json
+      node_for_json["override"]["role override"].should == "role override"
+      node_for_json["override"]["env override"].should == "env override"
+    end
+
+    it "merges the default components into a combined default object" do
+      node.attributes.role_default["role default"] = "role default"
+      node.attributes.env_default["env default"] = "env default"
+      node_for_json = node.for_json
+      node_for_json["default"]["role default"].should == "role default"
+      node_for_json["default"]["env default"].should == "env default"
+    end
+
+
+    it "should deserialize itself from json", :json => true do
+      node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA))
+      json = Chef::JSONCompat.to_json(node)
+      serialized_node = Chef::JSONCompat.from_json(json)
+      serialized_node.should be_a_kind_of(Chef::Node)
+      serialized_node.name.should eql(node.name)
+      serialized_node.chef_environment.should eql(node.chef_environment)
+      node.each_attribute do |k,v|
+        serialized_node[k].should eql(v)
+      end
+      serialized_node.run_list.should == node.run_list
+    end
+  end
+
+  describe "to_s" do
+    it "should turn into a string like node[name]" do
+      node.name("airplane")
+      node.to_s.should eql("node[airplane]")
+    end
+  end
+
+  describe "api model" do
+    before(:each) do
+      @rest = mock("Chef::REST")
+      Chef::REST.stub!(:new).and_return(@rest)
+      @query = mock("Chef::Search::Query")
+      Chef::Search::Query.stub!(:new).and_return(@query)
+    end
+
+    describe "list" do
+      describe "inflated" do
+        it "should return a hash of node names and objects" do
+          n1 = mock("Chef::Node", :name => "one")
+          @query.should_receive(:search).with(:node).and_yield(n1)
+          r = Chef::Node.list(true)
+          r["one"].should == n1
+        end
+      end
+
+      it "should return a hash of node names and urls" do
+        @rest.should_receive(:get_rest).and_return({ "one" => "http://foo" })
+        r = Chef::Node.list
+        r["one"].should == "http://foo"
+      end
+    end
+
+    describe "load" do
+      it "should load a node by name" do
+        @rest.should_receive(:get_rest).with("nodes/monkey").and_return("foo")
+        Chef::Node.load("monkey").should == "foo"
+      end
+    end
+
+    describe "destroy" do
+      it "should destroy a node" do
+        @rest.should_receive(:delete_rest).with("nodes/monkey").and_return("foo")
+        node.name("monkey")
+        node.destroy
+      end
+    end
+
+    describe "save" do
+      it "should update a node if it already exists" do
+        node.name("monkey")
+        @rest.should_receive(:put_rest).with("nodes/monkey", node).and_return("foo")
+        node.save
+      end
+
+      it "should not try and create if it can update" do
+        node.name("monkey")
+        @rest.should_receive(:put_rest).with("nodes/monkey", node).and_return("foo")
+        @rest.should_not_receive(:post_rest)
+        node.save
+      end
+
+      it "should create if it cannot update" do
+        node.name("monkey")
+        exception = mock("404 error", :code => "404")
+        @rest.should_receive(:put_rest).and_raise(Net::HTTPServerException.new("foo", exception))
+        @rest.should_receive(:post_rest).with("nodes", node)
+        node.save
+      end
+
+      describe "when whyrun mode is enabled" do
+        before do
+          Chef::Config[:why_run] = true
+        end
+        after do
+          Chef::Config[:why_run] = false
+        end
+        it "should not save" do
+          node.name("monkey")
+          @rest.should_not_receive(:put_rest)
+          @rest.should_not_receive(:post_rest)
+          node.save
+        end
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb
new file mode 100644
index 0000000..3904435
--- /dev/null
+++ b/spec/unit/platform_spec.rb
@@ -0,0 +1,255 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe "Chef::Platform supports" do
+  [
+    :mac_os_x,
+    :mac_os_x_server,
+    :freebsd,
+    :ubuntu,
+    :debian,
+    :centos,
+    :fedora,
+    :suse,
+    :opensuse,
+    :redhat,
+    :oracle,
+    :gentoo,
+    :arch,
+    :solaris,
+    :mswin,
+    :mingw32,
+    :windows,
+    :gcel
+  ].each do |platform|
+    it "#{platform}" do
+      Chef::Platform.platforms.should have_key(platform)
+    end
+  end
+end
+
+describe Chef::Platform do
+
+  before :all do
+    @original_platform_map = Chef::Platform.platforms
+  end
+
+  after :all do ||
+    Chef::Platform.platforms = @original_platform_map
+  end
+
+  before(:each) do
+    Chef::Platform.platforms = {
+      :darwin => {
+        ">= 10.11" => {
+          :file => "new_darwinian"
+        },
+        "9.2.2" => {
+          :file => "darwinian",
+          :else => "thing"
+        },
+        :default => {
+          :file => "old school",
+          :snicker => "snack"
+        }
+      },
+      :mars_volta => {
+      },
+      :default => {
+        :file => Chef::Provider::File,
+        :pax => "brittania",
+        :cat => "nice"
+      }
+    }
+    @events = Chef::EventDispatch::Dispatcher.new
+  end
+
+  it "should allow you to look up a platform by name and version, returning the provider map for it" do
+    pmap = Chef::Platform.find("Darwin", "9.2.2")
+    pmap.should be_a_kind_of(Hash)
+    pmap[:file].should eql("darwinian")
+  end
+
+  it "should allow you to look up a platform by name and version using \"greater than\" style operators" do
+    pmap = Chef::Platform.find("Darwin", "11.1.0")
+    pmap.should be_a_kind_of(Hash)
+    pmap[:file].should eql("new_darwinian")
+  end
+
+  it "should use the default providers for an os if the specific version does not exist" do
+    pmap = Chef::Platform.find("Darwin", "1")
+    pmap.should be_a_kind_of(Hash)
+    pmap[:file].should eql("old school")
+  end
+
+  it "should use the default providers if the os doesn't give me a default, but does exist" do
+    pmap = Chef::Platform.find("mars_volta", "1")
+    pmap.should be_a_kind_of(Hash)
+    pmap[:file].should eql(Chef::Provider::File)
+  end
+
+  it "should use the default provider if the os does not exist" do
+    pmap = Chef::Platform.find("AIX", "1")
+    pmap.should be_a_kind_of(Hash)
+    pmap[:file].should eql(Chef::Provider::File)
+  end
+
+  it "should merge the defaults for an os with the specific version" do
+    pmap = Chef::Platform.find("Darwin", "9.2.2")
+    pmap[:file].should eql("darwinian")
+    pmap[:snicker].should eql("snack")
+  end
+
+  it "should merge the defaults for an os with the universal defaults" do
+    pmap = Chef::Platform.find("Darwin", "9.2.2")
+    pmap[:file].should eql("darwinian")
+    pmap[:pax].should eql("brittania")
+  end
+
+  it "should allow you to look up a provider for a platform directly by symbol" do
+    Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian")
+  end
+
+  it "should raise an exception if a provider cannot be found for a resource type" do
+    lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError)
+  end
+
+  it "should look up a provider for a resource with a Chef::Resource object" do
+    kitty = Chef::Resource::Cat.new("loulou")
+    Chef::Platform.find_provider("Darwin", "9.2.2", kitty).should eql("nice")
+  end
+
+  it "should look up a provider with a node and a Chef::Resource object" do
+    kitty = Chef::Resource::Cat.new("loulou")
+    node = Chef::Node.new
+    node.name("Intel")
+    node.automatic_attrs[:platform] = "mac_os_x"
+    node.automatic_attrs[:platform_version] = "9.2.2"
+    Chef::Platform.find_provider_for_node(node, kitty).should eql("nice")
+  end
+
+  it "should not throw an exception when the platform version has an unknown format" do
+    Chef::Platform.find_provider(:darwin, "bad-version", :file).should eql("old school")
+  end
+
+  it "should prefer an explicit provider" do
+    kitty = Chef::Resource::Cat.new("loulou")
+    kitty.stub!(:provider).and_return(Chef::Provider::File)
+    node = Chef::Node.new
+    node.name("Intel")
+    node.automatic_attrs[:platform] = "mac_os_x"
+    node.automatic_attrs[:platform_version] = "9.2.2"
+    Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::File)
+  end
+
+  it "should look up a provider based on the resource name if nothing else matches" do
+    kitty = Chef::Resource::Cat.new("loulou")
+    class Chef::Provider::Cat < Chef::Provider; end
+    Chef::Platform.platforms[:default].delete(:cat)
+    node = Chef::Node.new
+    node.name("Intel")
+    node.automatic_attrs[:platform] = "mac_os_x"
+    node.automatic_attrs[:platform_version] = "8.5"
+    Chef::Platform.find_provider_for_node(node, kitty).should eql(Chef::Provider::Cat)
+  end
+
+  def setup_file_resource
+    node = Chef::Node.new
+    node.automatic_attrs[:platform] = "mac_os_x"
+    node.automatic_attrs[:platform_version] = "9.2.2"
+    run_context = Chef::RunContext.new(node, {}, @events)
+    [ Chef::Resource::File.new("whateva", run_context), run_context ]
+  end
+
+  it "returns a provider object given a Chef::Resource object which has a valid run context and an action" do
+    file, run_context = setup_file_resource
+    provider = Chef::Platform.provider_for_resource(file, :foo)
+    provider.should be_an_instance_of(Chef::Provider::File)
+    provider.new_resource.should equal(file)
+    provider.run_context.should equal(run_context)
+  end
+
+  it "returns a provider object given a Chef::Resource object which has a valid run context without an action" do
+    file, run_context = setup_file_resource
+    provider = Chef::Platform.provider_for_resource(file)
+    provider.should be_an_instance_of(Chef::Provider::File)
+    provider.new_resource.should equal(file)
+    provider.run_context.should equal(run_context)
+  end
+
+  it "raises an error when trying to find the provider for a resource with no run context" do
+    file = Chef::Resource::File.new("whateva")
+    lambda {Chef::Platform.provider_for_resource(file)}.should raise_error(ArgumentError)
+  end
+
+  it "does not support finding a provider by resource and node -- a run context is required" do
+    lambda {Chef::Platform.provider_for_node('node', 'resource')}.should raise_error(NotImplementedError)
+  end
+
+  it "should update the provider map with map" do
+    Chef::Platform.set(
+         :platform => :darwin,
+         :version => "9.2.2",
+         :resource => :file,
+         :provider => "masterful"
+    )
+    Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful")
+    Chef::Platform.set(
+         :platform => :darwin,
+         :resource => :file,
+         :provider => "masterful"
+    )
+    Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful")
+    Chef::Platform.set(
+         :resource => :file,
+         :provider => "masterful"
+    )
+    Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+    Chef::Platform.set(
+         :platform => :hero,
+         :version => "9.2.2",
+         :resource => :file,
+         :provider => "masterful"
+    )
+    Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful")
+
+    Chef::Platform.set(
+         :resource => :file,
+         :provider => "masterful"
+    )
+    Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+    Chef::Platform.platforms = {}
+
+    Chef::Platform.set(
+         :resource => :file,
+         :provider => "masterful"
+    )
+    Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+    Chef::Platform.platforms = { :neurosis => {} }
+    Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful")
+    Chef::Platform.platforms[:neurosis][:default][:package].should eql("masterful")
+
+  end
+
+
+end
diff --git a/spec/unit/provider/breakpoint_spec.rb b/spec/unit/provider/breakpoint_spec.rb
new file mode 100644
index 0000000..9776245
--- /dev/null
+++ b/spec/unit/provider/breakpoint_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+describe Chef::Provider::Breakpoint do
+
+  before do
+    @resource = Chef::Resource::Breakpoint.new
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @collection = mock("resource collection")
+    @run_context.stub!(:resource_collection).and_return(@collection)
+    @provider = Chef::Provider::Breakpoint.new(@resource, @run_context)
+  end
+
+  it "responds to load_current_resource" do
+    @provider.should respond_to(:load_current_resource)
+  end
+
+  it "gets the iterator from @collection and pauses it" do
+    Shell.stub!(:running?).and_return(true)
+    @iterator = mock("stepable_iterator")
+    @collection.stub!(:iterator).and_return(@iterator)
+    @iterator.should_receive(:pause)
+    @provider.action_break
+    @resource.should be_updated
+  end
+
+  it "doesn't pause the iterator if chef-shell isn't running" do
+    Shell.stub!(:running?).and_return(false)
+    @iterator = mock("stepable_iterator")
+    @collection.stub!(:iterator).and_return(@iterator)
+    @iterator.should_not_receive(:pause)
+    @provider.action_break
+  end
+
+end
diff --git a/spec/unit/provider/cookbook_file/content_spec.rb b/spec/unit/provider/cookbook_file/content_spec.rb
new file mode 100644
index 0000000..b771b76
--- /dev/null
+++ b/spec/unit/provider/cookbook_file/content_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::CookbookFile::Content do
+
+  let(:new_resource) { mock('Chef::Resource::CookbookFile (new)', :cookbook_name => 'apache2', :cookbook => 'apache2') }
+  let(:content) do
+    @run_context = mock('Chef::RunContext')
+    @current_resource = mock('Chef::Resource::CookbookFile (current)')
+    Chef::Provider::CookbookFile::Content.new(new_resource, @current_resource, @run_context)
+  end
+
+  it "prefers the explicit cookbook name on the resource to the implicit one" do
+    new_resource.stub!(:cookbook).and_return('nginx')
+    content.send(:resource_cookbook).should == 'nginx'
+  end
+
+  it "falls back to the implicit cookbook name on the resource" do
+    content.send(:resource_cookbook).should == 'apache2'
+  end
+
+end
+
diff --git a/spec/unit/provider/cookbook_file_spec.rb b/spec/unit/provider/cookbook_file_spec.rb
new file mode 100644
index 0000000..4456907
--- /dev/null
+++ b/spec/unit/provider/cookbook_file_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2009-2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'ostruct'
+
+require 'support/shared/unit/provider/file'
+
+describe Chef::Provider::CookbookFile do
+  let(:node) { double('Chef::Node') }
+  let(:events) { double('Chef::Events').as_null_object }  # mock all the methods
+  let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
+  let(:enclosing_directory) {
+    canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates")))
+  }
+  let(:resource_path) {
+    canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt")))
+  }
+
+  # Subject
+
+  let(:provider) do
+    provider = described_class.new(resource, run_context)
+    provider.stub!(:content).and_return(content)
+    provider
+  end
+
+  let(:resource) do
+    resource = Chef::Resource::CookbookFile.new("seattle", @run_context)
+    resource.path(resource_path)
+    resource.cookbook_name = 'apache2'
+    resource
+  end
+
+  let(:content) do
+    content = mock('Chef::Provider::CookbookFile::Content')
+  end
+
+  it_behaves_like Chef::Provider::File
+
+end
diff --git a/spec/unit/provider/cron/unix_spec.rb b/spec/unit/provider/cron/unix_spec.rb
new file mode 100644
index 0000000..ffdfa19
--- /dev/null
+++ b/spec/unit/provider/cron/unix_spec.rb
@@ -0,0 +1,121 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Author:: Toomas Pelberg (toomasp at gmx.net)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# Copyright:: Copyright (c) 2010 Toomas Pelberg
+# 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::Cron::Unix do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Cron.new("cronhole some stuff")
+    @new_resource.user "root"
+    @new_resource.minute "30"
+    @new_resource.command "/bin/true"
+
+    @provider = Chef::Provider::Cron::Unix.new(@new_resource, @run_context)
+  end
+
+  it "should inherit from Chef::Provider:Cron" do
+    @provider.should be_a(Chef::Provider::Cron)
+  end
+
+  describe "read_crontab" do
+    before :each do
+      @status = mock("Status", :exitstatus => 0)
+      @stdout = StringIO.new(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+      CRONTAB
+      @provider.stub!(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status)
+    end
+
+    it "should call crontab -l with the user" do
+      @provider.should_receive(:popen4).with("crontab -l #{@new_resource.user}").and_return(@status)
+      @provider.send(:read_crontab)
+    end
+
+    it "should return the contents of the crontab" do
+      crontab = @provider.send(:read_crontab)
+      crontab.should == <<-CRONTAB
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+CRONTAB
+    end
+
+    it "should return nil if the user has no crontab" do
+      status = mock("Status", :exitstatus => 1)
+      @provider.stub!(:popen4).and_return(status)
+      @provider.send(:read_crontab).should == nil
+    end
+
+    it "should raise an exception if another error occurs" do
+      status = mock("Status", :exitstatus => 2)
+      @provider.stub!(:popen4).and_return(status)
+      lambda do
+        @provider.send(:read_crontab)
+      end.should raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2")
+    end
+  end
+
+  describe "write_crontab" do
+    before :each do
+      @status = mock("Status", :exitstatus => 0)
+      @provider.stub!(:run_command_and_return_stdout_stderr).and_return(@status, String.new, String.new)
+      @tempfile = mock("foo", :path => "/tmp/foo", :close => true)
+      Tempfile.stub!(:new).and_return(@tempfile)
+      @tempfile.should_receive(:flush)
+      @tempfile.should_receive(:chmod).with(420)
+      @tempfile.should_receive(:close!)
+    end
+
+    it "should call crontab for the user" do
+      @provider.should_receive(:run_command_and_return_stdout_stderr).with(hash_including(:user => @new_resource.user))
+      @tempfile.should_receive(:<<).with("Foo")
+      @provider.send(:write_crontab, "Foo")
+    end
+
+    it "should call crontab with a file containing the crontab" do
+      @provider.should_receive(:run_command_and_return_stdout_stderr) do |args|
+        (args[:command] =~ %r{\A/usr/bin/crontab (/\S+)\z}).should be_true
+        $1.should == "/tmp/foo"
+        @status
+      end
+      @tempfile.should_receive(:<<).with("Foo\n# wibble\n wah!!")
+      @provider.send(:write_crontab, "Foo\n# wibble\n wah!!")
+    end
+
+    it "should raise an exception if the command returns non-zero" do
+      @tempfile.should_receive(:<<).with("Foo")
+      @status.stub!(:exitstatus).and_return(1)
+      lambda do
+        @provider.send(:write_crontab, "Foo")
+      end.should raise_error(Chef::Exceptions::Cron, /Error updating state of #{@new_resource.name}, exit: 1/)
+    end
+  end
+end
diff --git a/spec/unit/provider/cron_spec.rb b/spec/unit/provider/cron_spec.rb
new file mode 100644
index 0000000..8a819b6
--- /dev/null
+++ b/spec/unit/provider/cron_spec.rb
@@ -0,0 +1,830 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Cron do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Cron.new("cronhole some stuff", @run_context)
+    @new_resource.user "root"
+    @new_resource.minute "30"
+    @new_resource.command "/bin/true"
+
+    @provider = Chef::Provider::Cron.new(@new_resource, @run_context)
+  end
+
+  describe "when examining the current system state" do
+    context "with no crontab for the user" do
+      before :each do
+        @provider.stub!(:read_crontab).and_return(nil)
+      end
+
+      it "should set cron_empty" do
+        @provider.load_current_resource
+        @provider.cron_empty.should == true
+        @provider.cron_exists.should == false
+      end
+
+      it "should report an empty crontab" do
+        Chef::Log.should_receive(:debug).with("Cron empty for '#{@new_resource.user}'")
+        @provider.load_current_resource
+      end
+    end
+
+    context "with no matching entry in the user's crontab" do
+      before :each do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+CRONTAB
+      end
+
+      it "should not set cron_exists or cron_empty" do
+        @provider.load_current_resource
+        @provider.cron_exists.should == false
+        @provider.cron_empty.should == false
+      end
+
+      it "should report no entry found" do
+        Chef::Log.should_receive(:debug).with("Cron '#{@new_resource.name}' not found")
+        @provider.load_current_resource
+      end
+
+      it "should not fail if there's an existing cron with a numerical argument" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: foo[bar] (baz)
+21 */4 * * * some_prog 1234567
+CRONTAB
+        lambda {
+          @provider.load_current_resource
+        }.should_not raise_error
+      end
+    end
+
+    context "with a matching entry in the user's crontab" do
+      before :each do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+* 5 * 1 * /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+      end
+
+      it "should set cron_exists" do
+        @provider.load_current_resource
+        @provider.cron_exists.should == true
+        @provider.cron_empty.should == false
+      end
+
+      it "should pull the details out of the cron line" do
+        cron = @provider.load_current_resource
+        cron.minute.should == '*'
+        cron.hour.should == '5'
+        cron.day.should == '*'
+        cron.month.should == '1'
+        cron.weekday.should == '*'
+        cron.command.should == '/bin/true param1 param2'
+      end
+
+      it "should pull env vars out" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo at example.com
+SHELL=/bin/foosh
+PATH=/bin:/foo
+HOME=/home/foo
+* 5 * 1 * /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+        cron = @provider.load_current_resource
+        cron.mailto.should == 'foo at example.com'
+        cron.shell.should == '/bin/foosh'
+        cron.path.should == '/bin:/foo'
+        cron.home.should == '/home/foo'
+        cron.minute.should == '*'
+        cron.hour.should == '5'
+        cron.day.should == '*'
+        cron.month.should == '1'
+        cron.weekday.should == '*'
+        cron.command.should == '/bin/true param1 param2'
+      end
+
+      it "should parse and load generic and standard environment variables from cron entry" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MAILTO=warn at example.com
+TEST=lol
+FLAG=1
+* 5 * * * /bin/true
+CRONTAB
+        cron = @provider.load_current_resource
+
+        cron.mailto.should == "warn at example.com"
+        cron.environment.should == {"TEST" => "lol", "FLAG" => "1"}
+      end
+
+      it "should not break with variabels that match the cron resource internals" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+# Chef Name: cronhole some stuff
+MINUTE=40
+HOUR=midnight
+TEST=lol
+ENVIRONMENT=production
+* 5 * * * /bin/true
+CRONTAB
+        cron = @provider.load_current_resource
+
+        cron.minute.should == '*'
+        cron.hour.should == '5'
+        cron.environment.should == {"MINUTE" => "40", "HOUR" => "midnight", "TEST" => "lol", "ENVIRONMENT" => "production"}
+      end
+
+      it "should report the match" do
+        Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'")
+        @provider.load_current_resource
+      end
+    end
+
+    context "with a matching entry in the user's crontab using month names and weekday names (#CHEF-3178)" do
+      before :each do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+* 5 * Jan Mon /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+      end
+
+      it "should set cron_exists" do
+        @provider.load_current_resource
+        @provider.cron_exists.should == true
+        @provider.cron_empty.should == false
+      end
+
+      it "should pull the details out of the cron line" do
+        cron = @provider.load_current_resource
+        cron.minute.should == '*'
+        cron.hour.should == '5'
+        cron.day.should == '*'
+        cron.month.should == 'Jan'
+        cron.weekday.should == 'Mon'
+        cron.command.should == '/bin/true param1 param2'
+      end
+
+      it "should report the match" do
+        Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'")
+        @provider.load_current_resource
+      end
+    end
+
+    context "with a matching entry without a crontab line" do
+      it "should set cron_exists and leave current_resource values at defaults" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+CRONTAB
+        cron = @provider.load_current_resource
+        @provider.cron_exists.should == true
+        cron.minute.should == '*'
+        cron.hour.should == '*'
+        cron.day.should == '*'
+        cron.month.should == '*'
+        cron.weekday.should == '*'
+        cron.command.should == nil
+      end
+
+      it "should not pick up a commented out crontab line" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#* 5 * 1 * /bin/true param1 param2
+CRONTAB
+        cron = @provider.load_current_resource
+        @provider.cron_exists.should == true
+        cron.minute.should == '*'
+        cron.hour.should == '*'
+        cron.day.should == '*'
+        cron.month.should == '*'
+        cron.weekday.should == '*'
+        cron.command.should == nil
+      end
+
+      it "should not pick up a later crontab entry" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#* 5 * 1 * /bin/true param1 param2
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+CRONTAB
+        cron = @provider.load_current_resource
+        @provider.cron_exists.should == true
+        cron.minute.should == '*'
+        cron.hour.should == '*'
+        cron.day.should == '*'
+        cron.month.should == '*'
+        cron.weekday.should == '*'
+        cron.command.should == nil
+      end
+    end
+  end
+
+  describe "cron_different?" do
+    before :each do
+      @current_resource = Chef::Resource::Cron.new("cronhole some stuff")
+      @current_resource.user "root"
+      @current_resource.minute "30"
+      @current_resource.command "/bin/true"
+      @provider.current_resource = @current_resource
+    end
+
+    [:minute, :hour, :day, :month, :weekday, :command, :mailto, :path, :shell, :home].each do |attribute|
+      it "should return true if #{attribute} doesn't match" do
+        @new_resource.send(attribute, "something_else")
+        @provider.cron_different?.should eql(true)
+      end
+    end
+
+    it "should return true if environment doesn't match" do
+      @new_resource.environment "FOO" => "something_else"
+      @provider.cron_different?.should eql(true)
+    end
+
+    it "should return false if the objects are identical" do
+      @provider.cron_different?.should == false
+    end
+  end
+
+  describe "action_create" do
+    before :each do
+      @provider.stub!(:write_crontab)
+      @provider.stub!(:read_crontab).and_return(nil)
+    end
+
+    context "when there is no existing crontab" do
+      before :each do
+        @provider.cron_exists = false
+        @provider.cron_empty = true
+      end
+
+      it "should create a crontab with the entry" do
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should include env variables that are set" do
+        @new_resource.mailto 'foo at example.com'
+        @new_resource.path '/usr/bin:/my/custom/path'
+        @new_resource.shell '/bin/foosh'
+        @new_resource.home '/home/foo'
+        @new_resource.environment "TEST" => "LOL"
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+# Chef Name: cronhole some stuff
+MAILTO=foo at example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+TEST=LOL
+30 * * * * /bin/true
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should mark the resource as updated" do
+        @provider.run_action(:create)
+        @new_resource.should be_updated_by_last_action
+      end
+
+      it "should log the action" do
+        Chef::Log.should_receive(:info).with("cron[cronhole some stuff] added crontab entry")
+        @provider.run_action(:create)
+      end
+    end
+
+    context "when there is a crontab with no matching section" do
+      before :each do
+        @provider.cron_exists = false
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+      end
+
+      it "should add the entry to the crontab" do
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should include env variables that are set" do
+        @new_resource.mailto 'foo at example.com'
+        @new_resource.path '/usr/bin:/my/custom/path'
+        @new_resource.shell '/bin/foosh'
+        @new_resource.home '/home/foo'
+        @new_resource.environment "TEST" => "LOL"
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+# Chef Name: cronhole some stuff
+MAILTO=foo at example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+TEST=LOL
+30 * * * * /bin/true
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should mark the resource as updated" do
+        @provider.run_action(:create)
+        @new_resource.should be_updated_by_last_action
+      end
+
+      it "should log the action" do
+        Chef::Log.should_receive(:info).with("cron[cronhole some stuff] added crontab entry")
+        @provider.run_action(:create)
+      end
+    end
+
+    context "when there is a crontab with a matching but different section" do
+      before :each do
+        @provider.cron_exists = true
+        @provider.stub!(:cron_different?).and_return(true)
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+      end
+
+      it "should update the crontab entry" do
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should include env variables that are set" do
+        @new_resource.mailto 'foo at example.com'
+        @new_resource.path '/usr/bin:/my/custom/path'
+        @new_resource.shell '/bin/foosh'
+        @new_resource.home '/home/foo'
+        @new_resource.environment "TEST" => "LOL"
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo at example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+TEST=LOL
+30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should mark the resource as updated" do
+        @provider.run_action(:create)
+        @new_resource.should be_updated_by_last_action
+      end
+
+      it "should log the action" do
+        Chef::Log.should_receive(:info).with("cron[cronhole some stuff] updated crontab entry")
+        @provider.run_action(:create)
+      end
+    end
+
+    context "when there is a crontab with a matching section with no crontab line in it" do
+      before :each do
+        @provider.cron_exists = true
+        @provider.stub!(:cron_different?).and_return(true)
+      end
+
+      it "should add the crontab to the entry" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+        CRONTAB
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should not blat any following entries" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * * * /bin/true
+#30 * * * * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:create)
+      end
+
+      it "should handle env vars with no crontab" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=bar at example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/barsh
+HOME=/home/foo
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+        @new_resource.mailto 'foo at example.com'
+        @new_resource.path '/usr/bin:/my/custom/path'
+        @new_resource.shell '/bin/foosh'
+        @new_resource.home '/home/foo'
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo at example.com
+PATH=/usr/bin:/my/custom/path
+SHELL=/bin/foosh
+HOME=/home/foo
+30 * * * * /bin/true
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:create)
+      end
+    end
+
+    context "when there is a crontab with a matching and identical section" do
+      before :each do
+        @provider.cron_exists = true
+        @provider.stub!(:cron_different?).and_return(false)
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+* 5 * * * /bin/true
+
+# Another comment
+CRONTAB
+      end
+
+      it "should not update the crontab" do
+        @provider.should_not_receive(:write_crontab)
+        @provider.run_action(:create)
+      end
+
+      it "should not mark the resource as updated" do
+        @provider.run_action(:create)
+        @new_resource.should_not be_updated_by_last_action
+      end
+
+      it "should log nothing changed" do
+        Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'")
+        Chef::Log.should_receive(:debug).with("Skipping existing cron entry '#{@new_resource.name}'")
+        @provider.run_action(:create)
+      end
+    end
+  end
+
+  describe "action_delete" do
+    before :each do
+      @provider.stub!(:write_crontab)
+      @provider.stub!(:read_crontab).and_return(nil)
+    end
+
+    context "when the user's crontab has no matching section" do
+      before :each do
+        @provider.cron_exists = false
+      end
+
+      it "should do nothing" do
+        @provider.should_not_receive(:write_crontab)
+        Chef::Log.should_not_receive(:info)
+        @provider.run_action(:delete)
+      end
+
+      it "should not mark the resource as updated" do
+        @provider.run_action(:delete)
+        @new_resource.should_not be_updated_by_last_action
+      end
+    end
+
+    context "when the user has a crontab with a matching section" do
+      before :each do
+        @provider.cron_exists = true
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+      end
+
+      it "should remove the entry" do
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:delete)
+      end
+
+      it "should remove any env vars with the entry" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo at example.com
+FOO=test
+30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:delete)
+      end
+
+      it "should mark the resource as updated" do
+        @provider.run_action(:delete)
+        @new_resource.should be_updated_by_last_action
+      end
+
+      it "should log the action" do
+        Chef::Log.should_receive(:info).with("#{@new_resource} deleted crontab entry")
+        @provider.run_action(:delete)
+      end
+    end
+
+    context "when the crontab has a matching section with no crontab line" do
+      before :each do
+        @provider.cron_exists = true
+      end
+
+      it "should remove the section" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+        CRONTAB
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+        ENDCRON
+        @provider.run_action(:delete)
+      end
+
+      it "should not blat following sections" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:delete)
+      end
+
+      it "should remove any envvars with the section" do
+        @provider.stub!(:read_crontab).and_return(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: cronhole some stuff
+MAILTO=foo at example.com
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        CRONTAB
+        @provider.should_receive(:write_crontab).with(<<-ENDCRON)
+0 2 * * * /some/other/command
+
+#30 * * 3 * /bin/true
+# Chef Name: something else
+2 * 1 * * /bin/false
+
+# Another comment
+        ENDCRON
+        @provider.run_action(:delete)
+      end
+    end
+  end
+
+  describe "read_crontab" do
+    before :each do
+      @status = mock("Status", :exitstatus => 0)
+      @stdout = StringIO.new(<<-CRONTAB)
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+      CRONTAB
+      @provider.stub!(:popen4).and_yield(1234, StringIO.new, @stdout, StringIO.new).and_return(@status)
+    end
+
+    it "should call crontab -l with the user" do
+      @provider.should_receive(:popen4).with("crontab -l -u #{@new_resource.user}").and_return(@status)
+      @provider.send(:read_crontab)
+    end
+
+    it "should return the contents of the crontab" do
+      crontab = @provider.send(:read_crontab)
+      crontab.should == <<-CRONTAB
+0 2 * * * /some/other/command
+
+# Chef Name: something else
+* 5 * * * /bin/true
+
+# Another comment
+      CRONTAB
+    end
+
+    it "should return nil if the user has no crontab" do
+      status = mock("Status", :exitstatus => 1)
+      @provider.stub!(:popen4).and_return(status)
+      @provider.send(:read_crontab).should == nil
+    end
+
+    it "should raise an exception if another error occurs" do
+      status = mock("Status", :exitstatus => 2)
+      @provider.stub!(:popen4).and_return(status)
+      lambda do
+        @provider.send(:read_crontab)
+      end.should raise_error(Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: 2")
+    end
+  end
+
+  describe "write_crontab" do
+    before :each do
+      @status = mock("Status", :exitstatus => 0)
+      @stdin = StringIO.new
+      @provider.stub!(:popen4).and_yield(1234, @stdin, StringIO.new, StringIO.new).and_return(@status)
+    end
+
+    it "should call crontab for the user" do
+      @provider.should_receive(:popen4).with("crontab -u #{@new_resource.user} -", :waitlast => true).and_return(@status)
+      @provider.send(:write_crontab, "Foo")
+    end
+
+    it "should write the given string to the crontab command" do
+      @provider.send(:write_crontab, "Foo\n# wibble\n wah!!")
+      @stdin.string.should == "Foo\n# wibble\n wah!!"
+    end
+
+    it "should raise an exception if the command returns non-zero" do
+      @status.stub!(:exitstatus).and_return(1)
+      lambda do
+        @provider.send(:write_crontab, "Foo")
+      end.should raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1")
+    end
+
+    it "should raise an exception if the command die's and parent tries to write" do
+      class WriteErrPipe
+        def write(str)
+          raise Errno::EPIPE, "Test"
+        end
+      end
+      @status.stub!(:exitstatus).and_return(1)
+      @provider.stub!(:popen4).and_yield(1234, WriteErrPipe.new, StringIO.new, StringIO.new).and_return(@status)
+
+      Chef::Log.should_receive(:debug).with("Broken pipe - Test")
+
+      lambda do
+        @provider.send(:write_crontab, "Foo")
+      end.should raise_error(Chef::Exceptions::Cron, "Error updating state of #{@new_resource.name}, exit: 1")
+    end
+
+  end
+end
diff --git a/spec/unit/provider/deploy/revision_spec.rb b/spec/unit/provider/deploy/revision_spec.rb
new file mode 100644
index 0000000..13eaee6
--- /dev/null
+++ b/spec/unit/provider/deploy/revision_spec.rb
@@ -0,0 +1,111 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Provider::Deploy::Revision do
+
+  before do
+    @temp_dir = Dir.mktmpdir
+    Chef::Config[:file_cache_path] = @temp_dir
+    @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+    @resource.revision("8a3195bf3efa246f743c5dfa83683201880f935c")
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @provider = Chef::Provider::Deploy::Revision.new(@resource, @run_context)
+    @provider.load_current_resource
+    @runner = mock("runnah")
+    Chef::Runner.stub!(:new).and_return(@runner)
+    @expected_release_dir = "/my/deploy/dir/releases/8a3195bf3efa246f743c5dfa83683201880f935c"
+  end
+
+  after do
+    # Make sure we don't keep any state in our tests
+    FileUtils.rspec_reset
+    FileUtils.rm_rf @temp_dir if File.directory?( @temp_dir )
+  end
+
+
+  it "uses the resolved revision from the SCM as the release slug" do
+    @provider.scm_provider.stub!(:revision_slug).and_return("uglySlugly")
+    @provider.send(:release_slug).should == "uglySlugly"
+  end
+
+  it "deploys to a dir named after the revision" do
+    @provider.release_path.should == @expected_release_dir
+  end
+
+  it "stores the release dir in the file cache in the cleanup step" do
+    FileUtils.stub!(:mkdir_p)
+    FileUtils.stub!(:cp_r)
+    @provider.cleanup!
+    @provider.stub!(:release_slug).and_return("73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2")
+    @provider.load_current_resource
+    @provider.cleanup!
+    second_release = "/my/deploy/dir/releases/73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2"
+
+    @provider.all_releases.should == [@expected_release_dir,second_release]
+  end
+
+  it "removes a release from the file cache when it's used again in another release and append it to the end" do
+    FileUtils.stub!(:mkdir_p)
+    FileUtils.stub!(:cp_r)
+    @provider.cleanup!
+    @provider.stub!(:release_slug).and_return("73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2")
+    @provider.load_current_resource
+    @provider.cleanup!
+    second_release = "/my/deploy/dir/releases/73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2"
+    @provider.all_releases.should == [@expected_release_dir,second_release]
+    @provider.cleanup!
+
+    @provider.stub!(:release_slug).and_return("8a3195bf3efa246f743c5dfa83683201880f935c")
+    @provider.load_current_resource
+    @provider.cleanup!
+    @provider.all_releases.should == [second_release, @expected_release_dir]
+  end
+
+  it "removes a release from the file cache when it's deleted by :cleanup!" do
+    release_paths = %w{first second third fourth fifth}.map do |release_name|
+       "/my/deploy/dir/releases/#{release_name}"
+    end
+    release_paths.each do |release_path|
+      @provider.send(:release_created, release_path)
+    end
+    @provider.all_releases.should == release_paths
+
+    FileUtils.stub!(:rm_rf)
+    @provider.cleanup!
+
+    expected_release_paths = (%w{second third fourth fifth} << @resource.revision).map do |release_name|
+       "/my/deploy/dir/releases/#{release_name}"
+    end
+
+    @provider.all_releases.should == expected_release_paths
+  end
+
+  it "regenerates the file cache if it's not available" do
+    oldest = "/my/deploy/dir/releases/oldest"
+    latest = "/my/deploy/dir/releases/latest"
+    Dir.should_receive(:glob).with("/my/deploy/dir/releases/*").and_return([latest, oldest])
+    ::File.should_receive(:ctime).with(oldest).and_return(Time.now - 10)
+    ::File.should_receive(:ctime).with(latest).and_return(Time.now - 1)
+    @provider.all_releases.should == [oldest, latest]
+  end
+
+end
diff --git a/spec/unit/provider/deploy/timestamped_spec.rb b/spec/unit/provider/deploy/timestamped_spec.rb
new file mode 100644
index 0000000..a9cea11
--- /dev/null
+++ b/spec/unit/provider/deploy/timestamped_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Provider::Deploy::Timestamped do
+
+  before do
+    @release_time = Time.utc( 2004, 8, 15, 16, 23, 42)
+    Time.stub!(:now).and_return(@release_time)
+    @expected_release_dir = "/my/deploy/dir/releases/20040815162342"
+    @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @timestamped_deploy = Chef::Provider::Deploy::Timestamped.new(@resource, @run_context)
+    @runner = mock("runnah")
+    Chef::Runner.stub!(:new).and_return(@runner)
+  end
+
+  it "gives a timestamp for release_slug" do
+    @timestamped_deploy.send(:release_slug).should == "20040815162342"
+  end
+
+end
diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb
new file mode 100644
index 0000000..1a9fee8
--- /dev/null
+++ b/spec/unit/provider/deploy_spec.rb
@@ -0,0 +1,634 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Provider::Deploy do
+
+  before do
+    @release_time = Time.utc( 2004, 8, 15, 16, 23, 42)
+    Time.stub!(:now).and_return(@release_time)
+    @expected_release_dir = "/my/deploy/dir/releases/20040815162342"
+    @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @provider = Chef::Provider::Deploy.new(@resource, @run_context)
+    @provider.stub!(:release_slug)
+    @provider.stub!(:release_path).and_return(@expected_release_dir)
+  end
+
+  it "loads scm resource" do
+    @provider.scm_provider.should_receive(:load_current_resource)
+    @provider.load_current_resource
+  end
+
+  it "supports :deploy and :rollback actions" do
+    @provider.should respond_to(:action_deploy)
+    @provider.should respond_to(:action_rollback)
+  end
+
+  context "when the deploy resource has a timeout attribute" do
+    let(:ten_seconds) { 10 }
+    before { @resource.timeout(ten_seconds) }
+    it "relays the timeout to the scm resource" do
+      @provider.scm_provider.new_resource.timeout.should == ten_seconds
+    end
+  end
+
+  context "when the deploy resource has no timeout attribute" do
+    it "should not set a timeout on the scm resource" do
+      @provider.scm_provider.new_resource.timeout.should be_nil
+    end
+  end
+
+  context "when the deploy_to dir does not exist yet" do
+    before do
+      FileUtils.should_receive(:mkdir_p).with(@resource.deploy_to).ordered
+      FileUtils.should_receive(:mkdir_p).with(@resource.shared_path).ordered
+      ::File.stub!(:directory?).and_return(false)
+      @provider.stub(:symlink)
+      @provider.stub(:migrate)
+      @provider.stub(:copy_cached_repo)
+    end
+
+    it "creates deploy_to dir" do
+      ::Dir.should_receive(:chdir).with(@expected_release_dir).exactly(4).times
+      @provider.stub(:update_cached_repo)
+      @provider.deploy
+    end
+
+  end
+
+  it "does not create deploy_to dir if it exists" do
+    ::File.stub!(:directory?).and_return(true)
+    ::Dir.should_receive(:chdir).with(@expected_release_dir).exactly(4).times
+    FileUtils.should_not_receive(:mkdir_p).with(@resource.deploy_to)
+    FileUtils.should_not_receive(:mkdir_p).with(@resource.shared_path)
+    @provider.stub(:copy_cached_repo)
+    @provider.stub(:update_cached_repo)
+    @provider.stub(:symlink)
+    @provider.stub(:migrate)
+    @provider.deploy
+  end
+
+  it "ensures the deploy_to dir ownership after the verfication that it exists" do
+    ::Dir.should_receive(:chdir).with(@expected_release_dir).exactly(4).times
+    @provider.should_receive(:verify_directories_exist).ordered
+    @provider.should_receive(:enforce_ownership).ordered
+    @provider.stub(:copy_cached_repo)
+    @provider.stub(:update_cached_repo)
+    @provider.stub(:install_gems)
+    @provider.stub(:enforce_ownership)
+    @provider.stub(:symlink)
+    @provider.stub(:migrate)
+    @provider.deploy
+  end
+
+  it "updates and copies the repo, then does a migrate, symlink, restart, restart, cleanup on deploy" do
+    FileUtils.stub(:mkdir_p).with("/my/deploy/dir")
+    FileUtils.stub(:mkdir_p).with("/my/deploy/dir/shared")
+    @provider.should_receive(:enforce_ownership).twice
+    @provider.should_receive(:update_cached_repo)
+    @provider.should_receive(:copy_cached_repo)
+    @provider.should_receive(:install_gems)
+    @provider.should_receive(:callback).with(:before_migrate, nil)
+    @provider.should_receive(:migrate)
+    @provider.should_receive(:callback).with(:before_symlink, nil)
+    @provider.should_receive(:symlink)
+    @provider.should_receive(:callback).with(:before_restart, nil)
+    @provider.should_receive(:restart)
+    @provider.should_receive(:callback).with(:after_restart, nil)
+    @provider.should_receive(:cleanup!)
+    @provider.deploy
+  end
+
+  it "should not deploy if there is already a deploy at release_path, and it is the current release" do
+    @provider.stub!(:all_releases).and_return([@expected_release_dir])
+    @provider.stub!(:current_release?).with(@expected_release_dir).and_return(true)
+    @provider.should_not_receive(:deploy)
+    @provider.run_action(:deploy)
+  end
+
+  it "should call action_rollback if there is already a deploy of this revision at release_path, and it is not the current release" do
+    @provider.stub!(:all_releases).and_return([@expected_release_dir, "102021"])
+    @provider.stub!(:current_release?).with(@expected_release_dir).and_return(false)
+    @provider.should_receive(:rollback_to).with(@expected_release_dir)
+    @provider.should_receive(:current_release?)
+    @provider.run_action(:deploy)
+  end
+
+  it "calls deploy when deploying a new release" do
+    @provider.stub!(:all_releases).and_return([])
+    @provider.should_receive(:deploy)
+    @provider.run_action(:deploy)
+  end
+
+  it "runs action svn_force_export when new_resource.svn_force_export is true" do
+    @resource.svn_force_export true
+    @provider.scm_provider.should_receive(:run_action).with(:force_export)
+    @provider.update_cached_repo
+  end
+
+  it "Removes the old release before deploying when force deploying over it" do
+    @provider.stub!(:all_releases).and_return([@expected_release_dir])
+    FileUtils.should_receive(:rm_rf).with(@expected_release_dir)
+    @provider.should_receive(:deploy)
+    @provider.run_action(:force_deploy)
+  end
+
+  it "deploys as normal when force deploying and there's no prior release at the same path" do
+    @provider.stub!(:all_releases).and_return([])
+    @provider.should_receive(:deploy)
+    @provider.run_action(:force_deploy)
+  end
+
+  it "dont care by default if error happens on deploy" do
+    @provider.stub!(:all_releases).and_return(['previous_release'])
+    @provider.stub!(:deploy).and_return{ raise "Unexpected error" }
+    @provider.stub!(:previous_release_path).and_return('previous_release')
+    @provider.should_not_receive(:rollback)
+    lambda {
+      @provider.run_action(:deploy)
+    }.should raise_exception(RuntimeError, "Unexpected error")
+  end
+
+  it "rollbacks to previous release if error happens on deploy" do
+    @resource.rollback_on_error true
+    @provider.stub!(:all_releases).and_return(['previous_release'])
+    @provider.stub!(:deploy).and_return{ raise "Unexpected error" }
+    @provider.stub!(:previous_release_path).and_return('previous_release')
+    @provider.should_receive(:rollback)
+    lambda {
+      @provider.run_action(:deploy)
+    }.should raise_exception(RuntimeError, "Unexpected error")
+  end
+
+  describe "on systems without broken Dir.glob results" do
+    it "sets the release path to the penultimate release when one is not specified, symlinks, and rm's the last release on rollback" do
+      @provider.stub!(:release_path).and_return("/my/deploy/dir/releases/3")
+      all_releases = ["/my/deploy/dir/releases/1", "/my/deploy/dir/releases/2", "/my/deploy/dir/releases/3", "/my/deploy/dir/releases/4", "/my/deploy/dir/releases/5"]
+      Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+      @provider.should_receive(:symlink)
+      FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/4")
+      FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/5")
+      @provider.run_action(:rollback)
+      @provider.release_path.should eql("/my/deploy/dir/releases/3")
+    end
+
+    it "sets the release path to the specified release, symlinks, and rm's any newer releases on rollback" do
+      @provider.unstub!(:release_path)
+      all_releases = ["/my/deploy/dir/releases/20040815162342", "/my/deploy/dir/releases/20040700000000",
+                      "/my/deploy/dir/releases/20040600000000", "/my/deploy/dir/releases/20040500000000"].sort!
+      Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+      @provider.should_receive(:symlink)
+      FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+      @provider.run_action(:rollback)
+      @provider.release_path.should eql("/my/deploy/dir/releases/20040700000000")
+    end
+
+    it "sets the release path to the penultimate release, symlinks, and rm's the last release on rollback" do
+      @provider.unstub!(:release_path)
+      all_releases = [ "/my/deploy/dir/releases/20040815162342",
+                       "/my/deploy/dir/releases/20040700000000",
+                       "/my/deploy/dir/releases/20040600000000",
+                       "/my/deploy/dir/releases/20040500000000"]
+      Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+      @provider.should_receive(:symlink)
+      FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+      @provider.run_action(:rollback)
+      @provider.release_path.should eql("/my/deploy/dir/releases/20040700000000")
+    end
+
+    describe "if there are no releases to fallback to" do
+
+      it "an exception is raised when there is only 1 release" do
+        #@provider.unstub!(:release_path) -- unstub the release path on top to feed our own release path
+        all_releases = [ "/my/deploy/dir/releases/20040815162342"]
+        Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+        #@provider.should_receive(:symlink)
+        #FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+        #@provider.run_action(:rollback)
+        #@provider.release_path.should eql(NIL) -- no check needed since assertions will fail
+        lambda {
+          @provider.run_action(:rollback)
+        }.should raise_exception(RuntimeError, "There is no release to rollback to!")
+      end
+
+      it "an exception is raised when there are no releases" do
+        all_releases = []
+        Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+        lambda {
+          @provider.run_action(:rollback)
+        }.should raise_exception(RuntimeError, "There is no release to rollback to!")
+      end
+    end
+  end
+
+  describe "CHEF-628: on systems with broken Dir.glob results" do
+    it "sets the release path to the penultimate release, symlinks, and rm's the last release on rollback" do
+      @provider.unstub!(:release_path)
+      all_releases = [ "/my/deploy/dir/releases/20040500000000",
+                       "/my/deploy/dir/releases/20040600000000",
+                       "/my/deploy/dir/releases/20040700000000",
+                       "/my/deploy/dir/releases/20040815162342" ]
+      Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+      @provider.should_receive(:symlink)
+      FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342")
+      @provider.run_action(:rollback)
+      @provider.release_path.should eql("/my/deploy/dir/releases/20040700000000")
+    end
+  end
+
+  it "raises a runtime error when there's no release to rollback to" do
+    all_releases = []
+    Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+    lambda {@provider.run_action(:rollback)}.should raise_error(RuntimeError)
+  end
+
+  it "runs the new resource collection in the runner during a callback" do
+    @runner = mock("Runner")
+    Chef::Runner.stub!(:new).and_return(@runner)
+    @runner.should_receive(:converge)
+    callback_code = Proc.new { :noop }
+    @provider.callback(:whatevs, callback_code)
+  end
+
+  it "loads callback files from the release/ dir if the file exists" do
+    foo_callback = @expected_release_dir + "/deploy/foo.rb"
+    ::File.should_receive(:exist?).with(foo_callback).once.and_return(true)
+    ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+    @provider.should_receive(:from_file).with(foo_callback)
+    @provider.callback(:foo, "deploy/foo.rb")
+  end
+
+  it "raises a runtime error if a callback file is explicitly specified but does not exist" do
+    baz_callback =   "/deploy/baz.rb"
+    ::File.should_receive(:exist?).with("#{@expected_release_dir}/#{baz_callback}").and_return(false)
+    @resource.before_migrate  baz_callback
+    @provider.define_resource_requirements
+    @provider.action = :deploy
+    lambda {@provider.process_resource_requirements}.should raise_error(RuntimeError)
+  end
+
+  it "runs a default callback if the callback code is nil" do
+    bar_callback = @expected_release_dir + "/deploy/bar.rb"
+    ::File.should_receive(:exist?).with(bar_callback).and_return(true)
+    ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+    @provider.should_receive(:from_file).with(bar_callback)
+    @provider.callback(:bar, nil)
+  end
+
+  it "skips an eval callback if the file doesn't exist" do
+    barbaz_callback = @expected_release_dir + "/deploy/barbaz.rb"
+    ::File.should_receive(:exist?).with(barbaz_callback).and_return(false)
+    ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+    @provider.should_not_receive(:from_file)
+    @provider.callback(:barbaz, nil)
+  end
+
+  # CHEF-3449 #converge_by is called in #recipe_eval and must happen in sequence
+  # with the other calls to #converge_by to keep the train on the tracks
+  it "evaluates a callback file before the corresponding step" do
+    @provider.should_receive(:verify_directories_exist)
+    @provider.should_receive(:update_cached_repo)
+    @provider.should_receive(:enforce_ownership)
+    @provider.should_receive(:copy_cached_repo)
+    @provider.should_receive(:install_gems)
+    @provider.should_receive(:enforce_ownership)
+    @provider.should_receive(:converge_by).ordered # before_migrate
+    @provider.should_receive(:migrate).ordered
+    @provider.should_receive(:converge_by).ordered # before_symlink
+    @provider.should_receive(:symlink).ordered
+    @provider.should_receive(:converge_by).ordered # before_restart
+    @provider.should_receive(:restart).ordered
+    @provider.should_receive(:converge_by).ordered # after_restart
+    @provider.should_receive(:cleanup!)
+    @provider.deploy
+  end
+
+  it "gets a SCM provider as specified by its resource" do
+    @provider.scm_provider.should be_an_instance_of(Chef::Provider::Git)
+    @provider.scm_provider.new_resource.destination.should eql("/my/deploy/dir/shared/cached-copy")
+  end
+
+  it "syncs the cached copy of the repo" do
+    @provider.scm_provider.should_receive(:run_action).with(:sync)
+    @provider.update_cached_repo
+  end
+
+  it "makes a copy of the cached repo in releases dir" do
+    FileUtils.should_receive(:mkdir_p).with("/my/deploy/dir/releases")
+    FileUtils.should_receive(:cp_r).with("/my/deploy/dir/shared/cached-copy/.", @expected_release_dir, :preserve => true)
+    @provider.copy_cached_repo
+  end
+
+  it "calls the internal callback :release_created when cleaning up the releases" do
+    FileUtils.stub!(:mkdir_p)
+    FileUtils.stub!(:cp_r)
+    @provider.should_receive(:release_created)
+    @provider.cleanup!
+  end
+
+  it "chowns the whole release dir to user and group specified in the resource" do
+    @resource.user "foo"
+    @resource.group "bar"
+    FileUtils.should_receive(:chown_R).with("foo", "bar", "/my/deploy/dir")
+    @provider.enforce_ownership
+  end
+
+  it "skips the migration when resource.migrate => false but runs symlinks before migration" do
+    @resource.migrate false
+    @provider.should_not_receive :run_command
+    @provider.should_receive :run_symlinks_before_migrate
+    @provider.migrate
+  end
+
+  it "links the database.yml and runs resource.migration_command when resource.migrate #=> true" do
+    @resource.migrate true
+    @resource.migration_command "migration_foo"
+    @resource.user "deployNinja"
+    @resource.group "deployNinjas"
+    @resource.environment "RAILS_ENV" => "production"
+    FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml")
+    @provider.should_receive(:enforce_ownership)
+
+    STDOUT.stub!(:tty?).and_return(true)
+    Chef::Log.stub!(:info?).and_return(true)
+    @provider.should_receive(:run_command).with(:command => "migration_foo", :cwd => @expected_release_dir,
+                                                :user => "deployNinja", :group => "deployNinjas",
+                                                :log_level => :info, :live_stream => STDOUT,
+                                                :log_tag => "deploy[/my/deploy/dir]",
+                                                :environment => {"RAILS_ENV"=>"production"})
+    @provider.migrate
+  end
+
+  it "purges the current release's /log /tmp/pids/ and /public/system directories" do
+    FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/log")
+    FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/tmp/pids")
+    FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/public/system")
+    @provider.purge_tempfiles_from_current_release
+  end
+
+  it "symlinks temporary files and logs from the shared dir into the current release" do
+    FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/system")
+    FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/pids")
+    FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/log")
+    FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/tmp")
+    FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/public")
+    FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/config")
+    FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/system", @expected_release_dir + "/public/system")
+    FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/pids", @expected_release_dir + "/tmp/pids")
+    FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/log", @expected_release_dir + "/log")
+    FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml")
+    @provider.should_receive(:enforce_ownership)
+    @provider.link_tempfiles_to_current_release
+  end
+
+  it "symlinks the current release dir into production" do
+    FileUtils.should_receive(:rm_f).with("/my/deploy/dir/current")
+    FileUtils.should_receive(:ln_sf).with(@expected_release_dir, "/my/deploy/dir/current")
+    @provider.should_receive(:enforce_ownership)
+    @provider.link_current_release_to_production
+  end
+
+  context "with a customized app layout" do
+
+    before do
+      @resource.purge_before_symlink(%w{foo bar})
+      @resource.create_dirs_before_symlink(%w{baz qux})
+      @resource.symlinks "foo/bar" => "foo/bar", "baz" => "qux/baz"
+      @resource.symlink_before_migrate "radiohead/in_rainbows.yml" => "awesome"
+    end
+
+    it "purges the purge_before_symlink directories" do
+      FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/foo")
+      FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/bar")
+      @provider.purge_tempfiles_from_current_release
+    end
+
+    it "symlinks files from the shared directory to the current release directory" do
+      FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/baz")
+      FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/qux")
+      FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/foo/bar")
+      FileUtils.stub(:mkdir_p).with(@resource.shared_path + "/baz")
+      FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/foo/bar", @expected_release_dir + "/foo/bar")
+      FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/baz", @expected_release_dir + "/qux/baz")
+      FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/radiohead/in_rainbows.yml", @expected_release_dir + "/awesome")
+      @provider.should_receive(:enforce_ownership)
+      @provider.link_tempfiles_to_current_release
+    end
+
+  end
+
+  it "does nothing for restart if restart_command is empty" do
+    @provider.should_not_receive(:run_command)
+    @provider.restart
+  end
+
+  it "runs the restart command in the current application dir when the resource has a restart_command" do
+    @resource.restart_command "restartcmd"
+    @provider.should_receive(:run_command).with(:command => "restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug)
+    @provider.restart
+  end
+
+  it "lists all available releases" do
+    all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+                    "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000"].sort!
+    Dir.should_receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases)
+    @provider.all_releases.should eql(all_releases)
+  end
+
+  it "removes all but the 5 newest releases" do
+    all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+                    "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000",
+                    "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000",
+                    "/my/deploy/dir/20040200000000", "/my/deploy/dir/20040100000000"].sort!
+    @provider.stub!(:all_releases).and_return(all_releases)
+    FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040100000000")
+    FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040200000000")
+    FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040300000000")
+    @provider.cleanup!
+  end
+
+  it "removes all but a certain number of releases when the resource has a keep_releases" do
+    @resource.keep_releases 7
+    all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+                    "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000",
+                    "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000",
+                    "/my/deploy/dir/20040200000000", "/my/deploy/dir/20040100000000"].sort!
+    @provider.stub!(:all_releases).and_return(all_releases)
+    FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040100000000")
+    @provider.cleanup!
+  end
+
+  it "fires a callback for :release_deleted when deleting an old release" do
+    all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000",
+                    "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000",
+                    "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000"].sort!
+    @provider.stub!(:all_releases).and_return(all_releases)
+    FileUtils.stub!(:rm_rf)
+    @provider.should_receive(:release_deleted).with("/my/deploy/dir/20040300000000")
+    @provider.cleanup!
+  end
+
+  it "puts resource.to_hash in @configuration for backwards compat with capistano-esque deploy hooks" do
+    @provider.instance_variable_get(:@configuration).should == @resource.to_hash
+  end
+
+  it "sets @configuration[:environment] to the value of RAILS_ENV for backwards compat reasons" do
+    resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+    resource.environment "production"
+    provider = Chef::Provider::Deploy.new(resource, @run_context)
+    provider.instance_variable_get(:@configuration)[:environment].should eql("production")
+  end
+
+  it "shouldn't give a no method error on migrate if the environment is nil" do
+    @provider.stub!(:enforce_ownership)
+    @provider.stub!(:run_symlinks_before_migrate)
+    @provider.stub!(:run_command)
+    @provider.migrate
+
+  end
+
+  context "using inline recipes for callbacks" do
+
+    it "runs an inline recipe with the provided block for :callback_name == {:recipe => &block} " do
+      snitch = nil
+      recipe_code = Proc.new {snitch = 42}
+      #@provider.should_receive(:instance_eval).with(&recipe_code)
+      @provider.callback(:whateverz, recipe_code)
+      snitch.should == 42
+    end
+
+    it "loads a recipe file from the specified path and from_file evals it" do
+      ::File.should_receive(:exist?).with(@expected_release_dir + "/chefz/foobar_callback.rb").once.and_return(true)
+      ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield
+      @provider.should_receive(:from_file).with(@expected_release_dir + "/chefz/foobar_callback.rb")
+      @provider.callback(:whateverz, "chefz/foobar_callback.rb")
+    end
+
+    it "instance_evals a block/proc for restart command" do
+      snitch = nil
+      restart_cmd = Proc.new {snitch = 42}
+      @resource.restart(&restart_cmd)
+      @provider.restart
+      snitch.should == 42
+    end
+
+  end
+
+  describe "API bridge to capistrano" do
+    it "defines sudo as a forwarder to execute" do
+      @provider.should_receive(:execute).with("the moon, fool")
+      @provider.sudo("the moon, fool")
+    end
+
+    it "defines run as a forwarder to execute, setting the user, group, cwd and environment to new_resource.user" do
+      mock_execution = mock("Resource::Execute")
+      @provider.should_receive(:execute).with("iGoToHell4this").and_return(mock_execution)
+      @resource.user("notCoolMan")
+      @resource.group("Ggroup")
+      @resource.environment("APP_ENV" => 'staging')
+      @resource.deploy_to("/my/app")
+      mock_execution.should_receive(:user).with("notCoolMan")
+      mock_execution.should_receive(:group).with("Ggroup")
+      mock_execution.should_receive(:cwd){|*args|
+        if args.empty?
+          nil
+        else
+          args.size.should == 1
+          args.first.should == @provider.release_path
+        end
+      }.twice
+      mock_execution.should_receive(:environment){ |*args|
+        if args.empty?
+          nil
+        else
+          args.size.should == 1
+          args.first.should == {"APP_ENV" => "staging"}
+        end
+      }.twice
+      @provider.run("iGoToHell4this")
+
+    end
+
+    it "defines run as a forwarder to execute, setting cwd and environment but not override" do
+      mock_execution = mock("Resource::Execute")
+      @provider.should_receive(:execute).with("iGoToHell4this").and_return(mock_execution)
+      @resource.user("notCoolMan")
+      mock_execution.should_receive(:user).with("notCoolMan")
+      mock_execution.should_receive(:cwd).with(no_args()).and_return("/some/value")
+      mock_execution.should_receive(:environment).with(no_args()).and_return({})
+      @provider.run("iGoToHell4this")
+    end
+
+
+    it "converts sudo and run to exec resources in hooks" do
+      runner = mock("tehRunner")
+      Chef::Runner.stub!(:new).and_return(runner)
+
+      snitch = nil
+      @resource.user("tehCat")
+
+      callback_code = Proc.new do
+        snitch = 42
+        temp_collection = self.resource_collection
+        run("tehMice")
+        snitch = temp_collection.lookup("execute[tehMice]")
+      end
+
+      runner.should_receive(:converge)
+      #
+      @provider.callback(:phony, callback_code)
+      snitch.should be_an_instance_of(Chef::Resource::Execute)
+      snitch.user.should == "tehCat"
+    end
+  end
+
+  describe "installing gems from a gems.yml" do
+
+    before do
+      ::File.stub!(:exist?).with("#{@expected_release_dir}/gems.yml").and_return(true)
+      @gem_list = [{:name=>"eventmachine", :version=>"0.12.9"}]
+    end
+
+    it "reads a gems.yml file, creating gem providers for each with action :upgrade" do
+      IO.should_receive(:read).with("#{@expected_release_dir}/gems.yml").and_return("cookie")
+      YAML.should_receive(:load).with("cookie").and_return(@gem_list)
+
+      gems = @provider.send(:gem_packages)
+
+      gems.map { |g| g.action }.should == [[:install]]
+      gems.map { |g| g.name }.should == %w{eventmachine}
+      gems.map { |g| g.version }.should == %w{0.12.9}
+    end
+
+    it "takes a list of gem providers converges them" do
+      IO.stub!(:read)
+      YAML.stub!(:load).and_return(@gem_list)
+      expected_gem_resources = @provider.send(:gem_packages).map { |r| [r.name, r.version] }
+      gem_runner = @provider.send(:gem_resource_collection_runner)
+      # no one has heard of defining == to be meaningful so I have use this monstrosity
+      actual = gem_runner.run_context.resource_collection.all_resources.map { |r| [r.name, r.version] }
+      actual.should == expected_gem_resources
+    end
+
+  end
+
+end
diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb
new file mode 100644
index 0000000..61946d2
--- /dev/null
+++ b/spec/unit/provider/directory_spec.rb
@@ -0,0 +1,188 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 '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)
+    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 "scanning file security metadata on windows" do
+    before do
+    end
+
+    it "describes the directory's access rights" do
+      pending
+    end
+  end
+
+  describe "scanning file security metadata on unix" do
+    before do
+      Chef::Platform.stub!(:windows?).and_return(false)
+    end
+    let(:mock_stat) do
+      cstats = mock("stats")
+      cstats.stub!(:uid).and_return(500)
+      cstats.stub!(:gid).and_return(500)
+      cstats.stub!(:mode).and_return(0755)
+      cstats
+    end
+
+    it "describes the access mode as a String of octal integers" do
+      File.stub!(:exists?).and_return(true)
+      File.should_receive(:stat).and_return(mock_stat)
+      @directory.load_current_resource
+      @directory.current_resource.mode.should == "0755"
+    end
+
+    context "when user and group are specified with UID/GID" do
+      it "describes the current owner and group as UID and GID" do
+        File.stub!(:exists?).and_return(true)
+        File.should_receive(:stat).and_return(mock_stat)
+        @directory.load_current_resource
+        @directory.current_resource.path.should eql(@new_resource.path)
+        @directory.current_resource.owner.should eql(500)
+        @directory.current_resource.group.should eql(500)
+      end
+    end
+
+    context "when user/group are specified with user/group names" do
+    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"
+
+    File.should_receive(:exists?).at_least(:once).and_return(false)
+    File.should_receive(:directory?).with("/tmp").and_return(true)
+    Dir.should_receive(:mkdir).with(@new_resource.path).once.and_return(true)
+
+    @directory.should_receive(:do_acl_changes)
+    @directory.stub!(:do_selinux)
+    @directory.run_action(:create)
+    @directory.new_resource.should be_updated
+  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
+    lambda { @directory.run_action(:create) }.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+  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
+    File.should_receive(:exists?).with(@new_resource.path).ordered.and_return(false)
+
+    File.should_receive(:exists?).with('/path/to').ordered.and_return(false)
+    File.should_receive(:exists?).with('/path').ordered.and_return(true)
+    File.should_receive(:writable?).with('/path').ordered.and_return(true)
+    File.should_receive(:exists?).with(@new_resource.path).ordered.and_return(false)
+
+    FileUtils.should_receive(:mkdir_p).with(@new_resource.path).and_return(true)
+    @directory.should_receive(:do_acl_changes)
+    @directory.stub!(:do_selinux)
+    @directory.run_action(:create)
+    @new_resource.should be_updated
+  end
+
+
+  it "should raise an error when creating a directory when parent directory is a file" do
+    File.should_receive(:directory?).and_return(false)
+    Dir.should_not_receive(:mkdir).with(@new_resource.path)
+    lambda { @directory.run_action(:create) }.should raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+    @directory.new_resource.should_not be_updated
+  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"
+    File.should_receive(:directory?).at_least(:once).and_return(true)
+    File.should_receive(:writable?).with("/tmp").and_return(true)
+    File.should_receive(:exists?).at_least(:once).and_return(true)
+    Dir.should_not_receive(:mkdir).with(@new_resource.path)
+    @directory.should_receive(:do_acl_changes)
+    @directory.run_action(:create)
+  end
+
+  it "should delete the directory if it exists, and is writable with action_delete" do
+    File.should_receive(:directory?).and_return(true)
+    File.should_receive(:writable?).once.and_return(true)
+    Dir.should_receive(:delete).with(@new_resource.path).once.and_return(true)
+    @directory.run_action(:delete)
+  end
+
+  it "should raise an exception if it cannot delete the directory due to bad permissions" do
+    File.stub!(:exists?).and_return(true)
+    File.stub!(:writable?).and_return(false)
+    lambda {  @directory.run_action(:delete) }.should raise_error(RuntimeError)
+  end
+
+  it "should take no action when deleting a target directory that does not exist" do
+    @new_resource.path "/an/invalid/path"
+    File.stub!(:exists?).and_return(false)
+    Dir.should_not_receive(:delete).with(@new_resource.path)
+    @directory.run_action(:delete)
+    @directory.new_resource.should_not be_updated
+  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"
+    File.stub!(:exists?).and_return(true)
+    File.should_receive(:directory?).and_return(false)
+    Dir.should_not_receive(:delete).with(@new_resource.path)
+    lambda { @directory.run_action(:delete) }.should raise_error(RuntimeError)
+    @directory.new_resource.should_not be_updated
+  end
+
+  def stub_file_cstats
+    cstats = mock("stats")
+    cstats.stub!(:uid).and_return(500)
+    cstats.stub!(:gid).and_return(500)
+    cstats.stub!(:mode).and_return(0755)
+    # File.stat is called in:
+    # - Chef::Provider::File.load_current_resource_attrs
+    # - Chef::ScanAccessControl via Chef::Provider::File.setup_acl
+    File.stub!(:stat).and_return(cstats)
+  end
+end
diff --git a/spec/unit/provider/env_spec.rb b/spec/unit/provider/env_spec.rb
new file mode 100644
index 0000000..673ba97
--- /dev/null
+++ b/spec/unit/provider/env_spec.rb
@@ -0,0 +1,232 @@
+#
+# Author:: Doug MacEachern (<dougm at vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Env do
+
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Env.new("FOO")
+    @new_resource.value("bar")
+    @provider = Chef::Provider::Env.new(@new_resource, @run_context)
+  end
+
+  it "assumes the key_name exists by default" do
+    @provider.key_exists.should be_true
+  end
+
+  describe "when loading the current status" do
+    before do
+      #@current_resource = @new_resource.clone
+      #Chef::Resource::Env.stub!(:new).and_return(@current_resource)
+      @provider.current_resource = @current_resource
+      @provider.stub!(:env_value).with("FOO").and_return("bar")
+      @provider.stub!(:env_key_exists).and_return(true)
+    end
+
+    it "should create a current resource with the same name as the new resource" do
+      @provider.load_current_resource
+      @provider.new_resource.name.should == "FOO"
+    end
+
+    it "should set the key_name to the key name of the new resource" do
+      @provider.load_current_resource
+      @provider.current_resource.key_name.should == "FOO"
+    end
+
+    it "should check if the key_name exists" do
+      @provider.should_receive(:env_key_exists).with("FOO").and_return(true)
+      @provider.load_current_resource
+      @provider.key_exists.should be_true
+    end
+
+    it "should flip the value of exists if the key does not exist" do
+      @provider.should_receive(:env_key_exists).with("FOO").and_return(false)
+      @provider.load_current_resource
+      @provider.key_exists.should be_false
+    end
+
+    it "should return the current resource" do
+      @provider.load_current_resource.should be_a_kind_of(Chef::Resource::Env)
+    end
+  end
+
+  describe "action_create" do
+    before do
+      @provider.key_exists = false
+      @provider.stub!(:create_env).and_return(true)
+      @provider.stub!(:modify_env).and_return(true)
+    end
+
+    it "should call create_env if the key does not exist" do
+      @provider.should_receive(:create_env).and_return(true)
+      @provider.action_create
+    end
+
+    it "should set the new_resources updated flag when it creates the key" do
+      @provider.action_create
+      @new_resource.should be_updated
+    end
+
+    it "should check to see if the values are the same if the key exists" do
+      @provider.key_exists = true
+      @provider.should_receive(:compare_value).and_return(false)
+      @provider.action_create
+    end
+
+    it "should call modify_env if the key exists and values are not equal" do
+      @provider.key_exists = true
+      @provider.stub!(:compare_value).and_return(true)
+      @provider.should_receive(:modify_env).and_return(true)
+      @provider.action_create
+    end
+
+    it "should set the new_resources updated flag when it updates an existing value" do
+      @provider.key_exists = true
+      @provider.stub!(:compare_value).and_return(true)
+      @provider.stub!(:modify_env).and_return(true)
+      @provider.action_create
+      @new_resource.should be_updated
+    end
+  end
+
+  describe "action_delete" do
+    before(:each) do
+      @provider.current_resource = @current_resource
+      @provider.key_exists = false
+      @provider.stub!(:delete_element).and_return(false)
+      @provider.stub!(:delete_env).and_return(true)
+    end
+
+    it "should not call delete_env if the key does not exist" do
+      @provider.should_not_receive(:delete_env)
+      @provider.action_delete
+    end
+
+    it "should not call delete_element if the key does not exist" do
+      @provider.should_not_receive(:delete_element)
+      @provider.action_delete
+    end
+
+    it "should call delete_env if the key exists" do
+      @provider.key_exists = true
+      @provider.should_receive(:delete_env)
+      @provider.action_delete
+    end
+
+    it "should set the new_resources updated flag to true if the key is deleted" do
+      @provider.key_exists = true
+      @provider.action_delete
+      @new_resource.should be_updated
+    end
+  end
+
+  describe "action_modify" do
+    before(:each) do
+      @provider.current_resource = @current_resource
+      @provider.key_exists = true
+      @provider.stub!(:modify_env).and_return(true)
+    end
+
+    it "should call modify_group if the key exists and values are not equal" do
+      @provider.should_receive(:compare_value).and_return(true)
+      @provider.should_receive(:modify_env).and_return(true)
+      @provider.action_modify
+    end
+
+    it "should set the new resources updated flag to true if modify_env is called" do
+      @provider.stub!(:compare_value).and_return(true)
+      @provider.stub!(:modify_env).and_return(true)
+      @provider.action_modify
+      @new_resource.should be_updated
+    end
+
+    it "should not call modify_env if the key exists but the values are equal" do
+      @provider.should_receive(:compare_value).and_return(false)
+      @provider.should_not_receive(:modify_env)
+      @provider.action_modify
+    end
+
+    it "should raise a Chef::Exceptions::Env if the key doesn't exist" do
+      @provider.key_exists = false
+      lambda { @provider.action_modify }.should raise_error(Chef::Exceptions::Env)
+    end
+  end
+
+  describe "delete_element" do
+    before(:each) do
+      @current_resource = Chef::Resource::Env.new("FOO")
+
+      @new_resource.delim ";"
+      @new_resource.value "C:/bar/bin"
+
+      @current_resource.value "C:/foo/bin;C:/bar/bin"
+      @provider.current_resource = @current_resource
+    end
+
+    it "should return true if the element is not found" do
+      @new_resource.stub!(:value).and_return("C:/baz/bin")
+      @provider.delete_element.should eql(true)
+    end
+
+    it "should return false if the delim not defined" do
+      @new_resource.stub!(:delim).and_return(nil)
+      @provider.delete_element.should eql(false)
+    end
+
+    it "should return true if the element is deleted" do
+      @new_resource.value("C:/foo/bin")
+      @provider.should_receive(:create_env)
+      @provider.delete_element.should eql(true)
+      @new_resource.should be_updated
+    end
+  end
+
+  describe "compare_value" do
+    before(:each) do
+      @new_resource.value("C:/bar")
+      @current_resource = @new_resource.clone
+      @provider.current_resource = @current_resource
+    end
+
+    it "should return false if the values are equal" do
+      @provider.compare_value.should be_false
+    end
+
+    it "should return true if the values not are equal" do
+      @new_resource.value("C:/elsewhere")
+      @provider.compare_value.should be_true
+    end
+
+    it "should return false if the current value contains the element" do
+      @new_resource.delim(";")
+      @current_resource.value("C:/bar;C:/foo;C:/baz")
+
+      @provider.compare_value.should be_false
+    end
+
+    it "should return true if the current value does not contain the element" do
+      @new_resource.delim(";")
+      @current_resource.value("C:/biz;C:/foo/bin;C:/baz")
+      @provider.compare_value.should be_true
+    end
+  end
+end
diff --git a/spec/unit/provider/erl_call_spec.rb b/spec/unit/provider/erl_call_spec.rb
new file mode 100644
index 0000000..fa85b6c
--- /dev/null
+++ b/spec/unit/provider/erl_call_spec.rb
@@ -0,0 +1,86 @@
+#
+# 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");
+# 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::ErlCall do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::ErlCall.new("test", @node)
+    @new_resource.code("io:format(\"burritos\", []).")
+    @new_resource.node_name("chef at localhost")
+    @new_resource.name("test")
+
+    @provider = Chef::Provider::ErlCall.new(@new_resource, @run_context)
+
+    @provider.stub!(:popen4).and_return(@status)
+    @stdin = StringIO.new
+    @stdout = StringIO.new('{ok, woohoo}')
+    @stderr = StringIO.new
+    @pid = 2342999
+  end
+
+  it "should return a Chef::Provider::ErlCall object" do
+    provider = Chef::Provider::ErlCall.new(@new_resource, @run_context)
+    provider.should be_a_kind_of(Chef::Provider::ErlCall)
+  end
+
+  it "should return true" do
+    @provider.load_current_resource.should eql(true)
+  end
+
+  describe "when running a distributed erl call resource" do
+    before do
+      @new_resource.cookie("nomnomnom")
+      @new_resource.distributed(true)
+      @new_resource.name_type("sname")
+    end
+
+    it "should write to stdin of the erl_call command" do
+      expected_cmd = "erl_call -e -s -sname chef at localhost -c nomnomnom"
+      @provider.should_receive(:popen4).with(expected_cmd, :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr])
+      Process.should_receive(:wait).with(@pid)
+
+      @provider.action_run
+
+      @stdin.string.should == "#{@new_resource.code}\n"
+    end
+  end
+
+  describe "when running a local erl call resource" do
+    before do
+      @new_resource.cookie(nil)
+      @new_resource.distributed(false)
+      @new_resource.name_type("name")
+    end
+
+    it "should write to stdin of the erl_call command" do
+      @provider.should_receive(:popen4).with("erl_call -e  -name chef at localhost ", :waitlast => true).and_return([@pid, @stdin, @stdout, @stderr])
+      Process.should_receive(:wait).with(@pid)
+
+      @provider.action_run
+
+      @stdin.string.should == "#{@new_resource.code}\n"
+    end
+  end
+
+end
+
diff --git a/spec/unit/provider/execute_spec.rb b/spec/unit/provider/execute_spec.rb
new file mode 100644
index 0000000..0d108f5
--- /dev/null
+++ b/spec/unit/provider/execute_spec.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Prajakta Purohit (<prajakta at opscode.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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+#require 'spec_helper'
+
+describe Chef::Provider::Execute do
+  before do
+    @node = Chef::Node.new
+    @cookbook_collection = Chef::CookbookCollection.new([])
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    @new_resource = Chef::Resource::Execute.new("foo_resource", @run_context)
+    @new_resource.timeout 3600
+    @new_resource.returns 0
+    @new_resource.creates "/foo_resource"
+    @provider = Chef::Provider::Execute.new(@new_resource, @run_context)
+    @current_resource = Chef::Resource::Ifconfig.new("foo_resource", @run_context)
+    @provider.current_resource = @current_resource
+    Chef::Log.level = :info
+    # FIXME: There should be a test for how STDOUT.tty? changes the live_stream option being passed
+    STDOUT.stub!(:tty?).and_return(true)
+  end
+
+
+  it "should execute foo_resource" do
+    @provider.stub!(:load_current_resource)
+    opts = {}
+    opts[:timeout] = @new_resource.timeout
+    opts[:returns] = @new_resource.returns
+    opts[:log_level] = :info
+    opts[:log_tag] = @new_resource.to_s
+    opts[:live_stream] = STDOUT
+    @provider.should_receive(:shell_out!).with(@new_resource.command, opts)
+    Chef::Log.should_not_receive(:warn)
+
+    @provider.run_action(:run)
+    @new_resource.should be_updated
+  end
+
+  it "should do nothing if the sentinel file exists" do
+    @provider.stub!(:load_current_resource)
+    File.should_receive(:exists?).with(@new_resource.creates).and_return(true)
+    @provider.should_not_receive(:shell_out!)
+    Chef::Log.should_not_receive(:warn)
+
+    @provider.run_action(:run)
+    @new_resource.should_not be_updated
+  end
+
+  it "should respect cwd options for 'creates'" do
+    @new_resource.cwd "/tmp"
+    @new_resource.creates "foo_resource"
+    @provider.stub!(:load_current_resource)
+    File.should_receive(:exists?).with(@new_resource.creates).and_return(false)
+    File.should_receive(:exists?).with(File.join("/tmp", @new_resource.creates)).and_return(true)
+    Chef::Log.should_not_receive(:warn)
+    @provider.should_not_receive(:shell_out!)
+
+    @provider.run_action(:run)
+    @new_resource.should_not be_updated
+  end
+
+  it "should warn if user specified relative path without cwd" do
+    @new_resource.creates "foo_resource"
+    @provider.stub!(:load_current_resource)
+    Chef::Log.should_receive(:warn).with(/relative path/)
+    File.should_receive(:exists?).with(@new_resource.creates).and_return(true)
+    @provider.should_not_receive(:shell_out!)
+
+    @provider.run_action(:run)
+    @new_resource.should_not be_updated
+  end
+end
+
diff --git a/spec/unit/provider/file/content_spec.rb b/spec/unit/provider/file/content_spec.rb
new file mode 100644
index 0000000..9262a35
--- /dev/null
+++ b/spec/unit/provider/file/content_spec.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::File::Content do
+
+  #
+  # mock setup
+  #
+
+  let(:current_resource) do
+    mock("Chef::Provider::File::Resource (current)")
+  end
+
+  let(:enclosing_directory) {
+    canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates")))
+  }
+  let(:resource_path) {
+    canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt")))
+  }
+
+  let(:new_resource) do
+    mock("Chef::Provider::File::Resource (new)", :name => "seattle.txt", :path => resource_path)
+  end
+
+  let(:run_context) do
+    mock("Chef::RunContext")
+  end
+
+  #
+  # subject
+  #
+  let(:content) do
+    Chef::Provider::File::Content.new(new_resource, current_resource, run_context)
+  end
+
+  describe "when the resource has a content attribute set" do
+
+    before do
+      new_resource.stub!(:content).and_return("Do do do do, do do do do, do do do do, do do do do")
+    end
+
+    it "returns a tempfile" do
+      content.tempfile.should be_a_kind_of(Tempfile)
+    end
+
+    it "the tempfile contents should match the resource contents" do
+      IO.read(content.tempfile.path).should == new_resource.content
+    end
+
+    it "returns a tempfile in the tempdir when :file_staging_uses_destdir is not set" do
+      Chef::Config[:file_staging_uses_destdir] = false
+      content.tempfile.path.start_with?(Dir::tmpdir).should be_true
+      canonicalize_path(content.tempfile.path).start_with?(enclosing_directory).should be_false
+    end
+
+    it "returns a tempfile in the destdir when :file_desployment_uses_destdir is not set" do
+      Chef::Config[:file_staging_uses_destdir] = true
+      content.tempfile.path.start_with?(Dir::tmpdir).should be_false
+      canonicalize_path(content.tempfile.path).start_with?(enclosing_directory).should be_true
+    end
+
+  end
+
+  describe "when the resource does not have a content attribute set" do
+
+    before do
+      new_resource.stub!(:content).and_return(nil)
+    end
+
+    it "should return nil instead of a tempfile" do
+      content.tempfile.should be_nil
+    end
+
+  end
+end
+
diff --git a/spec/unit/provider/file_spec.rb b/spec/unit/provider/file_spec.rb
new file mode 100644
index 0000000..291f510
--- /dev/null
+++ b/spec/unit/provider/file_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2008-2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'support/shared/unit/provider/file'
+
+describe Chef::Provider::File do
+
+  let(:resource) do
+    # need to check for/against mutating state within the new_resource, so don't mock
+    resource = Chef::Resource::File.new("seattle")
+    resource.path(resource_path)
+    resource
+  end
+
+  let(:content) do
+    content = mock('Chef::Provider::File::Content')
+  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(:enclosing_directory) {
+    canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates")))
+  }
+  let(:resource_path) {
+    canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt")))
+  }
+
+  # Subject
+
+  let(:provider) do
+    provider = described_class.new(resource, run_context)
+    provider.stub!(:content).and_return(content)
+    provider
+  end
+
+  it_behaves_like Chef::Provider::File
+end
+
diff --git a/spec/unit/provider/git_spec.rb b/spec/unit/provider/git_spec.rb
new file mode 100644
index 0000000..2bf5593
--- /dev/null
+++ b/spec/unit/provider/git_spec.rb
@@ -0,0 +1,558 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+describe Chef::Provider::Git do
+
+  before(:each) do
+    STDOUT.stub!(:tty?).and_return(true)
+    Chef::Log.level = :info
+
+    @current_resource = Chef::Resource::Git.new("web2.0 app")
+    @current_resource.revision("d35af14d41ae22b19da05d7d03a0bafc321b244c")
+
+    @resource = Chef::Resource::Git.new("web2.0 app")
+    @resource.repository "git://github.com/opscode/chef.git"
+    @resource.destination "/my/deploy/dir"
+    @resource.revision "d35af14d41ae22b19da05d7d03a0bafc321b244c"
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @provider = Chef::Provider::Git.new(@resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  context "determining the revision of the currently deployed checkout" do
+
+    before do
+      @stdout = mock("standard out")
+      @stderr = mock("standard error")
+      @exitstatus = mock("exitstatus")
+    end
+
+    it "sets the current revision to nil if the deploy dir does not exist" do
+      ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(false)
+      @provider.find_current_revision.should be_nil
+    end
+
+    it "determines the current revision when there is one" do
+      ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+      @stdout = "9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\n"
+      @provider.should_receive(:shell_out!).with('git rev-parse HEAD', {:cwd => '/my/deploy/dir', :returns => [0,128]}).and_return(mock("ShellOut result", :stdout => @stdout))
+      @provider.find_current_revision.should eql("9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13")
+    end
+
+    it "gives the current revision as nil when there is no current revision" do
+      ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+      @stderr = "fatal: Not a git repository (or any of the parent directories): .git"
+      @stdout = ""
+      @provider.should_receive(:shell_out!).with('git rev-parse HEAD', :cwd => '/my/deploy/dir', :returns => [0,128]).and_return(mock("ShellOut result", :stdout => "", :stderr => @stderr))
+      @provider.find_current_revision.should be_nil
+    end
+  end
+
+  it "creates a current_resource with the currently deployed revision when a clone exists in the destination dir" do
+    @provider.stub!(:find_current_revision).and_return("681c9802d1c62a45b490786c18f0b8216b309440")
+    @provider.load_current_resource
+    @provider.current_resource.name.should eql(@resource.name)
+    @provider.current_resource.revision.should eql("681c9802d1c62a45b490786c18f0b8216b309440")
+  end
+
+  it "keeps the node and resource passed to it on initialize" do
+    @provider.node.should equal(@node)
+    @provider.new_resource.should equal(@resource)
+  end
+
+  context "resolving revisions to a SHA" do
+
+    before do
+      @git_ls_remote = "git ls-remote \"git://github.com/opscode/chef.git\" "
+    end
+
+    it "returns resource.revision as is if revision is already a full SHA" do
+      @provider.target_revision.should eql("d35af14d41ae22b19da05d7d03a0bafc321b244c")
+    end
+
+    it "converts resource.revision from a tag to a SHA" do
+      @resource.revision "v1.0"
+      @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" +
+                 "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n")
+      @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0*", {:log_tag=>"git[web2.0 app]"}).and_return(mock("ShellOut result", :stdout => @stdout))
+      @provider.target_revision.should eql("503c22a5e41f5ae3193460cca044ed1435029f53")
+    end
+
+    it "converts resource.revision from an annotated tag to the tagged SHA (not SHA of tag)" do
+      @resource.revision "v1.0"
+      @stdout = ("d03c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n" +
+                 "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0\n" +
+                 "663c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/v1.0^{}\n")
+      @provider.should_receive(:shell_out!).with(@git_ls_remote + "v1.0*", {:log_tag=>"git[web2.0 app]"}).and_return(mock("ShellOut result", :stdout => @stdout))
+      @provider.target_revision.should eql("663c22a5e41f5ae3193460cca044ed1435029f53")
+    end
+
+    it "raises an invalid remote reference error if you try to deploy from ``origin'' and assertions are run" do
+      @resource.revision "origin/"
+      @provider.action = :checkout
+      @provider.define_resource_requirements
+      ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+      lambda {@provider.process_resource_requirements}.should raise_error(Chef::Exceptions::InvalidRemoteGitReference)
+    end
+
+    it "raises an unresolvable git reference error if the revision can't be resolved to any revision and assertions are run" do
+      @resource.revision "FAIL, that's the revision I want"
+      @provider.action = :checkout
+      @provider.should_receive(:shell_out!).and_return(mock("ShellOut result", :stdout => "\n"))
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::UnresolvableGitReference)
+    end
+
+    it "does not raise an error if the revision can't be resolved when assertions are not run" do
+      @resource.revision "FAIL, that's the revision I want"
+      @provider.should_receive(:shell_out!).and_return(mock("ShellOut result", :stdout => "\n"))
+      @provider.target_revision.should == nil
+    end
+
+    it "does not raise an error when the revision is valid and assertions are run." do
+      @resource.revision "0.8-alpha"
+      @stdout = "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n"
+      @provider.should_receive(:shell_out!).with(@git_ls_remote + "0.8-alpha*", {:log_tag=>"git[web2.0 app]"}).and_return(mock("ShellOut result", :stdout => @stdout))
+      @provider.action = :checkout
+      ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should_not raise_error(RuntimeError)
+    end
+
+    it "gives the latest HEAD revision SHA if nothing is specified" do
+      @stdout =<<-SHAS
+28af684d8460ba4793eda3e7ac238c864a5d029a\tHEAD
+503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha
+28af684d8460ba4793eda3e7ac238c864a5d029a\trefs/heads/master
+c44fe79bb5e36941ce799cee6b9de3a2ef89afee\trefs/tags/0.5.2
+14534f0e0bf133dc9ff6dbe74f8a0c863ff3ac6d\trefs/tags/0.5.4
+d36fddb4291341a1ff2ecc3c560494e398881354\trefs/tags/0.5.6
+9e5ce9031cbee81015de680d010b603bce2dd15f\trefs/tags/0.6.0
+9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\trefs/tags/0.6.2
+014a69af1cdce619de82afaf6cdb4e6ac658fede\trefs/tags/0.7.0
+fa8097ff666af3ce64761d8e1f1c2aa292a11378\trefs/tags/0.7.2
+44f9be0b33ba5c10027ddb030a5b2f0faa3eeb8d\trefs/tags/0.7.4
+d7b9957f67236fa54e660cc3ab45ffecd6e0ba38\trefs/tags/0.7.8
+b7d19519a1c15f1c1a324e2683bd728b6198ce5a\trefs/tags/0.7.8^{}
+ebc1b392fe7e8f0fbabc305c299b4d365d2b4d9b\trefs/tags/chef-server-package
+SHAS
+      @resource.revision ''
+      @provider.should_receive(:shell_out!).with(@git_ls_remote + "HEAD", {:log_tag=>"git[web2.0 app]"}).and_return(mock("ShellOut result", :stdout => @stdout))
+      @provider.target_revision.should eql("28af684d8460ba4793eda3e7ac238c864a5d029a")
+    end
+  end
+
+  it "responds to :revision_slug as an alias for target_revision" do
+    @provider.should respond_to(:revision_slug)
+  end
+
+  context "with an ssh wrapper" do
+    let(:deploy_user)  { "deployNinja" }
+    let(:wrapper)      { "do_it_this_way.sh" }
+    let(:expected_cmd) { 'git clone  "git://github.com/opscode/chef.git" "/my/deploy/dir"' }
+    let(:default_options) do
+      {
+        :user => deploy_user,
+        :environment => { "GIT_SSH" => wrapper },
+        :log_tag => "git[web2.0 app]"
+      }
+    end
+    before do
+      @resource.user deploy_user
+      @resource.ssh_wrapper wrapper
+    end
+    context "without a timeout set" do
+      it "clones a repo with default git options" do
+        @provider.should_receive(:shell_out!).with(expected_cmd, default_options)
+        @provider.clone
+      end
+    end
+    context "with a timeout set" do
+      let (:seconds) { 10 }
+      before { @resource.timeout(seconds) }
+      it "clones a repo with amended git options" do
+        @provider.should_receive(:shell_out!).with(expected_cmd, default_options.merge(:timeout => seconds))
+        @provider.clone
+      end
+    end
+  end
+
+  it "runs a clone command with escaped destination" do
+    @resource.user "deployNinja"
+    @resource.destination "/Application Support/with/space"
+    @resource.ssh_wrapper "do_it_this_way.sh"
+    expected_cmd = "git clone  \"git://github.com/opscode/chef.git\" \"/Application Support/with/space\""
+    @provider.should_receive(:shell_out!).with(expected_cmd, :user => "deployNinja",
+                                                :environment =>{"GIT_SSH"=>"do_it_this_way.sh"},
+                                                :log_tag => "git[web2.0 app]")
+    @provider.clone
+  end
+
+  it "compiles a clone command using --depth for shallow cloning" do
+    @resource.depth 5
+    expected_cmd = "git clone --depth 5 \"git://github.com/opscode/chef.git\" \"/my/deploy/dir\""
+    @provider.should_receive(:shell_out!).with(expected_cmd, :log_tag => "git[web2.0 app]")
+    @provider.clone
+  end
+
+  it "compiles a clone command with a remote other than ``origin''" do
+    @resource.remote "opscode"
+    expected_cmd = "git clone -o opscode \"git://github.com/opscode/chef.git\" \"/my/deploy/dir\""
+    @provider.should_receive(:shell_out!).with(expected_cmd, :log_tag => "git[web2.0 app]")
+    @provider.clone
+  end
+
+  it "runs a checkout command with default options" do
+    expected_cmd = 'git checkout -b deploy d35af14d41ae22b19da05d7d03a0bafc321b244c'
+    @provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir",
+                                                             :log_tag => "git[web2.0 app]")
+    @provider.checkout
+  end
+
+  it "runs an enable_submodule command" do
+    @resource.enable_submodules true
+    expected_cmd = "git submodule sync"
+    @provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir",
+                                                             :log_tag => "git[web2.0 app]")
+    expected_cmd = "git submodule update --init --recursive"
+    @provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]")
+    @provider.enable_submodules
+  end
+
+  it "does nothing for enable_submodules if resource.enable_submodules #=> false" do
+    @provider.should_not_receive(:shell_out!)
+    @provider.enable_submodules
+  end
+
+  it "runs a sync command with default options" do
+    @provider.should_receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository)
+    expected_cmd = "git fetch origin && git fetch origin --tags && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c"
+    @provider.should_receive(:shell_out!).with(expected_cmd, :cwd=> "/my/deploy/dir", :log_tag => "git[web2.0 app]")
+    @provider.fetch_updates
+  end
+
+  it "runs a sync command with the user and group specified in the resource" do
+    @resource.user("whois")
+    @resource.group("thisis")
+    @provider.should_receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository)
+    expected_cmd = "git fetch origin && git fetch origin --tags && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c"
+    @provider.should_receive(:shell_out!).with(expected_cmd, :cwd => "/my/deploy/dir",
+                                                :user => "whois", :group => "thisis", :log_tag => "git[web2.0 app]")
+    @provider.fetch_updates
+  end
+
+  it "configures remote tracking branches when remote is ``origin''" do
+    @resource.remote "origin"
+    @provider.should_receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository)
+    fetch_command = "git fetch origin && git fetch origin --tags && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c"
+    @provider.should_receive(:shell_out!).with(fetch_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]")
+    @provider.fetch_updates
+  end
+
+  it "configures remote tracking branches when remote is not ``origin''" do
+    @resource.remote "opscode"
+    @provider.should_receive(:setup_remote_tracking_branches).with(@resource.remote, @resource.repository)
+    fetch_command = "git fetch opscode && git fetch opscode --tags && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c"
+    @provider.should_receive(:shell_out!).with(fetch_command, :cwd => "/my/deploy/dir", :log_tag => "git[web2.0 app]")
+    @provider.fetch_updates
+  end
+
+  context "configuring remote tracking branches" do
+
+    it "checks if a remote with this name already exists" do
+      command_response = double('shell_out')
+      command_response.stub(:exitstatus) { 1 }
+      expected_command = "git config --get remote.#{@resource.remote}.url"
+      @provider.should_receive(:shell_out!).with(expected_command,
+                                                 :cwd => "/my/deploy/dir",
+                                                 :log_tag => "git[web2.0 app]",
+                                                 :returns => [0,1,2]).and_return(command_response)
+      add_remote_command = "git remote add #{@resource.remote} #{@resource.repository}"
+      @provider.should_receive(:shell_out!).with(add_remote_command,
+                                                 :cwd => "/my/deploy/dir",
+                                                 :log_tag => "git[web2.0 app]")
+      @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository)
+    end
+
+    it "runs the config with the user and group specified in the resource" do
+      @resource.user("whois")
+      @resource.group("thisis")
+      command_response = double('shell_out')
+      command_response.stub(:exitstatus) { 1 }
+      expected_command = "git config --get remote.#{@resource.remote}.url"
+      @provider.should_receive(:shell_out!).with(expected_command,
+                                                 :cwd => "/my/deploy/dir",
+                                                 :log_tag => "git[web2.0 app]",
+                                                 :user => "whois",
+                                                 :group => "thisis",
+                                                 :returns => [0,1,2]).and_return(command_response)
+      add_remote_command = "git remote add #{@resource.remote} #{@resource.repository}"
+      @provider.should_receive(:shell_out!).with(add_remote_command,
+                                                 :cwd => "/my/deploy/dir",
+                                                 :log_tag => "git[web2.0 app]",
+                                                 :user => "whois",
+                                                 :group => "thisis")
+      @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository)
+    end
+
+    describe "when a remote with a given name hasn't been configured yet" do
+      it "adds a new remote " do
+        command_response = double('shell_out')
+        command_response.stub(:exitstatus) { 1 }
+        check_remote_command = "git config --get remote.#{@resource.remote}.url"
+        @provider.should_receive(:shell_out!).with(check_remote_command,
+                                                   :cwd => "/my/deploy/dir",
+                                                   :log_tag => "git[web2.0 app]",
+                                                   :returns => [0,1,2]).and_return(command_response)
+        expected_command = "git remote add #{@resource.remote} #{@resource.repository}"
+        @provider.should_receive(:shell_out!).with(expected_command,
+                                                   :cwd => "/my/deploy/dir",
+                                                   :log_tag => "git[web2.0 app]")
+        @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository)
+      end
+    end
+
+    describe "when a remote with a given name has already been configured" do
+      it "updates remote url when the url is different" do
+        command_response = double('shell_out')
+        command_response.stub(:exitstatus) { 0 }
+        command_response.stub(:stdout) { "some_other_url" }
+        check_remote_command = "git config --get remote.#{@resource.remote}.url"
+        @provider.should_receive(:shell_out!).with(check_remote_command,
+                                                   :cwd => "/my/deploy/dir",
+                                                   :log_tag => "git[web2.0 app]",
+                                                   :returns => [0,1,2]).and_return(command_response)
+        expected_command = "git config --replace-all remote.#{@resource.remote}.url #{@resource.repository}"
+        @provider.should_receive(:shell_out!).with(expected_command,
+                                                   :cwd => "/my/deploy/dir",
+                                                   :log_tag => "git[web2.0 app]")
+        @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository)
+      end
+
+      it "doesn't update remote url when the url is the same" do
+        command_response = double('shell_out')
+        command_response.stub(:exitstatus) { 0 }
+        command_response.stub(:stdout) { @resource.repository }
+        check_remote_command = "git config --get remote.#{@resource.remote}.url"
+        @provider.should_receive(:shell_out!).with(check_remote_command,
+                                                   :cwd => "/my/deploy/dir",
+                                                   :log_tag => "git[web2.0 app]",
+                                                   :returns => [0,1,2]).and_return(command_response)
+        unexpected_command = "git config --replace-all remote.#{@resource.remote}.url #{@resource.repository}"
+        @provider.should_not_receive(:shell_out!).with(unexpected_command,
+                                                       :cwd => "/my/deploy/dir",
+                                                       :log_tag => "git[web2.0 app]")
+        @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository)
+      end
+
+      it "resets remote url when it has multiple values" do
+        command_response = double('shell_out')
+        command_response.stub(:exitstatus) { 2 }
+        check_remote_command = "git config --get remote.#{@resource.remote}.url"
+        @provider.should_receive(:shell_out!).with(check_remote_command,
+                                                   :cwd => "/my/deploy/dir",
+                                                   :log_tag => "git[web2.0 app]",
+                                                   :returns => [0,1,2]).and_return(command_response)
+        expected_command = "git config --replace-all remote.#{@resource.remote}.url #{@resource.repository}"
+        @provider.should_receive(:shell_out!).with(expected_command,
+                                                   :cwd => "/my/deploy/dir",
+                                                   :log_tag => "git[web2.0 app]")
+        @provider.setup_remote_tracking_branches(@resource.remote, @resource.repository)
+      end
+    end
+  end
+
+  it "raises an error if the git clone command would fail because the enclosing directory doesn't exist" do
+    @provider.stub!(:shell_out!)
+    lambda {@provider.run_action(:sync)}.should raise_error(Chef::Exceptions::MissingParentDirectory)
+  end
+
+  it "does a checkout by cloning the repo and then enabling submodules" do
+    # will be invoked in load_current_resource
+    ::File.stub!(:exist?).with("/my/deploy/dir/.git").and_return(false)
+
+    ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['.','..'])
+    @provider.should_receive(:clone)
+    @provider.should_receive(:checkout)
+    @provider.should_receive(:enable_submodules)
+    @provider.run_action(:checkout)
+    # Even though an actual run will cause an update to occur, the fact that we've stubbed out
+    # the actions above will prevent updates from registering
+    # @resource.should be_updated
+  end
+
+  # REGRESSION TEST: on some OSes, the entries from an empty directory will be listed as
+  # ['..', '.'] but this shouldn't change the behavior
+  it "does a checkout by cloning the repo and then enabling submodules when the directory entries are listed as %w{.. .}" do
+    ::File.stub!(:exist?).with("/my/deploy/dir/.git").and_return(false)
+    ::File.stub!(:exist?).with("/my/deploy/dir").and_return(false)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['..','.'])
+    @provider.should_receive(:clone)
+    @provider.should_receive(:checkout)
+    @provider.should_receive(:enable_submodules)
+    @provider.should_receive(:add_remotes)
+    @provider.run_action(:checkout)
+   # @resource.should be_updated
+  end
+
+  it "should not checkout if the destination exists or is a non empty directory" do
+    # will be invoked in load_current_resource
+    ::File.stub!(:exist?).with("/my/deploy/dir/.git").and_return(false)
+
+    ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['.','..','foo','bar'])
+    @provider.should_not_receive(:clone)
+    @provider.should_not_receive(:checkout)
+    @provider.should_not_receive(:enable_submodules)
+    @provider.should_not_receive(:add_remotes)
+    @provider.run_action(:checkout)
+    @resource.should_not be_updated
+  end
+
+  it "syncs the code by updating the source when the repo has already been checked out" do
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    @provider.should_receive(:find_current_revision).exactly(2).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+    @provider.should_not_receive(:fetch_updates)
+    @provider.should_receive(:add_remotes)
+    @provider.run_action(:sync)
+    @resource.should_not be_updated
+  end
+
+  it "marks the resource as updated when the repo is updated and gets a new version" do
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    # invoked twice - first time from load_current_resource
+    @provider.should_receive(:find_current_revision).exactly(2).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+    @provider.stub!(:target_revision).and_return('28af684d8460ba4793eda3e7ac238c864a5d029a')
+    @provider.should_receive(:fetch_updates)
+    @provider.should_receive(:enable_submodules)
+    @provider.should_receive(:add_remotes)
+    @provider.run_action(:sync)
+   # @resource.should be_updated
+  end
+
+  it "does not fetch any updates if the remote revision matches the current revision" do
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    @provider.stub!(:find_current_revision).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+    @provider.stub!(:target_revision).and_return('d35af14d41ae22b19da05d7d03a0bafc321b244c')
+    @provider.should_not_receive(:fetch_updates)
+    @provider.should_receive(:add_remotes)
+    @provider.run_action(:sync)
+    @resource.should_not be_updated
+  end
+
+  it "clones the repo instead of fetching it if the deploy directory doesn't exist" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.git").exactly(2).and_return(false)
+    @provider.should_receive(:action_checkout)
+    @provider.should_not_receive(:shell_out!)
+    @provider.run_action(:sync)
+   # @resource.should be_updated
+  end
+
+  it "clones the repo instead of fetching updates if the deploy directory is empty" do
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.git").exactly(2).and_return(false)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::File.stub!(:directory?).with("/my/deploy/dir").and_return(true)
+    @provider.stub!(:sync_command).and_return("huzzah!")
+    @provider.should_receive(:action_checkout)
+    @provider.should_not_receive(:shell_out!).with("huzzah!", :cwd => "/my/deploy/dir")
+    @provider.run_action(:sync)
+    #@resource.should be_updated
+  end
+
+  it "does an export by cloning the repo then removing the .git directory" do
+    @provider.should_receive(:action_checkout)
+    FileUtils.should_receive(:rm_rf).with(@resource.destination + "/.git")
+    @provider.run_action(:export)
+    @resource.should be_updated
+  end
+
+  describe "calling add_remotes" do
+    it "adds a new remote for each entry in additional remotes hash" do
+      @resource.additional_remotes({:opscode => "opscode_repo_url",
+        :another_repo => "some_other_repo_url"})
+      STDOUT.stub(:tty?).and_return(false)
+      command_response = double('shell_out')
+      command_response.stub(:exitstatus) { 0 }
+      @resource.additional_remotes.each_pair do |remote_name, remote_url|
+        @provider.should_receive(:setup_remote_tracking_branches).with(remote_name, remote_url)
+      end
+      @provider.add_remotes
+    end
+  end
+
+  describe "calling multiple_remotes?" do
+    before(:each) do
+      @command_response = double('shell_out')
+    end
+
+    describe "when check remote command returns with status 2" do
+      it "returns true" do
+        @command_response.stub(:exitstatus) { 2 }
+        @provider.multiple_remotes?(@command_response).should be_true
+      end
+    end
+
+    describe "when check remote command returns with status 0" do
+      it "returns false" do
+        @command_response.stub(:exitstatus) { 0 }
+        @provider.multiple_remotes?(@command_response).should be_false
+      end
+    end
+
+    describe "when check remote command returns with status 0" do
+      it "returns false" do
+        @command_response.stub(:exitstatus) { 1 }
+        @provider.multiple_remotes?(@command_response).should be_false
+      end
+    end
+  end
+
+  describe "calling remote_matches?" do
+    before(:each) do
+      @command_response = double('shell_out')
+    end
+
+    describe "when output of the check remote command matches the repository url" do
+      it "returns true" do
+        @command_response.stub(:exitstatus) { 0 }
+        @command_response.stub(:stdout) { @resource.repository }
+        @provider.remote_matches?(@resource.repository, @command_response).should be_true
+      end
+    end
+
+    describe "when output of the check remote command doesn't match the repository url" do
+      it "returns false" do
+        @command_response.stub(:exitstatus) { 0 }
+        @command_response.stub(:stdout) { @resource.repository + "test" }
+        @provider.remote_matches?(@resource.repository, @command_response).should be_false
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/group/dscl_spec.rb b/spec/unit/provider/group/dscl_spec.rb
new file mode 100644
index 0000000..28d78cb
--- /dev/null
+++ b/spec/unit/provider/group/dscl_spec.rb
@@ -0,0 +1,294 @@
+#
+# Author:: Dreamcat4 (<dreamcat4 at gmail.com>)
+# Copyright:: Copyright (c) 2009 OpsCode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Group::Dscl do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("aj")
+    @current_resource = Chef::Resource::Group.new("aj")
+    @provider = Chef::Provider::Group::Dscl.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+    @status = mock("Process::Status", :exitstatus => 0)
+    @pid = 2342
+    @stdin = StringIO.new
+    @stdout = StringIO.new("\n")
+    @stderr = StringIO.new("")
+    @provider.stub!(:popen4).and_yield(@pid, at stdin, at stdout, at stderr).and_return(@status)
+  end
+
+  it "should run popen4 with the supplied array of arguments appended to the dscl command" do
+    @provider.should_receive(:popen4).with("dscl . -cmd /Path arg1 arg2")
+    @provider.dscl("cmd", "/Path", "arg1", "arg2")
+  end
+
+  it "should return an array of four elements - cmd, status, stdout, stderr" do
+    dscl_retval = @provider.dscl("cmd /Path args")
+    dscl_retval.should be_a_kind_of(Array)
+    dscl_retval.should == ["dscl . -cmd /Path args", at status,"\n",""]
+  end
+
+  describe "safe_dscl" do
+    before do
+      @node = Chef::Node.new
+      @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource)
+      @provider.stub!(:dscl).and_return(["cmd", @status, "stdout", "stderr"])
+    end
+
+    it "should run dscl with the supplied cmd /Path args" do
+      @provider.should_receive(:dscl).with("cmd /Path args")
+      @provider.safe_dscl("cmd /Path args")
+    end
+
+    describe "with the dscl command returning a non zero exit status for a delete" do
+      before do
+        @status = mock("Process::Status", :exitstatus => 1)
+        @provider.stub!(:dscl).and_return(["cmd", @status, "stdout", "stderr"])
+      end
+
+      it "should return an empty string of standard output for a delete" do
+        safe_dscl_retval = @provider.safe_dscl("delete /Path args")
+        safe_dscl_retval.should be_a_kind_of(String)
+        safe_dscl_retval.should == ""
+      end
+
+      it "should raise an exception for any other command" do
+        lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::Group)
+      end
+    end
+
+    describe "with the dscl command returning no such key" do
+      before do
+        @provider.stub!(:dscl).and_return(["cmd", @status, "No such key: ", "stderr"])
+      end
+
+      it "should raise an exception" do
+        lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::Group)
+      end
+    end
+
+    describe "with the dscl command returning a zero exit status" do
+      it "should return the third array element, the string of standard output" do
+        safe_dscl_retval = @provider.safe_dscl("cmd /Path args")
+        safe_dscl_retval.should be_a_kind_of(String)
+        safe_dscl_retval.should == "stdout"
+      end
+    end
+  end
+
+  describe "get_free_gid" do
+    before do
+      @node = Chef::Node.new
+      @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource)
+      @provider.stub!(:safe_dscl).and_return("\naj      200\njt      201\n")
+    end
+
+    it "should run safe_dscl with list /Groups gid" do
+      @provider.should_receive(:safe_dscl).with("list /Groups gid")
+      @provider.get_free_gid
+    end
+
+    it "should return the first unused gid number on or above 200" do
+      @provider.get_free_gid.should equal(202)
+    end
+
+    it "should raise an exception when the search limit is exhausted" do
+      search_limit = 1
+      lambda { @provider.get_free_gid(search_limit) }.should raise_error(RuntimeError)
+    end
+  end
+
+  describe "gid_used?" do
+    before do
+      @node = Chef::Node.new
+      @provider = Chef::Provider::Group::Dscl.new(@node, @new_resource)
+      @provider.stub!(:safe_dscl).and_return("\naj      500\n")
+    end
+
+    it "should run safe_dscl with list /Groups gid" do
+      @provider.should_receive(:safe_dscl).with("list /Groups gid")
+      @provider.gid_used?(500)
+    end
+
+    it "should return true for a used gid number" do
+      @provider.gid_used?(500).should be_true
+    end
+
+    it "should return false for an unused gid number" do
+      @provider.gid_used?(501).should be_false
+    end
+
+    it "should return false if not given any valid gid number" do
+      @provider.gid_used?(nil).should be_false
+    end
+  end
+
+  describe "set_gid" do
+    describe "with the new resource and a gid number which is already in use" do
+      before do
+        @provider.stub!(:gid_used?).and_return(true)
+      end
+
+      it "should raise an exception if the new resources gid is already in use" do
+        lambda { @provider.set_gid }.should raise_error(Chef::Exceptions::Group)
+      end
+    end
+
+    describe "with no gid number for the new resources" do
+      it "should run get_free_gid and return a valid, unused gid number" do
+        @provider.should_receive(:get_free_gid).and_return(501)
+        @provider.set_gid
+      end
+    end
+
+    describe "with blank gid number for the new resources" do
+      before do
+        @new_resource.instance_variable_set(:@gid, nil)
+        @new_resource.stub!(:safe_dscl)
+      end
+
+      it "should run get_free_gid and return a valid, unused gid number" do
+        @provider.should_receive(:get_free_gid).and_return(501)
+        @provider.set_gid
+      end
+    end
+
+    describe "with a valid gid number which is not already in use" do
+      it "should run safe_dscl with create /Groups/group PrimaryGroupID gid" do
+        @provider.stub(:get_free_gid).and_return(50)
+        @provider.should_receive(:safe_dscl).with("list /Groups gid")
+        @provider.should_receive(:safe_dscl).with("create /Groups/aj PrimaryGroupID 50").and_return(true)
+        @provider.set_gid
+      end
+    end
+  end
+
+  describe "set_members" do
+
+    describe "with existing members in the current resource and append set to false in the new resource" do
+      before do
+        @new_resource.stub!(:members).and_return([])
+        @new_resource.stub!(:append).and_return(false)
+        @current_resource.stub!(:members).and_return(["all", "your", "base"])
+      end
+
+      it "should log an appropriate message" do
+        Chef::Log.should_receive(:debug).with("group[aj] removing group members all your base")
+        @provider.set_members
+      end
+
+      it "should run safe_dscl with create /Groups/group GroupMembership to clear the Group's UID list" do
+        @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembers ''").and_return(true)
+        @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembership ''").and_return(true)
+        @provider.set_members
+      end
+    end
+
+    describe "with supplied members in the new resource" do
+      before do
+        @new_resource.members(["all", "your", "base"])
+        @current_resource.members([])
+      end
+
+      it "should log an appropriate debug message" do
+        Chef::Log.should_receive(:debug).with("group[aj] setting group members all, your, base")
+        @provider.set_members
+      end
+
+      it "should run safe_dscl with append /Groups/group GroupMembership and group members all, your, base" do
+        @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembers ''").and_return(true)
+        @provider.should_receive(:safe_dscl).with("append /Groups/aj GroupMembership all your base").and_return(true)
+        @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembership ''").and_return(true)
+        @provider.set_members
+      end
+    end
+
+    describe "with no members in the new resource" do
+      before do
+        @new_resource.append(true)
+        @new_resource.members([])
+      end
+
+      it "should not call safe_dscl" do
+        @provider.should_not_receive(:safe_dscl)
+        @provider.set_members
+      end
+    end
+  end
+
+  describe "when loading the current system state" do
+    before (:each) do
+      @provider.load_current_resource
+      @provider.define_resource_requirements
+    end
+    it "raises an error if the required binary /usr/bin/dscl doesn't exist" do
+      File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
+
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+    end
+
+    it "doesn't raise an error if /usr/bin/dscl exists" do
+      File.stub!(:exists?).and_return(true)
+      lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+    end
+  end
+
+  describe "when creating the group" do
+    it "creates the group, password field, gid, and sets group membership" do
+      @provider.should_receive(:set_gid).and_return(true)
+      @provider.should_receive(:set_members).and_return(true)
+      @provider.should_receive(:safe_dscl).with("create /Groups/aj Password '*'")
+      @provider.should_receive(:safe_dscl).with("create /Groups/aj")
+      @provider.create_group
+    end
+  end
+
+  describe "managing the group" do
+    it "should manage the group_name if it changed and the new resources group_name is not null" do
+      @current_resource.group_name("oldval")
+      @new_resource.group_name("newname")
+      @provider.should_receive(:safe_dscl).with("create /Groups/newname")
+      @provider.should_receive(:safe_dscl).with("create /Groups/newname Password '*'")
+      @provider.manage_group
+    end
+
+    it "should manage the gid if it changed and the new resources gid is not null" do
+      @current_resource.gid(23)
+      @new_resource.gid(42)
+      @provider.should_receive(:set_gid)
+      @provider.manage_group
+    end
+
+    it "should manage the members if it changed and the new resources members is not null" do
+      @current_resource.members(%{charlie root})
+      @new_resource.members(%{crab revenge})
+      @provider.should_receive(:set_members)
+      @provider.manage_group
+    end
+  end
+
+  describe "remove_group" do
+    it "should run safe_dscl with delete /Groups/group and with the new resources group name" do
+      @provider.should_receive(:safe_dscl).with("delete /Groups/aj").and_return(true)
+      @provider.remove_group
+    end
+  end
+end
diff --git a/spec/unit/provider/group/gpasswd_spec.rb b/spec/unit/provider/group/gpasswd_spec.rb
new file mode 100644
index 0000000..8889ba3
--- /dev/null
+++ b/spec/unit/provider/group/gpasswd_spec.rb
@@ -0,0 +1,108 @@
+#
+# Author:: AJ Christensen (<aj at opscode.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'
+
+describe Chef::Provider::Group::Gpasswd, "modify_group_members" do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("wheel")
+    @new_resource.members %w{lobster rage fist}
+    @new_resource.append false
+    @provider = Chef::Provider::Group::Gpasswd.new(@new_resource, @run_context)
+    #@provider.stub!(:run_command).and_return(true)
+  end
+
+  describe "when determining the current group state" do
+    before (:each) do
+      @provider.load_current_resource
+      @provider.define_resource_requirements
+    end
+
+    # Checking for required binaries is already done in the spec
+    # for Chef::Provider::Group - no need to repeat it here.  We'll
+    # include only what's specific to this provider.
+    it "should raise an error if the required binary /usr/bin/gpasswd doesn't exist" do
+      File.stub!(:exists?).and_return(true)
+      File.should_receive(:exists?).with("/usr/bin/gpasswd").and_return(false)
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+    end
+
+    it "shouldn't raise an error if the required binaries exist" do
+      File.stub!(:exists?).and_return(true)
+      lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+    end
+  end
+
+  describe "after the group's current state is known" do
+    before do
+      @current_resource = @new_resource.dup
+      @provider.current_resource = @new_resource
+    end
+
+    describe "when no group members are specified and append is not set" do
+      before do
+        @new_resource.append(false)
+        @new_resource.members([])
+      end
+
+      it "logs a message and sets group's members to 'none'" do
+        Chef::Log.should_receive(:debug).with("group[wheel] setting group members to: none")
+        @provider.should_receive(:shell_out!).with("gpasswd -M \"\" wheel")
+        @provider.modify_group_members
+      end
+    end
+
+    describe "when no group members are specified and append is set" do
+      before do
+        @new_resource.append(true)
+        @new_resource.members([])
+      end
+
+      it "logs a message and does not modify group membership" do
+        Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members to add")
+        @provider.should_not_receive(:shell_out!)
+        @provider.modify_group_members
+      end
+    end
+
+    describe "when the resource specifies group members" do
+      it "should log an appropriate debug message" do
+        Chef::Log.should_receive(:debug).with("group[wheel] setting group members to lobster, rage, fist")
+        @provider.stub!(:shell_out!)
+        @provider.modify_group_members
+      end
+
+      it "should run gpasswd with the members joined by ',' followed by the target group" do
+        @provider.should_receive(:shell_out!).with("gpasswd -M lobster,rage,fist wheel")
+        @provider.modify_group_members
+      end
+
+      it "should run gpasswd individually for each user when the append option is set" do
+        @new_resource.append(true)
+        @provider.should_receive(:shell_out!).with("gpasswd -a lobster wheel")
+        @provider.should_receive(:shell_out!).with("gpasswd -a rage wheel")
+        @provider.should_receive(:shell_out!).with("gpasswd -a fist wheel")
+        @provider.modify_group_members
+      end
+
+    end
+  end
+end
diff --git a/spec/unit/provider/group/groupadd_spec.rb b/spec/unit/provider/group/groupadd_spec.rb
new file mode 100644
index 0000000..0cc1167
--- /dev/null
+++ b/spec/unit/provider/group/groupadd_spec.rb
@@ -0,0 +1,175 @@
+#
+# Author:: AJ Christensen (<aj at opscode.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'
+
+describe Chef::Provider::Group::Groupadd, "set_options" do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("aj")
+    @new_resource.gid(50)
+    @new_resource.members(["root", "aj"])
+    @new_resource.system false
+    @new_resource.non_unique false
+    @current_resource = Chef::Resource::Group.new("aj")
+    @current_resource.gid(50)
+    @current_resource.members(["root", "aj"])
+    @current_resource.system false
+    @current_resource.non_unique false
+    @provider = Chef::Provider::Group::Groupadd.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  field_list = {
+    :gid => "-g"
+  }
+
+  field_list.each do |attribute, option|
+    it "should check for differences in #{attribute.to_s} between the current and new resources" do
+        @new_resource.should_receive(attribute)
+        @current_resource.should_receive(attribute)
+        @provider.set_options
+    end
+    it "should set the option for #{attribute} if the new resources #{attribute} is not null" do
+      @new_resource.stub!(attribute).and_return("wowaweea")
+      @provider.set_options.should eql(" #{option} '#{@new_resource.send(attribute)}' #{@new_resource.group_name}")
+    end
+  end
+
+  it "should combine all the possible options" do
+    match_string = ""
+    field_list.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option|
+      @new_resource.stub!(attribute).and_return("hola")
+      match_string << " #{option} 'hola'"
+    end
+    match_string << " aj"
+    @provider.set_options.should eql(match_string)
+  end
+
+  describe "when we want to create a system group" do
+    it "should not set groupadd_options '-r' when system is false" do
+      @new_resource.system(false)
+      @provider.groupadd_options.should_not =~ /-r/
+    end
+
+    it "should set groupadd -r if system is true" do
+      @new_resource.system(true)
+      @provider.groupadd_options.should == " -r"
+    end
+  end
+
+  describe "when we want to create a non_unique gid group" do
+    it "should not set groupadd_options '-o' when non_unique is false" do
+      @new_resource.non_unique(false)
+      @provider.groupadd_options.should_not =~ /-o/
+    end
+
+    it "should set groupadd -o if non_unique is true" do
+      @new_resource.non_unique(true)
+      @provider.groupadd_options.should == " -o"
+    end
+  end
+end
+
+describe Chef::Provider::Group::Groupadd, "create_group" do
+  before do
+    @node = Chef::Node.new
+    @new_resource = Chef::Resource::Group.new("aj")
+    @provider = Chef::Provider::Group::Groupadd.new(@node, @new_resource)
+    @provider.stub!(:run_command).and_return(true)
+    @provider.stub!(:set_options).and_return(" monkey")
+    @provider.stub!(:groupadd_options).and_return("")
+    @provider.stub!(:modify_group_members).and_return(true)
+  end
+
+  it "should run groupadd with the return of set_options" do
+    @provider.should_receive(:run_command).with({ :command => "groupadd monkey" }).and_return(true)
+    @provider.create_group
+  end
+
+  it "should modify the group members" do
+    @provider.should_receive(:modify_group_members).and_return(true)
+    @provider.create_group
+  end
+end
+
+describe Chef::Provider::Group::Groupadd do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("aj")
+    @provider = Chef::Provider::Group::Groupadd.new(@new_resource, @run_context)
+    @provider.stub!(:run_command).and_return(true)
+    @provider.stub!(:set_options).and_return(" monkey")
+  end
+
+  describe "manage group" do
+
+    it "should run groupmod with the return of set_options" do
+      @provider.stub!(:modify_group_members).and_return(true)
+      @provider.should_receive(:run_command).with({ :command => "groupmod monkey" }).and_return(true)
+      @provider.manage_group
+    end
+
+    it "should modify the group members" do
+      @provider.should_receive(:modify_group_members).and_return(true)
+      @provider.manage_group
+    end
+  end
+
+  describe "remove_group" do
+
+    it "should run groupdel with the new resources group name" do
+      @provider.should_receive(:run_command).with({ :command => "groupdel aj" }).and_return(true)
+      @provider.remove_group
+    end
+  end
+
+  describe "modify_group_members" do
+
+    it "should raise an error when calling modify_group_members" do
+      lambda { @provider.modify_group_members }.should raise_error(Chef::Exceptions::Group, "you must override modify_group_members in #{@provider.to_s}")
+    end
+  end
+
+  describe "load_current_resource" do
+    before do
+      File.stub!(:exists?).and_return(false)
+      @provider.define_resource_requirements
+    end
+    it "should raise an error if the required binary /usr/sbin/groupadd doesn't exist" do
+      File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(false)
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+    end
+    it "should raise an error if the required binary /usr/sbin/groupmod doesn't exist" do
+      File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true)
+      File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(false)
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+    end
+    it "should raise an error if the required binary /usr/sbin/groupdel doesn't exist" do
+      File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true)
+      File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(true)
+      File.should_receive(:exists?).with("/usr/sbin/groupdel").and_return(false)
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+    end
+
+  end
+end
diff --git a/spec/unit/provider/group/groupmod_spec.rb b/spec/unit/provider/group/groupmod_spec.rb
new file mode 100644
index 0000000..69b96c6
--- /dev/null
+++ b/spec/unit/provider/group/groupmod_spec.rb
@@ -0,0 +1,134 @@
+#
+# Author:: Dan Crosta (<dcrosta at late.am>)
+# Copyright:: Copyright (c) 2012 OpsCode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Group::Groupmod do
+    before do
+      @node = Chef::Node.new
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_context = Chef::RunContext.new(@node, {}, @events)
+      @new_resource = Chef::Resource::Group.new("wheel")
+      @new_resource.gid 123
+      @new_resource.members %w{lobster rage fist}
+      @new_resource.append false
+      @provider = Chef::Provider::Group::Groupmod.new(@new_resource, @run_context)
+    end
+
+  describe "manage_group" do
+    describe "when determining the current group state" do
+      it "should raise an error if the required binary /usr/sbin/group doesn't exist" do
+        File.should_receive(:exists?).with("/usr/sbin/group").and_return(false)
+        lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group)
+      end
+      it "should raise an error if the required binary /usr/sbin/user doesn't exist" do
+        File.should_receive(:exists?).with("/usr/sbin/group").and_return(true)
+        File.should_receive(:exists?).with("/usr/sbin/user").and_return(false)
+        lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group)
+      end
+
+      it "shouldn't raise an error if the required binaries exist" do
+        File.stub!(:exists?).and_return(true)
+        lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Group)
+      end
+    end
+
+    describe "after the group's current state is known" do
+      before do
+        @current_resource = @new_resource.dup
+        @provider.current_resource = @current_resource
+      end
+
+      describe "when no group members are specified and append is not set" do
+        before do
+          @new_resource.append(false)
+          @new_resource.members([])
+        end
+
+        it "logs a message and sets group's members to 'none', then removes existing group members" do
+          Chef::Log.should_receive(:debug).with("group[wheel] setting group members to: none")
+          Chef::Log.should_receive(:debug).with("group[wheel] removing members lobster, rage, fist")
+          @provider.should_receive(:shell_out!).with("group mod -n wheel_bak wheel")
+          @provider.should_receive(:shell_out!).with("group add -g '123' -o wheel")
+          @provider.should_receive(:shell_out!).with("group del wheel_bak")
+          @provider.manage_group
+        end
+      end
+
+      describe "when no group members are specified and append is set" do
+        before do
+          @new_resource.append(true)
+          @new_resource.members([])
+        end
+
+        it "logs a message and does not modify group membership" do
+          Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members to add")
+          @provider.should_not_receive(:shell_out!)
+          @provider.manage_group
+        end
+      end
+
+      describe "when removing some group members" do
+        before do
+          @new_resource.append(false)
+          @new_resource.members(%w{ lobster })
+        end
+
+        it "updates group membership correctly" do
+          Chef::Log.stub!(:debug)
+          @provider.should_receive(:shell_out!).with("group mod -n wheel_bak wheel")
+          @provider.should_receive(:shell_out!).with("user mod -G wheel lobster")
+          @provider.should_receive(:shell_out!).with("group add -g '123' -o wheel")
+          @provider.should_receive(:shell_out!).with("group del wheel_bak")
+          @provider.manage_group
+        end
+      end
+    end
+  end
+
+  describe "create_group" do
+    describe "when creating a new group" do
+      before do
+        @current_resource = Chef::Resource::Group.new("wheel")
+        @provider.current_resource = @current_resource
+      end
+
+      it "should run a group add command and some user mod commands" do
+        @provider.should_receive(:shell_out!).with("group add -g '123' wheel")
+        @provider.should_receive(:shell_out!).with("user mod -G wheel lobster")
+        @provider.should_receive(:shell_out!).with("user mod -G wheel rage")
+        @provider.should_receive(:shell_out!).with("user mod -G wheel fist")
+        @provider.create_group
+      end
+    end
+  end
+
+  describe "remove_group" do
+    describe "when removing an existing group" do
+      before do
+        @current_resource = @new_resource.dup
+        @provider.current_resource = @current_resource
+      end
+
+      it "should run a group del command" do
+        @provider.should_receive(:shell_out!).with("group del wheel")
+        @provider.remove_group
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/group/pw_spec.rb b/spec/unit/provider/group/pw_spec.rb
new file mode 100644
index 0000000..e7c38f9
--- /dev/null
+++ b/spec/unit/provider/group/pw_spec.rb
@@ -0,0 +1,140 @@
+#
+# Author:: Stephen Haynes (<sh at nomitor.com>)
+# Copyright:: Copyright (c) 2009 OpsCode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Group::Pw do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Group.new("wheel")
+    @new_resource.gid 50
+    @new_resource.members [ "root", "aj"]
+
+    @current_resource = Chef::Resource::Group.new("aj")
+    @current_resource.gid 50
+    @current_resource.members [ "root", "aj"]
+    @provider = Chef::Provider::Group::Pw.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  describe "when setting options for the pw command" do
+    it "does not set the gid option if gids match or are unmanaged" do
+      @provider.set_options.should ==  " wheel"
+    end
+
+    it "sets the option for gid if it is not nil" do
+      @new_resource.gid(42)
+      @provider.set_options.should eql(" wheel -g '42'")
+    end
+  end
+
+  describe "when creating a group" do
+    it "should run pw groupadd with the return of set_options and set_members_option" do
+      @new_resource.gid(23)
+      @provider.should_receive(:run_command).with({ :command => "pw groupadd wheel -g '23' -M root,aj" }).and_return(true)
+      @provider.create_group
+    end
+  end
+
+  describe "when managing the group" do
+
+    it "should run pw groupmod with the return of set_options" do
+      @new_resource.gid(42)
+      @provider.should_receive(:run_command).with({ :command => "pw groupmod wheel -g '42' -M root,aj" }).and_return(true)
+      @provider.manage_group
+    end
+
+  end
+
+  describe "when removing the group" do
+    it "should run pw groupdel with the new resources group name" do
+      @provider.should_receive(:run_command).with({ :command => "pw groupdel wheel" }).and_return(true)
+      @provider.remove_group
+    end
+  end
+
+  describe "when setting group membership" do
+
+    describe "with an empty members array in both the new and current resource" do
+      before do
+        @new_resource.stub!(:members).and_return([])
+        @current_resource.stub!(:members).and_return([])
+      end
+
+      it "should log an appropriate message" do
+        Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members")
+        @provider.set_members_option
+      end
+
+      it "should set no options" do
+        @provider.set_members_option.should eql("")
+      end
+    end
+
+    describe "with an empty members array in the new resource and existing members in the current resource" do
+      before do
+        @new_resource.stub!(:members).and_return([])
+        @current_resource.stub!(:members).and_return(["all", "your", "base"])
+      end
+
+      it "should log an appropriate message" do
+        Chef::Log.should_receive(:debug).with("group[wheel] removing group members all, your, base")
+        @provider.set_members_option
+      end
+
+      it "should set the -d option with the members joined by ','" do
+        @provider.set_members_option.should eql(" -d all,your,base")
+      end
+    end
+
+    describe "with supplied members array in the new resource and an empty members array in the current resource" do
+      before do
+        @new_resource.stub!(:members).and_return(["all", "your", "base"])
+        @current_resource.stub!(:members).and_return([])
+      end
+
+      it "should log an appropriate debug message" do
+        Chef::Log.should_receive(:debug).with("group[wheel] setting group members to all, your, base")
+        @provider.set_members_option
+      end
+
+      it "should set the -M option with the members joined by ','" do
+        @provider.set_members_option.should eql(" -M all,your,base")
+      end
+    end
+  end
+
+  describe"load_current_resource" do
+    before (:each) do
+      @provider.load_current_resource
+      @provider.define_resource_requirements
+    end
+    it "should raise an error if the required binary /usr/sbin/pw doesn't exist" do
+      File.should_receive(:exists?).with("/usr/sbin/pw").and_return(false)
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+    end
+
+    it "shouldn't raise an error if /usr/sbin/pw exists" do
+      File.stub!(:exists?).and_return(true)
+      lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+    end
+  end
+end
diff --git a/spec/unit/provider/group/usermod_spec.rb b/spec/unit/provider/group/usermod_spec.rb
new file mode 100644
index 0000000..7f2931f
--- /dev/null
+++ b/spec/unit/provider/group/usermod_spec.rb
@@ -0,0 +1,98 @@
+#
+# Author:: AJ Christensen (<aj at opscode.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'
+
+describe Chef::Provider::Group::Usermod do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("wheel")
+    @new_resource.members [ "all", "your", "base" ]
+    @provider = Chef::Provider::Group::Usermod.new(@new_resource, @run_context)
+    @provider.stub!(:run_command)
+  end
+
+  describe "modify_group_members" do
+
+    describe "with an empty members array" do
+      before do
+        @new_resource.stub!(:members).and_return([])
+      end
+
+      it "should log an appropriate message" do
+        Chef::Log.should_receive(:debug).with("group[wheel] not changing group members, the group has no members")
+        @provider.modify_group_members
+      end
+    end
+
+    describe "with supplied members" do
+      platforms = {
+        "openbsd" => "-G",
+        "netbsd" => "-G",
+        "solaris" => "-a -G",
+        "suse" => "-a -G",
+        "opensuse" => "-a -G",
+        "smartos" => "-G"
+      }
+
+      before do
+        @new_resource.stub!(:members).and_return(["all", "your", "base"])
+        File.stub!(:exists?).and_return(true)
+      end
+
+      it "should raise an error when setting the entire group directly" do
+        @provider.define_resource_requirements
+        @provider.load_current_resource
+        @provider.instance_variable_set("@group_exists", true)
+        @provider.action = :modify
+        lambda { @provider.run_action(@provider.process_resource_requirements) }.should raise_error(Chef::Exceptions::Group, "setting group members directly is not supported by #{@provider.to_s}, must set append true in group")
+      end
+
+      platforms.each do |platform, flags|
+        it "should usermod each user when the append option is set on #{platform}" do
+          @node.automatic_attrs[:platform] = platform
+          @new_resource.stub!(:append).and_return(true)
+          @provider.should_receive(:run_command).with({:command => "usermod #{flags} wheel all"})
+          @provider.should_receive(:run_command).with({:command => "usermod #{flags} wheel your"})
+          @provider.should_receive(:run_command).with({:command => "usermod #{flags} wheel base"})
+          @provider.modify_group_members
+        end
+      end
+    end
+  end
+
+  describe "when loading the current resource" do
+    before(:each) do
+      File.stub!(:exists?).and_return(false)
+      @provider.define_resource_requirements
+    end
+
+    it "should raise an error if the required binary /usr/sbin/usermod doesn't exist" do
+      File.stub!(:exists?).and_return(true)
+      File.should_receive(:exists?).with("/usr/sbin/usermod").and_return(false)
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Group)
+    end
+
+    it "shouldn't raise an error if the required binaries exist" do
+      File.stub!(:exists?).and_return(true)
+      lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Group)
+    end
+  end
+end
diff --git a/spec/unit/provider/group/windows_spec.rb b/spec/unit/provider/group/windows_spec.rb
new file mode 100644
index 0000000..a65cfc2
--- /dev/null
+++ b/spec/unit/provider/group/windows_spec.rb
@@ -0,0 +1,108 @@
+#
+# Author:: Doug MacEachern (<dougm at vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class Chef
+  class Util
+    class Windows
+      class NetGroup
+      end
+    end
+  end
+end
+
+describe Chef::Provider::Group::Windows do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("staff")
+    @net_group = mock("Chef::Util::Windows::NetGroup")
+    Chef::Util::Windows::NetGroup.stub!(:new).and_return(@net_group)
+    @provider = Chef::Provider::Group::Windows.new(@new_resource, @run_context)
+  end
+
+  describe "when creating the group" do
+    it "should call @net_group.local_add" do
+      @net_group.should_receive(:local_set_members).with([])
+      @net_group.should_receive(:local_add)
+      @provider.create_group
+    end
+  end
+
+  describe "manage_group" do
+    before do
+      @new_resource.members([ "us" ])
+      @current_resource = Chef::Resource::Group.new("staff")
+      @current_resource.members [ "all", "your", "base" ]
+
+      Chef::Util::Windows::NetGroup.stub!(:new).and_return(@net_group)
+      @net_group.stub!(:local_add_members)
+      @net_group.stub!(:local_set_members)
+      @provider.current_resource = @current_resource
+    end
+
+    it "should call @net_group.local_set_members" do
+      @new_resource.stub!(:append).and_return(false)
+      @net_group.should_receive(:local_set_members).with(@new_resource.members)
+      @provider.manage_group
+    end
+
+    it "should call @net_group.local_add_members" do
+      @new_resource.stub!(:append).and_return(true)
+      @net_group.should_receive(:local_add_members).with(@new_resource.members)
+      @provider.manage_group
+    end
+
+    it "should call @net_group.local_set_members if append fails" do
+      @new_resource.stub!(:append).and_return(true)
+      @net_group.stub!(:local_add_members).and_raise(ArgumentError)
+      @net_group.should_receive(:local_add_members).with(@new_resource.members)
+      @net_group.should_receive(:local_set_members).with(@new_resource.members + @current_resource.members)
+      @provider.manage_group
+    end
+
+  end
+
+  describe "remove_group" do
+    before do
+      Chef::Util::Windows::NetGroup.stub!(:new).and_return(@net_group)
+      @provider.stub!(:run_command).and_return(true)
+    end
+
+    it "should call @net_group.local_delete" do
+      @net_group.should_receive(:local_delete)
+      @provider.remove_group
+    end
+  end
+end
+
+describe Chef::Provider::Group::Windows, "NetGroup" do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("Creating a new group")
+    @new_resource.group_name "Remote Desktop Users"
+  end
+  it 'sets group_name correctly' do
+    Chef::Util::Windows::NetGroup.should_receive(:new).with("Remote Desktop Users")
+    Chef::Provider::Group::Windows.new(@new_resource, @run_context)
+  end
+end
diff --git a/spec/unit/provider/group_spec.rb b/spec/unit/provider/group_spec.rb
new file mode 100644
index 0000000..caad987
--- /dev/null
+++ b/spec/unit/provider/group_spec.rb
@@ -0,0 +1,259 @@
+#
+# Author:: AJ Christensen (<aj at opscode.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'
+
+describe Chef::Provider::User do
+
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Group.new("wheel", @run_context)
+    @new_resource.gid 500
+    @new_resource.members "aj"
+
+    @provider = Chef::Provider::Group.new(@new_resource, @run_context)
+
+    @current_resource = Chef::Resource::Group.new("aj", @run_context)
+    @current_resource.gid 500
+    @current_resource.members "aj"
+
+    @provider.current_resource = @current_resource
+
+    @pw_group = mock("Struct::Group",
+      :name => "wheel",
+      :gid => 20,
+      :mem => [ "root", "aj" ]
+      )
+    Etc.stub!(:getgrnam).with('wheel').and_return(@pw_group)
+  end
+
+  it "assumes the group exists by default" do
+    @provider.group_exists.should be_true
+  end
+
+  describe "when establishing the current state of the group" do
+
+    it "sets the group name of the current resource to the group name of the new resource" do
+      @provider.load_current_resource
+      @provider.current_resource.group_name.should == 'wheel'
+    end
+
+    it "does not modify the desired gid if set" do
+      @provider.load_current_resource
+      @new_resource.gid.should == 500
+    end
+
+    it "sets the desired gid to the current gid if none is set" do
+      @new_resource.instance_variable_set(:@gid, nil)
+      @provider.load_current_resource
+      @new_resource.gid.should == 20
+    end
+
+    it "looks up the group in /etc/group with getgrnam" do
+      Etc.should_receive(:getgrnam).with(@new_resource.group_name).and_return(@pw_group)
+      @provider.load_current_resource
+      @provider.current_resource.gid.should == 20
+      @provider.current_resource.members.should == %w{root aj}
+    end
+
+    it "should flip the value of exists if it cannot be found in /etc/group" do
+      Etc.stub!(:getgrnam).and_raise(ArgumentError)
+      @provider.load_current_resource
+      @provider.group_exists.should be_false
+    end
+
+    it "should return the current resource" do
+      @provider.load_current_resource.should equal(@provider.current_resource)
+    end
+  end
+
+  describe "when determining if the system is already in the target state" do
+    [ :gid, :members ].each do |attribute|
+      it "should return true if #{attribute} doesn't match" do
+        @current_resource.stub!(attribute).and_return("looooooooooooooooooool")
+        @provider.compare_group.should be_true
+      end
+    end
+
+    it "should return false if gid and members are equal" do
+      @provider.compare_group.should be_false
+    end
+
+    it "should return false if append is true and the group member(s) already exists" do
+      @current_resource.members << "extra_user"
+      @new_resource.stub!(:append).and_return(true)
+      @provider.compare_group.should be_false
+    end
+
+    it "should return true if append is true and the group member(s) do not already exist" do
+      @new_resource.members << "extra_user"
+      @new_resource.stub!(:append).and_return(true)
+      @provider.compare_group.should be_true
+    end
+
+  end
+
+  describe "when creating a group" do
+    it "should call create_group if the group does not exist" do
+      @provider.group_exists = false
+      @provider.should_receive(:create_group).and_return(true)
+      @provider.run_action(:create)
+    end
+
+    it "should set the new_resources updated flag when it creates the group" do
+      @provider.group_exists = false
+      @provider.stub!(:create_group)
+      @provider.run_action(:create)
+      @provider.new_resource.should be_updated
+    end
+
+    it "should check to see if the group has mismatched attributes if the group exists" do
+      @provider.group_exists = true
+      @provider.stub!(:compare_group).and_return(false)
+      @provider.run_action(:create)
+      @provider.new_resource.should_not be_updated
+    end
+
+    it "should call manage_group if the group exists and has mismatched attributes" do
+      @provider.group_exists = true
+      @provider.stub!(:compare_group).and_return(true)
+      @provider.should_receive(:manage_group).and_return(true)
+      @provider.run_action(:create)
+    end
+
+    it "should set the new_resources updated flag when it creates the group if we call manage_group" do
+      @provider.group_exists = true
+      @provider.stub!(:compare_group).and_return(true)
+      @provider.stub!(:manage_group).and_return(true)
+      @provider.run_action(:create)
+      @new_resource.should be_updated
+    end
+  end
+
+  describe "when removing a group" do
+
+    it "should not call remove_group if the group does not exist" do
+      @provider.group_exists = false
+      @provider.should_not_receive(:remove_group)
+      @provider.run_action(:remove)
+      @provider.new_resource.should_not be_updated
+    end
+
+    it "should call remove_group if the group exists" do
+      @provider.group_exists = true
+      @provider.should_receive(:remove_group)
+      @provider.run_action(:remove)
+      @provider.new_resource.should be_updated
+    end
+  end
+
+  describe "when updating a group" do
+    before(:each) do
+      @provider.group_exists = true
+      @provider.stub!(:manage_group).and_return(true)
+    end
+
+    it "should run manage_group if the group exists and has mismatched attributes" do
+      @provider.should_receive(:compare_group).and_return(true)
+      @provider.should_receive(:manage_group).and_return(true)
+      @provider.run_action(:manage)
+    end
+
+    it "should set the new resources updated flag to true if manage_group is called" do
+      @provider.stub!(:compare_group).and_return(true)
+      @provider.stub!(:manage_group).and_return(true)
+      @provider.run_action(:manage)
+      @new_resource.should be_updated
+    end
+
+    it "should not run manage_group if the group does not exist" do
+      @provider.group_exists = false
+      @provider.should_not_receive(:manage_group)
+      @provider.run_action(:manage)
+    end
+
+    it "should not run manage_group if the group exists but has no differing attributes" do
+      @provider.should_receive(:compare_group).and_return(false)
+      @provider.should_not_receive(:manage_group)
+      @provider.run_action(:manage)
+    end
+  end
+
+  describe "when modifying the group" do
+    before(:each) do
+      @provider.group_exists = true
+      @provider.stub!(:manage_group).and_return(true)
+    end
+
+    it "should run manage_group if the group exists and has mismatched attributes" do
+      @provider.should_receive(:compare_group).and_return(true)
+      @provider.should_receive(:manage_group).and_return(true)
+      @provider.run_action(:modify)
+    end
+
+    it "should set the new resources updated flag to true if manage_group is called" do
+      @provider.stub!(:compare_group).and_return(true)
+      @provider.stub!(:manage_group).and_return(true)
+      @provider.run_action(:modify)
+      @new_resource.should be_updated
+    end
+
+    it "should not run manage_group if the group exists but has no differing attributes" do
+      @provider.should_receive(:compare_group).and_return(false)
+      @provider.should_not_receive(:manage_group)
+      @provider.run_action(:modify)
+    end
+
+    it "should raise a Chef::Exceptions::Group if the group doesn't exist" do
+      @provider.group_exists = false
+      lambda { @provider.run_action(:modify) }.should raise_error(Chef::Exceptions::Group)
+    end
+  end
+
+  describe "when determining the reason for a change" do
+    it "should report which group members are missing if members are missing and appending to the group" do
+       @new_resource.members << "user1"
+       @new_resource.members << "user2"
+       @new_resource.stub!(:append).and_return true
+       @provider.compare_group.should be_true
+       @provider.change_desc.should == "add missing member(s): user1, user2"
+    end
+
+    it "should report that the group members will be overwritten if not appending" do
+       @new_resource.members << "user1"
+       @new_resource.stub!(:append).and_return false
+       @provider.compare_group.should be_true
+       @provider.change_desc.should == "replace group members with new list of members"
+    end
+
+    it "should report the gid will be changed when it does not match" do
+      @current_resource.stub!(:gid).and_return("BADF00D")
+      @provider.compare_group.should be_true
+      @provider.change_desc.should == "change gid #{@current_resource.gid} to #{@new_resource.gid}"
+
+    end
+
+    it "should report no change reason when no change is required" do
+      @provider.compare_group.should be_false
+      @provider.change_desc.should == nil
+    end
+  end
+
+end
diff --git a/spec/unit/provider/http_request_spec.rb b/spec/unit/provider/http_request_spec.rb
new file mode 100644
index 0000000..e8ce5f9
--- /dev/null
+++ b/spec/unit/provider/http_request_spec.rb
@@ -0,0 +1,137 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Provider::HttpRequest do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::HttpRequest.new('adam')
+    @new_resource.name "adam"
+    @new_resource.url "http://www.opscode.com/"
+    @new_resource.message "is cool"
+
+    @provider = Chef::Provider::HttpRequest.new(@new_resource, @run_context)
+  end
+
+  describe "load_current_resource" do
+
+    it "should set up a Chef::REST client, with no authentication" do
+      Chef::HTTP::Simple.should_receive(:new).with(@new_resource.url)
+      @provider.load_current_resource
+    end
+  end
+
+  describe "when making REST calls" do
+    before(:each) do
+      # run_action(x) forces load_current_resource to run;
+      # that would overwrite our supplied mock Chef::Rest # object
+      @provider.stub!(:load_current_resource).and_return(true)
+      @http = mock("Chef::REST")
+      @provider.http = @http
+    end
+
+    describe "action_get" do
+
+      it "should inflate a message block at runtime" do
+        @new_resource.message { "return" }
+        @http.should_receive(:get).with("http://www.opscode.com/?message=return", {})
+        @provider.run_action(:get)
+        @new_resource.should be_updated
+      end
+
+      it "should run a GET request" do
+        @http.should_receive(:get).with("http://www.opscode.com/?message=is cool", {})
+        @provider.run_action(:get)
+        @new_resource.should be_updated
+      end
+    end
+
+    describe "action_put" do
+      it "should run a PUT request with the message as the payload" do
+        @http.should_receive(:put).with("http://www.opscode.com/", @new_resource.message, {})
+        @provider.run_action(:put)
+        @new_resource.should be_updated
+      end
+
+      it "should inflate a message block at runtime" do
+        @new_resource.stub!(:message).and_return(lambda { "return" })
+        @http.should_receive(:put).with("http://www.opscode.com/", "return", {})
+        @provider.run_action(:put)
+        @new_resource.should be_updated
+      end
+    end
+
+    describe "action_post" do
+      it "should run a PUT request with the message as the payload" do
+        @http.should_receive(:post).with("http://www.opscode.com/", @new_resource.message, {})
+        @provider.run_action(:post)
+        @new_resource.should be_updated
+      end
+
+      it "should inflate a message block at runtime" do
+        @new_resource.message { "return" }
+        @http.should_receive(:post).with("http://www.opscode.com/", "return", {})
+        @provider.run_action(:post)
+        @new_resource.should be_updated
+      end
+    end
+
+    describe "action_delete" do
+      it "should run a DELETE request" do
+        @http.should_receive(:delete).with("http://www.opscode.com/", {})
+        @provider.run_action(:delete)
+        @new_resource.should be_updated
+      end
+    end
+
+    describe "action_head" do
+      before do
+        @provider.http = @http
+      end
+
+      it "should inflate a message block at runtime" do
+        @new_resource.message { "return" }
+        @http.should_receive(:head).with("http://www.opscode.com/?message=return", {}).and_return("")
+        @provider.run_action(:head)
+        @new_resource.should be_updated
+      end
+
+      it "should run a HEAD request" do
+        @http.should_receive(:head).with("http://www.opscode.com/?message=is cool", {}).and_return("")
+        @provider.run_action(:head)
+        @new_resource.should be_updated
+      end
+
+      it "should run a HEAD request with If-Modified-Since header" do
+        @new_resource.headers "If-Modified-Since" => File.mtime(__FILE__).httpdate
+        @http.should_receive(:head).with("http://www.opscode.com/?message=is cool", @new_resource.headers)
+        @provider.run_action(:head)
+      end
+
+      it "doesn't call converge_by if HEAD does not return modified" do
+        @http.should_receive(:head).and_return(false)
+        @provider.should_not_receive(:converge_by)
+        @provider.run_action(:head)
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/ifconfig/aix_spec.rb b/spec/unit/provider/ifconfig/aix_spec.rb
new file mode 100644
index 0000000..39e9919
--- /dev/null
+++ b/spec/unit/provider/ifconfig/aix_spec.rb
@@ -0,0 +1,180 @@
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/exceptions'
+
+describe Chef::Provider::Ifconfig::Aix do
+
+  before(:all) do
+    @ifconfig_output = <<-IFCONFIG
+en1: flags=1e080863,480<UP,BROADCAST,NOTRAILERS,RUNNING,SIMPLEX,MULTICAST,GROUPRT,64BIT,CHECKSUM_OFFLOAD(ACTIVE),CHAIN>
+        inet 10.153.11.59 netmask 0xffff0000 broadcast 10.153.255.255
+         tcp_sendspace 262144 tcp_recvspace 262144 rfc1323 1
+en0: flags=1e080863,480<UP,BROADCAST,NOTRAILERS,RUNNING,SIMPLEX,MULTICAST,GROUPRT,64BIT,CHECKSUM_OFFLOAD(ACTIVE),CHAIN> metric 1
+        inet 172.29.174.58 netmask 0xffffc000 broadcast 172.29.191.255
+         tcp_sendspace 262144 tcp_recvspace 262144 rfc1323 1
+lo0: flags=e08084b,c0<UP,BROADCAST,LOOPBACK,RUNNING,SIMPLEX,MULTICAST,GROUPRT,64BIT,LARGESEND,CHAIN>
+        inet 127.0.0.1 netmask 0xff000000 broadcast 127.255.255.255
+        inet6 ::1%1/0
+IFCONFIG
+  end
+
+
+  before(:each) do
+    @node = Chef::Node.new
+    @cookbook_collection = Chef::CookbookCollection.new([])
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+    @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+
+    @provider = Chef::Provider::Ifconfig::Aix.new(@new_resource, @run_context)
+  end
+
+  describe "#load_current_resource" do
+    before do
+      status = double("Status", :exitstatus => 0)
+      @provider.should_receive(:popen4).with("ifconfig -a").and_yield(@pid, at stdin,StringIO.new(@ifconfig_output), at stderr).and_return(status)
+      @new_resource.device "en0"
+    end
+    it "should load given interface with attributes." do
+      current_resource = @provider.load_current_resource
+      expect(current_resource.inet_addr).to eq("172.29.174.58")
+      expect(current_resource.target).to eq(@new_resource.target)
+      expect(current_resource.mask).to eq("255.255.192.0")
+      expect(current_resource.bcast).to eq("172.29.191.255")
+      expect(current_resource.metric).to eq("1")
+    end
+  end
+
+  describe "#action_add" do
+
+    it "should add an interface if it does not exist" do
+      @new_resource.device "en10"
+      @provider.stub!(:load_current_resource) do
+        @provider.instance_variable_set("@status", double("Status", :exitstatus => 0))
+        @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context))
+      end
+      command = "chdev -l #{@new_resource.device} -a netaddr=#{@new_resource.name}"
+      @provider.should_receive(:run_command).with(:command => command)
+
+      @provider.run_action(:add)
+      @new_resource.should be_updated
+    end
+
+    it "should raise exception if metric attribute is set" do
+      @new_resource.device "en0"
+      @new_resource.metric "1"
+      @provider.stub!(:load_current_resource) do
+        @provider.instance_variable_set("@status", double("Status", :exitstatus => 0))
+        @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context))
+      end
+
+      expect{@provider.run_action(:add)}.to raise_error(Chef::Exceptions::Ifconfig, "interface metric attribute cannot be set for :add action")
+    end
+  end
+
+  describe "#action_enable" do
+    it "should enable an interface if it does not exist" do
+      @new_resource.device "en10"
+      @provider.stub!(:load_current_resource) do
+        @provider.instance_variable_set("@status", double("Status", :exitstatus => 0))
+        @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context))
+      end
+      command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+      @provider.should_receive(:run_command).with(:command => command)
+
+      @provider.run_action(:enable)
+      @new_resource.should be_updated
+    end
+  end
+
+  describe "#action_disable" do
+
+    it "should not disable an interface if it does not exist" do
+      @new_resource.device "en10"
+      @provider.stub!(:load_current_resource) do
+        @provider.instance_variable_set("@status", double("Status", :exitstatus => 0))
+        @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context))
+      end
+
+      @provider.should_not_receive(:run_command)
+
+      @provider.run_action(:disable)
+      @new_resource.should_not be_updated
+    end
+
+    context "interface exists" do
+      before do
+        @new_resource.device "en10"
+        @provider.stub!(:load_current_resource) do
+          @provider.instance_variable_set("@status", double("Status", :exitstatus => 0))
+          current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+          current_resource.device @new_resource.device
+          @provider.instance_variable_set("@current_resource", current_resource)
+        end
+      end
+
+      it "should disable an interface if it exists" do
+        command = "ifconfig #{@new_resource.device} down"
+        @provider.should_receive(:run_command).with(:command => command)
+
+        @provider.run_action(:disable)
+        @new_resource.should be_updated
+      end
+
+    end
+  end
+
+  describe "#action_delete" do
+
+    it "should not delete an interface if it does not exist" do
+      @new_resource.device "en10"
+      @provider.stub!(:load_current_resource) do
+        @provider.instance_variable_set("@status", double("Status", :exitstatus => 0))
+        @provider.instance_variable_set("@current_resource", Chef::Resource::Ifconfig.new("10.0.0.1", @run_context))
+      end
+
+      @provider.should_not_receive(:run_command)
+
+      @provider.run_action(:delete)
+      @new_resource.should_not be_updated
+    end
+
+    context "interface exists" do
+      before do
+        @new_resource.device "en10"
+        @provider.stub!(:load_current_resource) do
+          @provider.instance_variable_set("@status", double("Status", :exitstatus => 0))
+          current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+          current_resource.device @new_resource.device
+          @provider.instance_variable_set("@current_resource", current_resource)
+        end
+      end
+
+      it "should delete an interface if it exists" do
+        command = "chdev -l #{@new_resource.device} -a state=down"
+        @provider.should_receive(:run_command).with(:command => command)
+
+        @provider.run_action(:delete)
+        @new_resource.should be_updated
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb
new file mode 100644
index 0000000..617e840
--- /dev/null
+++ b/spec/unit/provider/ifconfig/debian_spec.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Xabier de Zuazo (xabier at onddo.com)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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'
+
+describe Chef::Provider::Ifconfig::Debian do
+  before do
+    @node = Chef::Node.new
+    @cookbook_collection = Chef::CookbookCollection.new([])
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    #This new_resource can be called anything --> it is not the same as in ifconfig.rb
+    @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+    @new_resource.mask "255.255.254.0"
+    @new_resource.metric "1"
+    @new_resource.mtu "1500"
+    @new_resource.device "eth0"
+    @provider = Chef::Provider::Ifconfig::Debian.new(@new_resource, @run_context)
+    @current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+
+    status = mock("Status", :exitstatus => 0)
+    @provider.instance_variable_set("@status", status)
+    @provider.current_resource = @current_resource
+    @provider.stub!(:load_current_resource)
+    @provider.stub!(:run_command)
+
+    @config_filename_ifaces = "/etc/network/interfaces"
+    @config_filename_ifcfg = "/etc/network/interfaces.d/ifcfg-#{@new_resource.device}"
+  end
+
+  describe "generate_config for action_add" do
+   before do
+    @config_file_ifaces = StringIO.new
+    @config_file_ifcfg = StringIO.new
+    FileUtils.should_receive(:cp)
+    File.should_receive(:new).with(@config_filename_ifaces).and_return(StringIO.new)
+    File.should_receive(:open).with(@config_filename_ifaces, "w").and_yield(@config_file_ifaces)
+    File.should_receive(:new).with(@config_filename_ifcfg, "w").and_return(@config_file_ifcfg)
+    File.should_receive(:exist?).with(@config_filename_ifaces).and_return(true)
+   end
+
+   it "should create network-scripts directory" do
+    File.should_receive(:directory?).with(File.dirname(@config_filename_ifcfg)).and_return(false)
+    Dir.should_receive(:mkdir).with(File.dirname(@config_filename_ifcfg))
+    @provider.run_action(:add)
+   end
+
+   it "should write configure network-scripts directory" do
+    File.should_receive(:directory?).with(File.dirname(@config_filename_ifcfg)).and_return(true)
+    @provider.run_action(:add)
+    @config_file_ifaces.string.should match(/^\s*source\s+\/etc\/network\/interfaces[.]d\/[*]\s*$/)
+   end
+
+   it "should write a network-script" do
+    File.should_receive(:directory?).with(File.dirname(@config_filename_ifcfg)).and_return(true)
+    @provider.run_action(:add)
+    @config_file_ifcfg.string.should match(/^iface eth0 inet static\s*$/)
+    @config_file_ifcfg.string.should match(/^\s+address 10\.0\.0\.1\s*$/)
+    @config_file_ifcfg.string.should match(/^\s+netmask 255\.255\.254\.0\s*$/)
+   end
+  end
+
+  describe "delete_config for action_delete" do
+
+    it "should delete network-script if it exists" do
+      @current_resource.device @new_resource.device
+      File.should_receive(:exist?).with(@config_filename_ifcfg).and_return(true)
+      FileUtils.should_receive(:rm_f).with(@config_filename_ifcfg, :verbose => false)
+
+      @provider.run_action(:delete)
+    end
+  end
+end
diff --git a/spec/unit/provider/ifconfig/redhat_spec.rb b/spec/unit/provider/ifconfig/redhat_spec.rb
new file mode 100644
index 0000000..b6f83ff
--- /dev/null
+++ b/spec/unit/provider/ifconfig/redhat_spec.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Xabier de Zuazo (xabier at onddo.com)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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'
+
+describe Chef::Provider::Ifconfig::Redhat do
+  before do
+    @node = Chef::Node.new
+    @cookbook_collection = Chef::CookbookCollection.new([])
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    #This new_resource can be called anything --> it is not the same as in ifconfig.rb
+    @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+    @new_resource.mask "255.255.254.0"
+    @new_resource.metric "1"
+    @new_resource.mtu "1500"
+    @new_resource.device "eth0"
+    @provider = Chef::Provider::Ifconfig::Redhat.new(@new_resource, @run_context)
+    @current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+
+    status = mock("Status", :exitstatus => 0)
+    @provider.instance_variable_set("@status", status)
+    @provider.current_resource = @current_resource
+ end
+
+  describe "generate_config for action_add" do
+
+     it "should write network-script for centos" do
+      @provider.stub!(:load_current_resource)
+      @provider.stub!(:run_command)
+      config_filename = "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+      config_file = StringIO.new
+      File.should_receive(:new).with(config_filename, "w").and_return(config_file)
+
+      @provider.run_action(:add)
+      config_file.string.should match(/^\s*DEVICE=eth0\s*$/)
+      config_file.string.should match(/^\s*IPADDR=10\.0\.0\.1\s*$/)
+      config_file.string.should match(/^\s*NETMASK=255\.255\.254\.0\s*$/)
+     end
+  end
+
+  describe "delete_config for action_delete" do
+
+    it "should delete network-script if it exists for centos" do
+      @current_resource.device @new_resource.device
+      @provider.stub!(:load_current_resource)
+      @provider.stub!(:run_command)
+      config_filename =  "/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}"
+      File.should_receive(:exist?).with(config_filename).and_return(true)
+      FileUtils.should_receive(:rm_f).with(config_filename, :verbose => false)
+
+      @provider.run_action(:delete)
+    end
+  end
+end
diff --git a/spec/unit/provider/ifconfig_spec.rb b/spec/unit/provider/ifconfig_spec.rb
new file mode 100644
index 0000000..f4fdc23
--- /dev/null
+++ b/spec/unit/provider/ifconfig_spec.rb
@@ -0,0 +1,180 @@
+#
+# Author:: Prajakta Purohit (prajakta at opscode.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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+require 'spec_helper'
+require 'chef/exceptions'
+
+describe Chef::Provider::Ifconfig do
+  before do
+    @node = Chef::Node.new
+    @cookbook_collection = Chef::CookbookCollection.new([])
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    #This new_resource can be called anything --> it is not the same as in ifconfig.rb
+    @new_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+    @new_resource.mask "255.255.254.0"
+    @new_resource.metric "1"
+    @new_resource.mtu "1500"
+    @new_resource.device "eth0"
+    @provider = Chef::Provider::Ifconfig.new(@new_resource, @run_context)
+    @current_resource = Chef::Resource::Ifconfig.new("10.0.0.1", @run_context)
+
+    status = mock("Status", :exitstatus => 0)
+    @provider.instance_variable_set("@status", status)
+    @provider.current_resource = @current_resource
+
+ end
+  describe Chef::Provider::Ifconfig, "load_current_resource" do
+    before do
+      status = mock("Status", :exitstatus => 1)
+      @provider.should_receive(:popen4).and_return status
+      @provider.load_current_resource
+    end
+    it "should track state of ifconfig failure." do
+      @provider.instance_variable_get("@status").exitstatus.should_not == 0
+    end
+    it "should thrown an exception when ifconfig fails" do
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error Chef::Exceptions::Ifconfig
+    end
+  end
+  describe Chef::Provider::Ifconfig, "action_add" do
+
+    it "should add an interface if it does not exist" do
+      #@provider.stub!(:run_command).and_return(true)
+      @provider.stub!(:load_current_resource)
+      @current_resource.inet_addr nil
+      command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500"
+      @provider.should_receive(:run_command).with(:command => command)
+      @provider.should_receive(:generate_config)
+
+      @provider.run_action(:add)
+      @new_resource.should be_updated
+    end
+
+    it "should not add an interface if it already exists" do
+      @provider.stub!(:load_current_resource)
+      @provider.should_not_receive(:run_command)
+      @current_resource.inet_addr "10.0.0.1"
+      @provider.should_not_receive(:generate_config)
+
+      @provider.run_action(:add)
+      @new_resource.should_not be_updated
+    end
+
+    #We are not testing this case with the assumption that anyone writing the cookbook would not make a typo == lo
+    #it "should add a blank command if the #{@new_resource.device} == lo" do
+    #end
+  end
+
+  describe Chef::Provider::Ifconfig, "action_enable" do
+
+    it "should enable interface if does not exist" do
+      @provider.stub!(:load_current_resource)
+      @current_resource.inet_addr nil
+      command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500"
+      @provider.should_receive(:run_command).with(:command => command)
+      @provider.should_not_receive(:generate_config)
+
+      @provider.run_action(:enable)
+      @new_resource.should be_updated
+    end
+
+    it "should not enable interface if it already exists" do
+      @provider.stub!(:load_current_resource)
+      @provider.should_not_receive(:run_command)
+      @current_resource.inet_addr "10.0.0.1"
+      @provider.should_not_receive(:generate_config)
+
+      @provider.run_action(:enable)
+      @new_resource.should_not be_updated
+    end
+  end
+
+  describe Chef::Provider::Ifconfig, "action_delete" do
+
+    it "should delete interface if it exists" do
+      @provider.stub!(:load_current_resource)
+      @current_resource.device "eth0"
+      command = "ifconfig #{@new_resource.device} down"
+      @provider.should_receive(:run_command).with(:command => command)
+      @provider.should_receive(:delete_config)
+
+      @provider.run_action(:delete)
+      @new_resource.should be_updated
+    end
+
+    it "should not delete interface if it does not exist" do
+      @provider.stub!(:load_current_resource)
+      @provider.should_not_receive(:run_command)
+      @provider.should_not_receive(:delete_config)
+
+      @provider.run_action(:delete)
+      @new_resource.should_not be_updated
+    end
+  end
+
+  describe Chef::Provider::Ifconfig, "action_disable" do
+
+    it "should disable interface if it exists" do
+      @provider.stub!(:load_current_resource)
+      @current_resource.device "eth0"
+      command = "ifconfig #{@new_resource.device} down"
+      @provider.should_receive(:run_command).with(:command => command)
+      @provider.should_not_receive(:delete_config)
+
+      @provider.run_action(:disable)
+      @new_resource.should be_updated
+    end
+
+    it "should not delete interface if it does not exist" do
+      @provider.stub!(:load_current_resource)
+      @provider.should_not_receive(:run_command)
+      @provider.should_not_receive(:delete_config)
+
+      @provider.run_action(:disable)
+      @new_resource.should_not be_updated
+    end
+  end
+
+  describe Chef::Provider::Ifconfig, "action_delete" do
+
+    it "should delete interface of it exists" do
+      @provider.stub!(:load_current_resource)
+      @current_resource.device "eth0"
+      command = "ifconfig #{@new_resource.device} down"
+      @provider.should_receive(:run_command).with(:command => command)
+      @provider.should_receive(:delete_config)
+
+      @provider.run_action(:delete)
+      @new_resource.should be_updated
+    end
+
+    it "should not delete interface if it does not exist" do
+      # This is so that our fake values do not get overwritten
+      @provider.stub!(:load_current_resource)
+      # This is so that nothing actually runs
+      @provider.should_not_receive(:run_command)
+      @provider.should_not_receive(:delete_config)
+
+      @provider.run_action(:delete)
+      @new_resource.should_not be_updated
+    end
+  end
+end
diff --git a/spec/unit/provider/link_spec.rb b/spec/unit/provider/link_spec.rb
new file mode 100644
index 0000000..bd32045
--- /dev/null
+++ b/spec/unit/provider/link_spec.rb
@@ -0,0 +1,252 @@
+#
+# Author:: AJ Christensen (<aj at junglist.gen.nz>)
+# Author:: John Keiser (<jkeiser at opscode.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 'ostruct'
+
+require 'spec_helper'
+
+if Chef::Platform.windows?
+  require 'chef/win32/file' #probably need this in spec_helper
+end
+
+describe Chef::Resource::Link, :not_supported_on_win2k3 do
+  let(:provider) do
+    node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    run_context = Chef::RunContext.new(node, {}, @events)
+    Chef::Provider::Link.new(new_resource, run_context)
+  end
+  let(:new_resource) do
+    result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link")
+    result.to "#{CHEF_SPEC_DATA}/fofile"
+    result
+  end
+
+  def canonicalize(path)
+    Chef::Platform.windows? ? path.gsub('/', '\\') : path
+  end
+
+  describe "when the target is a symlink" do
+    before(:each) do
+      lstat = mock("stats", :ino => 5)
+      lstat.stub!(:uid).and_return(501)
+      lstat.stub!(:gid).and_return(501)
+      lstat.stub!(:mode).and_return(0777)
+      File.stub!(:lstat).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(lstat)
+      provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+      provider.file_class.stub!(:readlink).with("#{CHEF_SPEC_DATA}/fofile-link").and_return("#{CHEF_SPEC_DATA}/fofile")
+    end
+
+    describe "to a file that exists" do
+      before do
+        File.stub(:exist?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+        new_resource.owner 501 # only loaded in current_resource if present in new
+        new_resource.group 501
+        provider.load_current_resource
+      end
+
+      it "should set the symlink target" do
+        provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+      end
+      it "should set the link type" do
+        provider.current_resource.link_type.should == :symbolic
+      end
+      it "should update the source of the existing link with the links target" do
+        provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+      end
+      it "should set the owner" do
+        provider.current_resource.owner.should == 501
+      end
+      it "should set the group" do
+        provider.current_resource.group.should == 501
+      end
+
+      # We test create in unit tests because there is no other way to ensure
+      # it does no work.  Other create and delete scenarios are covered in
+      # the functional tests for links.
+      context 'when the desired state is identical' do
+        let(:new_resource) do
+          result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link")
+          result.to "#{CHEF_SPEC_DATA}/fofile"
+          result
+        end
+        it 'create does no work' do
+          provider.access_controls.should_not_receive(:set_all)
+          provider.run_action(:create)
+        end
+      end
+    end
+
+    describe "to a file that doesn't exist" do
+      before do
+        File.stub!(:exist?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+        provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+        provider.file_class.stub!(:readlink).with("#{CHEF_SPEC_DATA}/fofile-link").and_return("#{CHEF_SPEC_DATA}/fofile")
+        new_resource.owner "501" # only loaded in current_resource if present in new
+        new_resource.group "501"
+        provider.load_current_resource
+      end
+
+      it "should set the symlink target" do
+        provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+      end
+      it "should set the link type" do
+        provider.current_resource.link_type.should == :symbolic
+      end
+      it "should update the source of the existing link to the link's target" do
+        provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+      end
+      it "should not set the owner" do
+        provider.current_resource.owner.should be_nil
+      end
+      it "should not set the group" do
+        provider.current_resource.group.should be_nil
+      end
+    end
+  end
+
+  describe "when the target doesn't exist" do
+    before do
+      File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+      provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+      provider.load_current_resource
+    end
+
+    it "should set the symlink target" do
+      provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+    end
+    it "should update the source of the existing link to nil" do
+      provider.current_resource.to.should be_nil
+    end
+    it "should not set the owner" do
+      provider.current_resource.owner.should == nil
+    end
+    it "should not set the group" do
+      provider.current_resource.group.should == nil
+    end
+  end
+
+  describe "when the target is a regular old file" do
+    before do
+      stat = mock("stats", :ino => 5)
+      stat.stub!(:uid).and_return(501)
+      stat.stub!(:gid).and_return(501)
+      stat.stub!(:mode).and_return(0755)
+      provider.file_class.stub!(:stat).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(stat)
+
+      File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(true)
+      provider.file_class.stub!(:symlink?).with("#{CHEF_SPEC_DATA}/fofile-link").and_return(false)
+    end
+
+    describe "and the source does not exist" do
+      before do
+        File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(false)
+        provider.load_current_resource
+      end
+
+      it "should set the symlink target" do
+        provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+      end
+      it "should update the current source of the existing link with an empty string" do
+        provider.current_resource.to.should == ''
+      end
+      it "should not set the owner" do
+        provider.current_resource.owner.should == nil
+      end
+      it "should not set the group" do
+        provider.current_resource.group.should == nil
+      end
+    end
+
+    describe "and the source exists" do
+      before do
+        stat = mock("stats", :ino => 6)
+        stat.stub!(:uid).and_return(502)
+        stat.stub!(:gid).and_return(502)
+        stat.stub!(:mode).and_return(0644)
+
+        provider.file_class.stub!(:stat).with("#{CHEF_SPEC_DATA}/fofile").and_return(stat)
+
+        File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(true)
+        provider.load_current_resource
+      end
+
+      it "should set the symlink target" do
+        provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+      end
+      it "should update the current source of the existing link with an empty string" do
+        provider.current_resource.to.should == ''
+      end
+      it "should not set the owner" do
+        provider.current_resource.owner.should == nil
+      end
+      it "should not set the group" do
+        provider.current_resource.group.should == nil
+      end
+    end
+
+    describe "and is hardlinked to the source" do
+      before do
+        stat = mock("stats", :ino => 5)
+        stat.stub!(:uid).and_return(502)
+        stat.stub!(:gid).and_return(502)
+        stat.stub!(:mode).and_return(0644)
+
+        provider.file_class.stub!(:stat).with("#{CHEF_SPEC_DATA}/fofile").and_return(stat)
+
+        File.stub!(:exists?).with("#{CHEF_SPEC_DATA}/fofile").and_return(true)
+        provider.load_current_resource
+      end
+
+      it "should set the symlink target" do
+        provider.current_resource.target_file.should == "#{CHEF_SPEC_DATA}/fofile-link"
+      end
+      it "should set the link type" do
+        provider.current_resource.link_type.should == :hard
+      end
+      it "should update the source of the existing link to the link's target" do
+        provider.current_resource.to.should == canonicalize("#{CHEF_SPEC_DATA}/fofile")
+      end
+      it "should not set the owner" do
+        provider.current_resource.owner.should == nil
+      end
+      it "should not set the group" do
+        provider.current_resource.group.should == nil
+      end
+
+      # We test create in unit tests because there is no other way to ensure
+      # it does no work.  Other create and delete scenarios are covered in
+      # the functional tests for links.
+      context 'when the desired state is identical' do
+        let(:new_resource) do
+          result = Chef::Resource::Link.new("#{CHEF_SPEC_DATA}/fofile-link")
+          result.to "#{CHEF_SPEC_DATA}/fofile"
+          result.link_type :hard
+          result
+        end
+        it 'create does no work' do
+          provider.file_class.should_not_receive(:symlink)
+          provider.file_class.should_not_receive(:link)
+          provider.access_controls.should_not_receive(:set_all)
+          provider.run_action(:create)
+        end
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/log_spec.rb b/spec/unit/provider/log_spec.rb
new file mode 100644
index 0000000..f6ff526
--- /dev/null
+++ b/spec/unit/provider/log_spec.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Cary Penniman (<cary at rightscale.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'
+
+describe Chef::Provider::Log::ChefLog do
+
+  before(:each) do
+    @log_str = "this is my test string to log"
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+  end
+
+  it "should be registered with the default platform hash" do
+    Chef::Platform.platforms[:default][:log].should_not be_nil
+  end
+
+  it "should write the string to the Chef::Log object at default level (info)" do
+      @new_resource = Chef::Resource::Log.new(@log_str)
+      @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+      Chef::Log.should_receive(:info).with(@log_str).and_return(true)
+      @provider.action_write
+  end
+
+  it "should write the string to the Chef::Log object at debug level" do
+      @new_resource = Chef::Resource::Log.new(@log_str)
+      @new_resource.level :debug
+      @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+      Chef::Log.should_receive(:debug).with(@log_str).and_return(true)
+      @provider.action_write
+  end
+
+  it "should write the string to the Chef::Log object at info level" do
+      @new_resource = Chef::Resource::Log.new(@log_str)
+      @new_resource.level :info
+      @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+      Chef::Log.should_receive(:info).with(@log_str).and_return(true)
+      @provider.action_write
+  end
+
+  it "should write the string to the Chef::Log object at warn level" do
+      @new_resource = Chef::Resource::Log.new(@log_str)
+      @new_resource.level :warn
+      @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+      Chef::Log.should_receive(:warn).with(@log_str).and_return(true)
+      @provider.action_write
+  end
+
+  it "should write the string to the Chef::Log object at error level" do
+      @new_resource = Chef::Resource::Log.new(@log_str)
+      @new_resource.level :error
+      @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+      Chef::Log.should_receive(:error).with(@log_str).and_return(true)
+      @provider.action_write
+  end
+
+  it "should write the string to the Chef::Log object at fatal level" do
+      @new_resource = Chef::Resource::Log.new(@log_str)
+      @new_resource.level :fatal
+      @provider = Chef::Provider::Log::ChefLog.new(@new_resource, @run_context)
+      Chef::Log.should_receive(:fatal).with(@log_str).and_return(true)
+      @provider.action_write
+  end
+
+end
diff --git a/spec/unit/provider/mdadm_spec.rb b/spec/unit/provider/mdadm_spec.rb
new file mode 100644
index 0000000..25991e4
--- /dev/null
+++ b/spec/unit/provider/mdadm_spec.rb
@@ -0,0 +1,131 @@
+#
+# 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");
+# 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 'ostruct'
+
+describe Chef::Provider::Mdadm do
+
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Mdadm.new('/dev/md1')
+    @new_resource.devices ["/dev/sdz1","/dev/sdz2","/dev/sdz3"]
+    @provider = Chef::Provider::Mdadm.new(@new_resource, @run_context)
+  end
+
+  describe "when determining the current metadevice status" do
+    it "should set the current resources mount point to the new resources mount point" do
+      @provider.stub!(:shell_out!).and_return(OpenStruct.new(:status => 0))
+      @provider.load_current_resource
+      @provider.current_resource.name.should == '/dev/md1'
+      @provider.current_resource.raid_device.should == '/dev/md1'
+    end
+
+    it "determines that the metadevice exists when mdadm exit code is zero" do
+      @provider.stub!(:shell_out!).with("mdadm --detail --test /dev/md1", :returns => [0,4]).and_return(OpenStruct.new(:status => 0))
+      @provider.load_current_resource
+      @provider.current_resource.exists.should be_true
+    end
+
+    it "determines that the metadevice does not exist when mdadm exit code is 4" do
+      @provider.stub!(:shell_out!).with("mdadm --detail --test /dev/md1", :returns => [0,4]).and_return(OpenStruct.new(:status => 4))
+      @provider.load_current_resource
+      @provider.current_resource.exists.should be_false
+    end
+  end
+
+  describe "after the metadevice status is known" do
+    before(:each) do
+      @current_resource = Chef::Resource::Mdadm.new('/dev/md1')
+      @new_resource.level 5
+      @provider.stub!(:load_current_resource).and_return(true)
+      @provider.current_resource = @current_resource
+    end
+
+    describe "when creating the metadevice" do
+      it "should create the raid device if it doesnt exist" do
+        @current_resource.exists(false)
+        expected_command = "yes | mdadm --create /dev/md1 --level 5 --chunk=16 --metadata=0.90 --raid-devices 3 /dev/sdz1 /dev/sdz2 /dev/sdz3"
+        @provider.should_receive(:shell_out!).with(expected_command)
+        @provider.run_action(:create)
+      end
+
+      it "should specify a bitmap only if set" do
+        @current_resource.exists(false)
+        @new_resource.bitmap('grow')
+        expected_command = "yes | mdadm --create /dev/md1 --level 5 --chunk=16 --metadata=0.90 --bitmap=grow --raid-devices 3 /dev/sdz1 /dev/sdz2 /dev/sdz3"
+        @provider.should_receive(:shell_out!).with(expected_command)
+        @provider.run_action(:create)
+        @new_resource.should be_updated_by_last_action
+      end
+
+      it "should not specify a chunksize if raid level 1" do
+        @current_resource.exists(false)
+        @new_resource.level 1
+        expected_command = "yes | mdadm --create /dev/md1 --level 1 --metadata=0.90 --raid-devices 3 /dev/sdz1 /dev/sdz2 /dev/sdz3"
+        @provider.should_receive(:shell_out!).with(expected_command)
+        @provider.run_action(:create)
+        @new_resource.should be_updated_by_last_action
+      end
+
+      it "should not create the raid device if it does exist" do
+        @current_resource.exists(true)
+        @provider.should_not_receive(:shell_out!)
+        @provider.run_action(:create)
+        @new_resource.should_not be_updated_by_last_action
+      end
+    end
+
+    describe "when asembling the metadevice" do
+      it "should assemble the raid device if it doesnt exist" do
+        @current_resource.exists(false)
+        expected_mdadm_cmd = "yes | mdadm --assemble /dev/md1 /dev/sdz1 /dev/sdz2 /dev/sdz3"
+        @provider.should_receive(:shell_out!).with(expected_mdadm_cmd)
+        @provider.run_action(:assemble)
+        @new_resource.should be_updated_by_last_action
+      end
+
+        it "should not assemble the raid device if it doesnt exist" do
+        @current_resource.exists(true)
+        @provider.should_not_receive(:shell_out!)
+        @provider.run_action(:assemble)
+        @new_resource.should_not be_updated_by_last_action
+      end
+    end
+
+    describe "when stopping the metadevice" do
+
+      it "should stop the raid device if it exists" do
+        @current_resource.exists(true)
+        expected_mdadm_cmd = "yes | mdadm --stop /dev/md1"
+        @provider.should_receive(:shell_out!).with(expected_mdadm_cmd)
+        @provider.run_action(:stop)
+        @new_resource.should be_updated_by_last_action
+      end
+
+      it "should not attempt to stop the raid device if it does not exist" do
+        @current_resource.exists(false)
+        @provider.should_not_receive(:shell_out!)
+        @provider.run_action(:stop)
+        @new_resource.should_not be_updated_by_last_action
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/mount/aix_spec.rb b/spec/unit/provider/mount/aix_spec.rb
new file mode 100644
index 0000000..89de470
--- /dev/null
+++ b/spec/unit/provider/mount/aix_spec.rb
@@ -0,0 +1,237 @@
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh at clogeny.com>)
+# Copyright:: Copyright (c) 2013 OpsCode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'ostruct'
+
+describe Chef::Provider::Mount::Aix do
+
+  before(:all) do
+    @mounted_output = <<-MOUNT
+  node       mounted        mounted over    vfs       date        options
+-------- ---------------  ---------------  ------ ------------ ---------------
+         /dev/sdz1         /tmp/foo         jfs2   Jul 17 13:22 rw,log=/dev/hd8
+MOUNT
+
+    @unmounted_output = <<-UNMOUNTED
+  node       mounted        mounted over    vfs       date        options
+-------- ---------------  ---------------  ------ ------------ ---------------
+         /dev/sdz2         /                jfs2   Jul 17 13:22 rw,log=/dev/hd8
+UNMOUNTED
+
+    @conflict_mounted_output = <<-MOUNT
+  node       mounted        mounted over    vfs       date        options
+-------- ---------------  ---------------  ------ ------------ ---------------
+         /dev/sdz3         /tmp/foo         jfs2   Jul 17 13:22 rw,log=/dev/hd8
+MOUNT
+
+  @enabled_output = <<-ENABLED
+#MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct
+/tmp/foo:/dev/sdz1:jfs2::bootfs:10485760:rw:yes:no
+ENABLED
+  end
+
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Mount.new("/tmp/foo")
+    @new_resource.device      "/dev/sdz1"
+    @new_resource.device_type :device
+    @new_resource.fstype      "jfs2"
+
+    @new_resource.supports :remount => false
+
+    @provider = Chef::Provider::Mount::Aix.new(@new_resource, @run_context)
+
+    ::File.stub!(:exists?).with("/dev/sdz1").and_return true
+    ::File.stub!(:exists?).with("/tmp/foo").and_return true
+  end
+
+  def stub_mounted(provider, mounted_output)
+    response = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => mounted_output, :stderr => "")
+    provider.should_receive(:shell_out!).with("mount").and_return(response)
+  end
+
+  def stub_enabled(provider, enabled_output)
+    response = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => enabled_output, :stderr => "")
+    provider.should_receive(:shell_out).with("lsfs -c #{@new_resource.mount_point}").and_return(response)
+  end
+
+  def stub_mounted_enabled(provider, mounted_output, enabled_output)
+    stub_mounted(provider, mounted_output)
+    stub_enabled(provider, enabled_output)
+  end
+
+  describe "when discovering the current fs state" do
+    it "should set current_resource.mounted to true if device is already mounted" do
+      stub_mounted_enabled(@provider, @mounted_output, "")
+      @provider.load_current_resource
+
+      expect(@provider.current_resource.mounted).to be_true
+    end
+
+    it "should set current_resource.mounted to false if device is not mounted" do
+      stub_mounted_enabled(@provider, @unmounted_output, "")
+
+      @provider.load_current_resource
+
+      expect(@provider.current_resource.mounted).to be_false
+    end
+
+    it "should set current_resource.mounted to false if the mount point is used for another device" do
+      stub_mounted_enabled(@provider, @conflict_mounted_output, "")
+
+      @provider.load_current_resource
+
+      expect(@provider.current_resource.mounted).to be_false
+    end
+  end
+
+  # tests for #enabled?
+  it "should load current_resource with properties if device is already mounted and enabled" do
+    stub_mounted_enabled(@provider, @mounted_output, @enabled_output)
+
+    @provider.load_current_resource
+
+    expect(@provider.current_resource.enabled).to be_true
+    expect(@provider.current_resource.mounted).to be_true
+    expect(@provider.current_resource.mount_point).to eql(@new_resource.mount_point)
+    expect(@provider.current_resource.fstype).to eql("jfs2")
+    expect(@provider.current_resource.options).to eql(['rw'])
+  end
+
+  describe "mount_fs" do
+    it "should mount resource if it is not mounted" do
+      stub_mounted_enabled(@provider, @unmounted_output, "")
+
+      @provider.should_receive(:shell_out!).with("mount -v #{@new_resource.fstype} #{@new_resource.device} #{@new_resource.mount_point}")
+
+      @provider.run_action(:mount)
+    end
+
+    it "should not mount resource if it is already mounted" do
+      stub_mounted_enabled(@provider, @mounted_output, "")
+
+      @provider.should_not_receive(:mount_fs)
+
+      @provider.run_action(:mount)
+    end
+  end
+
+  describe "umount_fs" do
+    it "should umount resource if it is already mounted" do
+      stub_mounted_enabled(@provider, @mounted_output, "")
+
+      @provider.should_receive(:shell_out!).with("umount #{@new_resource.mount_point}")
+
+      @provider.run_action(:umount)
+    end
+
+    it "should not umount resource if it is not mounted" do
+      stub_mounted_enabled(@provider, @unmounted_output, "")
+
+      @provider.should_not_receive(:umount_fs)
+
+      @provider.run_action(:umount)
+    end
+  end
+
+  describe "remount_fs" do
+    it "should remount resource if it is already mounted and it supports remounting" do
+      @new_resource.supports({:remount => true})
+      stub_mounted_enabled(@provider, @mounted_output, "")
+
+      @provider.should_receive(:shell_out!).with("mount -o remount #{@new_resource.device} #{@new_resource.mount_point}")
+
+      @provider.run_action(:remount)
+    end
+
+    it "should remount with new mount options if it is already mounted and it supports remounting" do
+      @new_resource.supports({:remount => true})
+      @new_resource.options("nodev,rw")
+      stub_mounted_enabled(@provider, @mounted_output, "")
+
+      @provider.should_receive(:shell_out!).with("mount -o remount,nodev,rw #{@new_resource.device} #{@new_resource.mount_point}")
+
+      @provider.run_action(:remount)
+    end
+  end
+
+  describe "enable_fs" do
+    it "should enable mount if it is mounted and not enabled" do
+      @new_resource.options("nodev,rw")
+      stub_mounted_enabled(@provider, @mounted_output, "")
+      filesystems = StringIO.new
+      ::File.stub!(:open).with("/etc/filesystems", "a").and_yield(filesystems)
+
+      @provider.run_action(:enable)
+
+      filesystems.string.should match(%r{^/tmp/foo:\n\tdev\t\t= /dev/sdz1\n\tvfs\t\t= jfs2\n\tmount\t\t= false\n\toptions\t\t= nodev,rw\n$})
+    end
+
+    it "should not enable mount if it is mounted and already enabled and mount options are unchanged" do
+      stub_mounted_enabled(@provider, @mounted_output, @enabled_output)
+      @new_resource.options "rw"
+
+      @provider.should_not_receive(:enable_fs)
+
+      @provider.run_action(:enable)
+    end
+  end
+
+  describe "disable_fs" do
+    it "should disable mount if it is mounted and enabled" do
+      stub_mounted_enabled(@provider, @mounted_output, @enabled_output)
+
+      ::File.stub!(:open).with("/etc/filesystems", "r").and_return(<<-ETCFILESYSTEMS)
+/tmp/foo:
+  dev   = /dev/sdz1
+  vfs   = jfs2
+  log   = /dev/hd8
+  mount   = true
+  check   = true
+  vol   = /opt
+  free    = false
+  quota   = no
+
+/tmp/abc:
+  dev   = /dev/sdz2
+  vfs   = jfs2
+  mount   = true
+  options   = rw
+ETCFILESYSTEMS
+
+      filesystems = StringIO.new
+      ::File.stub!(:open).with("/etc/filesystems", "w").and_yield(filesystems)
+
+      @provider.run_action(:disable)
+
+      filesystems.string.should match(%r{^/tmp/abc:\s+dev\s+= /dev/sdz2\s+vfs\s+= jfs2\s+mount\s+= true\s+options\s+= rw\n$})
+    end
+
+    it "should not disable mount if it is not mounted" do
+      stub_mounted_enabled(@provider, @unmounted_output, "")
+
+      @provider.should_not_receive(:disable_fs)
+
+      @provider.run_action(:disable)
+    end
+  end
+end
diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb
new file mode 100644
index 0000000..cf0e879
--- /dev/null
+++ b/spec/unit/provider/mount/mount_spec.rb
@@ -0,0 +1,431 @@
+#
+# Author:: Joshua Timberman (<joshua at opscode.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 'ostruct'
+
+describe Chef::Provider::Mount::Mount do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Mount.new("/tmp/foo")
+    @new_resource.device      "/dev/sdz1"
+    @new_resource.device_type :device
+    @new_resource.fstype      "ext3"
+
+    @new_resource.supports :remount => false
+
+    @provider = Chef::Provider::Mount::Mount.new(@new_resource, @run_context)
+
+    ::File.stub!(:exists?).with("/dev/sdz1").and_return true
+    ::File.stub!(:exists?).with("/tmp/foo").and_return true
+    ::File.stub!(:realpath).with("/dev/sdz1").and_return "/dev/sdz1"
+    ::File.stub!(:realpath).with("/tmp/foo").and_return "/tmp/foo"
+  end
+
+  describe "when discovering the current fs state" do
+    before do
+      @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => ''))
+      ::File.stub!(:foreach).with("/etc/fstab")
+    end
+
+    it "should create a current resource with the same mount point and device" do
+      @provider.load_current_resource
+      @provider.current_resource.name.should == '/tmp/foo'
+      @provider.current_resource.mount_point.should == '/tmp/foo'
+      @provider.current_resource.device.should == '/dev/sdz1'
+    end
+
+    it "should accecpt device_type :uuid" do
+      @new_resource.device_type :uuid
+      @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a"
+      @stdout_findfs = mock("STDOUT", :first => "/dev/sdz1")
+      @provider.should_receive(:popen4).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_yield(@pid, at stdin, at stdout_findfs, at stderr).and_return(@status)
+      @provider.load_current_resource()
+      @provider.mountable?
+    end
+
+    describe "when dealing with network mounts" do
+      { "nfs" => "nfsserver:/vol/path",
+        "cifs" => "//cifsserver/share" }.each do |type, fs_spec|
+        it "should detect network fs_spec (#{type})" do
+          @new_resource.device fs_spec
+          @provider.network_device?.should be_true
+        end
+
+        it "should ignore trailing slash and set mounted to true for network mount (#{type})" do
+          @new_resource.device fs_spec
+          @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => "#{fs_spec}/ on /tmp/foo type #{type} (rw)\n"))
+          @provider.load_current_resource
+          @provider.current_resource.mounted.should be_true
+        end
+      end
+    end
+
+    it "should raise an error if the mount device does not exist" do
+      ::File.stub!(:exists?).with("/dev/sdz1").and_return false
+      lambda { @provider.load_current_resource();@provider.mountable? }.should raise_error(Chef::Exceptions::Mount)
+    end
+
+    it "should not call mountable? with load_current_resource - CHEF-1565" do
+      ::File.stub!(:exists?).with("/dev/sdz1").and_return false
+      @provider.should_receive(:mounted?).and_return(true)
+      @provider.should_receive(:enabled?).and_return(true)
+      @provider.should_not_receive(:mountable?)
+      @provider.load_current_resource
+    end
+
+    it "should raise an error if the mount device (uuid) does not exist" do
+      @new_resource.device_type :uuid
+      @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a"
+      status_findfs = mock("Status", :exitstatus => 1)
+      stdout_findfs = mock("STDOUT", :first => nil)
+      @provider.should_receive(:popen4).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_yield(@pid, at stdin,stdout_findfs, at stderr).and_return(status_findfs)
+      ::File.should_receive(:exists?).with("").and_return(false)
+      lambda { @provider.load_current_resource();@provider.mountable? }.should raise_error(Chef::Exceptions::Mount)
+    end
+
+    it "should raise an error if the mount point does not exist" do
+      ::File.stub!(:exists?).with("/tmp/foo").and_return false
+      lambda { @provider.load_current_resource();@provider.mountable? }.should raise_error(Chef::Exceptions::Mount)
+    end
+
+    it "does not expect the device to exist for tmpfs" do
+      @new_resource.fstype("tmpfs")
+      @new_resource.device("whatever")
+      lambda { @provider.load_current_resource();@provider.mountable? }.should_not raise_error
+    end
+
+    it "does not expect the device to exist for Fuse filesystems" do
+      @new_resource.fstype("fuse")
+      @new_resource.device("nilfs#xxx")
+      lambda { @provider.load_current_resource();@provider.mountable? }.should_not raise_error
+    end
+
+    it "does not expect the device to exist if it's none" do
+      @new_resource.device("none")
+      lambda { @provider.load_current_resource();@provider.mountable? }.should_not raise_error
+    end
+
+    it "should set mounted true if the mount point is found in the mounts list" do
+      @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => '/dev/sdz1 on /tmp/foo'))
+      @provider.load_current_resource()
+      @provider.current_resource.mounted.should be_true
+    end
+
+    it "should set mounted true if the symlink target of the device is found in the mounts list" do
+      target = "/dev/mapper/target"
+
+      ::File.stub!(:symlink?).with("#{@new_resource.device}").and_return(true)
+      ::File.stub!(:readlink).with("#{@new_resource.device}").and_return(target)
+
+      @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/mapper/target on /tmp/foo type ext3 (rw)\n"))
+      @provider.load_current_resource()
+      @provider.current_resource.mounted.should be_true
+    end
+
+    it "should set mounted true if the mount point is found last in the mounts list" do
+      mount = "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n"
+      mount << "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n"
+
+      @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => mount))
+      @provider.load_current_resource()
+      @provider.current_resource.mounted.should be_true
+    end
+
+    it "should set mounted false if the mount point is not last in the mounts list" do
+      mount = "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n"
+      mount << "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n"
+
+      @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => mount))
+      @provider.load_current_resource()
+      @provider.current_resource.mounted.should be_false
+    end
+
+    it "mounted should be false if the mount point is not found in the mounts list" do
+      @provider.stub!(:shell_out!).and_return(OpenStruct.new(:stdout => "/dev/sdy1 on /tmp/foo type ext3 (rw)\n"))
+      @provider.load_current_resource()
+      @provider.current_resource.mounted.should be_false
+    end
+
+    it "should set enabled to true if the mount point is last in fstab" do
+      fstab1 = "/dev/sdy1  /tmp/foo  ext3  defaults  1 2\n"
+      fstab2 = "#{@new_resource.device} #{@new_resource.mount_point}  ext3  defaults  1 2\n"
+
+      ::File.stub!(:foreach).with("/etc/fstab").and_yield(fstab1).and_yield(fstab2)
+
+      @provider.load_current_resource
+      @provider.current_resource.enabled.should be_true
+    end
+
+    it "should set enabled to true if the mount point is not last in fstab and mount_point is a substring of another mount" do
+      fstab1 = "#{@new_resource.device} #{@new_resource.mount_point}  ext3  defaults  1 2\n"
+      fstab2 = "/dev/sdy1  /tmp/foo/bar  ext3  defaults  1 2\n"
+
+      ::File.stub!(:foreach).with("/etc/fstab").and_yield(fstab1).and_yield(fstab2)
+
+      @provider.load_current_resource
+      @provider.current_resource.enabled.should be_true
+    end
+
+    it "should set enabled to true if the symlink target is in fstab" do
+      target = "/dev/mapper/target"
+
+      ::File.stub!(:symlink?).with("#{@new_resource.device}").and_return(true)
+      ::File.stub!(:readlink).with("#{@new_resource.device}").and_return(target)
+
+      fstab = "/dev/sdz1  /tmp/foo ext3  defaults  1 2\n"
+
+      ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+
+      @provider.load_current_resource
+      @provider.current_resource.enabled.should be_true
+    end
+
+    it "should set enabled to false if the mount point is not in fstab" do
+      fstab = "/dev/sdy1  #{@new_resource.mount_point}  ext3  defaults  1 2\n"
+      ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+
+      @provider.load_current_resource
+      @provider.current_resource.enabled.should be_false
+    end
+
+    it "should ignore commented lines in fstab " do
+       fstab = "\# #{@new_resource.device}  #{@new_resource.mount_point}  ext3  defaults  1 2\n"
+       ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+
+       @provider.load_current_resource
+       @provider.current_resource.enabled.should be_false
+     end
+
+    it "should set enabled to false if the mount point is not last in fstab" do
+      line_1 = "#{@new_resource.device} #{@new_resource.mount_point}  ext3  defaults  1 2\n"
+      line_2 = "/dev/sdy1 #{@new_resource.mount_point}  ext3  defaults  1 2\n"
+      ::File.stub!(:foreach).with("/etc/fstab").and_yield(line_1).and_yield(line_2)
+
+      @provider.load_current_resource
+      @provider.current_resource.enabled.should be_false
+    end
+
+    it "should not mangle the mount options if the device in fstab is a symlink" do
+      target = "/dev/mapper/target"
+      options = "rw,noexec,noauto"
+
+      ::File.stub!(:symlink?).with(@new_resource.device).and_return(true)
+      ::File.stub!(:readlink).with(@new_resource.device).and_return(target)
+
+      fstab = "#{@new_resource.device} #{@new_resource.mount_point} #{@new_resource.fstype} #{options} 1 2\n"
+      ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+      @provider.load_current_resource
+      @provider.current_resource.options.should eq(options.split(','))
+    end
+
+    it "should not mangle the mount options if the symlink target is in fstab" do
+      target = "/dev/mapper/target"
+      options = "rw,noexec,noauto"
+
+      ::File.stub!(:symlink?).with(@new_resource.device).and_return(true)
+      ::File.stub!(:readlink).with(@new_resource.device).and_return(target)
+
+      fstab = "#{target} #{@new_resource.mount_point} #{@new_resource.fstype} #{options} 1 2\n"
+      ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab
+      @provider.load_current_resource
+      @provider.current_resource.options.should eq(options.split(','))
+    end
+  end
+
+  context "after the mount's state has been discovered" do
+    before do
+      @current_resource = Chef::Resource::Mount.new("/tmp/foo")
+      @current_resource.device       "/dev/sdz1"
+      @current_resource.device_type  :device
+      @current_resource.fstype       "ext3"
+
+      @provider.current_resource = @current_resource
+    end
+
+    describe "mount_fs" do
+      it "should mount the filesystem if it is not mounted" do
+        @provider.rspec_reset
+        @provider.should_receive(:shell_out!).with("mount -t ext3 -o defaults /dev/sdz1 /tmp/foo")
+        @provider.mount_fs()
+      end
+
+      it "should mount the filesystem with options if options were passed" do
+        options = "rw,noexec,noauto"
+        @new_resource.options(%w{rw noexec noauto})
+        @provider.should_receive(:shell_out!).with("mount -t ext3 -o rw,noexec,noauto /dev/sdz1 /tmp/foo")
+        @provider.mount_fs()
+      end
+
+      it "should mount the filesystem specified by uuid" do
+        @new_resource.device "d21afe51-a0fe-4dc6-9152-ac733763ae0a"
+        @new_resource.device_type :uuid
+        @stdout_findfs = mock("STDOUT", :first => "/dev/sdz1")
+        @provider.stub!(:popen4).with("/sbin/findfs UUID=d21afe51-a0fe-4dc6-9152-ac733763ae0a").and_yield(@pid, at stdin, at stdout_findfs, at stderr).and_return(@status)
+        @stdout_mock = mock('stdout mock')
+        @stdout_mock.stub!(:each).and_yield("#{@new_resource.device} on #{@new_resource.mount_point}")
+        @provider.should_receive(:shell_out!).with("mount -t #{@new_resource.fstype} -o defaults -U #{@new_resource.device} #{@new_resource.mount_point}").and_return(@stdout_mock)
+        @provider.mount_fs()
+      end
+
+      it "should not mount the filesystem if it is mounted" do
+        @current_resource.stub!(:mounted).and_return(true)
+        @provider.should_not_receive(:shell_out!)
+        @provider.mount_fs()
+      end
+
+    end
+
+    describe "umount_fs" do
+      it "should umount the filesystem if it is mounted" do
+        @current_resource.mounted(true)
+        @provider.should_receive(:shell_out!).with("umount /tmp/foo")
+        @provider.umount_fs()
+      end
+
+      it "should not umount the filesystem if it is not mounted" do
+        @current_resource.mounted(false)
+        @provider.should_not_receive(:shell_out!)
+        @provider.umount_fs()
+      end
+    end
+
+    describe "remount_fs" do
+      it "should use mount -o remount if remount is supported" do
+        @new_resource.supports({:remount => true})
+        @current_resource.mounted(true)
+        @provider.should_receive(:shell_out!).with("mount -o remount #{@new_resource.mount_point}")
+        @provider.remount_fs
+      end
+
+      it "should umount and mount if remount is not supported" do
+        @new_resource.supports({:remount => false})
+        @current_resource.mounted(true)
+        @provider.should_receive(:umount_fs)
+        @provider.should_receive(:sleep).with(1)
+        @provider.should_receive(:mount_fs)
+        @provider.remount_fs()
+      end
+
+      it "should not try to remount at all if mounted is false" do
+        @current_resource.mounted(false)
+        @provider.should_not_receive(:shell_out!)
+        @provider.should_not_receive(:umount_fs)
+        @provider.should_not_receive(:mount_fs)
+        @provider.remount_fs()
+      end
+    end
+
+    describe "when enabling the fs" do
+      it "should enable if enabled isn't true" do
+        @current_resource.enabled(false)
+
+        @fstab = StringIO.new
+        ::File.stub!(:open).with("/etc/fstab", "a").and_yield(@fstab)
+        @provider.enable_fs
+        @fstab.string.should match(%r{^/dev/sdz1\s+/tmp/foo\s+ext3\s+defaults\s+0\s+2\s*$})
+      end
+
+      it "should not enable if enabled is true and resources match" do
+        @current_resource.enabled(true)
+        @current_resource.fstype("ext3")
+        @current_resource.options(["defaults"])
+        @current_resource.dump(0)
+        @current_resource.pass(2)
+        ::File.should_not_receive(:open).with("/etc/fstab", "a")
+
+        @provider.enable_fs
+      end
+
+      it "should enable if enabled is true and resources do not match" do
+        @current_resource.enabled(true)
+        @current_resource.fstype("auto")
+        @current_resource.options(["defaults"])
+        @current_resource.dump(0)
+        @current_resource.pass(2)
+        @fstab = StringIO.new
+        ::File.stub(:readlines).and_return([])
+        ::File.should_receive(:open).once.with("/etc/fstab", "w").and_yield(@fstab)
+        ::File.should_receive(:open).once.with("/etc/fstab", "a").and_yield(@fstab)
+
+        @provider.enable_fs
+      end
+    end
+
+    describe "when disabling the fs" do
+      it "should disable if enabled is true" do
+        @current_resource.enabled(true)
+
+        other_mount = "/dev/sdy1  /tmp/foo  ext3  defaults  1 2\n"
+        this_mount = "/dev/sdz1 /tmp/foo  ext3  defaults  1 2\n"
+
+        @fstab_read = [this_mount, other_mount]
+        ::File.stub!(:readlines).with("/etc/fstab").and_return(@fstab_read)
+        @fstab_write = StringIO.new
+        ::File.stub!(:open).with("/etc/fstab", "w").and_yield(@fstab_write)
+
+        @provider.disable_fs
+        @fstab_write.string.should match(Regexp.escape(other_mount))
+        @fstab_write.string.should_not match(Regexp.escape(this_mount))
+      end
+
+      it "should disable if enabled is true and ignore commented lines" do
+        @current_resource.enabled(true)
+
+        fstab_read = [%q{/dev/sdy1 /tmp/foo  ext3  defaults  1 2},
+                      %q{/dev/sdz1 /tmp/foo  ext3  defaults  1 2},
+                      %q{#/dev/sdz1 /tmp/foo  ext3  defaults  1 2}]
+        fstab_write = StringIO.new
+
+        ::File.stub!(:readlines).with("/etc/fstab").and_return(fstab_read)
+        ::File.stub!(:open).with("/etc/fstab", "w").and_yield(fstab_write)
+
+        @provider.disable_fs
+        fstab_write.string.should match(%r{^/dev/sdy1 /tmp/foo  ext3  defaults  1 2$})
+        fstab_write.string.should match(%r{^#/dev/sdz1 /tmp/foo  ext3  defaults  1 2$})
+        fstab_write.string.should_not match(%r{^/dev/sdz1 /tmp/foo  ext3  defaults  1 2$})
+      end
+
+      it "should disable only the last entry if enabled is true" do
+        @current_resource.stub!(:enabled).and_return(true)
+        fstab_read = ["/dev/sdz1 /tmp/foo  ext3  defaults  1 2\n",
+                      "/dev/sdy1 /tmp/foo  ext3  defaults  1 2\n",
+                      "/dev/sdz1 /tmp/foo  ext3  defaults  1 2\n"]
+
+        fstab_write = StringIO.new
+        ::File.stub!(:readlines).with("/etc/fstab").and_return(fstab_read)
+        ::File.stub!(:open).with("/etc/fstab", "w").and_yield(fstab_write)
+
+        @provider.disable_fs
+        fstab_write.string.should == "/dev/sdz1 /tmp/foo  ext3  defaults  1 2\n/dev/sdy1 /tmp/foo  ext3  defaults  1 2\n"
+      end
+
+      it "should not disable if enabled is false" do
+        @current_resource.stub!(:enabled).and_return(false)
+
+        ::File.stub!(:readlines).with("/etc/fstab").and_return([])
+        ::File.should_not_receive(:open).and_yield(@fstab)
+
+        @provider.disable_fs
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/mount/windows_spec.rb b/spec/unit/provider/mount/windows_spec.rb
new file mode 100644
index 0000000..4c9916a
--- /dev/null
+++ b/spec/unit/provider/mount/windows_spec.rb
@@ -0,0 +1,137 @@
+#
+# Author:: Doug MacEachern (<dougm at vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class Chef
+  class Util
+    class Windows
+      class NetUse
+      end
+      class Volume
+      end
+    end
+  end
+end
+
+GUID = "\\\\?\\Volume{578e72b5-6e70-11df-b5c5-000c29d4a7d9}\\"
+REMOTE = "\\\\server-name\\path"
+
+describe Chef::Provider::Mount::Windows do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Mount.new("X:")
+    @new_resource.device GUID
+    @current_resource = Chef::Resource::Mount.new("X:")
+    Chef::Resource::Mount.stub!(:new).and_return(@current_resource)
+
+    @net_use = mock("Chef::Util::Windows::NetUse")
+    Chef::Util::Windows::NetUse.stub!(:new).and_return(@net_use)
+    @vol = mock("Chef::Util::Windows::Volume")
+    Chef::Util::Windows::Volume.stub!(:new).and_return(@vol)
+
+    @provider = Chef::Provider::Mount::Windows.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  describe "when loading the current resource" do
+    it "should set mounted true if the mount point is found" do
+      @vol.stub!(:device).and_return(@new_resource.device)
+      @current_resource.should_receive(:mounted).with(true)
+      @provider.load_current_resource
+    end
+
+    it "should set mounted false if the mount point is not found" do
+      @vol.stub!(:device).and_raise(ArgumentError)
+      @current_resource.should_receive(:mounted).with(false)
+      @provider.load_current_resource
+    end
+
+    describe "with a local device" do
+      before do
+        @new_resource.device GUID
+        @vol.stub!(:device).and_return(@new_resource.device)
+        @net_use.stub!(:device).and_raise(ArgumentError)
+      end
+
+      it "should determine the device is a volume GUID" do
+        @provider.should_receive(:is_volume).with(@new_resource.device).and_return(true)
+        @provider.load_current_resource
+      end
+    end
+
+    describe "with a remote device" do
+      before do
+        @new_resource.device REMOTE
+        @net_use.stub!(:device).and_return(@new_resource.device)
+        @vol.stub!(:device).and_raise(ArgumentError)
+      end
+
+      it "should determine the device is remote" do
+        @provider.should_receive(:is_volume).with(@new_resource.device).and_return(false)
+        @provider.load_current_resource
+      end
+    end
+
+    describe "when mounting a file system" do
+      before do
+        @new_resource.device GUID
+        @vol.stub!(:add)
+        @vol.stub!(:device).and_raise(ArgumentError)
+        @provider.load_current_resource
+      end
+
+      it "should mount the filesystem if it is not mounted" do
+        @vol.should_receive(:add).with(:remote => @new_resource.device,
+                                       :username => @new_resource.username,
+                                       :domainname => @new_resource.domain,
+                                       :password => @new_resource.password)
+        @provider.mount_fs
+      end
+
+      it "should not mount the filesystem if it is mounted" do
+        @vol.should_not_receive(:add)
+        @current_resource.stub!(:mounted).and_return(true)
+        @provider.mount_fs
+      end
+    end
+
+    describe "when unmounting a file system" do
+      before do
+        @new_resource.device GUID
+        @vol.stub!(:delete)
+        @vol.stub!(:device).and_raise(ArgumentError)
+        @provider.load_current_resource
+      end
+
+      it "should umount the filesystem if it is mounted" do
+        @current_resource.stub!(:mounted).and_return(true)
+        @vol.should_receive(:delete)
+        @provider.umount_fs
+      end
+
+      it "should not umount the filesystem if it is not mounted" do
+        @current_resource.stub!(:mounted).and_return(false)
+        @vol.should_not_receive(:delete)
+        @provider.umount_fs
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/mount_spec.rb b/spec/unit/provider/mount_spec.rb
new file mode 100644
index 0000000..a44f970
--- /dev/null
+++ b/spec/unit/provider/mount_spec.rb
@@ -0,0 +1,170 @@
+#
+# Author:: Joshua Timberman (<joshua at opscode.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'
+
+describe Chef::Provider::Mount do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Mount.new('/tmp/foo')
+    @new_resource.device      "/dev/sdz1"
+    @new_resource.name        "/tmp/foo"
+    @new_resource.mount_point "/tmp/foo"
+    @new_resource.fstype      "ext3"
+
+    @current_resource = Chef::Resource::Mount.new('/tmp/foo')
+    @current_resource.device      "/dev/sdz1"
+    @current_resource.name        "/tmp/foo"
+    @current_resource.mount_point "/tmp/foo"
+    @current_resource.fstype      "ext3"
+
+    @provider = Chef::Provider::Mount.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  describe "when the target state is a mounted filesystem" do
+
+    it "should mount the filesystem if it isn't mounted" do
+      @current_resource.stub!(:mounted).and_return(false)
+      @provider.should_receive(:mount_fs).with.and_return(true)
+      @provider.run_action(:mount)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not mount the filesystem if it is mounted" do
+      @current_resource.stub!(:mounted).and_return(true)
+      @provider.should_not_receive(:mount_fs)
+      @provider.run_action(:mount)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+  end
+
+  describe "when the target state is an unmounted filesystem" do
+    it "should umount the filesystem if it is mounted" do
+      @current_resource.stub!(:mounted).and_return(true)
+      @provider.should_receive(:umount_fs).with.and_return(true)
+      @provider.run_action(:umount)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not umount the filesystem if it is not mounted" do
+      @current_resource.stub!(:mounted).and_return(false)
+      @provider.should_not_receive(:umount_fs)
+      @provider.run_action(:umount)
+      @new_resource.should_not be_updated_by_last_action
+    end
+  end
+
+  describe "when the filesystem should be remounted and the resource supports remounting" do
+    before do
+      @new_resource.supports[:remount] = true
+    end
+
+    it "should remount the filesystem if it is mounted" do
+      @current_resource.stub!(:mounted).and_return(true)
+      @provider.should_receive(:remount_fs).and_return(true)
+      @provider.run_action(:remount)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not remount the filesystem if it is not mounted" do
+      @current_resource.stub!(:mounted).and_return(false)
+      @provider.should_not_receive(:remount_fs)
+      @provider.run_action(:remount)
+      @new_resource.should_not be_updated_by_last_action
+    end
+  end
+  describe "when the filesystem should be remounted and the resource does not support remounting" do
+    before do
+      @new_resource.supports[:remount] = false
+    end
+
+    it "should fail to remount the filesystem" do
+      @provider.should_not_receive(:remount_fs)
+      lambda {@provider.run_action(:remount)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+  end
+  describe "when enabling the filesystem to be mounted" do
+    it "should enable the mount if it isn't enable" do
+      @current_resource.stub!(:enabled).and_return(false)
+      @provider.should_not_receive(:mount_options_unchanged?)
+      @provider.should_receive(:enable_fs).and_return(true)
+      @provider.run_action(:enable)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should enable the mount if it is enabled and mount options have changed" do
+      @current_resource.stub!(:enabled).and_return(true)
+      @provider.should_receive(:mount_options_unchanged?).and_return(false)
+      @provider.should_receive(:enable_fs).and_return(true)
+      @provider.run_action(:enable)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not enable the mount if it is enabled and mount options have not changed" do
+      @current_resource.stub!(:enabled).and_return(true)
+      @provider.should_receive(:mount_options_unchanged?).and_return(true)
+      @provider.should_not_receive(:enable_fs).and_return(true)
+      @provider.run_action(:enable)
+      @new_resource.should_not be_updated_by_last_action
+    end
+  end
+
+  describe "when the target state is to disable the mount" do
+    it "should disable the mount if it is enabled" do
+      @current_resource.stub!(:enabled).and_return(true)
+      @provider.should_receive(:disable_fs).with.and_return(true)
+      @provider.run_action(:disable)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not disable the mount if it isn't enabled" do
+      @current_resource.stub!(:enabled).and_return(false)
+      @provider.should_not_receive(:disable_fs)
+      @provider.run_action(:disable)
+      @new_resource.should_not be_updated_by_last_action
+    end
+  end
+
+
+  it "should delegates the mount implementation to subclasses" do
+    lambda { @provider.mount_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "should delegates the umount implementation to subclasses" do
+    lambda { @provider.umount_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "should delegates the remount implementation to subclasses" do
+    lambda { @provider.remount_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "should delegates the enable implementation to subclasses" do
+    lambda { @provider.enable_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "should delegates the disable implementation to subclasses" do
+    lambda { @provider.disable_fs }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+end
diff --git a/spec/unit/provider/ohai_spec.rb b/spec/unit/provider/ohai_spec.rb
new file mode 100644
index 0000000..8402c92
--- /dev/null
+++ b/spec/unit/provider/ohai_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Michael Leinartas (<mleinartas at gmail.com>)
+# Copyright:: Copyright (c) 2010 Michael Leinartas
+# 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/run_context'
+
+describe Chef::Provider::Ohai do
+  before(:each) do
+    # Copied from client_spec
+    @fqdn = "hostname.domainname"
+    @hostname = "hostname"
+    @platform = "example-platform"
+    @platform_version = "example-platform"
+    Chef::Config[:node_name] = @fqdn
+    mock_ohai = {
+      :fqdn => @fqdn,
+      :hostname => @hostname,
+      :platform => @platform,
+      :platform_version => @platform_version,
+      :data => {
+        :origdata => "somevalue"
+      },
+      :data2 => {
+        :origdata => "somevalue",
+        :newdata => "somevalue"
+      }
+    }
+    mock_ohai.stub!(:all_plugins).and_return(true)
+    mock_ohai.stub!(:require_plugin).and_return(true)
+    mock_ohai.stub!(:data).and_return(mock_ohai[:data],
+                                      mock_ohai[:data2])
+    Ohai::System.stub!(:new).and_return(mock_ohai)
+    Chef::Platform.stub!(:find_platform_and_version).and_return({ "platform" => @platform,
+                                                                  "platform_version" => @platform_version})
+    # Fake node with a dummy save
+    @node = Chef::Node.new
+    @node.name(@fqdn)
+    @node.stub!(:save).and_return(@node)
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Ohai.new("ohai_reload")
+    ohai = Ohai::System.new
+    ohai.all_plugins
+    @node.consume_external_attrs(ohai.data,{})
+
+    @provider = Chef::Provider::Ohai.new(@new_resource, @run_context)
+  end
+
+  describe "when reloading ohai" do
+    before do
+      @node.automatic_attrs[:origdata] = 'somevalue'
+    end
+
+    it "applies updated ohai data to the node" do
+      @node[:origdata].should == 'somevalue'
+      @node[:newdata].should be_nil
+      @provider.run_action(:reload)
+      @node[:origdata].should == 'somevalue'
+      @node[:newdata].should == 'somevalue'
+    end
+
+    it "should reload a specific plugin and cause node to pick up new values" do
+      @new_resource.plugin "someplugin"
+      @provider.run_action(:reload)
+      @node[:origdata].should == 'somevalue'
+      @node[:newdata].should == 'somevalue'
+    end
+  end
+end
diff --git a/spec/unit/provider/package/aix_spec.rb b/spec/unit/provider/package/aix_spec.rb
new file mode 100644
index 0000000..6e93615
--- /dev/null
+++ b/spec/unit/provider/package/aix_spec.rb
@@ -0,0 +1,171 @@
+#
+# Author:: Deepali Jagtap (deepali.jagtap at clogeny.com)
+# Author:: Prabhu Das (prabhu.das at clogeny.com)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'spec_helper'
+
+describe Chef::Provider::Package::Aix do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Package.new("samba.base")
+    @new_resource.source("/tmp/samba.base")
+
+    @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context)
+    ::File.stub!(:exists?).and_return(true)
+  end
+
+  describe "assessing the current package status" do
+    before 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 = mock("Status", :exitstatus => 0)
+    end
+
+    it "should create a current resource with the name of new_resource" do
+      @provider.stub!(:popen4).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.name.should == "samba.base"
+    end
+
+    it "should set the current resource bff package name to the new resource bff package name" do
+      @provider.stub!(:popen4).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.package_name.should == "samba.base"
+    end
+
+    it "should raise an exception if a source is supplied but not found" do
+      @provider.stub!(:popen4).and_return(@status)
+      ::File.stub!(:exists?).and_return(false)
+      @provider.define_resource_requirements
+      @provider.load_current_resource
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Package)
+    end
+
+
+    it "should get the source package version from lslpp if provided" do
+      @stdout = StringIO.new(@bffinfo)
+      @stdin, @stderr = StringIO.new, StringIO.new
+      @provider.should_receive(:popen4).with("installp -L -d /tmp/samba.base").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.should_receive(:popen4).with("lslpp -lcq samba.base").and_return(@status)
+      @provider.load_current_resource
+
+      @provider.current_resource.package_name.should == "samba.base"
+      @new_resource.version.should == "3.3.12.0"
+    end
+
+    it "should return the current version installed if found by lslpp" do
+      @stdout = StringIO.new(@bffinfo)
+      @stdin, @stderr = StringIO.new, StringIO.new
+      @provider.should_receive(:popen4).with("installp -L -d /tmp/samba.base").and_return(@status)
+      @provider.should_receive(:popen4).with("lslpp -lcq samba.base").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "3.3.12.0"
+    end
+
+    it "should raise an exception if the source is not set but we are installing" do
+      @new_resource = Chef::Resource::Package.new("samba.base")
+      @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context)
+      @provider.stub!(:popen4).and_return(@status)
+      lambda { @provider.run_action(:install) }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should raise an exception if installp/lslpp fails to run" do
+      @status = mock("Status", :exitstatus => -1)
+      @provider.stub!(:popen4).and_return(@status)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should return a current resource with a nil version if the package is not found" do
+      @stdout = StringIO.new
+      @provider.should_receive(:popen4).with("installp -L -d /tmp/samba.base").and_return(@status)
+      @provider.should_receive(:popen4).with("lslpp -lcq samba.base").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+  end
+
+  describe "candidate_version" do
+    it "should return the candidate_version variable if already setup" do
+      @provider.candidate_version = "3.3.12.0"
+      @provider.should_not_receive(:popen4)
+      @provider.candidate_version
+    end
+
+    it "should lookup the candidate_version if the variable is not already set" do
+      @status = mock("Status", :exitstatus => 0)
+      @provider.should_receive(:popen4).and_return(@status)
+      @provider.candidate_version
+    end
+
+    it "should throw and exception if the exitstatus is not 0" do
+      @status = mock("Status", :exitstatus => 1)
+      @provider.stub!(:popen4).and_return(@status)
+      lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+    end
+
+  end
+
+  describe "install and upgrade" do
+    it "should run installp -aYF -d with the package source to install" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "installp -aYF -d /tmp/samba.base samba.base"
+      })
+      @provider.install_package("samba.base", "3.3.12.0")
+    end
+
+    it "should run  when the package is a path to install" do
+      @new_resource = Chef::Resource::Package.new("/tmp/samba.base")
+      @provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context)
+      @new_resource.source.should == "/tmp/samba.base"
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "installp -aYF -d /tmp/samba.base /tmp/samba.base"
+      })
+      @provider.install_package("/tmp/samba.base", "3.3.12.0")
+    end
+
+    it "should run installp with -eLogfile option." do
+      @new_resource.stub!(:options).and_return("-e/tmp/installp.log")
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "installp -aYF  -e/tmp/installp.log -d /tmp/samba.base samba.base"
+      })
+      @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
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "installp -u samba.base"
+      })
+      @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
+      @new_resource.stub!(:options).and_return("-e/tmp/installp.log")
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "installp -u  -e/tmp/installp.log samba.base"
+      })
+      @provider.remove_package("samba.base", "3.3.12.0")
+    end
+
+  end
+end
+
diff --git a/spec/unit/provider/package/apt_spec.rb b/spec/unit/provider/package/apt_spec.rb
new file mode 100644
index 0000000..32b5516
--- /dev/null
+++ b/spec/unit/provider/package/apt_spec.rb
@@ -0,0 +1,310 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 'ostruct'
+
+describe Chef::Provider::Package::Apt do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new("irssi", @run_context)
+
+    @status = mock("Status", :exitstatus => 0)
+    @provider = Chef::Provider::Package::Apt.new(@new_resource, @run_context)
+    @stdin = StringIO.new
+    @stdout =<<-PKG_STATUS
+irssi:
+  Installed: (none)
+  Candidate: 0.8.14-1ubuntu4
+  Version table:
+     0.8.14-1ubuntu4 0
+        500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages
+PKG_STATUS
+    @stderr = ""
+    @shell_out = OpenStruct.new(:stdout => @stdout,:stdin => @stdin,:stderr => @stderr,:status => @status,:exitstatus => 0)
+  end
+
+  describe "when loading current resource" do
+
+    it "should create a current resource with the name of the new_resource" do
+      @provider.should_receive(:shell_out!).with("apt-cache policy #{@new_resource.package_name}").and_return(@shell_out)
+      @provider.load_current_resource
+
+      current_resource = @provider.current_resource
+      current_resource.should be_a(Chef::Resource::Package)
+      current_resource.name.should == "irssi"
+      current_resource.package_name.should == "irssi"
+      current_resource.version.should be_nil
+    end
+
+    it "should set the installed version if package has one" do
+      @stdout.replace(<<-INSTALLED)
+sudo:
+  Installed: 1.7.2p1-1ubuntu5.3
+  Candidate: 1.7.2p1-1ubuntu5.3
+  Version table:
+ *** 1.7.2p1-1ubuntu5.3 0
+        500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main Packages
+        500 http://security.ubuntu.com/ubuntu/ lucid-security/main Packages
+        100 /var/lib/dpkg/status
+     1.7.2p1-1ubuntu5 0
+        500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages
+INSTALLED
+      @provider.should_receive(:shell_out!).and_return(@shell_out)
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "1.7.2p1-1ubuntu5.3"
+      @provider.candidate_version.should eql("1.7.2p1-1ubuntu5.3")
+    end
+
+    # libmysqlclient-dev is a real package in newer versions of debian + ubuntu
+    # list of virtual packages: http://www.debian.org/doc/packaging-manuals/virtual-package-names-list.txt
+    it "should not install the virtual package there is a single provider package and it is installed" do
+      @new_resource.package_name("libmysqlclient15-dev")
+      virtual_package_out=<<-VPKG_STDOUT
+libmysqlclient15-dev:
+  Installed: (none)
+  Candidate: (none)
+  Version table:
+VPKG_STDOUT
+      virtual_package = mock(:stdout => virtual_package_out,:exitstatus => 0)
+      @provider.should_receive(:shell_out!).with("apt-cache policy libmysqlclient15-dev").and_return(virtual_package)
+      showpkg_out =<<-SHOWPKG_STDOUT
+Package: libmysqlclient15-dev
+Versions:
+
+Reverse Depends:
+  libmysqlclient-dev,libmysqlclient15-dev
+  libmysqlclient-dev,libmysqlclient15-dev
+  libmysqlclient-dev,libmysqlclient15-dev
+  libmysqlclient-dev,libmysqlclient15-dev
+  libmysqlclient-dev,libmysqlclient15-dev
+  libmysqlclient-dev,libmysqlclient15-dev
+Dependencies:
+Provides:
+Reverse Provides:
+libmysqlclient-dev 5.1.41-3ubuntu12.7
+libmysqlclient-dev 5.1.41-3ubuntu12.10
+libmysqlclient-dev 5.1.41-3ubuntu12
+SHOWPKG_STDOUT
+      showpkg = mock(:stdout => showpkg_out,:exitstatus => 0)
+      @provider.should_receive(:shell_out!).with("apt-cache showpkg libmysqlclient15-dev").and_return(showpkg)
+      real_package_out=<<-RPKG_STDOUT
+libmysqlclient-dev:
+  Installed: 5.1.41-3ubuntu12.10
+  Candidate: 5.1.41-3ubuntu12.10
+  Version table:
+ *** 5.1.41-3ubuntu12.10 0
+        500 http://us.archive.ubuntu.com/ubuntu/ lucid-updates/main Packages
+        100 /var/lib/dpkg/status
+     5.1.41-3ubuntu12.7 0
+        500 http://security.ubuntu.com/ubuntu/ lucid-security/main Packages
+     5.1.41-3ubuntu12 0
+        500 http://us.archive.ubuntu.com/ubuntu/ lucid/main Packages
+RPKG_STDOUT
+      real_package = mock(:stdout => real_package_out,:exitstatus => 0)
+      @provider.should_receive(:shell_out!).with("apt-cache policy libmysqlclient-dev").and_return(real_package)
+      @provider.load_current_resource
+    end
+
+    it "should raise an exception if you specify a virtual package with multiple provider packages" do
+      @new_resource.package_name("mp3-decoder")
+      virtual_package_out=<<-VPKG_STDOUT
+mp3-decoder:
+  Installed: (none)
+  Candidate: (none)
+  Version table:
+VPKG_STDOUT
+      virtual_package = mock(:stdout => virtual_package_out,:exitstatus => 0)
+      @provider.should_receive(:shell_out!).with("apt-cache policy mp3-decoder").and_return(virtual_package)
+      showpkg_out=<<-SHOWPKG_STDOUT
+Package: mp3-decoder
+Versions:
+
+Reverse Depends:
+  nautilus,mp3-decoder
+  vux,mp3-decoder
+  plait,mp3-decoder
+  ecasound,mp3-decoder
+  nautilus,mp3-decoder
+Dependencies:
+Provides:
+Reverse Provides:
+vlc-nox 1.0.6-1ubuntu1.8
+vlc 1.0.6-1ubuntu1.8
+vlc-nox 1.0.6-1ubuntu1
+vlc 1.0.6-1ubuntu1
+opencubicplayer 1:0.1.17-2
+mpg321 0.2.10.6
+mpg123 1.12.1-0ubuntu1
+SHOWPKG_STDOUT
+      showpkg = mock(:stdout => showpkg_out,:exitstatus => 0)
+      @provider.should_receive(:shell_out!).with("apt-cache showpkg mp3-decoder").and_return(showpkg)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should run apt-cache policy with the default_release option, if there is one and provider is explicitly defined" do
+      @new_resource = Chef::Resource::AptPackage.new("irssi", @run_context)
+      @provider = Chef::Provider::Package::Apt.new(@new_resource, @run_context)
+
+      @new_resource.stub!(:default_release).and_return("lenny-backports")
+      @new_resource.stub!(:provider).and_return("Chef::Provider::Package::Apt")
+      @provider.should_receive(:shell_out!).with("apt-cache -o APT::Default-Release=lenny-backports policy irssi").and_return(@shell_out)
+      @provider.load_current_resource
+    end
+
+  end
+
+  context "after loading the current resource" do
+    before do
+      @current_resource = Chef::Resource::Package.new("irssi", @run_context)
+      @provider.current_resource = @current_resource
+    end
+
+    describe "install_package" do
+      it "should run apt-get install with the package name and version" do
+        @provider.should_receive(:shell_out!).
+          with("apt-get -q -y install irssi=0.8.12-7",
+               :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil})
+        @provider.install_package("irssi", "0.8.12-7")
+      end
+
+      it "should run apt-get install with the package name and version and options if specified" do
+        @provider.should_receive(:shell_out!).
+          with("apt-get -q -y --force-yes install irssi=0.8.12-7",
+               :env => {"DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+        @new_resource.options("--force-yes")
+        @provider.install_package("irssi", "0.8.12-7")
+      end
+
+      it "should run apt-get install with the package name and version and default_release if there is one and provider is explicitly defined" do
+        @new_resource = nil
+        @new_resource = Chef::Resource::AptPackage.new("irssi", @run_context)
+        @new_resource.default_release("lenny-backports")
+
+        @provider.new_resource = @new_resource
+
+        @provider.should_receive(:shell_out!).
+          with("apt-get -q -y -o APT::Default-Release=lenny-backports install irssi=0.8.12-7",
+                :env => {"DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+
+        @provider.install_package("irssi", "0.8.12-7")
+      end
+    end
+
+    describe Chef::Provider::Package::Apt, "upgrade_package" do
+
+      it "should run install_package with the name and version" do
+        @provider.should_receive(:install_package).with("irssi", "0.8.12-7")
+        @provider.upgrade_package("irssi", "0.8.12-7")
+      end
+    end
+
+    describe Chef::Provider::Package::Apt, "remove_package" do
+
+      it "should run apt-get remove with the package name" do
+        @provider.should_receive(:shell_out!).
+          with("apt-get -q -y remove irssi",
+               :env => {"DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil})
+        @provider.remove_package("irssi", "0.8.12-7")
+      end
+
+      it "should run apt-get remove with the package name and options if specified" do
+        @provider.should_receive(:shell_out!).
+          with("apt-get -q -y --force-yes remove irssi",
+               :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+        @new_resource.options("--force-yes")
+
+        @provider.remove_package("irssi", "0.8.12-7")
+      end
+    end
+
+    describe "when purging a package" do
+
+      it "should run apt-get purge with the package name" do
+        @provider.should_receive(:shell_out!).
+          with("apt-get -q -y purge irssi",
+               :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+        @provider.purge_package("irssi", "0.8.12-7")
+      end
+
+      it "should run apt-get purge with the package name and options if specified" do
+        @provider.should_receive(:shell_out!).
+          with("apt-get -q -y --force-yes purge irssi",
+          :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+        @new_resource.options("--force-yes")
+
+        @provider.purge_package("irssi", "0.8.12-7")
+      end
+    end
+
+    describe "when preseeding a package" do
+      before(:each) do
+        @provider.stub!(:get_preseed_file).and_return("/tmp/irssi-0.8.12-7.seed")
+      end
+
+      it "should get the full path to the preseed response file" do
+        @provider.should_receive(:get_preseed_file).with("irssi", "0.8.12-7").and_return("/tmp/irssi-0.8.12-7.seed")
+        file = @provider.get_preseed_file("irssi", "0.8.12-7")
+
+        @provider.should_receive(:shell_out!).
+          with("debconf-set-selections /tmp/irssi-0.8.12-7.seed",
+          :env => {"DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil})
+
+        @provider.preseed_package(file)
+      end
+
+      it "should run debconf-set-selections on the preseed file if it has changed" do
+        @provider.should_receive(:shell_out!).
+          with("debconf-set-selections /tmp/irssi-0.8.12-7.seed",
+          :env => {"DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil})
+        file = @provider.get_preseed_file("irssi", "0.8.12-7")
+        @provider.preseed_package(file)
+      end
+
+      it "should not run debconf-set-selections if the preseed file has not changed" do
+        @provider.stub(:check_package_state)
+        @current_resource.version "0.8.11"
+        @new_resource.response_file "/tmp/file"
+        @provider.stub!(:get_preseed_file).and_return(false)
+        @provider.should_not_receive(:shell_out!)
+        @provider.run_action(:reconfig)
+      end
+    end
+
+    describe "when reconfiguring a package" do
+      it "should run dpkg-reconfigure package" do
+        @provider.should_receive(:shell_out!).
+          with("dpkg-reconfigure irssi",
+          :env => {"DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+        @provider.reconfig_package("irssi", "0.8.12-7")
+      end
+    end
+
+    describe "when installing a virtual package" do
+      it "should install the package without specifying a version" do
+          @provider.is_virtual_package = true
+          @provider.should_receive(:shell_out!).
+            with("apt-get -q -y install libmysqlclient-dev",
+                 :env => {"DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+          @provider.install_package("libmysqlclient-dev", "not_a_real_version")
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
new file mode 100644
index 0000000..30021e3
--- /dev/null
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -0,0 +1,216 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Package::Dpkg do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new("wget")
+    @new_resource.source "/tmp/wget_1.11.4-1ubuntu1_amd64.deb"
+
+    @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
+
+    @stdin = StringIO.new
+    @stdout = StringIO.new
+    @status = mock("Status", :exitstatus => 0)
+    @stderr = StringIO.new
+    @pid = mock("PID")
+    @provider.stub!(:popen4).and_return(@status)
+
+    ::File.stub!(:exists?).and_return(true)
+  end
+
+  describe "when loading the current resource state" do
+
+    it "should create a current resource with the name of the new_resource" do
+      @provider.load_current_resource
+      @provider.current_resource.package_name.should == "wget"
+    end
+
+    it "should raise an exception if a source is supplied but not found" do
+      @provider.load_current_resource
+      @provider.define_resource_requirements
+      ::File.stub!(:exists?).and_return(false)
+      lambda { @provider.run_action(:install) }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    describe 'gets the source package version from dpkg-deb' do
+      def check_version(version)
+        @stdout = StringIO.new("wget\t#{version}")
+        @provider.stub!(:popen4).with("dpkg-deb -W #{@new_resource.source}").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.load_current_resource
+        @provider.current_resource.package_name.should == "wget"
+        @new_resource.version.should == version
+      end
+
+      it 'if short version provided' do
+        check_version('1.11.4')
+      end
+
+      it 'if extended version provided' do
+        check_version('1.11.4-1ubuntu1')
+      end
+
+      it 'if distro-specific version provided' do
+        check_version('1.11.4-1ubuntu1~lucid')
+      end
+    end
+
+    it "gets the source package name from dpkg-deb correctly when the package name has `-', `+' or `.' characters" do
+      @stdout = StringIO.new("f.o.o-pkg++2\t1.11.4-1ubuntu1")
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.package_name.should == "f.o.o-pkg++2"
+    end
+
+    it "should raise an exception if the source is not set but we are installing" do
+      @new_resource = Chef::Resource::Package.new("wget")
+      @provider.new_resource = @new_resource
+      @provider.define_resource_requirements
+      @provider.load_current_resource
+      lambda { @provider.run_action(:install)}.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should return the current version installed if found by dpkg" do
+      @stdout = StringIO.new(<<-DPKG_S)
+Package: wget
+Status: install ok installed
+Priority: important
+Section: web
+Installed-Size: 1944
+Maintainer: Ubuntu Core developers <ubuntu-devel-discuss at lists.ubuntu.com>
+Architecture: amd64
+Version: 1.11.4-1ubuntu1
+Config-Version: 1.11.4-1ubuntu1
+Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5)
+Conflicts: wget-ssl
+DPKG_S
+      @provider.stub!(:popen4).with("dpkg -s wget").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "1.11.4-1ubuntu1"
+    end
+
+    it "should raise an exception if dpkg fails to run" do
+      @status = mock("Status", :exitstatus => -1)
+      @provider.stub!(:popen4).and_return(@status)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+    end
+  end
+
+  describe Chef::Provider::Package::Dpkg, "install and upgrade" do
+    it "should run dpkg -i with the package source" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @provider.install_package("wget", "1.11.4-1ubuntu1")
+    end
+
+    it "should run dpkg -i if the package is a path and the source is nil" do
+      @new_resource = Chef::Resource::Package.new("/tmp/wget_1.11.4-1ubuntu1_amd64.deb")
+      @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @provider.install_package("/tmp/wget_1.11.4-1ubuntu1_amd64.deb", "1.11.4-1ubuntu1")
+    end
+
+    it "should run dpkg -i if the package is a path and the source is nil for an upgrade" do
+      @new_resource = Chef::Resource::Package.new("/tmp/wget_1.11.4-1ubuntu1_amd64.deb")
+      @provider = Chef::Provider::Package::Dpkg.new(@new_resource, @run_context)
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @provider.upgrade_package("/tmp/wget_1.11.4-1ubuntu1_amd64.deb", "1.11.4-1ubuntu1")
+    end
+
+    it "should run dpkg -i with the package source and options if specified" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -i --force-yes /tmp/wget_1.11.4-1ubuntu1_amd64.deb",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @new_resource.stub!(:options).and_return("--force-yes")
+
+      @provider.install_package("wget", "1.11.4-1ubuntu1")
+    end
+    it "should upgrade by running install_package" do
+      @provider.should_receive(:install_package).with("wget", "1.11.4-1ubuntu1")
+      @provider.upgrade_package("wget", "1.11.4-1ubuntu1")
+    end
+  end
+
+  describe Chef::Provider::Package::Dpkg, "remove and purge" do
+    it "should run dpkg -r to remove the package" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -r wget",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @provider.remove_package("wget", "1.11.4-1ubuntu1")
+    end
+
+    it "should run dpkg -r to remove the package with options if specified" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -r --force-yes wget",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @new_resource.stub!(:options).and_return("--force-yes")
+
+      @provider.remove_package("wget", "1.11.4-1ubuntu1")
+    end
+
+    it "should run dpkg -P to purge the package" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -P wget",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @provider.purge_package("wget", "1.11.4-1ubuntu1")
+    end
+
+    it "should run dpkg -P to purge the package with options if specified" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "dpkg -P --force-yes wget",
+        :environment => {
+          "DEBIAN_FRONTEND" => "noninteractive"
+        }
+      })
+      @new_resource.stub!(:options).and_return("--force-yes")
+
+      @provider.purge_package("wget", "1.11.4-1ubuntu1")
+    end
+  end
+end
diff --git a/spec/unit/provider/package/easy_install_spec.rb b/spec/unit/provider/package/easy_install_spec.rb
new file mode 100644
index 0000000..02f8399
--- /dev/null
+++ b/spec/unit/provider/package/easy_install_spec.rb
@@ -0,0 +1,112 @@
+#
+# 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");
+# 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::Package::EasyInstall do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::EasyInstallPackage.new('boto')
+    @new_resource.version('1.8d')
+
+    @current_resource = Chef::Resource::EasyInstallPackage.new('boto')
+    @current_resource.version('1.8d')
+
+    @provider = Chef::Provider::Package::EasyInstall.new(@new_resource, @run_context)
+    Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+
+    @stdin = StringIO.new
+    @stdout = StringIO.new
+    @status = mock("Status", :exitstatus => 0)
+    @stderr = StringIO.new
+    @pid = 2342
+    @provider.stub!(:popen4).and_return(@status)
+  end
+
+  describe "easy_install_binary_path" do
+    it "should return a Chef::Provider::EasyInstall object" do
+      provider = Chef::Provider::Package::EasyInstall.new(@node, @new_resource)
+      provider.should be_a_kind_of(Chef::Provider::Package::EasyInstall)
+    end
+
+    it "should set the current resources package name to the new resources package name" do
+      $stdout.stub!(:write)
+      @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+      @provider.load_current_resource
+    end
+
+    it "should return a relative path to easy_install if no easy_install_binary is given" do
+      @provider.easy_install_binary_path.should eql("easy_install")
+    end
+
+    it "should return a specific path to easy_install if a easy_install_binary is given" do
+      @new_resource.should_receive(:easy_install_binary).and_return("/opt/local/bin/custom/easy_install")
+      @provider.easy_install_binary_path.should eql("/opt/local/bin/custom/easy_install")
+    end
+
+  end
+
+  describe "actions_on_package" do
+    it "should run easy_install with the package name and version" do
+      @provider.should_receive(:run_command).with({
+        :command => "easy_install \"boto==1.8d\""
+      })
+      @provider.install_package("boto", "1.8d")
+    end
+
+    it "should run easy_install with the package name and version and specified options" do
+      @provider.should_receive(:run_command).with({
+        :command => "easy_install --always-unzip \"boto==1.8d\""
+      })
+      @new_resource.stub!(:options).and_return("--always-unzip")
+      @provider.install_package("boto", "1.8d")
+    end
+
+    it "should run easy_install with the package name and version" do
+      @provider.should_receive(:run_command).with({
+        :command => "easy_install \"boto==1.8d\""
+      })
+      @provider.upgrade_package("boto", "1.8d")
+    end
+
+    it "should run easy_install -m with the package name and version" do
+      @provider.should_receive(:run_command).with({
+        :command => "easy_install -m boto"
+      })
+      @provider.remove_package("boto", "1.8d")
+    end
+
+    it "should run easy_install -m with the package name and version and specified options" do
+      @provider.should_receive(:run_command).with({
+        :command => "easy_install -x -m boto"
+      })
+      @new_resource.stub!(:options).and_return("-x")
+      @provider.remove_package("boto", "1.8d")
+    end
+
+    it "should run easy_install -m with the package name and version" do
+      @provider.should_receive(:run_command).with({
+        :command => "easy_install -m boto"
+      })
+      @provider.purge_package("boto", "1.8d")
+    end
+
+  end
+end
diff --git a/spec/unit/provider/package/freebsd_spec.rb b/spec/unit/provider/package/freebsd_spec.rb
new file mode 100644
index 0000000..2901b84
--- /dev/null
+++ b/spec/unit/provider/package/freebsd_spec.rb
@@ -0,0 +1,287 @@
+#
+# Authors:: Bryan McLellan (btm at loftninjas.org)
+#           Matthew Landauer (matthew at openaustralia.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
+# 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 'ostruct'
+
+describe Chef::Provider::Package::Freebsd, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource     = Chef::Resource::Package.new("zsh")
+    @current_resource = Chef::Resource::Package.new("zsh")
+
+    @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+    ::File.stub!(:exist?).with('/usr/ports/Makefile').and_return(false)
+  end
+
+  describe "when determining the current package state" do
+    before do
+      @provider.stub!(:ports_candidate_version).and_return("4.3.6")
+    end
+
+    it "should create a current resource with the name of the new_resource" do
+      current_resource = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context).current_resource
+      current_resource.name.should == "zsh"
+    end
+
+    it "should return a version if the package is installed" do
+      @provider.should_receive(:current_installed_version).and_return("4.3.6_7")
+      @provider.load_current_resource
+      @current_resource.version.should == "4.3.6_7"
+    end
+
+    it "should return nil if the package is not installed" do
+      @provider.should_receive(:current_installed_version).and_return(nil)
+      @provider.load_current_resource
+      @current_resource.version.should be_nil
+    end
+
+    it "should return a candidate version if it exists" do
+      @provider.should_receive(:current_installed_version).and_return(nil)
+      @provider.load_current_resource
+      @provider.candidate_version.should eql("4.3.6")
+    end
+  end
+
+  describe "when querying for package state and attributes" do
+    before do
+      #@new_resource = Chef::Resource::Package.new("zsh")
+
+      #@provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource)
+
+      #@status = mock("Status", :exitstatus => 0)
+      #@stdin = mock("STDIN", :null_object => true)
+      #@stdout = mock("STDOUT", :null_object => true)
+      #@stderr = mock("STDERR", :null_object => true)
+      #@pid = mock("PID", :null_object => true)
+    end
+
+    it "should return the version number when it is installed" do
+      pkg_info = OpenStruct.new(:stdout => "zsh-4.3.6_7")
+      @provider.should_receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).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)
+      @provider.stub!(:package_name).and_return("zsh")
+      @provider.current_installed_version.should == "4.3.6_7"
+    end
+
+    it "does not set the current version number when the package is not installed" do
+      pkg_info = OpenStruct.new(:stdout => "")
+      @provider.should_receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+      @provider.stub!(:package_name).and_return("zsh")
+      @provider.current_installed_version.should be_nil
+    end
+
+    it "should return the port path for a valid port name" do
+      whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
+      @provider.should_receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+      #@provider.should_receive(:popen4).with("whereis -s zsh").and_yield(@pid, @stdin, ["zsh: /usr/ports/shells/zsh"], @stderr).and_return(@status)
+      @provider.stub!(:port_name).and_return("zsh")
+      @provider.port_path.should == "/usr/ports/shells/zsh"
+    end
+
+    # Not happy with the form of these tests as they are far too closely tied to the implementation and so very fragile.
+    it "should return the ports candidate version when given a valid port path" do
+      @provider.stub!(:port_path).and_return("/usr/ports/shells/zsh")
+      make_v = OpenStruct.new(:stdout => "4.3.6\n")
+      @provider.should_receive(:shell_out!).with("make -V PORTVERSION", {:cwd=>"/usr/ports/shells/zsh", :returns=>[0, 1], :env=>nil}).and_return(make_v)
+      @provider.ports_candidate_version.should == "4.3.6"
+    end
+
+    it "should figure out the package name when we have ports" do
+      ::File.stub!(:exist?).with('/usr/ports/Makefile').and_return(true)
+      @provider.stub!(:port_path).and_return("/usr/ports/shells/zsh")
+      make_v = OpenStruct.new(:stdout => "zsh-4.3.6_7\n")
+      @provider.should_receive(:shell_out!).with("make -V PKGNAME", {:cwd=>"/usr/ports/shells/zsh", :env=>nil, :returns=>[0, 1]}).and_return(make_v)
+      #@provider.should_receive(:ports_makefile_variable_value).with("PKGNAME").and_return("zsh-4.3.6_7")
+      @provider.package_name.should == "zsh"
+    end
+  end
+
+  describe Chef::Provider::Package::Freebsd, "install_package" do
+    before(:each) do
+      @cmd_result = OpenStruct.new(:status => true)
+
+      @provider.current_resource = @current_resource
+      @provider.stub!(:package_name).and_return("zsh")
+      @provider.stub!(:latest_link_name).and_return("zsh")
+      @provider.stub!(:port_path).and_return("/usr/ports/shells/zsh")
+    end
+
+    it "should run pkg_add -r with the package name" do
+      @provider.should_receive(:shell_out!).with("pkg_add -r zsh", :env => nil).and_return(@cmd_result)
+      @provider.install_package("zsh", "4.3.6_7")
+    end
+
+    it "should run make install when installing from ports" do
+      @new_resource.stub!(:source).and_return("ports")
+      @provider.should_not_receive(:shell_out!).with("make -DBATCH -f /usr/ports/shells/zsh/Makefile install", :timeout => 1200, :env=>nil)
+      @provider.should_receive(:shell_out!).with("make -DBATCH install", :timeout => 1200, :env=>nil, :cwd => @provider.port_path).and_return(@cmd_result)
+      @provider.install_package("zsh", "4.3.6_7")
+    end
+  end
+
+  describe Chef::Provider::Package::Freebsd, "port path" do
+    before do
+      #@node = Chef::Node.new
+      @new_resource = Chef::Resource::Package.new("zsh")
+      @new_resource.cookbook_name = "adventureclub"
+      @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+    end
+
+    it "should figure out the port path from the package_name using whereis" do
+      whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
+      @provider.should_receive(:shell_out!).with("whereis -s zsh", :env=>nil).and_return(whereis)
+      @provider.port_path.should == "/usr/ports/shells/zsh"
+    end
+
+    it "should use the package_name as the port path when it starts with /" do
+      new_resource = Chef::Resource::Package.new("/usr/ports/www/wordpress")
+      provider = Chef::Provider::Package::Freebsd.new(new_resource, @run_context)
+      provider.should_not_receive(:popen4)
+      provider.port_path.should == "/usr/ports/www/wordpress"
+    end
+
+    it "should use the package_name as a relative path from /usr/ports when it contains / but doesn't start with it" do
+      # @new_resource = mock( "Chef::Resource::Package",
+      #                       :package_name => "www/wordpress",
+      #                       :cookbook_name => "xenoparadox")
+      new_resource = Chef::Resource::Package.new("www/wordpress")
+      provider = Chef::Provider::Package::Freebsd.new(new_resource, @run_context)
+      provider.should_not_receive(:popen4)
+      provider.port_path.should == "/usr/ports/www/wordpress"
+    end
+  end
+
+  describe Chef::Provider::Package::Freebsd, "ruby-iconv (package with a dash in the name)" do
+    before(:each) do
+      @new_resource     = Chef::Resource::Package.new("ruby-iconv")
+      @current_resource = Chef::Resource::Package.new("ruby-iconv")
+      @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+      @provider.current_resource = @current_resource
+      @provider.stub!(:port_path).and_return("/usr/ports/converters/ruby-iconv")
+      @provider.stub!(:package_name).and_return("ruby18-iconv")
+      @provider.stub!(:latest_link_name).and_return("ruby18-iconv")
+
+      @install_result = OpenStruct.new(:status => true)
+    end
+
+    it "should run pkg_add -r with the package name" do
+      @provider.should_receive(:shell_out!).with("pkg_add -r ruby18-iconv", :env => nil).and_return(@install_result)
+      @provider.install_package("ruby-iconv", "1.0")
+    end
+
+    it "should run make install when installing from ports" do
+      @new_resource.stub!(:source).and_return("ports")
+      @provider.should_receive(:shell_out!).with("make -DBATCH install", :timeout => 1200, :env=>nil, :cwd => @provider.port_path).and_return(@install_result)
+      @provider.install_package("ruby-iconv", "1.0")
+    end
+  end
+
+  describe Chef::Provider::Package::Freebsd, "remove_package" do
+    before(:each) do
+      @pkg_delete = OpenStruct.new(:status => true)
+      @new_resource.version "4.3.6_7"
+      @current_resource.version "4.3.6_7"
+      @provider.current_resource = @current_resource
+      @provider.stub!(:package_name).and_return("zsh")
+    end
+
+    it "should run pkg_delete with the package name and version" do
+      @provider.should_receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", :env => nil).and_return(@pkg_delete)
+      @provider.remove_package("zsh", "4.3.6_7")
+    end
+  end
+
+  # CHEF-4371
+  # There are some port names that contain special characters such as +'s.  This breaks the regular expression used to determine what
+  # version of a package is currently installed and to get the port_path.
+  #  Example package name: bonnie++
+
+  describe Chef::Provider::Package::Freebsd, "bonnie++ (package with a plus in the name :: CHEF-4371)" do
+    before(:each) do
+      @new_resource     = Chef::Resource::Package.new("bonnie++")
+      @current_resource = Chef::Resource::Package.new("bonnie++")
+      @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+      @provider.current_resource = @current_resource
+    end
+
+    it "should return the port path for a valid port name" do
+      whereis = OpenStruct.new(:stdout => "bonnie++: /usr/ports/benchmarks/bonnie++")
+      @provider.should_receive(:shell_out!).with("whereis -s bonnie++", :env => nil).and_return(whereis)
+      @provider.stub!(:port_name).and_return("bonnie++")
+      @provider.port_path.should == "/usr/ports/benchmarks/bonnie++"
+    end
+
+    it "should return the version number when it is installed" do
+      pkg_info = OpenStruct.new(:stdout => "bonnie++-1.96")
+      @provider.should_receive(:shell_out!).with('pkg_info -E "bonnie++*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+      @provider.stub!(:package_name).and_return("bonnie++")
+      @provider.current_installed_version.should == "1.96"
+    end
+  end
+
+  # A couple of examples to show up the difficulty of determining the command to install the binary package given the port:
+  # PORT DIRECTORY                        INSTALLED PACKAGE NAME  COMMAND TO INSTALL PACKAGE
+  # /usr/ports/lang/perl5.8               perl-5.8.8_1            pkg_add -r perl
+  # /usr/ports/databases/mysql50-server   mysql-server-5.0.45_1   pkg_add -r mysql50-server
+  #
+  # So, in one case it appears the command to install the package can be derived from the name of the port directory and in the
+  # other case it appears the command can be derived from the package name. Very confusing!
+  # Well, luckily, after much poking around, I discovered that the two can be disambiguated through the use of the LATEST_LINK
+  # variable which is set by the ports Makefile
+  #
+  # PORT DIRECTORY                        LATEST_LINK     INSTALLED PACKAGE NAME  COMMAND TO INSTALL PACKAGE
+  # /usr/ports/lang/perl5.8               perl            perl-5.8.8_1            pkg_add -r perl
+  # /usr/ports/databases/mysql50-server   mysql50-server  mysql-server-5.0.45_1   pkg_add -r mysql50-server
+  #
+  # The variable LATEST_LINK is named that way because the directory that "pkg_add -r" downloads from is called "Latest" and
+  # contains the "latest" versions of package as symbolic links to the files in the "All" directory.
+
+  describe Chef::Provider::Package::Freebsd, "install_package latest link fixes" do
+    it "should install the perl binary package with the correct name" do
+      @new_resource = Chef::Resource::Package.new("perl5.8")
+      @current_resource = Chef::Resource::Package.new("perl5.8")
+      @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+      @provider.current_resource = @current_resource
+      @provider.stub!(:package_name).and_return("perl")
+      @provider.stub!(:latest_link_name).and_return("perl")
+
+      cmd = OpenStruct.new(:status => true)
+      @provider.should_receive(:shell_out!).with("pkg_add -r perl", :env => nil).and_return(cmd)
+      @provider.install_package("perl5.8", "5.8.8_1")
+    end
+
+    it "should install the mysql50-server binary package with the correct name" do
+
+      @new_resource     = Chef::Resource::Package.new("mysql50-server")
+      @current_resource = Chef::Resource::Package.new("mysql50-server")
+      @provider = Chef::Provider::Package::Freebsd.new(@new_resource, @run_context)
+      @provider.current_resource = @current_resource
+      @provider.stub!(:package_name).and_return("mysql-server")
+      @provider.stub!(:latest_link_name).and_return("mysql50-server")
+
+      cmd = OpenStruct.new(:status => true)
+      @provider.should_receive(:shell_out!).with("pkg_add -r mysql50-server", :env=>nil).and_return(cmd)
+      @provider.install_package("mysql50-server", "5.0.45_1")
+    end
+  end
+end
diff --git a/spec/unit/provider/package/ips_spec.rb b/spec/unit/provider/package/ips_spec.rb
new file mode 100644
index 0000000..95839db
--- /dev/null
+++ b/spec/unit/provider/package/ips_spec.rb
@@ -0,0 +1,209 @@
+#
+# Author:: Bryan McLellan <btm 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 'ostruct'
+
+# based on the apt specs
+
+describe Chef::Provider::Package::Ips do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new("crypto/gnupg", @run_context)
+    @current_resource = Chef::Resource::Package.new("crypto/gnupg", @run_context)
+    Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+    @provider = Chef::Provider::Package::Ips.new(@new_resource, @run_context)
+
+    @stdin = StringIO.new
+    @stderr = StringIO.new
+    @stdout =<<-PKG_STATUS
+          Name: crypto/gnupg
+       Summary: GNU Privacy Guard
+   Description: A complete and free implementation of the OpenPGP Standard as
+                defined by RFC4880.
+      Category: Applications/System Utilities
+         State: Not installed
+     Publisher: solaris
+       Version: 2.0.17
+ Build Release: 5.11
+        Branch: 0.175.0.0.0.2.537
+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
+PKG_STATUS
+    @pid = 12345
+    @shell_out = OpenStruct.new(:stdout => @stdout,:stdin => @stdin,:stderr => @stderr,:status => @status,:exitstatus => 0)
+  end
+
+  context "when loading current resource" do
+    it "should create a current resource with the name of the new_resource" do
+      @provider.should_receive(:shell_out!).and_return(@shell_out)
+      Chef::Resource::Package.should_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
+      @provider.should_receive(:shell_out!).and_return(@shell_out)
+      @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+      @provider.load_current_resource
+    end
+
+    it "should run pkg info with the package name" do
+      @provider.should_receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(@shell_out)
+      @provider.load_current_resource
+    end
+
+    it "should set the installed version to nil on the current resource if package state is not installed" do
+      @provider.should_receive(:shell_out!).and_return(@shell_out)
+      @current_resource.should_receive(:version).with(nil).and_return(true)
+      @provider.load_current_resource
+    end
+
+    it "should set the installed version if package has one" do
+      @stdout.replace(<<-INSTALLED)
+          Name: crypto/gnupg
+       Summary: GNU Privacy Guard
+   Description: A complete and free implementation of the OpenPGP Standard as
+                defined by RFC4880.
+      Category: Applications/System Utilities
+         State: Installed
+     Publisher: solaris
+       Version: 2.0.17
+ Build Release: 5.11
+        Branch: 0.175.0.0.0.2.537
+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
+      @provider.should_receive(:shell_out!).and_return(@shell_out)
+      @provider.load_current_resource
+      @current_resource.version.should == "2.0.17"
+      @provider.candidate_version.should eql("2.0.17")
+    end
+
+    it "should return the current resouce" do
+      @provider.should_receive(:shell_out!).and_return(@shell_out)
+      @provider.load_current_resource.should eql(@current_resource)
+    end
+  end
+
+  context "when installing a package" do
+    it "should run pkg install with the package name and version" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkg install -q crypto/gnupg at 2.0.17"
+      })
+      @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
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkg --no-refresh install -q crypto/gnupg at 2.0.17"
+      })
+      @new_resource.stub!(:options).and_return("--no-refresh")
+      @provider.install_package("crypto/gnupg", "2.0.17")
+    end
+
+    it "should not contain invalid characters for the version string" do
+      @stdout.replace(<<-PKG_STATUS)
+          Name: security/sudo
+       Summary: sudo - authority delegation tool
+         State: Not Installed
+     Publisher: omnios
+       Version: 1.8.4.1 (1.8.4p1)
+ Build Release: 5.11
+        Branch: 0.151002
+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
+      @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "pkg install -q security/sudo at 1.8.4.1"
+      })
+      @provider.install_package("security/sudo", "1.8.4.1")
+    end
+
+    it "should not include the human-readable version in the candidate_version" do
+      @stdout.replace(<<-PKG_STATUS)
+          Name: security/sudo
+       Summary: sudo - authority delegation tool
+         State: Not Installed
+     Publisher: omnios
+       Version: 1.8.4.1 (1.8.4p1)
+ Build Release: 5.11
+        Branch: 0.151002
+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
+      @provider.should_receive(:shell_out!).and_return(@shell_out)
+      @provider.load_current_resource
+      @current_resource.version.should be_nil
+      @provider.candidate_version.should eql("1.8.4.1")
+    end
+
+    context "using the ips_package resource" do
+      before do
+        @new_resource = Chef::Resource::IpsPackage.new("crypto/gnupg", @run_context)
+        @current_resource = Chef::Resource::IpsPackage.new("crypto/gnupg", @run_context)
+        @provider = Chef::Provider::Package::Ips.new(@new_resource, @run_context)
+      end
+
+      context "when accept_license is true" do
+        before do
+          @new_resource.stub!(:accept_license).and_return(true)
+        end
+
+        it "should run pkg install with the --accept flag" do
+          @provider.should_receive(:run_command_with_systems_locale).with({
+            :command => "pkg install -q --accept crypto/gnupg at 2.0.17"
+          })
+          @provider.install_package("crypto/gnupg", "2.0.17")
+        end
+      end
+    end
+  end
+
+  context "when upgrading a package" do
+    it "should run pkg install with the package name and version" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkg install -q crypto/gnupg at 2.0.17"
+      })
+      @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
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkg uninstall -q crypto/gnupg at 2.0.17"
+      })
+      @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
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkg --no-refresh uninstall -q crypto/gnupg at 2.0.17"
+      })
+      @new_resource.stub!(:options).and_return("--no-refresh")
+      @provider.remove_package("crypto/gnupg", "2.0.17")
+    end
+  end
+end
diff --git a/spec/unit/provider/package/macports_spec.rb b/spec/unit/provider/package/macports_spec.rb
new file mode 100644
index 0000000..2f0db3f
--- /dev/null
+++ b/spec/unit/provider/package/macports_spec.rb
@@ -0,0 +1,203 @@
+#
+# Author:: David Balatero (<dbalatero at gmail.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Package::Macports do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new("zsh")
+    @current_resource = Chef::Resource::Package.new("zsh")
+
+    @provider = Chef::Provider::Package::Macports.new(@new_resource, @run_context)
+    Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+
+    @status = mock("Status", :exitstatus => 0)
+    @stdin = StringIO.new
+    @stdout = StringIO.new
+    @stderr = StringIO.new
+    @pid = 2342
+  end
+
+  describe "load_current_resource" do
+    it "should create a current resource with the name of the new_resource" do
+      @provider.should_receive(:current_installed_version).and_return(nil)
+      @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+
+      @provider.load_current_resource
+      @provider.current_resource.name.should == "zsh"
+    end
+
+    it "should create a current resource with the version if the package is installed" do
+      @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+      @provider.should_receive(:current_installed_version).and_return("4.2.7")
+
+      @provider.load_current_resource
+      @provider.candidate_version.should == "4.2.7"
+    end
+
+    it "should create a current resource with a nil version if the package is not installed" do
+      @provider.should_receive(:current_installed_version).and_return(nil)
+      @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+
+    it "should set a candidate version if one exists" do
+      @provider.should_receive(:current_installed_version).and_return(nil)
+      @provider.should_receive(:macports_candidate_version).and_return("4.2.7")
+      @provider.load_current_resource
+      @provider.candidate_version.should == "4.2.7"
+    end
+  end
+
+  describe "current_installed_version" do
+    it "should return the current version if the package is installed" do
+      @stdout.should_receive(:read).and_return(<<EOF
+The following ports are currently installed:
+  openssl @0.9.8k_0 (active)
+EOF
+      )
+
+      @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.current_installed_version.should == "0.9.8k_0"
+    end
+
+    it "should return nil if a package is not currently installed" do
+      @stdout.should_receive(:read).and_return("       \n")
+      @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.current_installed_version.should be_nil
+    end
+  end
+
+  describe "macports_candidate_version" do
+    it "should return the latest available version of a given package" do
+      @stdout.should_receive(:read).and_return("version: 4.2.7\n")
+      @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.macports_candidate_version.should == "4.2.7"
+    end
+
+    it "should return nil if there is no version for a given package" do
+      @stdout.should_receive(:read).and_return("Error: port fadsfadsfads not found\n")
+      @provider.should_receive(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.macports_candidate_version.should be_nil
+    end
+  end
+
+  describe "install_package" do
+    it "should run the port install command with the correct version" do
+      @current_resource.should_receive(:version).and_return("4.1.6")
+      @provider.current_resource = @current_resource
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port install zsh @4.2.7")
+
+      @provider.install_package("zsh", "4.2.7")
+    end
+
+    it "should not do anything if a package already exists with the same version" do
+      @current_resource.should_receive(:version).and_return("4.2.7")
+      @provider.current_resource = @current_resource
+      @provider.should_not_receive(:run_command_with_systems_locale)
+
+      @provider.install_package("zsh", "4.2.7")
+    end
+
+    it "should add options to the port command when specified" do
+      @current_resource.should_receive(:version).and_return("4.1.6")
+      @provider.current_resource = @current_resource
+      @new_resource.stub!(:options).and_return("-f")
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f install zsh @4.2.7")
+
+      @provider.install_package("zsh", "4.2.7")
+    end
+  end
+
+  describe "purge_package" do
+    it "should run the port uninstall command with the correct version" do
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh @4.2.7")
+      @provider.purge_package("zsh", "4.2.7")
+    end
+
+    it "should purge the currently active version if no explicit version is passed in" do
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh")
+      @provider.purge_package("zsh", nil)
+    end
+
+    it "should add options to the port command when specified" do
+      @new_resource.stub!(:options).and_return("-f")
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f uninstall zsh @4.2.7")
+      @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
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh @4.2.7")
+      @provider.remove_package("zsh", "4.2.7")
+    end
+
+    it "should remove the currently active version if no explicit version is passed in" do
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh")
+      @provider.remove_package("zsh", nil)
+    end
+
+    it "should add options to the port command when specified" do
+      @new_resource.stub!(:options).and_return("-f")
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f deactivate zsh @4.2.7")
+      @provider.remove_package("zsh", "4.2.7")
+    end
+  end
+
+  describe "upgrade_package" do
+    it "should run the port upgrade command with the correct version" do
+      @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6")
+      @provider.current_resource = @current_resource
+
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port upgrade zsh @4.2.7")
+
+      @provider.upgrade_package("zsh", "4.2.7")
+    end
+
+    it "should not run the port upgrade command if the version is already installed" do
+      @current_resource.should_receive(:version).at_least(:once).and_return("4.2.7")
+      @provider.current_resource = @current_resource
+      @provider.should_not_receive(:run_command_with_systems_locale)
+
+      @provider.upgrade_package("zsh", "4.2.7")
+    end
+
+    it "should call install_package if the package isn't currently installed" do
+      @current_resource.should_receive(:version).at_least(:once).and_return(nil)
+      @provider.current_resource = @current_resource
+      @provider.should_receive(:install_package).and_return(true)
+
+      @provider.upgrade_package("zsh", "4.2.7")
+    end
+
+    it "should add options to the port command when specified" do
+      @new_resource.stub!(:options).and_return("-f")
+      @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6")
+      @provider.current_resource = @current_resource
+
+      @provider.should_receive(:run_command_with_systems_locale).with(:command => "port -f upgrade zsh @4.2.7")
+
+      @provider.upgrade_package("zsh", "4.2.7")
+    end
+  end
+end
diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb
new file mode 100644
index 0000000..7e4abcb
--- /dev/null
+++ b/spec/unit/provider/package/pacman_spec.rb
@@ -0,0 +1,206 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek at web.de>)
+# Copyright:: Copyright (c) 2010 Jan Zimmek
+# 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::Package::Pacman do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new("nano")
+    @current_resource = Chef::Resource::Package.new("nano")
+
+    @status = mock("Status", :exitstatus => 0)
+    @provider = Chef::Provider::Package::Pacman.new(@new_resource, @run_context)
+    Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+    @provider.stub!(:popen4).and_return(@status)
+    @stdin = StringIO.new
+    @stdout = StringIO.new(<<-ERR)
+error: package "nano" not found
+ERR
+    @stderr = StringIO.new
+    @pid = 2342
+  end
+
+  describe "when determining the current package state" do
+    it "should create a current resource with the name of the new_resource" do
+      Chef::Resource::Package.should_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
+      @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+      @provider.load_current_resource
+    end
+
+    it "should run pacman query with the package name" do
+      @provider.should_receive(:popen4).with("pacman -Qi #{@new_resource.package_name}").and_return(@status)
+      @provider.load_current_resource
+    end
+
+    it "should read stdout on pacman" do
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @stdout.should_receive(:each).and_return(true)
+      @provider.load_current_resource
+    end
+
+    it "should set the installed version to nil on the current resource if pacman installed version not exists" do
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @current_resource.should_receive(:version).with(nil).and_return(true)
+      @provider.load_current_resource
+    end
+
+    it "should set the installed version if pacman has one" do
+      @stdout = StringIO.new(<<-PACMAN)
+Name           : nano
+Version        : 2.2.2-1
+URL            : http://www.nano-editor.org
+Licenses       : GPL
+Groups         : base
+Provides       : None
+Depends On     : glibc  ncurses
+Optional Deps  : None
+Required By    : None
+Conflicts With : None
+Replaces       : None
+Installed Size : 1496.00 K
+Packager       : Andreas Radke <andyrtr at archlinux.org>
+Architecture   : i686
+Build Date     : Mon 18 Jan 2010 06:16:16 PM CET
+Install Date   : Mon 01 Feb 2010 10:06:30 PM CET
+Install Reason : Explicitly installed
+Install Script : Yes
+Description    : Pico editor clone with enhancements
+PACMAN
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @current_resource.version.should == "2.2.2-1"
+    end
+
+    it "should set the candidate version if pacman has one" do
+      @stdout.stub!(:each).and_yield("core/nano 2.2.3-1 (base)").
+                            and_yield("    Pico editor clone with enhancements").
+                            and_yield("community/nanoblogger 3.4.1-1").
+                            and_yield("    NanoBlogger is a small weblog engine written in Bash for the command line")
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.candidate_version.should eql("2.2.3-1")
+    end
+
+    it "should use pacman.conf to determine valid repo names for package versions" do
+     @pacman_conf = <<-PACMAN_CONF
+[options]
+HoldPkg      = pacman glibc
+Architecture = auto
+
+[customrepo]
+Server = https://my.custom.repo
+
+[core]
+Include = /etc/pacman.d/mirrorlist
+
+[extra]
+Include = /etc/pacman.d/mirrorlist
+
+[community]
+Include = /etc/pacman.d/mirrorlist
+PACMAN_CONF
+
+      ::File.stub!(:exists?).with("/etc/pacman.conf").and_return(true)
+      ::File.stub!(:read).with("/etc/pacman.conf").and_return(@pacman_conf)
+      @stdout.stub!(:each).and_yield("customrepo/nano 1.2.3-4").
+                            and_yield("    My custom package")
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+
+      @provider.load_current_resource
+      @provider.candidate_version.should eql("1.2.3-4")
+    end
+
+    it "should raise an exception if pacman fails" do
+      @status.should_receive(:exitstatus).and_return(2)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should not raise an exception if pacman succeeds" do
+      @status.should_receive(:exitstatus).and_return(0)
+      lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should raise an exception if pacman does not return a candidate version" do
+      @stdout.stub!(:each).and_yield("")
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should return the current resouce" do
+      @provider.load_current_resource.should eql(@current_resource)
+    end
+  end
+
+  describe Chef::Provider::Package::Pacman, "install_package" do
+    it "should run pacman install with the package name and version" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pacman --sync --noconfirm --noprogressbar nano"
+      })
+      @provider.install_package("nano", "1.0")
+    end
+
+    it "should run pacman install with the package name and version and options if specified" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pacman --sync --noconfirm --noprogressbar --debug nano"
+      })
+      @new_resource.stub!(:options).and_return("--debug")
+
+      @provider.install_package("nano", "1.0")
+    end
+  end
+
+  describe Chef::Provider::Package::Pacman, "upgrade_package" do
+    it "should run install_package with the name and version" do
+      @provider.should_receive(:install_package).with("nano", "1.0")
+      @provider.upgrade_package("nano", "1.0")
+    end
+  end
+
+  describe Chef::Provider::Package::Pacman, "remove_package" do
+    it "should run pacman remove with the package name" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pacman --remove --noconfirm --noprogressbar nano"
+      })
+      @provider.remove_package("nano", "1.0")
+    end
+
+    it "should run pacman remove with the package name and options if specified" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pacman --remove --noconfirm --noprogressbar --debug nano"
+      })
+      @new_resource.stub!(:options).and_return("--debug")
+
+      @provider.remove_package("nano", "1.0")
+    end
+  end
+
+  describe Chef::Provider::Package::Pacman, "purge_package" do
+    it "should run remove_package with the name and version" do
+      @provider.should_receive(:remove_package).with("nano", "1.0")
+      @provider.purge_package("nano", "1.0")
+    end
+
+  end
+end
diff --git a/spec/unit/provider/package/portage_spec.rb b/spec/unit/provider/package/portage_spec.rb
new file mode 100644
index 0000000..f44731b
--- /dev/null
+++ b/spec/unit/provider/package/portage_spec.rb
@@ -0,0 +1,320 @@
+#
+# Author:: Caleb Tennis (<caleb.tennis at gmail.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'
+
+describe Chef::Provider::Package::Portage, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new("dev-util/git")
+    @new_resource_without_category = Chef::Resource::Package.new("git")
+    @current_resource = Chef::Resource::Package.new("dev-util/git")
+
+    @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context)
+    Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+  end
+
+  describe "when determining the current state of the package" do
+
+    it "should create a current resource with the name of new_resource" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"])
+      Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+      @provider.load_current_resource
+    end
+
+    it "should set the current resource package name to the new resource package name" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0"])
+      @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+      @provider.load_current_resource
+    end
+
+    it "should return a current resource with the correct version if the package is found" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"])
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "1.0.0"
+    end
+
+    it "should return a current resource with the correct version if the package is found with revision" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0-r1"])
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "1.0.0-r1"
+    end
+
+    it "should return a current resource with a nil version if the package is not found" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"])
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+
+    it "should return a package name match from /var/db/pkg/* if a category isn't specified and a match is found" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-foobar-0.9", "/var/db/pkg/dev-util/git-1.0.0"])
+      @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "1.0.0"
+    end
+
+    it "should return a current resource with a nil version if a category isn't specified and a name match from /var/db/pkg/* is not found" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/notgit-1.0.0"])
+      @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+
+    it "should throw an exception if a category isn't specified and multiple packages are found" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/funny-words/git-1.0.0"])
+      @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should return a current resource with a nil version if a category is specified and multiple packages are found" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/dev-util/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/funny-words/git-1.0.0"])
+      @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+
+    it "should return a current resource with a nil version if a category is not specified and multiple packages from the same category are found" do
+      ::Dir.stub!(:[]).with("/var/db/pkg/*/git-*").and_return(["/var/db/pkg/dev-util/git-1.0.0", "/var/db/pkg/dev-util/git-1.0.1"])
+      @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+  end
+
+  describe "once the state of the package is known" do
+
+    describe Chef::Provider::Package::Portage, "candidate_version" do
+      it "should return the candidate_version variable if already set" do
+        @provider.candidate_version = "1.0.0"
+        @provider.should_not_receive(:popen4)
+        @provider.candidate_version
+      end
+
+      it "should throw an exception if the exitstatus is not 0" do
+        @status = mock("Status", :exitstatus => 1)
+        @provider.stub!(:popen4).and_return(@status)
+        lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+      end
+
+      it "should find the candidate_version if a category is specifed and there are no duplicates" do
+        output = <<EOF
+Searching...
+[ Results for search key : git ]
+[ Applications found : 14 ]
+
+*  app-misc/digitemp [ Masked ]
+      Latest version available: 3.5.0
+      Latest version installed: [ Not Installed ]
+      Size of files: 261 kB
+      Homepage:      http://www.digitemp.com/ http://www.ibutton.com/
+      Description:   Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
+      License:       GPL-2
+
+*  dev-util/git
+      Latest version available: 1.6.0.6
+      Latest version installed: ignore
+      Size of files: 2,725 kB
+      Homepage:      http://git.or.cz/
+      Description:   GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
+      License:       GPL-2
+
+*  dev-util/gitosis [ Masked ]
+      Latest version available: 0.2_p20080825
+      Latest version installed: [ Not Installed ]
+      Size of files: 31 kB
+      Homepage:      http://eagain.net/gitweb/?p=gitosis.git;a=summary
+      Description:   gitosis -- software for hosting git repositories
+      License:       GPL-2
+EOF
+
+        @status = mock("Status", :exitstatus => 0)
+        @provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
+        @provider.candidate_version.should == "1.6.0.6"
+      end
+
+      it "should find the candidate_version if a category is not specifed and there are no duplicates" do
+        output = <<EOF
+Searching...
+[ Results for search key : git ]
+[ Applications found : 14 ]
+
+*  app-misc/digitemp [ Masked ]
+      Latest version available: 3.5.0
+      Latest version installed: [ Not Installed ]
+      Size of files: 261 kB
+      Homepage:      http://www.digitemp.com/ http://www.ibutton.com/
+      Description:   Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
+      License:       GPL-2
+
+*  dev-util/git
+      Latest version available: 1.6.0.6
+      Latest version installed: ignore
+      Size of files: 2,725 kB
+      Homepage:      http://git.or.cz/
+      Description:   GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
+      License:       GPL-2
+
+*  dev-util/gitosis [ Masked ]
+      Latest version available: 0.2_p20080825
+      Latest version installed: [ Not Installed ]
+      Size of files: 31 kB
+      Homepage:      http://eagain.net/gitweb/?p=gitosis.git;a=summary
+      Description:   gitosis -- software for hosting git repositories
+      License:       GPL-2
+EOF
+
+        @status = mock("Status", :exitstatus => 0)
+        @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+        @provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
+        @provider.candidate_version.should == "1.6.0.6"
+      end
+
+      it "should throw an exception if a category is not specified and there are duplicates" do
+        output = <<EOF
+Searching...
+[ Results for search key : git ]
+[ Applications found : 14 ]
+
+*  app-misc/digitemp [ Masked ]
+      Latest version available: 3.5.0
+      Latest version installed: [ Not Installed ]
+      Size of files: 261 kB
+      Homepage:      http://www.digitemp.com/ http://www.ibutton.com/
+      Description:   Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
+      License:       GPL-2
+
+*  app-misc/git
+      Latest version available: 4.3.20
+      Latest version installed: [ Not Installed ]
+      Size of files: 416 kB
+      Homepage:      http://www.gnu.org/software/git/
+      Description:   GNU Interactive Tools - increase speed and efficiency of most daily task
+      License:       GPL-2
+
+*  dev-util/git
+      Latest version available: 1.6.0.6
+      Latest version installed: ignore
+      Size of files: 2,725 kB
+      Homepage:      http://git.or.cz/
+      Description:   GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
+      License:       GPL-2
+
+*  dev-util/gitosis [ Masked ]
+      Latest version available: 0.2_p20080825
+      Latest version installed: [ Not Installed ]
+      Size of files: 31 kB
+      Homepage:      http://eagain.net/gitweb/?p=gitosis.git;a=summary
+      Description:   gitosis -- software for hosting git repositories
+      License:       GPL-2
+EOF
+
+        @status = mock("Status", :exitstatus => 0)
+        @provider = Chef::Provider::Package::Portage.new(@new_resource_without_category, @run_context)
+        @provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
+        lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+      end
+
+      it "should find the candidate_version if a category is specifed and there are category duplicates" do
+        output = <<EOF
+Searching...
+[ Results for search key : git ]
+[ Applications found : 14 ]
+
+*  app-misc/digitemp [ Masked ]
+      Latest version available: 3.5.0
+      Latest version installed: [ Not Installed ]
+      Size of files: 261 kB
+      Homepage:      http://www.digitemp.com/ http://www.ibutton.com/
+      Description:   Temperature logging and reporting using Dallas Semiconductor's iButtons and 1-Wire protocol
+      License:       GPL-2
+
+*  app-misc/git
+      Latest version available: 4.3.20
+      Latest version installed: [ Not Installed ]
+      Size of files: 416 kB
+      Homepage:      http://www.gnu.org/software/git/
+      Description:   GNU Interactive Tools - increase speed and efficiency of most daily task
+      License:       GPL-2
+
+*  dev-util/git
+      Latest version available: 1.6.0.6
+      Latest version installed: ignore
+      Size of files: 2,725 kB
+      Homepage:      http://git.or.cz/
+      Description:   GIT - the stupid content tracker, the revision control system heavily used by the Linux kernel team
+      License:       GPL-2
+
+*  dev-util/gitosis [ Masked ]
+      Latest version available: 0.2_p20080825
+      Latest version installed: [ Not Installed ]
+      Size of files: 31 kB
+      Homepage:      http://eagain.net/gitweb/?p=gitosis.git;a=summary
+      Description:   gitosis -- software for hosting git repositories
+      License:       GPL-2
+EOF
+
+        @status = mock("Status", :exitstatus => 0)
+        @provider = Chef::Provider::Package::Portage.new(@new_resource, @run_context)
+        @provider.should_receive(:popen4).and_yield(nil, nil, StringIO.new(output), nil).and_return(@status)
+        @provider.candidate_version.should == "1.6.0.6"
+      end
+    end
+
+    describe Chef::Provider::Package::Portage, "install_package" do
+      it "should install a normally versioned package using portage" do
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "emerge -g --color n --nospinner --quiet =dev-util/git-1.0.0"
+        })
+        @provider.install_package("dev-util/git", "1.0.0")
+      end
+
+      it "should install a tilde versioned package using portage" do
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "emerge -g --color n --nospinner --quiet ~dev-util/git-1.0.0"
+        })
+        @provider.install_package("dev-util/git", "~1.0.0")
+      end
+
+      it "should add options to the emerge command when specified" do
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "emerge -g --color n --nospinner --quiet --oneshot =dev-util/git-1.0.0"
+        })
+        @new_resource.stub!(:options).and_return("--oneshot")
+
+        @provider.install_package("dev-util/git", "1.0.0")
+      end
+    end
+
+    describe Chef::Provider::Package::Portage, "remove_package" do
+      it "should un-emerge the package with no version specified" do
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "emerge --unmerge --color n --nospinner --quiet dev-util/git"
+        })
+        @provider.remove_package("dev-util/git", nil)
+      end
+
+      it "should un-emerge the package with a version specified" do
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "emerge --unmerge --color n --nospinner --quiet =dev-util/git-1.0.0"
+        })
+        @provider.remove_package("dev-util/git", "1.0.0")
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb
new file mode 100644
index 0000000..86327be
--- /dev/null
+++ b/spec/unit/provider/package/rpm_spec.rb
@@ -0,0 +1,152 @@
+#
+# Author:: Joshua Timberman (<joshua at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'spec_helper'
+
+describe Chef::Provider::Package::Rpm do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Package.new("emacs")
+    @new_resource.source "/tmp/emacs-21.4-20.el5.i386.rpm"
+
+    @provider = Chef::Provider::Package::Rpm.new(@new_resource, @run_context)
+
+    @status = mock("Status", :exitstatus => 0)
+    ::File.stub!(:exists?).and_return(true)
+  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.stub!(:popen4).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.name.should == "emacs"
+    end
+
+    it "should set the current reource package name to the new resource package name" do
+      @provider.stub!(:popen4).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.package_name.should == 'emacs'
+    end
+
+    it "should raise an exception if a source is supplied but not found" do
+      ::File.stub!(:exists?).and_return(false)
+      lambda { @provider.run_action(:any) }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should get the source package version from rpm if provided" do
+      @stdout = StringIO.new("emacs 21.4-20.el5")
+      @provider.should_receive(:popen4).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/emacs-21.4-20.el5.i386.rpm").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.should_receive(:popen4).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' emacs").and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.package_name.should == "emacs"
+      @provider.new_resource.version.should == "21.4-20.el5"
+    end
+
+    it "should return the current version installed if found by rpm" do
+      @stdout = StringIO.new("emacs 21.4-20.el5")
+      @provider.should_receive(:popen4).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/emacs-21.4-20.el5.i386.rpm").and_return(@status)
+      @provider.should_receive(:popen4).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' emacs").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "21.4-20.el5"
+    end
+
+    it "should raise an exception if the source is not set but we are installing" do
+      new_resource = Chef::Resource::Package.new("emacs")
+      provider = Chef::Provider::Package::Rpm.new(new_resource, @run_context)
+      lambda { provider.run_action(:any) }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should raise an exception if rpm fails to run" do
+      status = mock("Status", :exitstatus => -1)
+      @provider.stub!(:popen4).and_return(status)
+      lambda { @provider.run_action(:any) }.should raise_error(Chef::Exceptions::Package)
+    end
+  end
+
+  describe "after the current resource is loaded" do
+    before do
+      @current_resource = Chef::Resource::Package.new("emacs")
+      @provider.current_resource = @current_resource
+    end
+
+    describe "when installing or upgrading" do
+      it "should run rpm -i with the package source to install" do
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "rpm  -i /tmp/emacs-21.4-20.el5.i386.rpm"
+        })
+        @provider.install_package("emacs", "21.4-20.el5")
+      end
+
+      it "should run rpm -U with the package source to upgrade" do
+        @current_resource.version("21.4-19.el5")
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "rpm  -U /tmp/emacs-21.4-20.el5.i386.rpm"
+        })
+        @provider.upgrade_package("emacs", "21.4-20.el5")
+      end
+
+      it "should install from a path when the package is a path and the source is nil" do
+        @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm")
+        @provider = Chef::Provider::Package::Rpm.new(@new_resource, @run_context)
+        @new_resource.source.should == "/tmp/emacs-21.4-20.el5.i386.rpm"
+        @current_resource = Chef::Resource::Package.new("emacs")
+        @provider.current_resource = @current_resource
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "rpm  -i /tmp/emacs-21.4-20.el5.i386.rpm"
+        })
+        @provider.install_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
+      end
+
+      it "should uprgrade from a path when the package is a path and the source is nil" do
+        @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm")
+        @provider = Chef::Provider::Package::Rpm.new(@new_resource, @run_context)
+        @new_resource.source.should == "/tmp/emacs-21.4-20.el5.i386.rpm"
+        @current_resource = Chef::Resource::Package.new("emacs")
+        @current_resource.version("21.4-19.el5")
+        @provider.current_resource = @current_resource
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "rpm  -U /tmp/emacs-21.4-20.el5.i386.rpm"
+        })
+        @provider.upgrade_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
+      end
+
+      it "installs with custom options specified in the resource" do
+        @provider.candidate_version = '11'
+        @new_resource.options("--dbpath /var/lib/rpm")
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "rpm --dbpath /var/lib/rpm -i /tmp/emacs-21.4-20.el5.i386.rpm"
+        })
+        @provider.install_package(@new_resource.name, @provider.candidate_version)
+      end
+    end
+
+    describe "when removing the package" do
+      it "should run rpm -e to remove the package" do
+        @provider.should_receive(:run_command_with_systems_locale).with({
+          :command => "rpm  -e emacs-21.4-20.el5"
+        })
+        @provider.remove_package("emacs", "21.4-20.el5")
+      end
+    end
+  end
+end
+
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
new file mode 100644
index 0000000..7d05673
--- /dev/null
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -0,0 +1,639 @@
+#
+# Author:: David Balatero (dbalatero at gmail.com)
+#
+# Copyright:: Copyright (c) 2009 David Balatero
+# 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 'pp'
+
+module GemspecBackcompatCreator
+  def gemspec(name, version)
+    if Gem::Specification.new.method(:initialize).arity == 0
+      Gem::Specification.new { |s| s.name = name; s.version = version }
+    else
+      Gem::Specification.new(name, version)
+    end
+  end
+end
+
+require 'spec_helper'
+require 'ostruct'
+
+describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
+  include GemspecBackcompatCreator
+
+  before do
+    @gem_env = Chef::Provider::Package::Rubygems::CurrentGemEnvironment.new
+  end
+
+  it "determines the gem paths from the in memory rubygems" do
+    @gem_env.gem_paths.should == Gem.path
+  end
+
+  it "determines the installed versions of gems from Gem.source_index" do
+    gems = [gemspec('rspec-core', Gem::Version.new('1.2.9')), gemspec('rspec-core', Gem::Version.new('1.3.0'))]
+    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+      Gem::Specification.should_receive(:find_all_by_name).with('rspec-core', Gem::Dependency.new('rspec-core').requirement).and_return(gems)
+    else
+      Gem.source_index.should_receive(:search).with(Gem::Dependency.new('rspec-core', nil)).and_return(gems)
+    end
+    @gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).should == gems
+  end
+
+  it "determines the installed versions of gems from the source index (part2: the unmockening)" do
+    expected = ['rspec-core', Gem::Version.new(RSpec::Core::Version::STRING)]
+    actual = @gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).map { |spec| [spec.name, spec.version] }
+    actual.should include(expected)
+  end
+
+  it "yields to a block with an alternate source list set" do
+    sources_in_block = nil
+    normal_sources = Gem.sources
+    begin
+      @gem_env.with_gem_sources("http://gems.example.org") do
+        sources_in_block = Gem.sources
+        raise RuntimeError, "sources should be reset even in case of an error"
+      end
+    rescue RuntimeError
+    end
+    sources_in_block.should == %w{http://gems.example.org}
+    Gem.sources.should == normal_sources
+  end
+
+  it "it doesnt alter the gem sources if none are set" do
+    sources_in_block = nil
+    normal_sources = Gem.sources
+    begin
+      @gem_env.with_gem_sources(nil) do
+        sources_in_block = Gem.sources
+        raise RuntimeError, "sources should be reset even in case of an error"
+      end
+    rescue RuntimeError
+    end
+    sources_in_block.should == normal_sources
+    Gem.sources.should == normal_sources
+  end
+
+  it "finds a matching gem candidate version" do
+    dep = Gem::Dependency.new('rspec', '>= 0')
+    dep_installer = Gem::DependencyInstaller.new
+    @gem_env.stub!(:dependency_installer).and_return(dep_installer)
+    latest = [[gemspec("rspec", Gem::Version.new("1.3.0")), "http://rubygems.org/"]]
+    dep_installer.should_receive(:find_gems_with_sources).with(dep).and_return(latest)
+    @gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0')).should == Gem::Version.new('1.3.0')
+  end
+
+  it "finds a matching gem candidate version on rubygems 2.0.0+" do
+    dep = Gem::Dependency.new('rspec', '>= 0')
+    dep_installer = Gem::DependencyInstaller.new
+    @gem_env.stub!(:dependency_installer).and_return(dep_installer)
+    best_gem = mock("best gem match", :spec => gemspec("rspec", Gem::Version.new("1.3.0")), :source => "https://rubygems.org")
+    available_set = mock("Gem::AvailableSet test double")
+    available_set.should_receive(:pick_best!)
+    available_set.should_receive(:set).and_return([best_gem])
+    dep_installer.should_receive(:find_gems_with_sources).with(dep).and_return(available_set)
+    @gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0')).should == 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
+      Gem::Package.stub!(: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 = mock("Gem::Package", :spec => "a gemspec from package")
+      Gem::Package.should_receive(:new).with("/path/to/package.gem").and_return(package)
+      @gem_env.spec_from_file("/path/to/package.gem").should == "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 = []
+    dep_installer = Gem::DependencyInstaller.new
+    @gem_env.stub!(:dependency_installer).and_return(dep_installer)
+    dep_installer.should_receive(:find_gems_with_sources).with(dep).and_return(latest)
+    @gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0')).should be_nil
+  end
+
+  it "finds a matching candidate version from a .gem file when the path to the gem is supplied" do
+    location = CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem'
+    @gem_env.candidate_version_from_file(Gem::Dependency.new('chef-integration-test', '>= 0'), location).should == Gem::Version.new('0.1.0')
+    @gem_env.candidate_version_from_file(Gem::Dependency.new('chef-integration-test', '>= 0.2.0'), location).should be_nil
+  end
+
+  it "finds a matching gem from a specific gemserver when explicit sources are given" do
+    dep = Gem::Dependency.new('rspec', '>= 0')
+    latest = [[gemspec("rspec", Gem::Version.new("1.3.0")), "http://rubygems.org/"]]
+
+    @gem_env.should_receive(:with_gem_sources).with('http://gems.example.com').and_yield
+    dep_installer = Gem::DependencyInstaller.new
+    @gem_env.stub!(:dependency_installer).and_return(dep_installer)
+    dep_installer.should_receive(:find_gems_with_sources).with(dep).and_return(latest)
+    @gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>=0'), 'http://gems.example.com').should == Gem::Version.new('1.3.0')
+  end
+
+  it "installs a gem with a hash of options for the dependency installer" do
+    dep_installer = Gem::DependencyInstaller.new
+    @gem_env.should_receive(:dependency_installer).with(:install_dir => '/foo/bar').and_return(dep_installer)
+    @gem_env.should_receive(:with_gem_sources).with('http://gems.example.com').and_yield
+    dep_installer.should_receive(:install).with(Gem::Dependency.new('rspec', '>= 0'))
+    @gem_env.install(Gem::Dependency.new('rspec', '>= 0'), :install_dir => '/foo/bar', :sources => ['http://gems.example.com'])
+  end
+
+  it "builds an uninstaller for a gem with options set to avoid requiring user input" do
+    # default options for uninstaller should be:
+    # :ignore => true, :executables => true
+    Gem::Uninstaller.should_receive(:new).with('rspec', :ignore => true, :executables => true)
+    @gem_env.uninstaller('rspec')
+  end
+
+  it "uninstalls all versions of a gem" do
+    uninstaller = mock('gem uninstaller')
+    uninstaller.should_receive(:uninstall)
+    @gem_env.should_receive(:uninstaller).with('rspec', :all => true).and_return(uninstaller)
+    @gem_env.uninstall('rspec')
+  end
+
+  it "uninstalls a specific version of a gem" do
+    uninstaller = mock('gem uninstaller')
+    uninstaller.should_receive(:uninstall)
+    @gem_env.should_receive(:uninstaller).with('rspec', :version => '1.2.3').and_return(uninstaller)
+    @gem_env.uninstall('rspec', '1.2.3')
+  end
+
+end
+
+describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
+  include GemspecBackcompatCreator
+
+  before do
+    Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache.clear
+    Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache.clear
+    @gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new('/usr/weird/bin/gem')
+  end
+
+  it "determines the gem paths from shelling out to gem env" do
+    gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
+    shell_out_result = OpenStruct.new(:stdout => gem_env_output)
+    @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env gempath').and_return(shell_out_result)
+    @gem_env.gem_paths.should == ['/path/to/gems', '/another/path/to/gems']
+  end
+
+  it "caches the gempaths by gem_binary" do
+    gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
+    shell_out_result = OpenStruct.new(:stdout => gem_env_output)
+    @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env gempath').and_return(shell_out_result)
+    expected = ['/path/to/gems', '/another/path/to/gems']
+    @gem_env.gem_paths.should == ['/path/to/gems', '/another/path/to/gems']
+    Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem'].should == expected
+  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)
+    @gem_env.should_not_receive(:shell_out!)
+    expected = ['/path/to/gems', '/another/path/to/gems']
+    Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem']= expected
+    @gem_env.gem_paths.should == ['/path/to/gems', '/another/path/to/gems']
+  end
+
+  it "builds the gems source index from the gem paths" do
+    @gem_env.stub!(:gem_paths).and_return(['/path/to/gems', '/another/path/to/gems'])
+    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+      @gem_env.gem_specification
+      Gem::Specification.dirs.should == [ '/path/to/gems/specifications', '/another/path/to/gems/specifications' ]
+    else
+      Gem::SourceIndex.should_receive(:from_gems_in).with('/path/to/gems/specifications', '/another/path/to/gems/specifications')
+      @gem_env.gem_source_index
+    end
+  end
+
+  it "determines the installed versions of gems from the source index" do
+    gems = [gemspec('rspec', Gem::Version.new('1.2.9')), gemspec('rspec', Gem::Version.new('1.3.0'))]
+    rspec_dep = Gem::Dependency.new('rspec', nil)
+    if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+      @gem_env.stub!(:gem_specification).and_return(Gem::Specification)
+      @gem_env.gem_specification.should_receive(:find_all_by_name).with(rspec_dep.name, rspec_dep.requirement).and_return(gems)
+    else
+      @gem_env.stub!(:gem_source_index).and_return(Gem.source_index)
+      @gem_env.gem_source_index.should_receive(:search).with(rspec_dep).and_return(gems)
+    end
+    @gem_env.installed_versions(Gem::Dependency.new('rspec', nil)).should == gems
+  end
+
+  it "determines the installed versions of gems from the source index (part2: the unmockening)" do
+    $stdout.stub!(:write)
+    path_to_gem = if windows?
+      `where gem`.split[1]
+    else
+      `which gem`.strip
+    end
+    pending("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] }
+    actual.should include(expected)
+  end
+
+  it "detects when the target gem environment is the jruby platform" do
+    gem_env_out=<<-JRUBY_GEM_ENV
+RubyGems Environment:
+  - RUBYGEMS VERSION: 1.3.6
+  - RUBY VERSION: 1.8.7 (2010-05-12 patchlevel 249) [java]
+  - INSTALLATION DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0
+  - RUBY EXECUTABLE: /Users/you/.rvm/rubies/jruby-1.5.0/bin/jruby
+  - EXECUTABLE DIRECTORY: /Users/you/.rvm/gems/jruby-1.5.0/bin
+  - RUBYGEMS PLATFORMS:
+    - ruby
+    - universal-java-1.6
+  - GEM PATHS:
+     - /Users/you/.rvm/gems/jruby-1.5.0
+     - /Users/you/.rvm/gems/jruby-1.5.0 at global
+  - GEM CONFIGURATION:
+     - :update_sources => true
+     - :verbose => true
+     - :benchmark => false
+     - :backtrace => false
+     - :bulk_threshold => 1000
+     - "install" => "--env-shebang"
+     - "update" => "--env-shebang"
+     - "gem" => "--no-rdoc --no-ri"
+     - :sources => ["http://rubygems.org/", "http://gems.github.com/"]
+  - REMOTE SOURCES:
+     - http://rubygems.org/
+     - http://gems.github.com/
+JRUBY_GEM_ENV
+    @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env').and_return(mock('jruby_gem_env', :stdout => gem_env_out))
+    expected = ['ruby', Gem::Platform.new('universal-java-1.6')]
+    @gem_env.gem_platforms.should == expected
+    # it should also cache the result
+    Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache['/usr/weird/bin/gem'].should == expected
+  end
+
+  it "uses the cached result for gem platforms if available" do
+    @gem_env.should_not_receive(:shell_out!)
+    expected = ['ruby', Gem::Platform.new('universal-java-1.6')]
+    Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache['/usr/weird/bin/gem']= expected
+    @gem_env.gem_platforms.should == expected
+  end
+
+  it "uses the current gem platforms when the target env is not jruby" do
+    gem_env_out=<<-RBX_GEM_ENV
+RubyGems Environment:
+  - RUBYGEMS VERSION: 1.3.6
+  - RUBY VERSION: 1.8.7 (2010-05-14 patchlevel 174) [x86_64-apple-darwin10.3.0]
+  - INSTALLATION DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514
+  - RUBYGEMS PREFIX: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514
+  - RUBY EXECUTABLE: /Users/ddeleo/.rvm/rubies/rbx-1.0.0-20100514/bin/rbx
+  - EXECUTABLE DIRECTORY: /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514/bin
+  - RUBYGEMS PLATFORMS:
+    - ruby
+    - x86_64-darwin-10
+    - x86_64-rubinius-1.0
+  - GEM PATHS:
+     - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514
+     - /Users/ddeleo/.rvm/gems/rbx-1.0.0-20100514 at global
+  - GEM CONFIGURATION:
+     - :update_sources => true
+     - :verbose => true
+     - :benchmark => false
+     - :backtrace => false
+     - :bulk_threshold => 1000
+     - :sources => ["http://rubygems.org/", "http://gems.github.com/"]
+     - "gem" => "--no-rdoc --no-ri"
+  - REMOTE SOURCES:
+     - http://rubygems.org/
+     - http://gems.github.com/
+RBX_GEM_ENV
+    @gem_env.should_receive(:shell_out!).with('/usr/weird/bin/gem env').and_return(mock('rbx_gem_env', :stdout => gem_env_out))
+    @gem_env.gem_platforms.should == Gem.platforms
+    Chef::Provider::Package::Rubygems::AlternateGemEnvironment.platform_cache['/usr/weird/bin/gem'].should == Gem.platforms
+  end
+
+  it "yields to a block while masquerading as a different gems platform" do
+    original_platforms = Gem.platforms
+    platforms_in_block = nil
+    begin
+      @gem_env.with_gem_platforms(['ruby', Gem::Platform.new('sparc64-java-1.7')]) do
+        platforms_in_block = Gem.platforms
+        raise "gem platforms should get set to the correct value even when an error occurs"
+      end
+    rescue RuntimeError
+    end
+    platforms_in_block.should == ['ruby', Gem::Platform.new('sparc64-java-1.7')]
+    Gem.platforms.should == original_platforms
+  end
+
+end
+
+describe Chef::Provider::Package::Rubygems do
+  before(:each) do
+    @node = Chef::Node.new
+    @new_resource = Chef::Resource::GemPackage.new("rspec-core")
+    @spec_version = @new_resource.version RSpec::Core::Version::STRING
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    # We choose detect omnibus via RbConfig::CONFIG['bindir'] in Chef::Provider::Package::Rubygems.new
+    RbConfig::CONFIG.stub!(:[]).with('bindir').and_return("/usr/bin/ruby")
+    @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+  end
+
+  it "triggers a gem configuration load so a later one will not stomp its config values" do
+    # ugly, is there a better way?
+    Gem.instance_variable_get(:@configuration).should_not be_nil
+  end
+
+  it "uses the CurrentGemEnvironment implementation when no gem_binary_path is provided" do
+    @provider.gem_env.should be_a_kind_of(Chef::Provider::Package::Rubygems::CurrentGemEnvironment)
+  end
+
+  it "uses the AlternateGemEnvironment implementation when a gem_binary_path is provided" do
+    @new_resource.gem_binary('/usr/weird/bin/gem')
+    provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+    provider.gem_env.gem_binary_location.should == '/usr/weird/bin/gem'
+  end
+
+  it "searches for a gem binary when running on Omnibus on Unix" do
+    platform_mock :unix do
+      RbConfig::CONFIG.stub!(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
+      ENV.stub!(:[]).with('PATH').and_return("/usr/bin:/usr/sbin:/opt/chef/embedded/bin")
+      File.stub!(:exists?).with('/usr/bin/gem').and_return(false)
+      File.stub!(:exists?).with('/usr/sbin/gem').and_return(true)
+      File.stub!(:exists?).with('/opt/chef/embedded/bin/gem').and_return(true) # should not get here
+      provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+      provider.gem_env.gem_binary_location.should == '/usr/sbin/gem'
+    end
+  end
+
+  it "searches for a gem binary when running on Omnibus on Windows" do
+    platform_mock :windows do
+      RbConfig::CONFIG.stub!(:[]).with('bindir').and_return("d:/opscode/chef/embedded/bin")
+      ENV.stub!(:[]).with('PATH').and_return('C:\windows\system32;C:\windows;C:\Ruby186\bin;d:\opscode\chef\embedded\bin')
+      File.stub!(:exists?).with('C:\\windows\\system32\\gem').and_return(false)
+      File.stub!(:exists?).with('C:\\windows\\gem').and_return(false)
+      File.stub!(:exists?).with('C:\\Ruby186\\bin\\gem').and_return(true)
+      File.stub!(:exists?).with('d:\\opscode\\chef\\bin\\gem').and_return(false) # should not get here
+      File.stub!(:exists?).with('d:\\opscode\\chef\\embedded\\bin\\gem').and_return(false) # should not get here
+      provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+      provider.gem_env.gem_binary_location.should == 'C:\Ruby186\bin\gem'
+    end
+  end
+
+  it "smites you when you try to use a hash of install options with an explicit gem binary" do
+    @new_resource.gem_binary('/foo/bar')
+    @new_resource.options(:fail => :burger)
+    lambda {Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)}.should raise_error(ArgumentError)
+  end
+
+  it "converts the new resource into a gem dependency" do
+    @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', @spec_version)
+    @new_resource.version('~> 1.2.0')
+    @provider.gem_dependency.should == Gem::Dependency.new('rspec-core', '~> 1.2.0')
+  end
+
+  describe "when determining the currently installed version" do
+
+    it "sets the current version to the version specified by the new resource if that version is installed" do
+      @provider.load_current_resource
+      @provider.current_resource.version.should == @spec_version
+    end
+
+    it "sets the current version to the highest installed version if the requested version is not installed" do
+      @new_resource.version('9000.0.2')
+      @provider.load_current_resource
+      @provider.current_resource.version.should == @spec_version
+    end
+
+    it "leaves the current version at nil if the package is not installed" do
+      @new_resource.package_name("no-such-gem-should-exist-with-this-name")
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+
+  end
+
+  describe "when determining the candidate version to install" do
+
+    it "does not query for available versions when the current version is the target version" do
+      @provider.current_resource = @new_resource.dup
+      @provider.candidate_version.should be_nil
+    end
+
+    it "determines the candidate version by querying the remote gem servers" do
+      @new_resource.source('http://mygems.example.com')
+      version = Gem::Version.new(@spec_version)
+      @provider.gem_env.should_receive(:candidate_version_from_remote).
+                        with(Gem::Dependency.new('rspec-core', @spec_version), "http://mygems.example.com").
+                        and_return(version)
+      @provider.candidate_version.should == @spec_version
+    end
+
+    it "parses the gem's specification if the requested source is a file" do
+      @new_resource.package_name('chef-integration-test')
+      @new_resource.version('>= 0')
+      @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+      @provider.candidate_version.should == '0.1.0'
+    end
+
+  end
+
+  describe "when installing a gem" do
+    before do
+      @current_resource = Chef::Resource::GemPackage.new('rspec-core')
+      @provider.current_resource = @current_resource
+      @gem_dep = Gem::Dependency.new('rspec-core', @spec_version)
+      @provider.stub!(:load_current_resource)
+    end
+
+    describe "in the current gem environment" do
+      it "installs the gem via the gems api when no explicit options are used" do
+        @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil)
+        @provider.action_install.should be_true
+      end
+
+      it "installs the gem via the gems api when a remote source is provided" do
+        @new_resource.source('http://gems.example.org')
+        sources = ['http://gems.example.org']
+        @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => sources)
+        @provider.action_install.should be_true
+      end
+
+      it "installs the gem from file via the gems api when no explicit options are used" do
+        @new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+        @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+        @provider.action_install.should be_true
+      end
+
+      it "installs the gem from file via the gems api when the package is a path and the source is nil" do
+        @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+        @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+        @provider.current_resource = @current_resource
+        @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem'
+        @provider.gem_env.should_receive(:install).with(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+        @provider.action_install.should be_true
+      end
+
+      # this catches 'gem_package "foo"' when "./foo" is a file in the cwd, and instead of installing './foo' it fetches the remote gem
+      it "installs the gem via the gems api, when the package has no file separator characters in it, but a matching file exists in cwd" do
+        ::File.stub!(:exists?).and_return(true)
+        @new_resource.package_name('rspec-core')
+        @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil)
+        @provider.action_install.should be_true
+      end
+
+      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"
+        @provider.should_receive(:shell_out!).with(expected, :env => nil)
+        @provider.action_install.should be_true
+      end
+
+      it "installs the gem via the gems api when options are given as a Hash" do
+        @new_resource.options(:install_dir => '/alt/install/location')
+        @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil, :install_dir => '/alt/install/location')
+        @provider.action_install.should be_true
+      end
+
+      describe "at a specific version" do
+        before do
+          @gem_dep = Gem::Dependency.new('rspec-core', @spec_version)
+        end
+
+        it "installs the gem via the gems api" do
+          @provider.gem_env.should_receive(:install).with(@gem_dep, :sources => nil)
+          @provider.action_install.should be_true
+        end
+      end
+      describe "at version specified with comparison operator" do
+        it "skips install if current version satisifies requested version" do
+          @current_resource.stub(:version).and_return("2.3.3")
+          @new_resource.stub(:version).and_return(">=2.3.0")
+
+          @provider.gem_env.should_not_receive(:install)
+          @provider.action_install
+        end
+
+        it "allows user to specify gem version with fuzzy operator" do
+          @current_resource.stub(:version).and_return("2.3.3")
+          @new_resource.stub(:version).and_return("~>2.3.0")
+
+          @provider.gem_env.should_not_receive(:install)
+          @provider.action_install
+        end
+      end
+    end
+
+    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')
+        @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil)
+        @provider.action_install.should be_true
+      end
+
+      it "installs the gem from file by shelling out to gem install" 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')
+        @provider.should_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)
+        @provider.action_install.should be_true
+      end
+
+      it "installs the gem from file by shelling out to gem install when the package is a path and the source is nil" do
+        @new_resource = Chef::Resource::GemPackage.new(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
+        @provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+        @provider.current_resource = @current_resource
+        @new_resource.gem_binary('/usr/weird/bin/gem')
+        @new_resource.version('>= 0')
+        @new_resource.source.should == CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem'
+        @provider.should_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)
+        @provider.action_install.should be_true
+      end
+    end
+
+  end
+
+  describe "when uninstalling a gem" do
+    before do
+      @new_resource = Chef::Resource::GemPackage.new("rspec")
+      @current_resource = @new_resource.dup
+      @current_resource.version('1.2.3')
+      @provider.new_resource = @new_resource
+      @provider.current_resource = @current_resource
+    end
+
+    describe "in the current gem environment" do
+      it "uninstalls via the api when no explicit options are used" do
+        # pre-reqs for action_remove to actually remove the package:
+        @provider.new_resource.version.should be_nil
+        @provider.current_resource.version.should_not be_nil
+        # the behavior we're testing:
+        @provider.gem_env.should_receive(:uninstall).with('rspec', nil)
+        @provider.action_remove
+      end
+
+      it "uninstalls via the api when options are given as a Hash" do
+        # pre-reqs for action_remove to actually remove the package:
+        @provider.new_resource.version.should be_nil
+        @provider.current_resource.version.should_not be_nil
+        # the behavior we're testing:
+        @new_resource.options(:install_dir => '/alt/install/location')
+        @provider.gem_env.should_receive(:uninstall).with('rspec', nil, :install_dir => '/alt/install/location')
+        @provider.action_remove
+      end
+
+      it "uninstalls via the gem command when options are given as a String" do
+        @new_resource.options('-i /alt/install/location')
+        @provider.should_receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil)
+        @provider.action_remove
+      end
+
+      it "uninstalls a specific version of a gem when a version is provided" do
+        @new_resource.version('1.2.3')
+        @provider.gem_env.should_receive(:uninstall).with('rspec', '1.2.3')
+        @provider.action_remove
+      end
+    end
+
+    describe "in an alternate gem environment" do
+      it "uninstalls via the gem command" do
+        @new_resource.gem_binary('/usr/weird/bin/gem')
+        @provider.should_receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil)
+        @provider.action_remove
+      end
+    end
+  end
+end
+
diff --git a/spec/unit/provider/package/smartos_spec.rb b/spec/unit/provider/package/smartos_spec.rb
new file mode 100644
index 0000000..e5092f6
--- /dev/null
+++ b/spec/unit/provider/package/smartos_spec.rb
@@ -0,0 +1,102 @@
+#
+# Author:: Trevor O (trevoro at joyent.com)
+# Author:: Yukihiko Sawanobori (sawanoboriyu at higanworks.com)
+# Copyright:: Copyright (c) 2012 Opscode
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
+require 'ostruct'
+
+describe Chef::Provider::Package::SmartOS, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource     = Chef::Resource::Package.new("varnish")
+    @current_resource = Chef::Resource::Package.new("varnish")
+
+
+	  @status = mock("Status", :exitstatus => 0)
+		@provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context)
+		Chef::Resource::Package.stub!(: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
+
+    it "should create a current resource with the name of the new_resource" do
+			@provider.should_receive(:shell_out!).and_return(@shell_out)
+			Chef::Resource::Package.should_receive(:new).and_return(@current_resource)
+			@provider.load_current_resource
+    end
+
+		it "should set the current resource package name" do
+			@provider.should_receive(:shell_out!).and_return(@shell_out)
+			@current_resource.should_receive(:package_name).with(@new_resource.package_name)
+			@provider.load_current_resource
+		end
+
+		it "should set the installed version if it is installed" do
+		  @provider.should_receive(:shell_out!).and_return(@shell_out)
+			@provider.load_current_resource
+			@current_resource.version.should == "2.1.5nb2"
+	  end
+
+		it "should set the installed version to nil if it's not installed" do
+			out = OpenStruct.new(:stdout => nil)
+			@provider.should_receive(:shell_out!).and_return(out)
+			@provider.load_current_resource
+			@current_resource.version.should == nil
+		end
+
+
+	end
+
+  describe "candidate_version" do
+    it "should return the candidate_version variable if already setup" do
+      @provider.candidate_version = "2.1.1"
+      @provider.should_not_receive(:shell_out!)
+      @provider.candidate_version
+    end
+
+    it "should lookup the candidate_version if the variable is not already set" do
+      search = mock()
+      search.should_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 = mock('shell_out!', :stdout => search)
+      @provider.should_receive(:shell_out!).with('/opt/local/bin/pkgin se varnish', :env => nil, :returns => [0,1]).and_return(@shell_out)
+      @provider.candidate_version.should == "2.3.4"
+    end
+  end
+
+	describe "when manipulating a resource" do
+
+		it "run pkgin and install the package" do
+			out = OpenStruct.new(:stdout => nil)
+      @provider.should_receive(:shell_out!).with("/opt/local/sbin/pkg_info -E \"varnish*\"", {:env => nil, :returns=>[0,1]}).and_return(@shell_out)
+      @provider.should_receive(:shell_out!).with("/opt/local/bin/pkgin -y install varnish-2.1.5nb2", {:env=>nil}).and_return(out)
+      @provider.load_current_resource
+      @provider.install_package("varnish", "2.1.5nb2")
+		end
+
+	end
+
+end
diff --git a/spec/unit/provider/package/solaris_spec.rb b/spec/unit/provider/package/solaris_spec.rb
new file mode 100644
index 0000000..dd7cea2
--- /dev/null
+++ b/spec/unit/provider/package/solaris_spec.rb
@@ -0,0 +1,181 @@
+#
+# Author:: Toomas Pelberg (<toomasp at gmx.net>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'spec_helper'
+
+describe Chef::Provider::Package::Solaris do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Package.new("SUNWbash")
+    @new_resource.source("/tmp/bash.pkg")
+
+    @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
+    ::File.stub!(:exists?).and_return(true)
+  end
+
+  describe "assessing the current package status" do
+    before do
+      @pkginfo =<<-PKGINFO
+PKGINST:  SUNWbash
+NAME:  GNU Bourne-Again shell (bash)
+CATEGORY:  system
+ARCH:  sparc
+VERSION:  11.10.0,REV=2005.01.08.05.16
+BASEDIR:  /
+VENDOR:  Sun Microsystems, Inc.
+DESC:  GNU Bourne-Again shell (bash) version 3.0
+PSTAMP:  sfw10-patch20070430084444
+INSTDATE:  Nov 04 2009 01:02
+HOTLINE:  Please contact your local service provider
+PKGINFO
+
+      @status = mock("Status", :exitstatus => 0)
+    end
+
+    it "should create a current resource with the name of new_resource" do
+      @provider.stub!(:popen4).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.name.should == "SUNWbash"
+    end
+
+    it "should set the current reource package name to the new resource package name" do
+      @provider.stub!(:popen4).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.package_name.should == "SUNWbash"
+    end
+
+    it "should raise an exception if a source is supplied but not found" do
+      @provider.stub!(:popen4).and_return(@status)
+      ::File.stub!(:exists?).and_return(false)
+      @provider.define_resource_requirements
+      @provider.load_current_resource
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Package)
+    end
+
+
+    it "should get the source package version from pkginfo if provided" do
+      @stdout = StringIO.new(@pkginfo)
+      @stdin, @stderr = StringIO.new, StringIO.new
+      @provider.should_receive(:popen4).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.should_receive(:popen4).with("pkginfo -l SUNWbash").and_return(@status)
+      @provider.load_current_resource
+
+      @provider.current_resource.package_name.should == "SUNWbash"
+      @new_resource.version.should == "11.10.0,REV=2005.01.08.05.16"
+    end
+
+    it "should return the current version installed if found by pkginfo" do
+      @stdout = StringIO.new(@pkginfo)
+      @stdin, @stderr = StringIO.new, StringIO.new
+      @provider.should_receive(:popen4).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
+      @provider.should_receive(:popen4).with("pkginfo -l SUNWbash").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "11.10.0,REV=2005.01.08.05.16"
+    end
+
+    it "should raise an exception if the source is not set but we are installing" do
+      @new_resource = Chef::Resource::Package.new("SUNWbash")
+      @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
+      @provider.stub!(:popen4).and_return(@status)
+      lambda { @provider.run_action(:install) }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should raise an exception if pkginfo fails to run" do
+      @status = mock("Status", :exitstatus => -1)
+      @provider.stub!(:popen4).and_return(@status)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should return a current resource with a nil version if the package is not found" do
+      @stdout = StringIO.new
+      @provider.should_receive(:popen4).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
+      @provider.should_receive(:popen4).with("pkginfo -l SUNWbash").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+  end
+
+  describe "candidate_version" do
+    it "should return the candidate_version variable if already setup" do
+      @provider.candidate_version = "11.10.0,REV=2005.01.08.05.16"
+      @provider.should_not_receive(:popen4)
+      @provider.candidate_version
+    end
+
+    it "should lookup the candidate_version if the variable is not already set" do
+      @status = mock("Status", :exitstatus => 0)
+      @provider.stub!(:popen4).and_return(@status)
+      @provider.should_receive(:popen4)
+      @provider.candidate_version
+    end
+
+    it "should throw and exception if the exitstatus is not 0" do
+      @status = mock("Status", :exitstatus => 1)
+      @provider.stub!(:popen4).and_return(@status)
+      lambda { @provider.candidate_version }.should raise_error(Chef::Exceptions::Package)
+    end
+
+  end
+
+  describe "install and upgrade" do
+    it "should run pkgadd -n -d with the package source to install" do
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkgadd -n -d /tmp/bash.pkg all"
+      })
+      @provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
+    end
+
+    it "should run pkgadd -n -d when the package is a path to install" do
+      @new_resource = Chef::Resource::Package.new("/tmp/bash.pkg")
+      @provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
+      @new_resource.source.should == "/tmp/bash.pkg"
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkgadd -n -d /tmp/bash.pkg all"
+      })
+      @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
+      @new_resource.stub!(:options).and_return("-a /tmp/myadmin")
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all"
+      })
+      @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
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkgrm -n SUNWbash"
+      })
+      @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
+      @new_resource.stub!(:options).and_return("-a /tmp/myadmin")
+      @provider.should_receive(:run_command_with_systems_locale).with({
+        :command => "pkgrm -n -a /tmp/myadmin SUNWbash"
+      })
+      @provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
+    end
+
+  end
+end
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
new file mode 100644
index 0000000..d2674f5
--- /dev/null
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -0,0 +1,1856 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Provider::Package::Yum do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new('cups')
+    @status = mock("Status", :exitstatus => 0)
+    @yum_cache = mock(
+      'Chef::Provider::Yum::YumCache',
+      :reload_installed => true,
+      :reset => true,
+      :installed_version => "1.2.4-11.18.el5",
+      :candidate_version => "1.2.4-11.18.el5_2.3",
+      :package_available? => true,
+      :version_available? => true,
+      :allow_multi_install => [ "kernel" ],
+      :package_repository => "base",
+      :disable_extra_repo_control => true
+    )
+    Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+    @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+    @pid = mock("PID")
+  end
+
+  describe "when loading the current system state" do
+    it "should create a current resource with the name of the new_resource" do
+      @provider.load_current_resource
+      @provider.current_resource.name.should == "cups"
+    end
+
+    it "should set the current resources package name to the new resources package name" do
+      @provider.load_current_resource
+      @provider.current_resource.package_name.should == "cups"
+    end
+
+    it "should set the installed version to nil on the current resource if no installed package" do
+      @yum_cache.stub!(:installed_version).and_return(nil)
+      @provider.load_current_resource
+      @provider.current_resource.version.should be_nil
+    end
+
+    it "should set the installed version if yum has one" do
+      @provider.load_current_resource
+      @provider.current_resource.version.should == "1.2.4-11.18.el5"
+    end
+
+    it "should set the candidate version if yum info has one" do
+      @provider.load_current_resource
+      @provider.candidate_version.should eql("1.2.4-11.18.el5_2.3")
+    end
+
+    it "should return the current resouce" do
+      @provider.load_current_resource.should eql(@provider.current_resource)
+    end
+
+    describe "when arch in package_name" do
+      it "should set the arch if no existing package_name is found and new_package_name+new_arch is available" do
+        @new_resource = Chef::Resource::YumPackage.new('testing.noarch')
+        @yum_cache = mock(
+          'Chef::Provider::Yum::YumCache'
+        )
+        @yum_cache.stub!(:installed_version) do |package_name, arch|
+          # nothing installed for package_name/new_package_name
+          nil
+        end
+        @yum_cache.stub!(:candidate_version) do |package_name, arch|
+          if package_name == "testing.noarch" || package_name == "testing.more.noarch"
+            nil
+          # candidate for new_package_name
+          elsif package_name == "testing" || package_name == "testing.more"
+            "1.1"
+          end
+        end
+        @yum_cache.stub!(:package_available?).and_return(true)
+        @yum_cache.stub!(:disable_extra_repo_control).and_return(true)
+        Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        @provider.load_current_resource
+        @provider.new_resource.package_name.should == "testing"
+        @provider.new_resource.arch.should == "noarch"
+        @provider.arch.should == "noarch"
+
+        @new_resource = Chef::Resource::YumPackage.new('testing.more.noarch')
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        @provider.load_current_resource
+        @provider.new_resource.package_name.should == "testing.more"
+        @provider.new_resource.arch.should == "noarch"
+        @provider.arch.should == "noarch"
+      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 = mock(
+          'Chef::Provider::Yum::YumCache'
+        )
+        @yum_cache.stub!(:installed_version) do |package_name, arch|
+          # installed for package_name
+          if package_name == "testing.beta3" || package_name == "testing.beta3.more"
+            "1.1"
+          elsif package_name == "testing" || package_name == "testing.beta3"
+            nil
+          end
+        end
+        @yum_cache.stub!(:candidate_version) do |package_name, arch|
+          # no candidate for package_name/new_package_name
+          nil
+        end
+        @yum_cache.stub!(:package_available?).and_return(true)
+        @yum_cache.stub!(:disable_extra_repo_control).and_return(true)
+        Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        # annoying side effect of the fun stub'ing above
+        @provider.load_current_resource
+        @provider.new_resource.package_name.should == "testing.beta3"
+        @provider.new_resource.arch.should == nil
+        @provider.arch.should == nil
+
+        @new_resource = Chef::Resource::YumPackage.new('testing.beta3.more')
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        @provider.load_current_resource
+        @provider.new_resource.package_name.should == "testing.beta3.more"
+        @provider.new_resource.arch.should == nil
+        @provider.arch.should == nil
+      end
+
+      it "should not set the arch when no existing package_name or new_package_name+new_arch is found" do
+        @new_resource = Chef::Resource::YumPackage.new('testing.beta3')
+        @yum_cache = mock(
+          'Chef::Provider::Yum::YumCache'
+        )
+        @yum_cache.stub!(:installed_version) do |package_name, arch|
+          # nothing installed for package_name/new_package_name
+          nil
+        end
+        @yum_cache.stub!(:candidate_version) do |package_name, arch|
+          # no candidate for package_name/new_package_name
+          nil
+        end
+        @yum_cache.stub!(:package_available?).and_return(true)
+        @yum_cache.stub!(:disable_extra_repo_control).and_return(true)
+        Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        @provider.load_current_resource
+        @provider.new_resource.package_name.should == "testing.beta3"
+        @provider.new_resource.arch.should == nil
+        @provider.arch.should == nil
+
+        @new_resource = Chef::Resource::YumPackage.new('testing.beta3.more')
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        @provider.load_current_resource
+        @provider.new_resource.package_name.should == "testing.beta3.more"
+        @provider.new_resource.arch.should == nil
+        @provider.arch.should == nil
+      end
+
+      it "should ensure it doesn't clobber an existing arch if passed" do
+        @new_resource = Chef::Resource::YumPackage.new('testing.i386')
+        @new_resource.arch("x86_64")
+        @yum_cache = mock(
+          'Chef::Provider::Yum::YumCache'
+        )
+         @yum_cache.stub!(:installed_version) do |package_name, arch|
+           # nothing installed for package_name/new_package_name
+         nil
+        end
+        @yum_cache.stub!(:candidate_version) do |package_name, arch|
+          if package_name == "testing.noarch"
+            nil
+          # candidate for new_package_name
+          elsif package_name == "testing"
+            "1.1"
+          end
+        end.and_return("something")
+        @yum_cache.stub!(:package_available?).and_return(true)
+        @yum_cache.stub!(:disable_extra_repo_control).and_return(true)
+        Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+        @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+        @provider.load_current_resource
+        @provider.new_resource.package_name.should == "testing.i386"
+        @provider.new_resource.arch.should == "x86_64"
+      end
+    end
+
+    it "should flush the cache if :before is true" do
+      @new_resource.stub!(:flush_cache).and_return({:after => false, :before => true})
+      @yum_cache.should_receive(:reload).once
+      @provider.load_current_resource
+    end
+
+    it "should flush the cache if :before is false" do
+      @new_resource.stub!(:flush_cache).and_return({:after => false, :before => false})
+      @yum_cache.should_not_receive(:reload)
+      @provider.load_current_resource
+    end
+
+    it "should detect --enablerepo or --disablerepo when passed among options, collect them preserving order and notify the yum cache" do
+      @new_resource.stub!(:options).and_return("--stuff --enablerepo=foo --otherthings --disablerepo=a,b,c  --enablerepo=bar")
+      @yum_cache.should_receive(:enable_extra_repo_control).with("--enablerepo=foo --disablerepo=a,b,c --enablerepo=bar")
+      @provider.load_current_resource
+    end
+
+    it "should let the yum cache know extra repos are disabled if --enablerepo or --disablerepo aren't among options" do
+      @new_resource.stub!(:options).and_return("--stuff --otherthings")
+      @yum_cache.should_receive(:disable_extra_repo_control)
+      @provider.load_current_resource
+    end
+
+    it "should let the yum cache know extra repos are disabled if options aren't set" do
+      @new_resource.stub!(:options).and_return(nil)
+      @yum_cache.should_receive(:disable_extra_repo_control)
+      @provider.load_current_resource
+    end
+
+    it "should search provides if package name can't be found then set package_name to match" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.18.el5",
+        :package_available? => false,
+        :version_available? => true,
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "1.2.4-11.18.el5", "x86_64", [])
+      @yum_cache.should_receive(:packages_from_require).and_return([pkg])
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      @new_resource.package_name.should == "test-package"
+    end
+
+    it "should search provides if package name can't be found, warn about multiple matches, but use the first one" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.18.el5",
+        :package_available? => false,
+        :version_available? => true,
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      pkg_x = Chef::Provider::Package::Yum::RPMPackage.new("test-package-x", "1.2.4-11.18.el5", "x86_64", [])
+      pkg_y = Chef::Provider::Package::Yum::RPMPackage.new("test-package-y", "1.2.6-11.3.el5", "i386", [])
+      @yum_cache.should_receive(:packages_from_require).and_return([pkg_x, pkg_y])
+      Chef::Log.should_receive(:warn).exactly(1).times.with(%r{matched multiple Provides})
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      @new_resource.package_name.should == "test-package-x"
+    end
+
+    it "should search provides if no package is available - if no match in installed provides then load the complete set" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.18.el5",
+        :package_available? => false,
+        :version_available? => true,
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @yum_cache.should_receive(:packages_from_require).twice.and_return([])
+      @yum_cache.should_receive(:reload_provides)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+    end
+
+    it "should search provides if no package is available and not load the complete set if action is :remove or :purge" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.18.el5",
+        :package_available? => false,
+        :version_available? => true,
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @yum_cache.should_receive(:packages_from_require).once.and_return([])
+      @yum_cache.should_not_receive(:reload_provides)
+      @new_resource.action(:remove)
+      @provider.load_current_resource
+      @yum_cache.should_receive(:packages_from_require).once.and_return([])
+      @yum_cache.should_not_receive(:reload_provides)
+      @new_resource.action(:purge)
+      @provider.load_current_resource
+    end
+
+    it "should search provides if no package is available - if no match in provides leave the name intact" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_provides => true,
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.18.el5",
+        :package_available? => false,
+        :version_available? => true,
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @yum_cache.should_receive(:packages_from_require).twice.and_return([])
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      @new_resource.package_name.should == "cups"
+    end
+  end
+
+  describe "when installing a package" do
+    it "should run yum install with the package name and version" do
+      @provider.load_current_resource
+      Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y install emacs-1.0"
+      )
+      @provider.install_package("emacs", "1.0")
+    end
+
+    it "should run yum localinstall if given a path to an rpm" do
+      @new_resource.stub!(:source).and_return("/tmp/emacs-21.4-20.el5.i386.rpm")
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+      )
+      @provider.install_package("emacs", "21.4-20.el5")
+    end
+
+    it "should run yum localinstall if given a path to an rpm as the package" do
+      @new_resource = Chef::Resource::Package.new("/tmp/emacs-21.4-20.el5.i386.rpm")
+      ::File.stub!(:exists?).and_return(true)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @new_resource.source.should == "/tmp/emacs-21.4-20.el5.i386.rpm"
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+      )
+      @provider.install_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
+    end
+
+    it "should run yum install with the package name, version and arch" do
+      @provider.load_current_resource
+      @new_resource.stub!(:arch).and_return("i386")
+      Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y install emacs-21.4-20.el5.i386"
+      )
+      @provider.install_package("emacs", "21.4-20.el5")
+    end
+
+    it "installs the package with the options given in the resource" do
+      @provider.load_current_resource
+      @provider.candidate_version = '11'
+      @new_resource.stub!(:options).and_return("--disablerepo epmd")
+      Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y --disablerepo epmd install cups-11"
+      )
+      @provider.install_package(@new_resource.name, @provider.candidate_version)
+    end
+
+    it "should raise an exception if the package is not available" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_from_cache => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.18.el5_2.3",
+        :package_available? => true,
+        :version_available? => nil,
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      lambda { @provider.install_package("lolcats", "0.99") }.should raise_error(Chef::Exceptions::Package, %r{Version .* not found})
+    end
+
+    it "should raise an exception if candidate version is older than the installed version and allow_downgrade is false" do
+      @new_resource.stub!(:allow_downgrade).and_return(false)
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.15.el5",
+        :package_available? => true,
+        :version_available? => true,
+        :allow_multi_install => [ "kernel" ],
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      lambda { @provider.install_package("cups", "1.2.4-11.15.el5") }.should raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
+    end
+
+    it "should not raise an exception if candidate version is older than the installed version and the package is list in yum's installonlypkg option" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.15.el5",
+        :package_available? => true,
+        :version_available? => true,
+        :allow_multi_install => [ "cups" ],
+        :package_repository => "base",
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+      )
+      @provider.install_package("cups", "1.2.4-11.15.el5")
+    end
+
+    it "should run yum downgrade if candidate version is older than the installed version and allow_downgrade is true" do
+      @new_resource.stub!(:allow_downgrade).and_return(true)
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.15.el5",
+        :package_available? => true,
+        :version_available? => true,
+        :allow_multi_install => [],
+        :package_repository => "base",
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y downgrade cups-1.2.4-11.15.el5"
+      )
+      @provider.install_package("cups", "1.2.4-11.15.el5")
+    end
+
+    it "should run yum install then flush the cache if :after is true" do
+      @new_resource.stub!(:flush_cache).and_return({:after => true, :before => false})
+      @provider.load_current_resource
+      Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y install emacs-1.0"
+      )
+      @yum_cache.should_receive(:reload).once
+      @provider.install_package("emacs", "1.0")
+    end
+
+    it "should run yum install then not flush the cache if :after is false" do
+      @new_resource.stub!(:flush_cache).and_return({:after => false, :before => false})
+      @provider.load_current_resource
+      Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y install emacs-1.0"
+      )
+      @yum_cache.should_not_receive(:reload)
+      @provider.install_package("emacs", "1.0")
+    end
+  end
+
+  describe "when upgrading a package" do
+    it "should run yum install if the package is installed and a version is given" do
+      @provider.load_current_resource
+      @provider.candidate_version = '11'
+      Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y install cups-11"
+      )
+      @provider.upgrade_package(@new_resource.name, @provider.candidate_version)
+    end
+
+    it "should run yum install if the package is not installed" do
+      @provider.load_current_resource
+      @current_resource = Chef::Resource::Package.new('cups')
+      @provider.candidate_version = '11'
+      Chef::Provider::Package::Yum::RPMUtils.stub!(:rpmvercmp).and_return(-1)
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y install cups-11"
+      )
+      @provider.upgrade_package(@new_resource.name, @provider.candidate_version)
+    end
+
+    it "should raise an exception if candidate version is older than the installed version" do
+      @yum_cache = mock(
+        'Chef::Provider::Yum::YumCache',
+        :reload_installed => true,
+        :reset => true,
+        :installed_version => "1.2.4-11.18.el5",
+        :candidate_version => "1.2.4-11.15.el5",
+        :package_available? => true,
+        :version_available? => true,
+        :allow_multi_install => [ "kernel" ],
+        :disable_extra_repo_control => true
+      )
+      Chef::Provider::Package::Yum::YumCache.stub!(:instance).and_return(@yum_cache)
+      @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+      @provider.load_current_resource
+      lambda { @provider.upgrade_package("cups", "1.2.4-11.15.el5") }.should raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
+    end
+
+    # Test our little workaround, some crossover into Chef::Provider::Package territory
+    it "should call action_upgrade in the parent if the current resource version is nil" do
+      @yum_cache.stub!(:installed_version).and_return(nil)
+      @provider.load_current_resource
+      @current_resource = Chef::Resource::Package.new('cups')
+      @provider.candidate_version = '11'
+      @provider.should_receive(:upgrade_package).with(
+        "cups",
+        "11"
+      )
+      @provider.action_upgrade
+    end
+
+    it "should call action_upgrade in the parent if the candidate version is nil" do
+      @provider.load_current_resource
+      @current_resource = Chef::Resource::Package.new('cups')
+      @provider.candidate_version = nil
+      @provider.should_not_receive(:upgrade_package)
+      @provider.action_upgrade
+    end
+
+    it "should call action_upgrade in the parent if the candidate is newer" do
+      @provider.load_current_resource
+      @current_resource = Chef::Resource::Package.new('cups')
+      @provider.candidate_version = '11'
+      @provider.should_receive(:upgrade_package).with(
+        "cups",
+        "11"
+      )
+      @provider.action_upgrade
+    end
+
+    it "should not call action_upgrade in the parent if the candidate is older" do
+      @yum_cache.stub!(:installed_version).and_return("12")
+      @provider.load_current_resource
+      @current_resource = Chef::Resource::Package.new('cups')
+      @provider.candidate_version = '11'
+      @provider.should_not_receive(:upgrade_package)
+      @provider.action_upgrade
+    end
+  end
+
+  describe "when removing a package" do
+    it "should run yum remove with the package name" do
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y remove emacs-1.0"
+      )
+      @provider.remove_package("emacs", "1.0")
+    end
+
+    it "should run yum remove with the package name and arch" do
+      @new_resource.stub!(:arch).and_return("x86_64")
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y remove emacs-1.0.x86_64"
+      )
+      @provider.remove_package("emacs", "1.0")
+    end
+  end
+
+  describe "when purging a package" do
+    it "should run yum remove with the package name" do
+      @provider.should_receive(:yum_command).with(
+        "yum -d0 -e0 -y remove emacs-1.0"
+      )
+      @provider.purge_package("emacs", "1.0")
+    end
+  end
+
+  describe "when running yum" do
+    it "should run yum once if it exits with a return code of 0" do
+      @status = mock("Status", :exitstatus => 0)
+      @provider.stub!(:output_of_command).and_return([@status, "", ""])
+      @provider.should_receive(:output_of_command).once.with(
+        "yum -d0 -e0 -y install emacs-1.0",
+        {:timeout => Chef::Config[:yum_timeout]}
+      )
+      @provider.yum_command("yum -d0 -e0 -y install emacs-1.0")
+    end
+
+    it "should run yum once if it exits with a return code > 0 and no scriptlet failures" do
+      @status = mock("Status", :exitstatus => 2)
+      @provider.stub!(:output_of_command).and_return([@status, "failure failure", "problem problem"])
+      @provider.should_receive(:output_of_command).once.with(
+        "yum -d0 -e0 -y install emacs-1.0",
+        {:timeout => Chef::Config[:yum_timeout]}
+      )
+      lambda { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.should raise_error(Chef::Exceptions::Exec)
+    end
+
+    it "should run yum once if it exits with a return code of 1 and %pre scriptlet failures" do
+      @status = mock("Status", :exitstatus => 1)
+      @provider.stub!(:output_of_command).and_return([@status, "error: %pre(demo-1-1.el5.centos.x86_64) scriptlet failed, exit status 2", ""])
+      @provider.should_receive(:output_of_command).once.with(
+        "yum -d0 -e0 -y install emacs-1.0",
+        {:timeout => Chef::Config[:yum_timeout]}
+      )
+      # will still raise an exception, can't stub out the subsequent call
+      lambda { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.should raise_error(Chef::Exceptions::Exec)
+    end
+
+    it "should run yum twice if it exits with a return code of 1 and %post scriptlet failures" do
+      @status = mock("Status", :exitstatus => 1)
+      @provider.stub!(:output_of_command).and_return([@status, "error: %post(demo-1-1.el5.centos.x86_64) scriptlet failed, exit status 2", ""])
+      @provider.should_receive(:output_of_command).twice.with(
+        "yum -d0 -e0 -y install emacs-1.0",
+        {:timeout => Chef::Config[:yum_timeout]}
+      )
+      # will still raise an exception, can't stub out the subsequent call
+      lambda { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.should raise_error(Chef::Exceptions::Exec)
+    end
+  end
+end
+
+describe Chef::Provider::Package::Yum::RPMUtils do
+  describe "version_parse" do
+    before do
+      @rpmutils = Chef::Provider::Package::Yum::RPMUtils
+    end
+
+    it "parses known good epoch strings" do
+      [
+        [ "0:3.3", [ 0, "3.3", nil ] ],
+        [ "9:1.7.3", [ 9, "1.7.3", nil ] ],
+        [ "15:20020927", [ 15, "20020927", nil ] ]
+      ].each do |x, y|
+        @rpmutils.version_parse(x).should == y
+      end
+    end
+
+    it "parses strange epoch strings" do
+      [
+        [ ":3.3", [ 0, "3.3", nil ] ],
+        [ "-1:1.7.3", [ nil, nil, "1:1.7.3" ] ],
+        [ "-:20020927", [ nil, nil, ":20020927" ] ]
+      ].each do |x, y|
+        @rpmutils.version_parse(x).should == y
+      end
+    end
+
+    it "parses known good version strings" do
+      [
+        [ "3.3", [ nil, "3.3", nil ] ],
+        [ "1.7.3", [ nil, "1.7.3", nil ] ],
+        [ "20020927", [ nil, "20020927", nil ] ]
+      ].each do |x, y|
+        @rpmutils.version_parse(x).should == y
+      end
+    end
+
+    it "parses strange version strings" do
+      [
+        [ "3..3", [ nil, "3..3", nil ] ],
+        [ "0001.7.3", [ nil, "0001.7.3", nil ] ],
+        [ "20020927,3", [ nil, "20020927,3", nil ] ]
+      ].each do |x, y|
+        @rpmutils.version_parse(x).should == y
+      end
+    end
+
+    it "parses known good version release strings" do
+      [
+        [ "3.3-0.pre3.1.60.el5_5.1", [ nil, "3.3", "0.pre3.1.60.el5_5.1" ] ],
+        [ "1.7.3-1jpp.2.el5", [ nil, "1.7.3", "1jpp.2.el5" ] ],
+        [ "20020927-46.el5", [ nil, "20020927", "46.el5" ] ]
+      ].each do |x, y|
+        @rpmutils.version_parse(x).should == y
+      end
+    end
+
+    it "parses strange version release strings" do
+      [
+        [ "3.3-", [ nil, "3.3", nil ] ],
+        [ "-1jpp.2.el5", [ nil, nil, "1jpp.2.el5" ] ],
+        [ "-0020020927-46.el5", [ nil, "-0020020927", "46.el5" ] ]
+      ].each do |x, y|
+        @rpmutils.version_parse(x).should == y
+      end
+    end
+  end
+
+  describe "rpmvercmp" do
+    before do
+      @rpmutils = Chef::Provider::Package::Yum::RPMUtils
+    end
+
+    it "should validate version compare logic for standard examples" do
+      [
+        # numeric
+        [ "0.0.2", "0.0.1", 1 ],
+        [ "0.2.0", "0.1.0", 1 ],
+        [ "2.0.0", "1.0.0", 1 ],
+        [ "0.0.1", "0.0.1", 0 ],
+        [ "0.0.1", "0.0.2", -1 ],
+        [ "0.1.0", "0.2.0", -1 ],
+        [ "1.0.0", "2.0.0", -1 ],
+        # alpha
+        [ "bb", "aa", 1 ],
+        [ "ab", "aa", 1 ],
+        [ "aa", "aa", 0 ],
+        [ "aa", "bb", -1 ],
+        [ "aa", "ab", -1 ],
+        [ "BB", "AA", 1 ],
+        [ "AA", "AA", 0 ],
+        [ "AA", "BB", -1 ],
+        [ "aa", "AA", 1 ],
+        [ "AA", "aa", -1 ],
+        # alphanumeric
+        [ "0.0.1b", "0.0.1a", 1 ],
+        [ "0.1b.0", "0.1a.0", 1 ],
+        [ "1b.0.0", "1a.0.0", 1 ],
+        [ "0.0.1a", "0.0.1a", 0 ],
+        [ "0.0.1a", "0.0.1b", -1 ],
+        [ "0.1a.0", "0.1b.0", -1 ],
+        [ "1a.0.0", "1b.0.0", -1 ],
+        # alphanumeric against alphanumeric
+        [ "0.0.1", "0.0.a", 1 ],
+        [ "0.1.0", "0.a.0", 1 ],
+        [ "1.0.0", "a.0.0", 1 ],
+        [ "0.0.a", "0.0.a", 0 ],
+        [ "0.0.a", "0.0.1", -1 ],
+        [ "0.a.0", "0.1.0", -1 ],
+        [ "a.0.0", "1.0.0", -1 ],
+        # alphanumeric against numeric
+        [ "0.0.2", "0.0.1a", 1 ],
+        [ "0.0.2a", "0.0.1", 1 ],
+        [ "0.0.1", "0.0.2a", -1 ],
+        [ "0.0.1a", "0.0.2", -1 ],
+        # length
+        [ "0.0.1aa", "0.0.1a", 1 ],
+        [ "0.0.1aa", "0.0.1aa", 0 ],
+        [ "0.0.1a", "0.0.1aa", -1 ],
+     ].each do |x, y, result|
+        @rpmutils.rpmvercmp(x,y).should == result
+      end
+    end
+
+    it "should validate version compare logic for strange examples" do
+      [
+        [ "2,0,0", "1.0.0", 1 ],
+        [ "0.0.1", "0,0.1", 0 ],
+        [ "1.0.0", "2,0,0", -1 ],
+        [ "002.0.0", "001.0.0", 1 ],
+        [ "001..0.1", "001..0.0", 1 ],
+        [ "-001..1", "-001..0", 1 ],
+        [ "1.0.1", nil, 1 ],
+        [ nil, nil, 0 ],
+        [ nil, "1.0.1", -1 ],
+        [ "1.0.1", "", 1 ],
+        [ "", "", 0 ],
+        [ "", "1.0.1", -1 ]
+     ].each do |x, y, result|
+        @rpmutils.rpmvercmp(x,y).should == result
+      end
+    end
+
+    it "tests isalnum good input" do
+      [ 'a', 'z', 'A', 'Z', '0', '9' ].each do |t|
+        @rpmutils.isalnum(t).should == true
+      end
+    end
+
+    it "tests isalnum bad input" do
+      [ '-', '.', '!', '^', ':', '_' ].each do |t|
+        @rpmutils.isalnum(t).should == false
+      end
+    end
+
+    it "tests isalpha good input" do
+      [ 'a', 'z', 'A', 'Z', ].each do |t|
+        @rpmutils.isalpha(t).should == true
+      end
+    end
+
+    it "tests isalpha bad input" do
+      [ '0', '9', '-', '.', '!', '^', ':', '_' ].each do |t|
+        @rpmutils.isalpha(t).should == false
+      end
+    end
+
+    it "tests isdigit good input" do
+      [ '0', '9', ].each do |t|
+        @rpmutils.isdigit(t).should == true
+      end
+    end
+
+    it "tests isdigit bad input" do
+      [ 'A', 'z', '-', '.', '!', '^', ':', '_' ].each do |t|
+        @rpmutils.isdigit(t).should == false
+      end
+    end
+  end
+
+end
+
+describe Chef::Provider::Package::Yum::RPMVersion do
+  describe "new - with parsing" do
+    before do
+      @rpmv = Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5")
+    end
+
+    it "should expose evr (name-version-release) available" do
+      @rpmv.e.should == 1
+      @rpmv.v.should == "1.6.5"
+      @rpmv.r.should == "9.36.el5"
+
+      @rpmv.evr.should == "1:1.6.5-9.36.el5"
+    end
+
+    it "should output a version-release string" do
+      @rpmv.to_s.should == "1.6.5-9.36.el5"
+    end
+  end
+
+  describe "new - no parsing" do
+    before do
+      @rpmv = Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5")
+    end
+
+    it "should expose evr (name-version-release) available" do
+      @rpmv.e.should == 1
+      @rpmv.v.should == "1.6.5"
+      @rpmv.r.should == "9.36.el5"
+
+      @rpmv.evr.should == "1:1.6.5-9.36.el5"
+    end
+
+    it "should output a version-release string" do
+      @rpmv.to_s.should == "1.6.5-9.36.el5"
+    end
+  end
+
+  it "should raise an error unless passed 1 or 3 args" do
+    lambda {
+      Chef::Provider::Package::Yum::RPMVersion.new()
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5")
+    }.should_not raise_error
+    lambda {
+      Chef::Provider::Package::Yum::RPMVersion.new("1:1.6.5-9.36.el5", "extra")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5")
+    }.should_not raise_error
+    lambda {
+      Chef::Provider::Package::Yum::RPMVersion.new("1", "1.6.5", "9.36.el5", "extra")
+    }.should raise_error(ArgumentError)
+  end
+
+  # thanks version_class_spec.rb!
+  describe "compare" do
+    it "should sort based on complete epoch-version-release data" do
+      [
+        # smaller, larger
+        [ "0:1.6.5-9.36.el5",
+          "1:1.6.5-9.36.el5" ],
+        [ "0:2.3-15.el5",
+          "0:3.3-15.el5" ],
+        [ "0:alpha9.8-27.2",
+          "0:beta9.8-27.2" ],
+        [ "0:0.09-14jpp.3",
+          "0:0.09-15jpp.3" ],
+        [ "0:0.9.0-0.6.20110211.el5",
+          "0:0.9.0-0.6.20120211.el5" ],
+        [ "0:1.9.1-4.el5",
+          "0:1.9.1-5.el5" ],
+        [ "0:1.4.10-7.20090624svn.el5",
+          "0:1.4.10-7.20090625svn.el5" ],
+        [ "0:2.3.4-2.el5",
+          "0:2.3.4-2.el6" ]
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+        lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+        sm.should be < lg
+        lg.should be > sm
+        sm.should_not == lg
+      end
+    end
+
+    it "should sort based on partial epoch-version-release data" do
+      [
+        # smaller, larger
+        [ ":1.6.5-9.36.el5",
+          "1:1.6.5-9.36.el5" ],
+        [ "2.3-15.el5",
+          "3.3-15.el5" ],
+        [ "alpha9.8",
+          "beta9.8" ],
+        [ "14jpp",
+          "15jpp" ],
+        [ "0.9.0-0.6",
+          "0.9.0-0.7" ],
+        [ "0:1.9",
+          "3:1.9" ],
+        [ "2.3-2.el5",
+          "2.3-2.el6" ]
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+        lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+        sm.should be < lg
+        lg.should be > sm
+        sm.should_not == lg
+      end
+    end
+
+    it "should verify equality of complete epoch-version-release data" do
+      [
+        [ "0:1.6.5-9.36.el5",
+          "0:1.6.5-9.36.el5" ],
+        [ "0:2.3-15.el5",
+          "0:2.3-15.el5" ],
+        [ "0:alpha9.8-27.2",
+          "0:alpha9.8-27.2" ]
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+        lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+        sm.should be == lg
+      end
+    end
+
+    it "should verify equality of partial epoch-version-release data" do
+      [
+        [ ":1.6.5-9.36.el5",
+          "0:1.6.5-9.36.el5" ],
+        [ "2.3-15.el5",
+          "2.3-15.el5" ],
+        [ "alpha9.8-3",
+          "alpha9.8-3" ]
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+        lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+        sm.should be == lg
+      end
+    end
+  end
+
+  describe "partial compare" do
+    it "should compare based on partial epoch-version-release data" do
+      [
+        # smaller, larger
+        [ "0:1.1.1-1",
+          "1:" ],
+        [ "0:1.1.1-1",
+          "0:1.1.2" ],
+        [ "0:1.1.1-1",
+          "0:1.1.2-1" ],
+        [ "0:",
+          "1:1.1.1-1" ],
+        [ "0:1.1.1",
+          "0:1.1.2-1" ],
+        [ "0:1.1.1-1",
+          "0:1.1.2-1" ],
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+        lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+        sm.partial_compare(lg).should be == -1
+        lg.partial_compare(sm).should be == 1
+        sm.partial_compare(lg).should_not be == 0
+      end
+    end
+
+    it "should verify equality based on partial epoch-version-release data" do
+      [
+        [ "0:",
+          "0:1.1.1-1" ],
+        [ "0:1.1.1",
+          "0:1.1.1-1" ],
+        [ "0:1.1.1-1",
+          "0:1.1.1-1" ],
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMVersion.new(smaller)
+        lg = Chef::Provider::Package::Yum::RPMVersion.new(larger)
+        sm.partial_compare(lg).should be == 0
+      end
+    end
+  end
+
+end
+
+describe Chef::Provider::Package::Yum::RPMPackage do
+  describe "new - with parsing" do
+    before do
+      @rpm = Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64", [])
+    end
+
+    it "should expose nevra (name-epoch-version-release-arch) available" do
+      @rpm.name.should == "testing"
+      @rpm.version.e.should == 1
+      @rpm.version.v.should == "1.6.5"
+      @rpm.version.r.should == "9.36.el5"
+      @rpm.arch.should == "x86_64"
+
+      @rpm.nevra.should == "testing-1:1.6.5-9.36.el5.x86_64"
+      @rpm.to_s.should == @rpm.nevra
+    end
+
+    it "should always have at least one provide, itself" do
+      @rpm.provides.size.should == 1
+      @rpm.provides[0].name == "testing"
+      @rpm.provides[0].version.evr == "1:1.6.5-9.36.el5"
+      @rpm.provides[0].flag == :==
+    end
+  end
+
+  describe "new - no parsing" do
+    before do
+      @rpm = Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", [])
+    end
+
+    it "should expose nevra (name-epoch-version-release-arch) available" do
+      @rpm.name.should == "testing"
+      @rpm.version.e.should == 1
+      @rpm.version.v.should == "1.6.5"
+      @rpm.version.r.should == "9.36.el5"
+      @rpm.arch.should == "x86_64"
+
+      @rpm.nevra.should == "testing-1:1.6.5-9.36.el5.x86_64"
+      @rpm.to_s.should == @rpm.nevra
+    end
+
+    it "should always have at least one provide, itself" do
+      @rpm.provides.size.should == 1
+      @rpm.provides[0].name == "testing"
+      @rpm.provides[0].version.evr == "1:1.6.5-9.36.el5"
+      @rpm.provides[0].flag == :==
+    end
+  end
+
+  it "should raise an error unless passed 4 or 6 args" do
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new()
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new("testing")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new("testing", "1:1.6.5-9.36.el5", "x86_64", [])
+    }.should_not raise_error
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", [])
+    }.should_not raise_error
+    lambda {
+      Chef::Provider::Package::Yum::RPMPackage.new("testing", "1", "1.6.5", "9.36.el5", "x86_64", [], "extra")
+    }.should raise_error(ArgumentError)
+  end
+
+  describe "<=>" do
+    it "should sort alphabetically based on package name" do
+      [
+        [ "a-test",
+          "b-test" ],
+        [ "B-test",
+          "a-test" ],
+        [ "A-test",
+          "B-test" ],
+        [ "Aa-test",
+          "aA-test" ],
+        [ "1test",
+          "2test" ],
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMPackage.new(smaller, "0:0.0.1-1", "x86_64", [])
+        lg = Chef::Provider::Package::Yum::RPMPackage.new(larger, "0:0.0.1-1", "x86_64", [])
+        sm.should be < lg
+        lg.should be > sm
+        sm.should_not == lg
+      end
+    end
+
+    it "should sort alphabetically based on package arch" do
+      [
+        [ "i386",
+          "x86_64" ],
+        [ "i386",
+          "noarch" ],
+        [ "noarch",
+          "x86_64" ],
+      ].each do |smaller, larger|
+        sm = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "0:0.0.1-1", smaller, [])
+        lg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "0:0.0.1-1", larger, [])
+        sm.should be < lg
+        lg.should be > sm
+        sm.should_not == lg
+      end
+    end
+  end
+
+end
+
+describe Chef::Provider::Package::Yum::RPMDbPackage do
+  before(:each) do
+    # name, version, arch, installed, available, repoid
+    @rpm_x = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], false, true, "base")
+    @rpm_y = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], true, true, "extras")
+    @rpm_z = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "noarch", [], true, false, "other")
+  end
+
+  describe "initialize" do
+    it "should return a Chef::Provider::Package::Yum::RPMDbPackage object" do
+      @rpm_x.should be_kind_of(Chef::Provider::Package::Yum::RPMDbPackage)
+    end
+  end
+
+  describe "available" do
+    it "should return true" do
+      @rpm_x.available.should be == true
+      @rpm_y.available.should be == true
+      @rpm_z.available.should be == false
+    end
+  end
+
+  describe "installed" do
+    it "should return true" do
+      @rpm_x.installed.should be == false
+      @rpm_y.installed.should be == true
+      @rpm_z.installed.should be == true
+    end
+  end
+
+  describe "repoid" do
+    it "should return the source repository repoid" do
+      @rpm_x.repoid.should be == "base"
+      @rpm_y.repoid.should be == "extras"
+      @rpm_z.repoid.should be == "other"
+    end
+  end
+end
+
+describe Chef::Provider::Package::Yum::RPMDependency do
+  describe "new - with parsing" do
+    before do
+      @rpmdep = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+    end
+
+    it "should expose name, version, flag available" do
+      @rpmdep.name.should == "testing"
+      @rpmdep.version.e.should == 1
+      @rpmdep.version.v.should == "1.6.5"
+      @rpmdep.version.r.should == "9.36.el5"
+      @rpmdep.flag.should == :==
+    end
+  end
+
+  describe "new - no parsing" do
+    before do
+      @rpmdep = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==)
+    end
+
+    it "should expose name, version, flag available" do
+      @rpmdep.name.should == "testing"
+      @rpmdep.version.e.should == 1
+      @rpmdep.version.v.should == "1.6.5"
+      @rpmdep.version.r.should == "9.36.el5"
+      @rpmdep.flag.should == :==
+    end
+  end
+
+  it "should raise an error unless passed 3 or 5 args" do
+    lambda {
+      Chef::Provider::Package::Yum::RPMDependency.new()
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMDependency.new("testing")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+    }.should_not raise_error
+    lambda {
+      Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==, "extra")
+    }.should raise_error(ArgumentError)
+    lambda {
+      Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==)
+    }.should_not raise_error
+    lambda {
+      Chef::Provider::Package::Yum::RPMDependency.new("testing", "1", "1.6.5", "9.36.el5", :==, "extra")
+    }.should raise_error(ArgumentError)
+  end
+
+  describe "parse" do
+    it "should parse a name, flag, version string into a valid RPMDependency object" do
+      @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing >= 1:1.6.5-9.36.el5")
+
+      @rpmdep.name.should == "testing"
+      @rpmdep.version.e.should == 1
+      @rpmdep.version.v.should == "1.6.5"
+      @rpmdep.version.r.should == "9.36.el5"
+      @rpmdep.flag.should == :>=
+    end
+
+    it "should parse a name into a valid RPMDependency object" do
+      @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing")
+
+      @rpmdep.name.should == "testing"
+      @rpmdep.version.e.should == nil
+      @rpmdep.version.v.should == nil
+      @rpmdep.version.r.should == nil
+      @rpmdep.flag.should == :==
+    end
+
+    it "should parse an invalid string into the name of a RPMDependency object" do
+      @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing blah >")
+
+      @rpmdep.name.should == "testing blah >"
+      @rpmdep.version.e.should == nil
+      @rpmdep.version.v.should == nil
+      @rpmdep.version.r.should == nil
+      @rpmdep.flag.should == :==
+    end
+
+    it "should parse various valid flags" do
+      [
+        [ ">", :> ],
+        [ ">=", :>= ],
+        [ "=", :== ],
+        [ "==", :== ],
+        [ "<=", :<= ],
+        [ "<", :< ]
+      ].each do |before, after|
+        @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing #{before} 1:1.1-1")
+        @rpmdep.flag.should == after
+      end
+    end
+
+    it "should parse various invalid flags and treat them as names" do
+      [
+        [ "<>", :== ],
+        [ "!=", :== ],
+        [ ">>", :== ],
+        [ "<<", :== ],
+        [ "!", :== ],
+        [ "~", :== ]
+      ].each do |before, after|
+        @rpmdep = Chef::Provider::Package::Yum::RPMDependency.parse("testing #{before} 1:1.1-1")
+        @rpmdep.name.should == "testing #{before} 1:1.1-1"
+        @rpmdep.flag.should == after
+      end
+    end
+  end
+
+  describe "satisfy?" do
+    it "should raise an error unless a RPMDependency is passed" do
+      @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+      @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :>=)
+      lambda {
+        @rpmprovide.satisfy?("hi")
+      }.should raise_error(ArgumentError)
+      lambda {
+        @rpmprovide.satisfy?(@rpmrequire)
+      }.should_not raise_error
+    end
+
+    it "should validate dependency satisfaction logic for standard examples" do
+      [
+        # names
+        [ "test", "test", true ],
+        [ "test", "foo", false ],
+        # full: epoch:version-relese
+        [ "testing = 1:1.1-1", "testing > 1:1.1-0", true ],
+        [ "testing = 1:1.1-1", "testing >= 1:1.1-0", true ],
+        [ "testing = 1:1.1-1", "testing >= 1:1.1-1", true ],
+        [ "testing = 1:1.1-1", "testing = 1:1.1-1", true ],
+        [ "testing = 1:1.1-1", "testing == 1:1.1-1", true ],
+        [ "testing = 1:1.1-1", "testing <= 1:1.1-1", true ],
+        [ "testing = 1:1.1-1", "testing <= 1:1.1-0", false ],
+        [ "testing = 1:1.1-1", "testing < 1:1.1-0", false ],
+        # partial: epoch:version
+        [ "testing = 1:1.1", "testing > 1:1.0", true ],
+        [ "testing = 1:1.1", "testing >= 1:1.0", true ],
+        [ "testing = 1:1.1", "testing >= 1:1.1", true ],
+        [ "testing = 1:1.1", "testing = 1:1.1", true ],
+        [ "testing = 1:1.1", "testing == 1:1.1", true ],
+        [ "testing = 1:1.1", "testing <= 1:1.1", true ],
+        [ "testing = 1:1.1", "testing <= 1:1.0", false ],
+        [ "testing = 1:1.1", "testing < 1:1.0", false ],
+        # partial: epoch
+        [ "testing = 1:", "testing > 0:", true ],
+        [ "testing = 1:", "testing >= 0:", true ],
+        [ "testing = 1:", "testing >= 1:", true ],
+        [ "testing = 1:", "testing = 1:", true ],
+        [ "testing = 1:", "testing == 1:", true ],
+        [ "testing = 1:", "testing <= 1:", true ],
+        [ "testing = 1:", "testing <= 0:", false ],
+        [ "testing = 1:", "testing < 0:", false ],
+        # mix and match!
+        [ "testing = 1:1.1-1", "testing == 1:1.1", true ],
+        [ "testing = 1:1.1-1", "testing == 1:", true ],
+     ].each do |prov, req, result|
+        @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.parse(prov)
+        @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse(req)
+
+        @rpmprovide.satisfy?(@rpmrequire).should == result
+        @rpmrequire.satisfy?(@rpmprovide).should == result
+      end
+    end
+  end
+
+end
+
+# thanks resource_collection_spec.rb!
+describe Chef::Provider::Package::Yum::RPMDb do
+  before(:each) do
+    @rpmdb = Chef::Provider::Package::Yum::RPMDb.new
+    # name, version, arch, installed, available
+    deps_v = [
+      Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)"),
+      Chef::Provider::Package::Yum::RPMDependency.parse("test-package-a = 0:1.6.5-9.36.el5")
+    ]
+    deps_z = [
+      Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)"),
+      Chef::Provider::Package::Yum::RPMDependency.parse("config(test) = 0:1.6.5-9.36.el5"),
+      Chef::Provider::Package::Yum::RPMDependency.parse("test-package-c = 0:1.6.5-9.36.el5")
+    ]
+    @rpm_v = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-a", "0:1.6.5-9.36.el5", "i386", deps_v, true, false, "base")
+    @rpm_w = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "i386", [], true, true, "extras")
+    @rpm_x = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "0:1.6.5-9.36.el5", "x86_64", [], false, true, "extras")
+    @rpm_y = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-b", "1:1.6.5-9.36.el5", "x86_64", [], true, true, "extras")
+    @rpm_z = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-c", "0:1.6.5-9.36.el5", "noarch", deps_z, true, true, "base")
+    @rpm_z_mirror = Chef::Provider::Package::Yum::RPMDbPackage.new("test-package-c", "0:1.6.5-9.36.el5", "noarch", deps_z, true, true, "base")
+  end
+
+  describe "initialize" do
+    it "should return a Chef::Provider::Package::Yum::RPMDb object" do
+      @rpmdb.should be_kind_of(Chef::Provider::Package::Yum::RPMDb)
+    end
+  end
+
+  describe "push" do
+    it "should accept an RPMDbPackage object through pushing" do
+      lambda { @rpmdb.push(@rpm_w) }.should_not raise_error
+    end
+
+    it "should accept multiple RPMDbPackage object through pushing" do
+      lambda { @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z) }.should_not raise_error
+    end
+
+    it "should only accept an RPMDbPackage object" do
+      lambda { @rpmdb.push("string") }.should raise_error
+    end
+
+    it "should add the package to the package db" do
+      @rpmdb.push(@rpm_w)
+      @rpmdb["test-package-b"].should_not be == nil
+    end
+
+    it "should add conditionally add the package to the available list" do
+      @rpmdb.available_size.should be == 0
+      @rpmdb.push(@rpm_v, @rpm_w)
+      @rpmdb.available_size.should be == 1
+    end
+
+    it "should add conditionally add the package to the installed list" do
+      @rpmdb.installed_size.should be == 0
+      @rpmdb.push(@rpm_w, @rpm_x)
+      @rpmdb.installed_size.should be == 1
+    end
+
+    it "should have a total of 2 packages in the RPMDb" do
+      @rpmdb.size.should be == 0
+      @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+      @rpmdb.size.should be == 2
+    end
+
+    it "should keep the Array unique when a duplicate is pushed" do
+      @rpmdb.push(@rpm_z, @rpm_z_mirror)
+      @rpmdb["test-package-c"].size.should be == 1
+    end
+
+    it "should register the package provides in the provides index" do
+      @rpmdb.push(@rpm_v, @rpm_w, @rpm_z)
+      @rpmdb.lookup_provides("test-package-a")[0].should be == @rpm_v
+      @rpmdb.lookup_provides("config(test)")[0].should be == @rpm_z
+      @rpmdb.lookup_provides("libz.so.1()(64bit)")[0].should be == @rpm_v
+      @rpmdb.lookup_provides("libz.so.1()(64bit)")[1].should be == @rpm_z
+    end
+  end
+
+  describe "<<" do
+    it "should accept an RPMPackage object through the << operator" do
+      lambda { @rpmdb << @rpm_w }.should_not raise_error
+    end
+  end
+
+  describe "lookup" do
+    it "should return an Array of RPMPackage objects by index" do
+      @rpmdb << @rpm_w
+      @rpmdb.lookup("test-package-b").should be_kind_of(Array)
+    end
+  end
+
+  describe "[]" do
+    it "should return an Array of RPMPackage objects though the [index] operator" do
+      @rpmdb << @rpm_w
+      @rpmdb["test-package-b"].should be_kind_of(Array)
+    end
+
+    it "should return an Array of 3 RPMPackage objects" do
+      @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+      @rpmdb["test-package-b"].size.should be == 3
+    end
+
+    it "should return an Array of RPMPackage objects sorted from newest to oldest" do
+      @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+      @rpmdb["test-package-b"][0].should be == @rpm_y
+      @rpmdb["test-package-b"][1].should be == @rpm_x
+      @rpmdb["test-package-b"][2].should be == @rpm_w
+    end
+  end
+
+  describe "lookup_provides" do
+    it "should return an Array of RPMPackage objects by index" do
+      @rpmdb << @rpm_z
+      x = @rpmdb.lookup_provides("config(test)")
+      x.should be_kind_of(Array)
+      x[0].should be == @rpm_z
+    end
+  end
+
+  describe "clear" do
+    it "should clear the RPMDb" do
+      @rpmdb.should_receive(:clear_available).once
+      @rpmdb.should_receive(:clear_installed).once
+      @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+      @rpmdb.size.should_not be == 0
+      @rpmdb.lookup_provides("config(test)").should be_kind_of(Array)
+      @rpmdb.clear
+      @rpmdb.lookup_provides("config(test)").should be == nil
+      @rpmdb.size.should be == 0
+    end
+  end
+
+  describe "clear_available" do
+    it "should clear the available list" do
+      @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+      @rpmdb.available_size.should_not be == 0
+      @rpmdb.clear_available
+      @rpmdb.available_size.should be == 0
+    end
+  end
+
+  describe "available?" do
+    it "should return true if a package is available" do
+      @rpmdb.available?(@rpm_w).should be == false
+      @rpmdb.push(@rpm_v, @rpm_w)
+      @rpmdb.available?(@rpm_v).should be == false
+      @rpmdb.available?(@rpm_w).should be == true
+    end
+  end
+
+  describe "clear_installed" do
+    it "should clear the installed list" do
+      @rpmdb.push(@rpm_w, @rpm_x, @rpm_y, @rpm_z)
+      @rpmdb.installed_size.should_not be == 0
+      @rpmdb.clear_installed
+      @rpmdb.installed_size.should be == 0
+    end
+  end
+
+  describe "installed?" do
+    it "should return true if a package is installed" do
+      @rpmdb.installed?(@rpm_w).should be == false
+      @rpmdb.push(@rpm_w, @rpm_x)
+      @rpmdb.installed?(@rpm_w).should be == true
+      @rpmdb.installed?(@rpm_x).should be == false
+    end
+  end
+
+  describe "whatprovides" do
+    it "should raise an error unless a RPMDependency is passed" do
+      @rpmprovide = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :==)
+      @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.new("testing", "1:1.6.5-9.36.el5", :>=)
+      lambda {
+        @rpmdb.whatprovides("hi")
+      }.should raise_error(ArgumentError)
+      lambda {
+        @rpmdb.whatprovides(@rpmrequire)
+      }.should_not raise_error
+    end
+
+    it "should return an Array of packages statisfying a RPMDependency" do
+      @rpmdb.push(@rpm_v, @rpm_w, @rpm_z)
+
+      @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse("test-package-a >= 1.6.5")
+      x = @rpmdb.whatprovides(@rpmrequire)
+      x.should be_kind_of(Array)
+      x[0].should be == @rpm_v
+
+      @rpmrequire = Chef::Provider::Package::Yum::RPMDependency.parse("libz.so.1()(64bit)")
+      x = @rpmdb.whatprovides(@rpmrequire)
+      x.should be_kind_of(Array)
+      x[0].should be == @rpm_v
+      x[1].should be == @rpm_z
+    end
+  end
+
+end
+
+describe Chef::Provider::Package::Yum::YumCache do
+  # allow for the reset of a Singleton
+  # thanks to Ian White (http://blog.ardes.com/2006/12/11/testing-singletons-with-ruby)
+  class << Chef::Provider::Package::Yum::YumCache
+    def reset_instance
+      Singleton.send :__init__, self
+      self
+    end
+  end
+
+  before(:each) do
+    @stdin = mock("STDIN", :nil_object => true)
+    @stdout = mock("STDOUT", :nil_object => true)
+
+    @stdout_good = <<EOF
+[option installonlypkgs] kernel kernel-bigmem kernel-enterprise
+erlang-mochiweb 0 1.4.1 5.el5 x86_64 ['erlang-mochiweb = 1.4.1-5.el5', 'mochiweb = 1.4.1-5.el5'] i installed
+zip 0 2.31 2.el5 x86_64 ['zip = 2.31-2.el5'] r base
+zisofs-tools 0 1.0.6 3.2.2 x86_64 [] a extras
+zlib 0 1.2.3 3 x86_64 ['zlib = 1.2.3-3', 'libz.so.1()(64bit)'] r base
+zlib 0 1.2.3 3 i386 ['zlib = 1.2.3-3', 'libz.so.1'] r base
+zlib-devel 0 1.2.3 3 i386 [] a extras
+zlib-devel 0 1.2.3 3 x86_64 ['zlib-devel = 1.2.3-3'] r base
+znc 0 0.098 1.el5 x86_64 [] a base
+znc-devel 0 0.098 1.el5 i386 [] a extras
+znc-devel 0 0.098 1.el5 x86_64 [] a base
+znc-extra 0 0.098 1.el5 x86_64 [] a base
+znc-modtcl 0 0.098 1.el5 x86_64 [] a base
+znc-test.beta1 0 0.098 1.el5 x86_64 [] a extras
+znc-test.test.beta1 0 0.098 1.el5 x86_64 [] a base
+EOF
+    @stdout_bad_type = <<EOF
+zip 0 2.31 2.el5 x86_64 ['zip = 2.31-2.el5'] r base
+zlib 0 1.2.3 3 x86_64 ['zlib = 1.2.3-3', 'libz.so.1()(64bit)'] c base
+zlib-devel 0 1.2.3 3 i386 [] a extras
+zlib-devel 0 1.2.3 3 x86_64 ['zlib-devel = 1.2.3-3'] bad installed
+znc-modtcl 0 0.098 1.el5 x86_64 [] a base
+EOF
+
+    @stdout_bad_separators = <<EOF
+zip 0 2.31 2.el5 x86_64 ['zip = 2.31-2.el5'] r base
+zlib 0 1.2.3 3 x86_64 ['zlib = 1.2.3-3', 'libz.so.1()(64bit)'] i base bad
+zlib-devel 0 1.2.3 3 i386 [] a extras
+bad zlib-devel 0 1.2.3 3 x86_64 ['zlib-devel = 1.2.3-3'] i installed
+znc-modtcl 0 0.098 1.el5 x86_64 [] a base bad
+EOF
+
+    @stdout_no_output = ""
+
+    @stderr = <<EOF
+yum-dump Config Error: File contains no section headers.
+file: file://///etc/yum.repos.d/CentOS-Base.repo, line: 12
+'qeqwewe\n'
+EOF
+    @status = mock("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_good, :stderr => @stderr)
+
+    # new singleton each time
+    Chef::Provider::Package::Yum::YumCache.reset_instance
+    @yc = Chef::Provider::Package::Yum::YumCache.instance
+    # load valid data
+    @yc.stub!(:shell_out!).and_return(@status)
+  end
+
+  describe "initialize" do
+    it "should return a Chef::Provider::Package::Yum::YumCache object" do
+      @yc.should be_kind_of(Chef::Provider::Package::Yum::YumCache)
+    end
+
+    it "should register reload for start of Chef::Client runs" do
+      Chef::Provider::Package::Yum::YumCache.reset_instance
+      Chef::Client.should_receive(:when_run_starts) do |&b|
+        b.should_not be_nil
+      end
+      @yc = Chef::Provider::Package::Yum::YumCache.instance
+    end
+  end
+
+  describe "refresh" do
+    it "should implicitly call yum-dump.py only once by default after being instantiated" do
+      @yc.should_receive(:shell_out!).once
+      @yc.installed_version("zlib")
+      @yc.reset
+      @yc.installed_version("zlib")
+    end
+
+    it "should run yum-dump.py using the system python when next_refresh is for :all" do
+      @yc.reload
+      @yc.should_receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --options --installed-provides$}, :timeout=>Chef::Config[:yum_timeout])
+      @yc.refresh
+    end
+
+    it "should run yum-dump.py with the installed flag when next_refresh is for :installed" do
+      @yc.reload_installed
+      @yc.should_receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --installed$}, :timeout=>Chef::Config[:yum_timeout])
+      @yc.refresh
+    end
+
+    it "should run yum-dump.py with the all-provides flag when next_refresh is for :provides" do
+      @yc.reload_provides
+      @yc.should_receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --options --all-provides$}, :timeout=>Chef::Config[:yum_timeout])
+      @yc.refresh
+    end
+
+    it "should pass extra_repo_control args to yum-dump.py" do
+      @yc.enable_extra_repo_control("--enablerepo=foo --disablerepo=bar")
+      @yc.should_receive(:shell_out!).with(%r{^/usr/bin/python .*/yum-dump.py --options --installed-provides --enablerepo=foo --disablerepo=bar$}, :timeout=>Chef::Config[:yum_timeout])
+      @yc.refresh
+    end
+
+    it "should warn about invalid data with too many separators" do
+      @status = mock("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_bad_separators, :stderr => @stderr)
+      @yc.stub!(:shell_out!).and_return(@status)
+      Chef::Log.should_receive(:warn).exactly(3).times.with(%r{Problem parsing})
+      @yc.refresh
+    end
+
+    it "should warn about invalid data with an incorrect type" do
+      @status = mock("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_bad_type, :stderr => @stderr)
+      @yc.stub!(:shell_out!).and_return(@status)
+      Chef::Log.should_receive(:warn).exactly(2).times.with(%r{Problem parsing})
+      @yc.refresh
+    end
+
+    it "should warn about no output from yum-dump.py" do
+      @status = mock("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_no_output, :stderr => @stderr)
+      @yc.stub!(:shell_out!).and_return(@status)
+      Chef::Log.should_receive(:warn).exactly(1).times.with(%r{no output from yum-dump.py})
+      @yc.refresh
+    end
+
+    it "should raise exception yum-dump.py exits with a non zero status" do
+      @status = mock("Status", :exitstatus => 1, :stdin => @stdin, :stdout => @stdout_no_output, :stderr => @stderr)
+      @yc.stub!(:shell_out!).and_return(@status)
+      lambda { @yc.refresh}.should raise_error(Chef::Exceptions::Package, %r{CentOS-Base.repo, line: 12})
+    end
+
+    it "should parse type 'i' into an installed state for a package" do
+      @yc.available_version("erlang-mochiweb").should be == nil
+      @yc.installed_version("erlang-mochiweb").should_not be == nil
+    end
+
+    it "should parse type 'a' into an available state for a package" do
+      @yc.available_version("znc").should_not be == nil
+      @yc.installed_version("znc").should be == nil
+    end
+
+    it "should parse type 'r' into an installed and available states for a package" do
+      @yc.available_version("zip").should_not be == nil
+      @yc.installed_version("zip").should_not be == nil
+    end
+
+    it "should parse installonlypkgs from yum-dump.py options output" do
+      @yc.allow_multi_install.should be == %w{kernel kernel-bigmem kernel-enterprise}
+    end
+  end
+
+  describe "installed_version" do
+    it "should take one or two arguments" do
+      lambda { @yc.installed_version("zip") }.should_not raise_error(ArgumentError)
+      lambda { @yc.installed_version("zip", "i386") }.should_not raise_error(ArgumentError)
+      lambda { @yc.installed_version("zip", "i386", "extra") }.should raise_error(ArgumentError)
+    end
+
+    it "should return version-release for matching package regardless of arch" do
+      @yc.installed_version("zip", "x86_64").should be == "2.31-2.el5"
+      @yc.installed_version("zip", nil).should be == "2.31-2.el5"
+    end
+
+    it "should return version-release for matching package and arch" do
+      @yc.installed_version("zip", "x86_64").should be == "2.31-2.el5"
+      @yc.installed_version("zisofs-tools", "i386").should be == nil
+    end
+
+    it "should return nil for an unmatched package" do
+      @yc.installed_version(nil, nil).should be == nil
+      @yc.installed_version("test1", nil).should be == nil
+      @yc.installed_version("test2", "x86_64").should be == nil
+    end
+  end
+
+  describe "available_version" do
+    it "should take one or two arguments" do
+      lambda { @yc.available_version("zisofs-tools") }.should_not raise_error(ArgumentError)
+      lambda { @yc.available_version("zisofs-tools", "i386") }.should_not raise_error(ArgumentError)
+      lambda { @yc.available_version("zisofs-tools", "i386", "extra") }.should raise_error(ArgumentError)
+    end
+
+    it "should return version-release for matching package regardless of arch" do
+      @yc.available_version("zip", "x86_64").should be == "2.31-2.el5"
+      @yc.available_version("zip", nil).should be == "2.31-2.el5"
+    end
+
+    it "should return version-release for matching package and arch" do
+      @yc.available_version("zip", "x86_64").should be == "2.31-2.el5"
+      @yc.available_version("zisofs-tools", "i386").should be == nil
+    end
+
+    it "should return nil for an unmatched package" do
+      @yc.available_version(nil, nil).should be == nil
+      @yc.available_version("test1", nil).should be == nil
+      @yc.available_version("test2", "x86_64").should be == nil
+    end
+  end
+
+  describe "version_available?" do
+    it "should take two or three arguments" do
+      lambda { @yc.version_available?("zisofs-tools") }.should raise_error(ArgumentError)
+      lambda { @yc.version_available?("zisofs-tools", "1.0.6-3.2.2") }.should_not raise_error(ArgumentError)
+      lambda { @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "x86_64") }.should_not raise_error(ArgumentError)
+    end
+
+    it "should return true if our package-version-arch is available" do
+      @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "x86_64").should be == true
+    end
+
+    it "should return true if our package-version, no arch, is available" do
+      @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", nil).should be == true
+      @yc.version_available?("zisofs-tools", "1.0.6-3.2.2").should be == true
+    end
+
+    it "should return false if our package-version-arch isn't available" do
+      @yc.version_available?("zisofs-tools", "1.0.6-3.2.2", "pretend").should be == false
+      @yc.version_available?("zisofs-tools", "pretend", "x86_64").should be == false
+      @yc.version_available?("pretend", "1.0.6-3.2.2", "x86_64").should be == false
+    end
+
+    it "should return false if our package-version, no arch, isn't available" do
+      @yc.version_available?("zisofs-tools", "pretend", nil).should be == false
+      @yc.version_available?("zisofs-tools", "pretend").should be == false
+      @yc.version_available?("pretend", "1.0.6-3.2.2").should be == false
+    end
+  end
+
+  describe "package_repository" do
+    it "should take two or three arguments" do
+      lambda { @yc.package_repository("zisofs-tools") }.should raise_error(ArgumentError)
+      lambda { @yc.package_repository("zisofs-tools", "1.0.6-3.2.2") }.should_not raise_error(ArgumentError)
+      lambda { @yc.package_repository("zisofs-tools", "1.0.6-3.2.2", "x86_64") }.should_not raise_error(ArgumentError)
+    end
+
+    it "should return repoid for package-version-arch" do
+      @yc.package_repository("zlib-devel", "1.2.3-3", "i386").should be == "extras"
+      @yc.package_repository("zlib-devel", "1.2.3-3", "x86_64").should be == "base"
+    end
+
+    it "should return repoid for package-version, no arch" do
+      @yc.package_repository("zisofs-tools", "1.0.6-3.2.2", nil).should be == "extras"
+      @yc.package_repository("zisofs-tools", "1.0.6-3.2.2").should be == "extras"
+    end
+
+    it "should return nil when no match for package-version-arch" do
+      @yc.package_repository("zisofs-tools", "1.0.6-3.2.2", "pretend").should be == nil
+      @yc.package_repository("zisofs-tools", "pretend", "x86_64").should be == nil
+      @yc.package_repository("pretend", "1.0.6-3.2.2", "x86_64").should be == nil
+    end
+
+    it "should return nil when no match for package-version, no arch" do
+      @yc.package_repository("zisofs-tools", "pretend", nil).should be == nil
+      @yc.package_repository("zisofs-tools", "pretend").should be == nil
+      @yc.package_repository("pretend", "1.0.6-3.2.2").should be == nil
+    end
+  end
+
+  describe "reset" do
+    it "should empty the installed and available packages RPMDb" do
+      @yc.available_version("zip", "x86_64").should be == "2.31-2.el5"
+      @yc.installed_version("zip", "x86_64").should be == "2.31-2.el5"
+      @yc.reset
+      @yc.available_version("zip", "x86_64").should be == nil
+      @yc.installed_version("zip", "x86_64").should be == nil
+    end
+  end
+
+  describe "package_available?" do
+    it "should return true a package name is available" do
+      @yc.package_available?("zisofs-tools").should be == true
+      @yc.package_available?("moo").should be == false
+      @yc.package_available?(nil).should be == false
+    end
+
+    it "should return true a package name + arch is available" do
+      @yc.package_available?("zlib-devel.i386").should be == true
+      @yc.package_available?("zisofs-tools.x86_64").should be == true
+      @yc.package_available?("znc-test.beta1.x86_64").should be == true
+      @yc.package_available?("znc-test.beta1").should be == true
+      @yc.package_available?("znc-test.test.beta1").should be == true
+      @yc.package_available?("moo.i386").should be == false
+      @yc.package_available?("zisofs-tools.beta").should be == false
+      @yc.package_available?("znc-test.test").should be == false
+    end
+  end
+
+  describe "enable_extra_repo_control" do
+    it "should set @extra_repo_control to arg" do
+      @yc.enable_extra_repo_control("--enablerepo=test")
+      @yc.extra_repo_control.should be == "--enablerepo=test"
+    end
+
+    it "should call reload once when set to flag cache for update" do
+      @yc.should_receive(:reload).once
+      @yc.enable_extra_repo_control("--enablerepo=test")
+      @yc.enable_extra_repo_control("--enablerepo=test")
+    end
+  end
+
+  describe "disable_extra_repo_control" do
+    it "should set @extra_repo_control to nil" do
+      @yc.enable_extra_repo_control("--enablerepo=test")
+      @yc.disable_extra_repo_control
+      @yc.extra_repo_control.should be == nil
+    end
+
+    it "should call reload once when cleared to flag cache for update" do
+      @yc.should_receive(:reload).once
+      @yc.enable_extra_repo_control("--enablerepo=test")
+      @yc.should_receive(:reload).once
+      @yc.disable_extra_repo_control
+      @yc.disable_extra_repo_control
+    end
+  end
+
+end
diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb
new file mode 100644
index 0000000..cf01939
--- /dev/null
+++ b/spec/unit/provider/package/zypper_spec.rb
@@ -0,0 +1,221 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Provider::Package::Zypper do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new("cups")
+
+    @current_resource = Chef::Resource::Package.new("cups")
+
+    @status = mock("Status", :exitstatus => 0)
+
+    @provider = Chef::Provider::Package::Zypper.new(@new_resource, @run_context)
+    Chef::Resource::Package.stub!(:new).and_return(@current_resource)
+    @provider.stub!(:popen4).and_return(@status)
+    @stderr = StringIO.new
+    @stdout = StringIO.new
+    @pid = mock("PID")
+    @provider.stub!(:`).and_return("2.0")
+  end
+
+  describe "when loading the current package state" do
+    it "should create a current resource with the name of the new_resource" do
+      Chef::Resource::Package.should_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
+      @current_resource.should_receive(:package_name).with(@new_resource.package_name)
+      @provider.load_current_resource
+    end
+
+    it "should run zypper info with the package name" do
+      @provider.should_receive(:popen4).with("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
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @current_resource.should_receive(:version).with(nil).and_return(true)
+      @provider.load_current_resource
+    end
+
+    it "should set the installed version if zypper info has one" do
+      @stdout = StringIO.new("Version: 1.0\nInstalled: Yes\n")
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @current_resource.should_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
+      @stdout = StringIO.new("Version: 1.0\nInstalled: No\nStatus: out-of-date (version 0.9 installed)")
+
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      @provider.load_current_resource
+      @provider.candidate_version.should eql("1.0")
+    end
+
+    it "should raise an exception if zypper info fails" do
+      @status.should_receive(:exitstatus).and_return(1)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should not raise an exception if zypper info succeeds" do
+      @status.should_receive(:exitstatus).and_return(0)
+      lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should return the current resouce" do
+      @provider.load_current_resource.should eql(@current_resource)
+    end
+  end
+
+  describe "install_package" do
+    it "should run zypper install with the package name and version" do
+      Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(true)
+      @provider.should_receive(:shell_out!).with(
+        "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
+      Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false)
+      @provider.should_receive(:shell_out!).with(
+        "zypper --non-interactive --no-gpg-checks install "+
+        "--auto-agree-with-licenses emacs=1.0")
+      @provider.install_package("emacs", "1.0")
+    end
+    it "should warn about gpg checks on zypper install" do
+      Chef::Log.should_receive(:warn).with(
+        /All packages will be installed without gpg signature checks/)
+      @provider.should_receive(:shell_out!).with(
+        "zypper --non-interactive --no-gpg-checks install "+
+        "--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
+      Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(true)
+      @provider.should_receive(:shell_out!).with(
+        "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
+      Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false)
+      @provider.should_receive(:shell_out!).with(
+        "zypper --non-interactive --no-gpg-checks install "+
+        "--auto-agree-with-licenses emacs=1.0")
+      @provider.upgrade_package("emacs", "1.0")
+    end
+    it "should warn about gpg checks on zypper upgrade" do
+      Chef::Log.should_receive(:warn).with(
+        /All packages will be installed without gpg signature checks/)
+      @provider.should_receive(:shell_out!).with(
+        "zypper --non-interactive --no-gpg-checks install "+
+        "--auto-agree-with-licenses emacs=1.0")
+      @provider.upgrade_package("emacs", "1.0")
+    end
+    it "should run zypper upgrade without gpg checks" do
+      @provider.should_receive(:shell_out!).with(
+        "zypper --non-interactive --no-gpg-checks install "+
+        "--auto-agree-with-licenses emacs=1.0")
+
+      @provider.upgrade_package("emacs", "1.0")
+    end
+  end
+
+  describe "remove_package" do
+    it "should run zypper remove with the package name" do
+      Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(true)
+      @provider.should_receive(:shell_out!).with(
+        "zypper --non-interactive remove emacs=1.0")
+      @provider.remove_package("emacs", "1.0")
+    end
+    it "should run zypper remove without gpg checks" do
+      Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false)
+      @provider.should_receive(:shell_out!).with(
+          "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
+      Chef::Log.should_receive(:warn).with(
+        /All packages will be installed without gpg signature checks/)
+      @provider.should_receive(:shell_out!).with(
+        "zypper --non-interactive --no-gpg-checks remove emacs=1.0")
+
+      @provider.remove_package("emacs", "1.0")
+    end
+  end
+
+  describe "purge_package" do
+    it "should run remove_package with the name and version" do
+      @provider.should_receive(:shell_out!).with(
+        "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
+      Chef::Config.stub(:[]).with(:zypper_check_gpg).and_return(false)
+      @provider.should_receive(:shell_out!).with(
+        "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
+      Chef::Log.should_receive(:warn).with(
+        /All packages will be installed without gpg signature checks/)
+      @provider.should_receive(:shell_out!).with(
+        "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
+      @provider.stub!(:`).and_return("0.11.6")
+    end
+
+    describe "install_package" do
+      it "should run zypper install with the package name and version" do
+        @provider.should_receive(:shell_out!).with(
+          "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
+        @provider.should_receive(:shell_out!).with(
+          "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
+        @provider.should_receive(:shell_out!).with(
+           "zypper --no-gpg-checks remove -y emacs")
+        @provider.remove_package("emacs", "1.0")
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb
new file mode 100644
index 0000000..f80eed1
--- /dev/null
+++ b/spec/unit/provider/package_spec.rb
@@ -0,0 +1,427 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Provider::Package do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Package.new('emacs')
+    @current_resource = Chef::Resource::Package.new('emacs')
+    @provider = Chef::Provider::Package.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+
+    @provider.candidate_version = "1.0"
+  end
+
+  describe "when installing a package" do
+    before(:each) do
+      @provider.current_resource = @current_resource
+      @provider.stub!(:install_package).and_return(true)
+    end
+
+    it "should raise a Chef::Exceptions::Package if no version is specified, and no candidate is available" do
+      @provider.candidate_version = nil
+      lambda { @provider.run_action(:install) }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should call preseed_package if a response_file is given" do
+      @new_resource.response_file("foo")
+      @provider.should_receive(:get_preseed_file).with(
+        @new_resource.name,
+        @provider.candidate_version
+      ).and_return("/var/cache/preseed-test")
+
+      @provider.should_receive(:preseed_package).with(
+        "/var/cache/preseed-test"
+      ).and_return(true)
+      @provider.run_action(:install)
+    end
+
+    it "should not call preseed_package if a response_file is not given" do
+      @provider.should_not_receive(:preseed_package)
+      @provider.run_action(:install)
+    end
+
+    it "should install the package at the candidate_version if it is not already installed" do
+      @provider.should_receive(:install_package).with(
+        @new_resource.name,
+        @provider.candidate_version
+      ).and_return(true)
+      @provider.run_action(:install)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should install the package at the version specified if it is not already installed" do
+      @new_resource.version("1.0")
+      @provider.should_receive(:install_package).with(
+        @new_resource.name,
+        @new_resource.version
+      ).and_return(true)
+      @provider.run_action(:install)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should install the package at the version specified if a different version is installed" do
+      @new_resource.version("1.0")
+      @current_resource.stub!(:version).and_return("0.99")
+      @provider.should_receive(:install_package).with(
+        @new_resource.name,
+        @new_resource.version
+      ).and_return(true)
+      @provider.run_action(:install)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not install the package if it is already installed and no version is specified" do
+      @current_resource.version("1.0")
+      @provider.should_not_receive(:install_package)
+      @provider.run_action(:install)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should not install the package if it is already installed at the version specified" do
+      @current_resource.version("1.0")
+      @new_resource.version("1.0")
+      @provider.should_not_receive(:install_package)
+      @provider.run_action(:install)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should call the candidate_version accessor only once if the package is already installed and no version is specified" do
+      @current_resource.version("1.0")
+      @provider.stub!(:candidate_version).and_return("1.0")
+      @provider.run_action(:install)
+    end
+
+    it "should call the candidate_version accessor only once if the package is already installed at the version specified" do
+      @current_resource.version("1.0")
+      @new_resource.version("1.0")
+      @provider.run_action(:install)
+    end
+
+    it "should set the resource to updated if it installs the package" do
+      @provider.run_action(:install)
+      @new_resource.should be_updated
+    end
+
+  end
+
+  describe "when upgrading the package" do
+    before(:each) do
+      @provider.stub!(:upgrade_package).and_return(true)
+    end
+
+    it "should upgrade the package if the current version is not the candidate version" do
+      @provider.should_receive(:upgrade_package).with(
+        @new_resource.name,
+        @provider.candidate_version
+      ).and_return(true)
+      @provider.run_action(:upgrade)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should set the resource to updated if it installs the package" do
+      @provider.run_action(:upgrade)
+      @new_resource.should be_updated
+    end
+
+    it "should not install the package if the current version is the candidate version" do
+      @current_resource.version "1.0"
+      @provider.should_not_receive(:upgrade_package)
+      @provider.run_action(:upgrade)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should print the word 'uninstalled' if there was no original version" do
+      @current_resource.stub!(:version).and_return(nil)
+      Chef::Log.should_receive(:info).with("package[emacs] upgraded from uninstalled to 1.0")
+      @provider.run_action(:upgrade)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should raise a Chef::Exceptions::Package if current version and candidate are nil" do
+      @current_resource.stub!(:version).and_return(nil)
+      @provider.candidate_version = nil
+      lambda { @provider.run_action(:upgrade) }.should raise_error(Chef::Exceptions::Package)
+    end
+
+    it "should not install the package if candidate version is nil" do
+      @current_resource.version "1.0"
+      @provider.candidate_version = nil
+      @provider.should_not_receive(:upgrade_package)
+      @provider.run_action(:upgrade)
+      @new_resource.should_not be_updated_by_last_action
+    end
+  end
+
+  describe "When removing the package" do
+    before(:each) do
+      @provider.stub!(:remove_package).and_return(true)
+      @current_resource.version '1.4.2'
+    end
+
+    it "should remove the package if it is installed" do
+      @provider.should be_removing_package
+      @provider.should_receive(:remove_package).with('emacs', nil)
+      @provider.run_action(:remove)
+      @new_resource.should be_updated
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should remove the package at a specific version if it is installed at that version" do
+      @new_resource.version "1.4.2"
+      @provider.should be_removing_package
+      @provider.should_receive(:remove_package).with('emacs', '1.4.2')
+      @provider.run_action(:remove)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not remove the package at a specific version if it is not installed at that version" do
+      @new_resource.version "1.0"
+      @provider.should_not be_removing_package
+      @provider.should_not_receive(:remove_package)
+      @provider.run_action(:remove)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should not remove the package if it is not installed" do
+      @provider.should_not_receive(:remove_package)
+      @current_resource.stub!(:version).and_return(nil)
+      @provider.run_action(:remove)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should set the resource to updated if it removes the package" do
+      @provider.run_action(:remove)
+      @new_resource.should be_updated
+    end
+
+  end
+
+  describe "When purging the package" do
+    before(:each) do
+      @provider.stub!(:purge_package).and_return(true)
+      @current_resource.version '1.4.2'
+    end
+
+    it "should purge the package if it is installed" do
+      @provider.should be_removing_package
+      @provider.should_receive(:purge_package).with('emacs', nil)
+      @provider.run_action(:purge)
+      @new_resource.should be_updated
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should purge the package at a specific version if it is installed at that version" do
+      @new_resource.version "1.4.2"
+      @provider.should be_removing_package
+      @provider.should_receive(:purge_package).with('emacs', '1.4.2')
+      @provider.run_action(:purge)
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should not purge the package at a specific version if it is not installed at that version" do
+      @new_resource.version "1.0"
+      @provider.should_not be_removing_package
+      @provider.should_not_receive(:purge_package)
+      @provider.run_action(:purge)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should not purge the package if it is not installed" do
+      @current_resource.instance_variable_set(:@version, nil)
+      @provider.should_not be_removing_package
+
+      @provider.should_not_receive(:purge_package)
+      @provider.run_action(:purge)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should set the resource to updated if it purges the package" do
+      @provider.run_action(:purge)
+      @new_resource.should be_updated
+    end
+
+  end
+
+  describe "when reconfiguring the package" do
+    before(:each) do
+      @provider.stub!(:reconfig_package).and_return(true)
+    end
+
+    it "should info log, reconfigure the package and update the resource" do
+      @current_resource.stub!(:version).and_return('1.0')
+      @new_resource.stub!(:response_file).and_return(true)
+      @provider.should_receive(:get_preseed_file).and_return('/var/cache/preseed-test')
+      @provider.stub!(:preseed_package).and_return(true)
+      @provider.stub!(:reconfig_package).and_return(true)
+      Chef::Log.should_receive(:info).with("package[emacs] reconfigured")
+      @provider.should_receive(:reconfig_package)
+      @provider.run_action(:reconfig)
+      @new_resource.should be_updated
+      @new_resource.should be_updated_by_last_action
+    end
+
+    it "should debug log and not reconfigure the package if the package is not installed" do
+      @current_resource.stub!(:version).and_return(nil)
+      Chef::Log.should_receive(:debug).with("package[emacs] is NOT installed - nothing to do")
+      @provider.should_not_receive(:reconfig_package)
+      @provider.run_action(:reconfig)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should debug log and not reconfigure the package if no response_file is given" do
+      @current_resource.stub!(:version).and_return('1.0')
+      @new_resource.stub!(:response_file).and_return(nil)
+      Chef::Log.should_receive(:debug).with("package[emacs] no response_file provided - nothing to do")
+      @provider.should_not_receive(:reconfig_package)
+      @provider.run_action(:reconfig)
+      @new_resource.should_not be_updated_by_last_action
+    end
+
+    it "should debug log and not reconfigure the package if the response_file has not changed" do
+      @current_resource.stub!(:version).and_return('1.0')
+      @new_resource.stub!(:response_file).and_return(true)
+      @provider.should_receive(:get_preseed_file).and_return(false)
+      @provider.stub!(:preseed_package).and_return(false)
+      Chef::Log.should_receive(:debug).with("package[emacs] preseeding has not changed - nothing to do")
+      @provider.should_not_receive(:reconfig_package)
+      @provider.run_action(:reconfig)
+      @new_resource.should_not be_updated_by_last_action
+    end
+  end
+
+  describe "when running commands to be implemented by subclasses" do
+    it "should raises UnsupportedAction for install" do
+      lambda { @provider.install_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+
+    it "should raises UnsupportedAction for upgrade" do
+      lambda { @provider.upgrade_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+
+    it "should raises UnsupportedAction for remove" do
+      lambda { @provider.remove_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+
+    it "should raises UnsupportedAction for purge" do
+      lambda { @provider.purge_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+
+    it "should raise UnsupportedAction for preseed_package" do
+      preseed_file = "/tmp/sun-jdk-package-preseed-file.seed"
+      lambda { @provider.preseed_package(preseed_file) }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+
+    it "should raise UnsupportedAction for reconfig" do
+      lambda { @provider.reconfig_package('emacs', '1.4.2') }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+  end
+
+  describe "when given a response file" do
+    before(:each) do
+      @cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+      Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, @cookbook_repo) }
+
+      @node = Chef::Node.new
+      cl = Chef::CookbookLoader.new(@cookbook_repo)
+      cl.load_cookbooks
+      @cookbook_collection = Chef::CookbookCollection.new(cl)
+
+      @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+      @provider.run_context = @run_context
+
+      @node.automatic_attrs[:platform] = 'PLATFORM: just testing'
+      @node.automatic_attrs[:platform_version] = 'PLATFORM VERSION: just testing'
+
+      @new_resource.response_file('java.response')
+      @new_resource.cookbook_name = 'java'
+    end
+
+    describe "creating the cookbook file resource to fetch the response file" do
+      before do
+        Chef::FileCache.should_receive(:create_cache_path).with('preseed/java').and_return("/tmp/preseed/java")
+      end
+
+      it "sets the preseed resource's runcontext to its own run context" do
+        Chef::FileCache.stub!(:create_cache_path).and_return("/tmp/preseed/java")
+        @provider.preseed_resource('java', '6').run_context.should_not be_nil
+        @provider.preseed_resource('java', '6').run_context.should equal(@provider.run_context)
+      end
+
+      it "should set the cookbook name of the remote file to the new resources cookbook name" do
+        @provider.preseed_resource('java', '6').cookbook_name.should == 'java'
+      end
+
+      it "should set remote files source to the new resources response file" do
+        @provider.preseed_resource('java', '6').source.should == 'java.response'
+      end
+
+      it "should never back up the cached response file" do
+        @provider.preseed_resource('java', '6').backup.should be_false
+      end
+
+      it "sets the install path of the resource to $file_cache/$cookbook/$pkg_name-$pkg_version.seed" do
+        @provider.preseed_resource('java', '6').path.should == '/tmp/preseed/java/java-6.seed'
+      end
+    end
+
+    describe "when installing the preseed file to the cache location" do
+      before do
+        @node.automatic_attrs[:platform] = :just_testing
+        @node.automatic_attrs[:platform_version] = :just_testing
+
+        @response_file_destination = Dir.tmpdir + '/preseed--java--java-6.seed'
+
+        @response_file_resource = Chef::Resource::CookbookFile.new(@response_file_destination, @run_context)
+        @response_file_resource.cookbook_name = 'java'
+        @response_file_resource.backup(false)
+        @response_file_resource.source('java.response')
+
+
+        @provider.should_receive(:preseed_resource).with('java', '6').and_return(@response_file_resource)
+      end
+
+      after do
+        FileUtils.rm(@response_file_destination) if ::File.exist?(@response_file_destination)
+      end
+
+      it "creates the preseed file in the cache" do
+        @response_file_resource.should_receive(:run_action).with(:create)
+        @provider.get_preseed_file("java", "6")
+      end
+
+      it "returns the path to the response file if the response file was updated" do
+        @provider.get_preseed_file("java", "6").should == @response_file_destination
+      end
+
+      it "should return false if the response file has not been updated" do
+        @response_file_resource.updated_by_last_action(false)
+        @response_file_resource.should_not be_updated_by_last_action
+        # don't let the response_file_resource set updated to true
+        @response_file_resource.should_receive(:run_action).with(:create)
+        @provider.get_preseed_file("java", "6").should be(false)
+      end
+
+    end
+
+  end
+end
diff --git a/spec/unit/provider/powershell_spec.rb b/spec/unit/provider/powershell_spec.rb
new file mode 100644
index 0000000..33c4028
--- /dev/null
+++ b/spec/unit/provider/powershell_spec.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+describe Chef::Provider::PowershellScript, "action_run" do
+
+  before(:each) do
+    @node = Chef::Node.new
+
+    @node.default["kernel"] = Hash.new
+    @node.default["kernel"][:machine] = :x86_64.to_s
+
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::PowershellScript.new('run some powershell code', @run_context)
+
+    @provider = Chef::Provider::PowershellScript.new(@new_resource, @run_context)
+  end
+
+  it "should set the -File flag as the last flag" do
+    @provider.flags.split(' ').pop.should == "-File"
+  end
+
+end
diff --git a/spec/unit/provider/registry_key_spec.rb b/spec/unit/provider/registry_key_spec.rb
new file mode 100644
index 0000000..2f6f817
--- /dev/null
+++ b/spec/unit/provider/registry_key_spec.rb
@@ -0,0 +1,269 @@
+#
+# Author:: Lamont Granquist (lamont at opscode.com)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::RegistryKey do
+
+  let(:testval1) { { :name => "one", :type => :string, :data => "1" } }
+  let(:testval1_wrong_type) { { :name => "one", :type => :multi_string, :data => "1" } }
+  let(:testval1_wrong_data) { { :name => "one", :type => :string, :data => "2" } }
+  let(:testval2) { { :name => "two", :type => :string, :data => "2" } }
+  let(:testkey1) { 'HKLM\Software\Opscode\Testing' }
+
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::RegistryKey.new("windows is fun", @run_context)
+    @new_resource.key testkey1
+    @new_resource.values( testval1 )
+    @new_resource.recursive false
+
+    @provider = Chef::Provider::RegistryKey.new(@new_resource, @run_context)
+
+    @provider.stub!(:running_on_windows!).and_return(true)
+    @double_registry = double(Chef::Win32::Registry)
+    @provider.stub!(:registry).and_return(@double_registry)
+  end
+
+  describe "when first created" do
+  end
+
+  describe "executing load_current_resource" do
+    describe "when the key exists" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).with(testkey1).and_return(true)
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 )
+        @provider.load_current_resource
+      end
+
+      it "should set the key of the current resource to the key of the new resource" do
+        @provider.current_resource.key.should == @new_resource.key
+      end
+
+      it "should set the architecture of the current resource to the architecture of the new resource" do
+        @provider.current_resource.architecture.should == @new_resource.architecture
+      end
+
+      it "should set the recursive flag of the current resource to the recursive flag of the new resource" do
+        @provider.current_resource.recursive.should == @new_resource.recursive
+      end
+
+      it "should set the values of the current resource to the values it got from the registry" do
+        @provider.current_resource.values.should == [ testval2 ]
+      end
+    end
+
+    describe "when the key does not exist" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).with(testkey1).and_return(false)
+        @provider.load_current_resource
+      end
+
+      it "should set the values in the current resource to empty array" do
+        @provider.current_resource.values.should == []
+      end
+    end
+  end
+
+  describe "action_create" do
+    context "when the key exists" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true)
+      end
+      it "should do nothing if the key and the value both exist" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 )
+        @double_registry.should_not_receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+      it "should create the value if the key exists but the value does not" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 )
+        @double_registry.should_receive(:set_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+      it "should set the value if the key exists but the data does not match" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_data )
+        @double_registry.should_receive(:set_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+      it "should set the value if the key exists but the type does not match" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_type )
+        @double_registry.should_receive(:set_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+    end
+    context "when the key exists and the values in the new resource are empty" do
+      it "when a value is in the key, it should do nothing" do
+        @provider.new_resource.values([])
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true)
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 )
+        @double_registry.should_not_receive(:create_key)
+        @double_registry.should_not_receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+      it "when no value is in the key, it should do nothing" do
+        @provider.new_resource.values([])
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true)
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( nil )
+        @double_registry.should_not_receive(:create_key)
+        @double_registry.should_not_receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+    end
+    context "when the key does not exist" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false)
+      end
+      it "should create the key and the value" do
+        @double_registry.should_receive(:create_key).with(testkey1, false)
+        @double_registry.should_receive(:set_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+    end
+    context "when the key does not exist and the values in the new resource are empty" do
+      it "should create the key" do
+        @new_resource.values([])
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false)
+        @double_registry.should_receive(:create_key).with(testkey1, false)
+        @double_registry.should_not_receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create
+      end
+    end
+  end
+
+  describe "action_create_if_missing" do
+    context "when the key exists" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true)
+      end
+      it "should do nothing if the key and the value both exist" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 )
+        @double_registry.should_not_receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create_if_missing
+      end
+      it "should create the value if the key exists but the value does not" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 )
+        @double_registry.should_receive(:set_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_create_if_missing
+      end
+      it "should not set the value if the key exists but the data does not match" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_data )
+        @double_registry.should_not_receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create_if_missing
+      end
+      it "should not set the value if the key exists but the type does not match" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_type )
+        @double_registry.should_not_receive(:set_value)
+        @provider.load_current_resource
+        @provider.action_create_if_missing
+      end
+    end
+    context "when the key does not exist" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false)
+      end
+      it "should create the key and the value" do
+        @double_registry.should_receive(:create_key).with(testkey1, false)
+        @double_registry.should_receive(:set_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_create_if_missing
+      end
+    end
+  end
+
+  describe "action_delete" do
+    context "when the key exists" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true)
+      end
+      it "deletes the value when the value exists" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 )
+        @double_registry.should_receive(:delete_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_delete
+      end
+      it "deletes the value when the value exists, but the type is wrong" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_type )
+        @double_registry.should_receive(:delete_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_delete
+      end
+      it "deletes the value when the value exists, but the data is wrong" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_data )
+        @double_registry.should_receive(:delete_value).with(testkey1, testval1)
+        @provider.load_current_resource
+        @provider.action_delete
+      end
+      it "does not delete the value when the value does not exist" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 )
+        @double_registry.should_not_receive(:delete_value)
+        @provider.load_current_resource
+        @provider.action_delete
+      end
+    end
+    context "when the key does not exist" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false)
+      end
+      it "does nothing" do
+        @double_registry.should_not_receive(:delete_value)
+        @provider.load_current_resource
+        @provider.action_delete
+      end
+    end
+  end
+
+  describe "action_delete_key" do
+    context "when the key exists" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true)
+      end
+      it "deletes the key" do
+        @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 )
+        @double_registry.should_receive(:delete_key).with(testkey1, false)
+        @provider.load_current_resource
+        @provider.action_delete_key
+      end
+    end
+    context "when the key does not exist" do
+      before(:each) do
+        @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false)
+      end
+      it "does nothing" do
+        @double_registry.should_not_receive(:delete_key)
+        @provider.load_current_resource
+        @provider.action_delete_key
+      end
+    end
+  end
+
+end
+
diff --git a/spec/unit/provider/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb
new file mode 100644
index 0000000..d3f1438
--- /dev/null
+++ b/spec/unit/provider/remote_directory_spec.rb
@@ -0,0 +1,222 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2010 Daniel DeLeo
+# 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 'digest/md5'
+require 'tmpdir'
+require 'chef/mixin/file_class'
+
+class Chef::CFCCheck
+  include Chef::Mixin::FileClass
+end
+
+describe Chef::Provider::RemoteDirectory do
+  before do
+    Chef::FileAccessControl.any_instance.stub(:set_all)
+
+    @resource = Chef::Resource::RemoteDirectory.new(File.join(Dir.tmpdir, "tafty"))
+    # in CHEF_SPEC_DATA/cookbooks/openldap/files/default/remotedir
+    @resource.source "remotedir"
+    @resource.cookbook('openldap')
+
+    @cookbook_repo = ::File.expand_path(::File.join(CHEF_SPEC_DATA, "cookbooks"))
+    Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, @cookbook_repo) }
+
+    @node = Chef::Node.new
+    cl = Chef::CookbookLoader.new(@cookbook_repo)
+    cl.load_cookbooks
+    @cookbook_collection = Chef::CookbookCollection.new(cl)
+
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+    @provider = Chef::Provider::RemoteDirectory.new(@resource, @run_context)
+    @provider.current_resource = @resource.clone
+  end
+
+  describe "when the contents of the directory changed on the first run and not on the second run" do
+    before do
+      @resource_second_run = @resource.clone
+      @provider_second_run = Chef::Provider::RemoteDirectory.new(@resource_second_run, @run_context)
+      @provider.run_action(:create)
+      @provider_second_run.run_action(:create)
+    end
+    it "identifies that the state has changed the after first run" do
+      @provider_second_run.new_resource.updated_by_last_action? == true
+    end
+    it "identifies that the state has not changed after the second run" do
+      @provider_second_run.new_resource.updated_by_last_action? == false
+    end
+  end
+
+  describe "when access control is configured on the resource" do
+    before do
+      @resource.mode  "0750"
+      @resource.group "wheel"
+      @resource.owner "root"
+
+      @resource.files_mode  "0640"
+      @resource.files_group "staff"
+      @resource.files_owner "toor"
+      @resource.files_backup 23
+
+      @resource.source "remotedir_root"
+    end
+
+    it "configures access control on intermediate directorys" do
+      directory_resource = @provider.send(:resource_for_directory, File.join(Dir.tmpdir, "intermediate_dir"))
+      directory_resource.path.should  == File.join(Dir.tmpdir, "intermediate_dir")
+      directory_resource.mode.should  == "0750"
+      directory_resource.group.should == "wheel"
+      directory_resource.owner.should == "root"
+      directory_resource.recursive.should be_true
+    end
+
+    it "configures access control on files in the directory" do
+      @resource.cookbook "berlin_style_tasty_cupcakes"
+      cookbook_file = @provider.send(:cookbook_file_resource,
+                                    "/target/destination/path.txt",
+                                    "relative/source/path.txt")
+      cookbook_file.cookbook_name.should  == "berlin_style_tasty_cupcakes"
+      cookbook_file.source.should         == "remotedir_root/relative/source/path.txt"
+      cookbook_file.mode.should           == "0640"
+      cookbook_file.group.should          == "staff"
+      cookbook_file.owner.should          == "toor"
+      cookbook_file.backup.should         == 23
+    end
+  end
+
+  describe "when creating the remote directory" do
+    before do
+      @node.automatic_attrs[:platform] = :just_testing
+      @node.automatic_attrs[:platform_version] = :just_testing
+
+      @destination_dir = Dir.mktmpdir << "/remote_directory_test"
+      @resource.path(@destination_dir)
+    end
+
+    after {FileUtils.rm_rf(@destination_dir)}
+
+    # CHEF-3552
+    it "creates the toplevel directory without error " do
+      @resource.recursive(false)
+      @provider.run_action(:create)
+      ::File.exist?(@destination_dir).should be_true
+    end
+
+    it "transfers the directory with all contents" do
+      @provider.run_action(:create)
+      ::File.exist?(@destination_dir + '/remote_dir_file1.txt').should be_true
+      ::File.exist?(@destination_dir + '/remote_dir_file2.txt').should be_true
+      ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file1.txt').should be_true
+      ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file2.txt').should be_true
+      ::File.exist?(@destination_dir + '/remotesubdir/.a_dotfile').should be_true
+      ::File.exist?(@destination_dir + '/.a_dotdir/.a_dotfile_in_a_dotdir').should be_true
+    end
+
+    describe "only if it is missing" do
+      it "should not overwrite existing files" do
+        @resource.overwrite(true)
+        @provider.run_action(:create)
+
+        File.open(@destination_dir + '/remote_dir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+        File.open(@destination_dir + '/remotesubdir/remote_subdir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+        file1md5 = Digest::MD5.hexdigest(File.read(@destination_dir + '/remote_dir_file1.txt'))
+        subdirfile1md5 = Digest::MD5.hexdigest(File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))
+
+        @provider.run_action(:create_if_missing)
+
+        file1md5.eql?(Digest::MD5.hexdigest(File.read(@destination_dir + '/remote_dir_file1.txt'))).should be_true
+        subdirfile1md5.eql?(Digest::MD5.hexdigest(File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))).should be_true
+      end
+    end
+
+    describe "with purging enabled" do
+      before {@resource.purge(true)}
+
+      it "removes existing files if purge is true" do
+        @provider.run_action(:create)
+        FileUtils.touch(@destination_dir + '/marked_for_death.txt')
+        FileUtils.touch(@destination_dir + '/remotesubdir/marked_for_death_again.txt')
+        @provider.run_action(:create)
+
+        ::File.exist?(@destination_dir + '/remote_dir_file1.txt').should be_true
+        ::File.exist?(@destination_dir + '/remote_dir_file2.txt').should be_true
+        ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file1.txt').should be_true
+        ::File.exist?(@destination_dir + '/remotesubdir/remote_subdir_file2.txt').should be_true
+
+        ::File.exist?(@destination_dir + '/marked_for_death.txt').should be_false
+        ::File.exist?(@destination_dir + '/remotesubdir/marked_for_death_again.txt').should be_false
+      end
+
+      it "removes files in subdirectories before files above" do
+        @provider.run_action(:create)
+        FileUtils.mkdir_p(@destination_dir + '/a/multiply/nested/directory/')
+        FileUtils.touch(@destination_dir + '/a/foo.txt')
+        FileUtils.touch(@destination_dir + '/a/multiply/bar.txt')
+        FileUtils.touch(@destination_dir + '/a/multiply/nested/baz.txt')
+        FileUtils.touch(@destination_dir + '/a/multiply/nested/directory/qux.txt')
+        @provider.run_action(:create)
+        ::File.exist?(@destination_dir + '/a/foo.txt').should be_false
+        ::File.exist?(@destination_dir + '/a/multiply/bar.txt').should be_false
+        ::File.exist?(@destination_dir + '/a/multiply/nested/baz.txt').should be_false
+        ::File.exist?(@destination_dir + '/a/multiply/nested/directory/qux.txt').should be_false
+      end
+
+      it "removes directory symlinks properly", :not_supported_on_win2k3 do
+        symlinked_dir_path = @destination_dir + '/symlinked_dir'
+        @provider.action = :create
+        @provider.run_action
+
+        @fclass = Chef::CFCCheck.new
+
+        Dir.mktmpdir do |tmp_dir|
+          begin
+            @fclass.file_class.symlink(tmp_dir.dup, symlinked_dir_path)
+            ::File.exist?(symlinked_dir_path).should be_true
+
+            @provider.run_action
+
+            ::File.exist?(symlinked_dir_path).should be_false
+            ::File.exist?(tmp_dir).should be_true
+          rescue Chef::Exceptions::Win32APIError => e
+            pending "This must be run as an Administrator to create symlinks"
+          end
+        end
+      end
+    end
+
+    describe "with overwrite disabled" do
+      before {@resource.purge(false)}
+      before {@resource.overwrite(false)}
+
+      it "leaves modifications alone" do
+        @provider.run_action(:create)
+        ::File.open(@destination_dir + '/remote_dir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+        ::File.open(@destination_dir + '/remotesubdir/remote_subdir_file1.txt', 'a') {|f| f.puts "blah blah blah" }
+        file1md5 = Digest::MD5.hexdigest(::File.read(@destination_dir + '/remote_dir_file1.txt'))
+        subdirfile1md5 = Digest::MD5.hexdigest(::File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))
+        @provider.run_action(:create)
+        file1md5.eql?(Digest::MD5.hexdigest(::File.read(@destination_dir + '/remote_dir_file1.txt'))).should be_true
+        subdirfile1md5.eql?(Digest::MD5.hexdigest(::File.read(@destination_dir + '/remotesubdir/remote_subdir_file1.txt'))).should be_true
+      end
+    end
+
+  end
+end
+
diff --git a/spec/unit/provider/remote_file/cache_control_data_spec.rb b/spec/unit/provider/remote_file/cache_control_data_spec.rb
new file mode 100644
index 0000000..2ae3c41
--- /dev/null
+++ b/spec/unit/provider/remote_file/cache_control_data_spec.rb
@@ -0,0 +1,211 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'uri'
+
+CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH = 64
+CACHE_FILE_MD5_HEX_LENGTH = 32
+CACHE_FILE_JSON_FILE_EXTENSION_LENGTH = 5
+CACHE_FILE_PATH_LIMIT =
+  CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH +
+  1 +
+  CACHE_FILE_MD5_HEX_LENGTH +
+  CACHE_FILE_JSON_FILE_EXTENSION_LENGTH # {friendly}-{md5hex}.json == 102
+
+describe Chef::Provider::RemoteFile::CacheControlData do
+
+  let(:uri) { URI.parse("http://www.google.com/robots.txt") }
+
+  subject(:cache_control_data) do
+    Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, current_file_checksum)
+  end
+
+  let(:cache_path) { "remote_file/http___www_google_com_robots_txt-9839677abeeadf0691026e0cabca2339.json" }
+
+  # the checksum of the file we have on disk already
+  let(:current_file_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }
+
+  context "when loading data for an unknown URI" do
+
+    before do
+      Chef::FileCache.should_receive(:load).with(cache_path).and_raise(Chef::Exceptions::FileNotFound, "nope")
+    end
+
+    context "and there is no current copy of the file" do
+      let(:current_file_checksum) { nil }
+
+      it "returns empty cache control data" do
+        cache_control_data.etag.should be_nil
+        cache_control_data.mtime.should be_nil
+      end
+    end
+
+    it "returns empty cache control data" do
+      cache_control_data.etag.should be_nil
+      cache_control_data.mtime.should be_nil
+    end
+
+    context "and the URI contains a password" do
+
+      let(:uri) { URI.parse("http://bob:password@example.org/") }
+      let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" }
+
+      it "loads the cache data from a path based on a sanitized URI" do
+        Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, current_file_checksum)
+      end
+    end
+  end
+
+  describe "when loading data for a known URI" do
+
+    # the checksum of the file last we fetched it.
+    let(:last_fetched_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" }
+
+    let(:etag) { "\"a-strong-identifier\"" }
+    let(:mtime) { "Tue, 21 May 2013 19:19:23 GMT" }
+
+    let(:cache_json_data) do
+      cache = {}
+      cache["etag"] = etag
+      cache["mtime"] = mtime
+      cache["checksum"] = last_fetched_checksum
+      cache.to_json
+    end
+
+    before do
+      Chef::FileCache.should_receive(:load).with(cache_path).and_return(cache_json_data)
+    end
+
+    context "and there is no on-disk copy of the file" do
+      let(:current_file_checksum) { nil }
+
+      it "returns empty cache control data" do
+        cache_control_data.etag.should be_nil
+        cache_control_data.mtime.should be_nil
+      end
+    end
+
+    context "and the cached checksum does not match the on-disk copy" do
+      let(:current_file_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" }
+
+      it "returns empty cache control data" do
+        cache_control_data.etag.should be_nil
+        cache_control_data.mtime.should be_nil
+      end
+    end
+
+    context "and the cached checksum matches the on-disk copy" do
+
+      it "populates the cache control data" do
+        cache_control_data.etag.should == etag
+        cache_control_data.mtime.should == mtime
+      end
+    end
+
+    context "and the cached checksum data is corrupted" do
+      let(:cache_json_data) { '{"foo",,"bar" []}' }
+
+      it "returns empty cache control data" do
+        cache_control_data.etag.should be_nil
+        cache_control_data.mtime.should be_nil
+      end
+    end
+  end
+
+  describe "when saving to disk" do
+
+    let(:etag) { "\"a-strong-identifier\"" }
+    let(:mtime) { "Tue, 21 May 2013 19:19:23 GMT" }
+    let(:fetched_file_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" }
+
+    let(:expected_serialization_data) do
+      data = {}
+      data["etag"] = etag
+      data["mtime"] = mtime
+      data["checksum"] = fetched_file_checksum
+      data
+    end
+
+    before do
+      cache_control_data.etag = etag
+      cache_control_data.mtime = mtime
+      cache_control_data.checksum = fetched_file_checksum
+    end
+
+    it "serializes its attributes to JSON" do
+      # we have to test this separately because ruby 1.8 hash order is unstable
+      # so we can't count on the order of the keys in the json format.
+
+      json_data = cache_control_data.json_data
+      Chef::JSONCompat.from_json(json_data).should == expected_serialization_data
+    end
+
+    it "writes data to the cache" do
+      json_data = cache_control_data.json_data
+      Chef::FileCache.should_receive(:store).with(cache_path, json_data)
+      cache_control_data.save
+    end
+
+    context "and the URI contains a password" do
+
+      let(:uri) { URI.parse("http://bob:password@example.org/") }
+      let(:cache_path) { "remote_file/http___bob_XXXX_example_org_-f121caacb74c05a35bcefdf578ed5fc9.json" }
+
+      it "writes the data to the cache with a sanitized path name" do
+        json_data = cache_control_data.json_data
+        Chef::FileCache.should_receive(:store).with(cache_path, json_data)
+        cache_control_data.save
+      end
+    end
+
+    # Cover the very long remote file path case -- see CHEF-4422 where
+    # local cache file names generated from the long uri exceeded
+    # local file system path limits resulting in exceptions from
+    # file system API's on both Windows and Unix systems.
+    context "and the URI results in a file cache path that exceeds #{CACHE_FILE_PATH_LIMIT} characters in length" do
+      let(:long_remote_path) { "http://www.bing.com/" +  ('0' * (CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH * 2 )) }
+      let(:uri) { URI.parse(long_remote_path) }
+      let(:truncated_remote_uri) { URI.parse(long_remote_path[0...CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH]) }
+      let(:truncated_file_cache_path) do
+        cache_control_data_truncated = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(truncated_remote_uri, current_file_checksum)
+        cache_control_data_truncated.send('sanitized_cache_file_basename')[0...CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH]
+      end
+
+      it "truncates the file cache path to 102 characters" do
+        normalized_cache_path = cache_control_data.send('sanitized_cache_file_basename')
+
+        Chef::FileCache.should_receive(:store).with("remote_file/" + normalized_cache_path, cache_control_data.json_data)              
+
+        cache_control_data.save
+
+        normalized_cache_path.length.should == CACHE_FILE_PATH_LIMIT
+      end
+
+      it "uses a file cache path that starts with the first #{CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH} characters of the URI" do
+        normalized_cache_path = cache_control_data.send('sanitized_cache_file_basename')
+
+        truncated_file_cache_path.length.should == CACHE_FILE_TRUNCATED_FRIENDLY_FILE_NAME_LENGTH
+        normalized_cache_path.start_with?(truncated_file_cache_path).should == true
+      end
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/provider/remote_file/content_spec.rb b/spec/unit/provider/remote_file/content_spec.rb
new file mode 100644
index 0000000..36ecfe1
--- /dev/null
+++ b/spec/unit/provider/remote_file/content_spec.rb
@@ -0,0 +1,230 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::RemoteFile::Content do
+
+  #
+  # mock setup
+  #
+
+  let(:current_resource) do
+    Chef::Resource::RemoteFile.new("remote-file-content-spec (current resource)")
+  end
+
+  let(:source) { [ "http://opscode.com/seattle.txt" ] }
+
+  let(:new_resource) do
+    r = Chef::Resource::RemoteFile.new("remote-file-content-spec (current resource)")
+    r.source(source)
+    r
+  end
+
+  let(:run_context) { mock("Chef::RunContext") }
+
+  #
+  # subject
+  #
+  let(:content) do
+    Chef::Provider::RemoteFile::Content.new(new_resource, current_resource, run_context)
+  end
+
+  describe "when the checksum of the current_resource matches the checksum set on the resource" do
+    before do
+      new_resource.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+      current_resource.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+    end
+
+    it "should return nil for the tempfile" do
+      content.tempfile.should be_nil
+    end
+
+    it "should not call any fetcher" do
+      Chef::Provider::RemoteFile::Fetcher.should_not_receive(:for_resource)
+    end
+  end
+
+  describe "when the checksum of the current_resource is a partial match for the checksum set on the resource" do
+    before do
+      new_resource.stub!(:checksum).and_return("0fd012fd")
+      current_resource.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+    end
+
+    it "should return nil for the tempfile" do
+      content.tempfile.should be_nil
+    end
+
+    it "should not call any fetcher" do
+      Chef::Provider::RemoteFile::Fetcher.should_not_receive(:for_resource)
+    end
+  end
+
+  shared_examples_for "the resource needs fetching" do
+    before do
+      # FIXME: test one or the other nil, test both not nil and not equal, abuse the regexp a little
+      @uri = mock("URI")
+      URI.should_receive(:parse).with(new_resource.source[0]).and_return(@uri)
+    end
+
+    describe "when the fetcher returns nil for the tempfile" do
+      before do
+        http_fetcher = mock("Chef::Provider::RemoteFile::HTTP", :fetch => nil)
+        Chef::Provider::RemoteFile::Fetcher.should_receive(:for_resource).with(@uri, new_resource, current_resource).and_return(http_fetcher)
+      end
+
+      it "should return nil for the tempfile" do
+        content.tempfile.should be_nil
+      end
+    end
+
+    describe "when the fetcher returns a valid tempfile" do
+
+      let(:mtime) { Time.now }
+      let(:tempfile) { mock("Tempfile") }
+      let(:http_fetcher) { mock("Chef::Provider::RemoteFile::HTTP", :fetch => tempfile) }
+
+      before do
+        Chef::Provider::RemoteFile::Fetcher.should_receive(:for_resource).with(@uri, new_resource, current_resource).and_return(http_fetcher)
+      end
+
+      it "should return the tempfile object to the caller" do
+        content.tempfile.should == tempfile
+      end
+
+    end
+  end
+  describe "when the checksum are both nil" do
+    before do
+      new_resource.checksum.should be_nil
+      current_resource.checksum.should be_nil
+    end
+    it_behaves_like "the resource needs fetching"
+  end
+
+  describe "when the current_resource checksum is nil" do
+    before do
+      new_resource.stub!(:checksum).and_return("fd012fd")
+      current_resource.stub!(:checksum).and_return(nil)
+    end
+    it_behaves_like "the resource needs fetching"
+  end
+
+  describe "when the new_resource checksum is nil" do
+    before do
+      new_resource.stub!(:checksum).and_return(nil)
+      current_resource.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+    end
+    it_behaves_like "the resource needs fetching"
+  end
+
+  describe "when the checksums are a partial match, but not to the leading portion" do
+    before do
+      new_resource.stub!(:checksum).and_return("fd012fd")
+      current_resource.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa")
+    end
+    it_behaves_like "the resource needs fetching"
+  end
+
+
+  describe "when the fetcher throws an exception" do
+    before do
+      new_resource.stub!(:checksum).and_return(nil)
+      current_resource.stub!(:checksum).and_return(nil)
+      @uri = mock("URI")
+      URI.should_receive(:parse).with(new_resource.source[0]).and_return(@uri)
+      http_fetcher = mock("Chef::Provider::RemoteFile::HTTP")
+      http_fetcher.should_receive(:fetch).and_raise(Errno::ECONNREFUSED)
+      Chef::Provider::RemoteFile::Fetcher.should_receive(:for_resource).with(@uri, new_resource, current_resource).and_return(http_fetcher)
+    end
+
+    it "should propagate the error back to the caller" do
+      lambda { content.tempfile }.should raise_error(Errno::ECONNREFUSED)
+    end
+  end
+
+  describe "when there is an array of sources and the first fails" do
+
+    let(:source) { [ "http://opscode.com/seattle.txt", "http://opscode.com/nyc.txt" ] }
+    before do
+      new_resource.stub!(:checksum).and_return(nil)
+      current_resource.stub!(:checksum).and_return(nil)
+      @uri0 = mock("URI0")
+      @uri1 = mock("URI1")
+      URI.should_receive(:parse).with(new_resource.source[0]).and_return(@uri0)
+      URI.should_receive(:parse).with(new_resource.source[1]).and_return(@uri1)
+      @http_fetcher_throws_exception = mock("Chef::Provider::RemoteFile::HTTP")
+      @http_fetcher_throws_exception.should_receive(:fetch).at_least(:once).and_raise(Errno::ECONNREFUSED)
+      Chef::Provider::RemoteFile::Fetcher.should_receive(:for_resource).with(@uri0, new_resource, current_resource).and_return(@http_fetcher_throws_exception)
+    end
+
+    describe "when the second url succeeds" do
+      before do
+        @tempfile = mock("Tempfile")
+        mtime = Time.now
+        http_fetcher_works = mock("Chef::Provider::RemoteFile::HTTP", :fetch => @tempfile)
+        Chef::Provider::RemoteFile::Fetcher.should_receive(:for_resource).with(@uri1, new_resource, current_resource).and_return(http_fetcher_works)
+      end
+
+      it "should return a valid tempfile" do
+        content.tempfile.should == @tempfile
+      end
+
+      it "should not mutate the new_resource" do
+        content.tempfile
+        new_resource.source.length.should == 2
+      end
+    end
+
+    describe "when both urls fail" do
+      before do
+        Chef::Provider::RemoteFile::Fetcher.should_receive(:for_resource).with(@uri1, new_resource, current_resource).and_return(@http_fetcher_throws_exception)
+      end
+
+      it "should propagate the error back to the caller" do
+        lambda { content.tempfile }.should raise_error(Errno::ECONNREFUSED)
+      end
+    end
+  end
+
+  describe "when there is an array of sources and the first succeeds" do
+    let(:source) { [ "http://opscode.com/seattle.txt", "http://opscode.com/nyc.txt" ] }
+    before do
+      new_resource.stub!(:checksum).and_return(nil)
+      current_resource.stub!(:checksum).and_return(nil)
+      @uri0 = mock("URI0")
+      URI.should_receive(:parse).with(new_resource.source[0]).and_return(@uri0)
+      URI.should_not_receive(:parse).with(new_resource.source[1])
+      @tempfile = mock("Tempfile")
+      mtime = Time.now
+      http_fetcher_works = mock("Chef::Provider::RemoteFile::HTTP", :fetch => @tempfile)
+      Chef::Provider::RemoteFile::Fetcher.should_receive(:for_resource).with(@uri0, new_resource, current_resource).and_return(http_fetcher_works)
+    end
+
+    it "should return a valid tempfile" do
+      content.tempfile.should == @tempfile
+    end
+
+    it "should not mutate the new_resource" do
+      content.tempfile
+      new_resource.source.length.should == 2
+    end
+  end
+
+end
+
diff --git a/spec/unit/provider/remote_file/fetcher_spec.rb b/spec/unit/provider/remote_file/fetcher_spec.rb
new file mode 100644
index 0000000..68802fb
--- /dev/null
+++ b/spec/unit/provider/remote_file/fetcher_spec.rb
@@ -0,0 +1,75 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::RemoteFile::Fetcher do
+
+  let(:current_resource) { mock("current resource") }
+  let(:new_resource) { mock("new resource") }
+  let(:fetcher_instance) { mock("fetcher") }
+
+  describe "when passed an http url" do
+    let(:uri) { mock("uri", :scheme => "http" ) }
+    before do
+      Chef::Provider::RemoteFile::HTTP.should_receive(:new).and_return(fetcher_instance)
+    end
+    it "returns an http fetcher" do
+      described_class.for_resource(uri, new_resource, current_resource).should == fetcher_instance
+    end
+  end
+
+  describe "when passed an https url" do
+    let(:uri) { mock("uri", :scheme => "https" ) }
+    before do
+      Chef::Provider::RemoteFile::HTTP.should_receive(:new).and_return(fetcher_instance)
+    end
+    it "returns an http fetcher" do
+      described_class.for_resource(uri, new_resource, current_resource).should == fetcher_instance
+    end
+  end
+
+  describe "when passed an ftp url" do
+    let(:uri) { mock("uri", :scheme => "ftp" ) }
+    before do
+      Chef::Provider::RemoteFile::FTP.should_receive(:new).and_return(fetcher_instance)
+    end
+    it "returns an ftp fetcher" do
+      described_class.for_resource(uri, new_resource, current_resource).should == fetcher_instance
+    end
+  end
+
+  describe "when passed a file url" do
+    let(:uri) { mock("uri", :scheme => "file" ) }
+    before do
+      Chef::Provider::RemoteFile::LocalFile.should_receive(:new).and_return(fetcher_instance)
+    end
+    it "returns a localfile fetcher" do
+      described_class.for_resource(uri, new_resource, current_resource).should == fetcher_instance
+    end
+  end
+
+  describe "when passed a url we do not recognize" do
+    let(:uri) { mock("uri", :scheme => "xyzzy" ) }
+    it "throws an ArgumentError exception" do
+      lambda { described_class.for_resource(uri, new_resource, current_resource) }.should raise_error(ArgumentError)
+    end
+  end
+
+end
+
diff --git a/spec/unit/provider/remote_file/ftp_spec.rb b/spec/unit/provider/remote_file/ftp_spec.rb
new file mode 100644
index 0000000..8dd1ef2
--- /dev/null
+++ b/spec/unit/provider/remote_file/ftp_spec.rb
@@ -0,0 +1,219 @@
+#
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# 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::FTP do
+  let(:enclosing_directory) {
+    canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates")))
+  }
+  let(:resource_path) {
+    canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt")))
+  }
+
+  let(:new_resource) do
+    r = Chef::Resource::RemoteFile.new("remote file ftp backend test (new resource)")
+    r.ftp_active_mode(false)
+    r.path(resource_path)
+    r
+  end
+
+  let(:current_resource) do
+    Chef::Resource::RemoteFile.new("remote file ftp backend test (current resource)'")
+  end
+
+  let(:ftp) do
+    ftp = mock(Net::FTP, { })
+    ftp.stub!(:connect)
+    ftp.stub!(:login)
+    ftp.stub!(:voidcmd)
+    ftp.stub!(:mtime).and_return(Time.now)
+    ftp.stub!(:getbinaryfile)
+    ftp.stub!(:close)
+    ftp.stub!(:passive=)
+    ftp
+  end
+
+  let(:tempfile_path) { "/tmp/somedir/remote-file-ftp-backend-spec-test" }
+
+  let(:tempfile) do
+    t = StringIO.new
+    t.stub(:path).and_return(tempfile_path)
+    t
+  end
+
+  let(:uri) { URI.parse("ftp://opscode.com/seattle.txt") }
+
+  before(:each) do
+    Net::FTP.stub!(:new).with().and_return(ftp)
+    Tempfile.stub!(:new).and_return(tempfile)
+  end
+
+  describe "when first created" do
+
+    it "throws an argument exception when no path is given" do
+      uri.path = ""
+      lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError)
+    end
+
+    it "throws an argument exception when only a / is given" do
+      uri.path = "/"
+      lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError)
+    end
+
+    it "throws an argument exception when no filename is given" do
+      uri.path = "/the/whole/path/"
+      lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError)
+    end
+
+    it "throws an argument exception when the typecode is invalid" do
+      uri.typecode = "d"
+      lambda { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }.should raise_error(ArgumentError)
+    end
+
+    it "does not use passive mode when new_resource sets ftp_active_mode to true" do
+      new_resource.ftp_active_mode(true)
+      fetcher = Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
+      fetcher.use_passive_mode?.should be_false
+    end
+
+    it "uses passive mode when new_resource sets ftp_active_mode to false" do
+      new_resource.ftp_active_mode(false)
+      fetcher = Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
+      fetcher.use_passive_mode?.should be_true
+    end
+  end
+
+  describe "when fetching the object" do
+
+    let(:cache_control_data) { Chef::Provider::RemoteFile::CacheControlData.new(uri) }
+    let(:current_resource_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" }
+
+    subject(:fetcher) { Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource) }
+
+    before do
+      current_resource.checksum(current_resource_checksum)
+      #Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data)
+    end
+
+    it "should connect to the host from the uri on the default port 21" do
+      ftp.should_receive(:connect).with("opscode.com", 21)
+      fetcher.fetch
+    end
+
+    it "should set passive true when ftp_active_mode is false" do
+      new_resource.ftp_active_mode(false)
+      ftp.should_receive(:passive=).with(true)
+      fetcher.fetch
+    end
+
+    it "should set passive false when ftp_active_mode is false" do
+      new_resource.ftp_active_mode(true)
+      ftp.should_receive(:passive=).with(false)
+      fetcher.fetch
+    end
+
+    it "should use anonymous ftp when no userinfo is provided" do
+      ftp.should_receive(:login).with("anonymous", nil)
+      fetcher.fetch
+    end
+
+    context "and the URI specifies an alternate port" do
+      let(:uri) { URI.parse("ftp://opscode.com:8021/seattle.txt") }
+
+      it "should connect on an alternate port when one is provided" do
+        uri = URI.parse("ftp://opscode.com:8021/seattle.txt")
+        ftp.should_receive(:connect).with("opscode.com", 8021)
+        fetcher.fetch
+      end
+
+    end
+
+    context "and the URI contains a username and password" do
+      let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt") }
+
+      it "should use authenticated ftp when userinfo is provided" do
+        ftp.should_receive(:login).with("the_user", "the_password")
+        fetcher.fetch
+      end
+    end
+
+    context "and the uri sets the typecode to ascii" do
+      let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt;type=a") }
+
+      it "fetches the file with ascii typecode set" do
+        ftp.should_receive(:voidcmd).with("TYPE A").once
+        fetcher.fetch
+      end
+
+    end
+
+    context "and the uri sets the typecode to image" do
+      let(:uri) { URI.parse("ftp://the_user:the_password@opscode.com/seattle.txt;type=i") }
+
+      it "should accept image for the typecode" do
+        ftp.should_receive(:voidcmd).with("TYPE I").once
+        fetcher.fetch
+      end
+
+    end
+
+    context "and the uri specifies a nested path" do
+      let(:uri) { URI.parse("ftp://opscode.com/the/whole/path/seattle.txt") }
+
+      it "should fetch the file from the correct path" do
+        ftp.should_receive(:voidcmd).with("CWD the").once
+        ftp.should_receive(:voidcmd).with("CWD whole").once
+        ftp.should_receive(:voidcmd).with("CWD path").once
+        ftp.should_receive(:getbinaryfile).with("seattle.txt", tempfile.path)
+        fetcher.fetch
+      end
+
+    end
+
+    context "when not using last modified based conditional fetching" do
+      before do
+        new_resource.use_last_modified(false)
+      end
+
+      it "should return a tempfile in the result" do
+        result = fetcher.fetch
+        result.should equal(tempfile)
+      end
+
+    end
+
+    context "and proxying is enabled" do
+      before do
+        Chef::Config[:ftp_proxy] = "socks5://socks.example.com:5000"
+        Chef::Config[:ftp_proxy_user] = "bill"
+        Chef::Config[:ftp_proxy_pass] = "ted"
+      end
+
+      it "fetches the file via the proxy" do
+        current_socks_server = ENV["SOCKS_SERVER"]
+        ENV.should_receive(:[]=).with("SOCKS_SERVER", "socks5://bill:ted at socks.example.com:5000").ordered
+        ENV.should_receive(:[]=).with("SOCKS_SERVER", current_socks_server).ordered
+        result = fetcher.fetch
+        result.should equal(tempfile)
+      end
+
+    end
+
+  end
+end
diff --git a/spec/unit/provider/remote_file/http_spec.rb b/spec/unit/provider/remote_file/http_spec.rb
new file mode 100644
index 0000000..e763a60
--- /dev/null
+++ b/spec/unit/provider/remote_file/http_spec.rb
@@ -0,0 +1,303 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Lamont Granquist
+# 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::HTTP do
+
+  let(:uri) { URI.parse("http://opscode.com/seattle.txt") }
+
+  let(:existing_file_source) { nil }
+
+  let(:current_resource_checksum) { "41e78735319af11327e9d2ca8535ea1c191e5ac1f76bb08d88fe6c3f93a8c8e5" }
+
+  let(:current_resource) do
+    current_resource = Chef::Resource::RemoteFile.new("/tmp/foo.txt")
+    current_resource.source(existing_file_source) if existing_file_source
+    current_resource.checksum(current_resource_checksum)
+    current_resource
+  end
+
+  let(:new_resource) do
+    Chef::Resource::RemoteFile.new("/tmp/foo.txt")
+  end
+
+  subject(:fetcher) do
+    Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
+  end
+
+  let(:cache_control_data) { Chef::Provider::RemoteFile::CacheControlData.new(uri) }
+
+  describe "generating cache control headers" do
+
+    context "and there is no valid cache control data for this URI on disk" do
+
+      before do
+        Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data)
+      end
+
+      it "does not add conditional GET headers" do
+        fetcher.conditional_get_headers.should == {}
+      end
+
+      context "and the resource specifies custom headers" do
+        before do
+          new_resource.headers("x-myapp-header" => "custom-header-value")
+        end
+
+        it "has the user-specified custom headers" do
+          fetcher.headers.should == {"x-myapp-header" => "custom-header-value"}
+        end
+      end
+
+    end
+
+    context "and the cache control data matches the existing file" do
+
+      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
+      let(:etag) { "\"a-strong-unique-identifier\"" }
+
+      # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
+      let(:mtime) { "Tue, 21 May 2013 19:19:23 GMT" }
+
+      before do
+        cache_control_data.etag = etag
+        cache_control_data.mtime = mtime
+
+        Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data)
+      end
+
+      context "and no conditional get features are enabled" do
+        before do
+          new_resource.use_conditional_get(false)
+        end
+
+        it "does not add headers to the request" do
+          fetcher.headers.should == {}
+        end
+      end
+
+      context "and conditional get is enabled" do
+        before do
+          new_resource.use_conditional_get(true)
+        end
+
+        it "adds If-None-Match and If-Modified-Since headers to the request" do
+          headers = fetcher.headers
+          headers["if-none-match"].should == etag
+          headers["if-modified-since"].should == mtime
+        end
+
+        context "and custom headers are provided" do
+          before do
+            new_resource.headers("x-myapp-header" => "app-specific-header",
+                                 "if-none-match" => "custom-etag",
+                                 "if-modified-since" => "custom-last-modified")
+          end
+
+          it "preserves non-conflicting headers" do
+            fetcher.headers["x-myapp-header"].should == "app-specific-header"
+          end
+
+          it "prefers user-supplied cache control headers" do
+            headers = fetcher.headers
+            headers["if-none-match"].should == "custom-etag"
+            headers["if-modified-since"].should == "custom-last-modified"
+          end
+        end
+
+      end
+
+      context "and etag support is enabled" do
+        before do
+          new_resource.use_conditional_get(false)
+          new_resource.use_etags(true)
+        end
+
+        it "only adds If-None-Match headers to the request" do
+          headers = fetcher.headers
+          headers["if-none-match"].should == etag
+          headers.should_not have_key("if-modified-since")
+        end
+      end
+
+      context "and mtime support is enabled" do
+        before do
+          new_resource.use_conditional_get(false)
+          new_resource.use_last_modified(true)
+        end
+
+        it "only adds If-Modified-Since headers to the request" do
+          headers = fetcher.headers
+          headers["if-modified-since"].should == mtime
+          headers.should_not have_key("if-none-match")
+        end
+      end
+    end
+
+  end
+
+  describe "when fetching the uri" do
+
+    let(:expected_http_opts) { {} }
+    let(:expected_http_args) { [uri, expected_http_opts] }
+
+    let(:tempfile_path) { "/tmp/chef-mock-tempfile-abc123" }
+
+    let(:tempfile) { mock(Tempfile, :path => tempfile_path, :close => nil) }
+
+    let(:last_response) { {} }
+
+    let(:rest) do
+      rest = mock(Chef::HTTP::Simple)
+      rest.stub!(:streaming_request).and_return(tempfile)
+      rest.stub!(:last_response).and_return(last_response)
+      rest
+    end
+
+    before do
+      new_resource.headers({})
+      new_resource.use_last_modified(false)
+      Chef::Provider::RemoteFile::CacheControlData.should_receive(:load_and_validate).with(uri, current_resource_checksum).and_return(cache_control_data)
+
+      Chef::HTTP::Simple.should_receive(:new).with(*expected_http_args).and_return(rest)
+    end
+
+
+    describe "and the request does not return new content" do
+
+      it "should return a nil tempfile for a 304 HTTPNotModifed" do
+        # Streaming request returns nil for 304 errors
+        rest.stub(:streaming_request).and_return(nil)
+        fetcher.fetch.should be_nil
+      end
+
+    end
+
+    describe "and the request returns new content" do
+
+      let(:fetched_content_checksum) { "e2a8938cc31754f6c067b35aab1d0d4864272e9bf8504536ef3e79ebf8432305" }
+
+      before do
+        cache_control_data.should_receive(:save)
+        Chef::Digester.should_receive(:checksum_for_file).with(tempfile_path).and_return(fetched_content_checksum)
+      end
+
+      it "should return a tempfile" do
+        result = fetcher.fetch
+        result.should == tempfile
+        cache_control_data.etag.should be_nil
+        cache_control_data.mtime.should be_nil
+        cache_control_data.checksum.should == fetched_content_checksum
+      end
+
+      context "and the response does not contain an etag" do
+        let(:last_response) { {"etag" => nil} }
+        it "does not include an etag in the result" do
+          fetcher.fetch
+          cache_control_data.etag.should be_nil
+          cache_control_data.mtime.should be_nil
+          cache_control_data.checksum.should == fetched_content_checksum
+        end
+      end
+
+      context "and the response has an etag header" do
+        let(:last_response) { {"etag" => "abc123"} }
+
+        it "includes the etag value in the response" do
+          fetcher.fetch
+          cache_control_data.etag.should == "abc123"
+          cache_control_data.mtime.should be_nil
+          cache_control_data.checksum.should == fetched_content_checksum
+        end
+
+      end
+
+      context "and the response has no Date or Last-Modified header" do
+        let(:last_response) { {"date" => nil, "last_modified" => nil} }
+        it "does not set an mtime in the result" do
+          # RFC 2616 suggests that servers that do not set a Date header do not
+          # have a reliable clock, so no use in making them deal with dates.
+          fetcher.fetch
+          cache_control_data.etag.should be_nil
+          cache_control_data.mtime.should be_nil
+          cache_control_data.checksum.should == fetched_content_checksum
+        end
+      end
+
+      context "and the response has a Last-Modified header" do
+        let(:last_response) do
+          # Last-Modified should be preferred to Date if both are set
+          {"date" => "Fri, 17 May 2013 23:23:23 GMT", "last_modified" => "Fri, 17 May 2013 11:11:11 GMT"}
+        end
+
+        it "sets the mtime to the Last-Modified time in the response" do
+          fetcher.fetch
+          cache_control_data.etag.should be_nil
+          cache_control_data.mtime.should == last_response["last_modified"]
+        end
+      end
+
+      context "and the response has a Date header but no Last-Modified header" do
+        let(:last_response) do
+          {"date" => "Fri, 17 May 2013 23:23:23 GMT", "last_modified" => nil}
+        end
+
+        it "sets the mtime to the Date in the response" do
+          fetcher.fetch
+          cache_control_data.etag.should be_nil
+          cache_control_data.mtime.should == last_response["date"]
+          cache_control_data.checksum.should == fetched_content_checksum
+        end
+
+      end
+
+      context "and the target file is a tarball [CHEF-3140]" do
+
+        let(:uri) { URI.parse("http://opscode.com/tarball.tgz") }
+        let(:expected_http_opts) { {:disable_gzip => true} }
+
+        # CHEF-3140
+        # Some servers return tarballs as content type tar and encoding gzip, which
+        # is totally wrong. When this happens and gzip isn't disabled, Chef::HTTP::Simple
+        # will decompress the file for you, which is not at all what you expected
+        # to happen (you end up with an uncomressed tar archive instead of the
+        # gzipped tar archive you expected). To work around this behavior, we
+        # detect when users are fetching gzipped files and turn off gzip in
+        # Chef::HTTP::Simple.
+
+        it "should disable gzip compression in the client" do
+          # Before block in the parent context has set an expectation on
+          # Chef::HTTP::Simple.new() being called with expected arguments. Here we fufil
+          # that expectation, so that we can explicitly set it for this test.
+          # This is intended to provide insurance that refactoring of the parent
+          # context does not negate the value of this particular example.
+          Chef::HTTP::Simple.new(*expected_http_args)
+          Chef::HTTP::Simple.should_receive(:new).once.with(*expected_http_args).and_return(rest)
+          fetcher.fetch
+          cache_control_data.etag.should be_nil
+          cache_control_data.mtime.should be_nil
+          cache_control_data.checksum.should == fetched_content_checksum
+        end
+      end
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/provider/remote_file/local_file_spec.rb b/spec/unit/provider/remote_file/local_file_spec.rb
new file mode 100644
index 0000000..00634f5
--- /dev/null
+++ b/spec/unit/provider/remote_file/local_file_spec.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Jesse Campbell (<hikeit at gmail.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# 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::LocalFile do
+
+  let(:uri) { URI.parse("file:///nyan_cat.png") }
+
+  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 first created" do
+
+    it "stores the uri it is passed" do
+      fetcher.uri.should == uri
+    end
+
+    it "stores the new_resource" do
+      fetcher.new_resource.should == new_resource
+    end
+
+  end
+
+  describe "when fetching the object" do
+
+    let(:tempfile) { mock("Tempfile", :path => "/tmp/foo/bar/nyan.png", :close => nil) }
+    let(:chef_tempfile) { mock("Chef::FileContentManagement::Tempfile", :tempfile => tempfile) }
+
+    before do
+      current_resource.source("file:///nyan_cat.png")
+    end
+
+    it "stages the local file to a temporary file" do
+      Chef::FileContentManagement::Tempfile.should_receive(:new).with(new_resource).and_return(chef_tempfile)
+      ::FileUtils.should_receive(:cp).with(uri.path, tempfile.path)
+      tempfile.should_receive(:close)            
+
+      result = fetcher.fetch
+      result.should == tempfile
+    end
+
+  end
+
+end
diff --git a/spec/unit/provider/remote_file_spec.rb b/spec/unit/provider/remote_file_spec.rb
new file mode 100644
index 0000000..3b01b45
--- /dev/null
+++ b/spec/unit/provider/remote_file_spec.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2008-2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'support/shared/unit/provider/file'
+
+
+describe Chef::Provider::RemoteFile do
+  let(:resource) do
+    resource = Chef::Resource::RemoteFile.new("seattle", @run_context)
+    resource.path(resource_path)
+    resource.source("http://foo")
+    resource.cookbook_name = "monkey"
+    resource
+  end
+
+  let(:content) do
+    content = mock('Chef::Provider::File::Content::RemoteFile')
+  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(:enclosing_directory) {
+    canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates")))
+  }
+  let(:resource_path) {
+    canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt")))
+  }
+
+  subject(:provider) do
+    provider = described_class.new(resource, run_context)
+    provider.stub!(:content).and_return(content)
+    provider.stub!(:update_new_resource_checksum).and_return(nil) # Otherwise it doesn't behave like a File provider
+    provider
+  end
+
+  before do
+    Chef::FileCache.stub!(:load).with("remote_file/#{resource.name}").and_raise(Chef::Exceptions::FileNotFound)
+  end
+
+  it_behaves_like Chef::Provider::File
+
+end
+
diff --git a/spec/unit/provider/route_spec.rb b/spec/unit/provider/route_spec.rb
new file mode 100644
index 0000000..863f126
--- /dev/null
+++ b/spec/unit/provider/route_spec.rb
@@ -0,0 +1,243 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Route do
+  before do
+    @node = Chef::Node.new
+    @cookbook_collection = Chef::CookbookCollection.new([])
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+
+    @new_resource = Chef::Resource::Route.new('10.0.0.10')
+    @new_resource.gateway "10.0.0.9"
+    @current_resource = Chef::Resource::Route.new('10.0.0.10')
+    @current_resource.gateway "10.0.0.9"
+
+    @provider = Chef::Provider::Route.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  describe Chef::Provider::Route, "hex2ip" do
+    it "should return nil if ip address is invalid" do
+      @provider.hex2ip('foo').should be_nil # does not even look like an ip
+      @provider.hex2ip('ABCDEFGH').should be_nil # 8 chars, but invalid
+    end
+
+    it "should return quad-dotted notation for a valid IP" do
+      @provider.hex2ip('01234567').should == '103.69.35.1'
+      @provider.hex2ip('0064a8c0').should == '192.168.100.0'
+      @provider.hex2ip('00FFFFFF').should == '255.255.255.0'
+    end
+  end
+
+
+  describe Chef::Provider::Route, "load_current_resource" do
+    context "on linux" do
+      before do
+        @node.automatic_attrs[:os] = 'linux'
+        routing_table = "Iface	Destination	Gateway 	Flags	RefCnt	Use	Metric	Mask		MTU	Window	IRTT\n" +
+                        "eth0	0064A8C0	0984A8C0	0003	0	0	0	00FFFFFF	0	0	0\n"
+        route_file = StringIO.new(routing_table)
+        File.stub!(:open).with("/proc/net/route", "r").and_return(route_file)
+      end
+
+      it "should set is_running to false when a route is not detected" do
+        resource = Chef::Resource::Route.new('10.10.10.0/24')
+        resource.stub!(:gateway).and_return("10.0.0.1")
+        resource.stub!(:device).and_return("eth0")
+        provider = Chef::Provider::Route.new(resource, @run_context)
+
+        provider.load_current_resource
+        provider.is_running.should be_false
+      end
+
+      it "should detect existing routes and set is_running attribute correctly" do
+        resource = Chef::Resource::Route.new('192.168.100.0/24')
+        resource.stub!(:gateway).and_return("192.168.132.9")
+        resource.stub!(:device).and_return("eth0")
+        provider = Chef::Provider::Route.new(resource, @run_context)
+
+        provider.load_current_resource
+        provider.is_running.should be_true
+      end
+
+      it "should use gateway value when matching routes" do
+        resource = Chef::Resource::Route.new('192.168.100.0/24')
+        resource.stub!(:gateway).and_return("10.10.10.10")
+        resource.stub!(:device).and_return("eth0")
+        provider = Chef::Provider::Route.new(resource, @run_context)
+
+        provider.load_current_resource
+        provider.is_running.should be_false
+      end
+    end
+  end
+
+  describe Chef::Provider::Route, "action_add" do
+    it "should add the route if it does not exist" do
+      @provider.stub!(:run_command).and_return(true)
+      @current_resource.stub!(:gateway).and_return(nil)
+      @provider.should_receive(:generate_command).once.with(:add)
+      @provider.should_receive(:generate_config)
+      @provider.run_action(:add)
+      @new_resource.should be_updated
+    end
+
+    it "should not add the route if it exists" do
+      @provider.stub!(:run_command).and_return(true)
+      @provider.stub!(:is_running).and_return(true)
+      @provider.should_not_receive(:generate_command).with(:add)
+      @provider.should_receive(:generate_config)
+      @provider.run_action(:add)
+      @new_resource.should_not be_updated
+    end
+
+    it "should not delete config file for :add action (CHEF-3332)" do
+      @node.automatic_attrs[:platform] = 'centos'
+
+      route_file = StringIO.new
+      File.should_receive(:new).and_return(route_file)
+      @resource_add = Chef::Resource::Route.new('192.168.1.0/24 via 192.168.0.1')
+      @run_context.resource_collection << @resource_add
+      @provider.stub!(:run_command).and_return(true)
+
+      @resource_add.action(:add)
+      @provider.run_action(:add)
+      route_file.string.split("\n").should have(1).items
+      route_file.string.should match(/^192\.168\.1\.0\/24 via 192\.168\.0\.1$/)
+    end
+  end
+
+  describe Chef::Provider::Route, "action_delete" do
+    it "should delete the route if it exists" do
+      @provider.stub!(:run_command).and_return(true)
+      @provider.should_receive(:generate_command).once.with(:delete)
+      @provider.stub!(:is_running).and_return(true)
+      @provider.run_action(:delete)
+      @new_resource.should be_updated
+    end
+
+    it "should not delete the route if it does not exist" do
+      @current_resource.stub!(:gateway).and_return(nil)
+      @provider.stub!(:run_command).and_return(true)
+      @provider.should_not_receive(:generate_command).with(:add)
+      @provider.run_action(:delete)
+      @new_resource.should_not be_updated
+    end
+  end
+
+  describe Chef::Provider::Route, "generate_command for action_add" do
+    it "should include a netmask when a one is specified" do
+      @new_resource.stub!(:netmask).and_return('255.255.0.0')
+      @provider.generate_command(:add).should match(/\/\d{1,2}\s/)
+    end
+
+    it "should not include a netmask when a one is specified" do
+      @new_resource.stub!(:netmask).and_return(nil)
+      @provider.generate_command(:add).should_not match(/\/\d{1,2}\s/)
+    end
+
+    it "should include ' via $gateway ' when a gateway is specified" do
+      @provider.generate_command(:add).should match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/)
+    end
+
+    it "should not include ' via $gateway ' when a gateway is not specified" do
+      @new_resource.stub!(:gateway).and_return(nil)
+      @provider.generate_command(:add).should_not match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/)
+    end
+  end
+
+  describe Chef::Provider::Route, "generate_command for action_delete" do
+    it "should include a netmask when a one is specified" do
+      @new_resource.stub!(:netmask).and_return('255.255.0.0')
+      @provider.generate_command(:delete).should match(/\/\d{1,2}\s/)
+    end
+
+    it "should not include a netmask when a one is specified" do
+      @new_resource.stub!(:netmask).and_return(nil)
+      @provider.generate_command(:delete).should_not match(/\/\d{1,2}\s/)
+    end
+
+    it "should include ' via $gateway ' when a gateway is specified" do
+      @provider.generate_command(:delete).should match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/)
+    end
+
+    it "should not include ' via $gateway ' when a gateway is not specified" do
+      @new_resource.stub!(:gateway).and_return(nil)
+      @provider.generate_command(:delete).should_not match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\s/)
+    end
+  end
+
+  describe Chef::Provider::Route, "config_file_contents for action_add" do
+    it "should include a netmask when a one is specified" do
+      @new_resource.stub!(:netmask).and_return('255.255.0.0')
+      @provider.config_file_contents(:add, { :target => @new_resource.target, :netmask => @new_resource.netmask}).should match(/\/\d{1,2}.*\n$/)
+    end
+
+    it "should not include a netmask when a one is specified" do
+      @provider.config_file_contents(:add, { :target => @new_resource.target}).should_not match(/\/\d{1,2}.*\n$/)
+    end
+
+    it "should include ' via $gateway ' when a gateway is specified" do
+      @provider.config_file_contents(:add, { :target => @new_resource.target, :gateway => @new_resource.gateway}).should match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\n/)
+    end
+
+    it "should not include ' via $gateway ' when a gateway is not specified" do
+      @provider.generate_command(:add).should_not match(/\svia\s#{Regexp.escape(@new_resource.gateway.to_s)}\n/)
+    end
+  end
+
+  describe Chef::Provider::Route, "config_file_contents for action_delete" do
+    it "should return an empty string" do
+      @provider.config_file_contents(:delete).should match(/^$/)
+    end
+  end
+
+  describe Chef::Provider::Route, "generate_config method" do
+    %w[ centos redhat fedora ].each do |platform|
+      it "should write a route file on #{platform} platform" do
+        @node.automatic_attrs[:platform] = platform
+
+        route_file = StringIO.new
+        File.should_receive(:new).with("/etc/sysconfig/network-scripts/route-eth0", "w").and_return(route_file)
+        #Chef::Log.should_receive(:debug).with("route[10.0.0.10] writing route.eth0\n10.0.0.10 via 10.0.0.9\n")
+        @run_context.resource_collection << @new_resource
+        @provider.generate_config
+      end
+    end
+
+    it "should put all routes for a device in a route config file" do
+      @node.automatic_attrs[:platform] = 'centos'
+
+      route_file = StringIO.new
+      File.should_receive(:new).and_return(route_file)
+      @run_context.resource_collection << Chef::Resource::Route.new('192.168.1.0/24 via 192.168.0.1')
+      @run_context.resource_collection << Chef::Resource::Route.new('192.168.2.0/24 via 192.168.0.1')
+      @run_context.resource_collection << Chef::Resource::Route.new('192.168.3.0/24 via 192.168.0.1')
+
+      @provider.action = :add
+      @provider.generate_config
+      route_file.string.split("\n").should have(3).items
+      route_file.string.should match(/^192\.168\.1\.0\/24 via 192\.168\.0\.1$/)
+      route_file.string.should match(/^192\.168\.2\.0\/24 via 192\.168\.0\.1$/)
+      route_file.string.should match(/^192\.168\.3\.0\/24 via 192\.168\.0\.1$/)
+    end
+  end
+end
diff --git a/spec/unit/provider/ruby_block_spec.rb b/spec/unit/provider/ruby_block_spec.rb
new file mode 100644
index 0000000..6e5c9a6
--- /dev/null
+++ b/spec/unit/provider/ruby_block_spec.rb
@@ -0,0 +1,46 @@
+#
+# Author:: AJ Christensen (<aj at opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode
+# 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::RubyBlock, "initialize" do
+  before(:each) do
+    $evil_global_evil_laugh = :wahwah
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::RubyBlock.new("bloc party")
+    @new_resource.block { $evil_global_evil_laugh = :mwahahaha}
+    @provider = Chef::Provider::RubyBlock.new(@new_resource, @run_context)
+  end
+
+  it "should call the block and flag the resource as updated" do
+    @provider.run_action(:run)
+    $evil_global_evil_laugh.should == :mwahahaha
+    @new_resource.should be_updated
+  end
+
+  it "accepts `create' as an alias for `run'" do
+    # SEE ALSO: CHEF-3500
+    # "create" used to be the default action, it was renamed.
+    @provider.run_action(:create)
+    $evil_global_evil_laugh.should == :mwahahaha
+    @new_resource.should be_updated
+  end
+end
+
diff --git a/spec/unit/provider/script_spec.rb b/spec/unit/provider/script_spec.rb
new file mode 100644
index 0000000..5111a94
--- /dev/null
+++ b/spec/unit/provider/script_spec.rb
@@ -0,0 +1,96 @@
+#
+# Author:: Adam Jacob (adam at opscode.com)
+# Copyright:: Copyright (c) 2009 Opscode
+# 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::Script, "action_run" do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Script.new('run some perl code')
+    @new_resource.code "$| = 1; print 'i like beans'"
+    @new_resource.interpreter 'perl'
+
+    @provider = Chef::Provider::Script.new(@new_resource, @run_context)
+
+    @script_file = StringIO.new
+    @script_file.stub!(:path).and_return('/tmp/the_script_file')
+
+    @provider.stub!(:shell_out!).and_return(true)
+  end
+
+  it "creates a temporary file to store the script" do
+    @provider.script_file.should be_an_instance_of(Tempfile)
+  end
+
+  it "unlinks the tempfile when finished" do
+    tempfile_path = @provider.script_file.path
+    @provider.unlink_script_file
+    File.exist?(tempfile_path).should be_false
+  end
+
+  it "sets the owner and group for the script file" do
+    @new_resource.user 'toor'
+    @new_resource.group 'wheel'
+    @provider.stub!(:script_file).and_return(@script_file)
+    FileUtils.should_receive(:chown).with('toor', 'wheel', "/tmp/the_script_file")
+    @provider.set_owner_and_group
+  end
+
+  context "with the script file set to the correct owner and group" do
+    before do
+      @provider.stub!(:set_owner_and_group)
+      @provider.stub!(:script_file).and_return(@script_file)
+    end
+    describe "when writing the script to the file" do
+      it "should put the contents of the script in the temp file" do
+        @provider.action_run
+        @script_file.rewind
+        @script_file.string.should == "$| = 1; print 'i like beans'\n"
+      end
+
+      it "closes before executing the script and unlinks it when finished" do
+        @provider.action_run
+        @script_file.should be_closed
+      end
+
+    end
+
+    describe "when running the script" do
+      it 'should set the command to "interpreter"  "tempfile"' do
+        @provider.action_run
+        @new_resource.command.should == '"perl"  "/tmp/the_script_file"'
+      end
+
+      describe "with flags set on the resource" do
+        before do
+          @new_resource.flags '-f'
+        end
+
+        it "should set the command to 'interpreter flags tempfile'" do
+          @provider.action_run
+          @new_resource.command.should == '"perl" -f "/tmp/the_script_file"'
+        end
+
+      end
+
+    end
+  end
+
+end
diff --git a/spec/unit/provider/service/arch_service_spec.rb b/spec/unit/provider/service/arch_service_spec.rb
new file mode 100644
index 0000000..0865fda
--- /dev/null
+++ b/spec/unit/provider/service/arch_service_spec.rb
@@ -0,0 +1,330 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek at web.de>)
+# Author:: AJ Christensen (<aj at hjksolutions.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 'ostruct'
+
+
+# most of this code has been ripped from init_service_spec.rb
+# and is only slightly modified to match "arch" needs.
+
+describe Chef::Provider::Service::Arch, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("chef")
+    @new_resource.pattern("chef")
+    @new_resource.supports({:status => false})
+
+
+    @provider = Chef::Provider::Service::Arch.new(@new_resource, @run_context)
+
+    ::File.stub!(:exists?).with("/etc/rc.conf").and_return(true)
+    ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network apache sshd)")
+  end
+
+  describe "when first created" do
+    it "should set the current resources service name to the new resources service name" do
+      @provider.stub(:shell_out).and_return(OpenStruct.new(:exitstatus => 0, :stdout => ""))
+      @provider.load_current_resource
+      @provider.current_resource.service_name.should == 'chef'
+    end
+  end
+
+
+  describe "when the service supports status" do
+    before do
+      @new_resource.supports({:status => true})
+    end
+
+    it "should run '/etc/rc.d/service_name status'" do
+      @provider.should_receive(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 0))
+      @provider.load_current_resource
+    end
+
+    it "should set running to true if the status command returns 0" do
+      @provider.stub!(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 0))
+      @provider.load_current_resource
+      @provider.current_resource.running.should be_true
+    end
+
+    it "should set running to false if the status command returns anything except 0" do
+      @provider.stub!(:shell_out).with("/etc/rc.d/chef status").and_return(OpenStruct.new(:exitstatus => 1))
+      @provider.load_current_resource
+      @provider.current_resource.running.should be_false
+    end
+
+    it "should set running to false if the status command raises" do
+      @provider.stub!(:shell_out).with("/etc/rc.d/chef status").and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      @provider.load_current_resource
+      @provider.current_resource.running.should be_false
+    end
+
+  end
+
+
+  describe "when a status command has been specified" do
+    before do
+      @new_resource.status_command("/etc/rc.d/chefhasmonkeypants status")
+    end
+
+    it "should run the services status command if one has been specified" do
+      @provider.should_receive(:shell_out).with("/etc/rc.d/chefhasmonkeypants status").and_return(OpenStruct.new(:exitstatus => 0))
+      @provider.load_current_resource
+    end
+
+  end
+
+  it "should raise error if the node has a nil ps attribute and no other means to get status" do
+    @node.automatic_attrs[:command] = {:ps => nil}
+    @provider.define_resource_requirements
+    @provider.action = :start
+    lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+  end
+
+  it "should raise error if the node has an empty ps attribute and no other means to get status" do
+    @node.automatic_attrs[:command] = {:ps => ""}
+    @provider.define_resource_requirements
+    @provider.action = :start
+    lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+  end
+
+
+  it "should fail if file /etc/rc.conf does not exist" do
+    ::File.stub!(:exists?).with("/etc/rc.conf").and_return(false)
+    lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Service)
+  end
+
+  it "should fail if file /etc/rc.conf does not contain DAEMONS array" do
+    ::File.stub!(:read).with("/etc/rc.conf").and_return("")
+    lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Service)
+  end
+
+  describe "when discovering service status with ps" do
+    before do
+      @stdout = StringIO.new(<<-DEFAULT_PS)
+aj        7842  5057  0 21:26 pts/2    00:00:06 vi init.rb
+aj        7903  5016  0 21:26 pts/5    00:00:00 /bin/bash
+aj        8119  6041  0 21:34 pts/3    00:00:03 vi init_service_spec.rb
+DEFAULT_PS
+      @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+      @provider.stub!(:shell_out!).and_return(@status)
+
+      @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+    end
+
+    it "determines the service is running when it appears in ps" do
+      @stdout = StringIO.new(<<-RUNNING_PS)
+aj        7842  5057  0 21:26 pts/2    00:00:06 chef
+aj        7842  5057  0 21:26 pts/2    00:00:06 poos
+RUNNING_PS
+      @status.stub!(:stdout).and_return(@stdout)
+      @provider.load_current_resource
+      @provider.current_resource.running.should be_true
+    end
+
+    it "determines the service is not running when it does not appear in ps" do
+      @provider.stub!(:shell_out!).and_return(@status)
+      @provider.load_current_resource
+      @provider.current_resource.running.should be_false
+    end
+
+    it "should raise an exception if ps fails" do
+      @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      @provider.load_current_resource
+      @provider.action = :start
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+  end
+
+  it "should return existing entries in DAEMONS array" do
+    ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network !apache ssh)")
+    @provider.daemons.should == ['network', '!apache', 'ssh']
+  end
+
+  context "when the current service status is known" do
+    before do
+      @current_resource = Chef::Resource::Service.new("chef")
+      @provider.current_resource = @current_resource
+    end
+
+    describe Chef::Provider::Service::Arch, "enable_service" do
+      # before(:each) do
+      #   @new_resource = mock("Chef::Resource::Service",
+      #     :null_object => true,
+      #     :name => "chef",
+      #     :service_name => "chef",
+      #     :running => false
+      #   )
+      #   @new_resource.stub!(:start_command).and_return(false)
+      #
+      #   @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+      #   Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      # end
+
+      it "should add chef to DAEMONS array" do
+        ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network)")
+        @provider.should_receive(:update_daemons).with(['network', 'chef'])
+        @provider.enable_service()
+      end
+    end
+
+    describe Chef::Provider::Service::Arch, "disable_service" do
+      # before(:each) do
+      #   @new_resource = mock("Chef::Resource::Service",
+      #     :null_object => true,
+      #     :name => "chef",
+      #     :service_name => "chef",
+      #     :running => false
+      #   )
+      #   @new_resource.stub!(:start_command).and_return(false)
+      #
+      #   @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+      #   Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      # end
+
+      it "should remove chef from DAEMONS array" do
+        ::File.stub!(:read).with("/etc/rc.conf").and_return("DAEMONS=(network chef)")
+        @provider.should_receive(:update_daemons).with(['network', '!chef'])
+        @provider.disable_service()
+      end
+    end
+
+
+    describe Chef::Provider::Service::Arch, "start_service" do
+      # before(:each) do
+      #   @new_resource = mock("Chef::Resource::Service",
+      #     :null_object => true,
+      #     :name => "chef",
+      #     :service_name => "chef",
+      #     :running => false
+      #   )
+      #   @new_resource.stub!(:start_command).and_return(false)
+      #
+      #   @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+      #   Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      # end
+
+      it "should call the start command if one is specified" do
+        @new_resource.stub!(:start_command).and_return("/etc/rc.d/chef startyousillysally")
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/chef startyousillysally")
+        @provider.start_service()
+      end
+
+      it "should call '/etc/rc.d/service_name start' if no start command is specified" do
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} start")
+        @provider.start_service()
+      end
+    end
+
+    describe Chef::Provider::Service::Arch, "stop_service" do
+      # before(:each) do
+      #   @new_resource = mock("Chef::Resource::Service",
+      #     :null_object => true,
+      #     :name => "chef",
+      #     :service_name => "chef",
+      #     :running => false
+      #   )
+      #   @new_resource.stub!(:stop_command).and_return(false)
+      #
+      #   @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+      #   Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      # end
+
+      it "should call the stop command if one is specified" do
+        @new_resource.stub!(:stop_command).and_return("/etc/rc.d/chef itoldyoutostop")
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/chef itoldyoutostop")
+        @provider.stop_service()
+      end
+
+      it "should call '/etc/rc.d/service_name stop' if no stop command is specified" do
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} stop")
+        @provider.stop_service()
+      end
+    end
+
+    describe Chef::Provider::Service::Arch, "restart_service" do
+      # before(:each) do
+      #   @new_resource = mock("Chef::Resource::Service",
+      #     :null_object => true,
+      #     :name => "chef",
+      #     :service_name => "chef",
+      #     :running => false
+      #   )
+      #   @new_resource.stub!(:restart_command).and_return(false)
+      #   @new_resource.stub!(:supports).and_return({:restart => false})
+      #
+      #   @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+      #   Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      # end
+
+      it "should call 'restart' on the service_name if the resource supports it" do
+        @new_resource.stub!(:supports).and_return({:restart => true})
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} restart")
+        @provider.restart_service()
+      end
+
+      it "should call the restart_command if one has been specified" do
+        @new_resource.stub!(:restart_command).and_return("/etc/rc.d/chef restartinafire")
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} restartinafire")
+        @provider.restart_service()
+      end
+
+      it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+        @provider.should_receive(:stop_service)
+        @provider.should_receive(:sleep).with(1)
+        @provider.should_receive(:start_service)
+        @provider.restart_service()
+      end
+    end
+
+    describe Chef::Provider::Service::Arch, "reload_service" do
+      # before(:each) do
+      #   @new_resource = mock("Chef::Resource::Service",
+      #     :null_object => true,
+      #     :name => "chef",
+      #     :service_name => "chef",
+      #     :running => false
+      #   )
+      #   @new_resource.stub!(:reload_command).and_return(false)
+      #   @new_resource.stub!(:supports).and_return({:reload => false})
+      #
+      #   @provider = Chef::Provider::Service::Arch.new(@node, @new_resource)
+      #   Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      # end
+
+      it "should call 'reload' on the service if it supports it" do
+        @new_resource.stub!(:supports).and_return({:reload => true})
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} reload")
+        @provider.reload_service()
+      end
+
+      it "should should run the user specified reload command if one is specified and the service doesn't support reload" do
+        @new_resource.stub!(:reload_command).and_return("/etc/rc.d/chef lollerpants")
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/#{@new_resource.service_name} lollerpants")
+        @provider.reload_service()
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/service/debian_service_spec.rb b/spec/unit/provider/service/debian_service_spec.rb
new file mode 100644
index 0000000..ff0f4f4
--- /dev/null
+++ b/spec/unit/provider/service/debian_service_spec.rb
@@ -0,0 +1,344 @@
+#
+# Author:: AJ Christensen (<aj at hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# 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::Service::Debian do
+  before(:each) do
+    @node = Chef::Node.new
+    @node.automatic_attrs[:command] = {:ps => 'fuuuu'}
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("chef")
+    @provider = Chef::Provider::Service::Debian.new(@new_resource, @run_context)
+
+    @current_resource = Chef::Resource::Service.new("chef")
+    @provider.current_resource = @current_resource
+
+    @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil
+  end
+
+  describe "load_current_resource" do
+    it "ensures /usr/sbin/update-rc.d is available" do
+      File.should_receive(:exists?).with("/usr/sbin/update-rc.d") .and_return(false)
+
+      @provider.define_resource_requirements
+      lambda {
+        @provider.process_resource_requirements
+      }.should raise_error(Chef::Exceptions::Service)
+    end
+
+    context "when update-rc.d shows init linked to rc*.d/" do
+      before do
+        @provider.stub!(:assert_update_rcd_available)
+
+        result = <<-UPDATE_RC_D_SUCCESS
+  Removing any system startup links for /etc/init.d/chef ...
+    /etc/rc0.d/K20chef
+    /etc/rc1.d/K20chef
+    /etc/rc2.d/S20chef
+    /etc/rc3.d/S20chef
+    /etc/rc4.d/S20chef
+    /etc/rc5.d/S20chef
+    /etc/rc6.d/K20chef
+        UPDATE_RC_D_SUCCESS
+
+        @stdout = StringIO.new(result)
+        @stderr = StringIO.new
+        @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+        @provider.stub!(:shell_out!).and_return(@status)
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      end
+
+      it "says the service is enabled" do
+        @provider.service_currently_enabled?(@provider.get_priority).should be_true
+      end
+
+      it "stores the 'enabled' state" do
+        Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+        @provider.load_current_resource.should equal(@current_resource)
+        @current_resource.enabled.should be_true
+      end
+    end
+
+    context "when update-rc.d shows init isn't linked to rc*.d/" do
+      before do
+        @provider.stub!(:assert_update_rcd_available)
+        @status = mock("Status", :exitstatus => 0)
+        @stdout = StringIO.new(
+          " Removing any system startup links for /etc/init.d/chef ...")
+        @stderr = StringIO.new
+        @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+        @provider.stub!(:shell_out!).and_return(@status)
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+      end
+
+      it "says the service is disabled" do
+        @provider.service_currently_enabled?(@provider.get_priority).should be_false
+      end
+
+      it "stores the 'disabled' state" do
+        Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+        @provider.load_current_resource.should equal(@current_resource)
+        @current_resource.enabled.should be_false
+      end
+    end
+
+    context "when update-rc.d fails" do
+      before do
+        @status = mock("Status", :exitstatus => -1)
+        @provider.stub!(:popen4).and_return(@status)
+      end
+
+      it "raises an error" do
+        @provider.define_resource_requirements
+        lambda {
+          @provider.process_resource_requirements
+        }.should raise_error(Chef::Exceptions::Service)
+      end
+    end
+
+    {"Debian/Lenny and older" => {
+        "linked" => {
+          "stdout" => <<-STDOUT,
+ Removing any system startup links for /etc/init.d/chef ...
+     /etc/rc0.d/K20chef
+     /etc/rc1.d/K20chef
+     /etc/rc2.d/S20chef
+     /etc/rc3.d/S20chef
+     /etc/rc4.d/S20chef
+     /etc/rc5.d/S20chef
+     /etc/rc6.d/K20chef
+          STDOUT
+          "stderr" => ""
+        },
+        "not linked" => {
+          "stdout" =>
+            " Removing any system startup links for /etc/init.d/chef ...",
+          "stderr" => ""
+        },
+      },
+      "Debian/Squeeze and earlier" => {
+        "linked" => {
+          "stdout" => "update-rc.d: using dependency based boot sequencing",
+          "stderr" => <<-STDERR,
+insserv: remove service /etc/init.d/../rc0.d/K20chef-client
+  insserv: remove service /etc/init.d/../rc1.d/K20chef-client
+  insserv: remove service /etc/init.d/../rc2.d/S20chef-client
+  insserv: remove service /etc/init.d/../rc3.d/S20chef-client
+  insserv: remove service /etc/init.d/../rc4.d/S20chef-client
+  insserv: remove service /etc/init.d/../rc5.d/S20chef-client
+  insserv: remove service /etc/init.d/../rc6.d/K20chef-client
+  insserv: dryrun, not creating .depend.boot, .depend.start, and .depend.stop
+          STDERR
+        },
+        "not linked" => {
+          "stdout" => "update-rc.d: using dependency based boot sequencing",
+          "stderr" => ""
+        }
+      }
+    }.each do |model, streams|
+      context "on #{model}" do
+        context "when update-rc.d shows init linked to rc*.d/" do
+          before do
+            @provider.stub!(:assert_update_rcd_available)
+
+            @stdout = StringIO.new(streams["linked"]["stdout"])
+            @stderr = StringIO.new(streams["linked"]["stderr"])
+            @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+            @provider.stub!(:shell_out!).and_return(@status)
+            @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+          end
+
+          it "says the service is enabled" do
+            @provider.service_currently_enabled?(@provider.get_priority).should be_true
+          end
+
+          it "stores the 'enabled' state" do
+            Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+            @provider.load_current_resource.should equal(@current_resource)
+            @current_resource.enabled.should be_true
+          end
+
+          it "stores the start/stop priorities of the service" do
+            @provider.load_current_resource
+            expected_priorities = {"6"=>[:stop, "20"],
+              "0"=>[:stop, "20"],
+              "1"=>[:stop, "20"],
+              "2"=>[:start, "20"],
+              "3"=>[:start, "20"],
+              "4"=>[:start, "20"],
+              "5"=>[:start, "20"]}
+            @provider.current_resource.priority.should == expected_priorities
+          end
+        end
+
+        context "when update-rc.d shows init isn't linked to rc*.d/" do
+          before do
+            @provider.stub!(:assert_update_rcd_available)
+            @stdout = StringIO.new(streams["not linked"]["stdout"])
+            @stderr = StringIO.new(streams["not linked"]["stderr"])
+            @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+            @provider.stub!(:shell_out!).and_return(@status)
+            @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+          end
+
+          it "says the service is disabled" do
+            @provider.service_currently_enabled?(@provider.get_priority).should be_false
+          end
+
+          it "stores the 'disabled' state" do
+            Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+            @provider.load_current_resource.should equal(@current_resource)
+            @current_resource.enabled.should be_false
+          end
+        end
+      end
+    end
+
+  end
+
+  describe "action_enable" do
+    shared_examples_for "the service is up to date" do
+      it "does not enable the service" do
+        @provider.should_not_receive(:enable_service)
+        @provider.action_enable
+        @provider.set_updated_status
+        @provider.new_resource.should_not be_updated
+      end
+    end
+
+    shared_examples_for "the service is not up to date" do
+      it "enables the service and sets the resource as updated" do
+        @provider.should_receive(:enable_service).and_return(true)
+        @provider.action_enable
+        @provider.set_updated_status
+        @provider.new_resource.should be_updated
+      end
+    end
+
+    context "when the service is disabled" do
+      before do
+        @current_resource.enabled(false)
+      end
+
+      it_behaves_like "the service is not up to date"
+    end
+
+    context "when the service is enabled" do
+      before do
+        @current_resource.enabled(true)
+      end
+
+      context "and the service sets no priority" do
+        it_behaves_like "the service is up to date"
+      end
+
+      context "and the service requests the same priority as is set" do
+        before do
+          @current_resource.priority(80)
+          @new_resource.priority(80)
+        end
+        it_behaves_like "the service is up to date"
+      end
+
+      context "and the service requests a different priority than is set" do
+        before do
+          @current_resource.priority(20)
+          @new_resource.priority(80)
+        end
+        it_behaves_like "the service is not up to date"
+      end
+    end
+  end
+
+  def expect_commands(provider, commands)
+    commands.each do |command|
+      provider.should_receive(:run_command).with({:command => command})
+    end
+  end
+
+  describe "enable_service" do
+    let(:service_name) { @new_resource.service_name }
+    context "when the service doesn't set a priority" do
+      it "calls update-rc.d 'service_name' defaults" do
+        expect_commands(@provider, [
+          "/usr/sbin/update-rc.d -f #{service_name} remove",
+          "/usr/sbin/update-rc.d #{service_name} defaults"
+        ])
+        @provider.enable_service
+      end
+    end
+
+    context "when the service sets a simple priority" do
+      before do
+        @new_resource.priority(75)
+      end
+
+      it "calls update-rc.d 'service_name' defaults" do
+        expect_commands(@provider, [
+          "/usr/sbin/update-rc.d -f #{service_name} remove",
+          "/usr/sbin/update-rc.d #{service_name} defaults 75 25"
+        ])
+        @provider.enable_service
+      end
+    end
+
+    context "when the service sets complex priorities" do
+      before do
+        @new_resource.priority(2 => [:start, 20], 3 => [:stop, 55])
+      end
+
+      it "calls update-rc.d 'service_name' with those priorities" do
+        expect_commands(@provider, [
+          "/usr/sbin/update-rc.d -f #{service_name} remove",
+          "/usr/sbin/update-rc.d #{service_name} start 20 2 . stop 55 3 . "
+        ])
+        @provider.enable_service
+      end
+    end
+  end
+
+  describe "disable_service" do
+    let(:service_name) { @new_resource.service_name }
+    context "when the service doesn't set a priority" do
+      it "calls update-rc.d -f 'service_name' remove + stop with default priority" do
+        expect_commands(@provider, [
+          "/usr/sbin/update-rc.d -f #{service_name} remove",
+          "/usr/sbin/update-rc.d -f #{service_name} stop 80 2 3 4 5 ."
+        ])
+        @provider.disable_service
+      end
+    end
+
+    context "when the service sets a simple priority" do
+      before do
+        @new_resource.priority(75)
+      end
+
+      it "calls update-rc.d -f 'service_name' remove + stop with the specified priority" do
+        expect_commands(@provider, [
+          "/usr/sbin/update-rc.d -f #{service_name} remove",
+          "/usr/sbin/update-rc.d -f #{service_name} stop #{100 - @new_resource.priority} 2 3 4 5 ."
+        ])
+        @provider.disable_service
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/service/freebsd_service_spec.rb b/spec/unit/provider/service/freebsd_service_spec.rb
new file mode 100644
index 0000000..7861764
--- /dev/null
+++ b/spec/unit/provider/service/freebsd_service_spec.rb
@@ -0,0 +1,379 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Service::Freebsd do
+  before do
+    @node = Chef::Node.new
+    @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("apache22")
+    @new_resource.pattern("httpd")
+    @new_resource.supports({:status => false})
+
+    @current_resource = Chef::Resource::Service.new("apache22")
+
+    @provider = Chef::Provider::Service::Freebsd.new(@new_resource, at run_context)
+    @provider.action = :start
+    @init_command = "/usr/local/etc/rc.d/apache22"
+    Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+  end
+
+  describe "load_current_resource" do
+    before(:each) do
+      @stdout = StringIO.new(<<-PS_SAMPLE)
+413  ??  Ss     0:02.51 /usr/sbin/syslogd -s
+539  ??  Is     0:00.14 /usr/sbin/sshd
+545  ??  Ss     0:17.53 sendmail: accepting connections (sendmail)
+PS_SAMPLE
+      @status = mock(:stdout => @stdout, :exitstatus => 0)
+      @provider.stub!(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+
+      ::File.stub!(:exists?).and_return(false)
+      ::File.stub!(:exists?).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(true)
+      @lines = mock("lines")
+      @lines.stub!(:each).and_yield("sshd_enable=\"YES\"").
+                          and_yield("#{@new_resource.name}_enable=\"YES\"")
+      ::File.stub!(:open).and_return(@lines)
+
+      @rc_with_name = StringIO.new(<<-RC_SAMPLE)
+name="apache22"
+rcvar=`set_rcvar`
+RC_SAMPLE
+      ::File.stub!(:open).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(@rc_with_name)
+      @provider.stub(:service_enable_variable_name).and_return nil
+
+    end
+
+    it "should create a current resource with the name of the new resource" do
+      Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+      @provider.load_current_resource
+    end
+
+    it "should set the current resources service name to the new resources service name" do
+      @provider.load_current_resource
+      @current_resource.service_name.should == @new_resource.service_name
+    end
+
+    it "should not raise an exception if the rcscript have a name variable" do
+      @provider.load_current_resource
+      lambda { @provider.service_enable_variable_name }.should_not raise_error(Chef::Exceptions::Service)
+    end
+
+    describe "when the service supports status" do
+      before do
+        @new_resource.supports({:status => true})
+      end
+
+      it "should run '/etc/init.d/service_name status'" do
+        @provider.should_receive(:shell_out).with("/usr/local/etc/rc.d/#{@current_resource.service_name} status").and_return(@status)
+        @provider.load_current_resource
+      end
+
+      it "should set running to true if the status command returns 0" do
+        @provider.should_receive(:shell_out).with("/usr/local/etc/rc.d/#{@current_resource.service_name} status").and_return(@status)
+        @current_resource.should_receive(:running).with(true)
+        @provider.load_current_resource
+      end
+
+      it "should set running to false if the status command returns anything except 0" do
+        @provider.should_receive(:shell_out).with("/usr/local/etc/rc.d/#{@current_resource.service_name} status").and_raise(Mixlib::ShellOut::ShellCommandFailed)
+        @current_resource.should_receive(:running).with(false)
+        @provider.load_current_resource
+       # @provider.current_resource.running.should be_false
+      end
+    end
+
+    describe "when a status command has been specified" do
+      before do
+        @new_resource.status_command("/bin/chefhasmonkeypants status")
+      end
+
+      it "should run the services status command if one has been specified" do
+        @provider.should_receive(:shell_out).with("/bin/chefhasmonkeypants status").and_return(@status)
+        @provider.load_current_resource
+      end
+
+    end
+
+    it "should raise error if the node has a nil ps attribute and no other means to get status" do
+      @node.automatic_attrs[:command] = {:ps => nil}
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+    it "should raise error if the node has an empty ps attribute and no other means to get status" do
+      @node.automatic_attrs[:command] = {:ps => ""}
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+    describe "when executing assertions" do
+      it "should verify that /etc/rc.conf exists" do
+        ::File.should_receive(:exists?).with("/etc/rc.conf")
+        @provider.stub!(:service_enable_variable_name).and_return("#{@current_resource.service_name}_enable")
+        @provider.load_current_resource
+      end
+
+      context "and the init script is not found" do
+        [ "start", "reload", "restart", "enable" ].each do |action|
+          it "should raise an exception when the action is #{action}" do
+            ::File.stub!(:exists?).and_return(false)
+            @provider.load_current_resource
+            @provider.define_resource_requirements
+            @provider.instance_variable_get("@rcd_script_found").should be_false
+            @provider.action = action
+            lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+          end
+        end
+
+        [ "stop", "disable" ].each do |action|
+          it "should not raise an error when the action is #{action}" do
+            @provider.action = action
+            lambda { @provider.process_resource_requirements }.should_not raise_error
+          end
+        end
+      end
+
+      it "update state when current resource enabled state could not be determined" do
+        ::File.should_receive(:exists?).with("/etc/rc.conf").and_return false
+        @provider.load_current_resource
+        @provider.instance_variable_get("@enabled_state_found").should be_false
+      end
+
+      it "update state when current resource enabled state could be determined" do
+        ::File.stub!(:exist?).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(true)
+        ::File.should_receive(:exists?).with("/etc/rc.conf").and_return  true
+        @provider.load_current_resource
+        @provider.instance_variable_get("@enabled_state_found").should be_false
+        @provider.instance_variable_get("@rcd_script_found").should be_true
+        @provider.define_resource_requirements
+        lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service,
+          "Could not find the service name in /usr/local/etc/rc.d/#{@current_resource.service_name} and rcvar")
+      end
+
+      it "should throw an exception if service line is missing from rc.d script" do
+          pending "not implemented" do
+            false.should be_true
+          end
+      end
+
+    end
+
+    describe "when we have a 'ps' attribute" do
+      before do
+        @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+      end
+
+      it "should shell_out! the node's ps command" do
+        @provider.should_receive(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+        @provider.load_current_resource
+      end
+
+      it "should read stdout of the ps command" do
+        @provider.stub!(:shell_out!).and_return(@status)
+        @stdout.should_receive(:each_line).and_return(true)
+        @provider.load_current_resource
+      end
+
+      it "should set running to true if the regex matches the output" do
+        @stdout.stub!(:each_line).and_yield("555  ??  Ss     0:05.16 /usr/sbin/cron -s").
+                                  and_yield(" 9881  ??  Ss     0:06.67 /usr/local/sbin/httpd -DNOHTTPACCEPT")
+        @provider.load_current_resource
+        @current_resource.running.should be_true
+      end
+
+      it "should set running to false if the regex doesn't match" do
+        @provider.stub!(:shell_out!).and_return(@status)
+        @provider.load_current_resource
+        @current_resource.running.should be_false
+      end
+
+      it "should raise an exception if ps fails" do
+        @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+        @provider.load_current_resource
+        @provider.define_resource_requirements
+        lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+      end
+    end
+
+    it "should return the current resource" do
+      @provider.load_current_resource.should eql(@current_resource)
+    end
+
+    describe "when starting the service" do
+      it "should call the start command if one is specified" do
+        @new_resource.start_command("/etc/rc.d/chef startyousillysally")
+        @provider.should_receive(:shell_out!).with("/etc/rc.d/chef startyousillysally")
+        @provider.load_current_resource
+        @provider.start_service()
+      end
+
+      it "should call '/usr/local/etc/rc.d/service_name faststart' if no start command is specified" do
+        @provider.should_receive(:shell_out!).with("/usr/local/etc/rc.d/#{@new_resource.service_name} faststart")
+        @provider.load_current_resource
+        @provider.start_service()
+      end
+    end
+
+    describe Chef::Provider::Service::Init, "stop_service" do
+      it "should call the stop command if one is specified" do
+        @new_resource.stop_command("/etc/init.d/chef itoldyoutostop")
+        @provider.should_receive(:shell_out!).with("/etc/init.d/chef itoldyoutostop")
+        @provider.load_current_resource
+        @provider.stop_service()
+      end
+
+      it "should call '/usr/local/etc/rc.d/service_name faststop' if no stop command is specified" do
+        @provider.should_receive(:shell_out!).with("/usr/local/etc/rc.d/#{@new_resource.service_name} faststop")
+        @provider.load_current_resource
+        @provider.stop_service()
+      end
+    end
+
+    describe "when restarting a service" do
+      it "should call 'restart' on the service_name if the resource supports it" do
+        @new_resource.supports({:restart => true})
+        @provider.should_receive(:shell_out!).with("/usr/local/etc/rc.d/#{@new_resource.service_name} fastrestart")
+        @provider.load_current_resource
+        @provider.restart_service()
+      end
+
+      it "should call the restart_command if one has been specified" do
+        @new_resource.restart_command("/etc/init.d/chef restartinafire")
+        @provider.should_receive(:shell_out!).with("/etc/init.d/chef restartinafire")
+        @provider.load_current_resource
+        @provider.restart_service()
+      end
+    end
+
+    describe "when the rcscript does not have a name variable" do
+      before do
+        @rc_without_name = StringIO.new(<<-RC_SAMPLE)
+rcvar=`set_rcvar`
+RC_SAMPLE
+        ::File.stub!(:open).with("/usr/local/etc/rc.d/#{@current_resource.service_name}").and_return(@rc_with_noname)
+        @provider.current_resource = @current_resource
+      end
+
+      describe "when rcvar returns foobar_enable" do
+        before do
+          @rcvar_stdout = <<RCVAR_SAMPLE
+# apache22
+#
+# #{@current_resource.service_name}_enable="YES"
+#   (default: "")
+RCVAR_SAMPLE
+          @status = mock(:stdout => @rcvar_stdout, :exitstatus => 0)
+          @provider.stub!(:shell_out!).with("/usr/local/etc/rc.d/#{@current_resource.service_name} rcvar").and_return(@status)
+        end
+
+        it "should get the service name from rcvar if the rcscript does not have a name variable" do
+          @provider.load_current_resource
+          @provider.unstub!(:service_enable_variable_name)
+          @provider.service_enable_variable_name.should == "#{@current_resource.service_name}_enable"
+        end
+
+        it "should not raise an exception if the rcscript does not have a name variable" do
+          @provider.load_current_resource
+          lambda { @provider.service_enable_variable_name }.should_not raise_error(Chef::Exceptions::Service)
+        end
+      end
+
+      describe "when rcvar does not return foobar_enable" do
+        before do
+          @rcvar_stdout = <<RCVAR_SAMPLE
+# service_with_noname
+#
+RCVAR_SAMPLE
+          @status = mock(:stdout => @rcvar_stdout, :exitstatus => 0)
+          @provider.stub!(:shell_out!).with("/usr/local/etc/rc.d/#{@current_resource.service_name} rcvar").and_return(@status)
+        end
+
+        [ "start", "reload", "restart", "enable" ].each do |action|
+          it "should raise an exception when the action is #{action}" do
+            @provider.action = action
+            @provider.load_current_resource
+            @provider.define_resource_requirements
+            lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+          end
+        end
+
+        [ "stop", "disable" ].each do |action|
+          it "should not raise an error when the action is #{action}" do
+            ::File.stub!(:exist?).with("/usr/local/etc/rc.d/#{@new_resource.service_name}").and_return(true)
+            @provider.action = action
+            @provider.load_current_resource
+            @provider.define_resource_requirements
+            lambda { @provider.process_resource_requirements }.should_not raise_error(Chef::Exceptions::Service)
+          end
+        end
+      end
+    end
+  end
+
+  describe Chef::Provider::Service::Freebsd, "enable_service" do
+    before do
+      @provider.current_resource = @current_resource
+      @provider.stub!(:service_enable_variable_name).and_return("#{@current_resource.service_name}_enable")
+    end
+
+    it "should enable the service if it is not enabled" do
+      @current_resource.stub!(:enabled).and_return(false)
+      @provider.should_receive(:read_rc_conf).and_return([ "foo", "#{@current_resource.service_name}_enable=\"NO\"", "bar" ])
+      @provider.should_receive(:write_rc_conf).with(["foo", "bar", "#{@current_resource.service_name}_enable=\"YES\""])
+      @provider.enable_service()
+    end
+
+    it "should enable the service if it is not enabled and not already specified in the rc.conf file" do
+      @current_resource.stub!(:enabled).and_return(false)
+      @provider.should_receive(:read_rc_conf).and_return([ "foo", "bar" ])
+      @provider.should_receive(:write_rc_conf).with(["foo", "bar", "#{@current_resource.service_name}_enable=\"YES\""])
+      @provider.enable_service()
+    end
+
+    it "should not enable the service if it is already enabled" do
+      @current_resource.stub!(:enabled).and_return(true)
+      @provider.should_not_receive(:write_rc_conf)
+      @provider.enable_service
+    end
+  end
+
+  describe Chef::Provider::Service::Freebsd, "disable_service" do
+    before do
+      @provider.current_resource = @current_resource
+      @provider.stub!(:service_enable_variable_name).and_return("#{@current_resource.service_name}_enable")
+    end
+
+    it "should should disable the service if it is not disabled" do
+      @current_resource.stub!(:enabled).and_return(true)
+      @provider.should_receive(:read_rc_conf).and_return([ "foo", "#{@current_resource.service_name}_enable=\"YES\"", "bar" ])
+      @provider.should_receive(:write_rc_conf).with(["foo", "bar", "#{@current_resource.service_name}_enable=\"NO\""])
+      @provider.disable_service()
+    end
+
+    it "should not disable the service if it is already disabled" do
+      @current_resource.stub!(:enabled).and_return(false)
+      @provider.should_not_receive(:write_rc_conf)
+      @provider.disable_service()
+    end
+  end
+end
diff --git a/spec/unit/provider/service/gentoo_service_spec.rb b/spec/unit/provider/service/gentoo_service_spec.rb
new file mode 100644
index 0000000..b658cab
--- /dev/null
+++ b/spec/unit/provider/service/gentoo_service_spec.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Lee Jensen (<ljensen at engineyard.com>)
+# Author:: AJ Christensen (<aj at opscode.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'
+
+describe Chef::Provider::Service::Gentoo do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource     = Chef::Resource::Service.new("chef")
+    @current_resource = Chef::Resource::Service.new("chef")
+
+    @provider = Chef::Provider::Service::Gentoo.new(@new_resource, @run_context)
+    Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+    @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+    @provider.stub!(:shell_out).and_return(@status)
+    File.stub!(:exists?).with("/etc/init.d/chef").and_return(true)
+    File.stub!(:exists?).with("/sbin/rc-update").and_return(true)
+    File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(false)
+    File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return(false)
+  end
+ # new test: found_enabled state
+  #
+  describe "load_current_resource" do
+    it "should raise Chef::Exceptions::Service if /sbin/rc-update does not exist" do
+      File.should_receive(:exists?).with("/sbin/rc-update").and_return(false)
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+    it "should track when service file is not found in /etc/runlevels" do
+      @provider.load_current_resource
+      @provider.instance_variable_get("@found_script").should be_false
+    end
+
+    it "should track when service file is found in /etc/runlevels/**/" do
+      Dir.stub!(:glob).with("/etc/runlevels/**/chef").and_return(["/etc/runlevels/default/chef"])
+      @provider.load_current_resource
+      @provider.instance_variable_get("@found_script").should be_true
+    end
+
+    describe "when detecting the service enable state" do
+      describe "and the glob returns a default service script file" do
+        before do
+          Dir.stub!(:glob).with("/etc/runlevels/**/chef").and_return(["/etc/runlevels/default/chef"])
+        end
+
+        describe "and the file exists and is readable" do
+          before do
+            File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(true)
+            File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return(true)
+          end
+          it "should set enabled to true" do
+            @provider.load_current_resource
+            @current_resource.enabled.should be_true
+          end
+        end
+
+        describe "and the file exists but is not readable" do
+          before do
+            File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(true)
+            File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return(false)
+          end
+
+          it "should set enabled to false" do
+            @provider.load_current_resource
+            @current_resource.enabled.should be_false
+          end
+        end
+
+        describe "and the file does not exist" do
+          before do
+            File.stub!(:exists?).with("/etc/runlevels/default/chef").and_return(false)
+            File.stub!(:readable?).with("/etc/runlevels/default/chef").and_return("foobarbaz")
+          end
+
+          it "should set enabled to false" do
+            @provider.load_current_resource
+            @current_resource.enabled.should be_false
+          end
+
+        end
+      end
+
+  end
+
+    it "should return the current_resource" do
+      @provider.load_current_resource.should == @current_resource
+    end
+
+    it "should support the status command automatically" do
+      @provider.load_current_resource
+      @new_resource.supports[:status].should be_true
+    end
+
+    it "should support the restart command automatically" do
+      @provider.load_current_resource
+      @new_resource.supports[:restart].should be_true
+    end
+
+    it "should not support the reload command automatically" do
+      @provider.load_current_resource
+      @new_resource.supports[:reload].should_not be_true
+    end
+
+  end
+
+  describe "action_methods" do
+    before(:each) { @provider.stub!(:load_current_resource).and_return(@current_resource) }
+
+    describe Chef::Provider::Service::Gentoo, "enable_service" do
+      it "should call rc-update add *service* default" do
+        @provider.should_receive(:run_command).with({:command => "/sbin/rc-update add chef default"})
+        @provider.enable_service()
+      end
+    end
+
+    describe Chef::Provider::Service::Gentoo, "disable_service" do
+      it "should call rc-update del *service* default" do
+        @provider.should_receive(:run_command).with({:command => "/sbin/rc-update del chef default"})
+        @provider.disable_service()
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/provider/service/init_service_spec.rb b/spec/unit/provider/service/init_service_spec.rb
new file mode 100644
index 0000000..c7d47e6
--- /dev/null
+++ b/spec/unit/provider/service/init_service_spec.rb
@@ -0,0 +1,235 @@
+#
+# Author:: AJ Christensen (<aj at hjksolutions.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'
+
+describe Chef::Provider::Service::Init, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("chef")
+
+    @current_resource = Chef::Resource::Service.new("chef")
+
+    @provider = Chef::Provider::Service::Init.new(@new_resource, @run_context)
+    Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+    @stdout = StringIO.new(<<-PS)
+aj        7842  5057  0 21:26 pts/2    00:00:06 vi init.rb
+aj        7903  5016  0 21:26 pts/5    00:00:00 /bin/bash
+aj        8119  6041  0 21:34 pts/3    00:00:03 vi init_service_spec.rb
+PS
+    @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+    @provider.stub!(:shell_out!).and_return(@status)
+  end
+
+  it "should create a current resource with the name of the new resource" do
+    @provider.load_current_resource
+    @provider.current_resource.should equal(@current_resource)
+  end
+
+  it "should set the current resources service name to the new resources service name" do
+    @provider.load_current_resource
+    @current_resource.service_name.should == 'chef'
+  end
+
+  describe "when the service supports status" do
+    before do
+      @new_resource.supports({:status => true})
+    end
+
+    it "should run '/etc/init.d/service_name status'" do
+      @provider.should_receive(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status)
+      @provider.load_current_resource
+    end
+
+    it "should set running to true if the status command returns 0" do
+      @provider.stub!(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_true
+    end
+
+    it "should set running to false if the status command returns anything except 0" do
+      @status.stub!(:exitstatus).and_return(1)
+      @provider.stub!(:shell_out).with("/etc/init.d/#{@current_resource.service_name} status").and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_false
+    end
+
+    it "should set running to false if the status command raises" do
+      @provider.stub!(:shell_out).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      @provider.load_current_resource
+      @current_resource.running.should be_false
+    end
+  end
+
+  describe "when a status command has been specified" do
+    before do
+      @new_resource.stub!(:status_command).and_return("/etc/init.d/chefhasmonkeypants status")
+    end
+
+    it "should run the services status command if one has been specified" do
+      @provider.should_receive(:shell_out).with("/etc/init.d/chefhasmonkeypants status").and_return(@status)
+      @provider.load_current_resource
+    end
+
+  end
+
+  describe "when an init command has been specified" do
+    before do
+      @new_resource.stub!(:init_command).and_return("/opt/chef-server/service/erchef")
+      @provider = Chef::Provider::Service::Init.new(@new_resource, @run_context)
+    end
+
+    it "should use the init_command if one has been specified" do
+      @provider.should_receive(:shell_out!).with("/opt/chef-server/service/erchef start")
+      @provider.start_service
+    end
+
+  end
+
+  describe "when the node has not specified a ps command" do
+
+    it "should raise an error if the node has a nil ps attribute" do
+      @node.automatic_attrs[:command] = {:ps => nil}
+      @provider.load_current_resource
+      @provider.action = :start
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+    it "should raise an error if the node has an empty ps attribute" do
+      @node.automatic_attrs[:command] = {:ps => ""}
+      @provider.load_current_resource
+      @provider.action = :start
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+  end
+
+
+  describe "when we have a 'ps' attribute" do
+    it "should shell_out! the node's ps command" do
+      @provider.should_receive(:shell_out!).and_return(@status)
+      @provider.load_current_resource
+    end
+
+    it "should set running to true if the regex matches the output" do
+      @stdout = StringIO.new(<<-RUNNING_PS)
+aj        7842  5057  0 21:26 pts/2    00:00:06 chef
+aj        7842  5057  0 21:26 pts/2    00:00:06 poos
+RUNNING_PS
+      @status.stub!(:stdout).and_return(@stdout)
+      @provider.load_current_resource
+      @current_resource.running.should be_true
+    end
+
+    it "should set running to false if the regex doesn't match" do
+      @provider.stub!(:shell_out!).and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_false
+    end
+
+    it "should raise an exception if ps fails" do
+      @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      @provider.load_current_resource
+      @provider.action = :start
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+  end
+
+  it "should return the current resource" do
+    @provider.load_current_resource.should eql(@current_resource)
+  end
+
+  describe "when starting the service" do
+    it "should call the start command if one is specified" do
+      @new_resource.start_command("/etc/init.d/chef startyousillysally")
+      @provider.should_receive(:shell_out!).with("/etc/init.d/chef startyousillysally")
+      @provider.start_service()
+    end
+
+    it "should call '/etc/init.d/service_name start' if no start command is specified" do
+      @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} start")
+      @provider.start_service()
+    end
+  end
+
+  describe Chef::Provider::Service::Init, "stop_service" do
+    it "should call the stop command if one is specified" do
+      @new_resource.stop_command("/etc/init.d/chef itoldyoutostop")
+      @provider.should_receive(:shell_out!).with("/etc/init.d/chef itoldyoutostop")
+      @provider.stop_service()
+    end
+
+    it "should call '/etc/init.d/service_name stop' if no stop command is specified" do
+      @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} stop")
+      @provider.stop_service()
+    end
+  end
+
+  describe "when restarting a service" do
+    it "should call 'restart' on the service_name if the resource supports it" do
+      @new_resource.supports({:restart => true})
+      @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} restart")
+      @provider.restart_service()
+    end
+
+    it "should call the restart_command if one has been specified" do
+      @new_resource.restart_command("/etc/init.d/chef restartinafire")
+      @provider.should_receive(:shell_out!).with("/etc/init.d/#{@new_resource.service_name} restartinafire")
+      @provider.restart_service()
+    end
+
+    it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+      @provider.should_receive(:stop_service)
+      @provider.should_receive(:sleep).with(1)
+      @provider.should_receive(:start_service)
+      @provider.restart_service()
+    end
+  end
+
+  describe "when reloading a service" do
+    it "should call 'reload' on the service if it supports it" do
+      @new_resource.supports({:reload => true})
+      @provider.should_receive(:shell_out!).with("/etc/init.d/chef reload")
+      @provider.reload_service()
+    end
+
+    it "should should run the user specified reload command if one is specified and the service doesn't support reload" do
+      @new_resource.reload_command("/etc/init.d/chef lollerpants")
+      @provider.should_receive(:shell_out!).with("/etc/init.d/chef lollerpants")
+      @provider.reload_service()
+    end
+  end
+
+  describe "when a custom command has been specified" do
+    before do
+      @new_resource.start_command("/etc/init.d/chef startyousillysally")
+    end
+
+    it "should still pass all why run assertions" do
+      lambda { @provider.run_action(:start) }.should_not raise_error(Chef::Exceptions::Service)
+    end
+  end
+end
diff --git a/spec/unit/provider/service/insserv_service_spec.rb b/spec/unit/provider/service/insserv_service_spec.rb
new file mode 100644
index 0000000..8b5f09e
--- /dev/null
+++ b/spec/unit/provider/service/insserv_service_spec.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Bryan McLellan <btm at loftninjas.org>
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Service::Insserv do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+
+    @new_resource = Chef::Resource::Service.new("initgrediant")
+    @current_resource = Chef::Resource::Service.new("initgrediant")
+
+    @provider = Chef::Provider::Service::Insserv.new(@new_resource, @run_context)
+    @status = mock("Process::Status mock", :exitstatus => 0, :stdout => "")
+    @provider.stub!(:shell_out!).and_return(@status)
+  end
+
+  describe "load_current_resource" do
+    describe "when startup links exist" do
+      before do
+        Dir.stub!(:glob).with("/etc/rc**/S*initgrediant").and_return(["/etc/rc5.d/S18initgrediant", "/etc/rc2.d/S18initgrediant", "/etc/rc4.d/S18initgrediant", "/etc/rc3.d/S18initgrediant"])
+      end
+
+      it "sets the current enabled status to true" do
+        @provider.load_current_resource
+        @provider.current_resource.enabled.should be_true
+      end
+    end
+
+    describe "when startup links do not exist" do
+      before do
+        Dir.stub!(:glob).with("/etc/rc**/S*initgrediant").and_return([])
+      end
+
+      it "sets the current enabled status to false" do
+        @provider.load_current_resource
+        @provider.current_resource.enabled.should be_false
+      end
+    end
+
+  end
+
+  describe "enable_service" do
+    it "should call insserv and create the default links" do
+      @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -r -f #{@new_resource.service_name}"})
+      @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -d -f #{@new_resource.service_name}"})
+      @provider.enable_service
+    end
+  end
+
+  describe "disable_service" do
+    it "should call insserv and remove the links" do
+      @provider.should_receive(:run_command).with({:command=>"/sbin/insserv -r -f #{@new_resource.service_name}"})
+      @provider.disable_service
+    end
+  end
+end
+
diff --git a/spec/unit/provider/service/invokercd_service_spec.rb b/spec/unit/provider/service/invokercd_service_spec.rb
new file mode 100644
index 0000000..ca20657
--- /dev/null
+++ b/spec/unit/provider/service/invokercd_service_spec.rb
@@ -0,0 +1,212 @@
+#
+# Author:: AJ Christensen (<aj at hjksolutions.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'
+
+describe Chef::Provider::Service::Invokercd, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("chef")
+
+    @current_resource = Chef::Resource::Service.new("chef")
+
+    @provider = Chef::Provider::Service::Invokercd.new(@new_resource, @run_context)
+    Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+    @stdout = StringIO.new(<<-PS)
+aj        7842  5057  0 21:26 pts/2    00:00:06 vi init.rb
+aj        7903  5016  0 21:26 pts/5    00:00:00 /bin/bash
+aj        8119  6041  0 21:34 pts/3    00:00:03 vi init_service_spec.rb
+PS
+    @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+    @provider.stub!(:shell_out!).and_return(@status)
+  end
+
+  it "should create a current resource with the name of the new resource" do
+    @provider.load_current_resource
+    @provider.current_resource.should equal(@current_resource)
+  end
+
+  it "should set the current resources service name to the new resources service name" do
+    @provider.load_current_resource
+    @current_resource.service_name.should == 'chef'
+  end
+
+  describe "when the service supports status" do
+    before do
+      @new_resource.supports({:status => true})
+    end
+
+    it "should run '/usr/sbin/invoke-rc.d service_name status'" do
+      @provider.should_receive(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status)
+      @provider.load_current_resource
+    end
+
+    it "should set running to true if the status command returns 0" do
+      @provider.stub!(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_true
+    end
+
+    it "should set running to false if the status command returns anything except 0" do
+      @status.stub!(:exitstatus).and_return(1)
+      @provider.stub!(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_false
+    end
+
+    it "should set running to false if the status command raises" do
+      @provider.stub!(:shell_out).with("/usr/sbin/invoke-rc.d #{@current_resource.service_name} status").and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      @provider.load_current_resource
+      @current_resource.running.should be_false
+    end
+  end
+
+  describe "when a status command has been specified" do
+    before do
+      @new_resource.stub!(:status_command).and_return("/usr/sbin/invoke-rc.d chefhasmonkeypants status")
+    end
+
+    it "should run the services status command if one has been specified" do
+      @provider.should_receive(:shell_out).with("/usr/sbin/invoke-rc.d chefhasmonkeypants status").and_return(@status)
+      @provider.load_current_resource
+    end
+
+  end
+
+  describe "when the node has not specified a ps command" do
+    it "should raise error if the node has a nil ps attribute and no other means to get status" do
+      @node.automatic_attrs[:command] = {:ps => nil}
+      @provider.action = :start
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+    it "should raise error if the node has an empty ps attribute and no other means to get status" do
+      @node.automatic_attrs[:command] = {:ps => ""}
+      @provider.action = :start
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+  end
+
+
+  describe "when we have a 'ps' attribute" do
+    it "should shell_out! the node's ps command" do
+      @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+      @provider.should_receive(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+      @provider.load_current_resource
+    end
+
+    it "should set running to true if the regex matches the output" do
+      @stdout = StringIO.new(<<-RUNNING_PS)
+aj        7842  5057  0 21:26 pts/2    00:00:06 chef
+aj        7842  5057  0 21:26 pts/2    00:00:06 poos
+RUNNING_PS
+      @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+      @provider.should_receive(:shell_out!).and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_true
+    end
+
+    it "should set running to false if the regex doesn't match" do
+      @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+      @provider.should_receive(:shell_out!).and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_false
+    end
+
+    it "should raise an exception if ps fails" do
+      @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      @provider.action = :start
+      @provider.load_current_resource
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+  end
+
+  it "should return the current resource" do
+    @provider.load_current_resource.should eql(@current_resource)
+  end
+
+  describe "when starting the service" do
+    it "should call the start command if one is specified" do
+      @new_resource.start_command("/usr/sbin/invoke-rc.d chef startyousillysally")
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef startyousillysally")
+      @provider.start_service()
+    end
+
+    it "should call '/usr/sbin/invoke-rc.d service_name start' if no start command is specified" do
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} start")
+      @provider.start_service()
+    end
+  end
+
+  describe Chef::Provider::Service::Invokercd, "stop_service" do
+    it "should call the stop command if one is specified" do
+      @new_resource.stop_command("/usr/sbin/invoke-rc.d chef itoldyoutostop")
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef itoldyoutostop")
+      @provider.stop_service()
+    end
+
+    it "should call '/usr/sbin/invoke-rc.d service_name stop' if no stop command is specified" do
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} stop")
+      @provider.stop_service()
+    end
+  end
+
+  describe "when restarting a service" do
+    it "should call 'restart' on the service_name if the resource supports it" do
+      @new_resource.supports({:restart => true})
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restart")
+      @provider.restart_service()
+    end
+
+    it "should call the restart_command if one has been specified" do
+      @new_resource.restart_command("/usr/sbin/invoke-rc.d chef restartinafire")
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d #{@new_resource.service_name} restartinafire")
+      @provider.restart_service()
+    end
+
+    it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+      @provider.should_receive(:stop_service)
+      @provider.should_receive(:sleep).with(1)
+      @provider.should_receive(:start_service)
+      @provider.restart_service()
+    end
+  end
+
+  describe "when reloading a service" do
+    it "should call 'reload' on the service if it supports it" do
+      @new_resource.supports({:reload => true})
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef reload")
+      @provider.reload_service()
+    end
+
+    it "should should run the user specified reload command if one is specified and the service doesn't support reload" do
+      @new_resource.reload_command("/usr/sbin/invoke-rc.d chef lollerpants")
+      @provider.should_receive(:shell_out!).with("/usr/sbin/invoke-rc.d chef lollerpants")
+      @provider.reload_service()
+    end
+  end
+end
diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb
new file mode 100644
index 0000000..61bd558
--- /dev/null
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -0,0 +1,253 @@
+#
+# Author:: Igor Afonov <afonov at gmail.com>
+# Copyright:: Copyright (c) 2011 Igor Afonov
+# 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::Service::Macosx do
+  describe ".gather_plist_dirs" do
+    context "when HOME directory is set" do
+      before do
+        ENV.stub(:[]).with('HOME').and_return("/User/someuser")
+      end
+
+      it "includes users's LaunchAgents folder" do
+        described_class.gather_plist_dirs.should include("#{ENV['HOME']}/Library/LaunchAgents")
+      end
+    end
+
+    context "when HOME directory is not set" do
+      before do
+        ENV.stub(:[]).with('HOME').and_return(nil)
+      end
+
+      it "doesn't include user's LaunchAgents folder" do
+        described_class.gather_plist_dirs.should_not include("~/Library/LaunchAgents")
+      end
+    end
+  end
+
+  context "when service name is given as" do
+    let(:node) { Chef::Node.new }
+    let(:events) {Chef::EventDispatch::Dispatcher.new}
+    let(:run_context) { Chef::RunContext.new(node, {}, events) }
+    let(:provider) { described_class.new(new_resource, run_context) }
+    let(:stdout) { StringIO.new }
+
+    ["redis-server", "io.redis.redis-server"].each do |service_name|
+      before do
+        Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
+        provider.stub!(:shell_out!).
+                 with("launchctl list", {:group => 1001, :user => 101}).
+                 and_return(mock("ouput", :stdout => stdout))
+
+        File.stub!(:stat).and_return(mock("stat", :gid => 1001, :uid => 101))
+      end
+
+      context "#{service_name}" do
+        let(:new_resource) { Chef::Resource::Service.new(service_name) }
+        let!(:current_resource) { Chef::Resource::Service.new(service_name) }
+
+        describe "#load_current_resource" do
+          context "when launchctl returns pid in service list" do
+            let(:stdout) { StringIO.new <<-SVC_LIST }
+  12761 - 0x100114220.old.machinit.thing
+  7777  - io.redis.redis-server
+  - - com.lol.stopped-thing
+  SVC_LIST
+
+            before do
+              provider.load_current_resource
+            end
+
+            it "sets resource running state to true" do
+              provider.current_resource.running.should be_true
+            end
+
+            it "sets resouce enabled state to true" do
+              provider.current_resource.enabled.should be_true
+            end
+          end
+
+          describe "running unsupported actions" do
+            before do
+              Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
+            end
+            it "should throw an exception when enable action is attempted" do
+              lambda {provider.run_action(:enable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+            end
+            it "should throw an exception when reload action is attempted" do
+              lambda {provider.run_action(:reload)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+            end
+            it "should throw an exception when disable action is attempted" do
+              lambda {provider.run_action(:disable)}.should raise_error(Chef::Exceptions::UnsupportedAction)
+            end
+          end
+          context "when launchctl returns empty service pid" do
+            let(:stdout) { StringIO.new <<-SVC_LIST }
+  12761 - 0x100114220.old.machinit.thing
+  - - io.redis.redis-server
+  - - com.lol.stopped-thing
+  SVC_LIST
+
+            before do
+              provider.load_current_resource
+            end
+
+            it "sets resource running state to false" do
+              provider.current_resource.running.should be_false
+            end
+
+            it "sets resouce enabled state to true" do
+              provider.current_resource.enabled.should be_true
+            end
+          end
+
+          context "when launchctl doesn't return service entry at all" do
+            let(:stdout) { StringIO.new <<-SVC_LIST }
+  12761 - 0x100114220.old.machinit.thing
+  - - com.lol.stopped-thing
+  SVC_LIST
+
+            it "sets service running state to false" do
+              provider.load_current_resource
+              provider.current_resource.running.should be_false
+            end
+
+            context "and plist for service is not available" do
+              before do
+                Dir.stub!(:glob).and_return([])
+                provider.load_current_resource
+              end
+
+              it "sets resouce enabled state to false" do
+                provider.current_resource.enabled.should be_false
+              end
+            end
+
+            context "and plist for service is available" do
+              before do
+                Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
+                provider.load_current_resource
+              end
+
+              it "sets resouce enabled state to true" do
+                provider.current_resource.enabled.should be_true
+              end
+            end
+
+            describe "and several plists match service name" do
+              it "throws exception" do
+                Dir.stub!(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist",
+                                             "/Users/wtf/something.plist"])
+                provider.load_current_resource
+                provider.define_resource_requirements
+                lambda { provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+              end
+            end
+          end
+        end
+        describe "#start_service" do
+          before do
+            Chef::Resource::Service.stub!(:new).and_return(current_resource)
+            provider.load_current_resource
+            current_resource.stub!(:running).and_return(false)
+          end
+
+          it "calls the start command if one is specified and service is not running" do
+            new_resource.stub!(:start_command).and_return("cowsay dirty")
+
+            provider.should_receive(:shell_out!).with("cowsay dirty")
+            provider.start_service
+          end
+
+          it "shows warning message if service is already running" do
+            current_resource.stub!(:running).and_return(true)
+            Chef::Log.should_receive(:debug).with("service[#{service_name}] already running, not starting")
+
+            provider.start_service
+          end
+
+          it "starts service via launchctl if service found" do
+            provider.should_receive(:shell_out!).
+                     with("launchctl load -w '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'",
+                           :group => 1001, :user => 101).
+                     and_return(0)
+
+            provider.start_service
+          end
+        end
+
+        describe "#stop_service" do
+          before do
+            Chef::Resource::Service.stub!(:new).and_return(current_resource)
+
+            provider.load_current_resource
+            current_resource.stub!(:running).and_return(true)
+          end
+
+          it "calls the stop command if one is specified and service is running" do
+            new_resource.stub!(:stop_command).and_return("kill -9 123")
+
+            provider.should_receive(:shell_out!).with("kill -9 123")
+            provider.stop_service
+          end
+
+          it "shows warning message if service is not running" do
+            current_resource.stub!(:running).and_return(false)
+            Chef::Log.should_receive(:debug).with("service[#{service_name}] not running, not stopping")
+
+            provider.stop_service
+          end
+
+          it "stops the service via launchctl if service found" do
+            provider.should_receive(:shell_out!).
+                     with("launchctl unload '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'",
+                          :group => 1001, :user => 101).
+                     and_return(0)
+
+            provider.stop_service
+          end
+        end
+
+        describe "#restart_service" do
+          before do
+            Chef::Resource::Service.stub!(:new).and_return(current_resource)
+
+            provider.load_current_resource
+            current_resource.stub!(:running).and_return(true)
+            provider.stub!(:sleep)
+          end
+
+          it "issues a command if given" do
+            new_resource.stub!(:restart_command).and_return("reload that thing")
+
+            provider.should_receive(:shell_out!).with("reload that thing")
+            provider.restart_service
+          end
+
+          it "stops and then starts service" do
+            provider.should_receive(:stop_service)
+            provider.should_receive(:start_service);
+
+            provider.restart_service
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/unit/provider/service/redhat_spec.rb b/spec/unit/provider/service/redhat_spec.rb
new file mode 100644
index 0000000..3ce4301
--- /dev/null
+++ b/spec/unit/provider/service/redhat_spec.rb
@@ -0,0 +1,156 @@
+#
+# Author:: AJ Christensen (<aj at hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
+require 'ostruct'
+
+shared_examples_for "define_resource_requirements_common" do
+  it "should raise an error if /sbin/chkconfig does not exist" do
+    File.stub!(:exists?).with("/sbin/chkconfig").and_return(false)
+    @provider.stub!(:shell_out).with("/sbin/service chef status").and_raise(Errno::ENOENT)
+    @provider.stub!(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_raise(Errno::ENOENT)
+    @provider.load_current_resource
+    @provider.define_resource_requirements
+    lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+  end
+
+  it "should not raise an error if the service exists but is not added to any runlevels" do
+    status = mock("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
+    @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+    chkconfig = mock("Chkconfig", :exitstatus => 0, :stdout => "", :stderr => "service chef supports chkconfig, but is not referenced in any runlevel (run 'chkconfig --add chef')")
+    @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+    @provider.load_current_resource
+    @provider.define_resource_requirements
+    lambda { @provider.process_resource_requirements }.should_not raise_error
+  end
+end
+
+describe "Chef::Provider::Service::Redhat" do
+
+  before(:each) do
+    @node = Chef::Node.new
+    @node.automatic_attrs[:command] = {:ps => 'foo'}
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("chef")
+
+    @current_resource = Chef::Resource::Service.new("chef")
+
+    @provider = Chef::Provider::Service::Redhat.new(@new_resource, @run_context)
+    @provider.action = :start
+    Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+    File.stub!(:exists?).with("/sbin/chkconfig").and_return(true)
+  end
+
+  describe "while not in why run mode" do
+    before(:each) do
+      Chef::Config[:why_run] = false
+    end
+
+    describe "load current resource" do
+      it "sets the current enabled status to true if the service is enabled for any run level" do
+        status = mock("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
+        @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+        chkconfig = mock("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:off   2:off   3:off   4:off   5:on  6:off", :stderr => "")
+        @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        @provider.instance_variable_get("@service_missing").should be_false
+        @provider.load_current_resource
+        @current_resource.enabled.should be_true
+      end
+
+      it "sets the current enabled status to false if the regex does not match" do
+        status = mock("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
+        @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+        chkconfig = mock("Chkconfig", :exitstatus => 0, :stdout => "chef    0:off   1:off   2:off   3:off   4:off   5:off   6:off", :stderr => "")
+        @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        @provider.instance_variable_get("@service_missing").should be_false
+        @provider.load_current_resource.should eql(@current_resource)
+        @current_resource.enabled.should be_false
+      end
+    end
+
+    describe "define resource requirements" do
+      it_should_behave_like "define_resource_requirements_common"
+
+      context "when the service does not exist" do
+        before do
+          status = mock("Status", :exitstatus => 1, :stdout => "", :stderr => "chef: unrecognized service")
+          @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+          chkconfig = mock("Chkconfig", :existatus=> 1, :stdout => "", :stderr => "error reading information on service chef: No such file or directory")
+          @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+          @provider.load_current_resource
+          @provider.define_resource_requirements
+        end
+
+        [ "start", "reload", "restart", "enable" ].each do |action|
+          it "should raise an error when the action is #{action}" do
+            @provider.action = action
+            lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+          end
+        end
+
+        [ "stop", "disable" ].each do |action|
+          it "should not raise an error when the action is #{action}" do
+            @provider.action = action
+            lambda { @provider.process_resource_requirements }.should_not raise_error
+          end
+        end
+      end
+    end
+  end
+
+  describe "while in why run mode" do
+    before(:each) do
+      Chef::Config[:why_run] = true
+    end
+
+    after do
+      Chef::Config[:why_run] = false
+    end
+
+    describe "define resource requirements" do
+      it_should_behave_like "define_resource_requirements_common"
+
+      it "should not raise an error if the service does not exist" do
+        status = mock("Status", :exitstatus => 1, :stdout => "", :stderr => "chef: unrecognized service")
+        @provider.should_receive(:shell_out).with("/sbin/service chef status").and_return(status)
+        chkconfig = mock("Chkconfig", :existatus=> 1, :stdout => "", :stderr => "error reading information on service chef: No such file or directory")
+        @provider.should_receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+        @provider.load_current_resource
+        @provider.define_resource_requirements
+        lambda { @provider.process_resource_requirements }.should_not raise_error
+      end
+    end
+  end
+
+  describe "enable_service" do
+    it "should call chkconfig to add 'service_name'" do
+      @provider.should_receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} on")
+      @provider.enable_service
+    end
+  end
+
+  describe "disable_service" do
+    it "should call chkconfig to del 'service_name'" do
+      @provider.should_receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} off")
+      @provider.disable_service
+    end
+  end
+
+end
diff --git a/spec/unit/provider/service/simple_service_spec.rb b/spec/unit/provider/service/simple_service_spec.rb
new file mode 100644
index 0000000..73cb376
--- /dev/null
+++ b/spec/unit/provider/service/simple_service_spec.rb
@@ -0,0 +1,171 @@
+#
+# Author:: Mathieu Sauve-Frankel <msf at kisoku.net>
+# Copyright:: Copyright (c) 2009, Mathieu Sauve Frankel
+# 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::Service::Simple, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @node.automatic_attrs[:command] = {:ps => "ps -ef"}
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("chef")
+    @current_resource = Chef::Resource::Service.new("chef")
+
+    @provider = Chef::Provider::Service::Simple.new(@new_resource, @run_context)
+    Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+    @stdout = StringIO.new(<<-NOMOCKINGSTRINGSPLZ)
+aj        7842  5057  0 21:26 pts/2    00:00:06 vi init.rb
+aj        7903  5016  0 21:26 pts/5    00:00:00 /bin/bash
+aj        8119  6041  0 21:34 pts/3    00:00:03 vi simple_service_spec.rb
+NOMOCKINGSTRINGSPLZ
+    @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+    @provider.stub!(:shell_out!).and_return(@status)
+  end
+
+  it "should create a current resource with the name of the new resource" do
+    Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+    @provider.load_current_resource
+  end
+
+  it "should set the current resources service name to the new resources service name" do
+    @current_resource.should_receive(:service_name).with(@new_resource.service_name)
+    @provider.load_current_resource
+  end
+
+  it "should raise error if the node has a nil ps attribute and no other means to get status" do
+    @node.automatic_attrs[:command] = {:ps => nil}
+    @provider.define_resource_requirements
+    lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+  end
+
+  it "should raise error if the node has an empty ps attribute and no other means to get status" do
+    @node.automatic_attrs[:command] = {:ps => ""}
+    @provider.define_resource_requirements
+    lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+  end
+
+  describe "when we have a 'ps' attribute" do
+    it "should shell_out! the node's ps command" do
+      @provider.should_receive(:shell_out!).with(@node[:command][:ps]).and_return(@status)
+      @provider.load_current_resource
+    end
+
+    it "should read stdout of the ps command" do
+      @provider.stub!(:shell_out!).and_return(@status)
+      @stdout.should_receive(:each_line).and_return(true)
+      @provider.load_current_resource
+    end
+
+    it "should set running to true if the regex matches the output" do
+      @stdout = StringIO.new(<<-NOMOCKINGSTRINGSPLZ)
+aj        7842  5057  0 21:26 pts/2    00:00:06 chef
+aj        7842  5057  0 21:26 pts/2    00:00:06 poos
+NOMOCKINGSTRINGSPLZ
+      @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+      @provider.stub!(:shell_out!).and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_true
+    end
+
+    it "should set running to false if the regex doesn't match" do
+      @provider.stub!(:shell_out!).and_return(@status)
+      @provider.load_current_resource
+      @current_resource.running.should be_false
+    end
+
+    it "should raise an exception if ps fails" do
+      @provider.stub!(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
+      @provider.action = :start
+      @provider.load_current_resource
+      @provider.define_resource_requirements
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+  end
+
+  it "should return the current resource" do
+    @provider.load_current_resource.should eql(@current_resource)
+  end
+
+
+
+  describe "when starting the service" do
+    it "should call the start command if one is specified" do
+      @new_resource.stub!(:start_command).and_return("#{@new_resource.start_command}")
+      @provider.should_receive(:shell_out!).with("#{@new_resource.start_command}")
+      @provider.start_service()
+    end
+
+    it "should raise an exception if no start command is specified" do
+      @provider.define_resource_requirements
+      @provider.action = :start
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+  end
+
+  describe "when stopping a service" do
+    it "should call the stop command if one is specified" do
+      @new_resource.stop_command("/etc/init.d/themadness stop")
+      @provider.should_receive(:shell_out!).with("/etc/init.d/themadness stop")
+      @provider.stop_service()
+    end
+
+    it "should raise an exception if no stop command is specified" do
+      @provider.define_resource_requirements
+      @provider.action = :stop
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+  end
+
+  describe Chef::Provider::Service::Simple, "restart_service" do
+    it "should call the restart command if one has been specified" do
+      @new_resource.restart_command("/etc/init.d/foo restart")
+      @provider.should_receive(:shell_out!).with("/etc/init.d/foo restart")
+      @provider.restart_service()
+    end
+
+    it "should raise an exception if the resource doesn't support restart, no restart command is provided, and no stop command is provided" do
+      @provider.define_resource_requirements
+      @provider.action = :restart
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::Service)
+    end
+
+    it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do
+      @provider.should_receive(:stop_service)
+      @provider.should_receive(:sleep).with(1)
+      @provider.should_receive(:start_service)
+      @provider.restart_service()
+    end
+  end
+
+  describe Chef::Provider::Service::Simple, "reload_service" do
+    it "should raise an exception if reload is requested but no command is specified" do
+      @provider.define_resource_requirements
+      @provider.action = :reload
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+
+    it "should should run the user specified reload command if one is specified" do
+      @new_resource.reload_command("kill -9 1")
+      @provider.should_receive(:shell_out!).with("kill -9 1")
+      @provider.reload_service()
+    end
+  end
+end
diff --git a/spec/unit/provider/service/solaris_smf_service_spec.rb b/spec/unit/provider/service/solaris_smf_service_spec.rb
new file mode 100644
index 0000000..64afee0
--- /dev/null
+++ b/spec/unit/provider/service/solaris_smf_service_spec.rb
@@ -0,0 +1,143 @@
+#
+# Author:: Toomas Pelberg (<toomasp at gmx.net>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Service::Solaris do
+  before(:each) do
+    @node =Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new('chef')
+
+    @current_resource = Chef::Resource::Service.new('chef')
+
+    @provider = Chef::Provider::Service::Solaris.new(@new_resource, @run_context)
+    Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+    @stdin = StringIO.new
+    @stdout = StringIO.new
+    @stderr = StringIO.new
+    @pid = 2342
+    @stdout_string = "state disabled"
+    @stdout.stub!(:gets).and_return(@stdout_string)
+    @status = mock("Status", :exitstatus => 0, :stdout => @stdout)
+    @provider.stub!(:shell_out!).and_return(@status)
+  end
+
+  it "should raise an error if /bin/svcs does not exist" do
+    File.should_receive(:exists?).with("/bin/svcs").and_return(false)
+    lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Service)
+  end
+
+  describe "on a host with /bin/svcs" do
+
+    before do
+      File.stub!(:exists?).with('/bin/svcs').and_return(true)
+    end
+
+    describe "when discovering the current service state" do
+      it "should create a current resource with the name of the new resource" do
+        @provider.stub!(:popen4).with("/bin/svcs -l chef").and_return(@status)
+        Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+        @provider.load_current_resource
+      end
+
+
+      it "should return the current resource" do
+        @provider.stub!(:popen4).with("/bin/svcs -l chef").and_return(@status)
+        @provider.load_current_resource.should eql(@current_resource)
+      end
+
+      it "should popen4 '/bin/svcs -l service_name'" do
+        @provider.should_receive(:popen4).with("/bin/svcs -l chef").and_return(@status)
+        @provider.load_current_resource
+      end
+
+      it "should mark service as not running" do
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @current_resource.should_receive(:running).with(false)
+        @provider.load_current_resource
+      end
+
+      it "should mark service as running" do
+        @stdout.stub!(:each).and_yield("state online")
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @current_resource.should_receive(:running).with(true)
+        @provider.load_current_resource
+      end
+    end
+
+    describe "when enabling the service" do
+      before(:each) do
+        @provider.current_resource = @current_resource
+        @current_resource.enabled(true)
+      end
+
+      it "should call svcadm enable -s chef" do
+        @new_resource.stub!(:enable_command).and_return("#{@new_resource.enable_command}")
+        @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
+				@provider.enable_service.should be_true
+        @current_resource.enabled.should be_true
+      end
+
+      it "should call svcadm enable -s chef for start_service" do
+        @new_resource.stub!(:start_command).and_return("#{@new_resource.start_command}")
+        @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm enable -s #{@current_resource.service_name}").and_return(@status)
+        @provider.start_service.should be_true
+        @current_resource.enabled.should be_true
+      end
+
+    end
+
+
+    describe "when disabling the service" do
+      before(:each) do
+        @provider.current_resource = @current_resource
+        @current_resource.enabled(false)
+      end
+
+      it "should call svcadm disable -s chef" do
+        @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef").and_return(@status)
+        @provider.disable_service.should be_true
+        @current_resource.enabled.should be_false
+      end
+
+      it "should call svcadm disable -s chef for stop_service" do
+        @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm disable -s chef")
+        @provider.stop_service.should be_true
+        @current_resource.enabled.should be_false
+      end
+
+    end
+
+    describe "when reloading the service" do
+      before(:each) do
+        @status = mock("Process::Status", :exitstatus => 0)
+        @provider.current_resource = @current_resource
+      end
+
+      it "should call svcadm refresh chef" do
+        @provider.should_receive(:shell_out!).with("/usr/sbin/svcadm refresh chef").and_return(@status)
+        @provider.reload_service
+      end
+
+    end
+  end
+end
diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb
new file mode 100644
index 0000000..a107888
--- /dev/null
+++ b/spec/unit/provider/service/systemd_service_spec.rb
@@ -0,0 +1,239 @@
+#
+# Author:: Stephen Haynes (<sh at nomitor.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Service::Systemd do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Service.new('rsyslog.service')
+    @provider = Chef::Provider::Service::Systemd.new(@new_resource, @run_context)
+  end
+
+  describe "load_current_resource" do
+    before(:each) do
+      @current_resource = Chef::Resource::Service.new('rsyslog.service')
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+      @provider.stub!(:is_active?).and_return(false)
+      @provider.stub!(:is_enabled?).and_return(false)
+    end
+
+    it "should create a current resource with the name of the new resource" do
+      Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+      @provider.load_current_resource
+    end
+
+    it "should set the current resources service name to the new resources service name" do
+      @current_resource.should_receive(:service_name).with(@new_resource.service_name)
+      @provider.load_current_resource
+    end
+
+    it "should check if the service is running" do
+      @provider.should_receive(:is_active?)
+      @provider.load_current_resource
+    end
+
+    it "should set running to true if the service is running" do
+      @provider.stub!(:is_active?).and_return(true)
+      @current_resource.should_receive(:running).with(true)
+      @provider.load_current_resource
+    end
+
+    it "should set running to false if the service is not running" do
+      @provider.stub!(:is_active?).and_return(false)
+      @current_resource.should_receive(:running).with(false)
+      @provider.load_current_resource
+    end
+
+    describe "when a status command has been specified" do
+      before do
+        @new_resource.stub!(:status_command).and_return("/bin/chefhasmonkeypants status")
+      end
+
+      it "should run the services status command if one has been specified" do
+        @provider.stub!(:run_command_with_systems_locale).with({:command => "/bin/chefhasmonkeypants status"}).and_return(0)
+        @current_resource.should_receive(:running).with(true)
+        @provider.load_current_resource
+      end
+
+      it "should run the services status command if one has been specified and properly set status check state" do
+        @provider.stub!(:run_command_with_systems_locale).with({:command => "/bin/chefhasmonkeypants status"}).and_return(0)
+        @provider.load_current_resource
+        @provider.instance_variable_get("@status_check_success").should be_true
+      end
+
+      it "should set running to false if it catches a Chef::Exceptions::Exec when using a status command" do
+        @provider.stub!(:run_command_with_systems_locale).and_raise(Chef::Exceptions::Exec)
+        @current_resource.should_receive(:running).with(false)
+        @provider.load_current_resource
+      end
+
+      it "should update state to indicate status check failed when an exception is thrown using a status command" do
+        @provider.stub!(:run_command_with_systems_locale).and_raise(Chef::Exceptions::Exec)
+        @provider.load_current_resource
+        @provider.instance_variable_get("@status_check_success").should be_false
+      end
+    end
+
+    it "should check if the service is enabled" do
+      @provider.should_receive(:is_enabled?)
+      @provider.load_current_resource
+    end
+
+    it "should set enabled to true if the service is enabled" do
+      @provider.stub!(:is_enabled?).and_return(true)
+      @current_resource.should_receive(:enabled).with(true)
+      @provider.load_current_resource
+    end
+
+    it "should set enabled to false if the service is not enabled" do
+      @provider.stub!(:is_enabled?).and_return(false)
+      @current_resource.should_receive(:enabled).with(false)
+      @provider.load_current_resource
+    end
+
+    it "should return the current resource" do
+      @provider.load_current_resource.should eql(@current_resource)
+    end
+  end
+
+  describe "start and stop service" do
+    before(:each) do
+      @current_resource = Chef::Resource::Service.new('rsyslog.service')
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      @provider.current_resource = @current_resource
+    end
+
+    it "should call the start command if one is specified" do
+      @new_resource.stub!(:start_command).and_return("/sbin/rsyslog startyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog startyousillysally")
+      @provider.start_service
+    end
+
+    it "should call '/bin/systemctl start service_name' if no start command is specified" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl start #{@new_resource.service_name}"}).and_return(0)
+      @provider.start_service
+    end
+
+    it "should not call '/bin/systemctl start service_name' if it is already running" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl start #{@new_resource.service_name}"})
+      @provider.start_service
+    end
+
+    it "should call the restart command if one is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @new_resource.stub!(:restart_command).and_return("/sbin/rsyslog restartyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog restartyousillysally")
+      @provider.restart_service
+    end
+
+    it "should call '/bin/systemctl restart service_name' if no restart command is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl restart #{@new_resource.service_name}"}).and_return(0)
+      @provider.restart_service
+    end
+
+    it "should call the reload command if one is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @new_resource.stub!(:reload_command).and_return("/sbin/rsyslog reloadyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog reloadyousillysally")
+      @provider.reload_service
+    end
+
+    it "should call '/bin/systemctl reload service_name' if no reload command is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl reload #{@new_resource.service_name}"}).and_return(0)
+      @provider.reload_service
+    end
+
+    it "should call the stop command if one is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @new_resource.stub!(:stop_command).and_return("/sbin/rsyslog stopyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog stopyousillysally")
+      @provider.stop_service
+    end
+
+    it "should call '/bin/systemctl stop service_name' if no stop command is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl stop #{@new_resource.service_name}"}).and_return(0)
+      @provider.stop_service
+    end
+
+    it "should not call '/bin/systemctl stop service_name' if it is already stopped" do
+      @current_resource.stub!(:running).and_return(false)
+      @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl stop #{@new_resource.service_name}"})
+      @provider.stop_service
+    end
+  end
+
+  describe "enable and disable service" do
+    before(:each) do
+      @current_resource = Chef::Resource::Service.new('rsyslog.service')
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      @provider.current_resource = @current_resource
+    end
+
+    it "should call '/bin/systemctl enable service_name' to enable the service" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl enable #{@new_resource.service_name}"}).and_return(0)
+      @provider.enable_service
+    end
+
+    it "should call '/bin/systemctl disable service_name' to disable the service" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/bin/systemctl disable #{@new_resource.service_name}"}).and_return(0)
+      @provider.disable_service
+    end
+  end
+
+  describe "is_active?" do
+    before(:each) do
+      @current_resource = Chef::Resource::Service.new('rsyslog.service')
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+    end
+
+    it "should return true if '/bin/systemctl is-active service_name' returns 0" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-active rsyslog.service', :ignore_failure => true}).and_return(0)
+      @provider.is_active?.should be_true
+    end
+
+    it "should return false if '/bin/systemctl is-active service_name' returns anything except 0" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-active rsyslog.service', :ignore_failure => true}).and_return(1)
+      @provider.is_active?.should be_false
+    end
+  end
+
+  describe "is_enabled?" do
+    before(:each) do
+      @current_resource = Chef::Resource::Service.new('rsyslog.service')
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+    end
+
+    it "should return true if '/bin/systemctl is-enabled service_name' returns 0" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-enabled rsyslog.service', :ignore_failure => true}).and_return(0)
+      @provider.is_enabled?.should be_true
+    end
+
+    it "should return false if '/bin/systemctl is-enabled service_name' returns anything except 0" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => '/bin/systemctl is-enabled rsyslog.service', :ignore_failure => true}).and_return(1)
+      @provider.is_enabled?.should be_false
+    end
+  end
+end
diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb
new file mode 100644
index 0000000..8628a4e
--- /dev/null
+++ b/spec/unit/provider/service/upstart_service_spec.rb
@@ -0,0 +1,314 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Copyright:: Copyright (c) 2010 Bryan McLellan
+# 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::Service::Upstart do
+  before(:each) do
+    @node =Chef::Node.new
+    @node.name('upstarter')
+    @node.automatic_attrs[:platform] = 'ubuntu'
+    @node.automatic_attrs[:platform_version] = '9.10'
+
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::Service.new("rsyslog")
+    @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+  end
+
+  describe "when first created" do
+    before do
+      @platform = nil
+    end
+
+    it "should return /etc/event.d as the upstart job directory when running on Ubuntu 9.04" do
+      @node.automatic_attrs[:platform_version] = '9.04'
+      #Chef::Platform.stub!(:find_platform_and_version).and_return([ "ubuntu", "9.04" ])
+      @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+      @provider.instance_variable_get(:@upstart_job_dir).should == "/etc/event.d"
+      @provider.instance_variable_get(:@upstart_conf_suffix).should == ""
+    end
+
+    it "should return /etc/init as the upstart job directory when running on Ubuntu 9.10" do
+      @node.automatic_attrs[:platform_version] = '9.10'
+      @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+      @provider.instance_variable_get(:@upstart_job_dir).should == "/etc/init"
+      @provider.instance_variable_get(:@upstart_conf_suffix).should == ".conf"
+    end
+
+    it "should return /etc/init as the upstart job directory by default" do
+      @node.automatic_attrs[:platform_version] = '9000'
+      @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+      @provider.instance_variable_get(:@upstart_job_dir).should == "/etc/init"
+      @provider.instance_variable_get(:@upstart_conf_suffix).should == ".conf"
+    end
+  end
+
+  describe "load_current_resource" do
+    before(:each) do
+      @node.automatic_attrs[:command] = {:ps => "ps -ax"}
+
+      @current_resource = Chef::Resource::Service.new("rsyslog")
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+
+      @status = mock("Status", :exitstatus => 0)
+      @provider.stub!(:popen4).and_return(@status)
+      @stdin = StringIO.new
+      @stdout = StringIO.new
+      @stderr = StringIO.new
+      @pid = mock("PID")
+
+      ::File.stub!(:exists?).and_return(true)
+      ::File.stub!(:open).and_return(true)
+    end
+
+    it "should create a current resource with the name of the new resource" do
+      Chef::Resource::Service.should_receive(:new).and_return(@current_resource)
+      @provider.load_current_resource
+    end
+
+    it "should set the current resources service name to the new resources service name" do
+      @current_resource.should_receive(:service_name).with(@new_resource.service_name)
+      @provider.load_current_resource
+    end
+
+    it "should run '/sbin/status rsyslog'" do
+      @provider.should_receive(:popen4).with("/sbin/status rsyslog").and_return(@status)
+      @provider.load_current_resource
+    end
+
+    describe "when the status command uses the new format" do
+      before do
+      end
+
+      it "should set running to true if the status command returns 0" do
+        @stdout = StringIO.new("rsyslog start/running")
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.load_current_resource
+        @current_resource.running.should be_true
+      end
+
+      it "should set running to false if the status command returns anything except 0" do
+        @stdout = StringIO.new("rsyslog stop/waiting")
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.load_current_resource
+        @current_resource.running.should be_false
+      end
+    end
+
+    describe "when the status command uses the old format" do
+      it "should set running to true if the status command returns 0" do
+        @stdout = StringIO.new("rsyslog (start) running, process 32225")
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.load_current_resource
+        @current_resource.running.should be_true
+      end
+
+      it "should set running to false if the status command returns anything except 0" do
+        @stdout = StringIO.new("rsyslog (stop) waiting")
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.load_current_resource
+        @current_resource.running.should be_false
+      end
+    end
+
+    it "should set running to false if it catches a Chef::Exceptions::Exec" do
+      @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec)
+      @current_resource.should_receive(:running).with(false)
+      @provider.load_current_resource
+    end
+
+    it "should set enabled to true when it finds 'starts on'" do
+      @lines = mock("start on filesystem", :gets => "start on filesystem")
+      ::File.stub!(:open).and_yield(@lines)
+      @current_resource.should_receive(:running).with(false)
+      @provider.load_current_resource
+    end
+
+    it "should set enabled to false when it finds '#starts on'" do
+      @lines = mock("start on filesystem", :gets => "#start on filesystem")
+      ::File.stub!(:open).and_yield(@lines)
+      @current_resource.should_receive(:running).with(false)
+      @provider.load_current_resource
+    end
+
+    it "should assume disable when no job configuration file is found" do
+      ::File.stub!(:exists?).and_return(false)
+      @current_resource.should_receive(:running).with(false)
+      @provider.load_current_resource
+    end
+
+
+    it "should track state when the upstart configuration file fails to load" do
+      File.should_receive(:exists?).and_return false
+      @provider.load_current_resource
+      @provider.instance_variable_get("@config_file_found").should == false
+    end
+
+    describe "when a status command has been specified" do
+      before do
+        @new_resource.stub!(:status_command).and_return("/bin/chefhasmonkeypants status")
+      end
+
+      it "should run the services status command if one has been specified" do
+        @provider.stub!(:run_command_with_systems_locale).with({:command => "/bin/chefhasmonkeypants status"}).and_return(0)
+        @current_resource.should_receive(:running).with(true)
+        @provider.load_current_resource
+      end
+
+      it "should track state when the user-provided status command fails" do
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec)
+        @provider.load_current_resource
+        @provider.instance_variable_get("@command_success").should == false
+      end
+
+      it "should set running to false if it catches a Chef::Exceptions::Exec when using a status command" do
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_raise(Chef::Exceptions::Exec)
+        @current_resource.should_receive(:running).with(false)
+        @provider.load_current_resource
+      end
+    end
+
+    it "should track state when we fail to obtain service status via upstart_state" do
+      @provider.should_receive(:upstart_state).and_raise Chef::Exceptions::Exec
+      @provider.load_current_resource
+      @provider.instance_variable_get("@command_success").should == false
+    end
+
+    it "should return the current resource" do
+      @provider.load_current_resource.should eql(@current_resource)
+    end
+
+
+  end
+
+  describe "enable and disable service" do
+    before(:each) do
+      @current_resource = Chef::Resource::Service.new('rsyslog')
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      @provider.current_resource = @current_resource
+      Chef::Util::FileEdit.stub!(:new)
+    end
+
+    it "should enable the service if it is not enabled" do
+      @file = Object.new
+      Chef::Util::FileEdit.stub!(:new).and_return(@file)
+      @current_resource.stub!(:enabled).and_return(false)
+      @file.should_receive(:search_file_replace)
+      @file.should_receive(:write_file)
+      @provider.enable_service()
+    end
+
+    it "should disable the service if it is enabled" do
+      @file = Object.new
+      Chef::Util::FileEdit.stub!(:new).and_return(@file)
+      @current_resource.stub!(:enabled).and_return(true)
+      @file.should_receive(:search_file_replace)
+      @file.should_receive(:write_file)
+      @provider.disable_service()
+    end
+
+  end
+
+  describe "start and stop service" do
+    before(:each) do
+      @current_resource = Chef::Resource::Service.new('rsyslog')
+
+      Chef::Resource::Service.stub!(:new).and_return(@current_resource)
+      @provider.current_resource = @current_resource
+    end
+
+    it "should call the start command if one is specified" do
+      @new_resource.stub!(:start_command).and_return("/sbin/rsyslog startyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog startyousillysally")
+      @provider.start_service()
+    end
+
+    it "should call '/sbin/start service_name' if no start command is specified" do
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0)
+      @provider.start_service()
+    end
+
+    it "should not call '/sbin/start service_name' if it is already running" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"})
+      @provider.start_service()
+    end
+
+    it "should pass parameters to the start command if they are provided" do
+      @new_resource = Chef::Resource::Service.new("rsyslog")
+      @new_resource.parameters({ "OSD_ID" => "2" })
+      @provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
+      @provider.current_resource = @current_resource
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start rsyslog OSD_ID=2"}).and_return(0)
+      @provider.start_service()
+    end
+
+    it "should call the restart command if one is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @new_resource.stub!(:restart_command).and_return("/sbin/rsyslog restartyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog restartyousillysally")
+      @provider.restart_service()
+    end
+
+    it "should call '/sbin/restart service_name' if no restart command is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/restart #{@new_resource.service_name}"}).and_return(0)
+      @provider.restart_service()
+    end
+
+    it "should call '/sbin/start service_name' if restart_service is called for a stopped service" do
+      @current_resource.stub!(:running).and_return(false)
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/start #{@new_resource.service_name}"}).and_return(0)
+      @provider.restart_service()
+    end
+
+    it "should call the reload command if one is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @new_resource.stub!(:reload_command).and_return("/sbin/rsyslog reloadyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog reloadyousillysally")
+      @provider.reload_service()
+    end
+
+    it "should call '/sbin/reload service_name' if no reload command is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/reload #{@new_resource.service_name}"}).and_return(0)
+      @provider.reload_service()
+    end
+
+    it "should call the stop command if one is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @new_resource.stub!(:stop_command).and_return("/sbin/rsyslog stopyousillysally")
+      @provider.should_receive(:shell_out!).with("/sbin/rsyslog stopyousillysally")
+      @provider.stop_service()
+    end
+
+    it "should call '/sbin/stop service_name' if no stop command is specified" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"}).and_return(0)
+      @provider.stop_service()
+    end
+
+    it "should not call '/sbin/stop service_name' if it is already stopped" do
+      @current_resource.stub!(:running).and_return(false)
+      @provider.should_not_receive(:run_command_with_systems_locale).with({:command => "/sbin/stop #{@new_resource.service_name}"})
+      @provider.stop_service()
+    end
+  end
+end
diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb
new file mode 100644
index 0000000..7ec4ccf
--- /dev/null
+++ b/spec/unit/provider/service/windows_spec.rb
@@ -0,0 +1,235 @@
+#
+# Author:: Nuo Yan <nuo at opscode.com>
+# Author:: Seth Chisamore <schisamo at opscode.com>
+# Copyright:: Copyright (c) 2010-2011 Opscode, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Service::Windows, "load_current_resource" do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Service.new("chef")
+    @provider = Chef::Provider::Service::Windows.new(@new_resource, @run_context)
+    Object.send(:remove_const, 'Win32') if defined?(Win32)
+    Win32 = Module.new
+    Win32::Service = Class.new
+    Win32::Service::AUTO_START = 0x00000002
+    Win32::Service::DISABLED = 0x00000004
+    Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+      mock("StatusStruct", :current_state => "running"))
+    Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+      mock("ConfigStruct", :start_type => "auto start"))
+    Win32::Service.stub!(:exists?).and_return(true)
+  end
+
+  it "should set the current resources service name to the new resources service name" do
+    @provider.load_current_resource
+    @provider.current_resource.service_name.should == 'chef'
+  end
+
+  it "should return the current resource" do
+    @provider.load_current_resource.should equal(@provider.current_resource)
+  end
+
+  it "should set the current resources status" do
+    @provider.load_current_resource
+    @provider.current_resource.running.should be_true
+  end
+
+  it "should set the current resources start type" do
+    @provider.load_current_resource
+    @provider.current_resource.enabled.should be_true
+  end
+
+  describe Chef::Provider::Service::Windows, "start_service" do
+    before(:each) do
+      Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+        mock("StatusStruct", :current_state => "stopped"),
+        mock("StatusStruct", :current_state => "running"))
+    end
+
+    it "should call the start command if one is specified" do
+      @new_resource.start_command "sc start chef"
+      @provider.should_receive(:shell_out!).with("#{@new_resource.start_command}").and_return("Starting custom service")
+      @provider.start_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should use the built-in command if no start command is specified" do
+      Win32::Service.should_receive(:start).with(@new_resource.service_name)
+      @provider.start_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should do nothing if the service does not exist" do
+      Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+      Win32::Service.should_not_receive(:start).with(@new_resource.service_name)
+      @provider.start_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+
+    it "should do nothing if the service is running" do
+      Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+        mock("StatusStruct", :current_state => "running"))
+      @provider.load_current_resource
+      Win32::Service.should_not_receive(:start).with(@new_resource.service_name)
+      @provider.start_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+  end
+
+  describe Chef::Provider::Service::Windows, "stop_service" do
+
+    before(:each) do
+      Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+        mock("StatusStruct", :current_state => "running"),
+        mock("StatusStruct", :current_state => "stopped"))
+    end
+
+    it "should call the stop command if one is specified" do
+      @new_resource.stop_command "sc stop chef"
+      @provider.should_receive(:shell_out!).with("#{@new_resource.stop_command}").and_return("Stopping custom service")
+      @provider.stop_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should use the built-in command if no stop command is specified" do
+      Win32::Service.should_receive(:stop).with(@new_resource.service_name)
+      @provider.stop_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should do nothing if the service does not exist" do
+      Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+      Win32::Service.should_not_receive(:stop).with(@new_resource.service_name)
+      @provider.stop_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+
+    it "should do nothing if the service is stopped" do
+      Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+        mock("StatusStruct", :current_state => "stopped"))
+      @provider.load_current_resource
+      Win32::Service.should_not_receive(:stop).with(@new_resource.service_name)
+      @provider.stop_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+  end
+
+  describe Chef::Provider::Service::Windows, "restart_service" do
+
+    it "should call the restart command if one is specified" do
+      @new_resource.restart_command "sc restart"
+      @provider.should_receive(:shell_out!).with("#{@new_resource.restart_command}")
+      @provider.restart_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should stop then start the service if it is running" do
+      Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+        mock("StatusStruct", :current_state => "running"),
+        mock("StatusStruct", :current_state => "stopped"),
+        mock("StatusStruct", :current_state => "stopped"),
+        mock("StatusStruct", :current_state => "running"))
+      Win32::Service.should_receive(:stop).with(@new_resource.service_name)
+      Win32::Service.should_receive(:start).with(@new_resource.service_name)
+      @provider.restart_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should just start the service if it is stopped" do
+      Win32::Service.stub!(:status).with(@new_resource.service_name).and_return(
+        mock("StatusStruct", :current_state => "stopped"),
+        mock("StatusStruct", :current_state => "stopped"),
+        mock("StatusStruct", :current_state => "running"))
+      Win32::Service.should_receive(:start).with(@new_resource.service_name)
+      @provider.restart_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should do nothing if the service does not exist" do
+      Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+      Win32::Service.should_not_receive(:stop).with(@new_resource.service_name)
+      Win32::Service.should_not_receive(:start).with(@new_resource.service_name)
+      @provider.restart_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+
+  end
+
+  describe Chef::Provider::Service::Windows, "enable_service" do
+
+    before(:each) do
+      Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+        mock("ConfigStruct", :start_type => "disabled"))
+    end
+
+    it "should enable service" do
+      Win32::Service.should_receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::AUTO_START)
+      @provider.enable_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should do nothing if the service does not exist" do
+      Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+      Win32::Service.should_not_receive(:configure)
+      @provider.enable_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+
+    it "should do nothing if the service is enabled" do
+      Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+        mock("ConfigStruct", :start_type => "auto start"))
+      Win32::Service.should_not_receive(:configure)
+      @provider.enable_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+  end
+
+  describe Chef::Provider::Service::Windows, "disable_service" do
+
+    before(:each) do
+      Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+        mock("ConfigStruct", :start_type => "auto start"))
+    end
+
+    it "should disable service" do
+      Win32::Service.should_receive(:configure).with(:service_name => @new_resource.service_name, :start_type => Win32::Service::DISABLED)
+      @provider.disable_service
+      @new_resource.updated_by_last_action?.should be_true
+    end
+
+    it "should do nothing if the service does not exist" do
+      Win32::Service.stub!(:exists?).with(@new_resource.service_name).and_return(false)
+      Win32::Service.should_not_receive(:configure)
+      @provider.disable_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+
+    it "should do nothing if the service is disabled" do
+      Win32::Service.stub!(:config_info).with(@new_resource.service_name).and_return(
+        mock("ConfigStruct", :start_type => "disabled"))
+      @provider.load_current_resource
+      Win32::Service.should_not_receive(:configure)
+      @provider.disable_service
+      @new_resource.updated_by_last_action?.should be_false
+    end
+
+  end
+end
diff --git a/spec/unit/provider/service_spec.rb b/spec/unit/provider/service_spec.rb
new file mode 100644
index 0000000..3719af5
--- /dev/null
+++ b/spec/unit/provider/service_spec.rb
@@ -0,0 +1,169 @@
+#
+# Author:: AJ Christensen (<aj at hjksolutions.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'
+
+describe Chef::Provider::Service do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::Service.new("chef")
+    @current_resource = Chef::Resource::Service.new("chef")
+
+    @provider = Chef::Provider::Service.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+    @provider.stub!(:load_current_resource)
+  end
+
+  describe "when enabling the service" do
+    it "should enable the service if disabled and set the resource as updated" do
+      @current_resource.enabled(false)
+      @provider.should_receive(:enable_service).and_return(true)
+      @provider.action_enable
+      @provider.set_updated_status
+      @provider.new_resource.should be_updated
+    end
+
+    it "should not enable the service if already enabled" do
+      @current_resource.enabled(true)
+      @provider.should_not_receive(:enable_service)
+      @provider.action_enable
+      @provider.set_updated_status
+      @provider.new_resource.should_not be_updated
+    end
+  end
+
+
+  describe "when disabling the service" do
+    it "should disable the service if enabled and set the resource as updated" do
+      @current_resource.stub!(:enabled).and_return(true)
+      @provider.should_receive(:disable_service).and_return(true)
+      @provider.run_action(:disable)
+      @provider.new_resource.should be_updated
+    end
+
+    it "should not disable the service if already disabled" do
+      @current_resource.stub!(:enabled).and_return(false)
+      @provider.should_not_receive(:disable_service)
+      @provider.run_action(:disable)
+      @provider.new_resource.should_not be_updated
+    end
+  end
+
+  describe "action_start" do
+    it "should start the service if it isn't running and set the resource as updated" do
+      @current_resource.running(false)
+      @provider.should_receive(:start_service).with.and_return(true)
+      @provider.run_action(:start)
+      @provider.new_resource.should be_updated
+    end
+
+    it "should not start the service if already running" do
+      @current_resource.running(true)
+      @provider.should_not_receive(:start_service)
+      @provider.run_action(:start)
+      @provider.new_resource.should_not be_updated
+    end
+  end
+
+  describe "action_stop" do
+    it "should stop the service if it is running and set the resource as updated" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:stop_service).and_return(true)
+      @provider.run_action(:stop)
+      @provider.new_resource.should be_updated
+    end
+
+    it "should not stop the service if it's already stopped" do
+      @current_resource.stub!(:running).and_return(false)
+      @provider.should_not_receive(:stop_service)
+      @provider.run_action(:stop)
+      @provider.new_resource.should_not be_updated
+    end
+  end
+
+  describe "action_restart" do
+    before do
+      @current_resource.supports(:restart => true)
+    end
+
+    it "should restart the service if it's supported and set the resource as updated" do
+      @provider.should_receive(:restart_service).and_return(true)
+      @provider.run_action(:restart)
+      @provider.new_resource.should be_updated
+    end
+
+    it "should restart the service even if it isn't running and set the resource as updated" do
+      @current_resource.stub!(:running).and_return(false)
+      @provider.should_receive(:restart_service).and_return(true)
+      @provider.run_action(:restart)
+      @provider.new_resource.should be_updated
+    end
+  end
+
+  describe "action_reload" do
+    before do
+      @new_resource.supports(:reload => true)
+    end
+
+    it "should raise an exception if reload isn't supported" do
+      @new_resource.supports(:reload => false)
+      @new_resource.stub!(:reload_command).and_return(false)
+      lambda { @provider.run_action(:reload) }.should raise_error(Chef::Exceptions::UnsupportedAction)
+    end
+
+    it "should reload the service if it is running and set the resource as updated" do
+      @current_resource.stub!(:running).and_return(true)
+      @provider.should_receive(:reload_service).and_return(true)
+      @provider.run_action(:reload)
+      @provider.new_resource.should be_updated
+    end
+
+    it "should not reload the service if it's stopped" do
+      @current_resource.stub!(:running).and_return(false)
+      @provider.should_not_receive(:reload_service)
+      @provider.run_action(:stop)
+      @provider.new_resource.should_not be_updated
+    end
+  end
+
+  it "delegates enable_service to subclasses" do
+    lambda { @provider.enable_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "delegates disable_service to subclasses" do
+    lambda { @provider.disable_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "delegates start_service to subclasses" do
+    lambda { @provider.start_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "delegates stop_service to subclasses" do
+    lambda { @provider.stop_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "delegates restart_service to subclasses" do
+    lambda { @provider.restart_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+
+  it "delegates reload_service to subclasses" do
+    lambda { @provider.reload_service }.should raise_error(Chef::Exceptions::UnsupportedAction)
+  end
+end
diff --git a/spec/unit/provider/subversion_spec.rb b/spec/unit/provider/subversion_spec.rb
new file mode 100644
index 0000000..dd020d4
--- /dev/null
+++ b/spec/unit/provider/subversion_spec.rb
@@ -0,0 +1,281 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Provider::Subversion do
+
+  before do
+    @resource = Chef::Resource::Subversion.new("my app")
+    @resource.repository "http://svn.example.org/trunk/"
+    @resource.destination "/my/deploy/dir"
+    @resource.revision "12345"
+    @resource.svn_arguments(false)
+    @resource.svn_info_args(false)
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @provider = Chef::Provider::Subversion.new(@resource, @run_context)
+  end
+
+  it "converts resource attributes to options for run_command and popen4" do
+    @provider.run_options.should == {}
+    @resource.user 'deployninja'
+    @provider.run_options.should == {:user => "deployninja"}
+  end
+
+  context "determining the revision of the currently deployed code" do
+
+    before do
+      @stdout = mock("stdout")
+      @stderr = mock("stderr")
+      @exitstatus = mock("exitstatus")
+    end
+
+    it "sets the revision to nil if there isn't any deployed code yet" do
+      ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(false)
+      @provider.find_current_revision.should be_nil
+    end
+
+    it "determines the current revision if there's a checkout with svn data available" do
+      example_svn_info =  "Path: .\n" +
+                          "URL: http://svn.example.org/trunk/myapp\n" +
+                          "Repository Root: http://svn.example.org\n" +
+                          "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" +
+                          "Revision: 11739\nNode Kind: directory\n" +
+                          "Schedule: normal\n" +
+                          "Last Changed Author: codeninja\n" +
+                          "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision
+                          "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n"
+      ::File.should_receive(:exist?).at_least(1).times.with("/my/deploy/dir/.svn").and_return(true)
+      ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true)
+      ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield
+      @stdout.stub!(:string).and_return(example_svn_info)
+      @stderr.stub!(:string).and_return("")
+      @exitstatus.stub!(:exitstatus).and_return(0)
+      expected_command = ["svn info", {:cwd=>"/my/deploy/dir"}]
+      @provider.should_receive(:popen4).with(*expected_command).
+                                        and_yield("no-pid", "no-stdin", @stdout, at stderr).
+                                        and_return(@exitstatus)
+      @provider.find_current_revision.should eql("11410")
+    end
+
+    it "gives nil as the current revision if the deploy dir isn't a SVN working copy" do
+      example_svn_info = "svn: '/tmp/deploydir' is not a working copy\n"
+      ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
+      ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true)
+      ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield
+      @stdout.stub!(:string).and_return(example_svn_info)
+      @stderr.stub!(:string).and_return("")
+      @exitstatus.stub!(:exitstatus).and_return(1)
+      @provider.should_receive(:popen4).and_yield("no-pid", "no-stdin", @stdout, at stderr).
+                                        and_return(@exitstatus)
+      @provider.find_current_revision.should be_nil
+    end
+
+    it "finds the current revision when loading the current resource state" do
+      # note: the test is kinda janky, but it provides regression coverage for CHEF-2092
+      @resource.instance_variable_set(:@action, :sync)
+      @provider.should_receive(:find_current_revision).and_return("12345")
+      @provider.load_current_resource
+      @provider.current_resource.revision.should == "12345"
+    end
+  end
+
+  it "creates the current_resource object and sets its revision to the current deployment's revision as long as we're not exporting" do
+    @provider.stub!(:find_current_revision).and_return("11410")
+    @provider.new_resource.instance_variable_set :@action, [:checkout]
+    @provider.load_current_resource
+    @provider.current_resource.name.should eql(@resource.name)
+    @provider.current_resource.revision.should eql("11410")
+  end
+
+  context "resolving revisions to an integer" do
+
+    before do
+      @stdout = mock("stdout")
+      @stderr = mock("stderr")
+      @resource.svn_info_args "--no-auth-cache"
+    end
+
+    it "returns the revision number as is if it's already an integer" do
+      @provider.revision_int.should eql("12345")
+    end
+
+    it "queries the server and resolves the revision if it's not an integer (i.e. 'HEAD')" do
+      example_svn_info =  "Path: .\n" +
+                          "URL: http://svn.example.org/trunk/myapp\n" +
+                          "Repository Root: http://svn.example.org\n" +
+                          "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" +
+                          "Revision: 11739\nNode Kind: directory\n" +
+                          "Schedule: normal\n" +
+                          "Last Changed Author: codeninja\n" +
+                          "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision
+                          "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n"
+      exitstatus = mock("exitstatus")
+      exitstatus.stub!(:exitstatus).and_return(0)
+      @resource.revision "HEAD"
+      @stdout.stub!(:string).and_return(example_svn_info)
+      @stderr.stub!(:string).and_return("")
+      expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache  -rHEAD", {:cwd=>Dir.tmpdir}]
+      @provider.should_receive(:popen4).with(*expected_command).
+                                        and_yield("no-pid","no-stdin", at stdout, at stderr).
+                                        and_return(exitstatus)
+      @provider.revision_int.should eql("11410")
+    end
+
+    it "returns a helpful message if data from `svn info` can't be parsed" do
+      example_svn_info =  "some random text from an error message\n"
+      exitstatus = mock("exitstatus")
+      exitstatus.stub!(:exitstatus).and_return(0)
+      @resource.revision "HEAD"
+      @stdout.stub!(:string).and_return(example_svn_info)
+      @stderr.stub!(:string).and_return("")
+      @provider.should_receive(:popen4).and_yield("no-pid","no-stdin", at stdout, at stderr).
+                                        and_return(exitstatus)
+      lambda {@provider.revision_int}.should raise_error(RuntimeError, "Could not parse `svn info` data: some random text from an error message")
+
+    end
+
+    it "responds to :revision_slug as an alias for revision_sha" do
+      @provider.should respond_to(:revision_slug)
+    end
+
+  end
+
+  it "generates a checkout command with default options" do
+    @provider.checkout_command.should eql("svn checkout -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir")
+  end
+
+  it "generates a checkout command with authentication" do
+    @resource.svn_username "deployNinja"
+    @resource.svn_password "vanish!"
+    @provider.checkout_command.should eql("svn checkout -q --username deployNinja --password vanish!  " +
+                                          "-r12345 http://svn.example.org/trunk/ /my/deploy/dir")
+  end
+
+  it "generates a checkout command with arbitrary options" do
+    @resource.svn_arguments "--no-auth-cache"
+    @provider.checkout_command.should eql("svn checkout --no-auth-cache -q  -r12345 "+
+                                          "http://svn.example.org/trunk/ /my/deploy/dir")
+  end
+
+  it "generates a sync command with default options" do
+    @provider.sync_command.should eql("svn update -q  -r12345 /my/deploy/dir")
+  end
+
+  it "generates an export command with default options" do
+    @provider.export_command.should eql("svn export --force -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir")
+  end
+
+  it "doesn't try to find the current revision when loading the resource if running an export" do
+    @provider.new_resource.instance_variable_set :@action, [:export]
+    @provider.should_not_receive(:find_current_revision)
+    @provider.load_current_resource
+  end
+
+  it "doesn't try to find the current revision when loading the resource if running a force export" do
+    @provider.new_resource.instance_variable_set :@action, [:force_export]
+    @provider.should_not_receive(:find_current_revision)
+    @provider.load_current_resource
+  end
+
+  it "runs an export with the --force option" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    expected_cmd = "svn export --force -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+    @provider.should_receive(:run_command).with(:command => expected_cmd)
+    @provider.run_action(:force_export)
+    @resource.should be_updated
+  end
+
+  it "runs the checkout command for action_checkout" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    expected_cmd = "svn checkout -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+    @provider.should_receive(:run_command).with(:command => expected_cmd)
+    @provider.run_action(:checkout)
+    @resource.should be_updated
+  end
+
+  it "raises an error if the svn checkout command would fail because the enclosing directory doesn't exist" do
+    lambda {@provider.run_action(:sync)}.should raise_error(Chef::Exceptions::MissingParentDirectory)
+  end
+
+  it "should not checkout if the destination exists or is a non empty directory" do
+    ::File.stub!(:exist?).with("/my/deploy/dir/.svn").and_return(false)
+    ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true)
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::Dir.stub!(:entries).with("/my/deploy/dir").and_return(['.','..','foo','bar'])
+    @provider.should_not_receive(:checkout_command)
+    @provider.run_action(:checkout)
+    @resource.should_not be_updated
+  end
+
+  it "runs commands with the user and group specified in the resource" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    @resource.user "whois"
+    @resource.group "thisis"
+    expected_cmd = "svn checkout -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+    @provider.should_receive(:run_command).with(:command => expected_cmd, :user => "whois", :group => "thisis")
+    @provider.run_action(:checkout)
+    @resource.should be_updated
+  end
+
+  it "does a checkout for action_sync if there's no deploy dir" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").twice.and_return(false)
+    @provider.should_receive(:action_checkout)
+    @provider.run_action(:sync)
+  end
+
+  it "does a checkout for action_sync if the deploy dir exists but is empty" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").twice.and_return(false)
+    @provider.should_receive(:action_checkout)
+    @provider.run_action(:sync)
+  end
+
+  it "runs the sync_command on action_sync if the deploy dir exists and isn't empty" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
+    @provider.stub!(:find_current_revision).and_return("11410")
+    @provider.stub!(:current_revision_matches_target_revision?).and_return(false)
+    expected_cmd = "svn update -q  -r12345 /my/deploy/dir"
+    @provider.should_receive(:run_command).with(:command => expected_cmd)
+    @provider.run_action(:sync)
+    @resource.should be_updated
+  end
+
+  it "does not fetch any updates if the remote revision matches the current revision" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true)
+    @provider.stub!(:find_current_revision).and_return('12345')
+    @provider.stub!(:current_revision_matches_target_revision?).and_return(true)
+    @provider.run_action(:sync)
+    @resource.should_not be_updated
+  end
+
+  it "runs the export_command on action_export" do
+    ::File.stub!(:directory?).with("/my/deploy").and_return(true)
+    expected_cmd = "svn export --force -q  -r12345 http://svn.example.org/trunk/ /my/deploy/dir"
+    @provider.should_receive(:run_command).with(:command => expected_cmd)
+    @provider.run_action(:export)
+    @resource.should be_updated
+  end
+
+end
diff --git a/spec/unit/provider/template/content_spec.rb b/spec/unit/provider/template/content_spec.rb
new file mode 100644
index 0000000..4061d99
--- /dev/null
+++ b/spec/unit/provider/template/content_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::Template::Content do
+
+  let(:new_resource) do
+    mock("Chef::Resource::Template (new)",
+         :cookbook_name => 'openldap',
+         :source => 'openldap_stuff.conf.erb',
+         :local => false,
+         :cookbook => nil,
+         :variables => {},
+         :inline_helper_blocks => {},
+         :inline_helper_modules => [],
+         :helper_modules => [])
+  end
+
+  let(:rendered_file_location) { Dir.tmpdir + '/openldap_stuff.conf' }
+
+  let(:run_context) do
+    cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+    Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, cookbook_repo) }
+    cl = Chef::CookbookLoader.new(cookbook_repo)
+    cl.load_cookbooks
+    cookbook_collection = Chef::CookbookCollection.new(cl)
+    node = Chef::Node.new
+    mock("Chef::Resource::RunContext", :node => node, :cookbook_collection => cookbook_collection)
+  end
+
+  let(:content) do
+    current_resource = mock("Chef::Resource::Template (current)")
+    Chef::Provider::Template::Content.new(new_resource, current_resource, run_context)
+  end
+
+  after do
+    FileUtils.rm(rendered_file_location) if ::File.exist?(rendered_file_location)
+  end
+
+  it "finds the template file in the cookbook cache if it isn't local" do
+    content.template_location.should == CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/openldap_stuff.conf.erb'
+  end
+
+  it "finds the template file locally if it is local" do
+    new_resource.stub!(:local).and_return(true)
+    new_resource.stub!(:source).and_return('/tmp/its_on_disk.erb')
+    content.template_location.should == '/tmp/its_on_disk.erb'
+  end
+
+  it "should use the cookbook name if defined in the template resource" do
+    new_resource.stub!(:cookbook_name).and_return('apache2')
+    new_resource.stub!(:cookbook).and_return('openldap')
+    new_resource.stub!(:source).and_return("test.erb")
+    content.template_location.should == CHEF_SPEC_DATA + '/cookbooks/openldap/templates/default/test.erb'
+  end
+
+  it "creates the template with the rendered content" do
+    run_context.node.normal[:slappiness] = "a warm gun"
+    IO.read(content.tempfile.path).should == "slappiness is a warm gun"
+  end
+
+end
diff --git a/spec/unit/provider/template_spec.rb b/spec/unit/provider/template_spec.rb
new file mode 100644
index 0000000..ca8f1db
--- /dev/null
+++ b/spec/unit/provider/template_spec.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2008-2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'stringio'
+require 'spec_helper'
+require 'etc'
+require 'ostruct'
+require 'support/shared/unit/provider/file'
+
+
+describe Chef::Provider::Template do
+  let(:node) { double('Chef::Node') }
+  let(:events) { double('Chef::Events').as_null_object }  # mock all the methods
+  let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
+  let(:enclosing_directory) {
+    canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates")))
+  }
+  let(:resource_path) {
+    canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt")))
+  }
+
+  # Subject
+
+  let(:provider) do
+    provider = described_class.new(resource, run_context)
+    provider.stub!(:content).and_return(content)
+    provider
+  end
+
+  let(:resource) do
+    resource = Chef::Resource::Template.new("seattle", @run_context)
+    resource.path(resource_path)
+    resource
+  end
+
+  let(:content) do
+    content = mock('Chef::Provider::File::Content::Template', :template_location => "/foo/bar/baz")
+    File.stub(:exists?).with("/foo/bar/baz").and_return(true)
+    content
+  end
+
+  it_behaves_like Chef::Provider::File
+
+  context "when creating the template" do
+
+    let(:node) { double('Chef::Node') }
+    let(:events) { double('Chef::Events').as_null_object }  # mock all the methods
+    let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
+    let(:enclosing_directory) {
+      canonicalize_path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates")))
+    }
+    let(:resource_path) {
+      canonicalize_path(File.expand_path(File.join(enclosing_directory, "seattle.txt")))
+    }
+
+    # Subject
+
+    let(:provider) do
+      provider = described_class.new(resource, run_context)
+      provider.stub!(:content).and_return(content)
+      provider
+    end
+
+    it "stops executing when the local template source can't be found" do
+      setup_normal_file
+      content.stub!(:template_location).and_return("/baz/bar/foo")
+      File.stub(:exists?).with("/baz/bar/foo").and_return(false)
+      lambda { provider.run_action(:create) }.should raise_error Chef::Mixin::WhyRun::ResourceRequirements::Assertion::AssertionFailure
+    end
+
+  end
+end
diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb
new file mode 100644
index 0000000..dd98c55
--- /dev/null
+++ b/spec/unit/provider/user/dscl_spec.rb
@@ -0,0 +1,480 @@
+#
+# Author:: Dreamcat4 (<dreamcat4 at gmail.com>)
+# Copyright:: Copyright (c) 2009 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.
+#
+
+ShellCmdResult = Struct.new(:stdout, :stderr, :exitstatus)
+
+require 'spec_helper'
+require 'ostruct'
+
+describe Chef::Provider::User::Dscl do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @new_resource = Chef::Resource::User.new("toor")
+    @provider = Chef::Provider::User::Dscl.new(@new_resource, @run_context)
+  end
+
+  describe "when shelling out to dscl" do
+    it "should run dscl with the supplied cmd /Path args" do
+      shell_return = ShellCmdResult.new('stdout', 'err', 0)
+      @provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
+      @provider.safe_dscl("cmd /Path args").should == 'stdout'
+    end
+
+    it "returns an empty string from delete commands" do
+      shell_return = ShellCmdResult.new('out', 'err', 23)
+      @provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
+      @provider.safe_dscl("delete /Path args").should == ""
+    end
+
+    it "should raise an exception for any other command" do
+      shell_return = ShellCmdResult.new('out', 'err', 23)
+      @provider.should_receive(:shell_out).with('dscl . -cmd /Path arguments').and_return(shell_return)
+      lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+    end
+
+    it "raises an exception when dscl reports 'no such key'" do
+      shell_return = ShellCmdResult.new("No such key: ", 'err', 23)
+      @provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
+      lambda { @provider.safe_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+    end
+
+    it "raises an exception when dscl reports 'eDSRecordNotFound'" do
+      shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
+      @provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
+      lambda { @provider.safe_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
+    end
+  end
+
+  describe "get_free_uid" do
+    before do
+      @provider.stub!(:safe_dscl).and_return("\nwheel      200\nstaff      201\n")
+    end
+
+    it "should run safe_dscl with list /Users uid" do
+      @provider.should_receive(:safe_dscl).with("list /Users uid")
+      @provider.get_free_uid
+    end
+
+    it "should return the first unused uid number on or above 200" do
+      @provider.get_free_uid.should == 202
+    end
+
+    it "should raise an exception when the search limit is exhausted" do
+      search_limit = 1
+      lambda { @provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
+    end
+  end
+
+  describe "uid_used?" do
+    before do
+      @provider.stub!(:safe_dscl).and_return("\naj      500\n")
+    end
+
+    it "should run safe_dscl with list /Users uid" do
+      @provider.should_receive(:safe_dscl).with("list /Users uid")
+      @provider.uid_used?(500)
+    end
+
+    it "should return true for a used uid number" do
+      @provider.uid_used?(500).should be_true
+    end
+
+    it "should return false for an unused uid number" do
+      @provider.uid_used?(501).should be_false
+    end
+
+    it "should return false if not given any valid uid number" do
+      @provider.uid_used?(nil).should be_false
+    end
+  end
+
+  describe "when determining the uid to set" do
+    it "raises RequestedUIDUnavailable if the requested uid is already in use" do
+      @provider.stub!(:uid_used?).and_return(true)
+      @provider.should_receive(:get_free_uid).and_return(501)
+      lambda { @provider.set_uid }.should raise_error(Chef::Exceptions::RequestedUIDUnavailable)
+    end
+
+    it "finds a valid, unused uid when none is specified" do
+      @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
+      @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 501")
+      @provider.should_receive(:get_free_uid).and_return(501)
+      @provider.set_uid
+      @new_resource.uid.should == 501
+    end
+
+    it "sets the uid specified in the resource" do
+      @new_resource.uid(1000)
+      @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 1000").and_return(true)
+      @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
+      @provider.set_uid
+    end
+  end
+
+  describe "when modifying the home directory" do
+    before do
+      @new_resource.supports({ :manage_home => true })
+      @new_resource.home('/Users/toor')
+
+      @current_resource = @new_resource.dup
+      @provider.current_resource = @current_resource
+    end
+
+    it "deletes the home directory when resource#home is nil" do
+      @new_resource.instance_variable_set(:@home, nil)
+      @provider.should_receive(:safe_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true)
+      @provider.modify_home
+    end
+
+
+    it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
+      @new_resource.home('epic-fail')
+      lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
+    end
+
+    it "moves the users home to the new location if it exists and the target location is different" do
+      @new_resource.supports(:manage_home => true)
+
+      current_home = CHEF_SPEC_DATA + '/old_home_dir'
+      current_home_files = [current_home + '/my-dot-emacs', current_home + '/my-dot-vim']
+      @current_resource.home(current_home)
+      @new_resource.gid(23)
+      ::File.stub!(:exists?).with('/old/home/toor').and_return(true)
+      ::File.stub!(:exists?).with('/Users/toor').and_return(true)
+
+      FileUtils.should_receive(:mkdir_p).with('/Users/toor').and_return(true)
+      FileUtils.should_receive(:rmdir).with(current_home)
+      ::Dir.should_receive(:glob).with("#{CHEF_SPEC_DATA}/old_home_dir/*",::File::FNM_DOTMATCH).and_return(current_home_files)
+      FileUtils.should_receive(:mv).with(current_home_files, "/Users/toor", :force => true)
+      FileUtils.should_receive(:chown_R).with('toor','23','/Users/toor')
+
+      @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
+      @provider.modify_home
+    end
+
+    it "should raise an exception when the systems user template dir (skel) cannot be found" do
+      ::File.stub!(:exists?).and_return(false,false,false)
+      lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::User)
+    end
+
+    it "should run ditto to copy any missing files from skel to the new home dir" do
+      ::File.should_receive(:exists?).with("/System/Library/User\ Template/English.lproj").and_return(true)
+      FileUtils.should_receive(:chown_R).with('toor', '', '/Users/toor')
+      @provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
+      @provider.ditto_home
+    end
+
+    it "creates the user's NFSHomeDirectory and home directory" do
+      @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true)
+      @provider.should_receive(:ditto_home)
+      @provider.modify_home
+    end
+  end
+
+  describe "osx_shadow_hash?" do
+    it "should return true when the string is a shadow hash" do
+      @provider.osx_shadow_hash?("0"*8*155).should eql(true)
+    end
+
+    it "should return false otherwise" do
+      @provider.osx_shadow_hash?("any other string").should eql(false)
+    end
+  end
+
+  describe "when detecting the format of a password" do
+    it "detects a OS X salted sha1" do
+      @provider.osx_salted_sha1?("0"*48).should eql(true)
+      @provider.osx_salted_sha1?("any other string").should eql(false)
+    end
+  end
+
+  describe "guid" do
+    it "should run safe_dscl with read /Users/user GeneratedUID to get the users GUID" do
+      expected_uuid = "b398449e-cee0-45e0-80f8-b0b5b1bfdeaa"
+      @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(expected_uuid + "\n")
+      @provider.guid.should == expected_uuid
+    end
+  end
+
+  describe "shadow_hash_set?" do
+
+    it "should run safe_dscl with read /Users/user to see if the AuthenticationAuthority key exists" do
+      @provider.should_receive(:safe_dscl).with("read /Users/toor")
+      @provider.shadow_hash_set?
+    end
+
+    describe "when the user account has an AuthenticationAuthority key" do
+      it "uses the shadow hash when there is a ShadowHash field in the AuthenticationAuthority key" do
+        @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+        @provider.shadow_hash_set?.should be_true
+      end
+
+      it "does not use the shadow hash when there is no ShadowHash field in the AuthenticationAuthority key" do
+        @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: \n")
+        @provider.shadow_hash_set?.should be_false
+      end
+
+    end
+
+    describe "with no AuthenticationAuthority key in the user account" do
+      it "does not use the shadow hash" do
+        @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("")
+        @provider.shadow_hash_set?.should eql(false)
+      end
+    end
+  end
+
+  describe "when setting or modifying the user password" do
+    before do
+      @new_resource.password("password")
+      @output = StringIO.new
+    end
+
+    describe "when using a salted sha1 for the password" do
+      before do
+        @new_resource.password("F"*48)
+      end
+
+      it "should write a shadow hash file with the expected salted sha1" do
+        uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+        File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
+        @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+        @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+        expected_salted_sha1 = @new_resource.password
+        expected_shadow_hash = "00000000"*155
+        expected_shadow_hash[168] = expected_salted_sha1
+        @provider.modify_password
+        @output.string.strip.should == expected_shadow_hash
+      end
+    end
+
+    describe "when given a shadow hash file for the password" do
+      it "should write the shadow hash file directly to /var/db/shadow/hash/GUID" do
+        shadow_hash = '0123456789ABCDE0123456789ABCDEF' * 40
+        raise 'oops' unless shadow_hash.size == 1240
+        @new_resource.password shadow_hash
+        uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+        File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
+        @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+        @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+        @provider.modify_password
+        @output.string.strip.should == shadow_hash
+      end
+    end
+
+    describe "when given a string for the password" do
+      it "should output a salted sha1 and shadow hash file from the specified password" do
+        uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+        File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
+        @new_resource.password("password")
+        OpenSSL::Random.stub!(:random_bytes).and_return("\377\377\377\377\377\377\377\377")
+        expected_salted_sha1 = "F"*8+"SHA1-"*8
+        expected_shadow_hash = "00000000"*155
+        expected_shadow_hash[168] = expected_salted_sha1
+        @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+        @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+        @provider.modify_password
+        @output.string.strip.should match(/^0{168}(FFFFFFFF1C1AA7935D4E1190AFEC92343F31F7671FBF126D)0{1071}$/)
+      end
+    end
+
+    it "should write the output directly to the shadow hash file at /var/db/shadow/hash/GUID" do
+      shadow_file = StringIO.new
+      uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+      File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
+      @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+      @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
+      @provider.modify_password
+      shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
+    end
+
+    it "should run safe_dscl append /Users/user AuthenticationAuthority ;ShadowHash; when no shadow hash set" do
+      shadow_file = StringIO.new
+      uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
+      File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
+      @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
+      @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority:\n")
+      @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';ShadowHash;'")
+      @provider.modify_password
+      shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
+    end
+  end
+
+  describe "load_current_resource" do
+    it "should raise an error if the required binary /usr/bin/dscl doesn't exist" do
+      ::File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::User)
+    end
+
+    it "shouldn't raise an error if /usr/bin/dscl exists" do
+      ::File.stub!(:exists?).and_return(true)
+      lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::User)
+    end
+  end
+
+  describe "when the user does not yet exist and chef is creating it" do
+    context "with a numeric gid" do
+      before do
+        @new_resource.comment "#mockssuck"
+        @new_resource.gid 1001
+      end
+
+      it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
+        @provider.should_receive :dscl_create_user
+        @provider.should_receive :dscl_create_comment
+        @provider.should_receive :set_uid
+        @provider.should_receive :dscl_set_gid
+        @provider.should_receive :modify_home
+        @provider.should_receive :dscl_set_shell
+        @provider.should_receive :modify_password
+        @provider.create_user
+      end
+
+      it "creates the user and sets the comment field" do
+        @provider.should_receive(:safe_dscl).with("create /Users/toor").and_return(true)
+        @provider.dscl_create_user
+      end
+
+      it "sets the comment field" do
+        @provider.should_receive(:safe_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
+        @provider.dscl_create_comment
+      end
+
+      it "should run safe_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
+        @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
+        @provider.dscl_set_gid
+      end
+
+      it "should run safe_dscl with create /Users/user UserShell to set the users login shell" do
+        @provider.should_receive(:safe_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true)
+        @provider.dscl_set_shell
+      end
+    end
+
+    context "with a non-numeric gid" do
+      before do
+        @new_resource.comment "#mockssuck"
+        @new_resource.gid "newgroup"
+      end
+
+      it "should map the group name to a numeric ID when the group exists" do
+        @provider.should_receive(:safe_dscl).with("read /Groups/newgroup PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n")
+        @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").ordered.and_return(true)
+        @provider.dscl_set_gid
+      end
+
+      it "should raise an exception when the group does not exist" do
+        shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
+        @provider.should_receive(:shell_out).with('dscl . -read /Groups/newgroup PrimaryGroupID').and_return(shell_return)
+        lambda { @provider.dscl_set_gid }.should raise_error(Chef::Exceptions::GroupIDNotFound)
+      end
+    end
+  end
+
+  describe "when the user exists and chef is managing it" do
+    before do
+      @current_resource = @new_resource.dup
+      @provider.current_resource = @current_resource
+
+      # These are all different from @current_resource
+      @new_resource.username "mud"
+      @new_resource.uid 2342
+      @new_resource.gid 2342
+      @new_resource.home '/Users/death'
+      @new_resource.password 'goaway'
+    end
+
+    it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
+      @provider.should_receive :dscl_create_user
+      @provider.should_receive :dscl_create_comment
+      @provider.should_receive :set_uid
+      @provider.should_receive :dscl_set_gid
+      @provider.should_receive :modify_home
+      @provider.should_receive :dscl_set_shell
+      @provider.should_receive :modify_password
+      @provider.create_user
+    end
+  end
+
+  describe "when changing the gid" do
+    before do
+      @current_resource = @new_resource.dup
+      @provider.current_resource = @current_resource
+
+      # This is different from @current_resource
+      @new_resource.gid 2342
+    end
+
+    it "sets the gid" do
+      @provider.should_receive :dscl_set_gid
+      @provider.manage_user
+    end
+  end
+
+  describe "when the user exists and chef is removing it" do
+    it "removes the user's home directory when the resource is configured to manage home" do
+      @new_resource.supports({ :manage_home => true })
+      @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("NFSHomeDirectory: /Users/fuuuuuuuuuuuuu")
+      @provider.should_receive(:safe_dscl).with("delete /Users/toor")
+      FileUtils.should_receive(:rm_rf).with("/Users/fuuuuuuuuuuuuu")
+      @provider.remove_user
+    end
+
+    it "removes the user from any group memberships" do
+      Etc.stub(:group).and_yield(OpenStruct.new(:name => 'ragefisters', :mem => 'toor'))
+      @provider.should_receive(:safe_dscl).with("delete /Users/toor")
+      @provider.should_receive(:safe_dscl).with("delete /Groups/ragefisters GroupMembership 'toor'")
+      @provider.remove_user
+    end
+  end
+
+  describe "when discovering if a user is locked" do
+
+    it "determines the user is not locked when dscl shows an AuthenticationAuthority without a DisabledUser field" do
+      @provider.should_receive(:safe_dscl).with("read /Users/toor")
+      @provider.should_not be_locked
+    end
+
+    it "determines the user is locked when dscl shows an AuthenticationAuthority with a DisabledUser field" do
+      @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\nAuthenticationAuthority: ;DisabledUser;\n")
+      @provider.should be_locked
+    end
+
+    it "determines the user is not locked when dscl shows no AuthenticationAuthority" do
+      @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\n")
+      @provider.should_not be_locked
+    end
+  end
+
+  describe "when locking the user" do
+    it "should run safe_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
+      @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
+      @provider.lock_user
+    end
+  end
+
+  describe "when unlocking the user" do
+    it "removes DisabledUser from the authentication string" do
+      @provider.should_receive(:safe_dscl).with("read /Users/toor AuthenticationAuthority").and_return("\nAuthenticationAuthority: ;ShadowHash; ;DisabledUser;\n")
+      @provider.should_receive(:safe_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;'")
+      @provider.unlock_user
+    end
+  end
+end
diff --git a/spec/unit/provider/user/pw_spec.rb b/spec/unit/provider/user/pw_spec.rb
new file mode 100644
index 0000000..ea5bcfe
--- /dev/null
+++ b/spec/unit/provider/user/pw_spec.rb
@@ -0,0 +1,235 @@
+#
+# Author:: Stephen Haynes (<sh at nomitor.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'
+
+describe Chef::Provider::User::Pw do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::User.new("adam")
+    @new_resource.comment   "Adam Jacob"
+    @new_resource.uid       1000
+    @new_resource.gid       1000
+    @new_resource.home      "/home/adam"
+    @new_resource.shell     "/usr/bin/zsh"
+    @new_resource.password  "abracadabra"
+
+    @new_resource.supports :manage_home => true
+
+    @current_resource = Chef::Resource::User.new("adam")
+    @current_resource.comment  "Adam Jacob"
+    @current_resource.uid      1000
+    @current_resource.gid      1000
+    @current_resource.home     "/home/adam"
+    @current_resource.shell    "/usr/bin/zsh"
+    @current_resource.password "abracadabra"
+
+    @provider = Chef::Provider::User::Pw.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  describe "setting options to the pw command" do
+    field_list = {
+      'comment' => "-c",
+      'home' => "-d",
+      'gid' => "-g",
+      'uid' => "-u",
+      'shell' => "-s"
+    }
+    field_list.each do |attribute, option|
+      it "should check for differences in #{attribute} between the new and current resources" do
+        @current_resource.should_receive(attribute)
+        @new_resource.should_receive(attribute)
+        @provider.set_options
+      end
+
+      it "should set the option for #{attribute} if the new resources #{attribute} is not null" do
+        @new_resource.stub!(attribute).and_return("hola")
+        @provider.set_options.should eql(" #{@new_resource.username} #{option} '#{@new_resource.send(attribute)}' -m")
+      end
+
+      it "should set the option for #{attribute} if the new resources #{attribute} is not null, without homedir management" do
+        @new_resource.stub!(:supports).and_return({:manage_home => false})
+        @new_resource.stub!(attribute).and_return("hola")
+        @provider.set_options.should eql(" #{@new_resource.username} #{option} '#{@new_resource.send(attribute)}'")
+      end
+    end
+
+    it "should combine all the possible options" do
+      match_string = " adam"
+      field_list.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option|
+        @new_resource.stub!(attribute).and_return("hola")
+        match_string << " #{option} 'hola'"
+      end
+      match_string << " -m"
+      @provider.set_options.should eql(match_string)
+    end
+  end
+
+  describe "create_user" do
+    before(:each) do
+      @provider.stub!(:run_command).and_return(true)
+      @provider.stub!(:modify_password).and_return(true)
+    end
+
+    it "should run pw useradd with the return of set_options" do
+      @provider.should_receive(:run_command).with({ :command => "pw useradd adam -m" }).and_return(true)
+      @provider.create_user
+    end
+
+    it "should modify the password" do
+      @provider.should_receive(:modify_password).and_return(true)
+      @provider.create_user
+    end
+  end
+
+  describe "manage_user" do
+    before(:each) do
+      @provider.stub!(:run_command).and_return(true)
+      @provider.stub!(:modify_password).and_return(true)
+    end
+
+    it "should run pw usermod with the return of set_options" do
+      @provider.should_receive(:run_command).with({ :command => "pw usermod adam -m" }).and_return(true)
+      @provider.manage_user
+    end
+
+    it "should modify the password" do
+      @provider.should_receive(:modify_password).and_return(true)
+      @provider.create_user
+    end
+  end
+
+  describe "remove_user" do
+    it "should run pw userdel with the new resources user name" do
+      @new_resource.supports :manage_home => false
+      @provider.should_receive(:run_command).with({ :command => "pw userdel #{@new_resource.username}" }).and_return(true)
+      @provider.remove_user
+    end
+
+    it "should run pw userdel with the new resources user name and -r if manage_home is true" do
+      @provider.should_receive(:run_command).with({ :command => "pw userdel #{@new_resource.username} -r"}).and_return(true)
+      @provider.remove_user
+    end
+  end
+
+  describe "determining if the user is locked" do
+    it "should return true if user is locked" do
+      @current_resource.stub!(:password).and_return("*LOCKED*abracadabra")
+      @provider.check_lock.should eql(true)
+    end
+
+    it "should return false if user is not locked" do
+      @current_resource.stub!(:password).and_return("abracadabra")
+      @provider.check_lock.should eql(false)
+    end
+  end
+
+  describe "when locking the user" do
+    it "should run pw lock with the new resources username" do
+      @provider.should_receive(:run_command).with({ :command => "pw lock #{@new_resource.username}"})
+      @provider.lock_user
+    end
+  end
+
+  describe "when unlocking the user" do
+    it "should run pw unlock with the new resources username" do
+      @provider.should_receive(:run_command).with({ :command => "pw unlock #{@new_resource.username}"})
+      @provider.unlock_user
+    end
+  end
+
+  describe "when modifying the password" do
+    before(:each) do
+      @status = mock("Status", :exitstatus => 0)
+      @provider.stub!(:popen4).and_return(@status)
+      @pid, @stdin, @stdout, @stderr = nil, nil, nil, nil
+    end
+
+    it "should check for differences in password between the new and current resources" do
+      @current_resource.should_receive(:password)
+      @new_resource.should_receive(:password)
+      @provider.modify_password
+    end
+
+    describe "and the passwords are identical" do
+      before(:each) do
+        @new_resource.stub!(:password).and_return("abracadabra")
+        @current_resource.stub!(:password).and_return("abracadabra")
+      end
+
+      it "logs an appropriate message" do
+        Chef::Log.should_receive(:debug).with("user[adam] no change needed to password")
+        @provider.modify_password
+      end
+    end
+
+    describe "and the passwords are different" do
+      before(:each) do
+        @new_resource.stub!(:password).and_return("abracadabra")
+        @current_resource.stub!(:password).and_return("sesame")
+      end
+
+      it "should log an appropriate message" do
+        Chef::Log.should_receive(:debug).with("user[adam] updating password")
+        @provider.modify_password
+      end
+
+      it "should run pw usermod with the username and the option -H 0" do
+        @provider.should_receive(:popen4).with("pw usermod adam -H 0", :waitlast => true).and_return(@status)
+        @provider.modify_password
+      end
+
+      it "should send the new password to the stdin of pw usermod" do
+        @stdin = StringIO.new
+        @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
+        @provider.modify_password
+        @stdin.string.should == "abracadabra\n"
+      end
+
+      it "should raise an exception if pw usermod fails" do
+        @status.should_receive(:exitstatus).and_return(1)
+        lambda { @provider.modify_password }.should raise_error(Chef::Exceptions::User)
+      end
+
+      it "should not raise an exception if pw usermod succeeds" do
+        @status.should_receive(:exitstatus).and_return(0)
+        lambda { @provider.modify_password }.should_not raise_error(Chef::Exceptions::User)
+      end
+    end
+  end
+
+  describe "when loading the current state" do
+    before do
+      @provider.new_resource = Chef::Resource::User.new("adam")
+    end
+
+    it "should raise an error if the required binary /usr/sbin/pw doesn't exist" do
+      File.should_receive(:exists?).with("/usr/sbin/pw").and_return(false)
+      lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::User)
+    end
+
+    it "shouldn't raise an error if /usr/sbin/pw exists" do
+      File.stub!(:exists?).and_return(true)
+      lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::User)
+    end
+  end
+end
diff --git a/spec/unit/provider/user/solaris_spec.rb b/spec/unit/provider/user/solaris_spec.rb
new file mode 100644
index 0000000..5500eac
--- /dev/null
+++ b/spec/unit/provider/user/solaris_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::User::Solaris do
+
+  subject(:provider) do
+    p = described_class.new(@new_resource, @run_context)
+    p.current_resource = @current_resource
+
+    # Prevent the useradd-based provider tests from trying to write /etc/shadow
+    p.stub!(:write_shadow_file)
+    p
+  end
+
+  supported_useradd_options = {
+    'comment' => "-c",
+    'gid' => "-g",
+    'uid' => "-u",
+    'shell' => "-s"
+  }
+
+  include_examples "a useradd-based user provider", supported_useradd_options
+
+  describe "when we want to set a password" do
+    before(:each) do
+      @node = Chef::Node.new
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_context = Chef::RunContext.new(@node, {}, @events)
+
+      @new_resource = Chef::Resource::User.new("adam", @run_context)
+      @current_resource = Chef::Resource::User.new("adam", @run_context)
+
+      @new_resource.password "hocus-pocus"
+
+      # Let these tests run #write_shadow_file
+      provider.unstub!(:write_shadow_file)
+    end
+
+    it "should use its own shadow file writer to set the password" do
+      provider.should_receive(:write_shadow_file)
+      provider.stub!(:shell_out!).and_return(true)
+      provider.manage_user
+    end
+
+    it "should write out a modified version of the password file" do
+      password_file = Tempfile.new("shadow")
+      password_file.puts "adam:existingpassword:15441::::::"
+      password_file.close
+      provider.password_file = password_file.path
+      provider.stub!(:shell_out!).and_return(true)
+      # may not be able to write to /etc for tests...
+      temp_file = Tempfile.new("shadow")
+      Tempfile.stub!(:new).with("shadow", "/etc").and_return(temp_file)
+      @new_resource.password "verysecurepassword"
+      provider.manage_user
+      ::File.open(password_file.path, "r").read.should =~ /adam:verysecurepassword:/
+      password_file.unlink
+    end
+  end
+
+end
diff --git a/spec/unit/provider/user/useradd_spec.rb b/spec/unit/provider/user/useradd_spec.rb
new file mode 100644
index 0000000..5862ee3
--- /dev/null
+++ b/spec/unit/provider/user/useradd_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+#
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::User::Useradd do
+
+  subject(:provider) do
+    p = described_class.new(@new_resource, @run_context)
+    p.current_resource = @current_resource
+    p
+  end
+
+  supported_useradd_options = {
+    'comment' => "-c",
+    'gid' => "-g",
+    'uid' => "-u",
+    'shell' => "-s",
+    'password' => "-p"
+  }
+
+  include_examples "a useradd-based user provider", supported_useradd_options
+end
diff --git a/spec/unit/provider/user/windows_spec.rb b/spec/unit/provider/user/windows_spec.rb
new file mode 100644
index 0000000..b11eeae
--- /dev/null
+++ b/spec/unit/provider/user/windows_spec.rb
@@ -0,0 +1,178 @@
+#
+# Author:: Doug MacEachern (<dougm at vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class Chef
+  class Util
+    class Windows
+      class NetUser
+      end
+    end
+  end
+end
+
+describe Chef::Provider::User::Windows do
+  before(:each) do
+    @node = Chef::Node.new
+    @new_resource = Chef::Resource::User.new("monkey")
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @current_resource = Chef::Resource::User.new("monkey")
+
+    @net_user = mock("Chef::Util::Windows::NetUser")
+    Chef::Util::Windows::NetUser.stub!(:new).and_return(@net_user)
+
+    @provider = Chef::Provider::User::Windows.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  describe "when comparing the user's current attributes to the desired attributes" do
+    before do
+      @new_resource.comment   "Adam Jacob"
+      @new_resource.uid       1000
+      @new_resource.gid       1000
+      @new_resource.home      "/home/adam"
+      @new_resource.shell     "/usr/bin/zsh"
+      @new_resource.password  "abracadabra"
+
+      @provider.current_resource = @new_resource.clone
+    end
+    describe "and the attributes match" do
+      it "doesn't set the comment field to be updated" do
+        @provider.set_options.should_not have_key(:full_name)
+      end
+
+      it "doesn't set the home directory to be updated" do
+        @provider.set_options.should_not have_key(:home_dir)
+      end
+
+      it "doesn't set the group id to be updated" do
+        @provider.set_options.should_not have_key(:primary_group_id)
+      end
+
+      it "doesn't set the user id to be updated" do
+        @provider.set_options.should_not have_key(:user_id)
+      end
+
+      it "doesn't set the shell to be updated" do
+        @provider.set_options.should_not have_key(:script_path)
+      end
+
+      it "doesn't set the password to be updated" do
+        @provider.set_options.should_not have_key(:password)
+      end
+
+    end
+
+    describe "and the attributes do not match" do
+      before do
+        @current_resource = Chef::Resource::User.new("adam")
+        @current_resource.comment   "Adam Jacob-foo"
+        @current_resource.uid       1111
+        @current_resource.gid       1111
+        @current_resource.home      "/home/adam-foo"
+        @current_resource.shell     "/usr/bin/tcsh"
+        @current_resource.password  "foobarbaz"
+        @provider.current_resource = @current_resource
+      end
+
+      it "marks the full_name field to be updated" do
+        @provider.set_options[:full_name].should == "Adam Jacob"
+      end
+
+      it "marks the home_dir attribute to be updated" do
+        @provider.set_options[:home_dir].should == '/home/adam'
+      end
+
+      it "marks the primary_group_id attribute to be updated" do
+        @provider.set_options[:primary_group_id].should == 1000
+      end
+
+      it "marks the user_id attribute to be updated" do
+        @provider.set_options[:user_id].should == 1000
+      end
+
+      it "marks the script_path attribute to be updated" do
+        @provider.set_options[:script_path].should == '/usr/bin/zsh'
+      end
+
+      it "marks the password attribute to be updated" do
+        @provider.set_options[:password].should == 'abracadabra'
+      end
+    end
+  end
+
+  describe "when creating the user" do
+    it "should call @net_user.add with the return of set_options" do
+      @provider.stub!(:set_options).and_return(:name=> "monkey")
+      @net_user.should_receive(:add).with(:name=> "monkey")
+      @provider.create_user
+    end
+  end
+
+  describe "manage_user" do
+    before(:each) do
+      @provider.stub!(:set_options).and_return(:name=> "monkey")
+    end
+
+    it "should call @net_user.update with the return of set_options" do
+      @net_user.should_receive(:update).with(:name=> "monkey")
+      @provider.manage_user
+    end
+  end
+
+  describe "when removing the user" do
+    it "should call @net_user.delete" do
+      @net_user.should_receive(:delete)
+      @provider.remove_user
+    end
+  end
+
+  describe "when checking if the user is locked" do
+    before(:each) do
+      @current_resource.password "abracadabra"
+    end
+
+    it "should return true if user is locked" do
+      @net_user.stub!(:check_enabled).and_return(true)
+      @provider.check_lock.should eql(true)
+    end
+
+    it "should return false if user is not locked" do
+      @net_user.stub!(:check_enabled).and_return(false)
+      @provider.check_lock.should eql(false)
+    end
+  end
+
+  describe "locking the user" do
+    it "should call @net_user.disable_account" do
+      @net_user.stub!(:check_enabled).and_return(true)
+      @net_user.should_receive(:disable_account)
+      @provider.lock_user
+    end
+  end
+
+  describe "unlocking the user" do
+    it "should call @net_user.enable_account" do
+      @net_user.stub!(:check_enabled).and_return(false)
+      @net_user.should_receive(:enable_account)
+      @provider.unlock_user
+    end
+  end
+end
diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb
new file mode 100644
index 0000000..d054fcc
--- /dev/null
+++ b/spec/unit/provider/user_spec.rb
@@ -0,0 +1,460 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+EtcPwnamIsh = Struct.new(:name, :passwd, :uid, :gid, :gecos, :dir, :shell, :change, :uclass, :expire)
+EtcGrnamIsh = Struct.new(:name, :passwd, :gid, :mem)
+
+describe Chef::Provider::User do
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+
+    @new_resource = Chef::Resource::User.new("adam")
+    @new_resource.comment "Adam Jacob"
+    @new_resource.uid 1000
+    @new_resource.gid 1000
+    @new_resource.home "/home/adam"
+    @new_resource.shell "/usr/bin/zsh"
+
+    @current_resource = Chef::Resource::User.new("adam")
+    @current_resource.comment "Adam Jacob"
+    @current_resource.uid 1000
+    @current_resource.gid 1000
+    @current_resource.home "/home/adam"
+    @current_resource.shell "/usr/bin/zsh"
+
+    @provider = Chef::Provider::User.new(@new_resource, @run_context)
+    @provider.current_resource = @current_resource
+  end
+
+  describe "when first created" do
+    it "assume the user exists by default" do
+      @provider.user_exists.should eql(true)
+    end
+
+    it "does not know the locked state" do
+      @provider.locked.should eql(nil)
+    end
+  end
+
+  describe "executing load_current_resource" do
+    before(:each) do
+      @node = Chef::Node.new
+      #@new_resource = mock("Chef::Resource::User",
+      #  :null_object => true,
+      #  :username => "adam",
+      #  :comment => "Adam Jacob",
+      #  :uid => 1000,
+      #  :gid => 1000,
+      #  :home => "/home/adam",
+      #  :shell => "/usr/bin/zsh",
+      #  :password => nil,
+      #  :updated => nil
+      #)
+      Chef::Resource::User.stub!(:new).and_return(@current_resource)
+      @pw_user = EtcPwnamIsh.new
+      @pw_user.name = "adam"
+      @pw_user.gid = 1000
+      @pw_user.uid = 1000
+      @pw_user.gecos = "Adam Jacob"
+      @pw_user.dir = "/home/adam"
+      @pw_user.shell = "/usr/bin/zsh"
+      @pw_user.passwd = "*"
+      Etc.stub!(:getpwnam).and_return(@pw_user)
+    end
+
+    it "should create a current resource with the same name as the new resource" do
+      @provider.load_current_resource
+      @provider.current_resource.name.should == 'adam'
+    end
+
+    it "should set the username of the current resource to the username of the new resource" do
+      @provider.load_current_resource
+      @current_resource.username.should == @new_resource.username
+    end
+
+    it "should look up the user in /etc/passwd with getpwnam" do
+      Etc.should_receive(:getpwnam).with(@new_resource.username).and_return(@pw_user)
+      @provider.load_current_resource
+    end
+
+    it "should set user_exists to false if the user is not found with getpwnam" do
+      Etc.should_receive(:getpwnam).and_raise(ArgumentError)
+      @provider.load_current_resource
+      @provider.user_exists.should eql(false)
+    end
+
+    # The mapping between the Chef::Resource::User and Getpwnam struct
+    user_attrib_map = {
+      :uid => :uid,
+      :gid => :gid,
+      :comment => :gecos,
+      :home => :dir,
+      :shell => :shell
+    }
+    user_attrib_map.each do |user_attrib, getpwnam_attrib|
+      it "should set the current resources #{user_attrib} based on getpwnam #{getpwnam_attrib}" do
+        @current_resource.should_receive(user_attrib).with(@pw_user.send(getpwnam_attrib))
+        @provider.load_current_resource
+      end
+    end
+
+    it "should attempt to convert the group gid if one has been supplied" do
+      @provider.should_receive(:convert_group_name)
+      @provider.load_current_resource
+    end
+
+    it "shouldn't try and convert the group gid if none has been supplied" do
+      @new_resource.stub!(:gid).and_return(nil)
+      @provider.should_not_receive(:convert_group_name)
+      @provider.load_current_resource
+    end
+
+    it "should return the current resource" do
+      @provider.load_current_resource.should eql(@current_resource)
+    end
+
+    describe "and running assertions" do
+      def self.shadow_lib_unavail?
+        begin
+          require 'rubygems'
+          require 'shadow'
+        rescue LoadError => e
+          pending "ruby-shadow gem not installed for dynamic load test"
+          true
+        else
+          false
+        end
+      end
+
+      before (:each) do
+        user = @pw_user.dup
+        user.name = "root"
+        user.passwd = "x"
+        @new_resource.password "some new password"
+        Etc.stub!(:getpwnam).and_return(user)
+      end
+
+      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
+
+          context "and we are root", :requires_root => true do
+            it "should pass assertions when ruby-shadow can be loaded" do
+              @provider.action = 'create'
+              original_method = @provider.method(:require)
+              @provider.should_receive(:require) { |*args| original_method.call(*args) }
+              passwd_info = Struct::PasswdEntry.new(:sp_namp => "adm ", :sp_pwdp => "$1$T0N0Q.lc$nyG6pFI3Dpqa5cxUz/57j0", :sp_lstchg => 14861, :sp_min => 0, :sp_max => 99999,
+                                                    :sp_warn => 7, :sp_inact => -1, :sp_expire => -1, :sp_flag => -1)
+              Shadow::Passwd.should_receive(:getspnam).with("adam").and_return(passwd_info)
+              @provider.load_current_resource
+              @provider.define_resource_requirements
+              @provider.process_resource_requirements
+            end
+          end
+        end
+      end
+
+      it "should fail assertions when ruby-shadow cannot be loaded" do
+        @provider.should_receive(:require).with("shadow") { raise LoadError }
+        @provider.load_current_resource
+        @provider.define_resource_requirements
+        lambda {@provider.process_resource_requirements}.should raise_error Chef::Exceptions::MissingLibrary
+      end
+
+    end
+  end
+
+  describe "compare_user" do
+    let(:mapping) {
+      {
+        'username' => ["adam", "Adam"],
+        'comment' => ["Adam Jacob", "adam jacob"],
+        'uid' => [1000, 1001],
+        'gid' => [1000, 1001],
+        'home' => ["/home/adam", "/Users/adam"],
+        'shell'=> ["/usr/bin/zsh", "/bin/bash"],
+        'password'=> ["abcd","12345"]
+      }
+    }
+
+    %w{uid gid comment home shell password}.each do |attribute|
+      it "should return true if #{attribute} doesn't match" do
+        @new_resource.send(attribute, mapping[attribute][0])
+        @current_resource.send(attribute, mapping[attribute][1])
+        @provider.compare_user.should eql(true)
+      end
+    end
+
+    %w{uid gid}.each do |attribute|
+      it "should return false if string #{attribute} matches fixnum" do
+        @new_resource.send(attribute, "100")
+        @current_resource.send(attribute, 100)
+        @provider.compare_user.should eql(false)
+      end
+    end
+
+    it "should return false if the objects are identical" do
+      @provider.compare_user.should eql(false)
+    end
+  end
+
+  describe "action_create" do
+    before(:each) do
+      @provider.stub!(:load_current_resource)
+      # @current_resource = mock("Chef::Resource::User",
+      #   :null_object => true,
+      #   :username => "adam",
+      #   :comment => "Adam Jacob",
+      #   :uid => 1000,
+      #   :gid => 1000,
+      #   :home => "/home/adam",
+      #   :shell => "/usr/bin/zsh",
+      #   :password => nil,
+      #   :updated => nil
+      # )
+      # @provider = Chef::Provider::User.new(@node, @new_resource)
+      # @provider.current_resource = @current_resource
+      # @provider.user_exists = false
+      # @provider.stub!(:create_user).and_return(true)
+      # @provider.stub!(:manage_user).and_return(true)
+    end
+
+    it "should call create_user if the user does not exist" do
+      @provider.user_exists = false
+      @provider.should_receive(:create_user).and_return(true)
+      @provider.action_create
+      @provider.set_updated_status
+      @new_resource.should be_updated
+    end
+
+    it "should call manage_user if the user exists and has mismatched attributes" do
+      @provider.user_exists = true
+      @provider.stub!(:compare_user).and_return(true)
+      @provider.should_receive(:manage_user).and_return(true)
+      @provider.action_create
+    end
+
+    it "should set the new_resources updated flag when it creates the user if we call manage_user" do
+      @provider.user_exists = true
+      @provider.stub!(:compare_user).and_return(true)
+      @provider.stub!(:manage_user).and_return(true)
+      @provider.action_create
+      @provider.set_updated_status
+      @new_resource.should be_updated
+    end
+  end
+
+  describe "action_remove" do
+    before(:each) do
+      @provider.stub!(:load_current_resource)
+    end
+
+    it "should not call remove_user if the user does not exist" do
+      @provider.user_exists = false
+      @provider.should_not_receive(:remove_user)
+      @provider.action_remove
+    end
+
+    it "should call remove_user if the user exists" do
+      @provider.user_exists = true
+      @provider.should_receive(:remove_user)
+      @provider.action_remove
+    end
+
+    it "should set the new_resources updated flag to true if the user is removed" do
+      @provider.user_exists = true
+      @provider.should_receive(:remove_user)
+      @provider.action_remove
+      @provider.set_updated_status
+      @new_resource.should be_updated
+    end
+  end
+
+  describe "action_manage" do
+    before(:each) do
+      @provider.stub!(:load_current_resource)
+      # @node = Chef::Node.new
+      # @new_resource = mock("Chef::Resource::User",
+      #   :null_object => true
+      # )
+      # @current_resource = mock("Chef::Resource::User",
+      #   :null_object => true
+      # )
+      # @provider = Chef::Provider::User.new(@node, @new_resource)
+      # @provider.current_resource = @current_resource
+      # @provider.user_exists = true
+      # @provider.stub!(:manage_user).and_return(true)
+    end
+
+    it "should run manage_user if the user exists and has mismatched attributes" do
+      @provider.should_receive(:compare_user).and_return(true)
+      @provider.should_receive(:manage_user).and_return(true)
+      @provider.action_manage
+    end
+
+    it "should set the new resources updated flag to true if manage_user is called" do
+      @provider.stub!(:compare_user).and_return(true)
+      @provider.stub!(:manage_user).and_return(true)
+      @provider.action_manage
+      @provider.set_updated_status
+      @new_resource.should be_updated
+    end
+
+    it "should not run manage_user if the user does not exist" do
+      @provider.user_exists = false
+      @provider.should_not_receive(:manage_user)
+      @provider.action_manage
+    end
+
+    it "should not run manage_user if the user exists but has no differing attributes" do
+      @provider.should_receive(:compare_user).and_return(false)
+      @provider.should_not_receive(:manage_user)
+      @provider.action_manage
+    end
+  end
+
+  describe "action_modify" do
+    before(:each) do
+      @provider.stub!(:load_current_resource)
+      # @node = Chef::Node.new
+      # @new_resource = mock("Chef::Resource::User",
+      #   :null_object => true
+      # )
+      # @current_resource = mock("Chef::Resource::User",
+      #   :null_object => true
+      # )
+      # @provider = Chef::Provider::User.new(@node, @new_resource)
+      # @provider.current_resource = @current_resource
+      # @provider.user_exists = true
+      # @provider.stub!(:manage_user).and_return(true)
+    end
+
+    it "should run manage_user if the user exists and has mismatched attributes" do
+      @provider.should_receive(:compare_user).and_return(true)
+      @provider.should_receive(:manage_user).and_return(true)
+      @provider.action_modify
+    end
+
+    it "should set the new resources updated flag to true if manage_user is called" do
+      @provider.stub!(:compare_user).and_return(true)
+      @provider.stub!(:manage_user).and_return(true)
+      @provider.action_modify
+      @provider.set_updated_status
+      @new_resource.should be_updated
+    end
+
+    it "should not run manage_user if the user exists but has no differing attributes" do
+      @provider.should_receive(:compare_user).and_return(false)
+      @provider.should_not_receive(:manage_user)
+      @provider.action_modify
+    end
+
+    it "should raise a Chef::Exceptions::User if the user doesn't exist" do
+      @provider.user_exists = false
+      lambda { @provider.action = :modify; @provider.run_action }.should raise_error(Chef::Exceptions::User)
+    end
+  end
+
+
+  describe "action_lock" do
+    before(:each) do
+      @provider.stub!(:load_current_resource)
+    end
+    it "should lock the user if it exists and is unlocked" do
+      @provider.stub!(:check_lock).and_return(false)
+      @provider.should_receive(:lock_user).and_return(true)
+      @provider.action_lock
+    end
+
+    it "should set the new resources updated flag to true if lock_user is called" do
+      @provider.stub!(:check_lock).and_return(false)
+      @provider.should_receive(:lock_user)
+      @provider.action_lock
+      @provider.set_updated_status
+      @new_resource.should be_updated
+    end
+
+    it "should raise a Chef::Exceptions::User if we try and lock a user that does not exist" do
+      @provider.user_exists = false
+      @provider.action = :lock
+
+      lambda { @provider.run_action }.should raise_error(Chef::Exceptions::User)
+    end
+  end
+
+  describe "action_unlock" do
+    before(:each) do
+      @provider.stub!(:load_current_resource)
+      # @node = Chef::Node.new
+      # @new_resource = mock("Chef::Resource::User",
+      #   :null_object => true
+      # )
+      # @current_resource = mock("Chef::Resource::User",
+      #   :null_object => true
+      # )
+      # @provider = Chef::Provider::User.new(@node, @new_resource)
+      # @provider.current_resource = @current_resource
+      # @provider.user_exists = true
+      # @provider.stub!(:check_lock).and_return(true)
+      # @provider.stub!(:unlock_user).and_return(true)
+    end
+
+    it "should unlock the user if it exists and is locked" do
+      @provider.stub!(:check_lock).and_return(true)
+      @provider.should_receive(:unlock_user).and_return(true)
+      @provider.action_unlock
+      @provider.set_updated_status
+      @new_resource.should be_updated
+    end
+
+    it "should raise a Chef::Exceptions::User if we try and unlock a user that does not exist" do
+      @provider.user_exists = false
+      @provider.action = :unlock
+      lambda { @provider.run_action }.should raise_error(Chef::Exceptions::User)
+    end
+  end
+
+  describe "convert_group_name" do
+    before do
+      @new_resource.gid('999')
+      @group = EtcGrnamIsh.new('wheel', '*', 999, [])
+    end
+
+    it "should lookup the group name locally" do
+      Etc.should_receive(:getgrnam).with("999").and_return(@group)
+      @provider.convert_group_name.should == 999
+    end
+
+    it "should raise an error if we can't translate the group name during resource assertions" do
+      Etc.should_receive(:getgrnam).and_raise(ArgumentError)
+      @provider.define_resource_requirements
+      @provider.convert_group_name
+      lambda { @provider.process_resource_requirements }.should raise_error(Chef::Exceptions::User)
+    end
+
+    it "should set the new resources gid to the integerized version if available" do
+      Etc.should_receive(:getgrnam).with("999").and_return(@group)
+      @provider.convert_group_name
+      @new_resource.gid.should == 999
+    end
+  end
+end
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
new file mode 100644
index 0000000..7873be0
--- /dev/null
+++ b/spec/unit/provider_spec.rb
@@ -0,0 +1,169 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+
+class NoWhyrunDemonstrator < Chef::Provider
+  attr_reader :system_state_altered
+  def whyrun_supported?
+    false
+  end
+  def load_current_resource
+
+  end
+  def action_foo
+    @system_state_altered = true
+  end
+end
+
+class ConvergeActionDemonstrator < Chef::Provider
+  attr_reader :system_state_altered
+
+  def whyrun_supported?
+    true
+  end
+
+  def load_current_resource
+  end
+
+  def action_foo
+    converge_by("running a state changing action") do
+      @system_state_altered = true
+    end
+  end
+end
+
+describe Chef::Provider do
+  before(:each) do
+    @cookbook_collection = Chef::CookbookCollection.new([])
+    @node = Chef::Node.new
+    @node.name "latte"
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    @resource = Chef::Resource.new("funk", @run_context)
+    @resource.cookbook_name = "a_delicious_pie"
+    @provider = Chef::Provider.new(@resource, @run_context)
+  end
+
+  it "should store the resource passed to new as new_resource" do
+    @provider.new_resource.should eql(@resource)
+  end
+
+  it "should store the node passed to new as node" do
+    @provider.node.should eql(@node)
+  end
+
+  it "should have nil for current_resource by default" do
+    @provider.current_resource.should eql(nil)
+  end
+
+  it "should not support whyrun by default" do
+    @provider.send(:whyrun_supported?).should eql(false)
+  end
+
+  it "should return true for action_nothing" do
+    @provider.action_nothing.should eql(true)
+  end
+
+  it "evals embedded recipes with a pristine resource collection" do
+    @provider.run_context.instance_variable_set(:@resource_collection, "doesn't matter what this is")
+    temporary_collection = nil
+    snitch = Proc.new {temporary_collection = @run_context.resource_collection}
+    @provider.send(:recipe_eval, &snitch)
+    temporary_collection.should be_an_instance_of(Chef::ResourceCollection)
+    @provider.run_context.instance_variable_get(:@resource_collection).should == "doesn't matter what this is"
+  end
+
+  it "does not re-load recipes when creating the temporary run context" do
+    # we actually want to test that RunContext#load is never called, but we
+    # can't stub all instances of an object with rspec's mocks. :/
+    Chef::RunContext.stub!(:new).and_raise("not supposed to happen")
+    snitch = Proc.new {temporary_collection = @run_context.resource_collection}
+    @provider.send(:recipe_eval, &snitch)
+  end
+
+  context "when no converge actions are queued" do
+    before do
+      @provider.stub!(:whyrun_supported?).and_return(true)
+      @provider.stub!(:load_current_resource)
+    end
+
+    it "does not mark the new resource as updated" do
+      @resource.should_not be_updated
+      @resource.should_not be_updated_by_last_action
+    end
+  end
+
+  context "when converge actions have been added to the queue" do
+    describe "and provider supports whyrun mode" do
+      before do
+        @provider = ConvergeActionDemonstrator.new(@resource, @run_context)
+      end
+
+      it "should tell us that it does support whyrun" do
+        @provider.should be_whyrun_supported
+      end
+
+      it "queues up converge actions" do
+        @provider.action_foo
+        @provider.send(:converge_actions).should have(1).actions
+      end
+
+      it "executes pending converge actions to converge the system" do
+        @provider.run_action(:foo)
+        @provider.instance_variable_get(:@system_state_altered).should be_true
+      end
+
+      it "marks the resource as updated" do
+        @provider.run_action(:foo)
+        @resource.should be_updated
+        @resource.should be_updated_by_last_action
+      end
+    end
+
+    describe "and provider does not support whyrun mode" do
+      before do
+        Chef::Config[:why_run] = true
+        @provider = NoWhyrunDemonstrator.new(@resource, @run_context)
+      end
+
+      after do
+        Chef::Config[:why_run] = false
+      end
+
+      it "should tell us that it doesn't support whyrun" do
+        @provider.should_not be_whyrun_supported
+      end
+
+      it "should automatically generate a converge_by block on the provider's behalf" do
+        @provider.run_action(:foo)
+        @provider.send(:converge_actions).should have(0).actions
+        @provider.system_state_altered.should be_false
+      end
+
+      it "should automatically execute the generated converge_by block" do
+        @provider.run_action(:foo)
+        @provider.system_state_altered.should be_false
+        @resource.should_not be_updated
+        @resource.should_not be_updated_by_last_action
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb
new file mode 100644
index 0000000..4615bcb
--- /dev/null
+++ b/spec/unit/recipe_spec.rb
@@ -0,0 +1,270 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2008-2011 Opscode, Inc.
+# 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::Recipe do
+  before(:each) do
+    @cookbook_repo = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks"))
+    cl = Chef::CookbookLoader.new(@cookbook_repo)
+    cl.load_cookbooks
+    @cookbook_collection = Chef::CookbookCollection.new(cl)
+    @node = Chef::Node.new
+    @node.normal[:tags] = Array.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    @recipe = Chef::Recipe.new("hjk", "test", @run_context)
+
+    # Shell/ext.rb is on the run path, and it defines
+    # Chef::Recipe#resources to call pp, which we don't want when
+    # we're running tests.
+    @recipe.stub!(:pp)
+  end
+
+  describe "method_missing" do
+    describe "resources" do
+      it "should load a two word (zen_master) resource" do
+        lambda do
+          @recipe.zen_master "monkey" do
+            peace true
+          end
+        end.should_not raise_error(ArgumentError)
+      end
+
+      it "should load a one word (cat) resource" do
+        lambda do
+          @recipe.cat "loulou" do
+            pretty_kitty true
+          end
+        end.should_not raise_error(ArgumentError)
+      end
+
+      it "should load a four word (one_two_three_four) resource" do
+        lambda do
+          @recipe.one_two_three_four "numbers" do
+            i_can_count true
+          end
+        end.should_not raise_error(ArgumentError)
+      end
+
+      it "should throw an error if you access a resource that we can't find" do
+        lambda { @recipe.not_home("not_home_resource") }.should raise_error(NameError)
+      end
+
+      it "should require a name argument" do
+        lambda {
+          @recipe.cat
+        }.should raise_error(ArgumentError, "You must supply a name when declaring a cat resource")
+      end
+
+      it "should allow regular errors (not NameErrors) to pass unchanged" do
+        lambda {
+          @recipe.cat("felix") { raise ArgumentError, "You Suck" }
+        }.should raise_error(ArgumentError)
+      end
+
+      it "should add our zen_master to the collection" do
+        @recipe.zen_master "monkey" do
+          peace true
+        end
+        @run_context.resource_collection.lookup("zen_master[monkey]").name.should eql("monkey")
+      end
+
+      it "should add our zen masters to the collection in the order they appear" do
+        %w{monkey dog cat}.each do |name|
+          @recipe.zen_master name do
+            peace true
+          end
+        end
+
+        @run_context.resource_collection.map{|r| r.name}.should eql(["monkey", "dog", "cat"])
+      end
+
+      it "should return the new resource after creating it" do
+        res = @recipe.zen_master "makoto" do
+          peace true
+        end
+        res.resource_name.should eql(:zen_master)
+        res.name.should eql("makoto")
+      end
+
+      describe "should locate platform mapped resources" do
+
+        it "locate resource for particular platform" do
+          Object.const_set('ShaunTheSheep', Class.new(Chef::Resource){ provides :laughter, :on_platforms => ["television"] })
+          @node.automatic[:platform] = "television"
+          @node.automatic[:platform_version] = "123"
+          res = @recipe.laughter "timmy"
+          res.name.should eql("timmy")
+          res.kind_of?(ShaunTheSheep)
+        end
+
+        it "locate a resource for all platforms" do
+          Object.const_set("YourMom", Class.new(Chef::Resource){ provides :love_and_caring })
+          res = @recipe.love_and_caring "mommy"
+          res.name.should eql("mommy")
+          res.kind_of?(YourMom)
+        end
+
+      end
+    end
+
+    describe "resource definitions" do
+      it "should execute defined resources" do
+        crow_define = Chef::ResourceDefinition.new
+        crow_define.define :crow, :peace => false, :something => true do
+          zen_master "lao tzu" do
+            peace params[:peace]
+            something params[:something]
+          end
+        end
+        @run_context.definitions[:crow] = crow_define
+        @recipe.crow "mine" do
+          peace true
+        end
+        @run_context.resource_collection.resources(:zen_master => "lao tzu").name.should eql("lao tzu")
+        @run_context.resource_collection.resources(:zen_master => "lao tzu").something.should eql(true)
+      end
+
+      it "should set the node on defined resources" do
+        crow_define = Chef::ResourceDefinition.new
+        crow_define.define :crow, :peace => false, :something => true do
+          zen_master "lao tzu" do
+            peace params[:peace]
+            something params[:something]
+          end
+        end
+        @run_context.definitions[:crow] = crow_define
+        @node.normal[:foo] = false
+        @recipe.crow "mine" do
+          something node[:foo]
+        end
+        @recipe.resources(:zen_master => "lao tzu").something.should eql(false)
+      end
+    end
+
+  end
+
+  describe "instance_eval" do
+    it "should handle an instance_eval properly" do
+      code = <<-CODE
+  zen_master "gnome" do
+    peace = true
+  end
+  CODE
+      lambda { @recipe.instance_eval(code) }.should_not raise_error
+      @recipe.resources(:zen_master => "gnome").name.should eql("gnome")
+    end
+  end
+
+  describe "from_file" do
+    it "should load a resource from a ruby file" do
+      @recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb"))
+      res = @recipe.resources(:file => "/etc/nsswitch.conf")
+      res.name.should eql("/etc/nsswitch.conf")
+      res.action.should eql([:create])
+      res.owner.should eql("root")
+      res.group.should eql("root")
+      res.mode.should eql(0644)
+    end
+
+    it "should raise an exception if the file cannot be found or read" do
+      lambda { @recipe.from_file("/tmp/monkeydiving") }.should raise_error(IOError)
+    end
+  end
+
+  describe "include_recipe" do
+    it "should evaluate another recipe with include_recipe" do
+      @run_context.include_recipe "openldap::gigantor"
+      res = @run_context.resource_collection.resources(:cat => "blanket")
+      res.name.should eql("blanket")
+      res.pretty_kitty.should eql(false)
+    end
+
+    it "should load the default recipe for a cookbook if include_recipe is called without a ::" do
+      @run_context.include_recipe "openldap"
+      res = @run_context.resource_collection.resources(:cat => "blanket")
+      res.name.should eql("blanket")
+      res.pretty_kitty.should eql(true)
+    end
+
+    it "should store that it has seen a recipe in the run_context" do
+      @run_context.include_recipe "openldap"
+      @run_context.loaded_recipe?("openldap").should be_true
+    end
+
+    it "should not include the same recipe twice" do
+      @cookbook_collection[:openldap].should_receive(:load_recipe).with("default", @run_context)
+      @recipe.include_recipe "openldap"
+      @cookbook_collection[:openldap].should_not_receive(:load_recipe).with("default", @run_context)
+      @recipe.include_recipe "openldap"
+    end
+  end
+
+  describe "tags" do
+    it "should set tags via tag" do
+      @recipe.tag "foo"
+      @node[:tags].should include("foo")
+    end
+
+    it "should set multiple tags via tag" do
+      @recipe.tag "foo", "bar"
+      @node[:tags].should include("foo")
+      @node[:tags].should include("bar")
+    end
+
+    it "should not set the same tag twice via tag" do
+      @recipe.tag "foo"
+      @recipe.tag "foo"
+      @node[:tags].should eql([ "foo" ])
+    end
+
+    it "should return the current list of tags from tag with no arguments" do
+      @recipe.tag "foo"
+      @recipe.tag.should eql([ "foo" ])
+    end
+
+    it "should return true from tagged? if node is tagged" do
+      @recipe.tag "foo"
+      @recipe.tagged?("foo").should be(true)
+    end
+
+    it "should return false from tagged? if node is not tagged" do
+      @recipe.tagged?("foo").should be(false)
+    end
+
+    it "should return false from tagged? if node is not tagged" do
+      @recipe.tagged?("foo").should be(false)
+    end
+
+    it "should remove a tag from the tag list via untag" do
+      @recipe.tag "foo"
+      @recipe.untag "foo"
+      @node[:tags].should eql([])
+    end
+
+    it "should remove multiple tags from the tag list via untag" do
+      @recipe.tag "foo", "bar"
+      @recipe.untag "bar", "foo"
+      @node[:tags].should eql([])
+    end
+  end
+end
diff --git a/spec/unit/registry_helper_spec.rb b/spec/unit/registry_helper_spec.rb
new file mode 100644
index 0000000..49469b2
--- /dev/null
+++ b/spec/unit/registry_helper_spec.rb
@@ -0,0 +1,376 @@
+#
+# Author:: Prajakta Purohit (prajakta at opscode.com)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::RegistryKey do
+
+  let(:value1) { { :name => "one", :type => :string, :data => "1" } }
+  let(:key_path) { 'HKCU\Software\OpscodeNumbers' }
+  let(:key) { 'Software\OpscodeNumbers' }
+  let(:key_parent) { 'Software' }
+  let(:key_to_delete) { 'OpscodeNumbers' }
+  let(:sub_key) {'OpscodePrimes'}
+  let(:missing_key_path) {'HKCU\Software'}
+
+  before(:each) do
+    Chef::Win32::Registry.any_instance.stub(:machine_architecture).and_return(:x86_64)
+    @registry = Chef::Win32::Registry.new()
+
+    #Making the values for registry constants available on unix
+    Object.send(:remove_const, 'Win32') if defined?(Win32)
+    Win32 = Module.new
+    Win32::Registry = Class.new
+    Win32::Registry::KEY_SET_VALUE = 0x0002
+    Win32::Registry::KEY_QUERY_VALUE = 0x0001
+    Win32::Registry::KEY_WRITE = 0x00020000 | 0x0002 | 0x0004
+    Win32::Registry::KEY_READ = 0x00020000 | 0x0001 | 0x0008 | 0x0010
+
+    Win32::Registry::Error = Class.new(RuntimeError)
+
+    @hive_mock = mock("::Win32::Registry::HKEY_CURRENT_USER")
+    @reg_mock = mock("reg")
+  end
+
+  describe "get_values" do
+    it "gets all values for a key if the key exists" do
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:map)
+      @registry.get_values(key_path)
+    end
+
+    it "throws an exception if key does not exist" do
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      lambda{@registry.get_values(key_path)}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "set_value" do
+    it "does nothing if key and hive and value exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(true)
+      @registry.should_receive(:data_exists?).with(key_path, value1).and_return(true)
+      @registry.set_value(key_path, value1)
+    end
+
+    it "updates value if key and hive and value exist, but data is different" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(true)
+      @registry.should_receive(:data_exists?).with(key_path, value1).and_return(false)
+      @hive_mock.should_receive(:open).with(key, Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @registry.should_receive(:get_type_from_name).with(:string).and_return(1)
+      @reg_mock.should_receive(:write).with("one", 1, "1")
+      @registry.set_value(key_path, value1)
+    end
+
+    it "creates value if the key exists and the value does not exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(false)
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | ::Win32::Registry::KEY_QUERY_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @registry.should_receive(:get_type_from_name).with(:string).and_return(1)
+      @reg_mock.should_receive(:write).with("one", 1, "1")
+      @registry.set_value(key_path, value1)
+    end
+
+    it "should raise an exception if the key does not exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      lambda {@registry.set_value(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "delete_value" do
+    it "deletes value if value exists" do
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_SET_VALUE | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:delete_value).with("one").and_return(true)
+      @registry.delete_value(key_path, value1)
+    end
+
+    it "raises an exception if the key does not exist" do
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      @registry.delete_value(key_path, value1)
+    end
+
+    it "does nothing if the value does not exist" do
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(false)
+      @registry.delete_value(key_path, value1)
+    end
+  end
+
+  describe "create_key" do
+    it "creates key if intermediate keys are missing and recursive is set to true" do
+      @registry.should_receive(:keys_missing?).with(key_path).and_return(true)
+      @registry.should_receive(:create_missing).with(key_path)
+      @registry.should_receive(:key_exists?).with(key_path).and_return(false)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture)
+      @registry.create_key(key_path, true)
+    end
+
+    it "raises an exception if intermediate keys are missing and recursive is set to false" do
+      @registry.should_receive(:keys_missing?).with(key_path).and_return(true)
+      lambda{@registry.create_key(key_path, false)}.should raise_error(Chef::Exceptions::Win32RegNoRecursive)
+    end
+
+    it "does nothing if the key exists" do
+      @registry.should_receive(:keys_missing?).with(key_path).and_return(true)
+      @registry.should_receive(:create_missing).with(key_path)
+      @registry.should_receive(:key_exists?).with(key_path).and_return(true)
+      @registry.create_key(key_path, true)
+    end
+
+    it "create key if intermediate keys not missing and recursive is set to false" do
+      @registry.should_receive(:keys_missing?).with(key_path).and_return(false)
+      @registry.should_receive(:key_exists?).with(key_path).and_return(false)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture)
+      @registry.create_key(key_path, false)
+    end
+
+    it "create key if intermediate keys not missing and recursive is set to true" do
+      @registry.should_receive(:keys_missing?).with(key_path).and_return(false)
+      @registry.should_receive(:key_exists?).with(key_path).and_return(false)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:create).with(key, ::Win32::Registry::KEY_WRITE | @registry.registry_system_architecture)
+      @registry.create_key(key_path, true)
+    end
+  end
+
+  describe "delete_key", :windows_only do
+    it "deletes key if it has subkeys and recursive is set to true" do
+      @registry.should_receive(:key_exists?).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:has_subkeys?).with(key_path).and_return(true)
+      @registry.should_receive(:get_subkeys).with(key_path).and_return([sub_key])
+      @registry.should_receive(:key_exists?).with(key_path+"\\"+sub_key).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path+"\\"+sub_key).and_return([@hive_mock, key+"\\"+sub_key])
+      @registry.should_receive(:has_subkeys?).with(key_path+"\\"+sub_key).and_return(false)
+      @registry.should_receive(:delete_key_ex).twice
+      @registry.delete_key(key_path, true)
+    end
+
+    it "raises an exception if it has subkeys but recursive is set to false" do
+      @registry.should_receive(:key_exists?).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:has_subkeys?).with(key_path).and_return(true)
+      lambda{@registry.delete_key(key_path, false)}.should raise_error(Chef::Exceptions::Win32RegNoRecursive)
+    end
+
+    it "deletes key if the key exists and has no subkeys" do
+      @registry.should_receive(:key_exists?).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:has_subkeys?).with(key_path).and_return(false)
+      @registry.should_receive(:delete_key_ex)
+      @registry.delete_key(key_path, true)
+    end
+  end
+
+  describe "key_exists?" do
+    it "returns true if key_exists" do
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @registry.key_exists?(key_path).should == true
+    end
+
+    it "returns false if key does not exist" do
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_raise(::Win32::Registry::Error)
+      @registry.key_exists?(key_path).should == false
+    end
+  end
+
+  describe "key_exists!" do
+    it "throws an exception if the key_parent does not exist" do
+      @registry.should_receive(:key_exists?).with(key_path).and_return(false)
+      lambda{@registry.key_exists!(key_path)}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "hive_exists?" do
+    it "returns true if the hive exists" do
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.hive_exists?(key_path) == true
+    end
+
+    it "returns false if the hive does not exist" do
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_raise(Chef::Exceptions::Win32RegHiveMissing)
+      @registry.hive_exists?(key_path) == false
+    end
+  end
+
+  describe "has_subkeys?" do
+    it "returns true if the key has subkeys" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:each_key).and_yield(key)
+      @registry.has_subkeys?(key_path) == true
+    end
+
+    it "returns false if the key does not have subkeys" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:each_key).and_return(no_args())
+      @registry.has_subkeys?(key_path).should == false
+    end
+
+    it "throws an exception if the key does not exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      lambda {@registry.set_value(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+  end
+
+  describe "get_subkeys" do
+    it "returns the subkeys if they exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:each_key).and_yield(sub_key)
+      @registry.get_subkeys(key_path)
+    end
+  end
+
+  describe "value_exists?" do
+    it "throws an exception if the key does not exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      lambda {@registry.value_exists?(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "returns true if the value exists" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:any?).and_yield("one")
+      @registry.value_exists?(key_path, value1) == true
+    end
+
+    it "returns false if the value does not exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:any?).and_yield(no_args())
+      @registry.value_exists?(key_path, value1) == false
+    end
+  end
+
+  describe "data_exists?" do
+    it "throws an exception if the key does not exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_raise(Chef::Exceptions::Win32RegKeyMissing)
+      lambda {@registry.data_exists?(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegKeyMissing)
+    end
+
+    it "returns true if the data exists" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @registry.should_receive(:get_type_from_name).with(:string).and_return(1)
+      @reg_mock.should_receive(:each).with(no_args()).and_yield("one", 1, "1")
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @registry.data_exists?(key_path, value1).should == true
+    end
+
+    it "returns false if the data does not exist" do
+      @registry.should_receive(:key_exists!).with(key_path).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @registry.should_receive(:get_type_from_name).with(:string).and_return(1)
+      @reg_mock.should_receive(:each).with(no_args()).and_yield("one", 1, "2")
+      @registry.data_exists?(key_path, value1).should == false
+    end
+  end
+
+  describe "value_exists!" do
+    it "does nothing if the value exists" do
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(true)
+      @registry.value_exists!(key_path, value1)
+    end
+
+    it "throws an exception if the value does not exist" do
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(false)
+      lambda{@registry.value_exists!(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegValueMissing)
+    end
+  end
+
+  describe "data_exists!" do
+    it "does nothing if the data exists" do
+      @registry.should_receive(:data_exists?).with(key_path, value1).and_return(true)
+      @registry.data_exists!(key_path, value1)
+    end
+
+    it "throws an exception if the data does not exist" do
+      @registry.should_receive(:data_exists?).with(key_path, value1).and_return(false)
+      lambda{@registry.data_exists!(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegDataMissing)
+    end
+  end
+
+  describe "type_matches?" do
+    it "returns true if type matches" do
+      @registry.should_receive(:value_exists!).with(key_path, value1).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @registry.should_receive(:get_type_from_name).with(:string).and_return(1)
+      @reg_mock.should_receive(:each).and_yield("one", 1)
+      @registry.type_matches?(key_path, value1).should == true
+    end
+
+    it "returns false if type does not match" do
+      @registry.should_receive(:value_exists!).with(key_path, value1).and_return(true)
+      @registry.should_receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
+      @hive_mock.should_receive(:open).with(key, ::Win32::Registry::KEY_READ | @registry.registry_system_architecture).and_yield(@reg_mock)
+      @reg_mock.should_receive(:each).and_yield("two", 2)
+      @registry.type_matches?(key_path, value1).should == false
+    end
+
+    it "throws an exception if value does not exist" do
+      @registry.should_receive(:value_exists?).with(key_path, value1).and_return(false)
+      lambda{@registry.type_matches?(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegValueMissing)
+    end
+  end
+
+  describe "type_matches!" do
+    it "does nothing if the type_matches" do
+      @registry.should_receive(:type_matches?).with(key_path, value1).and_return(true)
+      @registry.type_matches!(key_path, value1)
+    end
+
+    it "throws an exception if the type does not match" do
+      @registry.should_receive(:type_matches?).with(key_path, value1).and_return(false)
+      lambda{@registry.type_matches!(key_path, value1)}.should raise_error(Chef::Exceptions::Win32RegTypesMismatch)
+    end
+  end
+
+  describe "keys_missing?" do
+    it "returns true if the keys are missing" do
+      @registry.should_receive(:key_exists?).with(missing_key_path).and_return(false)
+      @registry.keys_missing?(key_path).should == true
+    end
+
+    it "returns false if no keys in the path are missing" do
+      @registry.should_receive(:key_exists?).with(missing_key_path).and_return(true)
+      @registry.keys_missing?(key_path).should == false
+    end
+  end
+end
diff --git a/spec/unit/resource/apt_package_spec.rb b/spec/unit/resource/apt_package_spec.rb
new file mode 100644
index 0000000..58b007c
--- /dev/null
+++ b/spec/unit/resource/apt_package_spec.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::AptPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::AptPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::AptPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::AptPackage)
+  end
+
+  it "should set the resource_name to :apt_package" do
+    @resource.resource_name.should eql(:apt_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Apt" do
+    @resource.provider.should eql(Chef::Provider::Package::Apt)
+  end
+
+  it "should support default_release" do
+    @resource.default_release("lenny-backports")
+    @resource.default_release.should eql("lenny-backports")
+  end
+end
diff --git a/spec/unit/resource/bash_spec.rb b/spec/unit/resource/bash_spec.rb
new file mode 100644
index 0000000..d729db6
--- /dev/null
+++ b/spec/unit/resource/bash_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::Bash do
+
+  before(:each) do
+    @resource = Chef::Resource::Bash.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Bash" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Bash)
+  end
+
+  it "should have a resource name of :bash" do
+    @resource.resource_name.should eql(:bash)
+  end
+
+  it "should have an interpreter of bash" do
+    @resource.interpreter.should eql("bash")
+  end
+
+end
diff --git a/spec/unit/resource/batch_spec.rb b/spec/unit/resource/batch_spec.rb
new file mode 100644
index 0000000..b74c7d2
--- /dev/null
+++ b/spec/unit/resource/batch_spec.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::Batch do
+
+  before(:each) do
+    node = Chef::Node.new
+
+    node.default["kernel"] = Hash.new
+    node.default["kernel"][:machine] = :x86_64.to_s
+
+    run_context = Chef::RunContext.new(node, nil, nil)
+
+    @resource = Chef::Resource::Batch.new("batch_unit_test", run_context)
+
+  end
+
+  it "should create a new Chef::Resource::Batch" do
+    @resource.should be_a_kind_of(Chef::Resource::Batch)
+  end
+
+  context "windows script" do
+    let(:resource_instance) { @resource }
+    let(:resource_instance_name ) { @resource.command }
+    let(:resource_name) { :batch }
+    let(:interpreter_file_name) { 'cmd.exe' }
+
+    it_should_behave_like "a Windows script resource"
+  end
+
+end
diff --git a/spec/unit/resource/breakpoint_spec.rb b/spec/unit/resource/breakpoint_spec.rb
new file mode 100644
index 0000000..412211a
--- /dev/null
+++ b/spec/unit/resource/breakpoint_spec.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Resource::Breakpoint do
+
+  before do
+    @breakpoint = Chef::Resource::Breakpoint.new
+  end
+
+  it "allows the action :break" do
+    @breakpoint.allowed_actions.should include(:break)
+  end
+
+  it "defaults to the break action" do
+    @breakpoint.action.should == "break"
+  end
+
+  it "names itself after the line number of the file where it's created" do
+    @breakpoint.name.should match(/breakpoint_spec\.rb\:[\d]{2}\:in \`new\'$/)
+  end
+
+  it "uses the breakpoint provider" do
+    @breakpoint.provider.should == Chef::Provider::Breakpoint
+  end
+
+end
diff --git a/spec/unit/resource/chef_gem_spec.rb b/spec/unit/resource/chef_gem_spec.rb
new file mode 100644
index 0000000..1d7ca8a
--- /dev/null
+++ b/spec/unit/resource/chef_gem_spec.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Bryan McLellan <btm at loftninjas.org>
+# Copyright:: Copyright (c) 2008, 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::ChefGem, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::ChefGem.new("foo")
+  end
+
+  it "should return a Chef::Resource::ChefGem" do
+    @resource.should be_a_kind_of(Chef::Resource::ChefGem)
+  end
+
+  it "should set the resource_name to :chef_gem" do
+    @resource.resource_name.should eql(:chef_gem)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Rubygems" do
+    @resource.provider.should eql(Chef::Provider::Package::Rubygems)
+  end
+end
+
+describe Chef::Resource::ChefGem, "gem_binary" do
+  before(:each) do
+    @resource = Chef::Resource::ChefGem.new("foo")
+  end
+
+  it "should raise an exception when gem_binary is set" do
+    lambda { @resource.gem_binary("/lol/cats/gem") }.should raise_error(ArgumentError)
+  end
+end
diff --git a/spec/unit/resource/conditional_action_not_nothing_spec.rb b/spec/unit/resource/conditional_action_not_nothing_spec.rb
new file mode 100644
index 0000000..49bc0ad
--- /dev/null
+++ b/spec/unit/resource/conditional_action_not_nothing_spec.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Xabier de Zuazo (<xabier at onddo.com>)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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::Resource::ConditionalActionNotNothing do
+
+  describe "after running a :nothing action" do
+    before do
+      @action = :nothing
+      @conditional = Chef::Resource::ConditionalActionNotNothing.new(@action)
+    end
+
+    it "indicates that resource convergence should not continue" do
+      @conditional.continue?.should be_false
+    end
+  end
+
+  describe "after running an action different to :nothing" do
+    before do
+      @action = :something
+      @conditional = Chef::Resource::ConditionalActionNotNothing.new(@action)
+    end
+
+    it "indicates that resource convergence should continue" do
+      @conditional.continue?.should be_true
+    end
+  end
+
+end
diff --git a/spec/unit/resource/conditional_spec.rb b/spec/unit/resource/conditional_spec.rb
new file mode 100644
index 0000000..1be7bce
--- /dev/null
+++ b/spec/unit/resource/conditional_spec.rb
@@ -0,0 +1,147 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2011 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 'ostruct'
+
+describe Chef::Resource::Conditional do
+  before do
+    Mixlib::ShellOut.any_instance.stub(:run_command).and_return(nil)
+    @status = OpenStruct.new(:success? => true)
+    Mixlib::ShellOut.any_instance.stub(:status).and_return(@status)
+  end
+
+  describe "when created as an `only_if`" do
+    describe "after running a successful command" do
+      before do
+        @conditional = Chef::Resource::Conditional.only_if("true")
+      end
+
+      it "indicates that resource convergence should continue" do
+        @conditional.continue?.should be_true
+      end
+    end
+
+    describe "after running a negative/false command" do
+      before do
+        @status.send("success?=", false)
+        @conditional = Chef::Resource::Conditional.only_if("false")
+      end
+
+      it "indicates that resource convergence should not continue" do
+        @conditional.continue?.should be_false
+      end
+    end
+
+    describe 'after running a command which timed out' do
+      before do
+        @conditional = Chef::Resource::Conditional.only_if("false")
+        @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
+      end
+
+      it 'indicates that resource convergence should not continue' do
+        @conditional.continue?.should be_false
+      end
+
+      it 'should log a warning' do
+        Chef::Log.should_receive(:warn).with("Command 'false' timed out")
+        @conditional.continue?
+      end
+    end
+
+    describe "after running a block that returns a truthy value" do
+      before do
+        @conditional = Chef::Resource::Conditional.only_if { Object.new }
+      end
+
+      it "indicates that resource convergence should continue" do
+        @conditional.continue?.should be_true
+      end
+    end
+
+    describe "after running a block that returns a falsey value" do
+      before do
+        @conditional = Chef::Resource::Conditional.only_if { nil }
+      end
+
+      it "indicates that resource convergence should not continue" do
+        @conditional.continue?.should be_false
+      end
+    end
+  end
+
+  describe "when created as a `not_if`" do
+    describe "after running a successful/true command" do
+      before do
+        @conditional = Chef::Resource::Conditional.not_if("true")
+      end
+
+      it "indicates that resource convergence should not continue" do
+        @conditional.continue?.should be_false
+      end
+    end
+
+    describe "after running a failed/false command" do
+      before do
+        @status.send("success?=", false)
+        @conditional = Chef::Resource::Conditional.not_if("false")
+      end
+
+      it "indicates that resource convergence should continue" do
+        @conditional.continue?.should be_true
+      end
+    end
+
+    describe 'after running a command which timed out' do
+      before do
+        @conditional = Chef::Resource::Conditional.not_if("false")
+        @conditional.stub(:shell_out).and_raise(Chef::Exceptions::CommandTimeout)
+      end
+
+      it 'indicates that resource convergence should continue' do
+        @conditional.continue?.should be_true
+      end
+
+      it 'should log a warning' do
+        Chef::Log.should_receive(:warn).with("Command 'false' timed out")
+        @conditional.continue?
+      end
+    end
+
+    describe "after running a block that returns a truthy value" do
+      before do
+        @conditional = Chef::Resource::Conditional.not_if { Object.new }
+      end
+
+      it "indicates that resource convergence should not continue" do
+        @conditional.continue?.should be_false
+      end
+    end
+
+    describe "after running a block that returns a falsey value" do
+      before do
+        @conditional = Chef::Resource::Conditional.not_if { nil }
+      end
+
+      it "indicates that resource convergence should continue" do
+        @conditional.continue?.should be_true
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/resource/cookbook_file_spec.rb b/spec/unit/resource/cookbook_file_spec.rb
new file mode 100644
index 0000000..6c55c80
--- /dev/null
+++ b/spec/unit/resource/cookbook_file_spec.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+#p 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::Resource::CookbookFile do
+  before do
+    @cookbook_file = Chef::Resource::CookbookFile.new('sourcecode_tarball.tgz')
+  end
+
+  it "uses the name parameter for the source parameter" do
+    @cookbook_file.name.should == 'sourcecode_tarball.tgz'
+  end
+
+  it "has a source parameter" do
+    @cookbook_file.name('config_file.conf')
+    @cookbook_file.name.should == 'config_file.conf'
+  end
+
+  it "defaults to a nil cookbook parameter (current cookbook will be used)" do
+    @cookbook_file.cookbook.should be_nil
+  end
+
+  it "has a cookbook parameter" do
+    @cookbook_file.cookbook("munin")
+    @cookbook_file.cookbook.should == 'munin'
+  end
+
+  it "sets the provider to Chef::Provider::CookbookFile" do
+    @cookbook_file.provider.should == Chef::Provider::CookbookFile
+  end
+
+  describe "when it has a backup number, group, mode, owner, source, checksum, and cookbook on nix or path, rights, deny_rights, checksum on windows" do
+    before do
+       if Chef::Platform.windows?
+         @cookbook_file.path("C:/temp/origin/file.txt")
+         @cookbook_file.rights(:read, "Everyone")
+         @cookbook_file.deny_rights(:full_control, "Clumsy_Sam")
+       else
+         @cookbook_file.path("/tmp/origin/file.txt")
+         @cookbook_file.group("wheel")
+         @cookbook_file.mode("0664")
+         @cookbook_file.owner("root")
+         @cookbook_file.source("/tmp/foo.txt")
+         @cookbook_file.cookbook("/tmp/cookbooks/cooked.rb")
+       end
+      @cookbook_file.checksum("1" * 64)
+    end
+
+
+    it "describes the state" do
+      state = @cookbook_file.state
+      if Chef::Platform.windows?
+        puts state
+        state[:rights].should == [{:permissions => :read, :principals => "Everyone"}]
+        state[:deny_rights].should == [{:permissions => :full_control, :principals => "Clumsy_Sam"}]
+      else
+        state[:group].should == "wheel"
+        state[:mode].should == "0664"
+        state[:owner].should == "root"
+      end
+      state[:checksum].should == "1" * 64
+    end
+
+    it "returns the path as its identity" do
+      if Chef::Platform.windows?
+        @cookbook_file.identity.should == "C:/temp/origin/file.txt"
+      else
+        @cookbook_file.identity.should == "/tmp/origin/file.txt"
+      end
+    end
+  end
+end
diff --git a/spec/unit/resource/cron_spec.rb b/spec/unit/resource/cron_spec.rb
new file mode 100644
index 0000000..7f294fa
--- /dev/null
+++ b/spec/unit/resource/cron_spec.rb
@@ -0,0 +1,181 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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::Resource::Cron do
+
+  before(:each) do
+    @resource = Chef::Resource::Cron.new("cronify")
+  end
+
+  it "should create a new Chef::Resource::Cron" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Cron)
+  end
+
+  it "should have a name" do
+    @resource.name.should eql("cronify")
+  end
+
+  it "should have a default action of 'create'" do
+    @resource.action.should eql(:create)
+  end
+
+  it "should accept create or delete for action" do
+    lambda { @resource.action :create }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :delete }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :lolcat }.should raise_error(ArgumentError)
+  end
+
+  it "should allow you to set a command" do
+    @resource.command "/bin/true"
+    @resource.command.should eql("/bin/true")
+  end
+
+  it "should allow you to set a user" do
+    @resource.user "daemon"
+    @resource.user.should eql("daemon")
+  end
+
+  it "should allow you to specify the minute" do
+    @resource.minute "30"
+    @resource.minute.should eql("30")
+  end
+
+  it "should allow you to specify the hour" do
+    @resource.hour "6"
+    @resource.hour.should eql("6")
+  end
+
+  it "should allow you to specify the day" do
+    @resource.day "10"
+    @resource.day.should eql("10")
+  end
+
+  it "should allow you to specify the month" do
+    @resource.month "10"
+    @resource.month.should eql("10")
+  end
+
+  it "should allow you to specify the weekday" do
+    @resource.weekday "2"
+    @resource.weekday.should eql("2")
+  end
+
+  it "should allow you to specify the mailto variable" do
+    @resource.mailto "test at example.com"
+    @resource.mailto.should eql("test at example.com")
+  end
+
+  it "should allow you to specify the path" do
+    @resource.path "/usr/bin:/usr/sbin"
+    @resource.path.should eql("/usr/bin:/usr/sbin")
+  end
+
+  it "should allow you to specify the home directory" do
+    @resource.home "/root"
+    @resource.home.should eql("/root")
+  end
+
+  it "should allow you to specify the shell to run the command with" do
+    @resource.shell "/bin/zsh"
+    @resource.shell.should eql("/bin/zsh")
+  end
+
+  it "should allow you to specify environment variables hash" do
+    env = {"TEST" => "LOL"}
+    @resource.environment env
+    @resource.environment.should eql(env)
+  end
+
+  it "should allow * for all time and date values" do
+    [ "minute", "hour", "day", "month", "weekday" ].each do |x|
+      @resource.send(x, "*").should eql("*")
+    end
+  end
+
+  it "should allow ranges for all time and date values" do
+    [ "minute", "hour", "day", "month", "weekday" ].each do |x|
+      @resource.send(x, "1-2,5").should eql("1-2,5")
+    end
+  end
+
+  it "should have a default value of * for all time and date values" do
+    [ "minute", "hour", "day", "month", "weekday" ].each do |x|
+      @resource.send(x).should eql("*")
+    end
+  end
+
+  it "should have a default value of root for the user" do
+    @resource.user.should eql("root")
+  end
+
+  it "should reject any minute over 59" do
+    lambda { @resource.minute "60" }.should raise_error(RangeError)
+  end
+
+  it "should reject any hour over 23" do
+    lambda { @resource.hour "24" }.should raise_error(RangeError)
+  end
+
+  it "should reject any day over 31" do
+    lambda { @resource.day "32" }.should raise_error(RangeError)
+  end
+
+  it "should reject any month over 12" do
+    lambda { @resource.month "13" }.should raise_error(RangeError)
+  end
+
+  it "should reject any weekday over 7" do
+    lambda { @resource.weekday "8" }.should raise_error(RangeError)
+  end
+
+  it "should convert integer schedule values to a string" do
+    [ "minute", "hour", "day", "month", "weekday" ].each do |x|
+      @resource.send(x, 5).should eql("5")
+    end
+  end
+
+  describe "when it has a time (minute, hour, day, month, weeekend) and user" do
+    before do
+      @resource.command("tackle")
+      @resource.minute("1")
+      @resource.hour("2")
+      @resource.day("3")
+      @resource.month("4")
+      @resource.weekday("5")
+      @resource.user("root")
+    end
+
+    it "describes the state" do
+      state = @resource.state
+      state[:minute].should == "1"
+      state[:hour].should == "2"
+      state[:day].should == "3"
+      state[:month].should == "4"
+      state[:weekday].should == "5"
+      state[:user].should == "root"
+    end
+
+    it "returns the command as its identity" do
+      @resource.identity.should == "tackle"
+    end
+  end
+end
diff --git a/spec/unit/resource/csh_spec.rb b/spec/unit/resource/csh_spec.rb
new file mode 100644
index 0000000..e1534a8
--- /dev/null
+++ b/spec/unit/resource/csh_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::Csh do
+
+  before(:each) do
+    @resource = Chef::Resource::Csh.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Csh" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Csh)
+  end
+
+  it "should have a resource name of :csh" do
+    @resource.resource_name.should eql(:csh)
+  end
+
+  it "should have an interpreter of csh" do
+    @resource.interpreter.should eql("csh")
+  end
+
+end
diff --git a/spec/unit/resource/deploy_revision_spec.rb b/spec/unit/resource/deploy_revision_spec.rb
new file mode 100644
index 0000000..1f50999
--- /dev/null
+++ b/spec/unit/resource/deploy_revision_spec.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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::Resource::DeployRevision do
+
+  it "defaults to the revision deploy provider" do
+    @resource = Chef::Resource::DeployRevision.new("deploy _this_!")
+    @resource.provider.should == Chef::Provider::Deploy::Revision
+  end
+
+  it "has a name of deploy_revision" do
+    @resource = Chef::Resource::DeployRevision.new("deploy _this_!")
+    @resource.resource_name.should == :deploy_revision
+  end
+
+end
+
+describe Chef::Resource::DeployBranch do
+
+  it "defaults to the revision deploy provider" do
+    @resource = Chef::Resource::DeployBranch.new("deploy _this_!")
+    @resource.provider.should == Chef::Provider::Deploy::Revision
+  end
+
+  it "has a name of deploy_branch" do
+    @resource = Chef::Resource::DeployBranch.new("deploy _this_!")
+    @resource.resource_name.should == :deploy_branch
+  end
+
+end
diff --git a/spec/unit/resource/deploy_spec.rb b/spec/unit/resource/deploy_spec.rb
new file mode 100644
index 0000000..7cc25ed
--- /dev/null
+++ b/spec/unit/resource/deploy_spec.rb
@@ -0,0 +1,273 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Resource::Deploy do
+
+  class << self
+    def resource_has_a_string_attribute(attr_name)
+      it "has a String attribute for #{attr_name.to_s}" do
+        @resource.send(attr_name, "this is a string")
+        @resource.send(attr_name).should eql("this is a string")
+        lambda {@resource.send(attr_name, 8675309)}.should raise_error(ArgumentError)
+      end
+    end
+
+    def resource_has_a_boolean_attribute(attr_name, opts={:defaults_to=>false})
+      it "has a Boolean attribute for #{attr_name.to_s}" do
+        @resource.send(attr_name).should eql(opts[:defaults_to])
+        @resource.send(attr_name, !opts[:defaults_to])
+        @resource.send(attr_name).should eql( !opts[:defaults_to] )
+      end
+    end
+
+    def resource_has_a_callback_attribute(attr_name)
+      it "has a Callback attribute #{attr_name}" do
+        callback_block = lambda { :noop }
+        lambda {@resource.send(attr_name, &callback_block)}.should_not raise_error
+        @resource.send(attr_name).should == callback_block
+        callback_file = "path/to/callback.rb"
+        lambda {@resource.send(attr_name, callback_file)}.should_not raise_error
+        @resource.send(attr_name).should == callback_file
+        lambda {@resource.send(attr_name, :this_is_fail)}.should raise_error(ArgumentError)
+      end
+    end
+  end
+
+  before do
+    @resource = Chef::Resource::Deploy.new("/my/deploy/dir")
+  end
+
+  resource_has_a_string_attribute(:repo)
+  resource_has_a_string_attribute(:deploy_to)
+  resource_has_a_string_attribute(:role)
+  resource_has_a_string_attribute(:restart_command)
+  resource_has_a_string_attribute(:migration_command)
+  resource_has_a_string_attribute(:user)
+  resource_has_a_string_attribute(:group)
+  resource_has_a_string_attribute(:repository_cache)
+  resource_has_a_string_attribute(:copy_exclude)
+  resource_has_a_string_attribute(:revision)
+  resource_has_a_string_attribute(:remote)
+  resource_has_a_string_attribute(:git_ssh_wrapper)
+  resource_has_a_string_attribute(:svn_username)
+  resource_has_a_string_attribute(:svn_password)
+  resource_has_a_string_attribute(:svn_arguments)
+  resource_has_a_string_attribute(:svn_info_args)
+
+  resource_has_a_boolean_attribute(:migrate, :defaults_to=>false)
+  resource_has_a_boolean_attribute(:enable_submodules, :defaults_to=>false)
+  resource_has_a_boolean_attribute(:shallow_clone, :defaults_to=>false)
+
+  it "uses the first argument as the deploy directory" do
+    @resource.deploy_to.should eql("/my/deploy/dir")
+  end
+
+  # For git, any revision, branch, tag, whatever is resolved to a SHA1 ref.
+  # For svn, the branch is included in the repo URL.
+  # Therefore, revision and branch ARE NOT SEPARATE THINGS
+  it "aliases #revision as #branch" do
+    @resource.branch "stable"
+    @resource.revision.should eql("stable")
+  end
+
+  it "takes the SCM resource to use as a constant, and defaults to git" do
+    @resource.scm_provider.should eql(Chef::Provider::Git)
+    @resource.scm_provider Chef::Provider::Subversion
+    @resource.scm_provider.should eql(Chef::Provider::Subversion)
+  end
+
+  it "allows scm providers to be set via symbol" do
+    @resource.scm_provider.should == Chef::Provider::Git
+    @resource.scm_provider :subversion
+    @resource.scm_provider.should == Chef::Provider::Subversion
+  end
+
+  it "allows scm providers to be set via string" do
+    @resource.scm_provider.should == Chef::Provider::Git
+    @resource.scm_provider "subversion"
+    @resource.scm_provider.should == Chef::Provider::Subversion
+  end
+
+  it "has a boolean attribute for svn_force_export defaulting to false" do
+    @resource.svn_force_export.should be_false
+    @resource.svn_force_export true
+    @resource.svn_force_export.should be_true
+    lambda {@resource.svn_force_export(10053)}.should raise_error(ArgumentError)
+  end
+
+  it "takes arbitrary environment variables in a hash" do
+    @resource.environment "RAILS_ENV" => "production"
+    @resource.environment.should == {"RAILS_ENV" => "production"}
+  end
+
+  it "takes string arguments to environment for backwards compat, setting RAILS_ENV, RACK_ENV, and MERB_ENV" do
+    @resource.environment "production"
+    @resource.environment.should == {"RAILS_ENV"=>"production", "RACK_ENV"=>"production","MERB_ENV"=>"production"}
+  end
+
+  it "sets destination to $deploy_to/shared/$repository_cache" do
+    @resource.destination.should eql("/my/deploy/dir/shared/cached-copy")
+  end
+
+  it "sets shared_path to $deploy_to/shared" do
+    @resource.shared_path.should eql("/my/deploy/dir/shared")
+  end
+
+  it "sets current_path to $deploy_to/current" do
+    @resource.current_path.should eql("/my/deploy/dir/current")
+  end
+
+  it "gets the current_path correct even if the shared_path is set (regression test)" do
+    @resource.shared_path
+    @resource.current_path.should eql("/my/deploy/dir/current")
+  end
+
+  it "gives #depth as 5 if shallow clone is true, nil otherwise" do
+    @resource.depth.should be_nil
+    @resource.shallow_clone true
+    @resource.depth.should eql("5")
+  end
+
+  it "aliases repo as repository" do
+    @resource.repository "git at github.com/opcode/cookbooks.git"
+    @resource.repo.should eql("git at github.com/opcode/cookbooks.git")
+  end
+
+  it "aliases git_ssh_wrapper as ssh_wrapper" do
+    @resource.ssh_wrapper "git_my_repo.sh"
+    @resource.git_ssh_wrapper.should eql("git_my_repo.sh")
+  end
+
+  it "has an Array attribute purge_before_symlink, default: log, tmp/pids, public/system" do
+    @resource.purge_before_symlink.should == %w{ log tmp/pids public/system }
+    @resource.purge_before_symlink %w{foo bar baz}
+    @resource.purge_before_symlink.should == %w{foo bar baz}
+  end
+
+  it "has an Array attribute create_dirs_before_symlink, default: tmp, public, config" do
+    @resource.create_dirs_before_symlink.should == %w{tmp public config}
+    @resource.create_dirs_before_symlink %w{foo bar baz}
+    @resource.create_dirs_before_symlink.should == %w{foo bar baz}
+  end
+
+  it 'has a Hash attribute symlinks, default: {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}' do
+    default = { "system" => "public/system", "pids" => "tmp/pids", "log" => "log"}
+    @resource.symlinks.should == default
+    @resource.symlinks "foo" => "bar/baz"
+    @resource.symlinks.should == {"foo" => "bar/baz"}
+  end
+
+  it 'has a Hash attribute symlink_before_migrate, default "config/database.yml" => "config/database.yml"' do
+    @resource.symlink_before_migrate.should == {"config/database.yml" => "config/database.yml"}
+    @resource.symlink_before_migrate "wtf?" => "wtf is going on"
+    @resource.symlink_before_migrate.should == {"wtf?" => "wtf is going on"}
+  end
+
+  resource_has_a_callback_attribute :before_migrate
+  resource_has_a_callback_attribute :before_symlink
+  resource_has_a_callback_attribute :before_restart
+  resource_has_a_callback_attribute :after_restart
+
+  it "aliases restart_command as restart" do
+    @resource.restart "foobaz"
+    @resource.restart_command.should == "foobaz"
+  end
+
+  it "takes a block for the restart parameter" do
+    restart_like_this = lambda {p :noop}
+    @resource.restart(&restart_like_this)
+    @resource.restart.should == restart_like_this
+  end
+
+  it "defaults to using the Deploy::Timestamped provider" do
+    @resource.provider.should == Chef::Provider::Deploy::Timestamped
+  end
+
+  it "allows providers to be set with a full class name" do
+    @resource.provider Chef::Provider::Deploy::Timestamped
+    @resource.provider.should == Chef::Provider::Deploy::Timestamped
+  end
+
+  it "allows deploy providers to be set via symbol" do
+    @resource.provider :revision
+    @resource.provider.should == Chef::Provider::Deploy::Revision
+  end
+
+  it "allows deploy providers to be set via string" do
+    @resource.provider "revision"
+    @resource.provider.should == Chef::Provider::Deploy::Revision
+  end
+
+  it "defaults keep_releases to 5" do
+    @resource.keep_releases.should == 5
+  end
+
+  it "allows keep_releases to be set via integer" do
+    @resource.keep_releases 10
+    @resource.keep_releases.should == 10
+  end
+
+  it "enforces a minimum keep_releases of 1" do
+    @resource.keep_releases 0
+    @resource.keep_releases.should == 1
+  end
+
+  describe "when it has a timeout attribute" do
+    let(:ten_seconds) { 10 }
+    before { @resource.timeout(ten_seconds) }
+    it "stores this timeout" do
+      @resource.timeout.should == ten_seconds
+    end
+  end
+
+  describe "when it has no timeout attribute" do
+    it "should have no default timeout" do
+      @resource.timeout.should be_nil
+    end
+  end
+
+  describe "when it has meta application root, revision, user, group,
+            scm provider, repository cache, environment, simlinks and migrate" do
+    before do
+      @resource.repository("http://uri.org")
+      @resource.deploy_to("/")
+      @resource.revision("1.2.3")
+      @resource.user("root")
+      @resource.group("pokemon")
+      @resource.scm_provider(Chef::Provider::Git)
+      @resource.repository_cache("cached-copy")
+      @resource.environment({"SUDO" => "TRUE"})
+      @resource.symlinks({"system" => "public/system"})
+      @resource.migrate(false)
+
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:deploy_to].should == "/"
+      state[:revision].should == "1.2.3"
+    end
+
+    it "returns the repository URI as its identity" do
+      @resource.identity.should == "http://uri.org"
+    end
+  end
+
+end
diff --git a/spec/unit/resource/directory_spec.rb b/spec/unit/resource/directory_spec.rb
new file mode 100644
index 0000000..dc042c5
--- /dev/null
+++ b/spec/unit/resource/directory_spec.rb
@@ -0,0 +1,82 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Directory do
+
+  before(:each) do
+    @resource = Chef::Resource::Directory.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Directory" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Directory)
+  end
+
+  it "should have a name" do
+    @resource.name.should eql("fakey_fakerton")
+  end
+
+  it "should have a default action of 'create'" do
+    @resource.action.should eql(:create)
+  end
+
+  it "should accept create or delete for action" do
+    lambda { @resource.action :create }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :delete }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :blues }.should raise_error(ArgumentError)
+  end
+
+  it "should use the object name as the path by default" do
+    @resource.path.should eql("fakey_fakerton")
+  end
+
+  it "should accept a string as the path" do
+    lambda { @resource.path "/tmp" }.should_not raise_error(ArgumentError)
+    @resource.path.should eql("/tmp")
+    lambda { @resource.path Hash.new }.should raise_error(ArgumentError)
+  end
+
+  it "should allow you to have specify whether the action is recursive with true/false" do
+    lambda { @resource.recursive true }.should_not raise_error(ArgumentError)
+    lambda { @resource.recursive false }.should_not raise_error(ArgumentError)
+    lambda { @resource.recursive "monkey" }.should raise_error(ArgumentError)
+  end
+
+  describe "when it has group, mode, and owner" do
+    before do
+      @resource.path("/tmp/foo/bar/")
+      @resource.group("wheel")
+      @resource.mode("0664")
+      @resource.owner("root")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:group].should == "wheel"
+      state[:mode].should == "0664"
+      state[:owner].should == "root"
+    end
+
+    it "returns the directory path as its identity" do
+      @resource.identity.should == "/tmp/foo/bar/"
+    end
+  end
+end
diff --git a/spec/unit/resource/dpkg_package_spec.rb b/spec/unit/resource/dpkg_package_spec.rb
new file mode 100644
index 0000000..9ef498a
--- /dev/null
+++ b/spec/unit/resource/dpkg_package_spec.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::DpkgPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::DpkgPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::DpkgPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::DpkgPackage)
+  end
+
+  it "should set the resource_name to :dpkg_package" do
+    @resource.resource_name.should eql(:dpkg_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Dpkg" do
+    @resource.provider.should eql(Chef::Provider::Package::Dpkg)
+  end
+end
diff --git a/spec/unit/resource/easy_install_package_spec.rb b/spec/unit/resource/easy_install_package_spec.rb
new file mode 100644
index 0000000..9682c81
--- /dev/null
+++ b/spec/unit/resource/easy_install_package_spec.rb
@@ -0,0 +1,48 @@
+#
+# 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");
+# 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::Resource::EasyInstallPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::EasyInstallPackage.new("foo")
+  end
+
+  it "should create a new Chef::Resource::EasyInstallPackage" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::EasyInstallPackage)
+  end
+
+  it "should return a Chef::Resource::EasyInstallPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::EasyInstallPackage)
+  end
+
+  it "should set the resource_name to :easy_install_package" do
+    @resource.resource_name.should eql(:easy_install_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::EasyInstall" do
+    @resource.provider.should eql(Chef::Provider::Package::EasyInstall)
+  end
+
+  it "should allow you to set the easy_install_binary attribute" do
+    @resource.easy_install_binary "/opt/local/bin/easy_install"
+    @resource.easy_install_binary.should eql("/opt/local/bin/easy_install")
+  end
+end
diff --git a/spec/unit/resource/env_spec.rb b/spec/unit/resource/env_spec.rb
new file mode 100644
index 0000000..b53cbe4
--- /dev/null
+++ b/spec/unit/resource/env_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Doug MacEachern (<dougm at vmware.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::Env do
+
+  before(:each) do
+    @resource = Chef::Resource::Env.new("FOO")
+  end
+
+  it "should create a new Chef::Resource::Env" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Env)
+  end
+
+  it "should have a name" do
+    @resource.name.should eql("FOO")
+  end
+
+  it "should have a default action of 'create'" do
+    @resource.action.should eql(:create)
+  end
+
+  { :create => false, :delete => false, :modify => false, :flibber => true }.each do |action,bad_value|
+    it "should #{bad_value ? 'not' : ''} accept #{action.to_s}" do
+      if bad_value
+        lambda { @resource.action action }.should raise_error(ArgumentError)
+      else
+        lambda { @resource.action action }.should_not raise_error(ArgumentError)
+      end
+    end
+  end
+
+  it "should use the object name as the key_name by default" do
+    @resource.key_name.should eql("FOO")
+  end
+
+  it "should accept a string as the env value via 'value'" do
+    lambda { @resource.value "bar" }.should_not raise_error(ArgumentError)
+  end
+
+  it "should not accept a Hash for the env value via 'to'" do
+    lambda { @resource.value Hash.new }.should raise_error(ArgumentError)
+  end
+
+  it "should allow you to set an env value via 'to'" do
+    @resource.value "bar"
+    @resource.value.should eql("bar")
+  end
+
+  describe "when it has key name and value" do
+    before do
+      @resource.key_name("charmander")
+      @resource.value("level7")
+      @resource.delim("hi")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:value].should == "level7"
+    end
+
+    it "returns the key name as its identity" do
+      @resource.identity.should == "charmander"
+    end
+  end
+
+end
diff --git a/spec/unit/resource/erl_call_spec.rb b/spec/unit/resource/erl_call_spec.rb
new file mode 100644
index 0000000..49df963
--- /dev/null
+++ b/spec/unit/resource/erl_call_spec.rb
@@ -0,0 +1,81 @@
+#
+# Author:: Joe Williams (<joe at joetify.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# 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::Resource::ErlCall do
+
+  before(:each) do
+    @resource = Chef::Resource::ErlCall.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::ErlCall" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::ErlCall)
+  end
+
+  it "should have a resource name of :erl_call" do
+    @resource.resource_name.should eql(:erl_call)
+  end
+
+  it "should have a default action of run" do
+    @resource.action.should eql("run")
+  end
+
+  it "should accept run as an action" do
+    lambda { @resource.action :run }.should_not raise_error(ArgumentError)
+  end
+
+  it "should allow you to set the code attribute" do
+    @resource.code "q()."
+    @resource.code.should eql("q().")
+  end
+
+  it "should allow you to set the cookie attribute" do
+    @resource.cookie "nomnomnom"
+    @resource.cookie.should eql("nomnomnom")
+  end
+
+  it "should allow you to set the distributed attribute" do
+    @resource.distributed true
+    @resource.distributed.should eql(true)
+  end
+
+  it "should allow you to set the name_type attribute" do
+    @resource.name_type "sname"
+    @resource.name_type.should eql("sname")
+  end
+
+  it "should allow you to set the node_name attribute" do
+    @resource.node_name "chef at erlang"
+    @resource.node_name.should eql("chef at erlang")
+  end
+
+  describe "when it has cookie and node_name" do
+    before do
+      @resource.code("erl-call:function()")
+      @resource.cookie("cookie")
+      @resource.node_name("raster")
+    end
+
+    it "returns the code as its identity" do
+      @resource.identity.should == "erl-call:function()"
+    end
+  end
+end
diff --git a/spec/unit/resource/execute_spec.rb b/spec/unit/resource/execute_spec.rb
new file mode 100644
index 0000000..8c8dcfb
--- /dev/null
+++ b/spec/unit/resource/execute_spec.rb
@@ -0,0 +1,26 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Execute do
+  let(:resource_instance_name) { "some command" }
+  let(:execute_resource) { Chef::Resource::Execute.new(resource_instance_name) }
+  it_behaves_like "an execute resource"
+end
diff --git a/spec/unit/resource/file_spec.rb b/spec/unit/resource/file_spec.rb
new file mode 100644
index 0000000..f937d00
--- /dev/null
+++ b/spec/unit/resource/file_spec.rb
@@ -0,0 +1,116 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::File do
+
+  before(:each) do
+    @resource = Chef::Resource::File.new("fakey_fakerton")
+  end
+
+  it "should have a name" do
+    @resource.name.should eql("fakey_fakerton")
+  end
+
+  it "should have a default action of 'create'" do
+    @resource.action.should eql("create")
+  end
+
+  it "should have a default content of nil" do
+    @resource.content.should be_nil
+  end
+
+  it "should be set to back up 5 files by default" do
+    @resource.backup.should eql(5)
+  end
+
+  it "should only accept strings for content" do
+    lambda { @resource.content 5 }.should raise_error(ArgumentError)
+    lambda { @resource.content :foo }.should raise_error(ArgumentError)
+    lambda { @resource.content "hello" => "there" }.should raise_error(ArgumentError)
+    lambda { @resource.content "hi" }.should_not raise_error(ArgumentError)
+  end
+
+  it "should only accept false or a number for backup" do
+    lambda { @resource.backup true }.should raise_error(ArgumentError)
+    lambda { @resource.backup false }.should_not raise_error(ArgumentError)
+    lambda { @resource.backup 10 }.should_not raise_error(ArgumentError)
+    lambda { @resource.backup "blues" }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a sha256 for checksum" do
+    lambda { @resource.checksum "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa" }.should_not raise_error(ArgumentError)
+    lambda { @resource.checksum "monkey!" }.should raise_error(ArgumentError)
+  end
+
+  it "should accept create, delete or touch for action" do
+    lambda { @resource.action :create }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :delete }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :touch }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :blues }.should raise_error(ArgumentError)
+  end
+
+  it "should use the object name as the path by default" do
+    @resource.path.should eql("fakey_fakerton")
+  end
+
+  it "should accept a string as the path" do
+    lambda { @resource.path "/tmp" }.should_not raise_error(ArgumentError)
+    @resource.path.should eql("/tmp")
+    lambda { @resource.path Hash.new }.should raise_error(ArgumentError)
+  end
+
+  describe "when it has a path, owner, group, mode, and checksum" do
+    before do
+      @resource.path("/tmp/foo.txt")
+      @resource.owner("root")
+      @resource.group("wheel")
+      @resource.mode("0644")
+      @resource.checksum("1" * 64)
+    end
+
+    context "on unix", :unix_only do
+      it "describes its state" do
+        state = @resource.state
+        state[:owner].should == "root"
+        state[:group].should == "wheel"
+        state[:mode].should == "0644"
+        state[:checksum].should == "1" * 64
+      end
+    end
+
+    it "returns the file path as its identity" do
+      @resource.identity.should == "/tmp/foo.txt"
+    end
+
+  end
+
+  describe "when access controls are set on windows", :windows_only => true do
+    before do
+      @resource.rights :read, "Everyone"
+      @resource.rights :full_control, "DOMAIN\User"
+    end
+    it "describes its state including windows ACL attributes" do
+      state = @resource.state
+      state[:rights].should == [ {:permissions => :read, :principals => "Everyone"},
+                               {:permissions => :full_control, :principals => "DOMAIN\User"} ]
+    end
+  end
+
+end
diff --git a/spec/unit/resource/freebsd_package_spec.rb b/spec/unit/resource/freebsd_package_spec.rb
new file mode 100644
index 0000000..b80a94f
--- /dev/null
+++ b/spec/unit/resource/freebsd_package_spec.rb
@@ -0,0 +1,39 @@
+#
+# Author:: AJ Christensen (<aj at opscode.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'
+
+describe Chef::Resource::FreebsdPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::FreebsdPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::FreebsdPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::FreebsdPackage)
+  end
+
+  it "should set the resource_name to :freebsd_package" do
+    @resource.resource_name.should eql(:freebsd_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::freebsd" do
+    @resource.provider.should eql(Chef::Provider::Package::Freebsd)
+  end
+end
+
diff --git a/spec/unit/resource/gem_package_spec.rb b/spec/unit/resource/gem_package_spec.rb
new file mode 100644
index 0000000..9870345
--- /dev/null
+++ b/spec/unit/resource/gem_package_spec.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::GemPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::GemPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::GemPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::GemPackage)
+  end
+
+  it "should set the resource_name to :gem_package" do
+    @resource.resource_name.should eql(:gem_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Rubygems" do
+    @resource.provider.should eql(Chef::Provider::Package::Rubygems)
+  end
+end
+
+describe Chef::Resource::GemPackage, "gem_binary" do
+  before(:each) do
+    @resource = Chef::Resource::GemPackage.new("foo")
+  end
+
+  it "should set the gem_binary variable to whatever is passed in" do
+    @resource.gem_binary("/opt/local/bin/gem")
+    @resource.gem_binary.should eql("/opt/local/bin/gem")
+  end
+end
diff --git a/spec/unit/resource/git_spec.rb b/spec/unit/resource/git_spec.rb
new file mode 100644
index 0000000..95a30c2
--- /dev/null
+++ b/spec/unit/resource/git_spec.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Resource::Git do
+
+  before(:each) do
+    @git = Chef::Resource::Git.new("my awesome webapp")
+  end
+
+  it "is a kind of Scm Resource" do
+    @git.should be_a_kind_of(Chef::Resource::Scm)
+    @git.should be_an_instance_of(Chef::Resource::Git)
+  end
+
+  it "uses the git provider" do
+    @git.provider.should eql(Chef::Provider::Git)
+  end
+
+  it "uses aliases revision as branch" do
+    @git.branch "HEAD"
+    @git.revision.should eql("HEAD")
+  end
+
+  it "aliases revision as reference" do
+    @git.reference "v1.0 tag"
+    @git.revision.should eql("v1.0 tag")
+  end
+
+end
diff --git a/spec/unit/resource/group_spec.rb b/spec/unit/resource/group_spec.rb
new file mode 100644
index 0000000..df68c56
--- /dev/null
+++ b/spec/unit/resource/group_spec.rb
@@ -0,0 +1,157 @@
+#
+# Author:: AJ Christensen (<aj at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Group, "initialize" do
+  before(:each) do
+    @resource = Chef::Resource::Group.new("admin")
+  end
+
+  it "should create a new Chef::Resource::Group" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Group)
+  end
+
+  it "should set the resource_name to :group" do
+    @resource.resource_name.should eql(:group)
+  end
+
+  it "should set the group_name equal to the argument to initialize" do
+    @resource.group_name.should eql("admin")
+  end
+
+  it "should default gid to nil" do
+    @resource.gid.should eql(nil)
+  end
+
+  it "should default members to an empty array" do
+    @resource.members.should eql([])
+  end
+
+  it "should alias users to members, also an empty array" do
+    @resource.users.should eql([])
+  end
+
+  it "should set action to :create" do
+    @resource.action.should eql(:create)
+  end
+
+  %w{create remove modify manage}.each do |action|
+    it "should allow action #{action}" do
+      @resource.allowed_actions.detect { |a| a == action.to_sym }.should eql(action.to_sym)
+    end
+  end
+
+  it "should accept domain groups (@ or \ separator) on non-windows" do
+    lambda { @resource.group_name "domain\@group" }.should_not raise_error(ArgumentError)
+    @resource.group_name.should == "domain\@group"
+    lambda { @resource.group_name "domain\\group" }.should_not raise_error(ArgumentError)
+    @resource.group_name.should == "domain\\group"
+    lambda { @resource.group_name "domain\\group^name" }.should_not raise_error(ArgumentError)
+    @resource.group_name.should == "domain\\group^name"
+  end
+end
+
+describe Chef::Resource::Group, "group_name" do
+  before(:each) do
+    @resource = Chef::Resource::Group.new("admin")
+  end
+
+  it "should allow a string" do
+    @resource.group_name "pirates"
+    @resource.group_name.should eql("pirates")
+  end
+
+  it "should not allow a hash" do
+    lambda { @resource.send(:group_name, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError)
+  end
+end
+
+describe Chef::Resource::Group, "gid" do
+  before(:each) do
+    @resource = Chef::Resource::Group.new("admin")
+  end
+
+  it "should allow an integer" do
+    @resource.gid 100
+    @resource.gid.should eql(100)
+  end
+
+  it "should not allow a hash" do
+    lambda { @resource.send(:gid, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError)
+  end
+end
+
+describe Chef::Resource::Group, "members" do
+  before(:each) do
+    @resource = Chef::Resource::Group.new("admin")
+  end
+
+  [ :users, :members].each do |method|
+    it "(#{method}) should allow and convert a string" do
+      @resource.send(method, "aj")
+      @resource.send(method).should eql(["aj"])
+    end
+
+    it "(#{method}) should allow an array" do
+      @resource.send(method, [ "aj", "adam" ])
+      @resource.send(method).should eql( ["aj", "adam"] )
+    end
+
+    it "(#{method}) should not allow a hash" do
+      lambda { @resource.send(method, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError)
+    end
+  end
+end
+
+describe Chef::Resource::Group, "append" do
+  before(:each) do
+    @resource = Chef::Resource::Group.new("admin")
+  end
+
+  it "should default to false" do
+    @resource.append.should eql(false)
+  end
+
+  it "should allow a boolean" do
+    @resource.append true
+    @resource.append.should eql(true)
+  end
+
+  it "should not allow a hash" do
+    lambda { @resource.send(:gid, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError)
+  end
+
+  describe "when it has members" do
+    before do
+      @resource.group_name("pokemon")
+      @resource.members(["blastoise", "pikachu"])
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:members].should eql(["blastoise", "pikachu"])
+    end
+
+    it "returns the group name as its identity" do
+      @resource.identity.should == "pokemon"
+    end
+  end
+end
diff --git a/spec/unit/resource/http_request_spec.rb b/spec/unit/resource/http_request_spec.rb
new file mode 100644
index 0000000..b636ca9
--- /dev/null
+++ b/spec/unit/resource/http_request_spec.rb
@@ -0,0 +1,59 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::HttpRequest do
+
+  before(:each) do
+    @resource = Chef::Resource::HttpRequest.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::HttpRequest" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::HttpRequest)
+  end
+
+  it "should set url to a string" do
+    @resource.url "http://slashdot.org"
+    @resource.url.should eql("http://slashdot.org")
+  end
+
+  it "should set the message to the name by default" do
+    @resource.message.should eql("fakey_fakerton")
+  end
+
+  it "should set message to a string" do
+    @resource.message "monkeybars"
+    @resource.message.should eql("monkeybars")
+  end
+
+  describe "when it has a message and headers" do
+    before do
+      @resource.url("http://www.trololol.net")
+      @resource.message("Get sum post brah.")
+      @resource.headers({"head" => "tail"})
+    end
+
+    it "returns the url as its identity" do
+      @resource.identity.should == "http://www.trololol.net"
+    end
+  end
+
+end
diff --git a/spec/unit/resource/ifconfig_spec.rb b/spec/unit/resource/ifconfig_spec.rb
new file mode 100644
index 0000000..10a4d09
--- /dev/null
+++ b/spec/unit/resource/ifconfig_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# 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::Resource::Ifconfig do
+
+  before(:each) do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @resource = Chef::Resource::Ifconfig.new("fakey_fakerton", @run_context)
+  end
+
+  describe "when it has target, hardware address, inet address, and a mask" do
+    before do
+      @resource.device("charmander")
+      @resource.target("team_rocket")
+      @resource.hwaddr("11.2223.223")
+      @resource.inet_addr("434.2343.23")
+      @resource.mask("255.255.545")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:inet_addr].should == "434.2343.23"
+      state[:mask].should == "255.255.545"
+    end
+
+    it "returns the device as its identity" do
+      @resource.identity.should == "charmander"
+    end
+  end
+
+  shared_examples "being a platform using the default ifconfig provider" do |platform, version|
+    before do
+      @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
+      @resource.provider_for_action(:add).should be_a_kind_of(Chef::Provider::Ifconfig)
+      @resource.provider_for_action(:add).should_not be_a_kind_of(Chef::Provider::Ifconfig::Debian)
+      @resource.provider_for_action(:add).should_not be_a_kind_of(Chef::Provider::Ifconfig::Redhat)
+    end
+  end
+
+  shared_examples "being a platform based on RedHat" do |platform, version|
+    before do
+      @node.automatic_attrs[:platform] = platform
+      @node.automatic_attrs[:platform_version] = version
+    end
+
+    it "should use an Provider::Ifconfig::Redhat as a provider for #{platform} #{version}" do
+      @resource.provider_for_action(:add).should be_a_kind_of(Chef::Provider::Ifconfig::Redhat)
+    end
+  end
+
+  shared_examples "being a platform based on a recent Debian" do |platform, version|
+    before do
+      @node.automatic_attrs[:platform] = platform
+      @node.automatic_attrs[:platform_version] = version
+    end
+
+    it "should use an Ifconfig::Debian as a provider for #{platform} #{version}" do
+      @resource.provider_for_action(:add).should be_a_kind_of(Chef::Provider::Ifconfig::Debian)
+    end
+  end
+
+  describe "when it is a RedHat platform" do
+    it_should_behave_like "being a platform based on RedHat", "redhat", "4.0"
+  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"
+  end
+
+  describe "when it is a new Debian platform" do
+    it_should_behave_like "being a platform based on a recent Debian", "debian", "7.0"
+  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"
+  end
+
+  describe "when it is a new Ubuntu platform" do
+    it_should_behave_like "being a platform based on a recent Debian", "ubuntu", "11.10"
+  end
+
+end
diff --git a/spec/unit/resource/ips_package_spec.rb b/spec/unit/resource/ips_package_spec.rb
new file mode 100644
index 0000000..6166192
--- /dev/null
+++ b/spec/unit/resource/ips_package_spec.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Bryan McLellan <btm at opscode.com>
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::IpsPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::IpsPackage.new("crypto/gnupg")
+  end
+
+  it "should return a Chef::Resource::IpsPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::IpsPackage)
+  end
+
+  it "should set the resource_name to :ips_package" do
+    @resource.resource_name.should eql(:ips_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Ips" do
+    @resource.provider.should eql(Chef::Provider::Package::Ips)
+  end
+
+  it "should support accept_license" do
+    @resource.accept_license(true)
+    @resource.accept_license.should eql(true)
+  end
+end
diff --git a/spec/unit/resource/link_spec.rb b/spec/unit/resource/link_spec.rb
new file mode 100644
index 0000000..fc3f7ff
--- /dev/null
+++ b/spec/unit/resource/link_spec.rb
@@ -0,0 +1,119 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Link do
+
+  before(:each) do
+    Chef::Resource::Link.any_instance.should_receive(:verify_links_supported!).and_return(true)
+    @resource = Chef::Resource::Link.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Link" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Link)
+  end
+
+  it "should have a name" do
+    @resource.name.should eql("fakey_fakerton")
+  end
+
+  it "should have a default action of 'create'" do
+    @resource.action.should eql(:create)
+  end
+
+  { :create => false, :delete => false, :blues => true }.each do |action,bad_value|
+    it "should #{bad_value ? 'not' : ''} accept #{action.to_s}" do
+      if bad_value
+        lambda { @resource.action action }.should raise_error(ArgumentError)
+      else
+        lambda { @resource.action action }.should_not raise_error(ArgumentError)
+      end
+    end
+  end
+
+  it "should use the object name as the target_file by default" do
+    @resource.target_file.should eql("fakey_fakerton")
+  end
+
+  it "should accept a string as the link source via 'to'" do
+    lambda { @resource.to "/tmp" }.should_not raise_error(ArgumentError)
+  end
+
+  it "should not accept a Hash for the link source via 'to'" do
+    lambda { @resource.to Hash.new }.should raise_error(ArgumentError)
+  end
+
+  it "should allow you to set a link source via 'to'" do
+    @resource.to "/tmp/foo"
+    @resource.to.should eql("/tmp/foo")
+  end
+
+  it "should allow you to specify the link type" do
+    @resource.link_type "symbolic"
+    @resource.link_type.should eql(:symbolic)
+  end
+
+  it "should default to a symbolic link" do
+    @resource.link_type.should eql(:symbolic)
+  end
+
+  it "should accept a hard link_type" do
+    @resource.link_type :hard
+    @resource.link_type.should eql(:hard)
+  end
+
+  it "should reject any other link_type but :hard and :symbolic" do
+    lambda { @resource.link_type "x-men" }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a group name or id for group" do
+    lambda { @resource.group "root" }.should_not raise_error(ArgumentError)
+    lambda { @resource.group 123 }.should_not raise_error(ArgumentError)
+    lambda { @resource.group "root*goo" }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a user name or id for owner" do
+    lambda { @resource.owner "root" }.should_not raise_error(ArgumentError)
+    lambda { @resource.owner 123 }.should_not raise_error(ArgumentError)
+    lambda { @resource.owner "root*goo" }.should raise_error(ArgumentError)
+  end
+
+  describe "when it has to, link_type, owner, and group" do
+    before do
+      @resource.target_file("/var/target.tar")
+      @resource.to("/to/dir/file.tar")
+      @resource.link_type(:symbolic)
+      @resource.owner("root")
+      @resource.group("0664")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:to].should == "/to/dir/file.tar"
+      state[:owner].should == "root"
+      state[:group].should == "0664"
+    end
+
+    it "returns the target file as its identity" do
+      @resource.identity.should == "/var/target.tar"
+    end
+  end
+end
diff --git a/spec/unit/resource/log_spec.rb b/spec/unit/resource/log_spec.rb
new file mode 100644
index 0000000..c0201e5
--- /dev/null
+++ b/spec/unit/resource/log_spec.rb
@@ -0,0 +1,73 @@
+#
+# Author:: Cary Penniman (<cary at rightscale.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Log do
+
+  before(:each) do
+    @log_str = "this is my string to log"
+    @resource = Chef::Resource::Log.new(@log_str)
+  end
+
+  it "should create a new Chef::Resource::Log" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Log)
+  end
+
+  it "supports the :write actions" do
+    @resource.allowed_actions.should include(:write)
+  end
+
+  it "should have a name of log" do
+    @resource.resource_name.should == :log
+  end
+
+  it "should allow you to set a log string" do
+    @resource.name.should == @log_str
+  end
+
+  it "should set the message to the first argument to new" do
+    @resource.message.should == @log_str
+  end
+
+  it "should accept a string for the log message" do
+    @resource.message "this is different"
+    @resource.message.should == "this is different"
+  end
+
+  it "should accept a vaild level option" do
+    @resource.level :debug
+    @resource.level :info
+    @resource.level :warn
+    @resource.level :error
+    @resource.level :fatal
+    lambda { @resource.level :unsupported }.should raise_error(ArgumentError)
+  end
+
+  describe "when the identity is defined" do
+    before do
+      @resource = Chef::Resource::Log.new("ery day I'm loggin-in")
+    end
+
+    it "returns the log string as its identity" do
+      @resource.identity.should == "ery day I'm loggin-in"
+    end
+  end
+end
diff --git a/spec/unit/resource/macports_package_spec.rb b/spec/unit/resource/macports_package_spec.rb
new file mode 100644
index 0000000..7e2e200
--- /dev/null
+++ b/spec/unit/resource/macports_package_spec.rb
@@ -0,0 +1,37 @@
+#
+# Author:: David Balatero (<dbalatero at gmail.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::MacportsPackage, "initialize" do
+  before(:each) do
+    @resource = Chef::Resource::MacportsPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::MacportsPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::MacportsPackage)
+  end
+
+  it "should set the resource_name to :macports_package" do
+    @resource.resource_name.should eql(:macports_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Macports" do
+    @resource.provider.should eql(Chef::Provider::Package::Macports)
+  end
+end
diff --git a/spec/unit/resource/mdadm_spec.rb b/spec/unit/resource/mdadm_spec.rb
new file mode 100644
index 0000000..310f420
--- /dev/null
+++ b/spec/unit/resource/mdadm_spec.rb
@@ -0,0 +1,102 @@
+#
+# Author:: Joe Williams (<joe at joetify.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# 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::Resource::Mdadm do
+
+  before(:each) do
+    @resource = Chef::Resource::Mdadm.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Mdadm" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Mdadm)
+  end
+
+  it "should have a resource name of :mdadm" do
+    @resource.resource_name.should eql(:mdadm)
+  end
+
+  it "should have a default action of create" do
+    @resource.action.should eql(:create)
+  end
+
+  it "should accept create, assemble, stop as actions" do
+    lambda { @resource.action :create }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :assemble }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :stop }.should_not raise_error(ArgumentError)
+  end
+
+  it "should allow you to set the raid_device attribute" do
+    @resource.raid_device "/dev/md3"
+    @resource.raid_device.should eql("/dev/md3")
+  end
+
+  it "should allow you to set the chunk attribute" do
+    @resource.chunk 256
+    @resource.chunk.should eql(256)
+  end
+
+  it "should allow you to set the level attribute" do
+    @resource.level 1
+    @resource.level.should eql(1)
+  end
+
+  it "should allow you to set the metadata attribute" do
+    @resource.metadata "1.2"
+    @resource.metadata.should eql("1.2")
+  end
+
+  it "should allow you to set the bitmap attribute" do
+    @resource.metadata "internal"
+    @resource.metadata.should eql("internal")
+  end
+
+  it "should allow you to set the devices attribute" do
+    @resource.devices ["/dev/sda", "/dev/sdb"]
+    @resource.devices.should eql(["/dev/sda", "/dev/sdb"])
+  end
+
+  it "should allow you to set the exists attribute" do
+    @resource.exists true
+    @resource.exists.should eql(true)
+  end
+
+  describe "when it has devices, level, and chunk" do
+    before do
+      @resource.raid_device("raider")
+      @resource.devices(["device1", "device2"])
+      @resource.level(1)
+      @resource.chunk(42)
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:devices].should eql(["device1", "device2"])
+      state[:level].should == 1
+      state[:chunk].should == 42
+    end
+
+    it "returns the raid device as its identity" do
+      @resource.identity.should == "raider"
+    end
+  end
+
+end
diff --git a/spec/unit/resource/mount_spec.rb b/spec/unit/resource/mount_spec.rb
new file mode 100644
index 0000000..fb41401
--- /dev/null
+++ b/spec/unit/resource/mount_spec.rb
@@ -0,0 +1,195 @@
+#
+# Author:: Joshua Timberman (<joshua at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::Mount do
+  before(:each) do
+    @resource = Chef::Resource::Mount.new("filesystem")
+  end
+
+  it "should create a new Chef::Resource::Mount" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Mount)
+  end
+
+  it "should have a name" do
+    @resource.name.should eql("filesystem")
+  end
+
+  it "should set mount_point to the name" do
+    @resource.mount_point.should eql("filesystem")
+  end
+
+  it "should have a default action of mount" do
+    @resource.action.should eql(:mount)
+  end
+
+  it "should accept mount, umount and remount as actions" do
+    lambda { @resource.action :mount }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :umount }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :remount }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :brooklyn }.should raise_error(ArgumentError)
+  end
+
+  it "should allow you to set the device attribute" do
+    @resource.device "/dev/sdb3"
+    @resource.device.should eql("/dev/sdb3")
+  end
+
+  it "should allow you to set the fstype attribute" do
+    @resource.fstype "nfs"
+    @resource.fstype.should eql("nfs")
+  end
+
+  it "should allow you to set the dump attribute" do
+    @resource.dump 1
+    @resource.dump.should eql(1)
+  end
+
+  it "should allow you to set the pass attribute" do
+    @resource.pass 1
+    @resource.pass.should eql(1)
+  end
+
+  it "should set the options attribute to defaults" do
+    @resource.options.should eql(["defaults"])
+  end
+
+  it "should allow options to be sent as a string, and convert to array" do
+    @resource.options "rw,noexec"
+    @resource.options.should be_a_kind_of(Array)
+  end
+
+  it "should allow options attribute as an array" do
+    @resource.options ["ro", "nosuid"]
+    @resource.options.should be_a_kind_of(Array)
+  end
+
+  it "should accept true for mounted" do
+    @resource.mounted(true)
+    @resource.mounted.should eql(true)
+  end
+
+  it "should accept false for mounted" do
+    @resource.mounted(false)
+    @resource.mounted.should eql(false)
+  end
+
+  it "should set mounted to false by default" do
+    @resource.mounted.should eql(false)
+  end
+
+  it "should not accept a string for mounted" do
+    lambda { @resource.mounted("poop") }.should raise_error(ArgumentError)
+  end
+
+  it "should accept true for enabled" do
+    @resource.enabled(true)
+    @resource.enabled.should eql(true)
+  end
+
+  it "should accept false for enabled" do
+    @resource.enabled(false)
+    @resource.enabled.should eql(false)
+  end
+
+  it "should set enabled to false by default" do
+    @resource.enabled.should eql(false)
+  end
+
+  it "should not accept a string for enabled" do
+    lambda { @resource.enabled("poop") }.should raise_error(ArgumentError)
+  end
+
+  it "should default all feature support to false" do
+    support_hash = { :remount => false }
+    @resource.supports.should == support_hash
+  end
+
+  it "should allow you to set feature support as an array" do
+    support_array = [ :remount ]
+    support_hash = { :remount => true }
+    @resource.supports(support_array)
+    @resource.supports.should == support_hash
+  end
+
+  it "should allow you to set feature support as a hash" do
+    support_hash = { :remount => true }
+    @resource.supports(support_hash)
+    @resource.supports.should == support_hash
+  end
+
+  it "should allow you to set username" do
+    @resource.username("Administrator")
+    @resource.username.should == "Administrator"
+  end
+
+  it "should allow you to set password" do
+    @resource.password("Jetstream123!")
+    @resource.password.should == "Jetstream123!"
+  end
+
+  it "should allow you to set domain" do
+    @resource.domain("TEST_DOMAIN")
+    @resource.domain.should == "TEST_DOMAIN"
+  end
+
+  describe "when it has mount point, device type, and fstype" do
+    before do
+      @resource.device("charmander")
+      @resource.mount_point("123.456")
+      @resource.device_type(:device)
+      @resource.fstype("ranked")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:mount_point].should == "123.456"
+      state[:device_type].should eql(:device)
+      state[:fstype].should == "ranked"
+    end
+
+    it "returns the device as its identity" do
+      @resource.identity.should == "charmander"
+    end
+  end
+
+  describe "when it has username, password and domain" do
+    before do
+      @resource.mount_point("T:")
+      @resource.device("charmander")
+      @resource.username("Administrator")
+      @resource.password("Jetstream123!")
+      @resource.domain("TEST_DOMAIN")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      puts state
+      state[:mount_point].should == "T:"
+      state[:username].should == "Administrator"
+      state[:password].should == "Jetstream123!"
+      state[:domain].should == "TEST_DOMAIN"
+      state[:device_type].should eql(:device)
+      state[:fstype].should == "auto"
+    end
+
+  end
+end
diff --git a/spec/unit/resource/ohai_spec.rb b/spec/unit/resource/ohai_spec.rb
new file mode 100644
index 0000000..b8d062b
--- /dev/null
+++ b/spec/unit/resource/ohai_spec.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Michael Leinartas (<mleinartas at gmail.com>)
+# Copyright:: Copyright (c) 2010 Michael Leinartas
+# 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::Resource::Ohai do
+
+  before(:each) do
+    @resource = Chef::Resource::Ohai.new("ohai_reload")
+  end
+
+  it "should create a new Chef::Resource::Ohai" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Ohai)
+  end
+
+  it "should have a resource name of :ohai" do
+    @resource.resource_name.should eql(:ohai)
+  end
+
+  it "should have a default action of create" do
+    @resource.action.should eql(:reload)
+  end
+
+  it "should allow you to set the plugin attribute" do
+    @resource.plugin "passwd"
+    @resource.plugin.should eql("passwd")
+  end
+
+  describe "when it has a plugin value" do
+    before do
+      @resource.name("test")
+      @resource.plugin("passwd")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:plugin].should == "passwd"
+    end
+
+    it "returns the name as its identity" do
+      @resource.identity.should == "test"
+    end
+  end
+
+
+end
diff --git a/spec/unit/resource/package_spec.rb b/spec/unit/resource/package_spec.rb
new file mode 100644
index 0000000..0f175dd
--- /dev/null
+++ b/spec/unit/resource/package_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Package do
+
+  before(:each) do
+    @resource = Chef::Resource::Package.new("emacs")
+  end
+
+  it "should create a new Chef::Resource::Package" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Package)
+  end
+
+  it "should set the package_name to the first argument to new" do
+    @resource.package_name.should eql("emacs")
+  end
+
+  it "should accept a string for the package name" do
+    @resource.package_name "something"
+    @resource.package_name.should eql("something")
+  end
+
+  it "should accept a string for the version" do
+    @resource.version "something"
+    @resource.version.should eql("something")
+  end
+
+  it "should accept a string for the response file" do
+    @resource.response_file "something"
+    @resource.response_file.should eql("something")
+  end
+
+  it "should accept a string for the source" do
+    @resource.source "something"
+    @resource.source.should eql("something")
+  end
+
+  it "should accept a string for the options" do
+    @resource.options "something"
+    @resource.options.should eql("something")
+  end
+
+ describe "when it has a package_name and version" do
+   before do
+     @resource.package_name("tomcat")
+     @resource.version("10.9.8")
+     @resource.options("-al")
+   end
+
+   it "describes its state" do
+     state = @resource.state
+     state[:version].should == "10.9.8"
+     state[:options].should == "-al"
+   end
+
+   it "returns the file path as its identity" do
+     @resource.identity.should == "tomcat"
+   end
+
+ end
+end
diff --git a/spec/unit/resource/pacman_package_spec.rb b/spec/unit/resource/pacman_package_spec.rb
new file mode 100644
index 0000000..ec5feeb
--- /dev/null
+++ b/spec/unit/resource/pacman_package_spec.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek at web.de>)
+# Copyright:: Copyright (c) 2010 Jan Zimmek
+# 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::Resource::PacmanPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::PacmanPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::PacmanPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::PacmanPackage)
+  end
+
+  it "should set the resource_name to :pacman_package" do
+    @resource.resource_name.should eql(:pacman_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Pacman" do
+    @resource.provider.should eql(Chef::Provider::Package::Pacman)
+  end
+end
diff --git a/spec/unit/resource/perl_spec.rb b/spec/unit/resource/perl_spec.rb
new file mode 100644
index 0000000..d25dc98
--- /dev/null
+++ b/spec/unit/resource/perl_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::Perl do
+
+  before(:each) do
+    @resource = Chef::Resource::Perl.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Perl" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Perl)
+  end
+
+  it "should have a resource name of :perl" do
+    @resource.resource_name.should eql(:perl)
+  end
+
+  it "should have an interpreter of perl" do
+    @resource.interpreter.should eql("perl")
+  end
+
+end
diff --git a/spec/unit/resource/portage_package_spec.rb b/spec/unit/resource/portage_package_spec.rb
new file mode 100644
index 0000000..510f3b5
--- /dev/null
+++ b/spec/unit/resource/portage_package_spec.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::PortagePackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::PortagePackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::PortagePackage" do
+    @resource.should be_a_kind_of(Chef::Resource::PortagePackage)
+  end
+
+  it "should set the resource_name to :portage_package" do
+    @resource.resource_name.should eql(:portage_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Portage" do
+    @resource.provider.should eql(Chef::Provider::Package::Portage)
+  end
+end
diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_spec.rb
new file mode 100644
index 0000000..a35e37c
--- /dev/null
+++ b/spec/unit/resource/powershell_spec.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Adam Edwards (<adamed at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::PowershellScript do
+
+  before(:each) do
+    node = Chef::Node.new
+
+    node.default["kernel"] = Hash.new
+    node.default["kernel"][:machine] = :x86_64.to_s
+
+    run_context = Chef::RunContext.new(node, nil, nil)
+
+    @resource = Chef::Resource::PowershellScript.new("powershell_unit_test", run_context)
+
+  end
+
+  it "should create a new Chef::Resource::PowershellScript" do
+    @resource.should be_a_kind_of(Chef::Resource::PowershellScript)
+  end
+
+  context "windowsscript" do
+    let(:resource_instance) { @resource }
+    let(:resource_instance_name ) { @resource.command }
+    let(:resource_name) { :powershell_script }
+    let(:interpreter_file_name) { 'powershell.exe' }
+
+    it_should_behave_like "a Windows script resource"
+  end
+
+end
diff --git a/spec/unit/resource/python_spec.rb b/spec/unit/resource/python_spec.rb
new file mode 100644
index 0000000..3362b68
--- /dev/null
+++ b/spec/unit/resource/python_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::Python do
+
+  before(:each) do
+    @resource = Chef::Resource::Python.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Python" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Python)
+  end
+
+  it "should have a resource name of :python" do
+    @resource.resource_name.should eql(:python)
+  end
+
+  it "should have an interpreter of python" do
+    @resource.interpreter.should eql("python")
+  end
+
+end
diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb
new file mode 100644
index 0000000..3f227e2
--- /dev/null
+++ b/spec/unit/resource/registry_key_spec.rb
@@ -0,0 +1,171 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2012 OpsCode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::RegistryKey, "initialize" do
+  before(:each) do
+    @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius')
+  end
+
+  it "should create a new Chef::Resource::RegistryKey" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::RegistryKey)
+  end
+
+  it "should set the resource_name to :registry_key" do
+    @resource.resource_name.should eql(:registry_key)
+  end
+
+  it "should set the key equal to the argument to initialize" do
+    @resource.key.should eql('HKCU\Software\Raxicoricofallapatorius')
+  end
+
+  it "should default recursive to false" do
+    @resource.recursive.should eql(false)
+  end
+
+  it "should default architecture to :machine" do
+    @resource.architecture.should eql(:machine)
+  end
+
+  it "should set action to :create" do
+    @resource.action.should eql(:create)
+  end
+
+  %w{create create_if_missing delete delete_key}.each do |action|
+    it "should allow action #{action}" do
+      @resource.allowed_actions.detect { |a| a == action.to_sym }.should eql(action.to_sym)
+    end
+  end
+end
+
+describe Chef::Resource::RegistryKey, "key" do
+  before(:each) do
+    @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius')
+  end
+
+  it "should allow a string" do
+    @resource.key 'HKCU\Software\Poosh'
+    @resource.key.should eql('HKCU\Software\Poosh')
+  end
+
+  it "should not allow an integer" do
+    lambda { @resource.send(:key, 100) }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow a hash" do
+    lambda { @resource.send(:key, { :sonic => "screwdriver" }) }.should raise_error(ArgumentError)
+  end
+end
+
+describe Chef::Resource::RegistryKey, "values" do
+  before(:each) do
+    @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius')
+  end
+
+  it "should allow a single proper hash of registry values" do
+    @resource.values( { :name => 'poosh', :type => :string, :data => 'carmen' } )
+    @resource.values.should eql([ { :name => 'poosh', :type => :string, :data => 'carmen' } ])
+  end
+
+  it "should allow an array of proper hashes of registry values" do
+    @resource.values [ { :name => 'poosh', :type => :string, :data => 'carmen' } ]
+    @resource.values.should eql([ { :name => 'poosh', :type => :string, :data => 'carmen' } ])
+  end
+
+  it "should throw an exception if the name field is missing" do
+    lambda { @resource.values [ { :type => :string, :data => 'carmen' } ] }.should raise_error(ArgumentError)
+  end
+
+  it "should throw an exception if the type field is missing" do
+    lambda { @resource.values [ { :name => 'poosh', :data => 'carmen' } ] }.should raise_error(ArgumentError)
+  end
+
+  it "should throw an exception if the data field is missing" do
+    lambda { @resource.values [ { :name => 'poosh', :type => :string } ] }.should raise_error(ArgumentError)
+  end
+
+  it "should throw an exception if extra fields are present" do
+    lambda { @resource.values [ { :name => 'poosh', :type => :string, :data => 'carmen', :screwdriver => 'sonic' } ] }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow a string" do
+    lambda { @resource.send(:values, 'souffle') }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow an integer" do
+    lambda { @resource.send(:values, 100) }.should raise_error(ArgumentError)
+  end
+end
+
+describe Chef::Resource::RegistryKey, "recursive" do
+  before(:each) do
+    @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius')
+  end
+
+  it "should allow a boolean" do
+    @resource.recursive(true)
+    @resource.recursive.should eql(true)
+  end
+
+  it "should not allow a hash" do
+    lambda { @resource.recursive({:sonic => :screwdriver}) }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow an array" do
+    lambda { @resource.recursive([:nose, :chin]) }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow a string" do
+    lambda { @resource.recursive('souffle') }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow an integer" do
+    lambda { @resource.recursive(100) }.should raise_error(ArgumentError)
+  end
+end
+
+describe Chef::Resource::RegistryKey, "architecture" do
+  before(:each) do
+    @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius')
+  end
+
+  [ :i386, :x86_64, :machine ].each do |arch|
+    it "should allow #{arch} as a symbol" do
+      @resource.architecture(arch)
+      @resource.architecture.should eql(arch)
+    end
+  end
+
+  it "should not allow a hash" do
+    lambda { @resource.architecture({:sonic => :screwdriver}) }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow an array" do
+    lambda { @resource.architecture([:nose, :chin]) }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow a string" do
+    lambda { @resource.architecture('souffle') }.should raise_error(ArgumentError)
+  end
+
+  it "should not allow an integer" do
+    lambda { @resource.architecture(100) }.should raise_error(ArgumentError)
+  end
+end
diff --git a/spec/unit/resource/remote_directory_spec.rb b/spec/unit/resource/remote_directory_spec.rb
new file mode 100644
index 0000000..d380006
--- /dev/null
+++ b/spec/unit/resource/remote_directory_spec.rb
@@ -0,0 +1,97 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::RemoteDirectory do
+
+  before(:each) do
+    @resource = Chef::Resource::RemoteDirectory.new("/etc/dunk")
+  end
+
+  it "should create a new Chef::Resource::RemoteDirectory" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::RemoteDirectory)
+  end
+
+  it "should set the path to the first argument to new" do
+    @resource.path.should eql("/etc/dunk")
+  end
+
+  it "should accept a string for the remote directory source" do
+    @resource.source "foo"
+    @resource.source.should eql("foo")
+  end
+
+  it "should have the basename of the remote directory resource as the default source" do
+    @resource.source.should eql("dunk")
+  end
+
+  it "should accept a number for the remote files backup" do
+    @resource.files_backup 1
+    @resource.files_backup.should eql(1)
+  end
+
+  it "should accept false for the remote files backup" do
+    @resource.files_backup false
+    @resource.files_backup.should eql(false)
+  end
+
+  it "should accept 3 or 4 digets for the files_mode" do
+    @resource.files_mode 100
+    @resource.files_mode.should eql(100)
+    @resource.files_mode 1000
+    @resource.files_mode.should eql(1000)
+  end
+
+  it "should accept a string or number for the files group" do
+    @resource.files_group "heart"
+    @resource.files_group.should eql("heart")
+    @resource.files_group 1000
+    @resource.files_group.should eql(1000)
+  end
+
+  it "should accept a string or number for the files owner" do
+    @resource.files_owner "heart"
+    @resource.files_owner.should eql("heart")
+    @resource.files_owner 1000
+    @resource.files_owner.should eql(1000)
+  end
+
+  describe "when it has cookbook, files owner, files mode, and source" do
+    before do
+      @resource.path("/var/path/")
+      @resource.cookbook("pokemon.rb")
+      @resource.files_owner("root")
+      @resource.files_group("supergroup")
+      @resource.files_mode("0664")
+      @resource.source("/var/source/")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:files_owner].should == "root"
+      state[:files_group].should == "supergroup"
+      state[:files_mode].should == "0664"
+    end
+
+    it "returns the path  as its identity" do
+      @resource.identity.should == "/var/path/"
+    end
+  end
+end
diff --git a/spec/unit/resource/remote_file_spec.rb b/spec/unit/resource/remote_file_spec.rb
new file mode 100644
index 0000000..643bc8b
--- /dev/null
+++ b/spec/unit/resource/remote_file_spec.rb
@@ -0,0 +1,163 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::RemoteFile do
+
+  before(:each) do
+    @resource = Chef::Resource::RemoteFile.new("fakey_fakerton")
+  end
+
+  describe "initialize" do
+    it "should create a new Chef::Resource::RemoteFile" do
+      @resource.should be_a_kind_of(Chef::Resource)
+      @resource.should be_a_kind_of(Chef::Resource::File)
+      @resource.should be_a_kind_of(Chef::Resource::RemoteFile)
+    end
+  end
+
+  it "says its provider is RemoteFile when the source is an absolute URI" do
+    @resource.source("http://www.google.com/robots.txt")
+    @resource.provider.should == Chef::Provider::RemoteFile
+    Chef::Platform.find_provider(:noplatform, 'noversion', @resource).should == Chef::Provider::RemoteFile
+  end
+
+
+  describe "source" do
+    it "does not have a default value for 'source'" do
+      @resource.source.should eql([])
+    end
+
+    it "should accept a URI for the remote file source" do
+      @resource.source "http://opscode.com/"
+      @resource.source.should eql([ "http://opscode.com/" ])
+    end
+
+    it "should accept an array of URIs for the remote file source" do
+      @resource.source([ "http://opscode.com/", "http://puppetlabs.com/" ])
+      @resource.source.should eql([ "http://opscode.com/", "http://puppetlabs.com/" ])
+    end
+
+    it "should accept an multiple URIs as arguments for the remote file source" do
+      @resource.source("http://opscode.com/", "http://puppetlabs.com/")
+      @resource.source.should eql([ "http://opscode.com/", "http://puppetlabs.com/" ])
+    end
+
+    it "does not accept a non-URI as the source" do
+      lambda { @resource.source("not-a-uri") }.should raise_error(Chef::Exceptions::InvalidRemoteFileURI)
+    end
+
+    it "should raise an exception when source is an empty array" do
+      lambda { @resource.source([]) }.should raise_error(ArgumentError)
+    end
+
+  end
+
+  describe "checksum" do
+    it "should accept a string for the checksum object" do
+      @resource.checksum "asdf"
+      @resource.checksum.should eql("asdf")
+    end
+
+    it "should default to nil" do
+      @resource.checksum.should == nil
+    end
+  end
+
+  describe "ftp_active_mode" do
+    it "should accept a boolean for the ftp_active_mode object" do
+      @resource.ftp_active_mode true
+      @resource.ftp_active_mode.should be_true
+    end
+
+    it "should default to false" do
+      @resource.ftp_active_mode.should be_false
+    end
+  end
+
+  describe "conditional get options" do
+    it "defaults to using etags and last modified" do
+      @resource.use_etags.should be_true
+      @resource.use_last_modified.should be_true
+    end
+
+    it "enable or disables etag and last modified options as a group" do
+      @resource.use_conditional_get(false)
+      @resource.use_etags.should be_false
+      @resource.use_last_modified.should be_false
+
+      @resource.use_conditional_get(true)
+      @resource.use_etags.should be_true
+      @resource.use_last_modified.should be_true
+    end
+
+    it "disables etags indivdually" do
+      @resource.use_etags(false)
+      @resource.use_etags.should be_false
+      @resource.use_last_modified.should be_true
+    end
+
+    it "disables last modified individually" do
+      @resource.use_last_modified(false)
+      @resource.use_last_modified.should be_false
+      @resource.use_etags.should be_true
+    end
+
+  end
+
+  describe "when it has group, mode, owner, source, and checksum" do
+    before do
+      if Chef::Platform.windows?
+        @resource.path("C:/temp/origin/file.txt")
+        @resource.rights(:read, "Everyone")
+        @resource.deny_rights(:full_control, "Clumsy_Sam")
+      else
+        @resource.path("/this/path/")
+        @resource.group("pokemon")
+        @resource.mode("0664")
+        @resource.owner("root")
+      end
+      @resource.source("https://www.google.com/images/srpr/logo3w.png")
+      @resource.checksum("1"*26)
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      if Chef::Platform.windows?
+        puts state
+        state[:rights].should == [{:permissions => :read, :principals => "Everyone"}]
+        state[:deny_rights].should == [{:permissions => :full_control, :principals => "Clumsy_Sam"}]
+      else
+        state[:group].should == "pokemon"
+        state[:mode].should == "0664"
+        state[:owner].should == "root"
+        state[:checksum].should == "1"*26
+      end
+    end
+
+    it "returns the path as its identity" do
+      if Chef::Platform.windows?
+        @resource.identity.should == "C:/temp/origin/file.txt"
+      else
+        @resource.identity.should == "/this/path/"
+      end
+    end
+  end
+end
diff --git a/spec/unit/resource/route_spec.rb b/spec/unit/resource/route_spec.rb
new file mode 100644
index 0000000..bca42f1
--- /dev/null
+++ b/spec/unit/resource/route_spec.rb
@@ -0,0 +1,107 @@
+#
+# Author:: Bryan McLellan (btm at loftninjas.org)
+# Author:: Tyler Cloke (<tyler at opscode.com>)
+# Copyright:: Copyright (c) 2008 Bryan McLellan
+# 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::Resource::Route do
+
+  before(:each) do
+    @resource = Chef::Resource::Route.new("10.0.0.10")
+  end
+
+  it "should create a new Chef::Resource::Route" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Route)
+  end
+
+  it "should have a name" do
+    @resource.name.should eql("10.0.0.10")
+  end
+
+  it "should have a default action of 'add'" do
+    @resource.action.should eql([:add])
+  end
+
+  it "should accept add or delete for action" do
+    lambda { @resource.action :add }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :delete }.should_not raise_error(ArgumentError)
+    lambda { @resource.action :lolcat }.should raise_error(ArgumentError)
+  end
+
+  it "should use the object name as the target by default" do
+    @resource.target.should eql("10.0.0.10")
+  end
+
+  it "should allow you to specify the netmask" do
+    @resource.netmask "255.255.255.0"
+    @resource.netmask.should eql("255.255.255.0")
+  end
+
+  it "should allow you to specify the gateway" do
+    @resource.gateway "10.0.0.1"
+    @resource.gateway.should eql("10.0.0.1")
+  end
+
+  it "should allow you to specify the metric" do
+    @resource.metric 10
+    @resource.metric.should eql(10)
+  end
+
+  it "should allow you to specify the device" do
+    @resource.device "eth0"
+    @resource.device.should eql("eth0")
+  end
+
+  it "should allow you to specify the route type" do
+    @resource.route_type "host"
+    @resource.route_type.should eql(:host)
+  end
+
+  it "should default to a host route type" do
+    @resource.route_type.should eql(:host)
+  end
+
+  it "should accept a net route type" do
+    @resource.route_type :net
+    @resource.route_type.should eql(:net)
+  end
+
+  it "should reject any other route_type but :host and :net" do
+    lambda { @resource.route_type "lolcat" }.should raise_error(ArgumentError)
+  end
+
+  describe "when it has netmask, gateway, and device" do
+    before do
+      @resource.target("charmander")
+      @resource.netmask("lemask")
+      @resource.gateway("111.111.111")
+      @resource.device("forcefield")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:netmask].should == "lemask"
+      state[:gateway].should == "111.111.111"
+    end
+
+    it "returns the target  as its identity" do
+      @resource.identity.should == "charmander"
+    end
+  end
+end
diff --git a/spec/unit/resource/rpm_package_spec.rb b/spec/unit/resource/rpm_package_spec.rb
new file mode 100644
index 0000000..25930a5
--- /dev/null
+++ b/spec/unit/resource/rpm_package_spec.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2010 Thomas Bishop
+# 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::Resource::RpmPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::RpmPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::RpmPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::RpmPackage)
+  end
+
+  it "should set the resource_name to :rpm_package" do
+    @resource.resource_name.should eql(:rpm_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Rpm" do
+    @resource.provider.should eql(Chef::Provider::Package::Rpm)
+  end
+end
diff --git a/spec/unit/resource/ruby_block_spec.rb b/spec/unit/resource/ruby_block_spec.rb
new file mode 100644
index 0000000..82bbd1f
--- /dev/null
+++ b/spec/unit/resource/ruby_block_spec.rb
@@ -0,0 +1,61 @@
+#
+# Author:: AJ Christensen (<aj at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::RubyBlock do
+
+  before(:each) do
+    @resource = Chef::Resource::RubyBlock.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::RubyBlock" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::RubyBlock)
+  end
+
+  it "should have a default action of 'create'" do
+    @resource.action.should eql("run")
+  end
+
+  it "should have a resource name of :ruby_block" do
+    @resource.resource_name.should eql(:ruby_block)
+  end
+
+  it "should accept a ruby block/proc/.. for the 'block' parameter" do
+    @resource.block do
+      "foo"
+    end.call.should eql("foo")
+  end
+
+  it "allows the action to be 'create'" do
+    @resource.action :create
+    @resource.action.should == [:create]
+  end
+
+  describe "when it has been initialized with block code" do
+    before do
+      @resource.block_name("puts 'harrrr'")
+    end
+
+    it "returns the block as its identity" do
+      @resource.identity.should == "puts 'harrrr'"
+    end
+  end
+end
diff --git a/spec/unit/resource/ruby_spec.rb b/spec/unit/resource/ruby_spec.rb
new file mode 100644
index 0000000..9bf7316
--- /dev/null
+++ b/spec/unit/resource/ruby_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::Ruby do
+
+  before(:each) do
+    @resource = Chef::Resource::Ruby.new("fakey_fakerton")
+  end
+
+  it "should create a new Chef::Resource::Ruby" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Ruby)
+  end
+
+  it "should have a resource name of :ruby" do
+    @resource.resource_name.should eql(:ruby)
+  end
+
+  it "should have an interpreter of ruby" do
+    @resource.interpreter.should eql("ruby")
+  end
+
+end
diff --git a/spec/unit/resource/scm_spec.rb b/spec/unit/resource/scm_spec.rb
new file mode 100644
index 0000000..8f6593a
--- /dev/null
+++ b/spec/unit/resource/scm_spec.rb
@@ -0,0 +1,172 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Scm do
+
+  before(:each) do
+    @resource = Chef::Resource::Scm.new("my awesome app")
+  end
+
+  it "should be a SCM resource" do
+    @resource.should be_a_kind_of(Chef::Resource::Scm)
+  end
+
+  it "supports :checkout, :export, :sync, :diff, and :log actions" do
+    @resource.allowed_actions.should include(:checkout)
+    @resource.allowed_actions.should include(:export)
+    @resource.allowed_actions.should include(:sync)
+    @resource.allowed_actions.should include(:diff)
+    @resource.allowed_actions.should include(:log)
+  end
+
+  it "takes the destination path as a string" do
+    @resource.destination "/path/to/deploy/dir"
+    @resource.destination.should eql("/path/to/deploy/dir")
+  end
+
+  it "takes a string for the repository URL" do
+    @resource.repository "git://github.com/opscode/chef.git"
+    @resource.repository.should eql("git://github.com/opscode/chef.git")
+  end
+
+  it "takes a string for the revision" do
+    @resource.revision "abcdef"
+    @resource.revision.should eql("abcdef")
+  end
+
+  it "defaults to the ``HEAD'' revision" do
+    @resource.revision.should eql("HEAD")
+  end
+
+  it "takes a string for the user to run as" do
+    @resource.user "dr_deploy"
+    @resource.user.should eql("dr_deploy")
+  end
+
+  it "also takes an integer for the user to run as" do
+    @resource.user 0
+    @resource.user.should eql(0)
+  end
+
+  it "takes a string for the group to run as, defaulting to nil" do
+    @resource.group.should be_nil
+    @resource.group "opsdevs"
+    @resource.group.should == "opsdevs"
+  end
+
+  it "also takes an integer for the group to run as" do
+    @resource.group 23
+    @resource.group.should == 23
+  end
+
+  it "has a svn_username String attribute" do
+    @resource.svn_username "moartestsplz"
+    @resource.svn_username.should eql("moartestsplz")
+  end
+
+  it "has a svn_password String attribute" do
+    @resource.svn_password "taftplz"
+    @resource.svn_password.should eql("taftplz")
+  end
+
+  it "has a svn_arguments String attribute" do
+    @resource.svn_arguments "--more-taft plz"
+    @resource.svn_arguments.should eql("--more-taft plz")
+  end
+
+  it "has a svn_info_args String attribute" do
+    @resource.svn_info_args.should be_nil
+    @resource.svn_info_args("--no-moar-plaintext-creds yep")
+    @resource.svn_info_args.should == "--no-moar-plaintext-creds yep"
+  end
+
+  it "takes the depth as an integer for shallow clones" do
+    @resource.depth 5
+    @resource.depth.should == 5
+    lambda {@resource.depth "five"}.should raise_error(ArgumentError)
+  end
+
+  it "defaults to nil depth for a full clone" do
+    @resource.depth.should be_nil
+  end
+
+  it "takes a boolean for #enable_submodules" do
+    @resource.enable_submodules true
+    @resource.enable_submodules.should be_true
+    lambda {@resource.enable_submodules "lolz"}.should raise_error(ArgumentError)
+  end
+
+  it "defaults to not enabling submodules" do
+    @resource.enable_submodules.should be_false
+  end
+
+  it "takes a string for the remote" do
+    @resource.remote "opscode"
+    @resource.remote.should eql("opscode")
+    lambda {@resource.remote 1337}.should raise_error(ArgumentError)
+  end
+
+  it "defaults to ``origin'' for the remote" do
+    @resource.remote.should == "origin"
+  end
+
+  it "takes a string for the ssh wrapper" do
+    @resource.ssh_wrapper "with_ssh_fu"
+    @resource.ssh_wrapper.should eql("with_ssh_fu")
+  end
+
+  it "defaults to nil for the ssh wrapper" do
+    @resource.ssh_wrapper.should be_nil
+  end
+
+  describe "when it has a timeout attribute" do
+    let(:ten_seconds) { 10 }
+    before { @resource.timeout(ten_seconds) }
+    it "stores this timeout" do
+      @resource.timeout.should == ten_seconds
+    end
+  end
+  describe "when it has no timeout attribute" do
+    it "should have no default timeout" do
+      @resource.timeout.should be_nil
+    end
+  end
+
+  describe "when it has repository, revision, user, and group" do
+    before do
+      @resource.destination("hell")
+      @resource.repository("apt")
+      @resource.revision("1.2.3")
+      @resource.user("root")
+      @resource.group("super_adventure_club")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:revision].should == "1.2.3"
+    end
+
+    it "returns the destination as its identity" do
+      @resource.identity.should == "hell"
+    end
+  end
+
+end
diff --git a/spec/unit/resource/script_spec.rb b/spec/unit/resource/script_spec.rb
new file mode 100644
index 0000000..f100b0d
--- /dev/null
+++ b/spec/unit/resource/script_spec.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Script do
+  let(:resource_instance_name) { "fakey_fakerton" }
+  let(:script_resource) { Chef::Resource::Script.new(resource_instance_name) }
+  let(:resource_name) { :script }
+
+  it "should accept a string for the interpreter" do
+    script_resource.interpreter "naaaaNaNaNaaNaaNaaNaa"
+    script_resource.interpreter.should eql("naaaaNaNaNaaNaaNaaNaa")
+  end
+
+  describe "when it has interpreter and flags" do
+    before do
+      script_resource.command("grep")
+      script_resource.interpreter("gcc")
+      script_resource.flags("-al")
+    end
+
+   it "returns the command as its identity" do
+      script_resource.identity.should == "grep"
+    end
+  end
+
+  it_behaves_like "a script resource"
+end
+
diff --git a/spec/unit/resource/service_spec.rb b/spec/unit/resource/service_spec.rb
new file mode 100644
index 0000000..067fa7d
--- /dev/null
+++ b/spec/unit/resource/service_spec.rb
@@ -0,0 +1,176 @@
+#
+# Author:: AJ Christensen (<aj at hjksolutions.com>)
+# Author:: Tyler Cloke (<tyler at opscode.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'
+
+describe Chef::Resource::Service do
+
+  before(:each) do
+    @resource = Chef::Resource::Service.new("chef")
+  end
+
+  it "should create a new Chef::Resource::Service" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::Service)
+  end
+
+  it "should set the service_name to the first argument to new" do
+    @resource.service_name.should eql("chef")
+  end
+
+  it "should set the pattern to be the service name by default" do
+    @resource.pattern.should eql("chef")
+  end
+
+  it "should accept a string for the service name" do
+    @resource.service_name "something"
+    @resource.service_name.should eql("something")
+  end
+
+  it "should accept a string for the service pattern" do
+    @resource.pattern ".*"
+    @resource.pattern.should eql(".*")
+  end
+
+  it "should not accept a regexp for the service pattern" do
+    lambda {
+      @resource.pattern /.*/
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a string for the service start command" do
+    @resource.start_command "/etc/init.d/chef start"
+    @resource.start_command.should eql("/etc/init.d/chef start")
+  end
+
+  it "should not accept a regexp for the service start command" do
+    lambda {
+      @resource.start_command /.*/
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a string for the service stop command" do
+    @resource.stop_command "/etc/init.d/chef stop"
+    @resource.stop_command.should eql("/etc/init.d/chef stop")
+  end
+
+  it "should not accept a regexp for the service stop command" do
+    lambda {
+      @resource.stop_command /.*/
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a string for the service status command" do
+    @resource.status_command "/etc/init.d/chef status"
+    @resource.status_command.should eql("/etc/init.d/chef status")
+  end
+
+  it "should not accept a regexp for the service status command" do
+    lambda {
+      @resource.status_command /.*/
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a string for the service restart command" do
+    @resource.restart_command "/etc/init.d/chef restart"
+    @resource.restart_command.should eql("/etc/init.d/chef restart")
+  end
+
+  it "should not accept a regexp for the service restart command" do
+    lambda {
+      @resource.restart_command /.*/
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a string for the service reload command" do
+    @resource.reload_command "/etc/init.d/chef reload"
+    @resource.reload_command.should eql("/etc/init.d/chef reload")
+  end
+
+  it "should not accept a regexp for the service reload command" do
+    lambda {
+      @resource.reload_command /.*/
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should accept a string for the service init command" do
+    @resource.init_command "/etc/init.d/chef"
+    @resource.init_command.should eql("/etc/init.d/chef")
+  end
+
+  it "should not accept a regexp for the service init command" do
+    lambda {
+      @resource.init_command /.*/
+    }.should raise_error(ArgumentError)
+  end
+
+  %w{enabled running}.each do |attrib|
+    it "should accept true for #{attrib}" do
+      @resource.send(attrib, true)
+      @resource.send(attrib).should eql(true)
+    end
+
+    it "should accept false for #{attrib}" do
+      @resource.send(attrib, false)
+      @resource.send(attrib).should eql(false)
+    end
+
+    it "should not accept a string for #{attrib}" do
+      lambda { @resource.send(attrib, "poop") }.should raise_error(ArgumentError)
+    end
+
+    it "should default all the feature support to false" do
+      support_hash = { :status => false, :restart => false, :reload=> false }
+      @resource.supports.should == support_hash
+    end
+
+    it "should allow you to set what features this resource supports as a array" do
+      support_array = [ :status, :restart ]
+      support_hash = { :status => true, :restart => true, :reload => false }
+      @resource.supports(support_array)
+      @resource.supports.should == support_hash
+    end
+
+    it "should allow you to set what features this resource supports as a hash" do
+      support_hash = { :status => true, :restart => true, :reload => false }
+      @resource.supports(support_hash)
+      @resource.supports.should == support_hash
+    end
+  end
+
+  describe "when it has pattern and supports" do
+    before do
+      @resource.service_name("superfriend")
+      @resource.enabled(true)
+      @resource.running(false)
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:enabled].should eql(true)
+      state[:running].should eql(false)
+    end
+
+    it "returns the service name as its identity" do
+      @resource.identity.should == "superfriend"
+    end
+  end
+
+
+end
diff --git a/spec/unit/resource/smartos_package_spec.rb b/spec/unit/resource/smartos_package_spec.rb
new file mode 100644
index 0000000..391713c
--- /dev/null
+++ b/spec/unit/resource/smartos_package_spec.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Thomas Bishop (<bishop.thomas at gmail.com>)
+# Copyright:: Copyright (c) 2010 Thomas Bishop
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::SmartosPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::SmartosPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::SmartosPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::SmartosPackage)
+  end
+
+  it "should set the resource_name to :smartos_package" do
+    @resource.resource_name.should eql(:smartos_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::SmartOS" do
+    @resource.provider.should eql(Chef::Provider::Package::SmartOS)
+  end
+end
diff --git a/spec/unit/resource/solaris_package_spec.rb b/spec/unit/resource/solaris_package_spec.rb
new file mode 100644
index 0000000..6d0260a
--- /dev/null
+++ b/spec/unit/resource/solaris_package_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Prabhu Das (<prabhu.das at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::SolarisPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::SolarisPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::SolarisPackage object" do
+    @resource.should be_a_kind_of(Chef::Resource::SolarisPackage)
+  end
+
+  it "should not raise any Error when valid number of arguments are provided" do
+    expect { Chef::Resource::SolarisPackage.new("foo") }.to_not raise_error
+  end
+
+  it "should raise ArgumentError when incorrect number of arguments are provided" do
+    expect { Chef::Resource::SolarisPackage.new }.to raise_error(ArgumentError)
+  end
+
+  it "should set the package_name to the name provided" do
+    @resource.package_name.should eql("foo")
+  end
+
+  it "should set the resource_name to :solaris_package" do
+    @resource.resource_name.should eql(:solaris_package)
+  end
+
+  it "should set the run_context to the run_context provided" do
+    @run_context = double()
+    @run_context.stub(:node)
+    resource = Chef::Resource::SolarisPackage.new("foo", @run_context)
+    resource.run_context.should eql(@run_context)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Solaris" do
+    @resource.provider.should eql(Chef::Provider::Package::Solaris)
+  end
+end
diff --git a/spec/unit/resource/subversion_spec.rb b/spec/unit/resource/subversion_spec.rb
new file mode 100644
index 0000000..67593c5
--- /dev/null
+++ b/spec/unit/resource/subversion_spec.rb
@@ -0,0 +1,58 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.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'
+
+describe Chef::Resource::Subversion do
+
+  before do
+    @svn = Chef::Resource::Subversion.new("ohai, svn project!")
+  end
+
+  it "is a subclass of Resource::Scm" do
+    @svn.should be_an_instance_of(Chef::Resource::Subversion)
+    @svn.should be_a_kind_of(Chef::Resource::Scm)
+  end
+
+  it "uses the subversion provider" do
+    @svn.provider.should eql(Chef::Provider::Subversion)
+  end
+
+  it "allows the force_export action" do
+    @svn.allowed_actions.should include(:force_export)
+  end
+
+  it "sets svn info arguments to --no-auth-cache by default" do
+    @svn.svn_info_args.should == '--no-auth-cache'
+  end
+
+  it "resets svn info arguments to nil when given false in the setter" do
+    @svn.svn_info_args(false)
+    @svn.svn_info_args.should be_nil
+  end
+
+  it "sets svn arguments to --no-auth-cache by default" do
+    @svn.svn_arguments.should == '--no-auth-cache'
+  end
+
+  it "resets svn arguments to nil when given false in the setter" do
+    @svn.svn_arguments(false)
+    @svn.svn_arguments.should be_nil
+  end
+
+end
diff --git a/spec/unit/resource/template_spec.rb b/spec/unit/resource/template_spec.rb
new file mode 100644
index 0000000..c9dfdc7
--- /dev/null
+++ b/spec/unit/resource/template_spec.rb
@@ -0,0 +1,211 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::Template do
+
+  before(:each) do
+    @resource = Chef::Resource::Template.new("fakey_fakerton")
+  end
+
+  describe "initialize" do
+    it "should create a new Chef::Resource::Template" do
+      @resource.should be_a_kind_of(Chef::Resource)
+      @resource.should be_a_kind_of(Chef::Resource::File)
+      @resource.should be_a_kind_of(Chef::Resource::Template)
+    end
+  end
+
+  describe "source" do
+    it "should accept a string for the template source" do
+      @resource.source "something"
+      @resource.source.should eql("something")
+    end
+
+    it "should have a default based on the param name with .erb appended" do
+      @resource.source.should eql("fakey_fakerton.erb")
+    end
+
+    it "should use only the basename of the file as the default" do
+      r = Chef::Resource::Template.new("/tmp/obit/fakey_fakerton")
+      r.source.should eql("fakey_fakerton.erb")
+    end
+  end
+
+  describe "variables" do
+    it "should accept a hash for the variable list" do
+      @resource.variables({ :reluctance => :awkward })
+      @resource.variables.should == { :reluctance => :awkward }
+    end
+  end
+
+  describe "cookbook" do
+    it "should accept a string for the cookbook name" do
+      @resource.cookbook("foo")
+      @resource.cookbook.should == "foo"
+    end
+
+    it "should default to nil" do
+      @resource.cookbook.should == nil
+    end
+  end
+
+  describe "local" do
+    it "should accept a boolean for whether a template is local or remote" do
+      @resource.local(true)
+      @resource.local.should == true
+    end
+
+    it "should default to false" do
+      @resource.local.should == false
+    end
+  end
+
+  describe "when it has a path, owner, group, mode, and checksum" do
+    before do
+      @resource.path("/tmp/foo.txt")
+      @resource.owner("root")
+      @resource.group("wheel")
+      @resource.mode("0644")
+      @resource.checksum("1" * 64)
+    end
+
+    context "on unix", :unix_only do
+      it "describes its state" do
+        state = @resource.state
+        state[:owner].should == "root"
+        state[:group].should == "wheel"
+        state[:mode].should == "0644"
+        state[:checksum].should == "1" * 64
+      end
+    end
+
+    context "on windows", :windows_only do
+      # according to Chef::Resource::File, windows state attributes are rights + deny_rights
+      pending "it describes its state"
+    end
+
+    it "returns the file path as its identity" do
+      @resource.identity.should == "/tmp/foo.txt"
+    end
+  end
+
+  describe "defining helper methods" do
+
+    module ExampleHelpers
+      def static_example
+        "static_example"
+      end
+    end
+
+    it "collects helper method bodies as blocks" do
+      @resource.helper(:example_1) { "example_1" }
+      @resource.helper(:example_2) { "example_2" }
+      @resource.inline_helper_blocks[:example_1].call.should == "example_1"
+      @resource.inline_helper_blocks[:example_2].call.should == "example_2"
+    end
+
+    it "compiles helper methods into a module" do
+      @resource.helper(:example_1) { "example_1" }
+      @resource.helper(:example_2) { "example_2" }
+      modules = @resource.helper_modules
+      modules.should have(1).module
+      o = Object.new
+      modules.each {|m| o.extend(m)}
+      o.example_1.should == "example_1"
+      o.example_2.should == "example_2"
+    end
+
+    it "compiles helper methods with arguments into a module" do
+      @resource.helper(:shout) { |quiet| quiet.upcase }
+      modules = @resource.helper_modules
+      o = Object.new
+      modules.each {|m| o.extend(m)}
+      o.shout("shout").should == "SHOUT"
+    end
+
+    it "raises an error when attempting to define a helper method without a method body" do
+      lambda { @resource.helper(:example) }.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "raises an error when attempting to define a helper method with a non-Symbod method name" do
+      lambda { @resource.helper("example") { "fail" } }.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "collects helper module bodies as blocks" do
+      @resource.helpers do
+        def example_1
+          "example_1"
+        end
+      end
+      module_body = @resource.inline_helper_modules.first
+      module_body.should be_a(Proc)
+    end
+
+    it "compiles helper module bodies into modules" do
+      @resource.helpers do
+        def example_1
+          "example_1"
+        end
+      end
+      modules = @resource.helper_modules
+      modules.should have(1).module
+      o = Object.new
+      modules.each {|m| o.extend(m)}
+      o.example_1.should == "example_1"
+    end
+
+    it "raises an error when no block or module name is given for helpers definition" do
+      lambda { @resource.helpers() }.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "raises an error when a non-module is given for helpers definition" do
+      lambda { @resource.helpers("NotAModule") }.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "raises an error when a module name and block are both given for helpers definition" do
+      lambda { @resource.helpers(ExampleHelpers) { module_code } }.should raise_error(Chef::Exceptions::ValidationFailed)
+    end
+
+    it "collects helper modules" do
+      @resource.helpers(ExampleHelpers)
+      @resource.helper_modules.should include(ExampleHelpers)
+    end
+
+    it "combines all helpers into a set of compiled modules" do
+      @resource.helpers(ExampleHelpers)
+      @resource.helpers do
+        def inline_module
+          "inline_module"
+        end
+      end
+      @resource.helper(:inline_method) { "inline_method" }
+      @resource.should have(3).helper_modules
+
+      o = Object.new
+      @resource.helper_modules.each {|m| o.extend(m)}
+      o.static_example.should == "static_example"
+      o.inline_module.should == "inline_module"
+      o.inline_method.should == "inline_method"
+    end
+
+
+  end
+
+end
diff --git a/spec/unit/resource/timestamped_deploy_spec.rb b/spec/unit/resource/timestamped_deploy_spec.rb
new file mode 100644
index 0000000..f380ffc
--- /dev/null
+++ b/spec/unit/resource/timestamped_deploy_spec.rb
@@ -0,0 +1,28 @@
+#
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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::Resource::TimestampedDeploy do
+
+  it "defaults to the TimestampedDeploy provider" do
+    @resource = Chef::Resource::TimestampedDeploy.new("stuff")
+    @resource.provider.should == Chef::Provider::Deploy::Timestamped
+  end
+
+end
diff --git a/spec/unit/resource/user_spec.rb b/spec/unit/resource/user_spec.rb
new file mode 100644
index 0000000..caf12aa
--- /dev/null
+++ b/spec/unit/resource/user_spec.rb
@@ -0,0 +1,129 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::Resource::User, "initialize" do
+  before(:each) do
+    @resource = Chef::Resource::User.new("adam")
+  end
+
+  it "should create a new Chef::Resource::User" do
+    @resource.should be_a_kind_of(Chef::Resource)
+    @resource.should be_a_kind_of(Chef::Resource::User)
+  end
+
+  it "should set the resource_name to :user" do
+    @resource.resource_name.should eql(:user)
+  end
+
+  it "should set the username equal to the argument to initialize" do
+    @resource.username.should eql("adam")
+  end
+
+  %w{comment uid gid home shell password}.each do |attrib|
+    it "should set #{attrib} to nil" do
+      @resource.send(attrib).should eql(nil)
+    end
+  end
+
+  it "should set action to :create" do
+    @resource.action.should eql(:create)
+  end
+
+  it "should set supports[:manage_home] to false" do
+    @resource.supports[:manage_home].should eql(false)
+  end
+
+  it "should set supports[:non_unique] to false" do
+    @resource.supports[:non_unique].should eql(false)
+  end
+
+  %w{create remove modify manage lock unlock}.each do |action|
+    it "should allow action #{action}" do
+      @resource.allowed_actions.detect { |a| a == action.to_sym }.should eql(action.to_sym)
+    end
+  end
+
+  it "should accept domain users (@ or \ separator) on non-windows" do
+    lambda { @resource.username "domain\@user" }.should_not raise_error(ArgumentError)
+    @resource.username.should == "domain\@user"
+    lambda { @resource.username "domain\\user" }.should_not raise_error(ArgumentError)
+    @resource.username.should  == "domain\\user"
+  end
+end
+
+%w{username comment home shell password}.each do |attrib|
+  describe Chef::Resource::User, attrib do
+    before(:each) do
+      @resource = Chef::Resource::User.new("adam")
+    end
+
+    it "should allow a string" do
+      @resource.send(attrib, "adam")
+      @resource.send(attrib).should eql("adam")
+    end
+
+    it "should not allow a hash" do
+      lambda { @resource.send(attrib, { :woot => "i found it" }) }.should raise_error(ArgumentError)
+    end
+  end
+end
+
+%w{uid gid}.each do |attrib|
+  describe Chef::Resource::User, attrib do
+    before(:each) do
+      @resource = Chef::Resource::User.new("adam")
+    end
+
+    it "should allow a string" do
+      @resource.send(attrib, "100")
+      @resource.send(attrib).should eql("100")
+    end
+
+    it "should allow an integer" do
+      @resource.send(attrib, 100)
+      @resource.send(attrib).should eql(100)
+    end
+
+    it "should not allow a hash" do
+      lambda { @resource.send(attrib, { :woot => "i found it" }) }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "when it has uid, gid, and home" do
+    before do
+      @resource = Chef::Resource::User.new("root")
+      @resource.uid(123)
+      @resource.gid(456)
+      @resource.home("/usr/local/root/")
+    end
+
+    it "describes its state" do
+      state = @resource.state
+      state[:uid].should == 123
+      state[:gid].should == 456
+      state[:home].should == "/usr/local/root/"
+    end
+
+    it "returns the username as its identity" do
+      @resource.identity.should == "root"
+    end
+  end
+
+end
diff --git a/spec/unit/resource/yum_package_spec.rb b/spec/unit/resource/yum_package_spec.rb
new file mode 100644
index 0000000..783a539
--- /dev/null
+++ b/spec/unit/resource/yum_package_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: AJ Christensen (<aj at opscode.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'
+
+describe Chef::Resource::YumPackage, "initialize" do
+
+  before(:each) do
+    @resource = Chef::Resource::YumPackage.new("foo")
+  end
+
+  it "should return a Chef::Resource::YumPackage" do
+    @resource.should be_a_kind_of(Chef::Resource::YumPackage)
+  end
+
+  it "should set the resource_name to :yum_package" do
+    @resource.resource_name.should eql(:yum_package)
+  end
+
+  it "should set the provider to Chef::Provider::Package::Yum" do
+    @resource.provider.should eql(Chef::Provider::Package::Yum)
+  end
+end
+
+describe Chef::Resource::YumPackage, "arch" do
+  before(:each) do
+    @resource = Chef::Resource::YumPackage.new("foo")
+  end
+
+  it "should set the arch variable to whatever is passed in" do
+    @resource.arch("i386")
+    @resource.arch.should eql("i386")
+  end
+end
+
+describe Chef::Resource::YumPackage, "flush_cache" do
+  before(:each) do
+    @resource = Chef::Resource::YumPackage.new("foo")
+  end
+
+  it "should default the flush timing to false" do
+    flush_hash = { :before => false, :after => false }
+    @resource.flush_cache.should == flush_hash
+  end
+
+  it "should allow you to set the flush timing with an array" do
+    flush_array = [ :before, :after ]
+    flush_hash = { :before => true, :after => true }
+    @resource.flush_cache(flush_array)
+    @resource.flush_cache.should == flush_hash
+  end
+
+  it "should allow you to set the flush timing with a hash" do
+    flush_hash = { :before => true, :after => true }
+    @resource.flush_cache(flush_hash)
+    @resource.flush_cache.should == flush_hash
+  end
+end
+
+describe Chef::Resource::YumPackage, "allow_downgrade" do
+  before(:each) do
+    @resource = Chef::Resource::YumPackage.new("foo")
+  end
+
+  it "should allow you to specify whether allow_downgrade is true or false" do
+    lambda { @resource.allow_downgrade true }.should_not raise_error(ArgumentError)
+    lambda { @resource.allow_downgrade false }.should_not raise_error(ArgumentError)
+    lambda { @resource.allow_downgrade "monkey" }.should raise_error(ArgumentError)
+  end
+end
diff --git a/spec/unit/resource_collection/stepable_iterator_spec.rb b/spec/unit/resource_collection/stepable_iterator_spec.rb
new file mode 100644
index 0000000..b649f8b
--- /dev/null
+++ b/spec/unit/resource_collection/stepable_iterator_spec.rb
@@ -0,0 +1,144 @@
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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::ResourceCollection::StepableIterator do
+  CRSI = Chef::ResourceCollection::StepableIterator
+
+  it "has an empty array for its collection by default" do
+    CRSI.new.collection.should == []
+  end
+
+  describe "doing basic iteration" do
+    before do
+      @simple_collection = [1,2,3,4]
+      @iterator = CRSI.for_collection(@simple_collection)
+    end
+
+    it "re-initializes the instance with a collection" do
+      @iterator.collection.should equal(@simple_collection)
+      @iterator.size.should == 4
+    end
+
+    it "iterates over the collection" do
+      sum = 0
+      @iterator.each do |int|
+        sum += int
+      end
+      sum.should == 10
+    end
+
+    it "iterates over the collection with each_index" do
+      collected_by_index = []
+      @iterator.each_index do |idx|
+        collected_by_index << @simple_collection[idx]
+      end
+      collected_by_index.should == @simple_collection
+      collected_by_index.should_not equal(@simple_collection)
+    end
+
+    it "iterates over the collection with index and element" do
+      collected = {}
+      @iterator.each_with_index do |element, index|
+        collected[index] = element
+      end
+      collected.should == {0=>1, 1=>2, 2=>3, 3=>4}
+    end
+
+  end
+
+  describe "pausing and resuming iteration" do
+
+    before do
+      @collection = []
+      @snitch_var = nil
+      @collection << lambda { @snitch_var = 23 }
+      @collection << lambda { @iterator.pause }
+      @collection << lambda { @snitch_var = 42 }
+
+      @iterator = CRSI.for_collection(@collection)
+      @iterator.each { |proc| proc.call }
+    end
+
+    it "allows the iteration to be paused" do
+      @snitch_var.should == 23
+    end
+
+    it "allows the iteration to be resumed" do
+      @snitch_var.should == 23
+      @iterator.resume
+      @snitch_var.should == 42
+    end
+
+    it "allows iteration to be rewound" do
+      @iterator.skip_back(2)
+      @iterator.resume
+      @snitch_var.should == 23
+      @iterator.resume
+      @snitch_var.should == 42
+    end
+
+    it "allows iteration to be fast forwarded" do
+      @iterator.skip_forward
+      @iterator.resume
+      @snitch_var.should == 23
+    end
+
+    it "allows iteration to be rewound" do
+      @snitch_var = nil
+      @iterator.rewind
+      @iterator.position.should == 0
+      @iterator.resume
+      @snitch_var.should == 23
+    end
+
+    it "allows iteration to be stepped" do
+      @snitch_var = nil
+      @iterator.rewind
+      @iterator.step
+      @iterator.position.should == 1
+      @snitch_var.should == 23
+    end
+
+    it "doesn't step if there are no more steps" do
+      @iterator.step.should == 3
+      lambda {@iterator.step}.should_not raise_error
+      @iterator.step.should be_nil
+    end
+
+    it "allows the iteration to start by being stepped" do
+      @snitch_var = nil
+      @iterator = CRSI.for_collection(@collection)
+      @iterator.iterate_on(:element) { |proc| proc.call }
+      @iterator.step
+      @iterator.position.should == 1
+      @snitch_var.should == 23
+    end
+
+    it "should work correctly when elements are added to the collection during iteration" do
+      @collection.insert(2, lambda { @snitch_var = 815})
+      @collection.insert(3, lambda { @iterator.pause })
+      @iterator.resume
+      @snitch_var.should == 815
+      @iterator.resume
+      @snitch_var.should == 42
+    end
+
+  end
+
+end
diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb
new file mode 100644
index 0000000..d8fc0db
--- /dev/null
+++ b/spec/unit/resource_collection_spec.rb
@@ -0,0 +1,286 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::ResourceCollection do
+
+  before(:each) do
+    @rc = Chef::ResourceCollection.new()
+    @resource = Chef::Resource::ZenMaster.new("makoto")
+  end
+
+  describe "initialize" do
+    it "should return a Chef::ResourceCollection" do
+      @rc.should be_kind_of(Chef::ResourceCollection)
+    end
+  end
+
+  describe "[]" do
+    it "should accept Chef::Resources through [index]" do
+      lambda { @rc[0] = @resource }.should_not raise_error
+      lambda { @rc[0] = "string" }.should raise_error
+    end
+
+    it "should allow you to fetch Chef::Resources by position" do
+      @rc[0] = @resource
+      @rc[0].should eql(@resource)
+    end
+  end
+
+  describe "push" do
+    it "should accept Chef::Resources through pushing" do
+      lambda { @rc.push(@resource) }.should_not raise_error
+      lambda { @rc.push("string") }.should raise_error
+    end
+  end
+
+  describe "<<" do
+    it "should accept the << operator" do
+      lambda { @rc << @resource }.should_not raise_error
+    end
+  end
+
+  describe "insert" do
+    it "should accept only Chef::Resources" do
+      lambda { @rc.insert(@resource) }.should_not raise_error
+      lambda { @rc.insert("string") }.should raise_error
+    end
+
+    it "should append resources to the end of the collection when not executing a run" do
+      zmr = Chef::Resource::ZenMaster.new("there is no spoon")
+      @rc.insert(@resource)
+      @rc.insert(zmr)
+      @rc[0].should eql(@resource)
+      @rc[1].should eql(zmr)
+    end
+
+    it "should insert resources to the middle of the collection if called while executing a run" do
+      resource_to_inject = Chef::Resource::ZenMaster.new("there is no spoon")
+      zmr = Chef::Resource::ZenMaster.new("morpheus")
+      dummy = Chef::Resource::ZenMaster.new("keanu reeves")
+      @rc.insert(zmr)
+      @rc.insert(dummy)
+
+      @rc.execute_each_resource do |resource|
+        @rc.insert(resource_to_inject) if resource == zmr
+      end
+
+      @rc[0].should eql(zmr)
+      @rc[1].should eql(resource_to_inject)
+      @rc[2].should eql(dummy)
+    end
+  end
+
+  describe "each" do
+    it "should allow you to iterate over every resource in the collection" do
+      load_up_resources
+      results = Array.new
+      lambda {
+        @rc.each do |r|
+          results << r.name
+        end
+      }.should_not raise_error
+      results.each_index do |i|
+        case i
+        when 0
+          results[i].should eql("dog")
+        when 1
+          results[i].should eql("cat")
+        when 2
+          results[i].should eql("monkey")
+        end
+      end
+    end
+  end
+
+  describe "each_index" do
+    it "should allow you to iterate over every resource by index" do
+      load_up_resources
+      results = Array.new
+      lambda {
+        @rc.each_index do |i|
+          results << @rc[i].name
+        end
+      }.should_not raise_error()
+      results.each_index do |i|
+        case i
+        when 0
+          results[i].should eql("dog")
+        when 1
+          results[i].should eql("cat")
+        when 2
+          results[i].should eql("monkey")
+        end
+      end
+    end
+  end
+
+  describe "lookup" do
+    it "should allow you to find resources by name via lookup" do
+      zmr = Chef::Resource::ZenMaster.new("dog")
+      @rc << zmr
+      @rc.lookup(zmr.to_s).should eql(zmr)
+
+      zmr = Chef::Resource::ZenMaster.new("cat")
+      @rc[0] = zmr
+      @rc.lookup(zmr).should eql(zmr)
+
+      zmr = Chef::Resource::ZenMaster.new("monkey")
+      @rc.push(zmr)
+      @rc.lookup(zmr).should eql(zmr)
+    end
+
+    it "should raise an exception if you send something strange to lookup" do
+      lambda { @rc.lookup(:symbol) }.should raise_error(ArgumentError)
+    end
+
+    it "should raise an exception if it cannot find a resource with lookup" do
+      lambda { @rc.lookup("zen_master[dog]") }.should raise_error(Chef::Exceptions::ResourceNotFound)
+    end
+  end
+
+  describe "resources" do
+
+    it "should find a resource by symbol and name (:zen_master => monkey)" do
+      load_up_resources
+      @rc.resources(:zen_master => "monkey").name.should eql("monkey")
+    end
+
+    it "should find a resource by symbol and array of names (:zen_master => [a,b])" do
+      load_up_resources
+      results = @rc.resources(:zen_master => [ "monkey", "dog" ])
+      results.length.should eql(2)
+      check_by_names(results, "monkey", "dog")
+    end
+
+    it "should find resources of multiple kinds (:zen_master => a, :file => b)" do
+      load_up_resources
+      results = @rc.resources(:zen_master => "monkey", :file => "something")
+      results.length.should eql(2)
+      check_by_names(results, "monkey", "something")
+    end
+
+    it "should find a resource by string zen_master[a]" do
+      load_up_resources
+      @rc.resources("zen_master[monkey]").name.should eql("monkey")
+    end
+
+    it "should find resources by strings of zen_master[a,b]" do
+      load_up_resources
+      results = @rc.resources("zen_master[monkey,dog]")
+      results.length.should eql(2)
+      check_by_names(results, "monkey", "dog")
+    end
+
+    it "should find resources of multiple types by strings of zen_master[a]" do
+      load_up_resources
+      results = @rc.resources("zen_master[monkey]", "file[something]")
+      results.length.should eql(2)
+      check_by_names(results, "monkey", "something")
+    end
+
+    it "should raise an exception if you pass a bad name to resources" do
+      lambda { @rc.resources("michael jackson") }.should raise_error(ArgumentError)
+    end
+
+    it "should raise an exception if you pass something other than a string or hash to resource" do
+      lambda { @rc.resources([Array.new]) }.should raise_error(ArgumentError)
+    end
+
+    it "raises an error when attempting to find a resource that does not exist" do
+      lambda {@rc.find("script[nonesuch]")}.should raise_error(Chef::Exceptions::ResourceNotFound)
+    end
+
+  end
+
+  describe "when validating a resource query object" do
+    it "accepts a string of the form 'resource_type[resource_name]'" do
+      @rc.validate_lookup_spec!("resource_type[resource_name]").should be_true
+    end
+
+    it "accepts a single-element :resource_type => 'resource_name' Hash" do
+      @rc.validate_lookup_spec!(:service => "apache2").should be_true
+    end
+
+
+    it "accepts a chef resource object" do
+      res = Chef::Resource.new("foo", nil)
+      @rc.validate_lookup_spec!(res).should be_true
+    end
+
+    it "rejects a malformed query string" do
+      lambda do
+        @rc.validate_lookup_spec!("resource_type[missing-end-bracket")
+      end.should raise_error(Chef::Exceptions::InvalidResourceSpecification)
+    end
+
+    it "rejects an argument that is not a String, Hash, or Chef::Resource" do
+      lambda do
+        @rc.validate_lookup_spec!(Object.new)
+      end.should raise_error(Chef::Exceptions::InvalidResourceSpecification)
+    end
+
+  end
+
+  describe "to_json" do
+    it "should serialize to json" do
+      json = @rc.to_json
+      json.should =~ /json_class/
+      json.should =~ /instance_vars/
+    end
+  end
+
+  describe "self.from_json" do
+    it "should deserialize itself from json" do
+      @rc << @resource
+      json = @rc.to_json
+      s_rc = Chef::JSONCompat.from_json(json)
+      s_rc.should be_a_kind_of(Chef::ResourceCollection)
+      s_rc[0].name.should eql(@resource.name)
+    end
+  end
+
+  describe "provides access to the raw resources array" do
+    it "returns the resources via the all_resources method" do
+      @rc.all_resources.should equal(@rc.instance_variable_get(:@resources))
+    end
+  end
+
+  describe "provides access to stepable iterator" do
+    it "returns the iterator object" do
+      @rc.instance_variable_set(:@iterator, :fooboar)
+      @rc.iterator.should == :fooboar
+    end
+  end
+
+  def check_by_names(results, *names)
+    names.each do |res_name|
+      results.detect{ |res| res.name == res_name }.should_not eql(nil)
+    end
+  end
+
+  def load_up_resources
+    %w{dog cat monkey}.each do |n|
+       @rc << Chef::Resource::ZenMaster.new(n)
+    end
+    @rc << Chef::Resource::File.new("something")
+  end
+
+end
diff --git a/spec/unit/resource_definition_spec.rb b/spec/unit/resource_definition_spec.rb
new file mode 100644
index 0000000..abac4c1
--- /dev/null
+++ b/spec/unit/resource_definition_spec.rb
@@ -0,0 +1,119 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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'
+
+describe Chef::ResourceDefinition do
+  before(:each) do
+    @def = Chef::ResourceDefinition.new()
+  end
+
+  describe "initialize" do
+    it "should be a Chef::ResourceDefinition" do
+      @def.should be_a_kind_of(Chef::ResourceDefinition)
+    end
+
+    it "should not initialize a new node if one is not provided" do
+      @def.node.should eql(nil)
+    end
+
+    it "should accept a node as an argument" do
+      node = Chef::Node.new
+      node.name("bobo")
+      @def = Chef::ResourceDefinition.new(node)
+      @def.node.name.should == "bobo"
+    end
+  end
+
+  describe "node" do
+    it "should set the node with node=" do
+      node = Chef::Node.new
+      node.name("bobo")
+      @def.node = node
+      @def.node.name.should == "bobo"
+    end
+
+    it "should return the node" do
+      @def.node = Chef::Node.new
+      @def.node.should be_a_kind_of(Chef::Node)
+    end
+  end
+
+  it "should accept a new definition with a symbol for a name" do
+    lambda {
+      @def.define :smoke do
+      end
+    }.should_not raise_error(ArgumentError)
+    lambda {
+      @def.define "george washington" do
+      end
+    }.should raise_error(ArgumentError)
+    @def.name.should eql(:smoke)
+  end
+
+  it "should accept a new definition with a hash" do
+    lambda {
+      @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do
+      end
+    }.should_not raise_error(ArgumentError)
+  end
+
+  it "should expose the prototype hash params in the params hash" do
+    @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do; end
+    @def.params[:cigar].should eql("cuban")
+    @def.params[:cigarette].should eql("marlboro")
+  end
+
+  it "should store the block passed to define as a proc under recipe" do
+    @def.define :smoke do
+      "I am what I am"
+    end
+    @def.recipe.should be_a_kind_of(Proc)
+    @def.recipe.call.should eql("I am what I am")
+  end
+
+  it "should set paramaters based on method_missing" do
+    @def.mind "to fly"
+    @def.params[:mind].should eql("to fly")
+  end
+
+  it "should raise an exception if prototype_params is not a hash" do
+    lambda {
+      @def.define :monkey, Array.new do
+      end
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should raise an exception if define is called without a block" do
+    lambda {
+      @def.define :monkey
+    }.should raise_error(ArgumentError)
+  end
+
+  it "should load a description from a file" do
+    @def.from_file(File.join(CHEF_SPEC_DATA, "definitions", "test.rb"))
+    @def.name.should eql(:rico_suave)
+    @def.params[:rich].should eql("smooth")
+  end
+
+  it "should turn itself into a string based on the name with to_s" do
+    @def.name = :woot
+    @def.to_s.should eql("woot")
+  end
+
+end
diff --git a/spec/unit/resource_platform_map_spec.rb b/spec/unit/resource_platform_map_spec.rb
new file mode 100644
index 0000000..99673d8
--- /dev/null
+++ b/spec/unit/resource_platform_map_spec.rb
@@ -0,0 +1,164 @@
+#
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::PlatformMap do
+
+  before(:each) do
+    @platform_map = Chef::Resource::PlatformMap.new({
+      :windows => {
+        "6.1" => {
+          :file => "softiefile",
+          :else => "thing"
+        },
+        :default => {
+          :file => Chef::Resource::File,
+          :ping => "pong",
+          :cat => "nice"
+        }
+      },
+      :pop_tron => {
+      },
+      :default => {
+        :soundwave => "lazerbeak",
+        :directory => Chef::Resource::Directory,
+      }
+    })
+  end
+
+  describe 'filtering the map' do
+    it "returns resources for platform and version" do
+      pmap = @platform_map.filter("Windows", "6.1")
+      pmap.should be_a_kind_of(Hash)
+      pmap[:file].should eql("softiefile")
+    end
+
+    it "returns platform default resources if version does not exist" do
+      pmap = @platform_map.filter("windows", "1")
+      pmap.should be_a_kind_of(Hash)
+      pmap[:file].should eql(Chef::Resource::File)
+    end
+
+    it "returns global default resources if none exist for plaform" do
+      pmap = @platform_map.filter("pop_tron", "1")
+      pmap.should be_a_kind_of(Hash)
+      pmap[:directory].should eql(Chef::Resource::Directory)
+    end
+
+    it "returns global default resources if platform does not exist" do
+      pmap = @platform_map.filter("BeOS", "1")
+      pmap.should be_a_kind_of(Hash)
+      pmap[:soundwave].should eql("lazerbeak")
+    end
+
+    it "returns a merged map of platform version and plaform default resources" do
+      pmap = @platform_map.filter("Windows", "6.1")
+      pmap[:file].should eql("softiefile")
+      pmap[:ping].should eql("pong")
+    end
+
+    it "returns a merged map of platform specific version and global defaults" do
+      pmap = @platform_map.filter("Windows", "6.1")
+      pmap[:file].should eql("softiefile")
+      pmap[:soundwave].should eql("lazerbeak")
+    end
+  end
+
+  describe 'finding a resource' do
+    it "returns a resource for a platform directly by short name" do
+      @platform_map.get(:file, "windows", "6.1").should eql("softiefile")
+    end
+
+    it "returns a default resource if platform and version don't exist" do
+      @platform_map.get(:remote_file).should eql(Chef::Resource::RemoteFile)
+    end
+
+    it "raises an exception if a resource cannot be found" do
+      lambda { @platform_map.get(:coffee, "windows", "6.1")}.should raise_error(NameError)
+    end
+
+    it "returns a resource with a Chef::Resource object" do
+      kitty = Chef::Resource::Cat.new("loulou")
+      @platform_map.get(kitty, "windows", "6.1").should eql("nice")
+    end
+  end
+
+  describe 'building the map' do
+    it "allows passing of a resource map at creation time" do
+      @new_map = Chef::Resource::PlatformMap.new({:the_dude => {:default => 'abides'}})
+      @new_map.map[:the_dude][:default].should eql("abides")
+    end
+
+    it "defaults to a resource map with :default key" do
+      @new_map = Chef::Resource::PlatformMap.new
+      @new_map.map.has_key?(:default)
+    end
+
+    it "updates the resource map with a map" do
+      @platform_map.set(
+       :platform => :darwin,
+       :version => "9.2.2",
+       :short_name => :file,
+       :resource => "masterful"
+      )
+      @platform_map.map[:darwin]["9.2.2"][:file].should eql("masterful")
+
+      @platform_map.set(
+       :platform => :darwin,
+       :short_name => :file,
+       :resource => "masterful"
+      )
+      @platform_map.map[:darwin][:default][:file].should eql("masterful")
+
+      @platform_map.set(
+       :short_name => :file,
+       :resource => "masterful"
+      )
+      @platform_map.map[:default][:file].should eql("masterful")
+
+      @platform_map.set(
+       :platform => :hero,
+       :version => "9.2.2",
+       :short_name => :file,
+       :resource => "masterful"
+      )
+      @platform_map.map[:hero]["9.2.2"][:file].should eql("masterful")
+
+      @platform_map.set(
+        :short_name => :file,
+        :resource => "masterful"
+      )
+      @platform_map.map[:default][:file].should eql("masterful")
+
+      @platform_map.set(
+       :short_name => :file,
+       :resource => "masterful"
+      )
+      @platform_map.map[:default][:file].should eql("masterful")
+
+      @platform_map.set(
+        :platform => :neurosis,
+        :short_name => :package,
+        :resource => "masterful"
+      )
+      @platform_map.map[:neurosis][:default][:package].should eql("masterful")
+    end
+  end
+
+end
diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb
new file mode 100644
index 0000000..e2ecde2
--- /dev/null
+++ b/spec/unit/resource_reporter_spec.rb
@@ -0,0 +1,631 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Author:: Prajakta Purohit (<prajakta at opscode.com>)
+# Author:: Tyler Cloke (<tyler 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 File.expand_path("../../spec_helper", __FILE__)
+require 'chef/resource_reporter'
+
+describe Chef::ResourceReporter do
+  before(:all) do
+    @reporting_toggle_default = Chef::Config[:enable_reporting]
+    Chef::Config[:enable_reporting] = true
+  end
+
+  after(:all) do
+    Chef::Config[:enable_reporting] = @reporting_toggle_default
+  end
+
+  before do
+    @node = Chef::Node.new
+    @node.name("spitfire")
+    @rest_client = mock("Chef::REST (mock)")
+    @rest_client.stub!(:post_rest).and_return(true)
+    @resource_reporter = Chef::ResourceReporter.new(@rest_client)
+    @run_id = @resource_reporter.run_id
+    @new_resource      = Chef::Resource::File.new("/tmp/a-file.txt")
+    @new_resource.cookbook_name = "monkey"
+    @cookbook_version = mock("Cookbook::Version", :version => "1.2.3")
+    @new_resource.stub!(:cookbook_version).and_return(@cookbook_version)
+    @current_resource  = Chef::Resource::File.new("/tmp/a-file.txt")
+    @start_time = Time.new
+    @end_time = Time.new + 20
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @run_status = Chef::RunStatus.new(@node, @events)
+    Time.stub!(:now).and_return(@start_time, @end_time)
+  end
+
+  context "when first created" do
+
+    it "has no updated resources" do
+      @resource_reporter.should have(0).updated_resources
+    end
+
+    it "reports a successful run" do
+      @resource_reporter.status.should == "success"
+    end
+
+    it "assumes the resource history feature is supported" do
+      @resource_reporter.reporting_enabled?.should be_true
+    end
+
+    it "should have no error_descriptions" do
+      @resource_reporter.error_descriptions.should eq({})
+      # @resource_reporter.error_descriptions.should be_empty
+      # @resource_reporter.should have(0).error_descriptions
+    end
+
+  end
+
+  context "after the chef run completes" do
+
+    before do
+    end
+
+    it "reports a successful run" do
+      pending "refactor how node gets set."
+      @resource_reporter.status.should == "success"
+    end
+  end
+
+  context "when chef fails" do
+    before do
+      @rest_client.stub!(:create_url).and_return("reports/nodes/spitfire/runs/#{@run_id}");
+      @rest_client.stub!(:raw_http_request).and_return({"result"=>"ok"});
+      @rest_client.stub!(:post_rest).and_return({"uri"=>"https://example.com/reports/nodes/spitfire/runs/#{@run_id}"});
+
+    end
+
+    context "before converging any resources" do
+      before do
+        @resource_reporter.run_started(@run_status)
+        @exception = Exception.new
+        @resource_reporter.run_failed(@exception)
+      end
+
+      it "sets the run status to 'failure'" do
+        @resource_reporter.status.should == "failure"
+      end
+
+      it "keeps the exception data" do
+        @resource_reporter.exception.should == @exception
+      end
+    end
+
+    context "when a resource fails before loading current state" do
+      before do
+        @exception = Exception.new
+        @exception.set_backtrace(caller)
+        @resource_reporter.resource_action_start(@new_resource, :create)
+        @resource_reporter.resource_failed(@new_resource, :create, @exception)
+        @resource_reporter.resource_completed(@new_resource)
+      end
+
+      it "collects the resource as an updated resource" do
+        @resource_reporter.should have(1).updated_resources
+      end
+
+      it "collects the desired state of the resource" do
+        update_record = @resource_reporter.updated_resources.first
+        update_record.new_resource.should == @new_resource
+      end
+    end
+
+    # TODO: make sure a resource that is skipped because of `not_if` doesn't
+    # leave us in a bad state.
+
+    context "once the a resource's current state is loaded" do
+      before do
+        @resource_reporter.resource_action_start(@new_resource, :create)
+        @resource_reporter.resource_current_state_loaded(@new_resource, :create, @current_resource)
+      end
+
+      context "and the resource was not updated" do
+        before do
+          @resource_reporter.resource_up_to_date(@new_resource, :create)
+        end
+
+        it "has no updated resources" do
+          @resource_reporter.should have(0).updated_resources
+        end
+      end
+
+      context "and the resource was updated" do
+        before do
+          @new_resource.content("this is the old content")
+          @current_resource.content("this is the new hotness")
+          @resource_reporter.resource_updated(@new_resource, :create)
+          @resource_reporter.resource_completed(@new_resource)
+        end
+
+        it "collects the updated resource" do
+          @resource_reporter.should have(1).updated_resources
+        end
+
+        it "collects the old state of the resource" do
+          update_record = @resource_reporter.updated_resources.first
+          update_record.current_resource.should == @current_resource
+        end
+
+        it "collects the new state of the resource" do
+          update_record = @resource_reporter.updated_resources.first
+          update_record.new_resource.should == @new_resource
+        end
+
+        context "and a subsequent resource fails before loading current resource" do
+          before do
+            @next_new_resource = Chef::Resource::Service.new("apache2")
+            @exception = Exception.new
+            @exception.set_backtrace(caller)
+            @resource_reporter.resource_failed(@next_new_resource, :create, @exception)
+            @resource_reporter.resource_completed(@next_new_resource)
+          end
+
+          it "collects the desired state of the failed resource" do
+            failed_resource_update = @resource_reporter.updated_resources.last
+            failed_resource_update.new_resource.should == @next_new_resource
+          end
+
+          it "does not have the current state of the failed resource" do
+            failed_resource_update = @resource_reporter.updated_resources.last
+            failed_resource_update.current_resource.should be_nil
+          end
+        end
+      end
+
+      # Some providers, such as RemoteDirectory and some LWRPs use other
+      # resources for their implementation. These should be hidden from reporting
+      # since we only care about the top-level resource and not the sub-resources
+      # used for implementation.
+      context "and a nested resource is updated" do
+        before do
+          @implementation_resource = Chef::Resource::CookbookFile.new("/preseed-file.txt")
+        @resource_reporter.resource_action_start(@implementation_resource , :create)
+        @resource_reporter.resource_current_state_loaded(@implementation_resource, :create, @implementation_resource)
+        @resource_reporter.resource_updated(@implementation_resource, :create)
+        @resource_reporter.resource_completed(@implementation_resource)
+        @resource_reporter.resource_updated(@new_resource, :create)
+        @resource_reporter.resource_completed(@new_resource)
+      end
+
+        it "does not collect data about the nested resource" do
+          @resource_reporter.should have(1).updated_resources
+        end
+      end
+
+      context "and a nested resource runs but is not updated" do
+        before do
+          @implementation_resource = Chef::Resource::CookbookFile.new("/preseed-file.txt")
+          @resource_reporter.resource_action_start(@implementation_resource , :create)
+          @resource_reporter.resource_current_state_loaded(@implementation_resource, :create, @implementation_resource)
+          @resource_reporter.resource_up_to_date(@implementation_resource, :create)
+          @resource_reporter.resource_completed(@implementation_resource)
+          @resource_reporter.resource_updated(@new_resource, :create)
+          @resource_reporter.resource_completed(@new_resource)
+        end
+
+        it "does not collect data about the nested resource" do
+          @resource_reporter.should have(1).updated_resources
+        end
+      end
+
+      context "and the resource failed to converge" do
+        before do
+          @exception = Exception.new
+          @exception.set_backtrace(caller)
+          @resource_reporter.resource_failed(@new_resource, :create, @exception)
+          @resource_reporter.resource_completed(@new_resource)
+        end
+
+        it "collects the resource as an updated resource" do
+          @resource_reporter.should have(1).updated_resources
+        end
+
+        it "collects the desired state of the resource" do
+          update_record = @resource_reporter.updated_resources.first
+          update_record.new_resource.should == @new_resource
+        end
+
+        it "collects the current state of the resource" do
+          update_record = @resource_reporter.updated_resources.first
+          update_record.current_resource.should == @current_resource
+        end
+      end
+
+    end
+  end
+
+  describe "when generating a report for the server" do
+
+    before do
+      @rest_client.stub!(:create_url).and_return("reports/nodes/spitfire/runs/#{@run_id}");
+      @rest_client.stub!(:raw_http_request).and_return({"result"=>"ok"});
+      @rest_client.stub!(:post_rest).and_return({"uri"=>"https://example.com/reports/nodes/spitfire/runs/#{@run_id}"});
+
+      @resource_reporter.run_started(@run_status)
+    end
+
+    context "for a successful client run" do
+      before do
+        # TODO: add inputs to generate expected output.
+
+        # expected_data = {
+        #    "action" : "end",
+        #    "resources" : [
+        #       {
+        #         "type" : "file",
+        #         "id" : "/etc/passwd",
+        #         "name" : "User Defined Resource Block Name",
+        #         "duration" : "1200",
+        #         "result" : "modified",
+        #         "before" : {
+        #              "state" : "exists",
+        #              "group" : "root",
+        #              "owner" : "root",
+        #              "checksum" : "xyz"
+        #         },
+        #         "after" : {
+        #              "state" : "modified",
+        #              "group" : "root",
+        #              "owner" : "root",
+        #              "checksum" : "abc"
+        #         },
+        #         "delta" : ""
+        #      },
+        #      {...}
+        #     ],
+        #    "status" : "success"
+        #    "data" : ""
+        # }
+        @resource_reporter.resource_action_start(@new_resource, :create)
+        @resource_reporter.resource_current_state_loaded(@new_resource, :create, @current_resource)
+        @resource_reporter.resource_updated(@new_resource, :create)
+        @resource_reporter.resource_completed(@new_resource)
+        @run_status.stop_clock
+        @report = @resource_reporter.prepare_run_data
+        @first_update_report = @report["resources"].first
+      end
+
+      it "includes the run's status" do
+        @report.should have_key("status")
+      end
+
+      it "includes a list of updated resources" do
+        @report.should have_key("resources")
+      end
+
+      it "includes an updated resource's type" do
+        @first_update_report.should have_key("type")
+      end
+
+      it "includes an updated resource's initial state" do
+        @first_update_report["before"].should == @current_resource.state
+      end
+
+      it "includes an updated resource's final state" do
+        @first_update_report["after"].should == @new_resource.state
+      end
+
+      it "includes the resource's name" do
+        @first_update_report["name"].should == @new_resource.name
+      end
+
+      it "includes the resource's id attribute" do
+        @first_update_report["id"].should == @new_resource.identity
+      end
+
+      it "includes the elapsed time for the resource to converge" do
+        # TODO: API takes integer number of milliseconds as a string. This
+        # should be an int.
+        @first_update_report.should have_key("duration")
+        @first_update_report["duration"].to_i.should be_within(100).of(0)
+      end
+
+      it "includes the action executed by the resource" do
+        # TODO: rename as "action"
+        @first_update_report["result"].should == "create"
+      end
+
+      it "includes the cookbook name of the resource" do
+        @first_update_report.should have_key("cookbook_name")
+        @first_update_report["cookbook_name"].should == "monkey"
+      end
+
+      it "includes the cookbook version of the resource" do
+        @first_update_report.should have_key("cookbook_version")
+        @first_update_report["cookbook_version"].should == "1.2.3"
+      end
+
+      it "includes the total resource count" do
+        @report.should have_key("total_res_count")
+        @report["total_res_count"].should == "1"
+      end
+
+      it "includes the data hash" do
+        @report.should have_key("data")
+        @report["data"].should == {}
+      end
+
+      it "includes the run_list" do
+        @report.should have_key("run_list")
+        @report["run_list"].should == @run_status.node.run_list.to_json
+      end
+
+      it "includes the end_time" do
+        @report.should have_key("end_time")
+        @report["end_time"].should == @run_status.end_time.to_s
+      end
+
+    end
+
+    context "for an unsuccessful run" do
+
+      before do
+        @backtrace = ["foo.rb:1 in `foo!'","bar.rb:2 in `bar!","'baz.rb:3 in `baz!'"]
+        @node = Chef::Node.new
+        @node.name("spitfire")
+        @exception = mock("ArgumentError")
+        @exception.stub!(:inspect).and_return("Net::HTTPServerException")
+        @exception.stub!(:message).and_return("Object not found")
+        @exception.stub!(:backtrace).and_return(@backtrace)
+        @resource_reporter.run_list_expand_failed(@node, @exception)
+        @resource_reporter.run_failed(@exception)
+        @report = @resource_reporter.prepare_run_data
+      end
+
+      it "includes the exception type in the event data" do
+        @report.should have_key("data")
+        @report["data"]["exception"].should have_key("class")
+        @report["data"]["exception"]["class"].should == "Net::HTTPServerException"
+      end
+
+      it "includes the exception message in the event data" do
+        @report["data"]["exception"].should have_key("message")
+        @report["data"]["exception"]["message"].should == "Object not found"
+      end
+
+      it "includes the exception trace in the event data" do
+        @report["data"]["exception"].should have_key("backtrace")
+        @report["data"]["exception"]["backtrace"].should == @backtrace.to_json
+      end
+
+      it "includes the error inspector output in the event data" do
+        @report["data"]["exception"].should have_key("description")
+        @report["data"]["exception"]["description"].should include({"title"=>"Error expanding the run_list:", "sections"=>[{"Unexpected Error:" => "RSpec::Mocks::Mock: Object not found"}]})
+      end
+
+    end
+
+    context "when new_resource does not have a cookbook_name" do
+      before do
+        @bad_resource = Chef::Resource::File.new("/tmp/a-file.txt")
+        @bad_resource.cookbook_name = nil
+
+        @resource_reporter.resource_action_start(@bad_resource, :create)
+        @resource_reporter.resource_current_state_loaded(@bad_resource, :create, @current_resource)
+        @resource_reporter.resource_updated(@bad_resource, :create)
+        @resource_reporter.resource_completed(@bad_resource)
+        @run_status.stop_clock
+        @report = @resource_reporter.prepare_run_data
+        @first_update_report = @report["resources"].first
+      end
+
+      it "includes an updated resource's initial state" do
+        @first_update_report["before"].should == @current_resource.state
+      end
+
+      it "includes an updated resource's final state" do
+        @first_update_report["after"].should == @new_resource.state
+      end
+
+      it "includes the resource's name" do
+        @first_update_report["name"].should == @new_resource.name
+      end
+
+      it "includes the resource's id attribute" do
+        @first_update_report["id"].should == @new_resource.identity
+      end
+
+      it "includes the elapsed time for the resource to converge" do
+        # TODO: API takes integer number of milliseconds as a string. This
+        # should be an int.
+        @first_update_report.should have_key("duration")
+        @first_update_report["duration"].to_i.should be_within(100).of(0)
+      end
+
+      it "includes the action executed by the resource" do
+        # TODO: rename as "action"
+        @first_update_report["result"].should == "create"
+      end
+
+      it "does not include a cookbook name for the resource" do
+        @first_update_report.should_not have_key("cookbook_name")
+      end
+
+      it "does not include a cookbook version for the resource" do
+        @first_update_report.should_not have_key("cookbook_version")
+      end
+    end
+
+    context "when including a resource that overrides Resource#state" do
+      before do
+        @current_state_resource = Chef::Resource::WithState.new("Stateful", @run_context)
+        @current_state_resource.state = nil
+
+        @new_state_resource = Chef::Resource::WithState.new("Stateful", @run_context)
+        @new_state_resource.state = "Running"
+        @resource_reporter.resource_action_start(@new_state_resource, :create)
+        @resource_reporter.resource_current_state_loaded(@new_state_resource, :create, @current_state_resource)
+        @resource_reporter.resource_updated(@new_state_resource, :create)
+        @resource_reporter.resource_completed(@new_state_resource)
+        @run_status.stop_clock
+        @report = @resource_reporter.prepare_run_data
+        @first_update_report = @report["resources"].first
+      end
+
+      it "sets before to {} instead of nil" do
+        @first_update_report.should have_key("before")
+        @first_update_report['before'].should eq({})
+      end
+
+      it "sets after to {} instead of 'Running'" do
+        @first_update_report.should have_key("after")
+        @first_update_report['after'].should eq({})
+      end
+    end
+
+  end
+
+  describe "when updating resource history on the server" do
+    before do
+      @resource_reporter.run_started(@run_status)
+      @run_status.start_clock
+    end
+
+    context "when the server does not support storing resource history" do
+      before do
+        # 404 getting the run_id
+        @response = Net::HTTPNotFound.new("a response body", "404", "Not Found")
+        @error = Net::HTTPServerException.new("404 message", @response)
+        @rest_client.should_receive(:post_rest).
+          with("reports/nodes/spitfire/runs", {:action => :start, :run_id => @run_id,
+                                               :start_time => @start_time.to_s},
+               {'X-Ops-Reporting-Protocol-Version' => Chef::ResourceReporter::PROTOCOL_VERSION}).
+          and_raise(@error)
+      end
+
+      it "assumes the feature is not enabled" do
+        @resource_reporter.run_started(@run_status)
+        @resource_reporter.reporting_enabled?.should be_false
+      end
+
+      it "does not send a resource report to the server" do
+        @resource_reporter.run_started(@run_status)
+        @rest_client.should_not_receive(:post_rest)
+        @resource_reporter.run_completed(@node)
+      end
+
+      it "prints an error about the 404" do
+        Chef::Log.should_receive(:debug).with(/404/)
+        @resource_reporter.run_started(@run_status)
+      end
+
+    end
+
+    context "when the server returns a 500 to the client" do
+      before do
+        # 500 getting the run_id
+        @response = Net::HTTPInternalServerError.new("a response body", "500", "Internal Server Error")
+        @error = Net::HTTPServerException.new("500 message", @response)
+        @rest_client.should_receive(:post_rest).
+          with("reports/nodes/spitfire/runs", {:action => :start, :run_id => @run_id, :start_time => @start_time.to_s},
+               {'X-Ops-Reporting-Protocol-Version' => Chef::ResourceReporter::PROTOCOL_VERSION}).
+          and_raise(@error)
+      end
+
+      it "assumes the feature is not enabled" do
+        @resource_reporter.run_started(@run_status)
+        @resource_reporter.reporting_enabled?.should be_false
+      end
+
+      it "does not send a resource report to the server" do
+        @resource_reporter.run_started(@run_status)
+        @rest_client.should_not_receive(:post_rest)
+        @resource_reporter.run_completed(@node)
+      end
+
+      it "prints an error about the error" do
+        Chef::Log.should_receive(:info).with(/500/)
+        @resource_reporter.run_started(@run_status)
+      end
+    end
+
+    context "when the server returns a 500 to the client and enable_reporting_url_fatals is true" do
+      before do
+        @enable_reporting_url_fatals = Chef::Config[:enable_reporting_url_fatals]
+        Chef::Config[:enable_reporting_url_fatals] = true
+        # 500 getting the run_id
+        @response = Net::HTTPInternalServerError.new("a response body", "500", "Internal Server Error")
+        @error = Net::HTTPServerException.new("500 message", @response)
+        @rest_client.should_receive(:post_rest).
+          with("reports/nodes/spitfire/runs", {:action => :start, :run_id => @run_id, :start_time => @start_time.to_s},
+               {'X-Ops-Reporting-Protocol-Version' => Chef::ResourceReporter::PROTOCOL_VERSION}).
+          and_raise(@error)
+      end
+
+      after do
+        Chef::Config[:enable_reporting_url_fatals] = @enable_reporting_url_fatals
+      end
+
+      it "fails the run and prints an message about the error" do
+        Chef::Log.should_receive(:error).with(/500/)
+        lambda {
+          @resource_reporter.run_started(@run_status)
+        }.should raise_error(Net::HTTPServerException)
+      end
+    end
+
+    context "after creating the run history document" do
+      before do
+        response = {"uri"=>"https://example.com/reports/nodes/spitfire/runs/@run_id"}
+        @rest_client.should_receive(:post_rest).
+          with("reports/nodes/spitfire/runs", {:action => :start, :run_id => @run_id, :start_time => @start_time.to_s},
+               {'X-Ops-Reporting-Protocol-Version' => Chef::ResourceReporter::PROTOCOL_VERSION}).
+          and_return(response)
+        @resource_reporter.run_started(@run_status)
+      end
+
+      it "creates a run document on the server at the start of the run" do
+        @resource_reporter.run_id.should == @run_id
+      end
+
+      it "updates the run document with resource updates at the end of the run" do
+        # update some resources...
+        @resource_reporter.resource_action_start(@new_resource, :create)
+        @resource_reporter.resource_current_state_loaded(@new_resource, :create, @current_resource)
+        @resource_reporter.resource_updated(@new_resource, :create)
+
+        @resource_reporter.stub!(:end_time).and_return(@end_time)
+        @expected_data = @resource_reporter.prepare_run_data
+
+        post_url = "https://chef_server/example_url"
+        response = {"result"=>"ok"}
+
+        @rest_client.should_receive(:create_url).
+          with("reports/nodes/spitfire/runs/#{@run_id}").
+          ordered.
+          and_return(post_url)
+        @rest_client.should_receive(:raw_http_request).ordered do |method, url, headers, data|
+          method.should eq(:POST)
+          url.should eq(post_url)
+          headers.should eq({'Content-Encoding' => 'gzip',
+                             'X-Ops-Reporting-Protocol-Version' => Chef::ResourceReporter::PROTOCOL_VERSION
+          })
+          data_stream = Zlib::GzipReader.new(StringIO.new(data))
+          data = data_stream.read
+          data.should eq(@expected_data.to_json)
+          response
+        end
+
+        @resource_reporter.run_completed(@node)
+      end
+    end
+  end
+end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
new file mode 100644
index 0000000..dd1a025
--- /dev/null
+++ b/spec/unit/resource_spec.rb
@@ -0,0 +1,913 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Author:: Seth Chisamore (<schisamo at opscode.com>)
+# Copyright:: Copyright (c) 2008-2011 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+class ResourceTestHarness < Chef::Resource
+  provider_base Chef::Provider::Package
+end
+
+describe Chef::Resource do
+  before(:each) do
+    @cookbook_repo_path =  File.join(CHEF_SPEC_DATA, 'cookbooks')
+    @cookbook_collection = Chef::CookbookCollection.new(Chef::CookbookLoader.new(@cookbook_repo_path))
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    @resource = Chef::Resource.new("funk", @run_context)
+  end
+
+  describe "when inherited" do
+
+    it "adds an entry to a list of subclasses" do
+      subclass = Class.new(Chef::Resource)
+      Chef::Resource.resource_classes.should include(subclass)
+    end
+
+    it "keeps track of subclasses of subclasses" do
+      subclass = Class.new(Chef::Resource)
+      subclass_of_subclass = Class.new(subclass)
+      Chef::Resource.resource_classes.should include(subclass_of_subclass)
+    end
+
+  end
+
+  describe "when declaring the identity attribute" do
+    it "has no identity attribute by default" do
+      Chef::Resource.identity_attr.should be_nil
+    end
+
+    it "sets an identity attribute" do
+      resource_class = Class.new(Chef::Resource)
+      resource_class.identity_attr(:path)
+      resource_class.identity_attr.should == :path
+    end
+
+    it "inherits an identity attribute from a superclass" do
+      resource_class = Class.new(Chef::Resource)
+      resource_subclass = Class.new(resource_class)
+      resource_class.identity_attr(:package_name)
+      resource_subclass.identity_attr.should == :package_name
+    end
+
+    it "overrides the identity attribute from a superclass when the identity attr is set" do
+      resource_class = Class.new(Chef::Resource)
+      resource_subclass = Class.new(resource_class)
+      resource_class.identity_attr(:package_name)
+      resource_subclass.identity_attr(:something_else)
+      resource_subclass.identity_attr.should == :something_else
+    end
+  end
+
+  describe "when no identity attribute has been declared" do
+    before do
+      @resource_sans_id = Chef::Resource.new("my-name")
+    end
+
+    # Would rather force identity attributes to be set for everything,
+    # but that's not plausible for back compat reasons.
+    it "uses the name as the identity" do
+      @resource_sans_id.identity.should == "my-name"
+    end
+  end
+
+  describe "when an identity attribute has been declared" do
+    before do
+      @file_resource_class = Class.new(Chef::Resource) do
+        identity_attr :path
+        attr_accessor :path
+      end
+
+      @file_resource = @file_resource_class.new("identity-attr-test")
+      @file_resource.path = "/tmp/foo.txt"
+    end
+
+    it "gives the value of its identity attribute" do
+      @file_resource.identity.should == "/tmp/foo.txt"
+    end
+  end
+
+  describe "when declaring state attributes" do
+    it "has no state_attrs by default" do
+      Chef::Resource.state_attrs.should be_empty
+    end
+
+    it "sets a list of state attributes" do
+      resource_class = Class.new(Chef::Resource)
+      resource_class.state_attrs(:checksum, :owner, :group, :mode)
+      resource_class.state_attrs.should =~ [:checksum, :owner, :group, :mode]
+    end
+
+    it "inherits state attributes from the superclass" do
+      resource_class = Class.new(Chef::Resource)
+      resource_subclass = Class.new(resource_class)
+      resource_class.state_attrs(:checksum, :owner, :group, :mode)
+      resource_subclass.state_attrs.should =~ [:checksum, :owner, :group, :mode]
+    end
+
+    it "combines inherited state attributes with non-inherited state attributes" do
+      resource_class = Class.new(Chef::Resource)
+      resource_subclass = Class.new(resource_class)
+      resource_class.state_attrs(:checksum, :owner)
+      resource_subclass.state_attrs(:group, :mode)
+      resource_subclass.state_attrs.should =~ [:checksum, :owner, :group, :mode]
+    end
+
+  end
+
+  describe "when a set of state attributes has been declared" do
+    before do
+      @file_resource_class = Class.new(Chef::Resource) do
+
+        state_attrs :checksum, :owner, :group, :mode
+
+        attr_accessor :checksum
+        attr_accessor :owner
+        attr_accessor :group
+        attr_accessor :mode
+      end
+
+      @file_resource = @file_resource_class.new("describe-state-test")
+      @file_resource.checksum = "abc123"
+      @file_resource.owner = "root"
+      @file_resource.group = "wheel"
+      @file_resource.mode = "0644"
+    end
+
+    it "describes its state" do
+      resource_state = @file_resource.state
+      resource_state.keys.should =~ [:checksum, :owner, :group, :mode]
+      resource_state[:checksum].should == "abc123"
+      resource_state[:owner].should == "root"
+      resource_state[:group].should == "wheel"
+      resource_state[:mode].should == "0644"
+    end
+  end
+
+  describe "load_prior_resource" do
+    before(:each) do
+      @prior_resource = Chef::Resource.new("funk")
+      @prior_resource.supports(:funky => true)
+      @prior_resource.source_line
+      @prior_resource.allowed_actions << :funkytown
+      @prior_resource.action(:funkytown)
+      @resource.allowed_actions << :funkytown
+      @run_context.resource_collection << @prior_resource
+    end
+
+    it "should load the attributes of a prior resource" do
+      @resource.load_prior_resource
+      @resource.supports.should == { :funky => true }
+    end
+
+    it "should not inherit the action from the prior resource" do
+      @resource.load_prior_resource
+      @resource.action.should_not == @prior_resource.action
+    end
+  end
+
+  describe "name" do
+    it "should have a name" do
+      @resource.name.should eql("funk")
+    end
+
+    it "should let you set a new name" do
+      @resource.name "monkey"
+      @resource.name.should eql("monkey")
+    end
+
+    it "should not be valid without a name" do
+      lambda { @resource.name false }.should raise_error(ArgumentError)
+    end
+
+    it "should always have a string for name" do
+      lambda { @resource.name Hash.new }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "noop" do
+    it "should accept true or false for noop" do
+      lambda { @resource.noop true }.should_not raise_error(ArgumentError)
+      lambda { @resource.noop false }.should_not raise_error(ArgumentError)
+      lambda { @resource.noop "eat it" }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "notifies" do
+    it "should make notified resources appear in the actions hash" do
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee")
+      @resource.delayed_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}.should_not be_nil
+    end
+
+    it "should make notified resources be capable of acting immediately" do
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee"), :immediate
+      @resource.immediate_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}.should_not be_nil
+    end
+
+    it "should raise an exception if told to act in other than :delay or :immediate(ly)" do
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      lambda {
+        @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee"), :someday
+      }.should raise_error(ArgumentError)
+    end
+
+    it "should allow multiple notified resources appear in the actions hash" do
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "coffee")
+      @resource.delayed_notifications.detect{|e| e.resource.name == "coffee" && e.action == :reload}.should_not be_nil
+
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("beans")
+      @resource.notifies :reload, @run_context.resource_collection.find(:zen_master => "beans")
+      @resource.delayed_notifications.detect{|e| e.resource.name == "beans" && e.action == :reload}.should_not be_nil
+    end
+
+    it "creates a notification for a resource that is not yet in the resource collection" do
+      @resource.notifies(:restart, :service => 'apache')
+      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, @resource)
+      @resource.delayed_notifications.should include(expected_notification)
+    end
+
+    it "notifies another resource immediately" do
+      @resource.notifies_immediately(:restart, :service => 'apache')
+      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, @resource)
+      @resource.immediate_notifications.should include(expected_notification)
+    end
+
+    it "notifies a resource to take action at the end of the chef run" do
+      @resource.notifies_delayed(:restart, :service => "apache")
+      expected_notification = Chef::Resource::Notification.new({:service => "apache"}, :restart, @resource)
+      @resource.delayed_notifications.should include(expected_notification)
+    end
+  end
+
+  describe "subscribes" do
+    it "should make resources appear in the actions hash of subscribed nodes" do
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      zr = @run_context.resource_collection.find(:zen_master => "coffee")
+      @resource.subscribes :reload, zr
+      zr.delayed_notifications.detect{|e| e.resource.name == "funk" && e.action == :reload}.should_not be_nil
+    end
+
+    it "should make resources appear in the actions hash of subscribed nodes" do
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      zr = @run_context.resource_collection.find(:zen_master => "coffee")
+      @resource.subscribes :reload, zr
+      zr.delayed_notifications.detect{|e| e.resource.name == @resource.name && e.action == :reload}.should_not be_nil
+
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("bean")
+      zrb = @run_context.resource_collection.find(:zen_master => "bean")
+      zrb.subscribes :reload, zr
+      zr.delayed_notifications.detect{|e| e.resource.name == @resource.name && e.action == :reload}.should_not be_nil
+    end
+
+    it "should make subscribed resources be capable of acting immediately" do
+      @run_context.resource_collection << Chef::Resource::ZenMaster.new("coffee")
+      zr = @run_context.resource_collection.find(:zen_master => "coffee")
+      @resource.subscribes :reload, zr, :immediately
+      zr.immediate_notifications.detect{|e| e.resource.name == @resource.name && e.action == :reload}.should_not be_nil
+    end
+  end
+
+  describe "defined_at" do
+    it "should correctly parse source_line on unix-like operating systems" do
+      @resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'"
+      @resource.defined_at.should == "/some/path/to/file.rb line 80"
+    end
+
+    it "should correctly parse source_line on Windows" do
+      @resource.source_line = "C:/some/path/to/file.rb:80 in 1`wombat_tears'"
+      @resource.defined_at.should == "C:/some/path/to/file.rb line 80"
+    end
+
+    it "should include the cookbook and recipe when it knows it" do
+      @resource.source_line = "/some/path/to/file.rb:80:in `wombat_tears'"
+      @resource.recipe_name = "wombats"
+      @resource.cookbook_name = "animals"
+      @resource.defined_at.should == "animals::wombats line 80"
+    end
+
+    it "should recognize dynamically defined resources" do
+      @resource.defined_at.should == "dynamically defined"
+    end
+  end
+
+  describe "to_s" do
+    it "should become a string like resource_name[name]" do
+      zm = Chef::Resource::ZenMaster.new("coffee")
+      zm.to_s.should eql("zen_master[coffee]")
+    end
+  end
+
+  describe "is" do
+    it "should return the arguments passed with 'is'" do
+      zm = Chef::Resource::ZenMaster.new("coffee")
+      zm.is("one", "two", "three").should == %w|one two three|
+    end
+
+    it "should allow arguments preceeded by is to methods" do
+      @resource.noop(@resource.is(true))
+      @resource.noop.should eql(true)
+    end
+  end
+
+  describe "to_json" do
+    it "should serialize to json" do
+      json = @resource.to_json
+      json.should =~ /json_class/
+      json.should =~ /instance_vars/
+    end
+  end
+
+  describe "to_hash" do
+    it "should convert to a hash" do
+      hash = @resource.to_hash
+      expected_keys = [ :allowed_actions, :params, :provider, :updated,
+        :updated_by_last_action, :before, :supports,
+        :noop, :ignore_failure, :name, :source_line,
+        :action, :retries, :retry_delay, :elapsed_time]
+      (hash.keys - expected_keys).should == []
+      (expected_keys - hash.keys).should == []
+      hash[:name].should eql("funk")
+    end
+  end
+
+  describe "self.json_create" do
+    it "should deserialize itself from json" do
+      json = @resource.to_json
+      serialized_node = Chef::JSONCompat.from_json(json)
+      serialized_node.should be_a_kind_of(Chef::Resource)
+      serialized_node.name.should eql(@resource.name)
+    end
+  end
+
+  describe "supports" do
+    it "should allow you to set what features this resource supports" do
+      support_hash = { :one => :two }
+      @resource.supports(support_hash)
+      @resource.supports.should eql(support_hash)
+    end
+
+    it "should return the current value of supports" do
+      @resource.supports.should == {}
+    end
+  end
+
+  describe "ignore_failure" do
+    it "should default to throwing an error if a provider fails for a resource" do
+      @resource.ignore_failure.should == false
+    end
+
+    it "should allow you to set whether a provider should throw exceptions with ignore_failure" do
+      @resource.ignore_failure(true)
+      @resource.ignore_failure.should == true
+    end
+
+    it "should allow you to epic_fail" do
+      @resource.epic_fail(true)
+      @resource.epic_fail.should == true
+    end
+  end
+
+  describe "retries" do
+    it "should default to not retrying if a provider fails for a resource" do
+      @resource.retries.should == 0
+    end
+
+    it "should allow you to set how many retries a provider should attempt after a failure" do
+      @resource.retries(2)
+      @resource.retries.should == 2
+    end
+
+    it "should default to a retry delay of 2 seconds" do
+      @resource.retry_delay.should == 2
+    end
+
+    it "should allow you to set the retry delay" do
+      @resource.retry_delay(10)
+      @resource.retry_delay.should == 10
+    end
+  end
+
+  describe "setting the base provider class for the resource" do
+
+    it "defaults to Chef::Provider for the base class" do
+      Chef::Resource.provider_base.should == Chef::Provider
+    end
+
+    it "allows the base provider to be overriden by a " do
+      ResourceTestHarness.provider_base.should == Chef::Provider::Package
+    end
+
+  end
+
+  it "supports accessing the node via the @node instance variable [DEPRECATED]" do
+    @resource.instance_variable_get(:@node).inspect.should == @node.inspect
+  end
+
+  it "runs an action by finding its provider, loading the current resource and then running the action" do
+    pending
+  end
+
+  describe "when updated by a provider" do
+    before do
+      @resource.updated_by_last_action(true)
+    end
+
+    it "records that it was updated" do
+      @resource.should be_updated
+    end
+
+    it "records that the last action updated the resource" do
+      @resource.should be_updated_by_last_action
+    end
+
+    describe "and then run again without being updated" do
+      before do
+        @resource.updated_by_last_action(false)
+      end
+
+      it "reports that it is updated" do
+        @resource.should be_updated
+      end
+
+      it "reports that it was not updated by the last action" do
+        @resource.should_not be_updated_by_last_action
+      end
+
+    end
+
+  end
+
+  describe "when invoking its action" do
+    before do
+      @resource = Chef::Resource.new("provided", @run_context)
+      @resource.provider = Chef::Provider::SnakeOil
+      @node.automatic_attrs[:platform] = "fubuntu"
+      @node.automatic_attrs[:platform_version] = '10.04'
+    end
+
+    it "does not run only_if if no only_if command is given" do
+      @resource.not_if.clear
+      @resource.run_action(:purr)
+    end
+
+    it "runs runs an only_if when one is given" do
+      snitch_variable = nil
+      @resource.only_if { snitch_variable = true }
+      @resource.only_if.first.positivity.should == :only_if
+      #Chef::Mixin::Command.should_receive(:only_if).with(true, {}).and_return(false)
+      @resource.run_action(:purr)
+      snitch_variable.should be_true
+    end
+
+    it "runs multiple only_if conditionals" do
+      snitch_var1, snitch_var2 = nil, nil
+      @resource.only_if { snitch_var1 = 1 }
+      @resource.only_if { snitch_var2 = 2 }
+      @resource.run_action(:purr)
+      snitch_var1.should == 1
+      snitch_var2.should == 2
+    end
+
+    it "accepts command options for only_if conditionals" do
+      Chef::Resource::Conditional.any_instance.should_receive(:evaluate_command).at_least(1).times
+      @resource.only_if("true", :cwd => '/tmp')
+      @resource.only_if.first.command_opts.should == {:cwd => '/tmp'}
+      @resource.run_action(:purr)
+    end
+
+    it "runs not_if as a command when it is a string" do
+      Chef::Resource::Conditional.any_instance.should_receive(:evaluate_command).at_least(1).times
+      @resource.not_if "pwd"
+      @resource.run_action(:purr)
+    end
+
+    it "runs not_if as a block when it is a ruby block" do
+      Chef::Resource::Conditional.any_instance.should_receive(:evaluate_block).at_least(1).times
+      @resource.not_if { puts 'foo' }
+      @resource.run_action(:purr)
+    end
+
+    it "does not run not_if if no not_if command is given" do
+      @resource.run_action(:purr)
+    end
+
+    it "accepts command options for not_if conditionals" do
+      @resource.not_if("pwd" , :cwd => '/tmp')
+      @resource.not_if.first.command_opts.should == {:cwd => '/tmp'}
+    end
+
+    it "accepts multiple not_if conditionals" do
+      snitch_var1, snitch_var2 = true, true
+      @resource.not_if {snitch_var1 = nil}
+      @resource.not_if {snitch_var2 = false}
+      @resource.run_action(:purr)
+      snitch_var1.should be_nil
+      snitch_var2.should be_false
+    end
+
+  end
+
+  describe "should_skip?" do
+    before do
+      @resource = Chef::Resource::Cat.new("sugar", @run_context)
+    end
+
+    it "should return false by default" do
+      @resource.should_skip?(:purr).should be_false
+    end
+
+    it "should return false when if_only is met" do
+      @resource.only_if { true }
+      @resource.should_skip?(:purr).should be_false
+    end
+
+    it "should return true when if_only is not met" do
+      @resource.only_if { false }
+      @resource.should_skip?(:purr).should be_true
+    end
+
+    it "should return true when not_if is met" do
+      @resource.not_if { true }
+      @resource.should_skip?(:purr).should be_true
+    end
+
+    it "should return false when if_only is not met" do
+      @resource.not_if { false }
+      @resource.should_skip?(:purr).should be_false
+    end
+
+    it "should return true when if_only is met but also not_if is met" do
+      @resource.only_if { true }
+      @resource.not_if { true }
+      @resource.should_skip?(:purr).should be_true
+    end
+
+    it "should return true when one of multiple if_only's is not met" do
+      @resource.only_if { true }
+      @resource.only_if { false }
+      @resource.only_if { true }
+      @resource.should_skip?(:purr).should be_true
+    end
+
+    it "should return true when one of multiple not_if's is met" do
+      @resource.not_if { false }
+      @resource.not_if { true }
+      @resource.not_if { false }
+      @resource.should_skip?(:purr).should be_true
+    end
+
+    it "should return true when action is :nothing" do
+      @resource.should_skip?(:nothing).should be_true
+    end
+
+    it "should return true when action is :nothing ignoring only_if/not_if conditionals" do
+      @resource.only_if { true }
+      @resource.not_if { false }
+      @resource.should_skip?(:nothing).should be_true
+    end
+
+    it "should print \"skipped due to action :nothing\" message for doc formatter when action is :nothing" do
+      fdoc = Chef::Formatters.new(:doc, STDOUT, STDERR)
+      @run_context.stub!(:events).and_return(fdoc)
+      fdoc.should_receive(:puts).with(" (skipped due to action :nothing)")
+      @resource.should_skip?(:nothing)
+    end
+
+  end
+
+  describe "when resource action is :nothing" do
+    before do
+      @resource1 = Chef::Resource::Cat.new("sugar", @run_context)
+      @resource1.action = :nothing
+
+      @node.automatic_attrs[:platform] = "fubuntu"
+      @node.automatic_attrs[:platform_version] = '10.04'
+    end
+
+    it "should not run only_if/not_if conditionals (CHEF-972)" do
+      snitch_var1 = 0
+      @resource1.only_if { snitch_var1 = 1 }
+      @resource1.not_if { snitch_var1 = 2 }
+      @resource1.run_action(:nothing)
+      snitch_var1.should == 0
+    end
+
+    it "should run only_if/not_if conditionals when notified to run another action (CHEF-972)" do
+      snitch_var1 = snitch_var2 = 0
+      @runner = Chef::Runner.new(@run_context)
+      Chef::Platform.set(
+        :resource => :cat,
+        :provider => Chef::Provider::SnakeOil
+      )
+
+      @resource1.only_if { snitch_var1 = 1 }
+      @resource1.not_if { snitch_var2 = 2 }
+      @resource2 = Chef::Resource::Cat.new("coffee", @run_context)
+      @resource2.notifies :purr, @resource1
+      @resource2.action = :purr
+
+      @run_context.resource_collection << @resource1
+      @run_context.resource_collection << @resource2
+      @runner.converge
+
+      snitch_var1.should == 1
+      snitch_var2.should == 2
+    end
+  end
+
+  describe "building the platform map" do
+
+    it 'adds mappings for a single platform' do
+      klz = Class.new(Chef::Resource)
+      Chef::Resource.platform_map.should_receive(:set).with(
+        :platform => :autobots, :short_name => :dinobot, :resource => klz
+      )
+      klz.provides :dinobot, :on_platforms => ['autobots']
+    end
+
+    it 'adds mappings for multiple platforms' do
+      klz = Class.new(Chef::Resource)
+      Chef::Resource.platform_map.should_receive(:set).twice
+      klz.provides :energy, :on_platforms => ['autobots','decepticons']
+    end
+
+    it 'adds mappings for all platforms' do
+      klz = Class.new(Chef::Resource)
+      Chef::Resource.platform_map.should_receive(:set).with(
+        :short_name => :tape_deck, :resource => klz
+      )
+      klz.provides :tape_deck
+    end
+
+  end
+
+  describe "lookups from the platform map" do
+
+    before(:each) do
+      @node = Chef::Node.new
+      @node.name("bumblebee")
+      @node.automatic[:platform] = "autobots"
+      @node.automatic[:platform_version] = "6.1"
+      Object.const_set('Soundwave', Class.new(Chef::Resource))
+      Object.const_set('Grimlock', Class.new(Chef::Resource){ provides :dinobot, :on_platforms => ['autobots'] })
+    end
+
+    after(:each) do
+      Object.send(:remove_const, :Soundwave)
+      Object.send(:remove_const, :Grimlock)
+    end
+
+    describe "resource_for_platform" do
+      it 'return a resource by short_name and platform' do
+        Chef::Resource.resource_for_platform(:dinobot,'autobots','6.1').should eql(Grimlock)
+      end
+      it "returns a resource by short_name if nothing else matches" do
+        Chef::Resource.resource_for_node(:soundwave, @node).should eql(Soundwave)
+      end
+    end
+
+    describe "resource_for_node" do
+      it "returns a resource by short_name and node" do
+        Chef::Resource.resource_for_node(:dinobot, @node).should eql(Grimlock)
+      end
+      it "returns a resource by short_name if nothing else matches" do
+        Chef::Resource.resource_for_node(:soundwave, @node).should eql(Soundwave)
+      end
+    end
+
+  end
+
+  describe "when creating notifications" do
+
+    describe "with a string resource spec" do
+
+      it "creates a delayed notification when timing is not specified" do
+        @resource.notifies(:run, "execute[foo]")
+        @run_context.delayed_notification_collection.should have(1).notifications
+      end
+
+      it "creates a delayed notification when :delayed is not specified" do
+        @resource.notifies(:run, "execute[foo]", :delayed)
+        @run_context.delayed_notification_collection.should have(1).notifications
+      end
+
+      it "creates an immediate notification when :immediate is specified" do
+        @resource.notifies(:run, "execute[foo]", :immediate)
+        @run_context.immediate_notification_collection.should have(1).notifications
+      end
+
+      it "creates an immediate notification when :immediately is specified" do
+        @resource.notifies(:run, "execute[foo]", :immediately)
+        @run_context.immediate_notification_collection.should have(1).notifications
+      end
+
+      describe "with a syntax error in the resource spec" do
+
+        it "raises an exception immmediately" do
+          lambda do
+            @resource.notifies(:run, "typo[missing-closing-bracket")
+          end.should raise_error(Chef::Exceptions::InvalidResourceSpecification)
+        end
+      end
+    end
+
+    describe "with a resource reference" do
+      before do
+        @notified_resource = Chef::Resource.new("punk", @run_context)
+      end
+
+      it "creates a delayed notification when timing is not specified" do
+        @resource.notifies(:run, @notified_resource)
+        @run_context.delayed_notification_collection.should have(1).notifications
+      end
+
+      it "creates a delayed notification when :delayed is not specified" do
+        @resource.notifies(:run, @notified_resource, :delayed)
+        @run_context.delayed_notification_collection.should have(1).notifications
+      end
+
+      it "creates an immediate notification when :immediate is specified" do
+        @resource.notifies(:run, @notified_resource, :immediate)
+        @run_context.immediate_notification_collection.should have(1).notifications
+      end
+
+      it "creates an immediate notification when :immediately is specified" do
+        @resource.notifies(:run, @notified_resource, :immediately)
+        @run_context.immediate_notification_collection.should have(1).notifications
+      end
+    end
+
+  end
+end
+
+describe Chef::Resource::Notification do
+  before do
+    @notification = Chef::Resource::Notification.new(:service_apache, :restart, :template_httpd_conf)
+  end
+
+  it "has a resource to be notified" do
+    @notification.resource.should == :service_apache
+  end
+
+  it "has an action to take on the service" do
+    @notification.action.should == :restart
+  end
+
+  it "has a notifying resource" do
+    @notification.notifying_resource.should == :template_httpd_conf
+  end
+
+  it "is a duplicate of another notification with the same target resource and action" do
+    other = Chef::Resource::Notification.new(:service_apache, :restart, :sync_web_app_code)
+    @notification.duplicates?(other).should be_true
+  end
+
+  it "is not a duplicate of another notification if the actions differ" do
+    other = Chef::Resource::Notification.new(:service_apache, :enable, :install_apache)
+    @notification.duplicates?(other).should be_false
+  end
+
+  it "is not a duplicate of another notification if the target resources differ" do
+    other = Chef::Resource::Notification.new(:service_sshd, :restart, :template_httpd_conf)
+    @notification.duplicates?(other).should be_false
+  end
+
+  it "raises an ArgumentError if you try to check a non-ducktype object for duplication" do
+    lambda {@notification.duplicates?(:not_a_notification)}.should raise_error(ArgumentError)
+  end
+
+  it "takes no action to resolve a resource reference that doesn't need to be resolved" do
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @notification.resource = @keyboard_cat
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @notification.notifying_resource = @long_cat
+    @resource_collection = Chef::ResourceCollection.new
+    # would raise an error since the resource is not in the collection
+    @notification.resolve_resource_reference(@resource_collection)
+    @notification.resource.should == @keyboard_cat
+  end
+
+  it "resolves a lazy reference to a resource" do
+    @notification.resource = {:cat => "keyboard_cat"}
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @keyboard_cat
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @notification.notifying_resource = @long_cat
+    @notification.resolve_resource_reference(@resource_collection)
+    @notification.resource.should == @keyboard_cat
+  end
+
+  it "resolves a lazy reference to its notifying resource" do
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @notification.resource = @keyboard_cat
+    @notification.notifying_resource = {:cat => "long_cat"}
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @long_cat
+    @notification.resolve_resource_reference(@resource_collection)
+    @notification.notifying_resource.should == @long_cat
+  end
+
+  it "resolves lazy references to both its resource and its notifying resource" do
+    @notification.resource = {:cat => "keyboard_cat"}
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @keyboard_cat
+    @notification.notifying_resource = {:cat => "long_cat"}
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @resource_collection << @long_cat
+    @notification.resolve_resource_reference(@resource_collection)
+    @notification.resource.should == @keyboard_cat
+    @notification.notifying_resource.should == @long_cat
+  end
+
+  it "raises a RuntimeError if you try to reference multiple resources" do
+    @notification.resource = {:cat => ["keyboard_cat", "cheez_cat"]}
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @keyboard_cat
+    @resource_collection << @cheez_cat
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @notification.notifying_resource = @long_cat
+    lambda {@notification.resolve_resource_reference(@resource_collection)}.should raise_error(RuntimeError)
+  end
+
+  it "raises a RuntimeError if you try to reference multiple notifying resources" do
+    @notification.notifying_resource = {:cat => ["long_cat", "cheez_cat"]}
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @long_cat
+    @resource_collection << @cheez_cat
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @notification.resource = @keyboard_cat
+    lambda {@notification.resolve_resource_reference(@resource_collection)}.should raise_error(RuntimeError)
+  end
+
+  it "raises a RuntimeError if it can't find a resource in the resource collection when resolving a lazy reference" do
+    @notification.resource = {:cat => "keyboard_cat"}
+    @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @cheez_cat
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @notification.notifying_resource = @long_cat
+    lambda {@notification.resolve_resource_reference(@resource_collection)}.should raise_error(RuntimeError)
+  end
+
+  it "raises a RuntimeError if it can't find a notifying resource in the resource collection when resolving a lazy reference" do
+    @notification.notifying_resource = {:cat => "long_cat"}
+    @cheez_cat = Chef::Resource::Cat.new("cheez_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @cheez_cat
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @notification.resource = @keyboard_cat
+    lambda {@notification.resolve_resource_reference(@resource_collection)}.should raise_error(RuntimeError)
+  end
+
+  it "raises an ArgumentError if improper syntax is used in the lazy reference to its resource" do
+    @notification.resource = "cat => keyboard_cat"
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @keyboard_cat
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @notification.notifying_resource = @long_cat
+    lambda {@notification.resolve_resource_reference(@resource_collection)}.should raise_error(ArgumentError)
+  end
+
+  it "raises an ArgumentError if improper syntax is used in the lazy reference to its notifying resource" do
+    @notification.notifying_resource = "cat => long_cat"
+    @long_cat = Chef::Resource::Cat.new("long_cat")
+    @resource_collection = Chef::ResourceCollection.new
+    @resource_collection << @long_cat
+    @keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @notification.resource = @keyboard_cat
+    lambda {@notification.resolve_resource_reference(@resource_collection)}.should raise_error(ArgumentError)
+  end
+
+  # Create test to resolve lazy references to both notifying resource and dest. resource
+  # Create tests to check proper error raising
+
+end
diff --git a/spec/unit/rest/auth_credentials_spec.rb b/spec/unit/rest/auth_credentials_spec.rb
new file mode 100644
index 0000000..7aa68de
--- /dev/null
+++ b/spec/unit/rest/auth_credentials_spec.rb
@@ -0,0 +1,325 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'uri'
+require 'net/https'
+
+KEY_DOT_PEM=<<-END_RSA_KEY
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh
+8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy
+YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei
+PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A
+O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x
+PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD
+2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk
+WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP
+g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa
+Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ
+I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/
+/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR
+xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO
+ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy
+bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A
+s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4
+DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz
+dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv
+GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq
+qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8
+OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R
+b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I
+YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12
+2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo
+Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
+-----END RSA PRIVATE KEY-----
+  END_RSA_KEY
+
+
+describe Chef::REST::AuthCredentials do
+  before do
+    @key_file_fixture = CHEF_SPEC_DATA + '/ssl/private_key.pem'
+    @key = OpenSSL::PKey::RSA.new(IO.read(@key_file_fixture).strip)
+    @auth_credentials = Chef::REST::AuthCredentials.new("client-name", @key)
+  end
+
+  it "has a client name" do
+    @auth_credentials.client_name.should == "client-name"
+  end
+
+  it "loads the private key when initialized with the path to the key" do
+    @auth_credentials.key.should respond_to(:private_encrypt)
+    @auth_credentials.key.to_s.should == KEY_DOT_PEM
+  end
+
+  describe "when loading the private key" do
+    it "strips extra whitespace before checking the key" do
+      key_file_fixture = CHEF_SPEC_DATA + '/ssl/private_key_with_whitespace.pem'
+      lambda {Chef::REST::AuthCredentials.new("client-name", @key_file_fixture)}.should_not raise_error
+    end
+  end
+
+  describe "generating signature headers for a request" do
+    before do
+      @request_time = Time.at(1270920860)
+      @request_params = {:http_method => :POST, :path => "/clients", :body => '{"some":"json"}', :host => "localhost"}
+    end
+
+    it "generates signature headers for the request" do
+      Time.stub!(:now).and_return(@request_time)
+      actual = @auth_credentials.signature_headers(@request_params)
+      actual["HOST"].should                    == "localhost"
+      actual["X-OPS-AUTHORIZATION-1"].should == "kBssX1ENEwKtNYFrHElN9vYGWS7OeowepN9EsYc9csWfh8oUovryPKDxytQ/"
+      actual["X-OPS-AUTHORIZATION-2"].should == "Wc2/nSSyxdWJjjfHzrE+YrqNQTaArOA7JkAf5p75eTUonCWcvNPjFrZVgKGS"
+      actual["X-OPS-AUTHORIZATION-3"].should == "yhzHJQh+lcVA9wwARg5Hu9q+ddS8xBOdm3Vp5atl5NGHiP0loiigMYvAvzPO"
+      actual["X-OPS-AUTHORIZATION-4"].should == "r9853eIxwYMhn5hLGhAGFQznJbE8+7F/lLU5Zmk2t2MlPY8q3o1Q61YD8QiJ"
+      actual["X-OPS-AUTHORIZATION-5"].should ==  "M8lIt53ckMyUmSU0DDURoiXLVkE9mag/6Yq2tPNzWq2AdFvBqku9h2w+DY5k"
+      actual["X-OPS-AUTHORIZATION-6"].should == "qA5Rnzw5rPpp3nrWA9jKkPw4Wq3+4ufO2Xs6w7GCjA=="
+      actual["X-OPS-CONTENT-HASH"].should == "1tuzs5XKztM1ANrkGNPah6rW9GY="
+      actual["X-OPS-SIGN"].should         =~ %r{(version=1\.0)|(algorithm=sha1;version=1.0;)}
+      actual["X-OPS-TIMESTAMP"].should    == "2010-04-10T17:34:20Z"
+      actual["X-OPS-USERID"].should       == "client-name"
+
+    end
+
+    describe "when configured for version 1.1 of the authn protocol" do
+      before do
+        Chef::Config[:authentication_protocol_version] = "1.1"
+      end
+
+      after do
+        Chef::Config[:authentication_protocol_version] = "1.0"
+      end
+
+      it "generates the correct signature for version 1.1" do
+        Time.stub!(:now).and_return(@request_time)
+        actual = @auth_credentials.signature_headers(@request_params)
+        actual["HOST"].should                    == "localhost"
+        actual["X-OPS-CONTENT-HASH"].should == "1tuzs5XKztM1ANrkGNPah6rW9GY="
+        actual["X-OPS-SIGN"].should         == "algorithm=sha1;version=1.1;"
+        actual["X-OPS-TIMESTAMP"].should    == "2010-04-10T17:34:20Z"
+        actual["X-OPS-USERID"].should       == "client-name"
+
+        # mixlib-authN will test the actual signature stuff for each version of
+        # the protocol so we won't test it again here.
+      end
+    end
+  end
+end
+
+describe Chef::REST::RESTRequest do
+  def new_request(method=nil)
+    method ||= :POST
+    Chef::REST::RESTRequest.new(method, @url, @req_body, @headers)
+  end
+
+  before do
+    @auth_credentials = Chef::REST::AuthCredentials.new("client-name", CHEF_SPEC_DATA + '/ssl/private_key.pem')
+    @url = URI.parse("http://chef.example.com:4000/?q=chef_is_awesome")
+    @req_body = '{"json_data":"as_a_string"}'
+    @headers = {"Content-type" =>"application/json", "Accept"=>"application/json", "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE}
+    @request = Chef::REST::RESTRequest.new(:POST, @url, @req_body, @headers)
+  end
+
+  it "stores the url it was created with" do
+    @request.url.should == @url
+  end
+
+  it "stores the HTTP method" do
+    @request.method.should == :POST
+  end
+
+  it "adds the chef version header" do
+    @request.headers.should == @headers.merge("X-Chef-Version" => ::Chef::VERSION)
+  end
+
+  describe "configuring the HTTP request" do
+    it "configures GET requests" do
+      @req_body = nil
+      rest_req = new_request(:GET)
+      rest_req.http_request.should be_a_kind_of(Net::HTTP::Get)
+      rest_req.http_request.path.should == "/?q=chef_is_awesome"
+      rest_req.http_request.body.should be_nil
+    end
+
+    it "configures POST requests, including the body" do
+      @request.http_request.should be_a_kind_of(Net::HTTP::Post)
+      @request.http_request.path.should == "/?q=chef_is_awesome"
+      @request.http_request.body.should == @req_body
+    end
+
+    it "configures PUT requests, including the body" do
+      rest_req = new_request(:PUT)
+      rest_req.http_request.should be_a_kind_of(Net::HTTP::Put)
+      rest_req.http_request.path.should == "/?q=chef_is_awesome"
+      rest_req.http_request.body.should == @req_body
+    end
+
+    it "configures DELETE requests" do
+      rest_req = new_request(:DELETE)
+      rest_req.http_request.should be_a_kind_of(Net::HTTP::Delete)
+      rest_req.http_request.path.should == "/?q=chef_is_awesome"
+      rest_req.http_request.body.should be_nil
+    end
+
+    it "configures HTTP basic auth" do
+      @url = URI.parse("http://homie:theclown@chef.example.com:4000/?q=chef_is_awesome")
+      rest_req = new_request(:GET)
+      rest_req.http_request.to_hash["authorization"].should == ["Basic aG9taWU6dGhlY2xvd24="]
+    end
+  end
+
+  describe "configuring the HTTP client" do
+    it "configures the HTTP client for the host and port" do
+      http_client = new_request.http_client
+      http_client.address.should == "chef.example.com"
+      http_client.port.should == 4000
+    end
+
+    it "configures the HTTP client with the read timeout set in the config file" do
+      Chef::Config[:rest_timeout] = 9001
+      new_request.http_client.read_timeout.should == 9001
+    end
+
+    describe "for proxy" do
+      before do
+        Chef::Config[:http_proxy]  = "http://proxy.example.com:3128"
+        Chef::Config[:https_proxy] = "http://sproxy.example.com:3129"
+        Chef::Config[:http_proxy_user] = nil
+        Chef::Config[:http_proxy_pass] = nil
+        Chef::Config[:https_proxy_user] = nil
+        Chef::Config[:https_proxy_pass] = nil
+        Chef::Config[:no_proxy] = nil
+      end
+
+      after do
+        Chef::Config[:http_proxy]  = nil
+        Chef::Config[:https_proxy] = nil
+        Chef::Config[:http_proxy_user] = nil
+        Chef::Config[:http_proxy_pass] = nil
+        Chef::Config[:https_proxy_user] = nil
+        Chef::Config[:https_proxy_pass] = nil
+        Chef::Config[:no_proxy] = nil
+      end
+
+      describe "with :no_proxy nil" do
+        it "configures the proxy address and port when using http scheme" do
+          http_client = new_request.http_client
+          http_client.proxy?.should == true
+          http_client.proxy_address.should == "proxy.example.com"
+          http_client.proxy_port.should == 3128
+          http_client.proxy_user.should be_nil
+          http_client.proxy_pass.should be_nil
+        end
+
+        it "configures the proxy address and port when using https scheme" do
+          @url.scheme = "https"
+          http_client = new_request.http_client
+          http_client.proxy?.should == true
+          http_client.proxy_address.should == "sproxy.example.com"
+          http_client.proxy_port.should == 3129
+          http_client.proxy_user.should be_nil
+          http_client.proxy_pass.should be_nil
+        end
+      end
+
+      describe "with :no_proxy set" do
+        before do
+          Chef::Config[:no_proxy] = "10.*,*.example.com"
+        end
+
+        it "does not configure the proxy address and port when using http scheme" do
+          http_client = new_request.http_client
+          http_client.proxy?.should == false
+          http_client.proxy_address.should be_nil
+          http_client.proxy_port.should be_nil
+          http_client.proxy_user.should be_nil
+          http_client.proxy_pass.should be_nil
+        end
+
+        it "does not configure the proxy address and port when using https scheme" do
+          @url.scheme = "https"
+          http_client = new_request.http_client
+          http_client.proxy?.should == false
+          http_client.proxy_address.should be_nil
+          http_client.proxy_port.should be_nil
+          http_client.proxy_user.should be_nil
+          http_client.proxy_pass.should be_nil
+        end
+      end
+
+      describe "with :http_proxy_user and :http_proxy_pass set" do
+        before do
+          Chef::Config[:http_proxy_user] = "homie"
+          Chef::Config[:http_proxy_pass] = "theclown"
+        end
+
+        after do
+          Chef::Config[:http_proxy_user] = nil
+          Chef::Config[:http_proxy_pass] = nil
+        end
+
+        it "configures the proxy user and pass when using http scheme" do
+          http_client = new_request.http_client
+          http_client.proxy?.should == true
+          http_client.proxy_user.should == "homie"
+          http_client.proxy_pass.should == "theclown"
+        end
+
+        it "does not configure the proxy user and pass when using https scheme" do
+          @url.scheme = "https"
+          http_client = new_request.http_client
+          http_client.proxy?.should == true
+          http_client.proxy_user.should be_nil
+          http_client.proxy_pass.should be_nil
+        end
+      end
+
+      describe "with :https_proxy_user and :https_proxy_pass set" do
+        before do
+          Chef::Config[:https_proxy_user] = "homie"
+          Chef::Config[:https_proxy_pass] = "theclown"
+        end
+
+        after do
+          Chef::Config[:https_proxy_user] = nil
+          Chef::Config[:https_proxy_pass] = nil
+        end
+
+        it "does not configure the proxy user and pass when using http scheme" do
+          http_client = new_request.http_client
+          http_client.proxy?.should == true
+          http_client.proxy_user.should be_nil
+          http_client.proxy_pass.should be_nil
+        end
+
+        it "configures the proxy user and pass when using https scheme" do
+          @url.scheme = "https"
+          http_client = new_request.http_client
+          http_client.proxy?.should == true
+          http_client.proxy_user.should == "homie"
+          http_client.proxy_pass.should == "theclown"
+        end
+      end
+    end
+  end
+
+end
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
new file mode 100644
index 0000000..0281bd2
--- /dev/null
+++ b/spec/unit/rest_spec.rb
@@ -0,0 +1,632 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Christopher Brown (<cb at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'uri'
+require 'net/https'
+require 'stringio'
+
+SIGNING_KEY_DOT_PEM="-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh
+8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy
+YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei
+PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A
+O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x
+PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD
+2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk
+WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP
+g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa
+Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ
+I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/
+/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR
+xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO
+ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy
+bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A
+s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4
+DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz
+dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv
+GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq
+qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8
+OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R
+b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I
+YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12
+2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo
+Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ==
+-----END RSA PRIVATE KEY-----"
+
+describe Chef::REST do
+  before(:each) do
+    @log_stringio = StringIO.new
+    Chef::Log.init(@log_stringio)
+
+    Chef::REST::CookieJar.stub(:instance).and_return({})
+    @base_url   = "http://chef.example.com:4000"
+    @monkey_uri = URI.parse("http://chef.example.com:4000/monkey")
+    @rest = Chef::REST.new(@base_url, nil, nil)
+
+    Chef::REST::CookieJar.instance.clear
+  end
+
+
+  describe "calling an HTTP verb on a path or absolute URL" do
+    it "adds a relative URL to the base url it was initialized with" do
+      @rest.create_url("foo/bar/baz").should == URI.parse(@base_url + "/foo/bar/baz")
+    end
+
+    it "replaces the base URL when given an absolute URL" do
+      @rest.create_url("http://chef-rulez.example.com:9000").should == URI.parse("http://chef-rulez.example.com:9000")
+    end
+
+    it "makes a :GET request with the composed url object" do
+      @rest.should_receive(:send_http_request).
+        with(:GET, @monkey_uri, STANDARD_READ_HEADERS, false).
+        and_return([1,2,3])
+      @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
+      @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
+      @rest.get_rest("monkey")
+    end
+
+    it "makes a :GET reqest for a streaming download with the composed url" do
+      @rest.should_receive(:streaming_request).with('monkey', {})
+      @rest.get_rest("monkey", true)
+    end
+
+    STANDARD_READ_HEADERS = {"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"}
+    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"}
+
+    it "makes a :DELETE request with the composed url object" do
+      @rest.should_receive(:send_http_request).
+        with(:DELETE, @monkey_uri, STANDARD_READ_HEADERS, false).
+        and_return([1,2,3])
+      @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
+      @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
+      @rest.delete_rest("monkey")
+    end
+
+    it "makes a :POST request with the composed url object and data" do
+      @rest.should_receive(:send_http_request).
+        with(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
+        and_return([1,2,3])
+      @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
+      @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
+      @rest.post_rest("monkey", "data")
+    end
+
+    it "makes a :PUT request with the composed url object and data" do
+      @rest.should_receive(:send_http_request).
+        with(:PUT, @monkey_uri, STANDARD_WRITE_HEADERS, "\"data\"").
+        and_return([1,2,3])
+      @rest.should_receive(:apply_response_middleware).with(1,2,3).and_return([1,2,3])
+      @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
+      @rest.put_rest("monkey", "data")
+    end
+  end
+
+  describe "legacy API" do
+    before(:each) do
+      Chef::Config[:node_name]  = "webmonkey.example.com"
+      Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
+      @rest = Chef::REST.new(@base_url)
+    end
+
+    it 'responds to raw_http_request as a public method' do
+      @rest.public_methods.map(&:to_s).should include("raw_http_request")
+    end
+
+    it 'calls the authn middleware' do
+      data = "\"secure data\""
+
+      auth_headers = STANDARD_WRITE_HEADERS.merge({"auth_done"=>"yep"})
+
+      @rest.authenticator.should_receive(:handle_request).
+        with(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data).
+        and_return([:POST, @monkey_uri, auth_headers, data])
+      @rest.should_receive(:send_http_request).
+        with(:POST, @monkey_uri, auth_headers, data).
+        and_return([1,2,3])
+      @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
+      @rest.raw_http_request(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data)
+    end
+
+    it 'sets correct authn headers' do
+      data = "\"secure data\""
+      method, uri, auth_headers, d = @rest.authenticator.handle_request(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data)
+
+      @rest.should_receive(:send_http_request).
+        with(:POST, @monkey_uri, auth_headers, data).
+        and_return([1,2,3])
+      @rest.should_receive('success_response?'.to_sym).with(1).and_return(true)
+      @rest.raw_http_request(:POST, @monkey_uri, STANDARD_WRITE_HEADERS, data)
+    end
+  end
+
+
+  describe "when configured to authenticate to the Chef server" do
+    before do
+      @url = URI.parse("http://chef.example.com:4000")
+      Chef::Config[:node_name]  = "webmonkey.example.com"
+      Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
+      @rest = Chef::REST.new(@url)
+    end
+
+    it "configures itself to use the node_name and client_key in the config by default" do
+      @rest.client_name.should == "webmonkey.example.com"
+      @rest.signing_key_filename.should == CHEF_SPEC_DATA + "/ssl/private_key.pem"
+    end
+
+    it "provides access to the raw key data" do
+      @rest.signing_key.should == SIGNING_KEY_DOT_PEM
+    end
+
+    it "does not error out when initialized without credentials" do
+      @rest = Chef::REST.new(@url, nil, nil) #should_not raise_error hides the bt from you, so screw it.
+      @rest.client_name.should be_nil
+      @rest.signing_key.should be_nil
+    end
+
+    it "indicates that requests should not be signed when it has no credentials" do
+      @rest = Chef::REST.new(@url, nil, nil)
+      @rest.sign_requests?.should be_false
+    end
+
+    it "raises PrivateKeyMissing when the key file doesn't exist" do
+      lambda {Chef::REST.new(@url, "client-name", "/dev/null/nothing_here")}.should raise_error(Chef::Exceptions::PrivateKeyMissing)
+    end
+
+    it "raises InvalidPrivateKey when the key file doesnt' look like a key" do
+      invalid_key_file = CHEF_SPEC_DATA + "/bad-config.rb"
+      lambda {Chef::REST.new(@url, "client-name", invalid_key_file)}.should raise_error(Chef::Exceptions::InvalidPrivateKey)
+    end
+
+    it "can take private key as a sting :raw_key in options during initializaton" do
+      Chef::REST.new(@url, "client-name", nil, :raw_key => SIGNING_KEY_DOT_PEM).signing_key.should == SIGNING_KEY_DOT_PEM
+    end
+
+    it "raises InvalidPrivateKey when the key passed as string :raw_key in options doesnt' look like a key" do
+      lambda {Chef::REST.new(@url, "client-name", nil, :raw_key => "bad key string")}.should raise_error(Chef::Exceptions::InvalidPrivateKey)
+    end
+
+  end
+
+  context "when making REST requests" do
+    before(:each) do
+      Chef::Config[:ssl_client_cert] = nil
+      Chef::Config[:ssl_client_key]  = nil
+      @url = URI.parse("https://one:80/?foo=bar")
+
+      @http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
+      @http_response.stub(:read_body)
+      @http_response.stub(:body).and_return("ninja")
+      @http_response.add_field("Content-Length", "5")
+
+      @http_client = Net::HTTP.new(@url.host, @url.port)
+      Net::HTTP.stub(:new).and_return(@http_client)
+      @http_client.stub(:request).and_yield(@http_response).and_return(@http_response)
+
+      @base_headers = { 'Accept' => 'application/json',
+                        'X-Chef-Version' => Chef::VERSION,
+                        'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE}
+      @req_with_body_headers = @base_headers.merge("Content-Type" => "application/json", "Content-Length" => '13')
+    end
+
+    describe "streaming downloads to a tempfile" do
+      before do
+        @tempfile = StringIO.new
+        @tempfile.stub(:close!)
+        @tempfile.stub(:path).and_return("/a-temporary-file")
+        Tempfile.stub(:new).with("chef-rest").and_return(@tempfile)
+        Tempfile.stub(:open).and_return(@tempfile)
+
+        @request_mock = {}
+        Net::HTTP::Get.stub(:new).and_return(@request_mock)
+      end
+
+      it "should build a new HTTP GET request without the application/json accept header" do
+        expected_headers = {'Accept' => "*/*", 'X-Chef-Version' => Chef::VERSION, 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE}
+        Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(@request_mock)
+        @rest.streaming_request(@url, {})
+      end
+
+      it "should create a tempfile for the output of a raw request" do
+        @rest.streaming_request(@url, {}).should equal(@tempfile)
+      end
+
+      it "should read the body of the response in chunks on a raw request" do
+        @http_response.should_receive(:read_body).and_return(true)
+        @rest.streaming_request(@url, {})
+      end
+
+      it "should populate the tempfile with the value of the raw request" do
+        @http_response.should_receive(:read_body).and_yield("ninja")
+        @rest.streaming_request(@url, {})
+        #@tempfile.string.should include("ninja")
+      end
+
+      it "should close the tempfile if we're doing a raw request" do
+        @tempfile.should_receive(:close).once.and_return(true)
+        @rest.streaming_request(@url, {})
+      end
+
+      it "should not raise a divide by zero exception if the size is 0" do
+        @http_response.stub(:header).and_return({ 'Content-Length' => "5" })
+        @http_response.stub(:read_body).and_yield('')
+        lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
+      end
+
+      it "should not raise a divide by zero exception if the Content-Length is 0" do
+        @http_response.stub(:header).and_return({ 'Content-Length' => "0" })
+        @http_response.stub(:read_body).and_yield("ninja")
+        lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
+      end
+
+    end
+
+    describe "as JSON API requests" do
+      before do
+        @request_mock = {}
+        Net::HTTP::Get.stub(:new).and_return(@request_mock)
+
+        @base_headers = {"Accept" => "application/json",
+          "X-Chef-Version" => Chef::VERSION,
+          "Accept-Encoding" => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE
+        }
+      end
+
+      it "should always include the X-Chef-Version header" do
+        Net::HTTP::Get.should_receive(:new).with("/?foo=bar", @base_headers).and_return(@request_mock)
+        @rest.request(:GET, @url, {})
+      end
+
+      it "sets the user agent to chef-client" do
+        # must reset to default b/c knife changes the UA
+        Chef::REST::RESTRequest.user_agent = Chef::REST::RESTRequest::DEFAULT_UA
+        @rest.request(:GET, @url, {})
+        @request_mock['User-Agent'].should match(/^Chef Client\/#{Chef::VERSION}/)
+      end
+
+      # CHEF-3140
+      context "when configured to disable compression" do
+        before do
+          @rest = Chef::REST.new(@base_url, nil, nil, :disable_gzip => true)
+        end
+
+        it "does not accept encoding gzip" do
+          @rest.send(:build_headers, :GET, @url, {}).should_not have_key("Accept-Encoding")
+        end
+
+        it "does not decompress a response encoded as gzip" do
+          @http_response.add_field("content-encoding", "gzip")
+          request = Net::HTTP::Get.new(@url.path)
+          Net::HTTP::Get.should_receive(:new).and_return(request)
+          # will raise a Zlib error if incorrect
+          @rest.request(:GET, @url, {}).should == "ninja"
+        end
+      end
+      context "when configured with custom http headers" do
+        before(:each) do
+          @custom_headers = {
+            'X-Custom-ChefSecret' => 'sharpknives',
+            'X-Custom-RequestPriority' => 'extremely low'
+          }
+          Chef::Config[:custom_http_headers] = @custom_headers
+        end
+
+        after(:each) do
+          Chef::Config[:custom_http_headers] = nil
+        end
+
+        it "should set them on the http request" do
+          url_string = an_instance_of(String)
+          header_hash = hash_including(@custom_headers)
+          Net::HTTP::Get.should_receive(:new).with(url_string, header_hash)
+          @rest.request(:GET, @url, {})
+        end
+      end
+
+      it "should set the cookie for this request if one exists for the given host:port" do
+        Chef::REST::CookieJar.instance["#{@url.host}:#{@url.port}"] = "cookie monster"
+        Net::HTTP::Get.should_receive(:new).with("/?foo=bar", @base_headers.merge('Cookie' => "cookie monster")).and_return(@request_mock)
+        @rest.request(:GET, @url, {})
+      end
+
+      it "should build a new HTTP GET request" do
+        Net::HTTP::Get.should_receive(:new).with("/?foo=bar", @base_headers).and_return(@request_mock)
+        @rest.request(:GET, @url, {})
+      end
+
+      it "should build a new HTTP POST request" do
+        request = Net::HTTP::Post.new(@url.path)
+        expected_headers = @base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13')
+
+        Net::HTTP::Post.should_receive(:new).with("/?foo=bar", expected_headers).and_return(request)
+        @rest.request(:POST, @url, {}, {:one=>:two})
+        request.body.should == '{"one":"two"}'
+      end
+
+      it "should build a new HTTP PUT request" do
+        request = Net::HTTP::Put.new(@url.path)
+        expected_headers = @base_headers.merge("Content-Type" => 'application/json', 'Content-Length' => '13')
+        Net::HTTP::Put.should_receive(:new).with("/?foo=bar",expected_headers).and_return(request)
+        @rest.request(:PUT, @url, {}, {:one=>:two})
+        request.body.should == '{"one":"two"}'
+      end
+
+      it "should build a new HTTP DELETE request" do
+        Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", @base_headers).and_return(@request_mock)
+        @rest.request(:DELETE, @url)
+      end
+
+      it "should raise an error if the method is not GET/PUT/POST/DELETE" do
+        lambda { @rest.request(:MONKEY, @url) }.should raise_error(ArgumentError)
+      end
+
+      it "returns nil when the response is successful but content-type is not JSON" do
+        @rest.request(:GET, @url).should == "ninja"
+      end
+
+      it "should inflate the body as to an object if JSON is returned" do
+        @http_response.add_field('content-type', "application/json")
+        @http_response.stub(:body).and_return('{"ohai2u":"json_api"}')
+        @rest.request(:GET, @url, {}).should == {"ohai2u"=>"json_api"}
+      end
+
+      %w[ HTTPFound HTTPMovedPermanently HTTPSeeOther HTTPUseProxy HTTPTemporaryRedirect HTTPMultipleChoice ].each do |resp_name|
+        it "should call request again on a #{resp_name} response" do
+          resp_cls  = Net.const_get(resp_name)
+          resp_code = Net::HTTPResponse::CODE_TO_OBJ.keys.detect { |k| Net::HTTPResponse::CODE_TO_OBJ[k] == resp_cls }
+          http_response = Net::HTTPFound.new("1.1", resp_code, "bob is somewhere else again")
+          http_response.add_field("location", @url.path)
+          http_response.stub(:read_body)
+
+          @http_client.stub(:request).and_yield(http_response).and_return(http_response)
+
+          lambda { @rest.request(:GET, @url) }.should raise_error(Chef::Exceptions::RedirectLimitExceeded)
+
+          [:PUT, :POST, :DELETE].each do |method|
+            lambda { @rest.request(method, @url) }.should raise_error(Chef::Exceptions::InvalidRedirect)
+          end
+        end
+      end
+
+      it "should return `false` when response is 304 NotModified" do
+        http_response = Net::HTTPNotModified.new("1.1", "304", "it's the same as when you asked 5 minutes ago")
+        http_response.stub(:read_body)
+
+        @http_client.stub(:request).and_yield(http_response).and_return(http_response)
+
+        @rest.request(:GET, @url).should be_false
+      end
+
+      describe "when the request fails" do
+        before do
+          @original_log_level = Chef::Log.level
+          Chef::Log.level = :info
+        end
+
+        after do
+          Chef::Log.level = @original_log_level
+        end
+
+        it "should show the JSON error message on an unsuccessful request" do
+          http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
+          http_response.add_field("content-type", "application/json")
+          http_response.stub(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }')
+          http_response.stub(:read_body)
+          @rest.stub(:sleep)
+          @http_client.stub(:request).and_yield(http_response).and_return(http_response)
+
+          lambda {@rest.request(:GET, @url)}.should raise_error(Net::HTTPFatalError)
+          @log_stringio.string.should match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four'))
+        end
+
+        it "decompresses the JSON error message on an unsuccessful request" do
+          http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
+          http_response.add_field("content-type", "application/json")
+          http_response.add_field("content-encoding", "deflate")
+          unzipped_body = '{ "error":[ "Ears get sore!", "Not even four" ] }'
+          gzipped_body = Zlib::Deflate.deflate(unzipped_body)
+          gzipped_body.force_encoding(Encoding::BINARY) if "strings".respond_to?(:force_encoding)
+
+          http_response.stub(:body).and_return gzipped_body
+          http_response.stub(:read_body)
+          @rest.stub(:sleep)
+          @rest.stub(:http_retry_count).and_return(0)
+          @http_client.stub(:request).and_yield(http_response).and_return(http_response)
+
+          lambda {@rest.request(:GET, @url)}.should raise_error(Net::HTTPFatalError)
+          @log_stringio.string.should match(Regexp.escape('INFO: HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four'))
+        end
+
+        it "should raise an exception on an unsuccessful request" do
+          http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth")
+          http_response.stub(:body)
+          http_response.stub(:read_body)
+          @rest.stub(:sleep)
+          @http_client.stub(:request).and_yield(http_response).and_return(http_response)
+          lambda {@rest.request(:GET, @url)}.should raise_error(Net::HTTPFatalError)
+        end
+      end
+
+
+    end
+
+    context "when streaming downloads to a tempfile" do
+      before do
+        @tempfile = Tempfile.open("chef-rspec-rest_spec-line-#{__LINE__}--")
+        Tempfile.stub(:new).with("chef-rest").and_return(@tempfile)
+        @request_mock = {}
+        Net::HTTP::Get.stub(:new).and_return(@request_mock)
+
+        @http_response = Net::HTTPSuccess.new("1.1",200, "it-works")
+        @http_response.stub(:read_body)
+        @http_client.stub(:request).and_yield(@http_response).and_return(@http_response)
+      end
+
+      after do
+        @tempfile.close!
+      end
+
+      it " build a new HTTP GET request without the application/json accept header" do
+        expected_headers = {'Accept' => "*/*", 'X-Chef-Version' => Chef::VERSION, 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE}
+        Net::HTTP::Get.should_receive(:new).with("/?foo=bar", expected_headers).and_return(@request_mock)
+        @rest.streaming_request(@url, {})
+      end
+
+      it "returns a tempfile containing the streamed response body" do
+        @rest.streaming_request(@url, {}).should equal(@tempfile)
+      end
+
+      it "writes the response body to a tempfile" do
+        @http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
+        @rest.streaming_request(@url, {})
+        IO.read(@tempfile.path).chomp.should == "realultimatepower"
+      end
+
+      it "closes the tempfile" do
+        @rest.streaming_request(@url, {})
+        @tempfile.should be_closed
+      end
+
+      it "yields the tempfile containing the streamed response body and then unlinks it when given a block" do
+        @http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
+        tempfile_path = nil
+        @rest.streaming_request(@url, {}) do |tempfile|
+          tempfile_path = tempfile.path
+          File.exist?(tempfile.path).should be_true
+          IO.read(@tempfile.path).chomp.should == "realultimatepower"
+        end
+        File.exist?(tempfile_path).should be_false
+      end
+
+      it "does not raise a divide by zero exception if the content's actual size is 0" do
+        @http_response.add_field('Content-Length', "5")
+        @http_response.stub(:read_body).and_yield('')
+        lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
+      end
+
+      it "does not raise a divide by zero exception when the Content-Length is 0" do
+        @http_response.add_field('Content-Length', "0")
+        @http_response.stub(:read_body).and_yield("ninja")
+        lambda { @rest.streaming_request(@url, {}) }.should_not raise_error
+      end
+
+      it "fetches a file and yields the tempfile it is streamed to" do
+        @http_response.stub(:read_body).and_yield("real").and_yield("ultimate").and_yield("power")
+        tempfile_path = nil
+        @rest.fetch("cookbooks/a_cookbook") do |tempfile|
+          tempfile_path = tempfile.path
+          IO.read(@tempfile.path).chomp.should == "realultimatepower"
+        end
+        File.exist?(tempfile_path).should be_false
+      end
+
+      it "closes and unlinks the tempfile if there is an error while streaming the content to the tempfile" do
+        path = @tempfile.path
+        path.should_not be_nil
+        @tempfile.stub(:write).and_raise(IOError)
+        @rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"}
+        File.exists?(path).should be_false
+      end
+
+      it "closes and unlinks the tempfile when the response is a redirect" do
+        tempfile = double("A tempfile", :path => "/tmp/ragefist", :close => true, :binmode => true)
+        tempfile.should_receive(:close!).at_least(1).times
+        Tempfile.stub(:new).with("chef-rest").and_return(tempfile)
+
+        redirect = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today")
+        redirect.add_field("location", @url.path)
+        redirect.stub(:read_body)
+
+        @http_client.should_receive(:request).and_yield(redirect).and_return(redirect)
+        @http_client.should_receive(:request).and_yield(@http_response).and_return(@http_response)
+        @rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"}
+      end
+
+      it "passes the original block to the redirected request" do
+        http_response = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today")
+        http_response.add_field("location","/that-thing-is-here-now")
+        http_response.stub(:read_body)
+
+        block_called = false
+        @http_client.stub(:request).and_yield(@http_response).and_return(http_response, @http_response)
+        @rest.fetch("cookbooks/a_cookbook") do |tmpfile|
+          block_called = true
+        end
+        block_called.should be_true
+      end
+    end
+  end
+
+  context "when following redirects" do
+    before do
+      Chef::Config[:node_name]  = "webmonkey.example.com"
+      Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem"
+      @rest = Chef::REST.new(@url)
+    end
+
+    it "raises a RedirectLimitExceeded when redirected more than 10 times" do
+      redirected = lambda {@rest.follow_redirect { redirected.call }}
+      lambda {redirected.call}.should raise_error(Chef::Exceptions::RedirectLimitExceeded)
+    end
+
+    it "does not count redirects from previous calls against the redirect limit" do
+      total_redirects = 0
+      redirected = lambda do
+        @rest.follow_redirect do
+          total_redirects += 1
+          redirected.call unless total_redirects >= 9
+        end
+      end
+      lambda {redirected.call}.should_not raise_error
+      total_redirects = 0
+      lambda {redirected.call}.should_not raise_error
+    end
+
+    it "does not sign the redirected request when sign_on_redirect is false" do
+      @rest.sign_on_redirect = false
+      @rest.follow_redirect { @rest.sign_requests?.should be_false }
+    end
+
+    it "resets sign_requests to the original value after following an unsigned redirect" do
+      @rest.sign_on_redirect = false
+      @rest.sign_requests?.should be_true
+
+      @rest.follow_redirect { @rest.sign_requests?.should be_false }
+      @rest.sign_requests?.should be_true
+    end
+
+    it "configures the redirect limit" do
+      total_redirects = 0
+      redirected = lambda do
+        @rest.follow_redirect do
+          total_redirects += 1
+          redirected.call unless total_redirects >= 9
+        end
+      end
+      lambda {redirected.call}.should_not raise_error
+
+      total_redirects = 0
+      @rest.redirect_limit = 3
+      lambda {redirected.call}.should raise_error(Chef::Exceptions::RedirectLimitExceeded)
+    end
+
+  end
+end
diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb
new file mode 100644
index 0000000..f36b7f1
--- /dev/null
+++ b/spec/unit/role_spec.rb
@@ -0,0 +1,328 @@
+#
+# Author:: Adam Jacob (<adam at opscode.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/role'
+
+describe Chef::Role do
+  before(:each) do
+    @role = Chef::Role.new
+    @role.name("ops_master")
+  end
+
+  it "has a name" do
+    @role.name("ops_master").should == "ops_master"
+  end
+
+  it "does not accept a name with spaces" do
+    lambda { @role.name "ops master" }.should raise_error(ArgumentError)
+  end
+
+  it "does not accept non-String objects for the name" do
+    lambda { @role.name({}) }.should raise_error(ArgumentError)
+  end
+
+  describe "when a run list is set" do
+
+    before do
+      @role.run_list(%w{ nginx recipe[ree] role[base]})
+    end
+
+
+    it "returns the run list" do
+      @role.run_list.should == %w{ nginx recipe[ree] role[base]}
+    end
+
+    describe "and per-environment run lists are set" do
+      before do
+        @role.name("base")
+        @role.run_list(%w{ recipe[nagios::client] recipe[tims-acl::bork]})
+        @role.env_run_list["prod"] = Chef::RunList.new(*(@role.run_list.to_a << "recipe[prod-base]"))
+        @role.env_run_list["dev"]  = Chef::RunList.new
+      end
+
+      it "uses the default run list as *the* run_list" do
+        @role.run_list.should == Chef::RunList.new("recipe[nagios::client]", "recipe[tims-acl::bork]")
+      end
+
+      it "gives the default run list as the when getting the _default run list" do
+        @role.run_list_for("_default").should == @role.run_list
+      end
+
+      it "gives an environment specific run list" do
+        @role.run_list_for("prod").should == Chef::RunList.new("recipe[nagios::client]", "recipe[tims-acl::bork]", "recipe[prod-base]")
+      end
+
+      it "gives the default run list when no run list exists for the given environment" do
+        @role.run_list_for("qa").should == @role.run_list
+      end
+
+      it "gives the environment specific run list even if it is empty" do
+        @role.run_list_for("dev").should == Chef::RunList.new
+      end
+
+      it "env_run_lists can only be set with _default run list in it" do
+        long_exception_name = Chef::Exceptions::InvalidEnvironmentRunListSpecification
+        lambda {@role.env_run_lists({})}.should raise_error(long_exception_name)
+      end
+
+    end
+
+
+    describe "using the old #recipes API" do
+      it "should let you set the recipe array" do
+        @role.recipes([ "one", "two" ]).should == [ "one", "two" ]
+      end
+
+      it "should let you return the recipe array" do
+        @role.recipes([ "one", "two" ])
+        @role.recipes.should == [ "one", "two" ]
+      end
+
+      it "should not list roles in the recipe array" do
+        @role.run_list([ "one", "role[two]"])
+        @role.recipes.should == [ "recipe[one]", "role[two]" ]
+      end
+
+    end
+
+  end
+
+
+
+  describe "default_attributes" do
+    it "should let you set the default attributes hash explicitly" do
+      @role.default_attributes({ :one => 'two' }).should == { :one => 'two' }
+    end
+
+    it "should let you return the default attributes hash" do
+      @role.default_attributes({ :one => 'two' })
+      @role.default_attributes.should == { :one => 'two' }
+    end
+
+    it "should throw an ArgumentError if we aren't a kind of hash" do
+      lambda { @role.default_attributes(Array.new) }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "override_attributes" do
+    it "should let you set the override attributes hash explicitly" do
+      @role.override_attributes({ :one => 'two' }).should == { :one => 'two' }
+    end
+
+    it "should let you return the override attributes hash" do
+      @role.override_attributes({ :one => 'two' })
+      @role.override_attributes.should == { :one => 'two' }
+    end
+
+    it "should throw an ArgumentError if we aren't a kind of hash" do
+      lambda { @role.override_attributes(Array.new) }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "update_from!" do
+    before(:each) do
+      @role.name('mars_volta')
+      @role.description('Great band!')
+      @role.run_list('one', 'two', 'role[a]')
+      @role.default_attributes({ :el_groupo => 'nuevo' })
+      @role.override_attributes({ :deloused => 'in the comatorium' })
+
+      @example = Chef::Role.new
+      @example.name('newname')
+      @example.description('Really Great band!')
+      @example.run_list('alpha', 'bravo', 'role[alpha]')
+      @example.default_attributes({ :el_groupo => 'nuevo dos' })
+      @example.override_attributes({ :deloused => 'in the comatorium XOXO' })
+    end
+
+    it "should update all fields except for name" do
+      @role.update_from!(@example)
+      @role.name.should == "mars_volta"
+      @role.description.should == @example.description
+      @role.run_list.should == @example.run_list
+      @role.default_attributes.should == @example.default_attributes
+      @role.override_attributes.should == @example.override_attributes
+    end
+  end
+
+  describe "when serialized as JSON", :json => true do
+    before(:each) do
+      @role.name('mars_volta')
+      @role.description('Great band!')
+      @role.run_list('one', 'two', 'role[a]')
+      @role.default_attributes({ :el_groupo => 'nuevo' })
+      @role.override_attributes({ :deloused => 'in the comatorium' })
+      @serialized_role = Chef::JSONCompat.to_json(@role)
+    end
+
+    it "should serialize to a json hash" do
+      Chef::JSONCompat.to_json(@role).should match(/^\{.+\}$/)
+    end
+
+    it "includes the name in the JSON output" do
+      @serialized_role.should =~ /"name":"mars_volta"/
+    end
+
+    it "includes its description in the JSON" do
+      @serialized_role.should match(/"description":"Great band!"/)
+    end
+
+    it "should include 'default_attributes'" do
+      @serialized_role.should =~ /"default_attributes":\{"el_groupo":"nuevo"\}/
+    end
+
+    it "should include 'override_attributes'" do
+      @serialized_role.should =~ /"override_attributes":\{"deloused":"in the comatorium"\}/
+    end
+
+    it "should include 'run_list'" do
+      #Activesupport messes with Chef json formatting
+      #This test should pass with and without activesupport
+      @serialized_role.should =~ /"run_list":\["recipe\[one\]","recipe\[two\]","role\[a\]"\]/
+    end
+
+    describe "and it has per-environment run lists" do
+      before do
+        @role.env_run_lists("_default" => ['one', 'two', 'role[a]'], "production" => ['role[monitoring]', 'role[auditing]', 'role[apache]'], "dev" => ["role[nginx]"])
+        @serialized_role = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@role), :create_additions => false)
+      end
+
+      it "includes the per-environment run lists" do
+        #Activesupport messes with Chef json formatting
+        #This test should pass with and without activesupport
+        @serialized_role["env_run_lists"]["production"].should == ['role[monitoring]', 'role[auditing]', 'role[apache]']
+        @serialized_role["env_run_lists"]["dev"].should == ["role[nginx]"]
+      end
+
+      it "does not include the default environment in the per-environment run lists" do
+        @serialized_role["env_run_lists"].should_not have_key("_default")
+      end
+
+    end
+  end
+
+  describe "when created from JSON", :json => true do
+    before(:each) do
+      @role.name('mars_volta')
+      @role.description('Great band!')
+      @role.run_list('one', 'two', 'role[a]')
+      @role.default_attributes({ 'el_groupo' => 'nuevo' })
+      @role.override_attributes({ 'deloused' => 'in the comatorium' })
+      @deserial = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(@role))
+    end
+
+    it "should deserialize to a Chef::Role object" do
+      @deserial.should be_a_kind_of(Chef::Role)
+    end
+
+    %w{
+      name
+      description
+      default_attributes
+      override_attributes
+      run_list
+    }.each do |t|
+      it "should preserves the '#{t}' attribute from the JSON object" do
+        @deserial.send(t.to_sym).should == @role.send(t.to_sym)
+      end
+    end
+  end
+
+  ROLE_DSL=<<-EOR
+name "ceiling_cat"
+description "like Aliens, but furry"
+EOR
+
+  describe "when loading from disk" do
+    it "should return a Chef::Role object from JSON" do
+      File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.json')).exactly(1).times.and_return(true)
+      IO.should_receive(:read).with(File.join(Chef::Config[:role_path], 'lolcat.json')).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }')
+      @role.should be_a_kind_of(Chef::Role)
+      @role.class.from_disk("lolcat")
+    end
+
+    it "should return a Chef::Role object from a Ruby DSL" do
+      File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).exactly(2).times.and_return(true)
+      File.should_receive(:readable?).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).exactly(1).times.and_return(true)
+      IO.should_receive(:read).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).and_return(ROLE_DSL)
+      @role.should be_a_kind_of(Chef::Role)
+      @role.class.from_disk("lolcat")
+    end
+
+    it "should raise an exception if the file does not exist" do
+      File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join(Chef::Config[:role_path], 'lolcat.rb')).exactly(1).times.and_return(false)
+      lambda {@role.class.from_disk("lolcat")}.should raise_error(Chef::Exceptions::RoleNotFound)
+    end
+  end
+
+  describe "when loading from disk and role_path is an array" do
+
+    before(:each) do
+      Chef::Config[:role_path] = ['/path1', '/path/path2']
+    end
+
+    it "should return a Chef::Role object from JSON" do
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.json')).exactly(1).times.and_return(true)
+      IO.should_receive(:read).with(File.join('/path1', 'lolcat.json')).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }')
+      @role.should be_a_kind_of(Chef::Role)
+      @role.class.from_disk("lolcat")
+    end
+
+    it "should return a Chef::Role object from JSON when role is in the second path" do
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.rb')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path/path2', 'lolcat.json')).exactly(1).times.and_return(true)
+      IO.should_receive(:read).with(File.join('/path/path2', 'lolcat.json')).and_return('{"name": "ceiling_cat", "json_class": "Chef::Role" }')
+      @role.should be_a_kind_of(Chef::Role)
+      @role.class.from_disk("lolcat")
+    end
+
+    it "should return a Chef::Role object from a Ruby DSL" do
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.rb')).exactly(2).times.and_return(true)
+      File.should_receive(:readable?).with(File.join('/path1', 'lolcat.rb')).exactly(1).times.and_return(true)
+      IO.should_receive(:read).with(File.join('/path1', 'lolcat.rb')).and_return(ROLE_DSL)
+      @role.should be_a_kind_of(Chef::Role)
+      @role.class.from_disk("lolcat")
+    end
+
+    it "should return a Chef::Role object from a Ruby DSL when role is in the second path" do
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.rb')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path/path2', 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path/path2', 'lolcat.rb')).exactly(2).times.and_return(true)
+      File.should_receive(:readable?).with(File.join('/path/path2', 'lolcat.rb')).exactly(1).times.and_return(true)
+      IO.should_receive(:read).with(File.join('/path/path2', 'lolcat.rb')).and_return(ROLE_DSL)
+      @role.should be_a_kind_of(Chef::Role)
+      @role.class.from_disk("lolcat")
+    end
+
+    it "should raise an exception if the file does not exist" do
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path1', 'lolcat.rb')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path/path2', 'lolcat.json')).exactly(1).times.and_return(false)
+      File.should_receive(:exists?).with(File.join('/path/path2', 'lolcat.rb')).exactly(1).times.and_return(false)
+      lambda {@role.class.from_disk("lolcat")}.should raise_error(Chef::Exceptions::RoleNotFound)
+    end
+
+  end
+end
+
diff --git a/spec/unit/run_context/cookbook_compiler_spec.rb b/spec/unit/run_context/cookbook_compiler_spec.rb
new file mode 100644
index 0000000..52f4772
--- /dev/null
+++ b/spec/unit/run_context/cookbook_compiler_spec.rb
@@ -0,0 +1,174 @@
+#
+# Author:: Daniel DeLeo (<dan 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 'support/lib/library_load_order'
+
+# These tests rely on fixture data in spec/data/run_context/cookbooks.
+#
+# Dependencies (circular or not) are specified by `depends` directives in the
+# metadata of these cookbooks.
+#
+# Side effects used to verify the behavior are implemented as code in the various file types.
+#
+describe Chef::RunContext::CookbookCompiler do
+
+  let(:node) { Chef::Node.new }
+
+  let(:events) { Chef::EventDispatch::Dispatcher.new }
+
+  let(:cookbook_loader) do
+    cl = Chef::CookbookLoader.new(chef_repo_path)
+    cl.load_cookbooks
+    cl
+  end
+
+  let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) }
+
+  let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) }
+
+  let(:cookbook_collection) { Chef::CookbookCollection.new(cookbook_loader) }
+
+  # Lazy evaluation of `expansion` here is used to mutate the run list before expanding it
+  let(:run_list_expansion) { node.run_list.expand('_default') }
+
+  let(:compiler) do
+    Chef::RunContext::CookbookCompiler.new(run_context, run_list_expansion, events)
+  end
+
+
+  describe "loading attribute files" do
+
+    # Attribute files in the fixture data will append their
+    # "cookbook_name::attribute_file_name" to the node's `:attr_load_order`
+    # attribute when loaded.
+
+    it "loads default.rb first, then other files in sort order" do
+      node.run_list("dependency1::default")
+
+      compiler.compile_attributes
+      node[:attr_load_order].should == ["dependency1::default", "dependency1::aa_first", "dependency1::zz_last"]
+    end
+
+    it "loads dependencies before loading the depending cookbook's attributes" do
+      # Also make sure that attributes aren't loaded twice if we have two
+      # recipes from the same cookbook in the run list
+      node.run_list("test-with-deps::default", "test-with-deps::server")
+
+      compiler.compile_attributes
+
+      # dependencies are stored in a hash so therefore unordered, but they should be loaded in sort order
+      node[:attr_load_order].should == ["dependency1::default",
+                                        "dependency1::aa_first",
+                                        "dependency1::zz_last",
+                                        "dependency2::default",
+                                        "test-with-deps::default"]
+    end
+
+    it "does not follow infinite dependency loops" do
+      node.run_list("test-with-circular-deps::default")
+
+      # Circular deps should not cause infinite loops
+      compiler.compile_attributes
+
+      node[:attr_load_order].should == ["circular-dep2::default", "circular-dep1::default", "test-with-circular-deps::default"]
+    end
+
+    it "loads attributes from cookbooks that don't have a default.rb attribute file" do
+      node.run_list("no-default-attr::default.rb")
+
+      compiler.compile_attributes
+
+      node[:attr_load_order].should == ["no-default-attr::server"]
+    end
+  end
+
+  describe "loading libraries" do
+    before do
+      LibraryLoadOrder.reset!
+    end
+
+    # One big test for everything. Individual behaviors are tested by the attribute code above.
+    it "loads libraries in run list order" do
+      node.run_list("test-with-deps::default", "test-with-circular-deps::default")
+
+      compiler.compile_libraries
+      LibraryLoadOrder.load_order.should == ["dependency1", "dependency2", "test-with-deps", "circular-dep2", "circular-dep1", "test-with-circular-deps"]
+    end
+  end
+
+  describe "loading LWRPs" do
+    before do
+      LibraryLoadOrder.reset!
+    end
+
+    # One big test for everything. Individual behaviors are tested by the attribute code above.
+    it "loads LWRPs in run list order" do
+      node.run_list("test-with-deps::default", "test-with-circular-deps::default")
+
+      compiler.compile_lwrps
+      LibraryLoadOrder.load_order.should == ["dependency1-provider",
+                                             "dependency1-resource",
+                                             "dependency2-provider",
+                                             "dependency2-resource",
+                                             "test-with-deps-provider",
+                                             "test-with-deps-resource",
+                                             "circular-dep2-provider",
+                                             "circular-dep2-resource",
+                                             "circular-dep1-provider",
+                                             "circular-dep1-resource",
+                                             "test-with-circular-deps-provider",
+                                             "test-with-circular-deps-resource"]
+    end
+  end
+
+  describe "loading resource definitions" do
+    before do
+      LibraryLoadOrder.reset!
+    end
+
+    # One big test for all load order concerns. Individual behaviors are tested
+    # by the attribute code above.
+    it "loads resource definitions in run list order" do
+      node.run_list("test-with-deps::default", "test-with-circular-deps::default")
+
+      compiler.compile_resource_definitions
+      LibraryLoadOrder.load_order.should == ["dependency1-definition",
+                                             "dependency2-definition",
+                                             "test-with-deps-definition",
+                                             "circular-dep2-definition",
+                                             "circular-dep1-definition",
+                                             "test-with-circular-deps-definition"]
+    end
+
+  end
+
+  describe "loading recipes" do
+    # Tests for this behavior are in RunContext's tests
+  end
+
+  describe "listing cookbook order" do
+    it "should return an array of cookbook names as symbols without duplicates" do
+      node.run_list("test-with-circular-deps::default", "circular-dep1::default", "circular-dep2::default")
+
+      compiler.cookbook_order.should == [:"circular-dep2",
+                                         :"circular-dep1",
+                                         :"test-with-circular-deps"]
+    end
+  end
+end
diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb
new file mode 100644
index 0000000..8063dff
--- /dev/null
+++ b/spec/unit/run_context_spec.rb
@@ -0,0 +1,119 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Tim Hinderliter (<tim at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'support/lib/library_load_order'
+
+Chef::Log.level = :debug
+
+describe Chef::RunContext do
+  before(:each) do
+    @chef_repo_path = File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks"))
+    cl = Chef::CookbookLoader.new(@chef_repo_path)
+    cl.load_cookbooks
+    @cookbook_collection = Chef::CookbookCollection.new(cl)
+    @node = Chef::Node.new
+    @node.run_list << "test" << "test::one" << "test::two"
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+  end
+
+  it "has a cookbook collection" do
+    @run_context.cookbook_collection.should == @cookbook_collection
+  end
+
+  it "has a node" do
+    @run_context.node.should == @node
+  end
+
+  describe "loading cookbooks for a run list" do
+    before do
+      @run_context.load(@node.run_list.expand('_default'))
+    end
+
+    it "should load all the definitions in the cookbooks for this node" do
+      @run_context.definitions.should have_key(:new_cat)
+      @run_context.definitions.should have_key(:new_badger)
+      @run_context.definitions.should have_key(:new_dog)
+    end
+
+    it "should load all the recipes specified for this node" do
+      @run_context.resource_collection[0].to_s.should == "cat[einstein]"
+      @run_context.resource_collection[1].to_s.should == "cat[loulou]"
+      @run_context.resource_collection[2].to_s.should == "cat[birthday]"
+      @run_context.resource_collection[3].to_s.should == "cat[peanut]"
+      @run_context.resource_collection[4].to_s.should == "cat[fat peanut]"
+    end
+
+    it "loads all the attribute files in the cookbook collection" do
+      @run_context.loaded_fully_qualified_attribute?("test", "george").should be_true
+      @node[:george].should == "washington"
+    end
+
+    it "registers attributes files as loaded so they won't be reloaded" do
+      # This test unfortunately is pretty tightly intertwined with the
+      # implementation of how nodes load attribute files, but is the only
+      # convenient way to test this behavior.
+      @node.should_not_receive(:from_file)
+      @node.include_attribute("test::george")
+    end
+
+
+  end
+
+  describe "querying the contents of cookbooks" do
+    before do
+      @chef_repo_path = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
+      cl = Chef::CookbookLoader.new(@chef_repo_path)
+      cl.load_cookbooks
+      @cookbook_collection = Chef::CookbookCollection.new(cl)
+      @node = Chef::Node.new
+      @node.set[:platform] = "ubuntu"
+      @node.set[:platform_version] = "13.04"
+      @node.name("testing")
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_context = Chef::RunContext.new(@node, @cookbook_collection, @events)
+    end
+
+
+    it "queries whether a given cookbook has a specific template" do
+      @run_context.should have_template_in_cookbook("openldap", "test.erb")
+      @run_context.should_not have_template_in_cookbook("openldap", "missing.erb")
+    end
+
+    it "errors when querying for a template in a not-available cookbook" do
+      expect do
+        @run_context.has_template_in_cookbook?("no-such-cookbook", "foo.erb")
+      end.to raise_error(Chef::Exceptions::CookbookNotFound)
+    end
+
+    it "queries whether a given cookbook has a specific cookbook_file" do
+      @run_context.should have_cookbook_file_in_cookbook("java", "java.response")
+      @run_context.should_not have_cookbook_file_in_cookbook("java", "missing.txt")
+    end
+
+    it "errors when querying for a cookbook_file in a not-available cookbook" do
+      expect do
+        @run_context.has_cookbook_file_in_cookbook?("no-such-cookbook", "foo.txt")
+      end.to raise_error(Chef::Exceptions::CookbookNotFound)
+    end
+  end
+
+end
diff --git a/spec/unit/run_list/run_list_expansion_spec.rb b/spec/unit/run_list/run_list_expansion_spec.rb
new file mode 100644
index 0000000..012ee9e
--- /dev/null
+++ b/spec/unit/run_list/run_list_expansion_spec.rb
@@ -0,0 +1,129 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::RunList::RunListExpansion do
+  before do
+    @run_list = Chef::RunList.new
+    @run_list << 'recipe[lobster]' << 'role[rage]' << 'recipe[fist]'
+    @expansion = Chef::RunList::RunListExpansion.new("_default", @run_list.run_list_items)
+  end
+
+  describe "before expanding the run list" do
+    it "has an array of run list items" do
+      @expansion.run_list_items.should == @run_list.run_list_items
+    end
+
+    it "has default_attrs" do
+      @expansion.default_attrs.should == Mash.new
+    end
+
+    it "has override attrs" do
+      @expansion.override_attrs.should == Mash.new
+    end
+
+    it "it has an empty list of recipes" do
+      @expansion.should have(0).recipes
+    end
+
+    it "has not applied its roles" do
+      @expansion.applied_role?('rage').should be_false
+    end
+  end
+
+  describe "after applying a role with environment-specific run lists" do
+    before do
+      @rage_role = Chef::Role.new.tap do |r|
+        r.name("rage")
+        r.env_run_lists('_default' => [], "prod" => ["recipe[prod-only]"])
+      end
+      @expansion = Chef::RunList::RunListExpansion.new("prod", @run_list.run_list_items)
+      @expansion.should_receive(:fetch_role).and_return(@rage_role)
+      @expansion.expand
+    end
+
+    it "has the correct list of recipes for the given environment" do
+      @expansion.recipes.should == ["lobster", "prod-only", "fist"]
+    end
+
+  end
+
+  describe "after applying a role" do
+    before do
+      @expansion.stub!(:fetch_role).and_return(Chef::Role.new)
+      @expansion.inflate_role('rage', "role[base]")
+    end
+
+    it "tracks the applied role" do
+      @expansion.applied_role?('rage').should be_true
+    end
+
+    it "does not inflate the role again" do
+      @expansion.inflate_role('rage', "role[base]").should be_false
+    end
+  end
+
+  describe "after expanding a run list" do
+    before do
+      @first_role = Chef::Role.new
+      @first_role.run_list('role[mollusk]')
+      @first_role.default_attributes({'foo' => 'bar'})
+      @first_role.override_attributes({'baz' => 'qux'})
+      @second_role = Chef::Role.new
+      @second_role.run_list('recipe[crabrevenge]')
+      @second_role.default_attributes({'foo' => 'boo'})
+      @second_role.override_attributes({'baz' => 'bux'})
+      @expansion.stub!(:fetch_role).and_return(@first_role, @second_role)
+      @expansion.expand
+    end
+
+    it "has the ordered list of recipes" do
+      @expansion.recipes.should == ['lobster', 'crabrevenge', 'fist']
+    end
+
+    it "has the merged attributes from the roles with outer roles overridding inner" do
+      @expansion.default_attrs.should == {'foo' => 'bar'}
+      @expansion.override_attrs.should == {'baz' => 'qux'}
+    end
+
+    it "has the list of all roles applied" do
+      # this is the correct order, but 1.8 hash order is not stable
+      @expansion.roles.should =~ ['rage', 'mollusk']
+    end
+
+  end
+
+  describe "after expanding a run list with a non existant role" do
+    before do
+      @expansion.stub!(:fetch_role) { @expansion.role_not_found('crabrevenge', "role[base]") }
+      @expansion.expand
+    end
+
+    it "is invalid" do
+      @expansion.should be_invalid
+      @expansion.errors?.should be_true # aliases
+    end
+
+    it "has a list of invalid role names" do
+      @expansion.errors.should include('crabrevenge')
+    end
+
+  end
+
+end
diff --git a/spec/unit/run_list/run_list_item_spec.rb b/spec/unit/run_list/run_list_item_spec.rb
new file mode 100644
index 0000000..6b9de71
--- /dev/null
+++ b/spec/unit/run_list/run_list_item_spec.rb
@@ -0,0 +1,117 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::RunList::RunListItem do
+
+  describe "when creating from a Hash" do
+    it "raises an exception when the hash doesn't have a :type key" do
+      lambda {Chef::RunList::RunListItem.new(:name => "tatft")}.should raise_error(ArgumentError)
+    end
+
+    it "raises an exception when the hash doesn't have an :name key" do
+      lambda {Chef::RunList::RunListItem.new(:type => 'R') }.should raise_error(ArgumentError)
+    end
+
+    it "sets the name and type as given in the hash" do
+      item = Chef::RunList::RunListItem.new(:type => 'fuuu', :name => 'uuuu')
+      item.to_s.should == 'fuuu[uuuu]'
+    end
+
+  end
+
+  describe "when creating an item from a string" do
+    it "parses a qualified recipe" do
+      item = Chef::RunList::RunListItem.new("recipe[rage]")
+      item.should be_a_recipe
+      item.should_not be_a_role
+      item.to_s.should == 'recipe[rage]'
+      item.name.should == 'rage'
+    end
+
+    it "parses a qualified recipe with a version" do
+      item = Chef::RunList::RunListItem.new("recipe[rage at 0.1.0]")
+      item.should be_a_recipe
+      item.should_not be_a_role
+      item.to_s.should == 'recipe[rage at 0.1.0]'
+      item.name.should == 'rage'
+      item.version.should == '0.1.0'
+    end
+
+    it "parses a qualified role" do
+      item = Chef::RunList::RunListItem.new("role[fist]")
+      item.should be_a_role
+      item.should_not be_a_recipe
+      item.to_s.should == 'role[fist]'
+      item.name.should == 'fist'
+    end
+
+    it "parses an unqualified recipe" do
+      item = Chef::RunList::RunListItem.new("lobster")
+      item.should be_a_recipe
+      item.should_not be_a_role
+      item.to_s.should == 'recipe[lobster]'
+      item.name.should == 'lobster'
+    end
+
+    it "raises an exception when the string has typo on the type part" do
+      lambda {Chef::RunList::RunListItem.new("Recipe[lobster]") }.should raise_error(ArgumentError)
+    end
+
+    it "raises an exception when the string has extra space between the type and the name" do
+      lambda {Chef::RunList::RunListItem.new("recipe [lobster]") }.should raise_error(ArgumentError)
+    end
+
+    it "raises an exception when the string does not close the bracket" do
+      lambda {Chef::RunList::RunListItem.new("recipe[lobster") }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "comparing to other run list items" do
+    it "is equal to another run list item that has the same name and type" do
+      item1 = Chef::RunList::RunListItem.new('recipe[lrf]')
+      item2 = Chef::RunList::RunListItem.new('recipe[lrf]')
+      item1.should == item2
+    end
+
+    it "is not equal to another run list item with the same name and different type" do
+      item1 = Chef::RunList::RunListItem.new('recipe[lrf]')
+      item2 = Chef::RunList::RunListItem.new('role[lrf]')
+      item1.should_not == item2
+    end
+
+    it "is not equal to another run list item with the same type and different name" do
+      item1 = Chef::RunList::RunListItem.new('recipe[lrf]')
+      item2 = Chef::RunList::RunListItem.new('recipe[lobsterragefist]')
+      item1.should_not == item2
+    end
+
+    it "is not equal to another run list item with the same name and type but different version" do
+      item1 = Chef::RunList::RunListItem.new('recipe[lrf,0.1.0]')
+      item2 = Chef::RunList::RunListItem.new('recipe[lrf,0.2.0]')
+      item1.should_not == item2
+    end
+  end
+
+  describe "comparing to strings" do
+    it "is equal to a string if that string matches its to_s representation" do
+      Chef::RunList::RunListItem.new('recipe[lrf]').should == 'recipe[lrf]'
+    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
new file mode 100644
index 0000000..5cef32c
--- /dev/null
+++ b/spec/unit/run_list/versioned_recipe_list_spec.rb
@@ -0,0 +1,123 @@
+#
+# Author:: Stephen Delano (<stephen at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+
+describe Chef::RunList::VersionedRecipeList do
+
+  describe "initialize" do
+    it "should create an empty array" do
+      l = Chef::RunList::VersionedRecipeList.new
+      l.should == []
+    end
+  end
+
+  describe "add_recipe" do
+    before(:each) do
+      @list = Chef::RunList::VersionedRecipeList.new
+      @list << "apt"
+      @list << "god"
+      @list << "apache2"
+    end
+
+    it "should append the recipe to the end of the list" do
+      @list.add_recipe "rails"
+      @list.should == ["apt", "god", "apache2", "rails"]
+    end
+
+    it "should not duplicate entries" do
+      @list.add_recipe "apt"
+      @list.should == ["apt", "god", "apache2"]
+    end
+
+    it "should allow you to specify a version" do
+      @list.add_recipe "rails", "1.0.0"
+      @list.should == ["apt", "god", "apache2", "rails"]
+      @list.with_versions.should 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"
+      @list.should == ["apt", "god", "apache2"]
+      @list.with_versions.should 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"
+      @list.with_versions.should 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"
+      @list.with_versions.should 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"
+      lambda {@list.add_recipe "rails", "0.1.0"}.should raise_error Chef::Exceptions::CookbookVersionConflict
+    end
+  end
+
+  describe "with_versions" do
+    before(:each) do
+      @recipes = [
+        {: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
+      @list.with_versions.should == @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[index][:name].should == 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
+    end
+
+    it "should return an array of hashes with :name and :version_constraint" do
+      @list.with_version_constraints.each do |x|
+        x.should have_key :name
+        x[:version_constraint].should_not be nil
+      end
+    end
+  end
+end
diff --git a/spec/unit/run_list_spec.rb b/spec/unit/run_list_spec.rb
new file mode 100644
index 0000000..f18f21a
--- /dev/null
+++ b/spec/unit/run_list_spec.rb
@@ -0,0 +1,312 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Author:: Christopher Walters (<cw at opscode.com>)
+# Copyright:: Copyright (c) 2008-2011 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/version_class'
+require 'chef/version_constraint'
+
+describe Chef::RunList do
+  before(:each) do
+    @run_list = Chef::RunList.new
+  end
+
+  describe "<<" do
+    it "should add a recipe to the run list and recipe list with the fully qualified name" do
+      @run_list << 'recipe[needy]'
+      @run_list.should include('recipe[needy]')
+      @run_list.recipes.should include("needy")
+    end
+
+    it "should add a role to the run list and role list with the fully qualified name" do
+      @run_list << "role[woot]"
+      @run_list.should include('role[woot]')
+      @run_list.roles.should include('woot')
+    end
+
+    it "should accept recipes that are unqualified" do
+      @run_list << "needy"
+      @run_list.should include('recipe[needy]')
+      @run_list.recipes.include?('needy').should == true
+    end
+
+    it "should not allow duplicates" do
+      @run_list << "needy"
+      @run_list << "needy"
+      @run_list.run_list.length.should == 1
+      @run_list.recipes.length.should == 1
+    end
+
+    it "should allow two versions of a recipe" do
+      @run_list << "recipe[needy at 0.2.0]"
+      @run_list << "recipe[needy at 0.1.0]"
+      @run_list.run_list.length.should == 2
+      @run_list.recipes.length.should == 2
+      @run_list.recipes.include?('needy').should == true
+    end
+
+    it "should not allow duplicate versions of a recipe" do
+      @run_list << "recipe[needy at 0.2.0]"
+      @run_list << "recipe[needy at 0.2.0]"
+      @run_list.run_list.length.should == 1
+      @run_list.recipes.length.should == 1
+    end
+  end
+
+  describe "add" do
+    # Testing only the basic functionality here
+    # since full behavior is tested above.
+    it "should add a recipe to the run_list" do
+      @run_list.add 'recipe[needy]'
+      @run_list.should include('recipe[needy]')
+    end
+
+    it "should add a role to the run_list" do
+      @run_list.add 'role[needy]'
+      @run_list.should include('role[needy]')
+    end
+  end
+
+  describe "==" do
+    it "should believe two RunLists are equal if they have the same members" do
+      @run_list << "foo"
+      r = Chef::RunList.new
+      r << "foo"
+      @run_list.should == r
+    end
+
+    it "should believe a RunList is equal to an array named after it's members" do
+      @run_list << "foo"
+      @run_list << "baz"
+      @run_list.should == [ "foo", "baz" ]
+    end
+  end
+
+  describe "empty?" do
+    it "should be emtpy if the run list has no members" do
+      @run_list.empty?.should == true
+    end
+
+    it "should not be empty if the run list has members" do
+      @run_list << "chromeo"
+      @run_list.empty?.should == false
+    end
+  end
+
+  describe "[]" do
+    it "should let you look up a member in the run list by position" do
+      @run_list << 'recipe[loulou]'
+      @run_list[0].should == 'recipe[loulou]'
+    end
+  end
+
+  describe "[]=" do
+    it "should let you set a member of the run list by position" do
+      @run_list[0] = 'recipe[loulou]'
+      @run_list[0].should == 'recipe[loulou]'
+    end
+
+    it "should properly expand a member of the run list given by position" do
+      @run_list[0] = 'loulou'
+      @run_list[0].should == 'recipe[loulou]'
+    end
+  end
+
+  describe "each" do
+    it "should yield each member to your block" do
+      @run_list << "foo"
+      @run_list << "bar"
+      seen = Array.new
+      @run_list.each { |r| seen << r }
+      seen.should be_include("recipe[foo]")
+      seen.should be_include("recipe[bar]")
+    end
+  end
+
+  describe "each_index" do
+    it "should yield each members index to your block" do
+      to_add = [ "recipe[foo]", "recipe[bar]", "recipe[baz]" ]
+      to_add.each { |i| @run_list << i }
+      @run_list.each_index { |i| @run_list[i].should == to_add[i] }
+    end
+  end
+
+  describe "include?" do
+    it "should be true if the run list includes the item" do
+      @run_list << "foo"
+      @run_list.include?("foo")
+    end
+  end
+
+  describe "reset" do
+    it "should reset the run_list based on the array you pass" do
+      @run_list << "chromeo"
+      list = %w{camp chairs snakes clowns}
+      @run_list.reset!(list)
+      list.each { |i| @run_list.should be_include(i) }
+      @run_list.include?("chromeo").should == false
+    end
+  end
+
+  describe "when expanding the run list" do
+    before(:each) do
+      @role = Chef::Role.new
+      @role.name "stubby"
+      @role.run_list "one", "two"
+      @role.default_attributes :one => :two
+      @role.override_attributes :three => :four
+
+      Chef::Role.stub!(:load).and_return(@role)
+      @rest = mock("Chef::REST", { :get_rest => @role, :url => "/" })
+      Chef::REST.stub!(:new).and_return(@rest)
+
+      @run_list << "role[stubby]"
+      @run_list << "kitty"
+    end
+
+    describe "from disk" do
+      it "should load the role from disk" do
+        Chef::Role.should_receive(:from_disk).with("stubby")
+        @run_list.expand("_default", "disk")
+      end
+
+      it "should log a helpful error if the role is not available" do
+        Chef::Role.stub!(:from_disk).and_raise(Chef::Exceptions::RoleNotFound)
+        Chef::Log.should_receive(:error).with("Role stubby (included by 'top level') is in the runlist but does not exist. Skipping expand.")
+        @run_list.expand("_default", "disk")
+      end
+    end
+
+    describe "from the chef server" do
+      it "should load the role from the chef server" do
+        #@rest.should_receive(:get_rest).with("roles/stubby")
+        expansion = @run_list.expand("_default", "server")
+        expansion.recipes.should == ['one', 'two', 'kitty']
+      end
+
+      it "should default to expanding from the server" do
+        @rest.should_receive(:get_rest).with("roles/stubby")
+        @run_list.expand("_default")
+      end
+
+      describe "with an environment set" do
+        before do
+          @role.env_run_list["production"] = Chef::RunList.new( "one", "two", "five")
+        end
+
+        it "expands the run list using the environment specific run list" do
+          expansion = @run_list.expand("production", "server")
+          expansion.recipes.should == %w{one two five kitty}
+        end
+
+        describe "and multiply nested roles" do
+          before do
+            @multiple_rest_requests = mock("Chef::REST")
+
+            @role.env_run_list["production"] << "role[prod-base]"
+
+            @role_prod_base = Chef::Role.new
+            @role_prod_base.name("prod-base")
+            @role_prod_base.env_run_list["production"] = Chef::RunList.new("role[nested-deeper]")
+
+
+            @role_nested_deeper = Chef::Role.new
+            @role_nested_deeper.name("nested-deeper")
+            @role_nested_deeper.env_run_list["production"] = Chef::RunList.new("recipe[prod-secret-sauce]")
+          end
+
+          it "expands the run list using the specified environment for all nested roles" do
+            Chef::REST.stub!(:new).and_return(@multiple_rest_requests)
+            @multiple_rest_requests.should_receive(:get_rest).with("roles/stubby").and_return(@role)
+            @multiple_rest_requests.should_receive(:get_rest).with("roles/prod-base").and_return(@role_prod_base)
+            @multiple_rest_requests.should_receive(:get_rest).with("roles/nested-deeper").and_return(@role_nested_deeper)
+
+            expansion = @run_list.expand("production", "server")
+            expansion.recipes.should == %w{one two five prod-secret-sauce kitty}
+          end
+
+        end
+
+      end
+
+    end
+
+    it "should return the list of expanded recipes" do
+      expansion = @run_list.expand("_default")
+      expansion.recipes[0].should == "one"
+      expansion.recipes[1].should == "two"
+    end
+
+    it "should return the list of default attributes" do
+      expansion = @run_list.expand("_default")
+      expansion.default_attrs[:one].should == :two
+    end
+
+    it "should return the list of override attributes" do
+      expansion = @run_list.expand("_default")
+      expansion.override_attrs[:three].should == :four
+    end
+
+    it "should recurse into a child role" do
+      dog = Chef::Role.new
+      dog.name "dog"
+      dog.default_attributes :seven => :nine
+      dog.run_list "three"
+      @role.run_list << "role[dog]"
+      Chef::Role.stub!(:from_disk).with("stubby").and_return(@role)
+      Chef::Role.stub!(:from_disk).with("dog").and_return(dog)
+
+      expansion = @run_list.expand("_default", 'disk')
+      expansion.recipes[2].should == "three"
+      expansion.default_attrs[:seven].should == :nine
+    end
+
+    it "should not recurse infinitely" do
+      dog = Chef::Role.new
+      dog.name "dog"
+      dog.default_attributes :seven => :nine
+      dog.run_list "role[dog]", "three"
+      @role.run_list << "role[dog]"
+      Chef::Role.stub!(:from_disk).with("stubby").and_return(@role)
+      Chef::Role.should_receive(:from_disk).with("dog").once.and_return(dog)
+
+      expansion = @run_list.expand("_default", 'disk')
+      expansion.recipes[2].should == "three"
+      expansion.recipes[3].should == "kitty"
+      expansion.default_attrs[:seven].should == :nine
+    end
+  end
+
+  describe "when converting to an alternate representation" do
+    before do
+      @run_list << "recipe[nagios::client]" << "role[production]" << "recipe[apache2]"
+    end
+
+    it "converts to an array of the string forms of its items" do
+      @run_list.to_a.should == ["recipe[nagios::client]", "role[production]", "recipe[apache2]"]
+    end
+
+    it "converts to json by converting its array form" do
+      @run_list.to_json.should == ["recipe[nagios::client]", "role[production]", "recipe[apache2]"].to_json
+    end
+
+  end
+
+end
diff --git a/spec/unit/run_lock_spec.rb b/spec/unit/run_lock_spec.rb
new file mode 100644
index 0000000..de302dc
--- /dev/null
+++ b/spec/unit/run_lock_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Daniel DeLeo (<dan 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 File.expand_path('../../spec_helper', __FILE__)
+require 'chef/client'
+
+describe Chef::RunLock do
+
+  default_pid_location = windows? ? 'C:\chef\cache\chef-client-running.pid' : '/var/chef/cache/chef-client-running.pid'
+
+  describe "when first created" do
+    it "locates the lockfile in the file cache path by default" do
+      run_lock = Chef::RunLock.new(Chef::Config.lockfile)
+      run_lock.runlock_file.should == default_pid_location
+    end
+
+    it "locates the lockfile in the user-configured path when set" do
+      Chef::Config.lockfile = "/tmp/chef-client-running.pid"
+      run_lock = Chef::RunLock.new(Chef::Config.lockfile)
+      run_lock.runlock_file.should == "/tmp/chef-client-running.pid"
+    end
+  end
+
+  # See also: spec/functional/run_lock_spec
+
+end
diff --git a/spec/unit/run_status_spec.rb b/spec/unit/run_status_spec.rb
new file mode 100644
index 0000000..049b86c
--- /dev/null
+++ b/spec/unit/run_status_spec.rb
@@ -0,0 +1,145 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+require 'spec_helper'
+
+describe Chef::RunStatus do
+  before do
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, {}, @events)
+    @run_status = Chef::RunStatus.new(@node, @events)
+  end
+
+  describe "before the run context has been set" do
+    it "converts to a hash" do
+      @run_status.to_hash
+    end
+  end
+
+  describe "when the run context has been set" do
+    before do
+      @run_status.run_context = @run_context
+    end
+
+    it "has a run context" do
+      @run_status.run_context.should equal(@run_context)
+    end
+
+    it "provides access to the run context's node" do
+      @run_status.node.should equal(@node)
+    end
+
+    it "converts to a hash" do
+      @run_status.to_hash[:node].should equal(@node)
+      @run_status.to_hash[:success].should be_true
+    end
+
+    describe "after it has recorded timing information" do
+      before do
+        @start_time = Time.new
+        @end_time = @start_time + 23
+        Time.stub!(:now).and_return(@start_time, @end_time)
+        @run_status.start_clock
+        @run_status.stop_clock
+      end
+
+      it "records the start time of the run" do
+        @run_status.start_time.should == @start_time
+      end
+
+      it "records the end time of the run" do
+        @run_status.end_time.should == @end_time
+      end
+
+      it "gives the elapsed time of the chef run" do
+        @run_status.elapsed_time.should == 23
+      end
+
+      it "includes timing information in its hash form" do
+        @run_status.to_hash[:start_time].should == @start_time
+        @run_status.to_hash[:end_time].should == @end_time
+        @run_status.to_hash[:elapsed_time].should == 23
+      end
+
+    end
+
+    describe "with resources in the resource_collection" do
+      before do
+        @all_resources = [Chef::Resource::Cat.new("whiskers"), Chef::Resource::ZenMaster.new('dtz')]
+        @run_context.resource_collection.all_resources.replace(@all_resources)
+      end
+
+      it "lists all resources" do
+        @run_status.all_resources.should == @all_resources
+      end
+
+      it "has no updated resources" do
+        @run_status.updated_resources.should be_empty
+      end
+
+      it "includes the list of all resources in its hash form" do
+        @run_status.to_hash[:all_resources].should == @all_resources
+        @run_status.to_hash[:updated_resources].should be_empty
+      end
+
+      describe "and some have been updated" do
+        before do
+          @all_resources.first.updated = true
+        end
+
+        it "lists the updated resources" do
+          @run_status.updated_resources.should == [@all_resources.first]
+        end
+
+        it "includes the list of updated resources in its hash form" do
+          @run_status.to_hash[:updated_resources].should == [@all_resources.first]
+        end
+      end
+    end
+
+    describe "when the run failed" do
+      before do
+        @exception = Exception.new("just testing")
+        @backtrace = caller
+        @exception.set_backtrace(@backtrace)
+        @run_status.exception = @exception
+      end
+
+      it "stores the exception" do
+        @run_status.exception.should equal(@exception)
+      end
+
+      it "stores the backtrace" do
+        @run_status.backtrace.should == @backtrace
+      end
+
+      it "says the run was not successful" do
+        @run_status.success?.should be_false
+        @run_status.failed?.should be_true
+      end
+
+      it "converts to a hash including the exception information" do
+        @run_status.to_hash[:success].should be_false
+        @run_status.to_hash[:exception].should == "Exception: just testing"
+        @run_status.to_hash[:backtrace].should == @backtrace
+      end
+    end
+  end
+end
diff --git a/spec/unit/runner_spec.rb b/spec/unit/runner_spec.rb
new file mode 100644
index 0000000..2808207
--- /dev/null
+++ b/spec/unit/runner_spec.rb
@@ -0,0 +1,402 @@
+
+# Author:: Adam Jacob (<adam at opscode.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'
+
+class SnitchyProvider < Chef::Provider
+  def self.all_actions_called
+    @all_actions_called ||= []
+  end
+
+  def self.action_called(action)
+    all_actions_called << action
+  end
+
+  def self.clear_action_record
+    @all_actions_called = nil
+  end
+
+  def load_current_resource
+    true
+  end
+
+  def action_first_action
+    @new_resource.updated_by_last_action(true)
+    self.class.action_called(:first)
+  end
+
+  def action_second_action
+    @new_resource.updated_by_last_action(true)
+    self.class.action_called(:second)
+  end
+
+  def action_third_action
+    @new_resource.updated_by_last_action(true)
+    self.class.action_called(:third)
+  end
+
+end
+
+class FailureResource < Chef::Resource
+
+  attr_accessor :action
+
+  def initialize(*args)
+    super
+    @action = :fail
+  end
+
+  def provider
+    FailureProvider
+  end
+end
+
+class FailureProvider < Chef::Provider
+
+  class ChefClientFail < StandardError; end
+
+  def load_current_resource
+    true
+  end
+
+  def action_fail
+    raise ChefClientFail, "chef had an error of some sort"
+  end
+end
+
+describe Chef::Runner do
+
+  before(:each) do
+    @node = Chef::Node.new
+    @node.name "latte"
+    @node.automatic[:platform] = "mac_os_x"
+    @node.automatic[:platform_version] = "10.5.1"
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new({}), @events)
+    @first_resource = Chef::Resource::Cat.new("loulou1", @run_context)
+    @run_context.resource_collection << @first_resource
+    Chef::Platform.set(
+      :resource => :cat,
+      :provider => Chef::Provider::SnakeOil
+    )
+    @runner = Chef::Runner.new(@run_context)
+  end
+
+  it "should pass each resource in the collection to a provider" do
+    @run_context.resource_collection.should_receive(:execute_each_resource).once
+    @runner.converge
+  end
+
+  it "should use the provider specified by the resource (if it has one)" do
+    provider = Chef::Provider::Easy.new(@run_context.resource_collection[0], @run_context)
+    # Expect provider to be called twice, because will fall back to old provider lookup
+    @run_context.resource_collection[0].should_receive(:provider).twice.and_return(Chef::Provider::Easy)
+    Chef::Provider::Easy.should_receive(:new).once.and_return(provider)
+    @runner.converge
+  end
+
+  it "should use the platform provider if it has one" do
+    Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil)
+    @runner.converge
+  end
+
+  it "should run the action for each resource" do
+    Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil)
+    provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context)
+    provider.should_receive(:action_sell).once.and_return(true)
+    Chef::Provider::SnakeOil.should_receive(:new).once.and_return(provider)
+    @runner.converge
+  end
+
+  it "should raise exceptions as thrown by a provider" do
+    provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context)
+    Chef::Provider::SnakeOil.stub!(:new).once.and_return(provider)
+    provider.stub!(:action_sell).once.and_raise(ArgumentError)
+    lambda { @runner.converge }.should raise_error(ArgumentError)
+  end
+
+  it "should not raise exceptions thrown by providers if the resource has ignore_failure set to true" do
+    @run_context.resource_collection[0].stub!(:ignore_failure).and_return(true)
+    provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context)
+    Chef::Provider::SnakeOil.stub!(:new).once.and_return(provider)
+    provider.stub!(:action_sell).once.and_raise(ArgumentError)
+    lambda { @runner.converge }.should_not raise_error(ArgumentError)
+  end
+
+  it "should retry with the specified delay if retries are specified" do
+    @first_resource.retries 3
+    provider = Chef::Provider::SnakeOil.new(@run_context.resource_collection[0], @run_context)
+    Chef::Provider::SnakeOil.stub!(:new).once.and_return(provider)
+    provider.stub!(:action_sell).and_raise(ArgumentError)
+    @first_resource.should_receive(:sleep).with(2).exactly(3).times
+    lambda { @runner.converge }.should raise_error(ArgumentError)
+  end
+
+  it "should execute immediate actions on changed resources" do
+    notifying_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    notifying_resource.action = :purr # only action that will set updated on the resource
+
+    @run_context.resource_collection << notifying_resource
+    @first_resource.action = :nothing # won't be updated unless notified by other resource
+
+    notifying_resource.notifies(:purr, @first_resource, :immediately)
+
+    @runner.converge
+
+    @first_resource.should be_updated
+  end
+
+  it "should follow a chain of actions" do
+    @first_resource.action = :nothing
+
+    middle_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    middle_resource.action = :nothing
+    @run_context.resource_collection << middle_resource
+    middle_resource.notifies(:purr, @first_resource, :immediately)
+
+    last_resource = Chef::Resource::Cat.new("snuffles", @run_context)
+    last_resource.action = :purr
+    @run_context.resource_collection << last_resource
+    last_resource.notifies(:purr, middle_resource, :immediately)
+
+    @runner.converge
+
+    last_resource.should be_updated   # by action(:purr)
+    middle_resource.should be_updated # by notification from last_resource
+    @first_resource.should be_updated # by notification from middle_resource
+  end
+
+  it "should execute delayed actions on changed resources" do
+    @first_resource.action = :nothing
+    second_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    second_resource.action = :purr
+
+    @run_context.resource_collection << second_resource
+    second_resource.notifies(:purr, @first_resource, :delayed)
+
+    @runner.converge
+
+    @first_resource.should be_updated
+  end
+
+  it "should execute delayed notifications when a failure occurs in the chef client run" do
+    @first_resource.action = :nothing
+    second_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    second_resource.action = :purr
+
+    @run_context.resource_collection << second_resource
+    second_resource.notifies(:purr, @first_resource, :delayed)
+
+    third_resource = FailureResource.new("explode", @run_context)
+    @run_context.resource_collection << third_resource
+
+    lambda {@runner.converge}.should raise_error(FailureProvider::ChefClientFail)
+
+    @first_resource.should be_updated
+  end
+
+  it "should execute delayed notifications when a failure occurs in a notification" do
+    @first_resource.action = :nothing
+    second_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    second_resource.action = :purr
+
+    @run_context.resource_collection << second_resource
+
+    third_resource = FailureResource.new("explode", @run_context)
+    third_resource.action = :nothing
+    @run_context.resource_collection << third_resource
+
+    second_resource.notifies(:fail, third_resource, :delayed)
+    second_resource.notifies(:purr, @first_resource, :delayed)
+
+    lambda {@runner.converge}.should raise_error(FailureProvider::ChefClientFail)
+
+    @first_resource.should be_updated
+  end
+
+  it "should execute delayed notifications when a failure occurs in multiple notifications" do
+    @first_resource.action = :nothing
+    second_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    second_resource.action = :purr
+
+    @run_context.resource_collection << second_resource
+
+    third_resource = FailureResource.new("explode", @run_context)
+    third_resource.action = :nothing
+    @run_context.resource_collection << third_resource
+
+    fourth_resource = FailureResource.new("explode again", @run_context)
+    fourth_resource.action = :nothing
+    @run_context.resource_collection << fourth_resource
+
+    second_resource.notifies(:fail, third_resource, :delayed)
+    second_resource.notifies(:fail, fourth_resource, :delayed)
+    second_resource.notifies(:purr, @first_resource, :delayed)
+
+    exception = nil
+    begin
+      @runner.converge
+    rescue => e
+      exception = e
+    end
+    exception.should be_a(Chef::Exceptions::MultipleFailures)
+
+    expected_message =<<-E
+Multiple failures occurred:
+* FailureProvider::ChefClientFail occurred in delayed notification: [explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
+* FailureProvider::ChefClientFail occurred in delayed notification: [explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
+E
+    exception.message.should == expected_message
+
+    @first_resource.should be_updated
+  end
+
+  it "does not duplicate delayed notifications" do
+    SnitchyProvider.clear_action_record
+
+    Chef::Platform.set(
+      :resource => :cat,
+      :provider => SnitchyProvider
+    )
+
+    @first_resource.action = :nothing
+
+    second_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    second_resource.action = :first_action
+    @run_context.resource_collection << second_resource
+
+    third_resource = Chef::Resource::Cat.new("snickers", @run_context)
+    third_resource.action = :first_action
+    @run_context.resource_collection << third_resource
+
+    second_resource.notifies(:second_action, @first_resource, :delayed)
+    second_resource.notifies(:third_action, @first_resource, :delayed)
+
+    third_resource.notifies(:second_action, @first_resource, :delayed)
+    third_resource.notifies(:third_action, @first_resource, :delayed)
+
+    @runner.converge
+    # resources 2 and 3 call :first_action in the course of normal resource
+    # execution, and schedule delayed actions :second and :third on the first
+    # resource. The duplicate actions should "collapse" to a single notification
+    # and order should be preserved.
+    SnitchyProvider.all_actions_called.should == [:first, :first, :second, :third]
+  end
+
+  it "executes delayed notifications in the order they were declared" do
+    SnitchyProvider.clear_action_record
+
+    Chef::Platform.set(
+      :resource => :cat,
+      :provider => SnitchyProvider
+    )
+
+    @first_resource.action = :nothing
+
+    second_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    second_resource.action = :first_action
+    @run_context.resource_collection << second_resource
+
+    third_resource = Chef::Resource::Cat.new("snickers", @run_context)
+    third_resource.action = :first_action
+    @run_context.resource_collection << third_resource
+
+    second_resource.notifies(:second_action, @first_resource, :delayed)
+    second_resource.notifies(:second_action, @first_resource, :delayed)
+
+    third_resource.notifies(:third_action, @first_resource, :delayed)
+    third_resource.notifies(:third_action, @first_resource, :delayed)
+
+    @runner.converge
+    SnitchyProvider.all_actions_called.should == [:first, :first, :second, :third]
+  end
+
+  it "does not fire notifications if the resource was not updated by the last action executed" do
+    # REGRESSION TEST FOR CHEF-1452
+    SnitchyProvider.clear_action_record
+
+    Chef::Platform.set(
+      :resource => :cat,
+      :provider => SnitchyProvider
+    )
+
+    @first_resource.action = :first_action
+
+    second_resource = Chef::Resource::Cat.new("peanut", @run_context)
+    second_resource.action = :nothing
+    @run_context.resource_collection << second_resource
+
+    third_resource = Chef::Resource::Cat.new("snickers", @run_context)
+    third_resource.action = :nothing
+    @run_context.resource_collection << third_resource
+
+    @first_resource.notifies(:second_action, second_resource, :immediately)
+    second_resource.notifies(:third_action, third_resource, :immediately)
+
+    @runner.converge
+
+    # All of the resources should only fire once:
+    SnitchyProvider.all_actions_called.should == [:first, :second, :third]
+
+    # all of the resources should be marked as updated for reporting purposes
+    @first_resource.should be_updated
+    second_resource.should be_updated
+    third_resource.should be_updated
+  end
+
+  it "should check a resource's only_if and not_if if notified by another resource" do
+    @first_resource.action = :buy
+
+    only_if_called_times = 0
+    @first_resource.only_if {only_if_called_times += 1; true}
+
+    not_if_called_times = 0
+    @first_resource.not_if {not_if_called_times += 1; false}
+
+    second_resource = Chef::Resource::Cat.new("carmel", @run_context)
+    @run_context.resource_collection << second_resource
+    second_resource.notifies(:purr, @first_resource, :delayed)
+    second_resource.action = :purr
+
+    # hits only_if first time when the resource is run in order, second on notify
+    @runner.converge
+
+    only_if_called_times.should == 2
+    not_if_called_times.should == 2
+  end
+
+  it "should resolve resource references in notifications when resources are defined lazily" do
+    @first_resource.action = :nothing
+
+    lazy_resources = lambda {
+      last_resource = Chef::Resource::Cat.new("peanut", @run_context)
+      @run_context.resource_collection << last_resource
+      last_resource.notifies(:purr, @first_resource.to_s, :delayed)
+      last_resource.action = :purr
+    }
+    second_resource = Chef::Resource::RubyBlock.new("myblock", @run_context)
+    @run_context.resource_collection << second_resource
+    second_resource.block { lazy_resources.call }
+
+    @runner.converge
+
+    @first_resource.should be_updated
+  end
+
+end
+
diff --git a/spec/unit/scan_access_control_spec.rb b/spec/unit/scan_access_control_spec.rb
new file mode 100644
index 0000000..3cfecfa
--- /dev/null
+++ b/spec/unit/scan_access_control_spec.rb
@@ -0,0 +1,184 @@
+# Author:: Daniel DeLeo (<dan 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 File.expand_path("../../spec_helper", __FILE__)
+require 'chef/scan_access_control'
+
+describe Chef::ScanAccessControl do
+
+  before do
+    @new_resource = Chef::Resource::File.new("/tmp/foo/bar/baz/link")
+    @real_file = "/tmp/foo/bar/real/file"
+    @current_resource = Chef::Resource::File.new(@new_resource.path)
+    @scanner = Chef::ScanAccessControl.new(@new_resource, @current_resource)
+  end
+
+  describe "when the fs entity does not exist" do
+
+    before do
+      @new_resource.tap do |f|
+        f.owner("root")
+        f.group("root")
+        f.mode('0755')
+      end
+      @scanner.set_all!
+    end
+
+    it "does not set any fields on the current resource" do
+      @current_resource.owner.should be_nil
+      @current_resource.group.should be_nil
+      @current_resource.mode.should be_nil
+    end
+
+  end
+
+  describe "when the fs entity exists" do
+
+    before do
+      @stat = mock("File::Stat for #{@new_resource.path}", :uid => 0, :gid => 0, :mode => 00100644)
+      File.should_receive(:realpath).with(@new_resource.path).and_return(@real_file)
+      File.should_receive(:stat).with(@real_file).and_return(@stat)
+      File.should_receive(:exist?).with(@new_resource.path).and_return(true)
+    end
+
+    describe "when new_resource does not specify mode, user or group" do
+      # these tests are necessary for minitest-chef-handler to use as an API, see CHEF-3235
+      before do
+        @scanner.set_all!
+      end
+
+      it "sets the mode of the current resource to the current mode as a String" do
+        @current_resource.mode.should == "0644"
+      end
+
+      context "on unix", :unix_only do
+        it "sets the group of the current resource to the current group as a String" do
+          @current_resource.group.should == Etc.getgrgid(0).name
+        end
+
+        it "sets the owner of the current resource to the current owner as a String" do
+          @current_resource.user.should == "root"
+        end
+      end
+
+      context "on windows", :windows_only do
+        it "sets the group of the current resource to the current group as a String" do
+          @current_resource.group.should == 0
+        end
+
+        it "sets the owner of the current resource to the current owner as a String" do
+          @current_resource.user.should == 0
+        end
+      end
+    end
+
+    describe "when new_resource specifies the mode with a string" do
+      before do
+        @new_resource.mode("0755")
+        @scanner.set_all!
+      end
+
+      it "sets the mode of the current resource to the file's current mode as a string" do
+        @current_resource.mode.should == "0644"
+      end
+    end
+
+    describe "when new_resource specified the mode with an integer" do
+      before do
+        @new_resource.mode(00755)
+        @scanner.set_all!
+      end
+
+      it "sets the mode of the current resource to the current mode as a String" do
+        @current_resource.mode.should == "0644"
+      end
+
+    end
+
+    describe "when new_resource specifies the user with a UID" do
+
+      before do
+        @new_resource.user(0)
+        @scanner.set_all!
+      end
+
+      it "sets the owner of current_resource to the UID of the current owner" do
+        @current_resource.user.should == 0
+      end
+    end
+
+    describe "when new_resource specifies the user with a username" do
+
+      before do
+        @new_resource.user("root")
+      end
+
+      it "sets the owner of current_resource to the username of the current owner" do
+        @root_passwd = mock("Struct::Passwd for uid 0", :name => "root")
+        Etc.should_receive(:getpwuid).with(0).and_return(@root_passwd)
+        @scanner.set_all!
+
+        @current_resource.user.should == "root"
+      end
+
+      describe "and there is no passwd entry for the user" do
+        it "sets the owner of the current_resource to the UID" do
+          Etc.should_receive(:getpwuid).with(0).and_raise(ArgumentError)
+          @scanner.set_all!
+          @current_resource.user.should == 0
+        end
+      end
+    end
+
+    describe "when new_resource specifies the group with a GID" do
+
+      before do
+        @new_resource.group(0)
+        @scanner.set_all!
+      end
+
+      it "sets the group of the current_resource to the gid of the current owner" do
+        @current_resource.group.should == 0
+      end
+
+    end
+
+    describe "when new_resource specifies the group with a group name" do
+      before do
+        @new_resource.group("wheel")
+      end
+
+      it "sets the group of the current resource to the group name" do
+        @group_entry = mock("Struct::Group for wheel", :name => "wheel")
+        Etc.should_receive(:getgrgid).with(0).and_return(@group_entry)
+        @scanner.set_all!
+
+        @current_resource.group.should == "wheel"
+      end
+
+      describe "and there is no group entry for the group" do
+        it "sets the current_resource's group to the GID" do
+          Etc.should_receive(:getgrgid).with(0).and_raise(ArgumentError)
+          @scanner.set_all!
+          @current_resource.group.should == 0
+        end
+      end
+
+    end
+  end
+end
+
diff --git a/spec/unit/search/query_spec.rb b/spec/unit/search/query_spec.rb
new file mode 100644
index 0000000..3f41c14
--- /dev/null
+++ b/spec/unit/search/query_spec.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Copyright:: Copyright (c) 2009,2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/search/query'
+
+describe Chef::Search::Query do
+  before(:each) do
+    @rest = mock("Chef::REST")
+    Chef::REST.stub!(:new).and_return(@rest)
+    @query = Chef::Search::Query.new
+  end
+
+  describe "search" do
+    before(:each) do
+      @response = {
+        "rows" => [
+          { "id" => "for you" },
+          { "id" => "hip hop" },
+          { "id" => "thought was down by law for you" },
+          { "id" => "kept it hard core for you" },
+        ],
+        "start" => 0,
+        "total" => 4
+      }
+      @rest.stub!(:get_rest).and_return(@response)
+    end
+
+    it "should accept a type as the first argument" do
+      lambda { @query.search("foo") }.should_not raise_error(ArgumentError)
+      lambda { @query.search(:foo) }.should_not raise_error(ArgumentError)
+      lambda { @query.search(Hash.new) }.should raise_error(ArgumentError)
+    end
+
+    it "should query for every object of a type by default" do
+      @rest.should_receive(:get_rest).with("search/foo?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
+      @query = Chef::Search::Query.new
+      @query.search(:foo)
+    end
+
+    it "should allow a custom query" do
+      @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
+      @query = Chef::Search::Query.new
+      @query.search(:foo, "gorilla:dundee")
+    end
+
+    it "should let you set a sort order" do
+      @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=id%20desc&start=0&rows=1000").and_return(@response)
+      @query = Chef::Search::Query.new
+      @query.search(:foo, "gorilla:dundee", "id desc")
+    end
+
+    it "should let you set a starting object" do
+      @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=id%20desc&start=2&rows=1000").and_return(@response)
+      @query = Chef::Search::Query.new
+      @query.search(:foo, "gorilla:dundee", "id desc", 2)
+    end
+
+    it "should let you set how many rows to return" do
+      @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=id%20desc&start=2&rows=40").and_return(@response)
+      @query = Chef::Search::Query.new
+      @query.search(:foo, "gorilla:dundee", "id desc", 2, 40)
+    end
+
+    it "should return the raw rows, start, and total if no block is passed" do
+      rows, start, total = @query.search(:foo)
+      rows.should equal(@response["rows"])
+      start.should equal(@response["start"])
+      total.should equal(@response["total"])
+    end
+
+    it "should call a block for each object in the response" do
+      @call_me = mock("blocky")
+      @response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
+      @query.search(:foo) { |r| @call_me.do(r) }
+    end
+
+    it "should page through the responses" do
+      @call_me = mock("blocky")
+      @response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
+      @query.search(:foo, "*:*", nil, 0, 1) { |r| @call_me.do(r) }
+    end
+  end
+end
diff --git a/spec/unit/shell/model_wrapper_spec.rb b/spec/unit/shell/model_wrapper_spec.rb
new file mode 100644
index 0000000..35dc591
--- /dev/null
+++ b/spec/unit/shell/model_wrapper_spec.rb
@@ -0,0 +1,97 @@
+#
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'ostruct'
+
+describe Shell::ModelWrapper do
+  before do
+    @model = OpenStruct.new(:name=>"Chef::Node")
+    @wrapper = Shell::ModelWrapper.new(@model)
+  end
+
+  describe "when created with an explicit model_symbol" do
+    before do
+      @model = OpenStruct.new(:name=>"Chef::ApiClient")
+      @wrapper = Shell::ModelWrapper.new(@model, :client)
+    end
+
+    it "uses the explicit model symbol" do
+      @wrapper.model_symbol.should == :client
+    end
+  end
+
+  it "determines the model symbol from the class name" do
+    @wrapper.model_symbol.should == :node
+  end
+
+  describe "when listing objects" do
+    before do
+      @node_1 = Chef::Node.new
+      @node_1.name("sammich")
+      @node_2 = Chef::Node.new
+      @node_2.name("yummy")
+      @server_response = {:node_1 => @node_1, :node_2 => @node_2}
+      @wrapper = Shell::ModelWrapper.new(Chef::Node)
+      Chef::Node.stub(:list).and_return(@server_response)
+    end
+
+    it "lists fully inflated objects without the resource IDs" do
+      @wrapper.all.should have(2).nodes
+      @wrapper.all.should include(@node_1, @node_2)
+    end
+
+    it "maps the listed nodes when given a block" do
+      @wrapper.all {|n| n.name }.sort.reverse.should == %w{yummy sammich}
+    end
+  end
+
+  describe "when searching for objects" do
+    before do
+      @node_1 = Chef::Node.new
+      @node_1.name("sammich")
+      @node_2 = Chef::Node.new
+      @node_2.name("yummy")
+      @server_response = {:node_1 => @node_1, :node_2 => @node_2}
+      @wrapper = Shell::ModelWrapper.new(Chef::Node)
+
+      # Creating a Chef::Search::Query object tries to read the private key...
+      @searcher = mock("Chef::Search::Query #{__FILE__}:#{__LINE__}")
+      Chef::Search::Query.stub!(:new).and_return(@searcher)
+    end
+
+    it "falls back to listing the objects when the 'query' is :all" do
+      Chef::Node.stub(:list).and_return(@server_response)
+      @wrapper.find(:all).should include(@node_1, @node_2)
+    end
+
+    it "searches for objects using the given query string" do
+      @searcher.should_receive(:search).with(:node, 'name:app*').and_yield(@node_1).and_yield(@node_2)
+      @wrapper.find("name:app*").should include(@node_1, @node_2)
+    end
+
+    it "creates a 'AND'-joined query string from a HASH" do
+      # Hash order woes
+      @searcher.should_receive(:search).with(:node, 'name:app* AND name:app*').and_yield(@node_1).and_yield(@node_2)
+      @wrapper.find(:name=>"app*",'name'=>"app*").should include(@node_1, @node_2)
+    end
+
+  end
+
+
+end
diff --git a/spec/unit/shell/shell_ext_spec.rb b/spec/unit/shell/shell_ext_spec.rb
new file mode 100644
index 0000000..22e9ae6
--- /dev/null
+++ b/spec/unit/shell/shell_ext_spec.rb
@@ -0,0 +1,153 @@
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Shell::Extensions do
+  describe "extending object for top level methods" do
+
+    before do
+      @shell_client = TestableShellSession.instance
+      Shell.stub!(:session).and_return(@shell_client)
+      @job_manager = TestJobManager.new
+      @root_context = Object.new
+      @root_context.instance_eval(&ObjectTestHarness)
+      Shell::Extensions.extend_context_object(@root_context)
+      @root_context.conf = mock("irbconf")
+    end
+
+    it "finds a subsession in irb for an object" do
+      target_context_obj = Chef::Node.new
+
+      irb_context = mock("context", :main => target_context_obj)
+      irb_session = mock("irb session", :context => irb_context)
+      @job_manager.jobs = [[:thread, irb_session]]
+      @root_context.stub!(:jobs).and_return(@job_manager)
+      @root_context.ensure_session_select_defined
+      @root_context.jobs.select_shell_session(target_context_obj).should == irb_session
+      @root_context.jobs.select_shell_session(:idontexist).should be_nil
+    end
+
+    it "finds, then switches to a session" do
+      @job_manager.jobs = []
+      @root_context.stub!(:ensure_session_select_defined)
+      @root_context.stub!(:jobs).and_return(@job_manager)
+      @job_manager.should_receive(:select_shell_session).and_return(:the_shell_session)
+      @job_manager.should_receive(:switch).with(:the_shell_session)
+      @root_context.find_or_create_session_for(:foo)
+    end
+
+    it "creates a new session if an existing one isn't found" do
+      @job_manager.jobs = []
+      @root_context.stub!(:jobs).and_return(@job_manager)
+      @job_manager.stub!(:select_shell_session).and_return(nil)
+      @root_context.should_receive(:irb).with(:foo)
+      @root_context.find_or_create_session_for(:foo)
+    end
+
+    it "switches to recipe context" do
+      @root_context.should respond_to(:recipe_mode)
+      @shell_client.recipe = :monkeyTime
+      @root_context.should_receive(:find_or_create_session_for).with(:monkeyTime)
+      @root_context.recipe_mode
+    end
+
+    it "switches to attribute context" do
+      @root_context.should respond_to(:attributes_mode)
+      @shell_client.node = "monkeyNodeTime"
+      @root_context.should_receive(:find_or_create_session_for).with("monkeyNodeTime")
+      @root_context.attributes_mode
+    end
+
+    it "has a help command" do
+      @root_context.should respond_to(:help)
+    end
+
+    it "turns irb tracing on and off" do
+      @root_context.should respond_to(:trace)
+      @root_context.conf.should_receive(:use_tracer=).with(true)
+      @root_context.stub!(:tracing?)
+      @root_context.tracing :on
+    end
+
+    it "says if tracing is on or off" do
+      @root_context.conf.stub!(:use_tracer).and_return(true)
+      @root_context.should_receive(:puts).with("tracing is on")
+      @root_context.tracing?
+    end
+
+    it "prints node attributes" do
+      node = mock("node", :attribute => {:foo => :bar})
+      @shell_client.node = node
+      @root_context.should_receive(:pp).with({:foo => :bar})
+      @root_context.ohai
+      @root_context.should_receive(:pp).with(:bar)
+      @root_context.ohai(:foo)
+    end
+
+    it "resets the recipe and reloads ohai data" do
+      @shell_client.should_receive(:reset!)
+      @root_context.reset
+    end
+
+    it "turns irb echo on and off" do
+      @root_context.conf.should_receive(:echo=).with(true)
+      @root_context.echo :on
+    end
+
+    it "says if echo is on or off" do
+      @root_context.conf.stub!(:echo).and_return(true)
+      @root_context.should_receive(:puts).with("echo is on")
+      @root_context.echo?
+    end
+
+    it "gives access to the stepable iterator" do
+      Shell::StandAloneSession.instance.stub!(:reset!)
+      Shell.session.stub!(:rebuild_context)
+      events = Chef::EventDispatch::Dispatcher.new
+      run_context = Chef::RunContext.new(Chef::Node.new, {}, events)
+      run_context.resource_collection.instance_variable_set(:@iterator, :the_iterator)
+      Shell.session.run_context = run_context
+      @root_context.chef_run.should == :the_iterator
+    end
+
+    it "lists directory contents" do
+      entries = %w{. .. someFile}
+      Dir.should_receive(:entries).with("/tmp").and_return(entries)
+      @root_context.ls "/tmp"
+    end
+
+  end
+
+  describe "extending the recipe object" do
+
+    before do
+      @events = Chef::EventDispatch::Dispatcher.new
+      @run_context = Chef::RunContext.new(Chef::Node.new, {}, @events)
+      @recipe_object = Chef::Recipe.new(nil, nil, @run_context)
+      Shell::Extensions.extend_context_recipe(@recipe_object)
+    end
+
+    it "gives a list of the resources" do
+      resource = @recipe_object.file("foo")
+      @recipe_object.should_receive(:pp).with(["file[foo]"])
+      @recipe_object.resources
+    end
+
+  end
+end
diff --git a/spec/unit/shell/shell_session_spec.rb b/spec/unit/shell/shell_session_spec.rb
new file mode 100644
index 0000000..a0fc302
--- /dev/null
+++ b/spec/unit/shell/shell_session_spec.rb
@@ -0,0 +1,154 @@
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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 "ostruct"
+
+
+class TestableShellSession < Shell::ShellSession
+
+  def rebuild_node
+    nil
+  end
+
+  def rebuild_collection
+    nil
+  end
+
+  def loading
+    nil
+  end
+
+  def loading_complete
+    nil
+  end
+
+end
+
+describe Shell::ShellSession do
+
+  it "is a singleton object" do
+    Shell::ShellSession.should include(Singleton)
+  end
+
+end
+
+describe Shell::ClientSession do
+  it "builds the node's run_context with the proper environment" do
+    @session = Shell::ClientSession.instance
+    @node = Chef::Node.build("foo")
+    @session.node = @node
+    @session.instance_variable_set(:@client, stub(:sync_cookbooks => {}))
+    @expansion = Chef::RunList::RunListExpansion.new(@node.chef_environment, [])
+
+    @node.run_list.should_receive(:expand).with(@node.chef_environment).and_return(@expansion)
+    @session.rebuild_context
+  end
+end
+
+describe Shell::StandAloneSession do
+  before do
+    @session = Shell::StandAloneSession.instance
+    @node = @session.node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = @session.run_context = Chef::RunContext.new(@node, {}, @events)
+    @recipe = @session.recipe = Chef::Recipe.new(nil, nil, @run_context)
+    Shell::Extensions.extend_context_recipe(@recipe)
+  end
+
+  it "has a run_context" do
+    @session.run_context.should equal(@run_context)
+  end
+
+  it "returns a collection based on it's standalone recipe file" do
+    @session.resource_collection.should == @recipe.run_context.resource_collection
+  end
+
+  it "gives nil for the definitions (for now)" do
+    @session.definitions.should be_nil
+  end
+
+  it "gives nil for the cookbook_loader" do
+    @session.cookbook_loader.should be_nil
+  end
+
+  it "runs chef with the standalone recipe" do
+    @session.stub!(:node_built?).and_return(true)
+    Chef::Log.stub!(:level)
+    chef_runner = mock("Chef::Runner.new", :converge => :converged)
+    # pre-heat resource collection cache
+    @session.resource_collection
+
+    Chef::Runner.should_receive(:new).with(@session.recipe.run_context).and_return(chef_runner)
+    @recipe.run_chef.should == :converged
+  end
+
+end
+
+describe Shell::SoloSession do
+  before do
+    Chef::Config[:shell_solo] = true
+    @session = Shell::SoloSession.instance
+    @node = Chef::Node.new
+    @events = Chef::EventDispatch::Dispatcher.new
+    @run_context = @session.run_context = Chef::RunContext.new(@node, {}, @events)
+    @session.node = @node
+    @recipe = @session.recipe = Chef::Recipe.new(nil, nil, @run_context)
+    Shell::Extensions.extend_context_recipe(@recipe)
+  end
+
+  after do
+    Chef::Config[:shell_solo] = nil
+  end
+
+  it "returns a collection based on it's compilation object and the extra recipe provided by chef-shell" do
+    @session.stub!(:node_built?).and_return(true)
+    kitteh = Chef::Resource::Cat.new("keyboard")
+    @recipe.run_context.resource_collection << kitteh
+    @session.resource_collection.should include(kitteh)
+  end
+
+  it "returns definitions from its compilation object" do
+    @session.definitions.should == @run_context.definitions
+  end
+
+  it "keeps json attribs and passes them to the node for consumption" do
+    @session.node_attributes = {"besnard_lakes" => "are_the_dark_horse"}
+    @session.node.besnard_lakes.should == "are_the_dark_horse"
+    #pending "1) keep attribs in an ivar 2) pass them to the node 3) feed them to the node on reset"
+  end
+
+  it "generates its resource collection from the compiled cookbooks and the ad hoc recipe" do
+    @session.stub!(:node_built?).and_return(true)
+    kitteh_cat = Chef::Resource::Cat.new("kitteh")
+    @run_context.resource_collection << kitteh_cat
+    keyboard_cat = Chef::Resource::Cat.new("keyboard_cat")
+    @recipe.run_context.resource_collection << keyboard_cat
+    #@session.rebuild_collection
+    @session.resource_collection.should include(kitteh_cat, keyboard_cat)
+  end
+
+  it "runs chef with a resource collection from the compiled cookbooks" do
+    @session.stub!(:node_built?).and_return(true)
+    Chef::Log.stub!(:level)
+    chef_runner = mock("Chef::Runner.new", :converge => :converged)
+    Chef::Runner.should_receive(:new).with(an_instance_of(Chef::RunContext)).and_return(chef_runner)
+
+    @recipe.run_chef.should == :converged
+  end
+
+end
diff --git a/spec/unit/shell_out_spec.rb b/spec/unit/shell_out_spec.rb
new file mode 100644
index 0000000..1330dd1
--- /dev/null
+++ b/spec/unit/shell_out_spec.rb
@@ -0,0 +1,18 @@
+require File.expand_path('../../spec_helper', __FILE__)
+
+describe "Chef::ShellOut deprecation notices" do
+  it "logs a warning when initializing a new Chef::ShellOut object" do
+    Chef::Log.should_receive(:warn).with("Chef::ShellOut is deprecated, please use Mixlib::ShellOut")
+    Chef::Log.should_receive(:warn).with(/Called from\:/)
+    Chef::ShellOut.new("pwd")
+  end
+end
+
+describe "Chef::Exceptions::ShellCommandFailed deprecation notices" do
+
+  it "logs a warning when referencing the constant Chef::Exceptions::ShellCommandFailed" do
+    Chef::Log.should_receive(:warn).with("Chef::Exceptions::ShellCommandFailed is deprecated, use Mixlib::ShellOut::ShellCommandFailed")
+    Chef::Log.should_receive(:warn).with(/Called from\:/)
+    Chef::Exceptions::ShellCommandFailed
+  end
+end
diff --git a/spec/unit/shell_spec.rb b/spec/unit/shell_spec.rb
new file mode 100644
index 0000000..fd42417
--- /dev/null
+++ b/spec/unit/shell_spec.rb
@@ -0,0 +1,161 @@
+# Author:: Daniel DeLeo (<dan at kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# 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 "ostruct"
+
+ObjectTestHarness = Proc.new do
+  extend Shell::Extensions::ObjectCoreExtensions
+
+  def conf=(new_conf)
+    @conf = new_conf
+  end
+
+  def conf
+    @conf
+  end
+
+  desc "rspecin'"
+  def rspec_method
+  end
+end
+
+class TestJobManager
+  attr_accessor :jobs
+end
+
+describe Shell do
+
+  before do
+    Shell.irb_conf = {}
+    Shell::ShellSession.instance.rspec_reset
+    Shell::ShellSession.instance.stub!(:reset!)
+  end
+
+  describe "reporting its status" do
+
+    it "alway says it is running" do
+      Shell.should be_running
+    end
+
+  end
+
+  describe "configuring IRB" do
+    it "configures irb history" do
+      Shell.configure_irb
+      Shell.irb_conf[:HISTORY_FILE].should == "~/.chef/chef_shell_history"
+      Shell.irb_conf[:SAVE_HISTORY].should == 1000
+    end
+
+    it "has a prompt like ``chef > '' in the default context" do
+      Shell.configure_irb
+
+      conf = OpenStruct.new
+      conf.main = Object.new
+      conf.main.instance_eval(&ObjectTestHarness)
+      Shell.irb_conf[:IRB_RC].call(conf)
+      conf.prompt_c.should      == "chef > "
+      conf.return_format.should == " => %s \n"
+      conf.prompt_i.should      == "chef > "
+      conf.prompt_n.should      == "chef ?> "
+      conf.prompt_s.should      == "chef%l> "
+
+    end
+
+    it "has a prompt like ``chef:recipe > '' in recipe context" do
+      Shell.configure_irb
+
+      conf = OpenStruct.new
+      events = Chef::EventDispatch::Dispatcher.new
+      conf.main = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, events))
+      Shell.irb_conf[:IRB_RC].call(conf)
+      conf.prompt_c.should      == "chef:recipe > "
+      conf.prompt_i.should      == "chef:recipe > "
+      conf.prompt_n.should      == "chef:recipe ?> "
+      conf.prompt_s.should      == "chef:recipe%l> "
+    end
+
+    it "has a prompt like ``chef:attributes > '' in attributes/node context" do
+      Shell.configure_irb
+
+      conf = OpenStruct.new
+      conf.main = Chef::Node.new
+      Shell.irb_conf[:IRB_RC].call(conf)
+      conf.prompt_c.should      == "chef:attributes > "
+      conf.prompt_i.should      == "chef:attributes > "
+      conf.prompt_n.should      == "chef:attributes ?> "
+      conf.prompt_s.should      == "chef:attributes%l> "
+    end
+
+  end
+
+  describe "convenience macros for creating the chef object" do
+
+    before do
+      @chef_object = Object.new
+      @chef_object.instance_eval(&ObjectTestHarness)
+    end
+
+    it "creates help text for methods with descriptions" do
+      @chef_object.help_descriptions.should == [Shell::Extensions::Help.new("rspec_method", "rspecin'", nil)]
+    end
+
+    it "adds help text when a new method is described then defined" do
+      describe_define =<<-EVAL
+        desc "foo2the Bar"
+        def baz
+        end
+      EVAL
+      @chef_object.instance_eval describe_define
+      @chef_object.help_descriptions.should == [Shell::Extensions::Help.new("rspec_method", "rspecin'"),
+                                                Shell::Extensions::Help.new("baz", "foo2the Bar")]
+    end
+
+    it "adds help text for subcommands" do
+      describe_define =<<-EVAL
+        subcommands :baz_obj_command => "something you can do with baz.baz_obj_command"
+        def baz
+        end
+      EVAL
+      @chef_object.instance_eval describe_define
+      expected_help_text_fragments = [Shell::Extensions::Help.new("rspec_method", "rspecin'")]
+      expected_help_text_fragments << Shell::Extensions::Help.new("baz.baz_obj_command", "something you can do with baz.baz_obj_command")
+      @chef_object.help_descriptions.should == expected_help_text_fragments
+    end
+
+    it "doesn't add previous subcommand help to commands defined afterward" do
+      describe_define =<<-EVAL
+        desc "swingFromTree"
+        def monkey_time
+        end
+
+        def super_monkey_time
+        end
+
+      EVAL
+      @chef_object.instance_eval describe_define
+      @chef_object.help_descriptions.should have(2).descriptions
+      @chef_object.help_descriptions.select {|h| h.cmd == "super_monkey_time" }.should be_empty
+    end
+
+    it "creates a help banner with the command descriptions" do
+      @chef_object.help_banner.should match(/^\|\ Command[\s]+\|\ Description[\s]*$/)
+      @chef_object.help_banner.should match(/^\|\ rspec_method[\s]+\|\ rspecin\'[\s]*$/)
+    end
+  end
+
+end
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
new file mode 100644
index 0000000..9121bab
--- /dev/null
+++ b/spec/unit/user_spec.rb
@@ -0,0 +1,255 @@
+#
+# 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'
+require 'tempfile'
+
+describe Chef::User do
+  before(:each) do
+    @user = Chef::User.new
+  end
+
+  describe "initialize" do
+    it "should be a Chef::User" do
+      @user.should be_a_kind_of(Chef::User)
+    end
+  end
+
+  describe "name" do
+    it "should let you set the name to a string" do
+      @user.name("ops_master").should == "ops_master"
+    end
+
+    it "should return the current name" do
+      @user.name "ops_master"
+      @user.name.should == "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
+      lambda { @user.name "Bar" }.should raise_error(ArgumentError)
+      # slashes
+      lambda { @user.name "foo/bar" }.should raise_error(ArgumentError)
+      # ?
+      lambda { @user.name "foo?" }.should raise_error(ArgumentError)
+      # &
+      lambda { @user.name "foo&" }.should raise_error(ArgumentError)
+    end
+
+
+    it "should not accept spaces" do
+      lambda { @user.name "ops master" }.should raise_error(ArgumentError)
+    end
+
+    it "should throw an ArgumentError if you feed it anything but a string" do
+      lambda { @user.name Hash.new }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "admin" do
+    it "should let you set the admin bit" do
+      @user.admin(true).should == true
+    end
+
+    it "should return the current admin value" do
+      @user.admin true
+      @user.admin.should == true
+    end
+
+    it "should default to false" do
+      @user.admin.should == false
+    end
+
+    it "should throw an ArgumentError if you feed it anything but true or false" do
+      lambda { @user.name Hash.new }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "public_key" do
+    it "should let you set the public key" do
+      @user.public_key("super public").should == "super public"
+    end
+
+    it "should return the current public key" do
+      @user.public_key("super public")
+      @user.public_key.should == "super public"
+    end
+
+    it "should throw an ArgumentError if you feed it something lame" do
+      lambda { @user.public_key Hash.new }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "private_key" do
+    it "should let you set the private key" do
+      @user.private_key("super private").should == "super private"
+    end
+
+    it "should return the private key" do
+      @user.private_key("super private")
+      @user.private_key.should == "super private"
+    end
+
+    it "should throw an ArgumentError if you feed it something lame" do
+      lambda { @user.private_key Hash.new }.should raise_error(ArgumentError)
+    end
+  end
+
+  describe "when serializing to JSON" do
+    before(:each) do
+      @user.name("black")
+      @user.public_key("crowes")
+      @json = @user.to_json
+    end
+
+    it "serializes as a JSON object" do
+      @json.should match(/^\{.+\}$/)
+    end
+
+    it "includes the name value" do
+      @json.should include(%q{"name":"black"})
+    end
+
+    it "includes the public key value" do
+      @json.should include(%{"public_key":"crowes"})
+    end
+
+    it "includes the 'admin' flag" do
+      @json.should include(%q{"admin":false})
+    end
+
+    it "includes the private key when present" do
+      @user.private_key("monkeypants")
+      @user.to_json.should include(%q{"private_key":"monkeypants"})
+    end
+
+    it "does not include the private key if not present" do
+      @json.should_not include("private_key")
+    end
+
+    it "includes the password if present" do
+      @user.password "password"
+      @user.to_json.should include(%q{"password":"password"})
+    end
+
+    it "does not include the password if not present" do
+      @json.should_not include("password")
+    end
+  end
+
+  describe "when deserializing from JSON" do
+    before(:each) do
+      user = { "name" => "mr_spinks",
+        "public_key" => "turtles",
+        "private_key" => "pandas",
+        "password" => "password",
+        "admin" => true }
+      @user = Chef::User.from_json(user.to_json)
+    end
+
+    it "should deserialize to a Chef::User object" do
+      @user.should be_a_kind_of(Chef::User)
+    end
+
+    it "preserves the name" do
+      @user.name.should == "mr_spinks"
+    end
+
+    it "preserves the public key" do
+      @user.public_key.should == "turtles"
+    end
+
+    it "preserves the admin status" do
+      @user.admin.should be_true
+    end
+
+    it "includes the private key if present" do
+      @user.private_key.should == "pandas"
+    end
+
+    it "includes the password if present" do
+      @user.password.should == "password"
+    end
+
+  end
+
+  describe "API Interactions" do
+    before (:each) do
+      @user = Chef::User.new
+      @user.name "foobar"
+      @http_client = mock("Chef::REST mock")
+      Chef::REST.stub!(: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" }} ]
+      end
+
+      it "lists all clients on an OSC server" do
+        @http_client.stub!(:get_rest).with("users").and_return(@osc_response)
+        Chef::User.list.should == @osc_response
+      end
+
+      it "lists all clients on an OHC/OPC server" do
+        @http_client.stub!(:get_rest).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.
+        Chef::User.list.should == @osc_response
+      end
+    end
+
+    describe "create" do
+      it "creates a new user via the API" do
+        @user.password "password"
+        @http_client.should_receive(:post_rest).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
+        @http_client.should_receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
+        user = Chef::User.load("foobar")
+        user.name.should == "foobar"
+        user.admin.should == true
+        user.public_key.should == "pubkey"
+      end
+    end
+
+    describe "update" do
+      it "updates an existing user on via the API" do
+        @http_client.should_receive(:put_rest).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
+        @http_client.should_receive(:delete_rest).with("users/foobar")
+        @user.destroy
+      end
+    end
+  end
+end
diff --git a/spec/unit/util/backup_spec.rb b/spec/unit/util/backup_spec.rb
new file mode 100644
index 0000000..c49694f
--- /dev/null
+++ b/spec/unit/util/backup_spec.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+require 'spec_helper'
+require 'tmpdir'
+
+describe Chef::Util::Backup do
+
+  let (:tempfile) do
+    Tempfile.new("chef-util-backup-spec-test")
+  end
+
+  before(:each) do
+    @new_resource = mock("new_resource")
+    @new_resource.should_receive(:path).at_least(:once).and_return(tempfile.path)
+    @backup = Chef::Util::Backup.new(@new_resource)
+  end
+
+  it "should store the resource passed to new as new_resource" do
+    @backup.new_resource.should eql(@new_resource)
+  end
+
+  describe "for cases when we don't want to back anything up" do
+
+    before(:each) do
+      @backup.should_not_receive(:do_backup)
+    end
+
+    it "should not attempt to backup a file if :backup is false" do
+      @new_resource.should_receive(:backup).at_least(:once).and_return(false)
+      @backup.backup!
+    end
+
+    it "should not attempt to backup a file if :backup == 0" do
+      @new_resource.should_receive(:backup).at_least(:once).and_return(0)
+      @backup.backup!
+    end
+
+    it "should not attempt to backup a file if it does not exist" do
+      @new_resource.should_receive(:backup).at_least(:once).and_return(1)
+      File.should_receive(:exist?).with(tempfile.path).at_least(:once).and_return(false)
+      @backup.backup!
+    end
+
+  end
+
+  describe "for cases when we want to back things up" do
+    before(:each) do
+      @backup.should_receive(:do_backup)
+    end
+
+    describe "when the number of backups is specified as 1" do
+      before(:each) do
+        @new_resource.should_receive(:backup).at_least(:once).and_return(1)
+      end
+
+      it "should not delete anything if this is the only backup" do
+        @backup.should_receive(:sorted_backup_files).and_return(['a'])
+        @backup.should_not_receive(:delete_backup)
+        @backup.backup!
+      end
+
+      it "should keep only 1 backup copy" do
+        @backup.should_receive(:sorted_backup_files).and_return(['a', 'b', 'c'])
+        @backup.should_receive(:delete_backup).with('b')
+        @backup.should_receive(:delete_backup).with('c')
+        @backup.backup!
+      end
+    end
+
+    describe "when the number of backups is specified as 2" do
+      before(:each) do
+        @new_resource.should_receive(:backup).at_least(:once).and_return(2)
+      end
+
+      it "should not delete anything if we only have one other backup" do
+        @backup.should_receive(:sorted_backup_files).and_return(['a', 'b'])
+        @backup.should_not_receive(:delete_backup)
+        @backup.backup!
+      end
+
+      it "should keep only 2 backup copies" do
+        @backup.should_receive(:sorted_backup_files).and_return(['a', 'b', 'c', 'd'])
+        @backup.should_receive(:delete_backup).with('c')
+        @backup.should_receive(:delete_backup).with('d')
+        @backup.backup!
+      end
+    end
+  end
+
+  describe "backup_filename" do
+    it "should return a timestamped path" do
+      @backup.should_receive(:path).and_return('/a/b/c.txt')
+      @backup.send(:backup_filename).should =~ %r|^/a/b/c.txt.chef-\d{14}.\d{6}$|
+    end
+    it "should strip the drive letter off for windows" do
+      @backup.should_receive(:path).and_return('c:\a\b\c.txt')
+      @backup.send(:backup_filename).should =~ %r|^\\a\\b\\c.txt.chef-\d{14}.\d{6}$|
+    end
+    it "should strip the drive letter off for windows (with forwardslashes)" do
+      @backup.should_receive(:path).and_return('c:/a/b/c.txt')
+      @backup.send(:backup_filename).should =~ %r|^/a/b/c.txt.chef-\d{14}.\d{6}$|
+    end
+  end
+
+  describe "backup_path" do
+    it "uses the file's directory when Chef::Config[:file_backup_path] is nil" do
+      @backup.should_receive(:path).and_return('/a/b/c.txt')
+      Chef::Config[:file_backup_path] = nil
+      @backup.send(:backup_path).should =~ %r|^/a/b/c.txt.chef-\d{14}.\d{6}$|
+    end
+
+    it "uses the configured Chef::Config[:file_backup_path]" do
+      @backup.should_receive(:path).and_return('/a/b/c.txt')
+      Chef::Config[:file_backup_path] = '/backupdir'
+      @backup.send(:backup_path).should =~ %r|^/backupdir[\\/]+a/b/c.txt.chef-\d{14}.\d{6}$|
+    end
+
+    it "uses the configured Chef::Config[:file_backup_path] and strips the drive on windows" do
+      @backup.should_receive(:path).and_return('c:\\a\\b\\c.txt')
+      Chef::Config[:file_backup_path] = 'c:\backupdir'
+      @backup.send(:backup_path).should =~ %r|^c:\\backupdir[\\/]+a\\b\\c.txt.chef-\d{14}.\d{6}$|
+    end
+  end
+
+end
diff --git a/spec/unit/util/diff_spec.rb b/spec/unit/util/diff_spec.rb
new file mode 100644
index 0000000..947ce1d
--- /dev/null
+++ b/spec/unit/util/diff_spec.rb
@@ -0,0 +1,579 @@
+#
+# Author:: Lamont Granquist (<lamont at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+require 'spec_helper'
+require 'tmpdir'
+
+shared_context "using file paths with spaces" do
+  let!(:old_tempfile) { Tempfile.new("chef-util diff-spec") }
+  let!(:new_tempfile) { Tempfile.new("chef-util diff-spec") }
+end
+
+shared_context "using file paths without spaces" do
+  let!(:old_tempfile) { Tempfile.new("chef-util-diff-spec") }
+  let!(:new_tempfile) { Tempfile.new("chef-util-diff-spec") }
+end
+
+shared_examples_for "a diff util" do
+  it "should return a Chef::Util::Diff" do
+    expect(differ).to be_a_kind_of(Chef::Util::Diff)
+  end
+
+  it "produces a diff even if the old_file does not exist" do
+    old_tempfile.close
+    old_tempfile.unlink
+    expect(differ.for_output).to eql(["(no diff)"])
+  end
+
+  it "produces a diff even if the new_file does not exist" do
+    new_tempfile.close
+    new_tempfile.unlink
+    expect(differ.for_output).to eql(["(no diff)"])
+  end
+
+  describe "when the two files exist with no content" do
+    it "calling for_output should return the error message" do
+      expect(differ.for_output).to eql(["(no diff)"])
+    end
+
+    it "calling for_reporting should be nil" do
+      expect(differ.for_reporting).to be_nil
+    end
+  end
+
+  describe "when diffs are disabled" do
+    before do
+      Chef::Config[:diff_disabled] = true
+    end
+
+    after do
+      Chef::Config[:diff_disabled] = false
+    end
+
+    it "calling for_output should return the error message" do
+      expect(differ.for_output).to eql( [ "(diff output suppressed by config)" ] )
+    end
+
+    it "calling for_reporting should be nil" do
+      expect(differ.for_reporting).to be_nil
+    end
+  end
+
+  describe "when the old_file has binary content" do
+    before do
+      old_tempfile.write("\x01\xff")
+      old_tempfile.close
+    end
+
+    it "calling for_output should return the error message" do
+      expect(differ.for_output).to eql( [ "(current file is binary, diff output suppressed)" ] )
+    end
+
+    it "calling for_reporting should be nil" do
+      expect(differ.for_reporting).to be_nil
+    end
+  end
+
+  describe "when the new_file has binary content" do
+    before do
+      new_tempfile.write("\x01\xff")
+      new_tempfile.close
+    end
+
+    it "calling for_output should return the error message" do
+      expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+    end
+
+    it "calling for_reporting should be nil" do
+      expect(differ.for_reporting).to be_nil
+    end
+  end
+
+  describe "when the default external encoding is UTF-8", :ruby_gte_19_only  do
+
+    before do
+      @saved_default_external = Encoding.default_external
+      Encoding.default_external = Encoding::UTF_8
+    end
+
+    after do
+      Encoding.default_external = @saved_default_external
+    end
+
+    describe "when a file has ASCII text" do
+      before do
+        new_tempfile.write(plain_ascii)
+        new_tempfile.close
+      end
+      it "calling for_output should return a valid diff" do
+        differ.for_output.join("\\n").should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+      end
+      it "calling for_reporting should return a utf-8 string" do
+        expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8)
+      end
+    end
+
+    describe "when a file has UTF-8 text" do
+      before do
+        new_tempfile.write(utf_8)
+        new_tempfile.close
+      end
+      it "calling for_output should return a valid diff" do
+        differ.for_output.join("\\n").should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+      end
+      it "calling for_reporting should return a utf-8 string" do
+        expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8)
+      end
+    end
+
+    describe "when a file has Latin-1 text" do
+      before do
+        new_tempfile.write(latin_1)
+        new_tempfile.close
+      end
+      it "calling for_output should complain that the content is binary" do
+        expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+      end
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+
+    describe "when a file has Shift-JIS text" do
+      before do
+        new_tempfile.write(shift_jis)
+        new_tempfile.close
+      end
+      it "calling for_output should complain that the content is binary" do
+        expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+      end
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+
+  end
+
+  describe "when the default external encoding is Latin-1", :ruby_gte_19_only  do
+
+    before do
+      @saved_default_external = Encoding.default_external
+      Encoding.default_external = Encoding::ISO_8859_1
+    end
+
+    after do
+      Encoding.default_external = @saved_default_external
+    end
+
+    describe "when a file has ASCII text" do
+      before do
+        new_tempfile.write(plain_ascii)
+        new_tempfile.close
+      end
+      it "calling for_output should return a valid diff" do
+        differ.for_output.join("\\n").should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+      end
+      it "calling for_reporting should return a utf-8 string" do
+        expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8)
+      end
+    end
+
+    describe "when a file has UTF-8 text" do
+      before do
+        new_tempfile.write(utf_8)
+        new_tempfile.close
+      end
+      it "calling for_output should complain that the content is binary" do
+        expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+      end
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+
+    describe "when a file has Latin-1 text" do
+      before do
+        new_tempfile.write(latin_1)
+        new_tempfile.close
+      end
+      it "calling for_output should return a valid diff" do
+        differ.for_output.join("\\n").should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+      end
+      it "calling for_reporting should return a utf-8 string" do
+        expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8)
+      end
+    end
+
+    describe "when a file has Shift-JIS text" do
+      before do
+        new_tempfile.write(shift_jis)
+        new_tempfile.close
+      end
+      it "calling for_output should complain that the content is binary" do
+        expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+      end
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+
+  end
+  describe "when the default external encoding is Shift_JIS", :ruby_gte_19_only  do
+
+    before do
+      @saved_default_external = Encoding.default_external
+      Encoding.default_external = Encoding::Shift_JIS
+    end
+
+    after do
+      Encoding.default_external = @saved_default_external
+    end
+
+    describe "when a file has ASCII text" do
+      before do
+        new_tempfile.write(plain_ascii)
+        new_tempfile.close
+      end
+      it "calling for_output should return a valid diff" do
+        differ.for_output.join("\\n").should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+      end
+      it "calling for_reporting should return a utf-8 string" do
+        expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8)
+      end
+    end
+
+    describe "when a file has UTF-8 text" do
+      before do
+        new_tempfile.write(utf_8)
+        new_tempfile.close
+      end
+      it "calling for_output should complain that the content is binary" do
+        expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+      end
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+
+    describe "when a file has Latin-1 text" do
+      before do
+        new_tempfile.write(latin_1)
+        new_tempfile.close
+      end
+      it "calling for_output should complain that the content is binary" do
+        expect(differ.for_output).to eql( [ "(new content is binary, diff output suppressed)" ])
+      end
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+
+    describe "when a file has Shift-JIS text" do
+      before do
+        new_tempfile.write(shift_jis)
+        new_tempfile.close
+      end
+      it "calling for_output should return a valid diff" do
+        differ.for_output.join("\\n").should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+      end
+      it "calling for_reporting should return a utf-8 string" do
+        expect(differ.for_reporting.encoding).to equal(Encoding::UTF_8)
+      end
+    end
+
+  end
+
+  describe "when testing the diff_filesize_threshold" do
+    before do
+      @diff_filesize_threshold_saved = Chef::Config[:diff_filesize_threshold]
+      Chef::Config[:diff_filesize_threshold] = 10
+    end
+
+    after do
+      Chef::Config[:diff_filesize_threshold] = @diff_filesize_threshold_saved
+    end
+
+    describe "when the old_file goes over the threshold" do
+      before do
+        old_tempfile.write("But thats what you get when Wu-Tang raised you")
+        old_tempfile.close
+      end
+
+      it "calling for_output should return the error message" do
+        expect(differ.for_output).to eql( [ "(file sizes exceed 10 bytes, diff output suppressed)" ])
+      end
+
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+
+    describe "when the new_file goes over the threshold" do
+      before do
+        new_tempfile.write("But thats what you get when Wu-Tang raised you")
+        new_tempfile.close
+      end
+
+      it "calling for_output should return the error message" do
+        expect(differ.for_output).to eql( [ "(file sizes exceed 10 bytes, diff output suppressed)" ])
+      end
+
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+  end
+
+  describe "when generating a valid diff" do
+    before do
+      old_tempfile.write("foo")
+      old_tempfile.close
+      new_tempfile.write("bar")
+      new_tempfile.close
+    end
+
+    it "calling for_output should return a unified diff" do
+      differ.for_output.size.should eql(5)
+      differ.for_output.join("\\n").should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+    end
+
+    it "calling for_reporting should return a unified diff" do
+      differ.for_reporting.should match(/\A--- .*\\n\+\+\+ .*\\n@@/m)
+    end
+
+    describe "when the diff output is too long" do
+
+      before do
+        @diff_output_threshold_saved = Chef::Config[:diff_output_threshold]
+        Chef::Config[:diff_output_threshold] = 10
+      end
+
+      after do
+        Chef::Config[:diff_output_threshold] = @diff_output_threshold_saved
+      end
+
+      it "calling for_output should return the error message" do
+        expect(differ.for_output).to eql(["(long diff of over 10 characters, diff output suppressed)"])
+      end
+
+      it "calling for_reporting should be nil" do
+        expect(differ.for_reporting).to be_nil
+      end
+    end
+  end
+
+  describe "when checking if files are binary or text" do
+
+    it "should identify zero-length files as text" do
+      Tempfile.open("chef-util-diff-spec") do |file|
+        file.close
+        differ.send(:is_binary?, file.path).should be_false
+      end
+    end
+
+    it "should identify text files as text" do
+      Tempfile.open("chef-util-diff-spec") do |file|
+        file.write(plain_ascii)
+        file.close
+        differ.send(:is_binary?, file.path).should be_false
+      end
+    end
+
+    it "should identify a null-terminated string files as binary" do
+      Tempfile.open("chef-util-diff-spec") do |file|
+        file.write("This is a binary file.\0")
+        file.close
+        differ.send(:is_binary?, file.path).should be_true
+      end
+    end
+
+    it "should identify null-teriminated multi-line string files as binary" do
+      Tempfile.open("chef-util-diff-spec") do |file|
+        file.write("This is a binary file.\nNo Really\nit is\0")
+        file.close
+        differ.send(:is_binary?, file.path).should be_true
+      end
+    end
+
+    describe "when the default external encoding is UTF-8", :ruby_gte_19_only  do
+
+      before do
+        @saved_default_external = Encoding.default_external
+        Encoding.default_external = Encoding::UTF_8
+      end
+
+      after do
+        Encoding.default_external = @saved_default_external
+      end
+
+      it "should identify normal ASCII as text" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(plain_ascii)
+          file.close
+          differ.send(:is_binary?, file.path).should be_false
+        end
+      end
+
+      it "should identify UTF-8 as text" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(utf_8)
+          file.close
+          differ.send(:is_binary?, file.path).should be_false
+        end
+      end
+
+      it "should identify Latin-1 that is invalid UTF-8 as binary" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(latin_1)
+          file.close
+          differ.send(:is_binary?, file.path).should be_true
+        end
+      end
+
+      it "should identify Shift-JIS that is invalid UTF-8 as binary" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(shift_jis)
+          file.close
+          differ.send(:is_binary?, file.path).should be_true
+        end
+      end
+
+    end
+
+    describe "when the default external encoding is Latin-1", :ruby_gte_19_only do
+
+      before do
+        @saved_default_external = Encoding.default_external
+        Encoding.default_external = Encoding::ISO_8859_1
+      end
+
+      after do
+        Encoding.default_external = @saved_default_external
+      end
+
+      it "should identify normal ASCII as text" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(plain_ascii)
+          file.close
+          differ.send(:is_binary?, file.path).should be_false
+        end
+      end
+
+      it "should identify UTF-8 that is invalid Latin-1 as binary" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(utf_8)
+          file.close
+          differ.send(:is_binary?, file.path).should be_true
+        end
+      end
+
+      it "should identify Latin-1 as text" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(latin_1)
+          file.close
+          differ.send(:is_binary?, file.path).should be_false
+        end
+      end
+
+      it "should identify Shift-JIS that is invalid Latin-1 as binary" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(shift_jis)
+          file.close
+          differ.send(:is_binary?, file.path).should be_true
+        end
+      end
+    end
+
+    describe "when the default external encoding is Shift-JIS", :ruby_gte_19_only do
+
+      before do
+        @saved_default_external = Encoding.default_external
+        Encoding.default_external = Encoding::Shift_JIS
+      end
+
+      after do
+        Encoding.default_external = @saved_default_external
+      end
+
+      it "should identify normal ASCII as text" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(plain_ascii)
+          file.close
+          differ.send(:is_binary?, file.path).should be_false
+        end
+      end
+      it "should identify UTF-8 that is invalid Shift-JIS as binary" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(utf_8)
+          file.close
+          differ.send(:is_binary?, file.path).should be_true
+        end
+      end
+
+      it "should identify Latin-1 that is invalid Shift-JIS as binary" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(latin_1)
+          file.close
+          differ.send(:is_binary?, file.path).should be_true
+        end
+      end
+
+      it "should identify Shift-JIS as text" do
+        Tempfile.open("chef-util-diff-spec") do |file|
+          file.write(shift_jis)
+          file.close
+          differ.send(:is_binary?, file.path).should be_false
+        end
+      end
+
+    end
+  end
+
+end
+
+describe Chef::Util::Diff, :uses_diff => true do
+  let!(:old_file) { old_tempfile.path }
+  let!(:new_file) { new_tempfile.path }
+
+  let(:plain_ascii) { "This is a text file.\nWith more than one line.\nAnd a \tTab.\nAnd lets make sure that other printable chars work too: ~!@\#$%^&*()`:\"<>?{}|_+,./;'[]\\-=\n" }
+  # these are all byte sequences that are illegal in the other encodings... (but they may legally transcode)
+  let(:utf_8) { "testing utf-8 unicode...\n\n\non a new line: \xE2\x80\x93\n" }  # unicode em-dash
+  let(:latin_1) { "It is more metal.\nif you have an \xFDmlaut.\n" } # NB: changed to y-with-diaresis, but i'm American so I don't know the difference
+  let(:shift_jis) { "I have no idea what this character is:\n \x83\x80.\n" } # seriously, no clue, but \x80 is nice and illegal in other encodings
+
+  let(:differ) do  # subject
+    differ = Chef::Util::Diff.new
+    differ.diff(old_file, new_file)
+    differ
+  end
+
+  describe "when file path has spaces" do
+    include_context "using file paths with spaces"
+
+    it_behaves_like "a diff util"
+  end
+
+
+  describe "when file path doesn't have spaces" do
+    include_context "using file paths without spaces"
+
+    it_behaves_like "a diff util"
+  end
+end
+
diff --git a/spec/unit/util/file_edit_spec.rb b/spec/unit/util/file_edit_spec.rb
new file mode 100644
index 0000000..3cf6017
--- /dev/null
+++ b/spec/unit/util/file_edit_spec.rb
@@ -0,0 +1,135 @@
+#
+# Author:: Nuo Yan (<nuo at opscode.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'
+
+describe Chef::Util::FileEdit do
+
+  before(:each) do
+
+    @hosts_content=<<-HOSTS
+127.0.0.1       localhost
+255.255.255.255 broadcasthost
+::1             localhost
+fe80::1%lo0     localhost
+HOSTS
+
+    @tempfile = Tempfile.open('file_edit_spec')
+    @tempfile.write(@hosts_content)
+    @tempfile.close
+    @fedit = Chef::Util::FileEdit.new(@tempfile.path)
+  end
+
+  after(:each) do
+    @tempfile && @tempfile.close!
+  end
+
+  describe "initialiize" do
+    it "should create a new Chef::Util::FileEdit object" do
+      Chef::Util::FileEdit.new(@tempfile.path).should be_kind_of(Chef::Util::FileEdit)
+    end
+
+    it "should throw an exception if the input file does not exist" do
+      lambda{Chef::Util::FileEdit.new("nonexistfile")}.should raise_error
+    end
+
+    it "should throw an exception if the input file is blank" do
+      lambda do
+        Chef::Util::FileEdit.new(File.join(CHEF_SPEC_DATA, "filedit", "blank"))
+      end.should raise_error
+    end
+  end
+
+  describe "search_file_replace" do
+    it "should accept regex passed in as a string (not Regexp object) and replace the match if there is one" do
+      @fedit.search_file_replace("localhost", "replacement")
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[0].should match(/replacement/)
+    end
+
+    it "should accept regex passed in as a Regexp object and replace the match if there is one" do
+      @fedit.search_file_replace(/localhost/, "replacement")
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[0].should match(/replacement/)
+    end
+
+    it "should do nothing if there isn't a match" do
+      @fedit.search_file_replace(/pattern/, "replacement")
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[0].should_not match(/replacement/)
+    end
+  end
+
+  describe "search_file_replace_line" do
+    it "should search for match and replace the whole line" do
+      @fedit.search_file_replace_line(/localhost/, "replacement line")
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[0].should match(/replacement/)
+      newfile[0].should_not match(/127/)
+    end
+  end
+
+  describe "search_file_delete" do
+    it "should search for match and delete the match" do
+      @fedit.search_file_delete(/localhost/)
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[0].should_not match(/localhost/)
+      newfile[0].should match(/127/)
+    end
+  end
+
+  describe "search_file_delete_line" do
+    it "should search for match and delete the matching line" do
+      @fedit.search_file_delete_line(/localhost/)
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[0].should_not match(/localhost/)
+      newfile[0].should match(/broadcasthost/)
+    end
+  end
+
+  describe "insert_line_after_match" do
+    it "should search for match and insert the given line after the matching line" do
+      @fedit.insert_line_after_match(/localhost/, "new line inserted")
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[1].should match(/new/)
+    end
+  end
+
+  describe "insert_line_if_no_match" do
+    it "should search for match and insert the given line if no line match" do
+      @fedit.insert_line_if_no_match(/pattern/, "new line inserted")
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile.last.should match(/new/)
+    end
+
+    it "should do nothing if there is a match" do
+      @fedit.insert_line_if_no_match(/localhost/, "replacement")
+      @fedit.write_file
+      newfile = File.new(@tempfile.path).readlines
+      newfile[1].should_not match(/replacement/)
+    end
+  end
+end
diff --git a/spec/unit/util/selinux_spec.rb b/spec/unit/util/selinux_spec.rb
new file mode 100644
index 0000000..710380e
--- /dev/null
+++ b/spec/unit/util/selinux_spec.rb
@@ -0,0 +1,172 @@
+#
+# Author:: Serdar Sutay (<serdar at opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+require 'spec_helper'
+
+describe Chef::Util::Selinux do
+  class TestClass
+    include Chef::Util::Selinux
+
+    def self.reset_state
+      @@selinux_enabled = nil
+      @@restorecon_path = nil
+      @@selinuxenabled_path = nil
+    end
+  end
+
+  before do
+    TestClass.reset_state
+    @test_instance = TestClass.new
+  end
+
+  after(:each) do
+    TestClass.reset_state
+  end
+
+  it "each part of ENV['PATH'] should be checked" do
+    expected_paths = ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/bin', '/usr/bin', '/sbin', '/usr/sbin' ]
+
+    expected_paths.each do |bin_path|
+      selinux_path = File.join(bin_path, "selinuxenabled")
+      File.should_receive(:executable?).with(selinux_path).and_return(false)
+    end
+
+    @test_instance.selinux_enabled?.should be_false
+  end
+
+  describe "when selinuxenabled binary exists" do
+    before do
+      @selinux_enabled_path = File.join("/sbin", "selinuxenabled")
+      File.stub!(:executable?) do |file_path|
+        file_path.end_with?("selinuxenabled").should be_true
+        file_path == @selinux_enabled_path
+      end
+    end
+
+    describe "when selinux is enabled" do
+      before do
+        cmd_result = mock("Cmd Result", :exitstatus => 0)
+        @test_instance.should_receive(:shell_out!).once.with(@selinux_enabled_path, {:returns=>[0, 1]}).and_return(cmd_result)
+      end
+
+      it "should report selinux is enabled" do
+        @test_instance.selinux_enabled?.should be_true
+        # should check the file system only once for multiple calls
+        @test_instance.selinux_enabled?.should be_true
+      end
+    end
+
+    describe "when selinux is disabled" do
+      before do
+        cmd_result = mock("Cmd Result", :exitstatus => 1)
+        @test_instance.should_receive(:shell_out!).once.with(@selinux_enabled_path, {:returns=>[0, 1]}).and_return(cmd_result)
+      end
+
+      it "should report selinux is disabled" do
+        @test_instance.selinux_enabled?.should be_false
+        # should check the file system only once for multiple calls
+        @test_instance.selinux_enabled?.should be_false
+      end
+    end
+
+    describe "when selinux gives an unexpected status" do
+      before do
+        cmd_result = mock("Cmd Result", :exitstatus => 101)
+        @test_instance.should_receive(:shell_out!).once.with(@selinux_enabled_path, {:returns=>[0, 1]}).and_return(cmd_result)
+      end
+
+      it "should throw an error" do
+        lambda {@test_instance.selinux_enabled?}.should raise_error(RuntimeError)
+      end
+    end
+  end
+
+  describe "when selinuxenabled binary doesn't exist" do
+    before do
+      File.stub!(:executable?) do |file_path|
+        file_path.end_with?("selinuxenabled").should be_true
+        false
+      end
+    end
+
+    it "should report selinux is disabled" do
+      @test_instance.selinux_enabled?.should be_false
+      # should check the file system only once for multiple calls
+      File.should_not_receive(:executable?)
+      @test_instance.selinux_enabled?.should be_false
+    end
+  end
+
+  describe "when restorecon binary exists on the system" do
+    let (:path) { "/path/to/awesome" }
+
+    before do
+      @restorecon_enabled_path = File.join("/sbin", "restorecon")
+      File.stub!(:executable?) do |file_path|
+        file_path.end_with?("restorecon").should be_true
+        file_path == @restorecon_enabled_path
+      end
+    end
+
+    it "should call restorecon non-recursive by default" do
+      restorecon_command = "#{@restorecon_enabled_path} -R #{path}"
+      @test_instance.should_receive(:shell_out!).twice.with(restorecon_command)
+      @test_instance.restore_security_context(path)
+      File.should_not_receive(:executable?)
+      @test_instance.restore_security_context(path)
+    end
+
+    it "should call restorecon recursive when recursive is set" do
+      restorecon_command = "#{@restorecon_enabled_path} -R -r #{path}"
+      @test_instance.should_receive(:shell_out!).twice.with(restorecon_command)
+      @test_instance.restore_security_context(path, true)
+      File.should_not_receive(:executable?)
+      @test_instance.restore_security_context(path, true)
+    end
+
+    it "should call restorecon non-recursive when recursive is not set" do
+      restorecon_command = "#{@restorecon_enabled_path} -R #{path}"
+      @test_instance.should_receive(:shell_out!).twice.with(restorecon_command)
+      @test_instance.restore_security_context(path)
+      File.should_not_receive(:executable?)
+      @test_instance.restore_security_context(path)
+    end
+
+    describe "when restorecon doesn't exist on the system" do
+      before do
+        File.stub!(:executable?) do |file_path|
+          file_path.end_with?("restorecon").should be_true
+          false
+        end
+      end
+
+      it "should log a warning message" do
+        log = [ ]
+        Chef::Log.stub(:warn) do |message|
+          log << message
+        end
+
+        @test_instance.restore_security_context(path)
+        log.should_not be_empty
+        File.should_not_receive(:executable?)
+        @test_instance.restore_security_context(path)
+      end
+    end
+  end
+end
diff --git a/spec/unit/version/platform_spec.rb b/spec/unit/version/platform_spec.rb
new file mode 100644
index 0000000..8b5b350
--- /dev/null
+++ b/spec/unit/version/platform_spec.rb
@@ -0,0 +1,61 @@
+# Author:: Xabier de Zuazo (<xabier at onddo.com>)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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/version/platform'
+
+describe Chef::Version::Platform do
+
+  it "is a subclass of Chef::Version" do
+    v = Chef::Version::Platform.new('1.1')
+    v.should be_an_instance_of(Chef::Version::Platform)
+    v.should be_a_kind_of(Chef::Version)
+  end
+
+  it "should transform 1 to 1.0.0" do
+    Chef::Version::Platform.new("1").to_s.should == "1.0.0"
+  end
+
+  describe "when creating valid Versions" do
+    good_versions = %w(1 1.2 1.2.3 1000.80.50000 0.300.25 001.02.00003)
+    good_versions.each do |v|
+      it "should accept '#{v}'" do
+        Chef::Version::Platform.new v
+      end
+    end
+  end
+
+  describe "when given bogus input" do
+    bad_versions = ["1.2.3.4", "1.2.a4", "a", "1.2 3", "1.2 a",
+                    "1 2 3", "1-2-3", "1_2_3", "1.2_3", "1.2-3"]
+    the_error = Chef::Exceptions::InvalidPlatformVersion
+    bad_versions.each do |v|
+      it "should raise #{the_error} when given '#{v}'" do
+        lambda { Chef::Version::Platform.new v }.should raise_error(the_error)
+      end
+    end
+  end
+
+  describe "<=>" do
+
+    it "should equate versions 1 and 1.0.0" do
+      Chef::Version::Platform.new("1").should == Chef::Version::Platform.new("1.0.0")
+    end
+
+  end
+
+end
+
diff --git a/spec/unit/version_class_spec.rb b/spec/unit/version_class_spec.rb
new file mode 100644
index 0000000..b0fcfbb
--- /dev/null
+++ b/spec/unit/version_class_spec.rb
@@ -0,0 +1,172 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+require 'chef/version_class'
+
+describe Chef::Version do
+  before do
+    @v0 = Chef::Version.new "0.0.0"
+    @v123 = Chef::Version.new "1.2.3"
+  end
+
+  it "should turn itself into a string" do
+    @v0.to_s.should == "0.0.0"
+    @v123.to_s.should == "1.2.3"
+  end
+
+  it "should make a round trip with its string representation" do
+    a = Chef::Version.new(@v123.to_s)
+    a.should == @v123
+  end
+
+  it "should transform 1.2 to 1.2.0" do
+    Chef::Version.new("1.2").to_s.should == "1.2.0"
+  end
+
+  it "should transform 01.002.0003 to 1.2.3" do
+    a = Chef::Version.new "01.002.0003"
+    a.should == @v123
+  end
+
+  describe "when creating valid Versions" do
+    good_versions = %w(1.2 1.2.3 1000.80.50000 0.300.25 001.02.00003)
+    good_versions.each do |v|
+      it "should accept '#{v}'" do
+        Chef::Version.new v
+      end
+    end
+  end
+
+  describe "when given bogus input" do
+    bad_versions = ["1.2.3.4", "1.2.a4", "1", "a", "1.2 3", "1.2 a",
+                    "1 2 3", "1-2-3", "1_2_3", "1.2_3", "1.2-3"]
+    the_error = Chef::Exceptions::InvalidCookbookVersion
+    bad_versions.each do |v|
+      it "should raise #{the_error} when given '#{v}'" do
+        lambda { Chef::Version.new v }.should raise_error(the_error)
+      end
+    end
+  end
+
+  describe "<=>" do
+
+    it "should equate versions 1.2 and 1.2.0" do
+      Chef::Version.new("1.2").should == Chef::Version.new("1.2.0")
+    end
+
+    it "should equate version 1.04 and 1.4" do
+      Chef::Version.new("1.04").should == Chef::Version.new("1.4")
+    end
+
+    it "should treat versions as numbers in the right way" do
+      Chef::Version.new("2.0").should be < Chef::Version.new("11.0")
+    end
+
+    it "should sort based on the version number" do
+      examples = [
+                  # smaller, larger
+                  ["1.0", "2.0"],
+                  ["1.2.3", "1.2.4"],
+                  ["1.2.3", "1.3.0"],
+                  ["1.2.3", "1.3"],
+                  ["1.2.3", "2.1.1"],
+                  ["1.2.3", "2.1"],
+                  ["1.2", "1.2.4"],
+                  ["1.2", "1.3.0"],
+                  ["1.2", "1.3"],
+                  ["1.2", "2.1.1"],
+                  ["1.2", "2.1"]
+                 ]
+      examples.each do |smaller, larger|
+        sm = Chef::Version.new(smaller)
+        lg = Chef::Version.new(larger)
+        sm.should be < lg
+        lg.should be > sm
+        sm.should_not == lg
+      end
+    end
+
+    it "should sort an array of versions" do
+      a = %w{0.0.0 0.0.1 0.1.0 0.1.1 1.0.0 1.1.0 1.1.1}.map do |s|
+        Chef::Version.new(s)
+      end
+      got = a.sort.map {|v| v.to_s }
+      got.should == %w{0.0.0 0.0.1 0.1.0 0.1.1 1.0.0 1.1.0 1.1.1}
+    end
+
+    it "should sort an array of versions, part 2" do
+      a = %w{9.8.7 1.0.0 1.2.3 4.4.6 4.5.6 0.8.6 4.5.5 5.9.8 3.5.7}.map do |s|
+        Chef::Version.new(s)
+      end
+      got = a.sort.map { |v| v.to_s }
+      got.should == %w{0.8.6 1.0.0 1.2.3 3.5.7 4.4.6 4.5.5 4.5.6 5.9.8 9.8.7}
+    end
+
+    describe "comparison examples" do
+      [
+       [ "0.0.0", :>, "0.0.0", false ],
+       [ "0.0.0", :>=, "0.0.0", true ],
+       [ "0.0.0", :==, "0.0.0", true ],
+       [ "0.0.0", :<=, "0.0.0", true ],
+       [ "0.0.0", :<, "0.0.0", false ],
+       [ "0.0.0", :>, "0.0.1", false ],
+       [ "0.0.0", :>=, "0.0.1", false ],
+       [ "0.0.0", :==, "0.0.1", false ],
+       [ "0.0.0", :<=, "0.0.1", true ],
+       [ "0.0.0", :<, "0.0.1", true ],
+       [ "0.0.1", :>, "0.0.1", false ],
+       [ "0.0.1", :>=, "0.0.1", true ],
+       [ "0.0.1", :==, "0.0.1", true ],
+       [ "0.0.1", :<=, "0.0.1", true ],
+       [ "0.0.1", :<, "0.0.1", false ],
+       [ "0.1.0", :>, "0.1.0", false ],
+       [ "0.1.0", :>=, "0.1.0", true ],
+       [ "0.1.0", :==, "0.1.0", true ],
+       [ "0.1.0", :<=, "0.1.0", true ],
+       [ "0.1.0", :<, "0.1.0", false ],
+       [ "0.1.1", :>, "0.1.1", false ],
+       [ "0.1.1", :>=, "0.1.1", true ],
+       [ "0.1.1", :==, "0.1.1", true ],
+       [ "0.1.1", :<=, "0.1.1", true ],
+       [ "0.1.1", :<, "0.1.1", false ],
+       [ "1.0.0", :>, "1.0.0", false ],
+       [ "1.0.0", :>=, "1.0.0", true ],
+       [ "1.0.0", :==, "1.0.0", true ],
+       [ "1.0.0", :<=, "1.0.0", true ],
+       [ "1.0.0", :<, "1.0.0", false ],
+       [ "1.0.0", :>, "0.0.1", true ],
+       [ "1.0.0", :>=, "1.9.2", false ],
+       [ "1.0.0", :==, "9.7.2", false ],
+       [ "1.0.0", :<=, "1.9.1", true ],
+       [ "1.0.0", :<, "1.9.0", true ],
+       [ "1.2.2", :>, "1.2.1", true ],
+       [ "1.2.2", :>=, "1.2.1", true ],
+       [ "1.2.2", :==, "1.2.1", false ],
+       [ "1.2.2", :<=, "1.2.1", false ],
+       [ "1.2.2", :<, "1.2.1", false ]
+      ].each do |spec|
+        it "(#{spec.first(3).join(' ')}) should be #{spec[3]}" do
+          got = Chef::Version.new(spec[0]).send(spec[1],
+                                                Chef::Version.new(spec[2]))
+          got.should == spec[3]
+        end
+      end
+    end
+  end
+end
+
diff --git a/spec/unit/version_constraint/platform_spec.rb b/spec/unit/version_constraint/platform_spec.rb
new file mode 100644
index 0000000..a3599ae
--- /dev/null
+++ b/spec/unit/version_constraint/platform_spec.rb
@@ -0,0 +1,46 @@
+# Author:: Xabier de Zuazo (<xabier at onddo.com>)
+# Copyright:: Copyright (c) 2013 Onddo Labs, SL.
+# 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/version_constraint/platform'
+
+describe Chef::VersionConstraint::Platform do
+
+  it "is a subclass of Chef::VersionConstraint" do
+    v = Chef::VersionConstraint::Platform.new
+    v.should be_an_instance_of(Chef::VersionConstraint::Platform)
+    v.should be_a_kind_of(Chef::VersionConstraint)
+  end
+
+  it "should work with Chef::Version::Platform classes" do
+    vc = Chef::VersionConstraint::Platform.new("1.0")
+    vc.version.should be_an_instance_of(Chef::Version::Platform)
+  end
+
+  describe "include?" do
+
+    it "pessimistic ~> x" do
+      vc = Chef::VersionConstraint::Platform.new "~> 1"
+      vc.should include "1.3.3"
+      vc.should include "1.4"
+
+      vc.should_not include "2.2"
+      vc.should_not include "0.3.0"
+    end
+
+  end
+end
+
diff --git a/spec/unit/version_constraint_spec.rb b/spec/unit/version_constraint_spec.rb
new file mode 100644
index 0000000..2c1246b
--- /dev/null
+++ b/spec/unit/version_constraint_spec.rb
@@ -0,0 +1,139 @@
+#
+# Author:: Seth Falcon (<seth at opscode.com>)
+# Copyright:: Copyright 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'spec_helper'
+require 'chef/version_constraint'
+
+describe Chef::VersionConstraint do
+  describe "validation" do
+    bad_version = ["> >", ">= 1.2.z", "> 1.2.3 < 5.0", "> 1.2.3, < 5.0"]
+    bad_op = ["<3.0.1", ">$ 1.2.3", "! 3.4"]
+    o_error = Chef::Exceptions::InvalidVersionConstraint
+    v_error = Chef::Exceptions::InvalidCookbookVersion
+    bad_version.each do |s|
+      it "should raise #{v_error} when given #{s}" do
+        lambda { Chef::VersionConstraint.new s }.should raise_error(v_error)
+      end
+    end
+    bad_op.each do |s|
+      it "should raise #{o_error} when given #{s}" do
+        lambda { Chef::VersionConstraint.new s }.should raise_error(o_error)
+      end
+    end
+
+    it "should interpret a lone version number as implicit = OP" do
+      vc = Chef::VersionConstraint.new("1.2.3")
+      vc.to_s.should == "= 1.2.3"
+    end
+
+    it "should allow initialization with [] for back compatibility" do
+      Chef::VersionConstraint.new([]) == Chef::VersionConstraint.new
+    end
+
+    it "should allow initialization with ['1.2.3'] for back compatibility" do
+      Chef::VersionConstraint.new(["1.2"]) == Chef::VersionConstraint.new("1.2")
+    end
+
+  end
+
+  it "should default to >= 0.0.0" do
+    vc = Chef::VersionConstraint.new
+    vc.to_s.should == ">= 0.0.0"
+  end
+
+  it "should default to >= 0.0.0 when initialized with nil" do
+    Chef::VersionConstraint.new(nil).to_s.should == ">= 0.0.0"
+  end
+
+  it "should work with Chef::Version classes" do
+    vc = Chef::VersionConstraint.new("1.0")
+    vc.version.should be_an_instance_of(Chef::Version)
+  end
+
+  describe "include?" do
+    describe "handles various input data types" do
+      before do
+        @vc = Chef::VersionConstraint.new "> 1.2.3"
+      end
+      it "String" do
+        @vc.should include "1.4"
+      end
+      it "Chef::Version" do
+        @vc.should include Chef::Version.new("1.4")
+      end
+      it "Chef::CookbookVersion" do
+        cv = Chef::CookbookVersion.new("alice")
+        cv.version = "1.4"
+        @vc.should include cv
+      end
+    end
+
+    it "strictly less than" do
+      vc = Chef::VersionConstraint.new "< 1.2.3"
+      vc.should_not include "1.3.0"
+      vc.should_not include "1.2.3"
+      vc.should include "1.2.2"
+    end
+
+    it "strictly greater than" do
+      vc = Chef::VersionConstraint.new "> 1.2.3"
+      vc.should include "1.3.0"
+      vc.should_not include "1.2.3"
+      vc.should_not include "1.2.2"
+    end
+
+    it "less than or equal to" do
+      vc = Chef::VersionConstraint.new "<= 1.2.3"
+      vc.should_not include "1.3.0"
+      vc.should include "1.2.3"
+      vc.should include "1.2.2"
+    end
+
+    it "greater than or equal to" do
+      vc = Chef::VersionConstraint.new ">= 1.2.3"
+      vc.should include "1.3.0"
+      vc.should include "1.2.3"
+      vc.should_not include "1.2.2"
+    end
+
+    it "equal to" do
+      vc = Chef::VersionConstraint.new "= 1.2.3"
+      vc.should_not include "1.3.0"
+      vc.should include "1.2.3"
+      vc.should_not include "0.3.0"
+    end
+
+    it "pessimistic ~> x.y.z" do
+      vc = Chef::VersionConstraint.new "~> 1.2.3"
+      vc.should include "1.2.3"
+      vc.should include "1.2.4"
+
+      vc.should_not include "1.2.2"
+      vc.should_not include "1.3.0"
+      vc.should_not include "2.0.0"
+    end
+
+    it "pessimistic ~> x.y" do
+      vc = Chef::VersionConstraint.new "~> 1.2"
+      vc.should include "1.3.3"
+      vc.should include "1.4"
+
+      vc.should_not include "2.2"
+      vc.should_not include "0.3.0"
+    end
+  end
+end
diff --git a/spec/unit/windows_service_spec.rb b/spec/unit/windows_service_spec.rb
new file mode 100644
index 0000000..ba3d298
--- /dev/null
+++ b/spec/unit/windows_service_spec.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Mukta Aphale (<mukta.aphale at clogeny.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'spec_helper'
+if Chef::Platform.windows?
+  require 'chef/application/windows_service'
+end
+
+describe "Chef::Application::WindowsService", :windows_only do
+  let (:instance) {Chef::Application::WindowsService.new}
+  let (:shell_out_result) {Object.new}
+  let (:tempfile) {Tempfile.new "log_file"}
+  before do
+    instance.stub(:parse_options)
+    shell_out_result.stub(:stdout)
+    shell_out_result.stub(:stderr)
+  end
+  it "runs chef-client in new process" do
+    instance.should_receive(:configure_chef).twice
+    instance.service_init
+    instance.should_receive(:run_chef_client).and_call_original
+    instance.should_receive(:shell_out).and_return(shell_out_result)
+    instance.stub(:running?).and_return(true, false)
+    instance.instance_variable_get(:@service_signal).stub(:wait)
+    instance.stub(:state).and_return(4)
+    instance.service_main
+  end
+  it "passes config params to new process" do
+    Chef::Config.merge!({:log_location => tempfile.path, :config_file => "test_config_file", :log_level => :info})
+    instance.should_receive(:configure_chef).twice
+    instance.service_init
+    instance.stub(:running?).and_return(true, false)
+    instance.instance_variable_get(:@service_signal).stub(:wait)
+    instance.stub(:state).and_return(4)
+    instance.should_receive(:run_chef_client).and_call_original
+    instance.should_receive(:shell_out).with("chef-client  --no-fork -c test_config_file -L #{tempfile.path}").and_return(shell_out_result)
+    instance.service_main
+    tempfile.unlink
+  end
+end
diff --git a/tasks/rspec.rb b/tasks/rspec.rb
new file mode 100644
index 0000000..95ae274
--- /dev/null
+++ b/tasks/rspec.rb
@@ -0,0 +1,75 @@
+#
+# Author:: Adam Jacob (<adam at opscode.com>)
+# Author:: Daniel DeLeo (<dan at opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'rubygems'
+require 'rake'
+
+CHEF_ROOT = File.join(File.dirname(__FILE__), "..")
+
+begin
+  require 'rspec/core/rake_task'
+
+  task :default => :spec
+
+  desc "Run all specs in spec directory"
+  RSpec::Core::RakeTask.new(:spec) do |t|
+    t.rspec_opts = ['--options', "\"#{CHEF_ROOT}/.rspec\""]
+    t.pattern = FileList['spec/**/*_spec.rb']
+  end
+
+  desc "Run all functional specs (in functional/ directory)"
+  RSpec::Core::RakeTask.new(:functional) do |t|
+    t.rspec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
+    t.pattern = FileList['spec/functional/**/*_spec.rb']
+  end
+
+  desc "Run the rspec tests with activesupport loaded"
+  RSpec::Core::RakeTask.new(:spec_activesupport) do |t|
+    t.rspec_opts = ['--options', "\"#{CHEF_ROOT}/.rspec\"", "--require active_support/core_ext"]
+    t.pattern = FileList['spec/unit/**/*_spec.rb']
+  end
+
+  namespace :spec do
+    desc "Run all specs in spec directory with RCov"
+    RSpec::Core::RakeTask.new(:rcov) do |t|
+      t.rspec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
+      t.pattern = FileList['spec/**/*_spec.rb']
+      t.rcov = true
+      t.rcov_opts = lambda do
+        IO.readlines("#{CHEF_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
+      end
+    end
+
+    desc "Print Specdoc for all specs"
+    RSpec::Core::RakeTask.new(:doc) do |t|
+      t.rspec_opts = ["--format", "specdoc", "--dry-run"]
+      t.pattern = FileList['spec/**/*_spec.rb']
+    end
+
+    [:unit].each do |sub|
+      desc "Run the specs under spec/#{sub}"
+      RSpec::Core::RakeTask.new(sub) do |t|
+        t.rspec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
+        t.pattern = FileList["spec/#{sub}/**/*_spec.rb"]
+      end
+    end
+  end
+rescue LoadError
+  STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n"
+end

-- 
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