summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--CHANGELOG.md70
-rw-r--r--DOC_CHANGES.md379
-rw-r--r--Gemfile2
-rw-r--r--MAINTAINERS.md69
-rw-r--r--MAINTAINERS.toml88
-rw-r--r--README.md8
-rw-r--r--RELEASE_NOTES.md123
-rw-r--r--Rakefile114
-rw-r--r--VERSION2
-rw-r--r--appveyor.yml2
-rwxr-xr-xbin/chef-service-manager5
-rw-r--r--chef-config/Rakefile57
-rw-r--r--chef-config/VERSION1
-rw-r--r--chef-config/chef-config.gemspec2
-rw-r--r--chef-config/lib/chef-config/config.rb5
-rw-r--r--chef-config/lib/chef-config/exceptions.rb4
-rw-r--r--chef-config/lib/chef-config/package_task.rb223
-rw-r--r--chef-config/lib/chef-config/path_helper.rb31
-rw-r--r--chef-config/lib/chef-config/version.rb11
-rw-r--r--chef-config/lib/chef-config/workstation_config_loader.rb179
-rw-r--r--chef-config/spec/unit/workstation_config_loader_spec.rb (renamed from spec/unit/workstation_config_loader_spec.rb)40
-rw-r--r--chef-windows.gemspec2
-rw-r--r--chef.gemspec5
-rw-r--r--kitchen-tests/Gemfile1
-rw-r--r--lib/chef.rb2
-rw-r--r--lib/chef/api_client.rb160
-rw-r--r--lib/chef/api_client_v1.rb325
-rw-r--r--lib/chef/application.rb2
-rw-r--r--lib/chef/application/apply.rb18
-rw-r--r--lib/chef/application/client.rb6
-rw-r--r--lib/chef/application/solo.rb2
-rw-r--r--lib/chef/application/windows_service_manager.rb31
-rw-r--r--lib/chef/chef_class.rb58
-rw-r--r--lib/chef/chef_fs/config.rb46
-rw-r--r--lib/chef/chef_fs/file_pattern.rb19
-rw-r--r--lib/chef/chef_fs/file_system/acl_dir.rb7
-rw-r--r--lib/chef/chef_fs/file_system/acls_dir.rb6
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_dir.rb5
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_object.rb7
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb11
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb11
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb27
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb13
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb26
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb11
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_dir.rb14
-rw-r--r--lib/chef/chef_fs/file_system/data_bags_dir.rb8
-rw-r--r--lib/chef/chef_fs/file_system/environments_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/file_system_entry.rb11
-rw-r--r--lib/chef/chef_fs/file_system/memory_dir.rb5
-rw-r--r--lib/chef/chef_fs/file_system/multiplexed_dir.rb15
-rw-r--r--lib/chef/chef_fs/file_system/nodes_dir.rb2
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_dir.rb13
-rw-r--r--lib/chef/chef_fs/knife.rb42
-rw-r--r--lib/chef/chef_fs/path_utils.rb99
-rw-r--r--lib/chef/client.rb21
-rw-r--r--lib/chef/config.rb33
-rw-r--r--lib/chef/constants.rb27
-rw-r--r--lib/chef/cookbook/metadata.rb28
-rw-r--r--lib/chef/cookbook/synchronizer.rb2
-rw-r--r--lib/chef/cookbook_version.rb6
-rw-r--r--lib/chef/delayed_evaluator.rb21
-rw-r--r--lib/chef/deprecation/mixin/template.rb3
-rw-r--r--lib/chef/deprecation/provider/cookbook_file.rb2
-rw-r--r--lib/chef/deprecation/provider/file.rb2
-rw-r--r--lib/chef/deprecation/provider/remote_file.rb3
-rw-r--r--lib/chef/deprecation/provider/template.rb2
-rw-r--r--lib/chef/deprecation/warnings.rb7
-rw-r--r--lib/chef/dsl/reboot_pending.rb2
-rw-r--r--lib/chef/dsl/recipe.rb33
-rw-r--r--lib/chef/dsl/resources.rb10
-rw-r--r--lib/chef/event_dispatch/base.rb73
-rw-r--r--lib/chef/event_dispatch/dispatcher.rb24
-rw-r--r--lib/chef/event_dispatch/dsl.rb64
-rw-r--r--lib/chef/exceptions.rb31
-rw-r--r--lib/chef/formatters/base.rb3
-rw-r--r--lib/chef/formatters/doc.rb46
-rw-r--r--lib/chef/formatters/error_inspectors/compile_error_inspector.rb16
-rw-r--r--lib/chef/formatters/minimal.rb4
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb5
-rw-r--r--lib/chef/http/http_request.rb2
-rw-r--r--lib/chef/knife.rb90
-rw-r--r--lib/chef/knife/bootstrap.rb6
-rw-r--r--lib/chef/knife/bootstrap/chef_vault_handler.rb1
-rw-r--r--lib/chef/knife/bootstrap/client_builder.rb4
-rw-r--r--lib/chef/knife/bootstrap/templates/archlinux-gems.erb76
-rw-r--r--lib/chef/knife/bootstrap/templates/chef-aix.erb72
-rw-r--r--lib/chef/knife/bootstrap/templates/chef-full.erb10
-rw-r--r--lib/chef/knife/client_bulk_delete.rb4
-rw-r--r--lib/chef/knife/client_create.rb8
-rw-r--r--lib/chef/knife/client_delete.rb6
-rw-r--r--lib/chef/knife/client_edit.rb12
-rw-r--r--lib/chef/knife/client_list.rb4
-rw-r--r--lib/chef/knife/client_reregister.rb4
-rw-r--r--lib/chef/knife/client_show.rb4
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb7
-rw-r--r--lib/chef/knife/core/custom_manifest_loader.rb69
-rw-r--r--lib/chef/knife/core/gem_glob_loader.rb138
-rw-r--r--lib/chef/knife/core/hashed_command_loader.rb80
-rw-r--r--lib/chef/knife/core/object_loader.rb1
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb277
-rw-r--r--lib/chef/knife/node_run_list_remove.rb13
-rw-r--r--lib/chef/knife/null.rb10
-rw-r--r--lib/chef/knife/osc_user_create.rb6
-rw-r--r--lib/chef/knife/osc_user_delete.rb4
-rw-r--r--lib/chef/knife/osc_user_edit.rb6
-rw-r--r--lib/chef/knife/osc_user_list.rb4
-rw-r--r--lib/chef/knife/osc_user_reregister.rb4
-rw-r--r--lib/chef/knife/osc_user_show.rb4
-rw-r--r--lib/chef/knife/rehash.rb62
-rw-r--r--lib/chef/knife/ssl_check.rb5
-rw-r--r--lib/chef/knife/user_create.rb6
-rw-r--r--lib/chef/knife/user_delete.rb8
-rw-r--r--lib/chef/knife/user_edit.rb9
-rw-r--r--lib/chef/knife/user_list.rb4
-rw-r--r--lib/chef/knife/user_reregister.rb4
-rw-r--r--lib/chef/knife/user_show.rb4
-rw-r--r--lib/chef/log.rb6
-rw-r--r--lib/chef/mixin/deprecation.rb16
-rw-r--r--lib/chef/mixin/params_validate.rb497
-rw-r--r--lib/chef/mixin/template.rb47
-rw-r--r--lib/chef/mixin/wide_string.rb72
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb54
-rw-r--r--lib/chef/mixin/windows_env_helper.rb5
-rw-r--r--lib/chef/node.rb19
-rw-r--r--lib/chef/node_map.rb127
-rw-r--r--lib/chef/osc_user.rb194
-rw-r--r--lib/chef/platform/handler_map.rb40
-rw-r--r--lib/chef/platform/priority_map.rb41
-rw-r--r--lib/chef/platform/provider_handler_map.rb (renamed from lib/chef/mixin/wstring.rb)20
-rw-r--r--lib/chef/platform/provider_mapping.rb14
-rw-r--r--lib/chef/platform/provider_priority_map.rb24
-rw-r--r--lib/chef/platform/query_helpers.rb5
-rw-r--r--lib/chef/platform/rebooter.rb2
-rw-r--r--lib/chef/platform/resource_handler_map.rb29
-rw-r--r--lib/chef/platform/resource_priority_map.rb29
-rw-r--r--lib/chef/property.rb536
-rw-r--r--lib/chef/provider.rb237
-rw-r--r--lib/chef/provider/batch.rb8
-rw-r--r--lib/chef/provider/deploy.rb12
-rw-r--r--lib/chef/provider/directory.rb16
-rw-r--r--lib/chef/provider/dsc_resource.rb4
-rw-r--r--lib/chef/provider/dsc_script.rb2
-rw-r--r--lib/chef/provider/group/pw.rb2
-rw-r--r--lib/chef/provider/ifconfig.rb4
-rw-r--r--lib/chef/provider/lwrp_base.rb76
-rw-r--r--lib/chef/provider/mount.rb10
-rw-r--r--lib/chef/provider/mount/aix.rb2
-rw-r--r--lib/chef/provider/package.rb33
-rw-r--r--lib/chef/provider/package/aix.rb1
-rw-r--r--lib/chef/provider/package/apt.rb1
-rw-r--r--lib/chef/provider/package/dpkg.rb16
-rw-r--r--lib/chef/provider/package/homebrew.rb1
-rw-r--r--lib/chef/provider/package/ips.rb1
-rw-r--r--lib/chef/provider/package/macports.rb1
-rw-r--r--lib/chef/provider/package/openbsd.rb1
-rw-r--r--lib/chef/provider/package/pacman.rb1
-rw-r--r--lib/chef/provider/package/paludis.rb1
-rw-r--r--lib/chef/provider/package/portage.rb2
-rw-r--r--lib/chef/provider/package/rpm.rb4
-rw-r--r--lib/chef/provider/package/rubygems.rb2
-rw-r--r--lib/chef/provider/package/smartos.rb1
-rw-r--r--lib/chef/provider/package/solaris.rb2
-rw-r--r--lib/chef/provider/package/yum.rb23
-rw-r--r--lib/chef/provider/package/zypper.rb1
-rw-r--r--lib/chef/provider/powershell_script.rb63
-rw-r--r--lib/chef/provider/registry_key.rb10
-rw-r--r--lib/chef/provider/remote_directory.rb2
-rw-r--r--lib/chef/provider/service.rb40
-rw-r--r--lib/chef/provider/service/aix.rb2
-rw-r--r--lib/chef/provider/service/debian.rb10
-rw-r--r--lib/chef/provider/service/freebsd.rb2
-rw-r--r--lib/chef/provider/service/gentoo.rb6
-rw-r--r--lib/chef/provider/service/init.rb6
-rw-r--r--lib/chef/provider/service/insserv.rb6
-rw-r--r--lib/chef/provider/service/invokercd.rb6
-rw-r--r--lib/chef/provider/service/macosx.rb6
-rw-r--r--lib/chef/provider/service/openbsd.rb5
-rw-r--r--lib/chef/provider/service/redhat.rb66
-rw-r--r--lib/chef/provider/service/simple.rb4
-rw-r--r--lib/chef/provider/service/systemd.rb8
-rw-r--r--lib/chef/provider/service/upstart.rb11
-rw-r--r--lib/chef/provider/template/content.rb10
-rw-r--r--lib/chef/provider/user.rb2
-rw-r--r--lib/chef/provider/windows_script.rb8
-rw-r--r--lib/chef/provider_resolver.rb112
-rw-r--r--lib/chef/recipe.rb9
-rw-r--r--lib/chef/resource.rb770
-rw-r--r--lib/chef/resource/action_provider.rb69
-rw-r--r--lib/chef/resource/chef_gem.rb6
-rw-r--r--lib/chef/resource/deploy.rb10
-rw-r--r--lib/chef/resource/dsc_script.rb4
-rw-r--r--lib/chef/resource/file/verification.rb9
-rw-r--r--lib/chef/resource/ips_package.rb1
-rw-r--r--lib/chef/resource/lwrp_base.rb11
-rw-r--r--lib/chef/resource/macports_package.rb3
-rw-r--r--lib/chef/resource/mount.rb8
-rw-r--r--lib/chef/resource/openbsd_package.rb11
-rw-r--r--lib/chef/resource/package.rb5
-rw-r--r--lib/chef/resource/registry_key.rb2
-rw-r--r--lib/chef/resource/service.rb12
-rw-r--r--lib/chef/resource/solaris_package.rb5
-rw-r--r--lib/chef/resource/yum_package.rb11
-rw-r--r--lib/chef/resource_resolver.rb83
-rw-r--r--lib/chef/run_context.rb487
-rw-r--r--lib/chef/run_list/versioned_recipe_list.rb11
-rw-r--r--lib/chef/user.rb240
-rw-r--r--lib/chef/user_v1.rb335
-rw-r--r--lib/chef/util/powershell/ps_credential.rb4
-rw-r--r--lib/chef/util/windows/net_group.rb191
-rw-r--r--lib/chef/util/windows/net_use.rb106
-rw-r--r--lib/chef/util/windows/volume.rb38
-rw-r--r--lib/chef/version.rb6
-rw-r--r--lib/chef/win32/api.rb1
-rw-r--r--lib/chef/win32/api/file.rb20
-rw-r--r--lib/chef/win32/api/net.rb206
-rw-r--r--lib/chef/win32/api/registry.rb45
-rw-r--r--lib/chef/win32/api/system.rb23
-rw-r--r--lib/chef/win32/api/unicode.rb43
-rw-r--r--lib/chef/win32/crypto.rb1
-rw-r--r--lib/chef/win32/file.rb31
-rw-r--r--lib/chef/win32/mutex.rb3
-rw-r--r--lib/chef/win32/net.rb170
-rw-r--r--lib/chef/win32/process.rb13
-rw-r--r--lib/chef/win32/registry.rb26
-rw-r--r--lib/chef/win32/security.rb2
-rw-r--r--lib/chef/win32/security/token.rb2
-rwxr-xr-xlib/chef/win32/system.rb62
-rw-r--r--lib/chef/win32/unicode.rb36
-rw-r--r--lib/chef/win32/version.rb4
-rw-r--r--lib/chef/workstation_config_loader.rb161
-rw-r--r--spec/data/cookbooks/openldap/templates/default/helpers.erb14
-rw-r--r--spec/data/dsc_lcm.pfxbin0 -> 2597 bytes
-rw-r--r--spec/data/run_context/cookbooks/include/recipes/default.rb24
-rw-r--r--spec/data/run_context/cookbooks/include/recipes/includee.rb3
-rw-r--r--spec/data/trusted_certs/opscode.pem109
-rw-r--r--spec/functional/provider/whyrun_safe_ruby_block_spec.rb2
-rw-r--r--spec/functional/rebooter_spec.rb2
-rw-r--r--spec/functional/resource/deploy_revision_spec.rb2
-rw-r--r--spec/functional/resource/dsc_script_spec.rb93
-rw-r--r--spec/functional/resource/group_spec.rb111
-rw-r--r--spec/functional/resource/package_spec.rb2
-rw-r--r--spec/functional/resource/powershell_script_spec.rb (renamed from spec/functional/resource/powershell_spec.rb)48
-rw-r--r--spec/functional/win32/registry_helper_spec.rb12
-rw-r--r--spec/functional/win32/service_manager_spec.rb4
-rw-r--r--spec/integration/client/client_spec.rb53
-rw-r--r--spec/integration/knife/chef_repo_path_spec.rb24
-rw-r--r--spec/integration/recipes/lwrp_spec.rb8
-rw-r--r--spec/integration/recipes/recipe_dsl_spec.rb1055
-rw-r--r--spec/integration/recipes/resource_action_spec.rb356
-rw-r--r--spec/integration/recipes/resource_converge_if_changed_spec.rb423
-rw-r--r--spec/integration/recipes/resource_load_spec.rb206
-rw-r--r--spec/spec_helper.rb37
-rw-r--r--spec/support/platform_helpers.rb8
-rw-r--r--spec/support/shared/functional/win32_service.rb3
-rw-r--r--spec/support/shared/shared_examples.rb2
-rw-r--r--spec/support/shared/unit/api_versioning.rb4
-rw-r--r--spec/unit/api_client_spec.rb223
-rw-r--r--spec/unit/api_client_v1_spec.rb457
-rw-r--r--spec/unit/application/solo_spec.rb7
-rw-r--r--spec/unit/chef_class_spec.rb27
-rw-r--r--spec/unit/chef_fs/path_util_spec.rb108
-rw-r--r--spec/unit/client_spec.rb54
-rw-r--r--spec/unit/config_spec.rb31
-rw-r--r--spec/unit/cookbook/metadata_spec.rb26
-rw-r--r--spec/unit/cookbook/syntax_check_spec.rb1
-rw-r--r--spec/unit/cookbook_version_spec.rb2
-rw-r--r--spec/unit/data_bag_item_spec.rb2
-rw-r--r--spec/unit/data_bag_spec.rb2
-rw-r--r--spec/unit/deprecation_spec.rb9
-rw-r--r--spec/unit/dsl/reboot_pending_spec.rb2
-rw-r--r--spec/unit/environment_spec.rb2
-rw-r--r--spec/unit/event_dispatch/dispatcher_spec.rb23
-rw-r--r--spec/unit/event_dispatch/dsl_spec.rb83
-rw-r--r--spec/unit/exceptions_spec.rb2
-rw-r--r--spec/unit/formatters/doc_spec.rb6
-rw-r--r--spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb26
-rw-r--r--spec/unit/json_compat_spec.rb9
-rw-r--r--spec/unit/knife/bootstrap/client_builder_spec.rb16
-rw-r--r--spec/unit/knife/client_bulk_delete_spec.rb8
-rw-r--r--spec/unit/knife/client_create_spec.rb2
-rw-r--r--spec/unit/knife/client_delete_spec.rb6
-rw-r--r--spec/unit/knife/client_edit_spec.rb15
-rw-r--r--spec/unit/knife/client_list_spec.rb2
-rw-r--r--spec/unit/knife/client_reregister_spec.rb4
-rw-r--r--spec/unit/knife/client_show_spec.rb4
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb7
-rw-r--r--spec/unit/knife/core/custom_manifest_loader_spec.rb41
-rw-r--r--spec/unit/knife/core/gem_glob_loader_spec.rb210
-rw-r--r--spec/unit/knife/core/hashed_command_loader_spec.rb93
-rw-r--r--spec/unit/knife/core/subcommand_loader_spec.rb208
-rw-r--r--spec/unit/knife/node_run_list_remove_spec.rb17
-rw-r--r--spec/unit/knife/osc_user_create_spec.rb10
-rw-r--r--spec/unit/knife/osc_user_delete_spec.rb2
-rw-r--r--spec/unit/knife/osc_user_edit_spec.rb2
-rw-r--r--spec/unit/knife/osc_user_list_spec.rb2
-rw-r--r--spec/unit/knife/osc_user_reregister_spec.rb2
-rw-r--r--spec/unit/knife/osc_user_show_spec.rb2
-rw-r--r--spec/unit/knife/ssl_check_spec.rb4
-rw-r--r--spec/unit/knife/user_create_spec.rb2
-rw-r--r--spec/unit/knife/user_delete_spec.rb4
-rw-r--r--spec/unit/knife/user_edit_spec.rb4
-rw-r--r--spec/unit/knife/user_list_spec.rb2
-rw-r--r--spec/unit/knife/user_reregister_spec.rb2
-rw-r--r--spec/unit/knife/user_show_spec.rb4
-rw-r--r--spec/unit/lwrp_spec.rb327
-rw-r--r--spec/unit/mixin/params_validate_spec.rb6
-rw-r--r--spec/unit/mixin/windows_architecture_helper_spec.rb21
-rw-r--r--spec/unit/node_map_spec.rb12
-rw-r--r--spec/unit/node_spec.rb9
-rw-r--r--spec/unit/osc_user_spec.rb276
-rw-r--r--spec/unit/platform_spec.rb2
-rw-r--r--spec/unit/property/state_spec.rb506
-rw-r--r--spec/unit/property/validation_spec.rb663
-rw-r--r--spec/unit/property_spec.rb972
-rw-r--r--spec/unit/provider/deploy_spec.rb12
-rw-r--r--spec/unit/provider/directory_spec.rb35
-rw-r--r--spec/unit/provider/dsc_resource_spec.rb6
-rw-r--r--spec/unit/provider/dsc_script_spec.rb4
-rw-r--r--spec/unit/provider/ifconfig_spec.rb24
-rw-r--r--spec/unit/provider/mount/aix_spec.rb3
-rw-r--r--spec/unit/provider/mount/mount_spec.rb6
-rw-r--r--spec/unit/provider/mount/windows_spec.rb14
-rw-r--r--spec/unit/provider/mount_spec.rb13
-rw-r--r--spec/unit/provider/package/dpkg_spec.rb9
-rw-r--r--spec/unit/provider/package/rpm_spec.rb459
-rw-r--r--spec/unit/provider/package/rubygems_spec.rb18
-rw-r--r--spec/unit/provider/package/yum_spec.rb121
-rw-r--r--spec/unit/provider/powershell_script_spec.rb (renamed from spec/unit/provider/powershell_spec.rb)0
-rw-r--r--spec/unit/provider/registry_key_spec.rb12
-rw-r--r--spec/unit/provider/service/aix_service_spec.rb6
-rw-r--r--spec/unit/provider/service/gentoo_service_spec.rb8
-rw-r--r--spec/unit/provider/service/macosx_spec.rb8
-rw-r--r--spec/unit/provider/service/openbsd_service_spec.rb18
-rw-r--r--spec/unit/provider/service/redhat_spec.rb96
-rw-r--r--spec/unit/provider/service/upstart_service_spec.rb18
-rw-r--r--spec/unit/provider/template/content_spec.rb41
-rw-r--r--spec/unit/provider_resolver_spec.rb306
-rw-r--r--spec/unit/provider_spec.rb4
-rw-r--r--spec/unit/recipe_spec.rb26
-rw-r--r--spec/unit/registry_helper_spec.rb16
-rw-r--r--spec/unit/resource/breakpoint_spec.rb2
-rw-r--r--spec/unit/resource/cron_spec.rb2
-rw-r--r--spec/unit/resource/deploy_spec.rb8
-rw-r--r--spec/unit/resource/directory_spec.rb2
-rw-r--r--spec/unit/resource/dsc_resource_spec.rb2
-rw-r--r--spec/unit/resource/dsc_script_spec.rb8
-rw-r--r--spec/unit/resource/env_spec.rb2
-rw-r--r--spec/unit/resource/erl_call_spec.rb2
-rw-r--r--spec/unit/resource/file/verification_spec.rb38
-rw-r--r--spec/unit/resource/file_spec.rb2
-rw-r--r--spec/unit/resource/group_spec.rb2
-rw-r--r--spec/unit/resource/link_spec.rb2
-rw-r--r--spec/unit/resource/mdadm_spec.rb2
-rw-r--r--spec/unit/resource/mount_spec.rb2
-rw-r--r--spec/unit/resource/ohai_spec.rb2
-rw-r--r--spec/unit/resource/powershell_script_spec.rb (renamed from spec/unit/resource/powershell_spec.rb)0
-rw-r--r--spec/unit/resource/registry_key_spec.rb2
-rw-r--r--spec/unit/resource/route_spec.rb2
-rw-r--r--spec/unit/resource/ruby_block_spec.rb6
-rw-r--r--spec/unit/resource/service_spec.rb8
-rw-r--r--spec/unit/resource/user_spec.rb2
-rw-r--r--spec/unit/resource/windows_service_spec.rb2
-rw-r--r--spec/unit/resource/yum_package_spec.rb11
-rw-r--r--spec/unit/resource_collection_spec.rb2
-rw-r--r--spec/unit/resource_resolver_spec.rb53
-rw-r--r--spec/unit/resource_spec.rb113
-rw-r--r--spec/unit/role_spec.rb2
-rw-r--r--spec/unit/run_context/child_run_context_spec.rb133
-rw-r--r--spec/unit/run_context_spec.rb7
-rw-r--r--spec/unit/run_list/versioned_recipe_list_spec.rb163
-rw-r--r--spec/unit/run_list_spec.rb2
-rw-r--r--spec/unit/runner_spec.rb4
-rw-r--r--spec/unit/user_spec.rb504
-rw-r--r--spec/unit/user_v1_spec.rb584
378 files changed, 14973 insertions, 5021 deletions
diff --git a/.travis.yml b/.travis.yml
index 2910725d7b..d7ad317e28 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,7 +11,6 @@ branches:
- master
- 10-stable
- 11-stable
- - 12-stable
# do not run expensive spec tests on PRs, only on branches
script: "
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3605e3084..22f02eee2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,73 @@
+## Unreleased
+* [**Ranjib Dey**](https://github.com/ranjib):
+ [pr#3588](https://github.com/chef/chef/pull/3588) Count skipped resources among total resources in doc formatter
+* [**John Kerry**](https://github.com/jkerry):
+ [pr#3539](https://github.com/chef/chef/pull/3539) Fix issue: registry\_key resource is case sensitive in chef but not on windows
+* [**David Eddy**](https://github.com/bahamas10):
+ [pr#3443](https://github.com/chef/chef/pull/3443) remove extraneous space
+* [**margueritepd**](https://github.com/margueritepd):
+ [pr#3693](https://github.com/chef/chef/pull/3693) Interpolate `%{path}` in verify command
+* [**Jeremy Fleischman**](https://github.com/jfly):
+ [pr#3383](https://github.com/chef/chef/pull/3383) gem\_package should install to the systemwide Ruby when using ChefDK
+* [**Stefano Rivera**](https://github.com/stefanor):
+ [pr#3657](https://github.com/chef/chef/pull/3657) fix upstart status\_commands
+* [**ABE Satoru**](https://github.com/polamjag):
+ [pr#3764](https://github.com/chef/chef/pull/3764) uniquify chef\_repo\_path
+* [**Renan Vicente**](https://github.com/renanvicente):
+ [pr#3771](https://github.com/chef/chef/pull/3771) add depth property for deploy resource
+* [**James Belchamber**](https://github.com/JamesBelchamber):
+ [pr#1796](https://github.com/chef/chef/pull/1796): make mount options aware
+* [**Nate Walck**](https://github.com/natewalck):
+ [pr#3594](https://github.com/chef/chef/pull/3594): Update service provider for OSX 10.11
+* [**Nate Walck**](https://github.com/natewalck):
+ [pr#3704](https://github.com/chef/chef/pull/3704): Add SIP (OS X 10.11) support
+* [**Phil Dibowitz**](https://github.com/jaymzh):
+ [pr#3805](https://github.com/chef/chef/pull/3805) LWRP parameter validators should use truthiness
+* [**Igor Shpakov**](https://github.com/Igorshp):
+ [pr#3743](https://github.com/chef/chef/pull/3743) speed improvement for `remote_directory` resource
+
+* [pr#3799](https://github.com/chef/chef/pull/3799) fix supports hash issues in service providers
+* [pr#3817](https://github.com/chef/chef/pull/3817) Remove now-useless forcing of ruby Garbage Collector run
+* [pr#3774](https://github.com/chef/chef/pull/3774) Add support for yum-deprecated in yum provider
+* [pr#3793](https://github.com/chef/chef/pull/3793) CHEF-5372: Support specific `run_levels` for RedHat service
+* [pr#2460](https://github.com/chef/chef/pull/2460) add privacy flag
+* [pr#1259](https://github.com/chef/chef/pull/1259) CHEF-5012: add methods for template breadcrumbs
+* [pr#3656](https://github.com/chef/chef/pull/3656) remove use of self.provides?
+* [pr#3455](https://github.com/chef/chef/pull/3455) powershell\_script: do not allow suppression of syntax errors
+* [pr#3519](https://github.com/chef/chef/pull/3519) The wording seemed odd.
+* [pr#3208](https://github.com/chef/chef/pull/3208) Missing require (require what you use).
+* [pr#3449](https://github.com/chef/chef/pull/3449) correcting minor typo in user\_edit knife action
+* [pr#3572](https://github.com/chef/chef/pull/3572) Use windows paths without case-sensitivity.
+* [pr#3666](https://github.com/chef/chef/pull/3666) Support SNI in `knife ssl check`.
+* [pr#3667](https://github.com/chef/chef/pull/3667) Change chef service to start as 'Automatic delayed start'.
+* [pr#3683](https://github.com/chef/chef/pull/3683) Correct Windows reboot command to delay in minutes, per the property.
+* [pr#3698](https://github.com/chef/chef/pull/3698) Add ability to specify dependencies in chef-service-manager.
+* [pr#3728](https://github.com/chef/chef/pull/3728) Rewrite NetLocalGroup things to use FFI
+* [pr#3754](https://github.com/chef/chef/pull/3754) Fix functional tests for group resource - fix #3728
+* [pr#3498](https://github.com/chef/chef/pull/3498) Use dpkg-deb directly rather than regex
+* [pr#3759](https://github.com/chef/chef/pull/3759) Repair service convergence test on AIX
+* [pr#3329](https://github.com/chef/chef/pull/3329) Use ifconfig target property
+* [pr#3652](https://github.com/chef/chef/pull/3652) Fix explanation for configuring audit mode in client.rb
+* [pr#3687](https://github.com/chef/chef/pull/3687) Add formatter and force-logger/formatter options to chef-apply
+* [pr#3768](https://github.com/chef/chef/pull/3768) Make reboot\_pending? look for CBS RebootPending
+* [pr#3815](https://github.com/chef/chef/pull/3815) Fix `powershell_script` validation to use correct architecture
+* [pr#3772](https://github.com/chef/chef/pull/3772) Add `ps_credential` dsl method to `dsc_script`
+
+## 12.4.1
+
+* [**Noah Kantrowitz**](https://github.com/coderanger):
+ [pr#3605](https://github.com/chef/chef/pull/3605) Rework `Resource#action` to match 12.3 API
+
+* [pr#3586](https://github.com/chef/chef/issues/3586) Fix bug preventing light weight resources from being used with heavy weight providers
+* [Issue #3593](https://github.com/chef/chef/issues/3593) Fix bug where provider priority map did not take into consideration a provided block
+* [pr#3630](https://github.com/chef/chef/pull/3630) Restore Chef::User and Chef::ApiClient namespace to API V0 functionality and move new functionality into Chef::UserV1 and Chef::ApiClientV1 until Chef 13.
+* [pr#3611](https://github.com/chef/chef/pull/3611) Call `provides?` even if `provides` is not called
+* [pr#3589](https://github.com/chef/chef/pull/3589) Fix errant bashisms
+* [pr#3620](https://github.com/chef/chef/pull/3620) Fix issue where recipe names in run list mutate when version constaints are present
+* [pr#3623](https://github.com/chef/chef/pull/3623) Allow LWRPs to access the real class when accessed through `Chef::Resource` and `Chef::Provider`
+* [pr#3627](https://github.com/chef/chef/pull/3627) Separate priority map and DSL handler map so that `provides` has veto power over priority
+* [pr#3638](https://github.com/chef/chef/pull/3638) Deprecate passing more than 1 argument to create a resource
+
## 12.4.0
* [**Phil Dibowitz**](https://github.com/jaymzh):
diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md
index 46712f114c..4c07dea872 100644
--- a/DOC_CHANGES.md
+++ b/DOC_CHANGES.md
@@ -6,79 +6,392 @@ Example Doc Change:
Description of the required change.
-->
-### Resources now *all* get automatic DSL
+### PSCredential Support for `dsc_script`
-When you declare a resource (no matter where) you now get automatic DSL for it, based on your class name:
+`dsc_script` now supports the use of `ps_credential` to create a PSCredential
+object similar to `dsc_resource`. The `ps_credential` helper function takes in
+a string and when `to_s` is called on it, produces an object that can be embedded
+in your `dsc_script`. For example, you can write:
```ruby
-module MyModule
- class MyResource < Chef::Resource
- # Names the resource "my_resource"
+dsc_script 'create-foo-user' do
+ code <<-EOH
+ User FooUser
+ {
+ UserName = 'FooUser'
+ Password = #{ps_credential('FooBarBaz1!')}
+ }
+ EOH
+ configuration_data <<-EOH
+ @{
+ AllNodes = @(
+ @{
+ NodeName = "localhost";
+ CertificateID = 'A8DB81D8059F349F7EF19104399B898F701D4167'
+ }
+ )
+ }
+ EOH
+end
+```
+
+Note, you still need to configure the CertificateID in the LCM.
+
+### chef-client -j JSON
+Add to the [description of chef-client options](https://docs.chef.io/ctl_chef_client.html#options):
+
+> This option can also be used to set a node's `chef_environment`. For example,
+running `chef-client -j /path/to/file.json` where `/path/to/file.json` is
+similar to:
+```
+{
+ "chef_environment": "pre-production"
+}
+```
+will set the node's environment to `"pre-production"`.
+
+> *Note that the environment specified by `chef_environment` in your JSON will
+take precedence over an environment specified by `-E ENVIROMENT` when both options
+are provided.*
+
+### Resources Made Easy
+
+Resources are central to Chef. The system is extensible so that you can write
+your own reusable resources, and use them in your recipes, and even publish them
+so that others can use them too!
+
+However, writing these resources has not been as easy as we would have liked. In
+Chef 12.5, we are fixing this with a large number of DSL improvements designed
+to reduce the number of things you need to type and think about when you create
+a resource. Resources should be your go-to solution for many Chef problems, and
+these changes make them easy enough to dash off in an instant, while retaining
+all the power you're accustomed to.
+
+The process to create a resource is now:
+
+1. Make a resource file in your cookbook (like `resources/my_resource.rb`).
+2. Add the recipes defining your actions using the `action :create <recipe>` DSL.
+3. Add properties so the user can tweak some knobs on your resource (like paths,
+ or preferences), using the `property :my_property, <type>, <options>` DSL.
+4. Use the resource in your recipe!
+
+There are other things you can do, but this is the most basic (and the first)
+thing you will start with.
+
+Let's demonstrate the new features by taking a simple recipe from the awesome
+[learnchef tutorial](https://learn.chef.io/learn-the-basics/rhel/configure-a-package-and-service/),
+and turning it into a reusable resource:
+
+```ruby
+package 'httpd'
+
+service 'httpd' do
+ action [:enable, :start]
+end
+
+file '/var/www/html/index.html' do
+ content '<html>
+ <body>
+ <h1>hello world</h1>
+ </body>
+</html>'
+end
+
+service 'iptables' do
+ action :stop
+end
+```
+
+We'll design a resource that lets you write this recipe instead:
+
+```ruby
+single_page_website 'mysite' do
+ homepage '<html>
+ <body>
+ <h1>hello world</h1>
+ </body>
+ </html>'
+end
+```
+
+#### Declaring the Resource
+
+The first thing we do is declare the resource. We can do that by creating an
+empty file, `resources/single_page_website.rb`, in our cookbook.
+
+When you do this, the `single_page_website` resource will work in all recipes!
+
+```ruby
+single_page_website 'mysite'
+```
+
+It won't do anything yet, though :)
+
+#### Declaring an Action
+
+Let's make our resource do something. To start with, we'll just have it do exactly
+what the learnchef tutorial does, but in the resource. Put this in
+`resources/single_page_website.rb`:
+
+```ruby
+action :create do
+ package 'httpd'
+
+ service 'httpd' do
+ action [:enable, :start]
+ end
+
+ file '/var/www/html/index.html' do
+ content '<html>
+ <body>
+ <h1>hello world</h1>
+ </body>
+ </html>'
+ end
+
+ service 'iptables' do
+ action :stop
+ end
+end
+```
+
+Now, your simple recipe can use this resource to do what learnchef did:
+
+```ruby
+single_page_website 'mysite'
+```
+
+We've got ourselves an httpd!
+
+You will notice the only thing we've done is to add `action :create` around the
+recipe. The `action` keyword lets you declare a recipe inline, which will be
+executed when the user uses your resource in a recipe.
+
+#### Declaring a resource property: "homepage"
+
+This isn't super reusable yet--you might want your webpage to say something other
+than "hello world". Let's add a couple of properties for that, by putting this
+at the top of `resources/single_page_website`, and modifying the recipe to use
+"title" and "body":
+
+```ruby
+property :homepage, String, default: '<h1>hello world</h1>'
+
+action :create do
+ package 'httpd'
+
+ service 'httpd' do
+ action [:enable, :start]
+ end
+
+ file '/var/www/html/index.html' do
+ content homepage
+ end
+
+ service 'iptables' do
+ action :stop
end
end
```
-When this happens, the resource can be used in a recipe:
+Now you can run this recipe:
```ruby
-my_resource 'blah' do
+single_page_website 'mysite' do
+ homepage '<h1>My own page</h1>'
end
```
-If you have an abstract class that should *not* have DSL, set `resource_name` to `nil`:
+And you've got a website with your stuff!
+
+What you've done here is add *properties*. Properties are the *desired state* of
+a resource, in this case, `homepage` defines the text on the website. When you
+add a property, you're letting a user give it whatever value they want.
+
+When you define a property, there are three bits:
+`property :<name>, <type>, <options>`. *Name* defines the name of the property,
+so that people can set the property using `name <value>` when they use your
+resource. *Type* defines the type of the property: for example, String, Integer
+and Array are all possible types. Type is optional. *Options* define a large
+number of validation and other options. You've seen `default` already now,
+but there are a ton of others.
+
+#### Adding another property: "not_found_page"
+
+What if we want a custom 404 page for when people try to go to other pages in
+our website? Let's add one more property, to make this even nicer:
```ruby
-module MyModule
- # This will not have DSL
- class MyBaseResource < Chef::Resource
- resource_name nil
+property :homepage, String, default: '<h1>hello world</h1>'
+property :not_found_page, String, default: '<h1>No such page! Sorry. 404.</h1>'
+
+action :create do
+ package 'httpd'
+
+ service 'httpd' do
+ action [:enable, :start]
end
- # This will have DSL `my_resource`
- class MyResource < MyBaseResource
+
+ file '/var/www/html/index.html' do
+ content homepage
+ end
+
+ # These together tell Apache to use your custom 404 page:
+ file '/var/www/html/404.html' do
+ content not_found_page
+ end
+ file '/var/www/html/.htaccess' do
+ content 'ErrorDocument 404 /404.html'
end
+
+ service 'iptables' do
+ action :stop
+ end
+end
+```
+
+Now you can run this recipe:
+
+```ruby
+single_page_website 'mysite' do
+ homepage '<h1>My own page</h1>'
+ not_found_page '<h1>Grr. Page not found. Sorry. (404)</h1>'
end
```
-When you do this, `my_base_resource` will not work in a recipe (but `my_resource` will).
+#### Adding another action: "stop"
-You can still use `provides` to provide other DSL names:
+What if we want to stop the website? Just add another action into the bottom of
+`resources/single_page_website.rb`:
```ruby
-module MyModule
- class MyResource < Chef::Resource
- provides :super_resource
+action :stop do
+ service 'httpd' do
+ action :stop
end
end
```
-Which enables this recipe:
+This action looks a lot like the other.
+
+There are a ton of other things you can do to create resources, but this should
+give you a pretty basic idea.
+
+### Advanced Resource Capabilities
+
+#### Ruby Developers: Resources as Classes
+
+If you are a Ruby developer, we've made it easier to create a Resource outside
+of a cookbook (or in a library) by declaring a class! Declare
+`class SinglePageWebsite < Chef::Resource` and put the entire resource
+declaration inside, and the `single_page_website` resource will work!
+
+#### Reading the current value: load_current_value
+
+There is a pitfall inherent in a resource, where users will sometimes omit a
+property from a resource, and become surprised when the system overwrites it
+with the default! For example, if your website already exists, this recipe
+will replace the *homepage* with "hello world":
+
+```ruby
+single_page_website 'mysite' do
+ not_found_page '<h1>nice</h1>'
+end
+```
+
+It's not at all clear that that's what the user wanted--they didn't say anything
+about the homepage, so why did something happen to it?
+
+To guard against this, you can implement `load_current_value` in your resource.
+Put this in `resources/single_page_website.rb`:
```ruby
-super_resource 'wowzers' do
+load_current_value do
+ if File.exist?('/var/www/html/index.html')
+ homepage IO.read('/var/www/html/index.html')
+ end
+ if File.exist?('/var/www/html/404.html')
+ not_found_page IO.read('/var/www/html/404.html')
+ end
end
```
-(Note that when you use provides in this manner, resource_name will be `my_resource` and declared_type will be `super_resource`. This won't affect most people, but it is worth noting as a matter of explanation.)
+Now, the above recipe knows what the current homepage is, and will not change it!
-Users are encouraged to declare resources in their own namespaces instead of putting them in the `Chef::Resource` namespace.
+This capability is also used for several other things, including reporting (to
+describe what changed) and pure Ruby actions.
-### Resources may now use `allowed_actions` and `default_action`
+#### Pure Ruby Actions
-Instead of overriding `Chef::Resource.initialize` and setting `@allowed_actions` and `@action` in the constructor, you may now use the `allowed_actions` and `default_action` DSL to declare them:
+Some resources need to talk directly to Ruby to do their dirty work, rather than using other resources. In those cases, you need to:
+
+- Make the updates only if the user specified properties that *need* to change.
+- Make sure and call updates if the resource does not exist (need to be created).
+- Print useful green text if the update is happening.
+- Not actually make any changes in why-run mode!
+
+`converge_if_changed` handles all of the above by comparing the user's desired
+property values against the *current* value as loaded by `load_current_value`.
+Simply wrap the part of your recipe that does a set in `converge_if_changed`.
+As an example, here is a basic `my_file` resource that creates a file with the
+given content:
```ruby
-class MyResource < Chef::Resource
- allowed_actions :create, :delete
- default_action :create
+# resources/my_file.rb
+property :path, String, name_property: true
+property :content, String
+
+load_current_value do
+ if File.exist?(path)
+ content IO.read(path)
+ end
+end
+
+action :create do
+ converge_if_changed do
+ IO.write(path, content)
+ end
end
```
-### LWRPs are no longer automatically placed in the `Chef::Resource` namespace
+The above code will only call `IO.write` if the file does not exist, or if the
+user specified content that is different from what is on disk. It will print out
+something like this, showing the changes:
+
+```ruby
+Recipe: basic_chef_client::block
+ * my_file[blah] action create
+ - update my_file[blah]
+ - set content to "hola mundo" (was "hello world")
+ ```
+
+##### Handling Multiple Operations
-Starting with Chef 12.4.0, accessing an LWRP class by name from the `Chef::Resource` namespace will trigger a deprecation warning message. This means that if your cookbook includes the LWRP `mycookbook/resources/myresource.rb`, you will no longer be able to extend or reference `Chef::Resource::MycookbookMyresource` in Ruby code. LWRP recipe DSL does not change: the LWRP will still be available to recipes as `mycookbook_myresource`.
+If you have two separate, expensive operations to handle converge, `converge_if_changed`
+can be called multiple times with multiple properties. Adding `mode` to `my_file`
+demonstrates this:
-You can still get the LWRP class by calling `Chef::ResourceResolver.resolve(:mycookbook_myresource)`.
+```ruby
+# resources/my_file.rb
+property :path, String, name_property: true
+property :content, String
+property :mode, String
-The primary aim here is clearing out the `Chef::Resource` namespace.
+load_current_value do
+ if File.exist?(path)
+ content IO.read(path)
+ mode File.stat(path).mode
+ end
+end
-References to these classes is deprecated (and will emit a warning) in Chef 12, and will be removed in Chef 13.
+action :create do
+ # Only change content here
+ converge_if_changed :content do
+ IO.write(path, content)
+ end
+ # Only change mode here
+ converge_if_changed :mode do
+ File.chmod(mode, path)
+ end
+end
+```
diff --git a/Gemfile b/Gemfile
index c5b0f15bc3..af0bef493c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -13,7 +13,7 @@ end
group(:development, :test) do
gem "simplecov"
gem 'rack', "~> 1.5.1"
- gem 'cheffish', "~> 1.2"
+ gem 'cheffish', "~> 1.3"
gem 'ruby-shadow', :platforms => :ruby unless RUBY_PLATFORM.downcase.match(/(aix|cygwin)/)
end
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index e3bf07573a..3c777366f8 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -19,7 +19,7 @@ a maintainer, lieutenant, or the project lead.
## Chef Core
Handles the core parts of the Chef DSL, base resource and provider
-infrastructure, and the Chef applications. Includes anything not covered by
+infrastructure, the Chef applications and [omnibus-chef](https://github.com/chef/omnibus-chef). Includes anything not covered by
another component.
### Lieutenant
@@ -29,12 +29,14 @@ another component.
### Maintainers
* [Bryan McLellan](https://github.com/btm)
+* [Noah Kantrowitz](https://github.com/coderanger)
* [Daniel DeLeo](https://github.com/danielsdeleo)
* [AJ Christensen](https://github.com/fujin)
* [Phil Dibowitz](https://github.com/jaymzh)
* [Jay Mundrawala](https://github.com/jdmundrawala)
* [Jon Cowie](https://github.com/jonlives)
* [Lamont Granquist](https://github.com/lamont-granquist)
+* [Claire McQuin](https://github.com/mcquin)
* [Steven Murawski](https://github.com/smurawski)
* [Tyler Ball](https://github.com/tyler-ball)
* [Ranjib Dey](https://github.com/ranjib)
@@ -79,10 +81,13 @@ The specific components of Chef related to a given platform - including (but not
## Ubuntu
+### Lieutenant
+
+* [Ranjib Dey](https://github.com/ranjib)
+
### Maintainers
* [Lamont Granquist](https://github.com/lamont-granquist)
-* [Ranjib Dey](https://github.com/ranjib)
* [Thom May](https://github.com/thommay)
## Windows
@@ -96,16 +101,21 @@ The specific components of Chef related to a given platform - including (but not
* [Jay Mundrawala](https://github.com/jdmundrawala)
* [Kartik Cating-Subramanian](https://github.com/ksubrama)
* [Steven Murawski](https://github.com/smurawski)
+* [Salim Alam](https://github.com/chefsalim)
## Solaris
+### Lieutenant
+
+* [Thom May](https://github.com/thommay)
+
### Maintainers
* [Lamont Granquist](https://github.com/lamont-granquist)
## AIX
-### Maintainers
+### Lieutenant
* [Lamont Granquist](https://github.com/lamont-granquist)
@@ -119,6 +129,34 @@ The specific components of Chef related to a given platform - including (but not
* [Tyler Ball](https://github.com/tyler-ball)
+## Debian
+
+### Lieutenant
+
+* [Thom May](https://github.com/thommay)
+
+### Maintainers
+
+* [Lamont Granquist](https://github.com/lamont-granquist)
+
+## Fedora
+
+### Maintainers
+
+* [Lamont Granquist](https://github.com/lamont-granquist)
+
+## openSUSE
+
+### Maintainers
+
+* [Lamont Granquist](https://github.com/lamont-granquist)
+
+## SUSE Enterprise Linux Server
+
+### Maintainers
+
+* [Lamont Granquist](https://github.com/lamont-granquist)
+
## FreeBSD
### Lieutenant
@@ -130,3 +168,28 @@ The specific components of Chef related to a given platform - including (but not
* [Cory Stephenson](https://github.com/Aevin1387)
* [David Aronsohn](https://github.com/tbunnyman)
+## OpenBSD
+
+### Lieutenant
+
+* [Joe Miller](https://github.com/joemiller)
+
+## Gentoo
+
+### Maintainers
+
+* [Lamont Granquist](https://github.com/lamont-granquist)
+
+## OmniOS
+
+### Maintainers
+
+* [Thom May](https://github.com/thommay)
+
+## ArchLinux
+
+### Maintainers
+
+* [Lamont Granquist](https://github.com/lamont-granquist)
+* [Ryan Cragun](https://github.com/ryancragun)
+
diff --git a/MAINTAINERS.toml b/MAINTAINERS.toml
index f949fd4542..61c75c1a30 100644
--- a/MAINTAINERS.toml
+++ b/MAINTAINERS.toml
@@ -26,7 +26,7 @@ a maintainer, lieutenant, or the project lead.
title = "Chef Core"
text = """
Handles the core parts of the Chef DSL, base resource and provider
-infrastructure, and the Chef applications. Includes anything not covered by
+infrastructure, the Chef applications and [omnibus-chef](https://github.com/chef/omnibus-chef). Includes anything not covered by
another component.
"""
@@ -34,12 +34,14 @@ another component.
maintainers = [
"btm",
+ "coderanger",
"danielsdeleo",
"fujin",
"jaymzh",
"jdmundrawala",
"jonlives",
"lamont-granquist",
+ "mcquin",
"smurawski",
"tyler-ball",
"ranjib"
@@ -95,9 +97,10 @@ The specific components of Chef related to a given platform - including (but not
[Org.Components.Subsystems.Ubuntu]
title = "Ubuntu"
+ lieutenant = "ranjib"
+
maintainers = [
"lamont-granquist",
- "ranjib",
"thommay"
]
@@ -108,12 +111,15 @@ The specific components of Chef related to a given platform - including (but not
maintainers = [
"jdmundrawala",
"ksubrama",
- "smurawski"
+ "smurawski",
+ "chefsalim"
]
[Org.Components.Subsystems.Solaris]
title = "Solaris"
+ lieutenant = "thommay"
+
maintainers = [
"lamont-granquist"
]
@@ -121,9 +127,7 @@ The specific components of Chef related to a given platform - including (but not
[Org.Components.Subsystems.AIX]
title = "AIX"
- maintainers = [
- "lamont-granquist"
- ]
+ lieutenant = "lamont-granquist"
[Org.Components.Subsystems."Mac OS X"]
title = "Mac OS X"
@@ -134,6 +138,36 @@ The specific components of Chef related to a given platform - including (but not
"tyler-ball"
]
+ [Org.Components.Subsystems.Debian]
+ title = "Debian"
+
+ lieutenant = "thommay"
+
+ maintainers = [
+ "lamont-granquist"
+ ]
+
+ [Org.Components.Subsystems.Fedora]
+ title = "Fedora"
+
+ maintainers = [
+ "lamont-granquist"
+ ]
+
+ [Org.Components.Subsystems.openSUSE]
+ title = "openSUSE"
+
+ maintainers = [
+ "lamont-granquist"
+ ]
+
+ [Org.Components.Subsystems."SUSE Enterprise Linux"]
+ title = "SUSE Enterprise Linux Server"
+
+ maintainers = [
+ "lamont-granquist"
+ ]
+
[Org.Components.Subsystems.FreeBSD]
title = "FreeBSD"
@@ -144,6 +178,32 @@ The specific components of Chef related to a given platform - including (but not
"tBunnyMan"
]
+ [Org.Components.Subsystems.OpenBSD]
+ title = "OpenBSD"
+
+ lieutenant = "joemiller"
+
+ [Org.Components.Subsystems.Gentoo]
+ title = "Gentoo"
+
+ maintainers = [
+ "lamont-granquist"
+ ]
+
+ [Org.Components.Subsystems.OmniOS]
+ title = "OmniOS"
+
+ maintainers = [
+ "thommay"
+ ]
+
+ [Org.Components.Subsystems.ArchLinux]
+ title = "ArchLinux"
+
+ maintainers = [
+ "lamont-granquist",
+ "ryancragun"
+ ]
[people]
[people.adamhjk]
@@ -229,3 +289,19 @@ The specific components of Chef related to a given platform - including (but not
[people.ksubrama]
Name = "Kartik Cating-Subramanian"
GitHub = "ksubrama"
+
+ [people.joemiller]
+ Name = "Joe Miller"
+ GitHub = "joemiller"
+
+ [people.coderanger]
+ Name = "Noah Kantrowitz"
+ GitHub = "coderanger"
+
+ [people.ryancragun]
+ Name = "Ryan Cragun"
+ GitHub = "ryancragun"
+
+ [people.chefsalim]
+ Name = "Salim Alam"
+ GitHub = "chefsalim"
diff --git a/README.md b/README.md
index 3b1c7190fb..d84d857473 100644
--- a/README.md
+++ b/README.md
@@ -68,11 +68,11 @@ read the
The general development process is:
-1. Fork this repo and clone it to your workstation
-2. Create a feature branch for your change
-3. Write code and tests
+1. Fork this repo and clone it to your workstation.
+2. Create a feature branch for your change.
+3. Write code and tests.
4. Push your feature branch to github and open a pull request against
- master
+ master.
Once your repository is set up, you can start working on the code. We do use
TDD with RSpec, so you'll need to get a development environment running.
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 049640d7ab..cba5b9f415 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,87 +1,76 @@
-# Chef Client Release Notes 12.4.0:
+# Chef Client Release Notes 12.5.0:
+* OSX 10.11 support (support for SIP and service changes)
-## Knife Key Management Commands for Users and Clients
+## PSCredential support for the `dsc_script` resource
-`knife user` and `knife client` now have a suite of subcommands that live under
-the `key` subcommand. These subcommands allow you to list, show, create, delete
-and edit keys for a given user or client. They can be used to implement
-key rotation with multiple expiring keys for a single actor or just
-for basic key management. See `knife user key` and `knife client key`
-for a full list of subcommands and their usage.
-
-## System Loggers
-
-You can now have all Chef logs sent to a logging system of your choice.
-
-### Syslog Logger
-
-Syslog can be used by adding the following line to your chef config
-file:
-
-```ruby
-log_location Chef::Log::Syslog.new("chef-client", ::Syslog::LOG_DAEMON)
-```
-
-THis will write to the `daemon` facility with the originator set as
-`chef-client`.
-
-### Windows Event Logger
-
-The logger can be used by adding the following line to your chef config file:
+The `dsc_script` resource now supports the use of the `ps_credential`
+helper method. This method generates a Ruby object which can be described
+as a Powershell PSCredential object. For example, if you wanted to created
+a user using DSC, previously you would have had to do something like:
```ruby
-log_location Chef::Log::WinEvt.new
+dsc_script 'create-foo-user' do
+ code <<-EOH
+ $username = "placeholder"
+ $password = "#{FooBarBaz1!}" | ConvertTo-SecureString -asPlainText -Force
+ $cred = New-Object System.Management.Automation.PSCredential($username, $password)
+ User FooUser00
+ {
+ Ensure = "Present"
+ UserName = 'FooUser00'
+ Password = $cred
+ }
+ EOH
+ configuration_data_script "path/to/config/data.psd1"
+end
```
-This will write to the Application log with the source set as Chef.
-
-## RemoteFile resource supports UNC paths on Windows
-
-You can now use UNC paths with `remote_file` on Windows machines. For
-example, you can get `Foo.tar.gz` off of `fooshare` on `foohost` using
-the following resource:
+This can now be replaced with
```ruby
-remote_file 'C:\Foo.tar.gz' do
- source "\\\\foohost\\fooshare\\Foo.tar.gz"
+dsc_script 'create-foo-user' do
+ code <<-EOH
+ User FooUser00
+ {
+ Ensure = "Present"
+ UserName = 'FooUser00'
+ Password = #{ps_credential("FooBarBaz1!")}
+ }
+ EOH
+ configuration_data_script "path/to/config/data.psd1"
end
```
-## WindowsPackage resource supports URLs
+## New `knife rehash` for faster command loading
-The `windows_package` resource now allows specifying URLs for the source
-attribute. For example, you could install 7zip with the following resource:
+The new `knife rehash` command speeds up day-to-day knife usage by
+caching information about installed plugins and available commands.
+Initial testing has shown substantial improvements in `knife` startup
+times for users with a large number of Gems installed and Windows
+users.
-```ruby
-windows_package '7zip' do
- source "http://www.7-zip.org/a/7z938-x64.msi"
-end
-```
+To use this feature, simply run `knife rehash` and continue using
+`knife`. When you install or remove gems that provide knife plugins,
+run `knife rehash` again to keep the cache up to date.
+
+## Support for `/usr/bin/yum-deprecated` in the yum provider
-Internally, this is done by using a `remote_file` resource to download the
-contents at the specified url. If needed, you can modify the attributes of
-the `remote_file` resource using the `remote_file_attributes` attribute.
-The `remote_file_attributes` accepts a hash of attributes that will be set
-on the underlying remote_file. For example, the checksum of the contents can
-be verified using
+In Fedora 22 yum has been deprecated in favor of DNF. Unfortunately, while DNF tries to be backwards
+compatible with yum, the yum provider in Chef is not compatible with DNF. Until a proper `dnf_package`
+resource and associated provider is written and merged into core, 12.5.0 has been patched so that the
+`yum_package` resource takes a property named `yum_binary` which can be set to point at the yum binary
+to run for all its commands. The `yum_binary` will also default to `yum-deprecated` if the
+`/usr/bin/yum-deprecated` command is found on the system. This means that Fedora 22 users can run
+something like this early in their chef-client run:
```ruby
-windows_package '7zip' do
- source "http://www.7-zip.org/a/7z938-x64.msi"
- remote_file_attributes {
- :path => "C:\\7zip.msi",
- :checksum => '7c8e873991c82ad9cfcdbdf45254ea6101e9a645e12977dcd518979e50fdedf3'
- }
+if File.exist?("/usr/bin/dnf")
+ execute "dnf install -y yum" do
+ not_if { File.exist?("/usr/bin/yum-deprecated") }
+ end
end
```
-To make the transition easier from the Windows cookbook, `windows_package` also
-accepts the `checksum` attribute, and the previous resource could be rewritten
-as:
+After which the yum-deprecated binary will exist, and the yum provider will find it and should operate
+normally and successfully.
-```ruby
-windows_package '7zip' do
- source "http://www.7-zip.org/a/7z938-x64.msi"
- checksum '7c8e873991c82ad9cfcdbdf45254ea6101e9a645e12977dcd518979e50fdedf3'
-end
-```
diff --git a/Rakefile b/Rakefile
index 628e0e1d45..6b9a52f68d 100644
--- a/Rakefile
+++ b/Rakefile
@@ -20,121 +20,15 @@
VERSION = IO.read(File.expand_path("../VERSION", __FILE__)).strip
require 'rubygems'
-require 'rubygems/package_task'
+require 'chef-config/package_task'
require 'rdoc/task'
require_relative 'tasks/rspec'
require_relative 'tasks/external_tests'
require_relative 'tasks/maintainers'
-GEM_NAME = "chef"
-
-desc "build Gems of Chef's components"
-task :package_components do
- Dir.chdir("chef-config") do
- sh "rake package"
- end
-end
-
-task :package => :package_components
-
-desc "build and install chef's components"
-task :install_components => :package_components do
- Dir.chdir("chef-config") do
- sh "rake install"
- end
-end
-
-task :install => :install_components
-
-desc "clean up builds of Chef's components"
-task :clobber_component_packages do
- Dir.chdir("chef-config") do
- sh "rake clobber_package"
- end
-end
-
-task :clobber_package => :clobber_component_packages
-
-desc "Update the version number for Chef's components"
-task :update_components_versions do
- Dir.chdir("chef-config") do
- sh "rake version"
- end
-end
-
-desc "Regenerate lib/chef/version.rb from VERSION file"
-task :version => :update_components_versions do
- contents = <<-VERSION_RB
-# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-# NOTE: This file is generated by running `rake version` in the top level of
-# this repo. Do not edit this manually. Edit the VERSION file and run the rake
-# task instead.
-#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
-class Chef
- CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '#{VERSION}'
-end
-
-#
-# NOTE: the Chef::Version class is defined in version_class.rb
-#
-# NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs. The
-# Chef::Version class is for _cookbooks_ only, and cannot handle
-# pre-release chef-client versions like "10.14.0.rc.2". Please
-# use Rubygem's Gem::Version class instead.
-#
-VERSION_RB
- version_rb_path = File.expand_path("../lib/chef/version.rb", __FILE__)
- IO.write(version_rb_path, contents)
-end
-
-Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
- gemspec = eval(IO.read(gemspec_path))
- Gem::PackageTask.new(gemspec).define
-end
-
-def with_clean_env(&block)
- if defined?(Bundler)
- Bundler.with_clean_env(&block)
- else
- block.call
- end
-end
-
-desc "Build and install a chef gem"
-task :install => [:package] do
- with_clean_env do
- sh %{gem install pkg/#{GEM_NAME}-#{VERSION}.gem --no-rdoc --no-ri}
- end
-end
-
-task :uninstall do
- sh %{gem uninstall #{GEM_NAME} -x -v #{VERSION} }
-end
-
-desc "Build it, tag it and ship it"
-task :ship => [:clobber_package, :gem] do
- sh("git tag #{VERSION}")
- sh("git push opscode --tags")
- Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
- sh("gem push #{built_gem}")
- end
+ChefConfig::PackageTask.new(File.expand_path('..', __FILE__), 'Chef') do |package|
+ package.component_paths = ['chef-config']
+ package.generate_version_class = true
end
task :pedant do
diff --git a/VERSION b/VERSION
index b53f8861dc..9a03cd310d 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-12.4.0.rc.2
+12.5.0.current.0
diff --git a/appveyor.yml b/appveyor.yml
index 5609648cb1..06448e2be2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -14,7 +14,7 @@ skip_tags: true
branches:
only:
- master
- - 12-stable
+ - 12.4-stable
cache:
- C:\Ruby200\lib\ruby\gems\2.0.0
diff --git a/bin/chef-service-manager b/bin/chef-service-manager
index 7cef10f506..7c031f70d4 100755
--- a/bin/chef-service-manager
+++ b/bin/chef-service-manager
@@ -28,10 +28,11 @@ if Chef::Platform.windows?
:service_name => "chef-client",
:service_display_name => "Chef Client Service",
:service_description => "Runs Chef Client on regular, configurable intervals.",
- :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../../../bin/chef-windows-service'))
+ :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../../../bin/chef-windows-service')),
+ :delayed_start => true,
+ :dependencies => ['Winmgmt']
}
Chef::Application::WindowsServiceManager.new(chef_client_service).run
else
puts "chef-service-manager is only available on Windows platforms."
end
-
diff --git a/chef-config/Rakefile b/chef-config/Rakefile
index 10b6010de3..36e7e2572d 100644
--- a/chef-config/Rakefile
+++ b/chef-config/Rakefile
@@ -1,26 +1,8 @@
require 'rspec/core/rake_task'
-require 'rubygems/package_task'
+require 'chef-config/package_task'
-VERSION = IO.read(File.expand_path("../../VERSION", __FILE__)).strip
-
-Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
- gemspec = eval(IO.read(gemspec_path))
- Gem::PackageTask.new(gemspec).define
-end
-
-def with_clean_env(&block)
- if defined?(Bundler)
- Bundler.with_clean_env(&block)
- else
- block.call
- end
-end
-
-desc "Build and install a chef-config gem"
-task :install => [:package] do
- with_clean_env do
- sh(%{gem install pkg/chef-config-#{ChefConfig::VERSION}.gem --no-rdoc --no-ri}, verbose: true)
- end
+ChefConfig::PackageTask.new(File.expand_path('..', __FILE__), 'ChefConfig') do |package|
+ package.module_path = 'chef-config'
end
task :default => :spec
@@ -30,36 +12,3 @@ RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = FileList['spec/**/*_spec.rb']
end
-desc "Regenerate lib/chef/version.rb from VERSION file"
-task :version do
- contents = <<-VERSION_RB
-# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-# NOTE: This file is generated by running `rake version` in the top level of
-# this repo. Do not edit this manually. Edit the VERSION file and run the rake
-# task instead.
-#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-
-module ChefConfig
- VERSION = '#{VERSION}'
-end
-
-VERSION_RB
- version_rb_path = File.expand_path("../lib/chef-config/version.rb", __FILE__)
- IO.write(version_rb_path, contents)
-end
-
diff --git a/chef-config/VERSION b/chef-config/VERSION
new file mode 100644
index 0000000000..9a03cd310d
--- /dev/null
+++ b/chef-config/VERSION
@@ -0,0 +1 @@
+12.5.0.current.0
diff --git a/chef-config/chef-config.gemspec b/chef-config/chef-config.gemspec
index f0a569f3c1..475bd0f2d2 100644
--- a/chef-config/chef-config.gemspec
+++ b/chef-config/chef-config.gemspec
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
spec.email = ["adam@chef.io"]
spec.summary = %q{Chef's default configuration and config loading}
- spec.homepage = "TODO: Put your gem's website or public repo URL here."
+ spec.homepage = "https://github.com/chef/chef"
spec.license = "Apache-2.0"
spec.require_paths = ["lib"]
diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb
index 63de8a451f..a3f06e9b23 100644
--- a/chef-config/lib/chef-config/config.rb
+++ b/chef-config/lib/chef-config/config.rb
@@ -128,7 +128,7 @@ module ChefConfig
if chef_repo_path.kind_of?(String)
PathHelper.join(chef_repo_path, child_path)
else
- chef_repo_path.map { |path| PathHelper.join(path, child_path)}
+ chef_repo_path.uniq.map { |path| PathHelper.join(path, child_path)}
end
end
@@ -739,6 +739,3 @@ module ChefConfig
end
end
end
-
-
-
diff --git a/chef-config/lib/chef-config/exceptions.rb b/chef-config/lib/chef-config/exceptions.rb
index f5d76d856b..1f80e505df 100644
--- a/chef-config/lib/chef-config/exceptions.rb
+++ b/chef-config/lib/chef-config/exceptions.rb
@@ -20,7 +20,7 @@ require 'chef-config/logger'
module ChefConfig
- class InvalidPath < StandardError
- end
+ class ConfigurationError < ArgumentError; end
+ class InvalidPath < StandardError; end
end
diff --git a/chef-config/lib/chef-config/package_task.rb b/chef-config/lib/chef-config/package_task.rb
new file mode 100644
index 0000000000..0aa063a2ff
--- /dev/null
+++ b/chef-config/lib/chef-config/package_task.rb
@@ -0,0 +1,223 @@
+#
+# Author:: Kartik Null Cating-Subramanian (<ksubramanian@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'rake'
+require 'rubygems'
+require 'rubygems/package_task'
+
+module ChefConfig
+ class PackageTask < Rake::TaskLib
+
+ # Full path to root of top-level repository. All other files (like VERSION or
+ # lib/<module_path>/version.rb are rooted at this path).
+ attr_accessor :root_path
+
+ # Name of the top-level module/library build built. This is used to define
+ # the top level module which contains VERSION and MODULE_ROOT.
+ attr_accessor :module_name
+
+ # Should the generated version.rb be in a class or module? Default is false (module).
+ attr_accessor :generate_version_class
+
+ # Paths to the roots of any components that also support ChefPackageTask.
+ # If relative paths are provided, they are rooted against root_path.
+ attr_accessor :component_paths
+
+ # This is the module name as it appears on the path "lib/module/".
+ # e.g. for module_name "ChefDK", you'd want module_path to be "chef-dk".
+ # The default is module_name but lower-cased.
+ attr_writer :module_path
+
+ def module_path
+ @module_path || module_name.downcase
+ end
+
+ # Path to a VERSION file with a single string that contains the package version.
+ # By default, this is root_path/VERSION
+ attr_accessor :version_file_path
+
+ # Directory used to store package files and output that is generated.
+ # This has the same meaning (or lack thereof) as package_dir in
+ # rake/packagetask.
+ attr_accessor :package_dir
+
+ # Name of git remote used to push tags during a release. Default is origin.
+ attr_accessor :git_remote
+
+ def initialize(root_path=nil, module_name=nil)
+ init(root_path, module_name)
+ yield self if block_given?
+ define unless root_path.nil? || module_name.nil?
+ end
+
+ def init(root_path, module_name)
+ @root_path = root_path
+ @module_name = module_name
+ @component_paths = []
+ @module_path = nil
+ @version_file_path = 'VERSION'
+ @package_dir = 'pkg'
+ @git_remote = 'origin'
+ @generate_version_class = false
+ end
+
+ def component_full_paths
+ component_paths.map { |path| File.expand_path(path, root_path)}
+ end
+
+ def version_rb_path
+ File.expand_path("lib/#{module_path}/version.rb", root_path)
+ end
+
+ def version
+ IO.read(File.expand_path(version_file_path, root_path)).strip
+ end
+
+ def full_package_dir
+ File.expand_path(package_dir, root_path)
+ end
+
+ def class_or_module
+ generate_version_class ? 'class' : 'module'
+ end
+
+ def with_clean_env(&block)
+ if defined?(Bundler)
+ Bundler.with_clean_env(&block)
+ else
+ block.call
+ end
+ end
+
+ def define
+ fail 'Need to provide package root and module name' if root_path.nil? || module_name.nil?
+
+ desc 'Build Gems of component dependencies'
+ task :package_components do
+ component_full_paths.each do |component_path|
+ Dir.chdir(component_path) do
+ sh 'rake package'
+ end
+ end
+ end
+
+ task :package => :package_components
+
+ desc 'Build and install component dependencies'
+ task :install_components => :package_components do
+ component_full_paths.each do |component_path|
+ Dir.chdir(component_path) do
+ sh 'rake install'
+ end
+ end
+ end
+
+ task :install => :install_components
+
+ desc 'Clean up builds of component dependencies'
+ task :clobber_component_packages do
+ component_full_paths.each do |component_path|
+ Dir.chdir(component_path) do
+ sh 'rake clobber_package'
+ end
+ end
+ end
+
+ task :clobber_package => :clobber_component_packages
+
+ desc 'Update the version number for component dependencies'
+ task :update_components_versions do
+ component_full_paths.each do |component_path|
+ Dir.chdir(component_path) do
+ sh 'rake version'
+ end
+ end
+ end
+
+ desc 'Regenerate lib/#{@module_path}/version.rb from VERSION file'
+ task :version => :update_components_versions do
+ contents = <<-VERSION_RB
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# NOTE: This file is generated by running `rake version` in the top level of
+# this repo. Do not edit this manually. Edit the VERSION file and run the rake
+# task instead.
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+#{class_or_module} #{module_name}
+ #{module_name.upcase}_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
+ VERSION = '#{version}'
+end
+
+#
+# NOTE: the Chef::Version class is defined in version_class.rb
+#
+# NOTE: DO NOT Use the Chef::Version class on #{module_name}::VERSIONs. The
+# Chef::Version class is for _cookbooks_ only, and cannot handle
+# pre-release versions like "10.14.0.rc.2". Please use Rubygem's
+# Gem::Version class instead.
+#
+ VERSION_RB
+ IO.write(version_rb_path, contents)
+ end
+
+ Dir[File.expand_path("*gemspec", root_path)].reverse.each do |gemspec_path|
+ gemspec = eval(IO.read(gemspec_path))
+ Gem::PackageTask.new(gemspec) do |task|
+ task.package_dir = full_package_dir
+ end
+ end
+
+ desc "Build and install a #{module_path} gem"
+ task :install => [:package] do
+ with_clean_env do
+ full_module_path = File.join(full_package_dir, module_path)
+ sh %{gem install #{full_module_path}-#{version}.gem --no-rdoc --no-ri}
+ end
+ end
+
+ task :uninstall do
+ sh %{gem uninstall #{module_path} -x -v #{version} }
+ end
+
+ desc 'Build it, tag it and ship it'
+ task :ship => [:clobber_package, :gem] do
+ sh("git tag #{version}")
+ sh("git push #{git_remote} --tags")
+ Dir[File.expand_path('*.gem', full_package_dir)].reverse.each do |built_gem|
+ sh("gem push #{built_gem}")
+ end
+ end
+ end
+ end
+
+end
diff --git a/chef-config/lib/chef-config/path_helper.rb b/chef-config/lib/chef-config/path_helper.rb
index acc6b76377..45f451479a 100644
--- a/chef-config/lib/chef-config/path_helper.rb
+++ b/chef-config/lib/chef-config/path_helper.rb
@@ -228,6 +228,37 @@ module ChefConfig
joined_paths
end
end
+
+ # Determine if the given path is protected by OS X System Integrity Protection.
+ def self.is_sip_path?(path, node)
+ if node['platform'] == 'mac_os_x' and Gem::Version.new(node['platform_version']) >= Gem::Version.new('10.11')
+ # todo: parse rootless.conf for this?
+ sip_paths= [
+ '/System', '/bin', '/sbin', '/usr',
+ ]
+ sip_paths.each do |sip_path|
+ ChefConfig.logger.info("This is a SIP path, checking if it in exceptions list.")
+ return true if path.start_with?(sip_path)
+ end
+ false
+ else
+ false
+ end
+ end
+ # Determine if the given path is on the exception list for OS X System Integrity Protection.
+ def self.writable_sip_path?(path)
+ # todo: parse rootless.conf for this?
+ sip_exceptions = [
+ '/System/Library/Caches', '/System/Library/Extensions',
+ '/System/Library/Speech', '/System/Library/User Template',
+ '/usr/libexec/cups', '/usr/local', '/usr/share/man'
+ ]
+ sip_exceptions.each do |exception_path|
+ return true if path.start_with?(exception_path)
+ end
+ ChefConfig.logger.error("Cannot write to a SIP Path on OS X 10.11+")
+ false
+ end
end
end
diff --git a/chef-config/lib/chef-config/version.rb b/chef-config/lib/chef-config/version.rb
index a6bf636540..9579f0638d 100644
--- a/chef-config/lib/chef-config/version.rb
+++ b/chef-config/lib/chef-config/version.rb
@@ -20,6 +20,15 @@
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
module ChefConfig
- VERSION = '12.4.0.rc.2'
+ CHEFCONFIG_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
+ VERSION = '12.5.0.current.0'
end
+#
+# NOTE: the Chef::Version class is defined in version_class.rb
+#
+# NOTE: DO NOT Use the Chef::Version class on ChefConfig::VERSIONs. The
+# Chef::Version class is for _cookbooks_ only, and cannot handle
+# pre-release versions like "10.14.0.rc.2". Please use Rubygem's
+# Gem::Version class instead.
+#
diff --git a/chef-config/lib/chef-config/workstation_config_loader.rb b/chef-config/lib/chef-config/workstation_config_loader.rb
new file mode 100644
index 0000000000..177cd776d4
--- /dev/null
+++ b/chef-config/lib/chef-config/workstation_config_loader.rb
@@ -0,0 +1,179 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef-config/config'
+require 'chef-config/exceptions'
+require 'chef-config/logger'
+require 'chef-config/path_helper'
+require 'chef-config/windows'
+
+module ChefConfig
+ class WorkstationConfigLoader
+
+ # Path to a config file requested by user, (e.g., via command line option). Can be nil
+ attr_accessor :explicit_config_file
+
+ # TODO: initialize this with a logger for Chef and Knife
+ def initialize(explicit_config_file, logger=nil)
+ @explicit_config_file = explicit_config_file
+ @chef_config_dir = nil
+ @config_location = nil
+ @logger = logger || NullLogger.new
+ end
+
+ def no_config_found?
+ config_location.nil?
+ end
+
+ def config_location
+ @config_location ||= (explicit_config_file || locate_local_config)
+ end
+
+ def chef_config_dir
+ if @chef_config_dir.nil?
+ @chef_config_dir = false
+ full_path = working_directory.split(File::SEPARATOR)
+ (full_path.length - 1).downto(0) do |i|
+ candidate_directory = File.join(full_path[0..i] + [".chef"])
+ if File.exist?(candidate_directory) && File.directory?(candidate_directory)
+ @chef_config_dir = candidate_directory
+ break
+ end
+ end
+ end
+ @chef_config_dir
+ end
+
+ def load
+ # Ignore it if there's no explicit_config_file and can't find one at a
+ # default path.
+ return false if config_location.nil?
+
+ if explicit_config_file && !path_exists?(config_location)
+ raise ChefConfig::ConfigurationError, "Specified config file #{config_location} does not exist"
+ end
+
+ # Have to set Config.config_file b/c other config is derived from it.
+ Config.config_file = config_location
+ read_config(IO.read(config_location), config_location)
+ end
+
+ # (Private API, public for test purposes)
+ def env
+ ENV
+ end
+
+ # (Private API, public for test purposes)
+ def path_exists?(path)
+ Pathname.new(path).expand_path.exist?
+ end
+
+ private
+
+ def have_config?(path)
+ if path_exists?(path)
+ logger.info("Using config at #{path}")
+ true
+ else
+ logger.debug("Config not found at #{path}, trying next option")
+ false
+ end
+ end
+
+ def locate_local_config
+ candidate_configs = []
+
+ # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
+ if env['KNIFE_HOME']
+ candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb')
+ candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb')
+ end
+ # Look for $PWD/knife.rb
+ if Dir.pwd
+ candidate_configs << File.join(Dir.pwd, 'config.rb')
+ candidate_configs << File.join(Dir.pwd, 'knife.rb')
+ end
+ # Look for $UPWARD/.chef/knife.rb
+ if chef_config_dir
+ candidate_configs << File.join(chef_config_dir, 'config.rb')
+ candidate_configs << File.join(chef_config_dir, 'knife.rb')
+ end
+ # Look for $HOME/.chef/knife.rb
+ PathHelper.home('.chef') do |dot_chef_dir|
+ candidate_configs << File.join(dot_chef_dir, 'config.rb')
+ candidate_configs << File.join(dot_chef_dir, 'knife.rb')
+ end
+
+ candidate_configs.find do | candidate_config |
+ have_config?(candidate_config)
+ end
+ end
+
+ def working_directory
+ a = if ChefConfig.windows?
+ env['CD']
+ else
+ env['PWD']
+ end || Dir.pwd
+
+ a
+ end
+
+ def read_config(config_content, config_file_path)
+ Config.from_string(config_content, config_file_path)
+ rescue SignalException
+ raise
+ rescue SyntaxError => e
+ message = ""
+ message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
+ message << "#{e.class.name}: #{e.message}\n"
+ if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
+ line = file_line[/:([\d]+)$/, 1].to_i
+ message << highlight_config_error(config_file_path, line)
+ end
+ raise ChefConfig::ConfigurationError, message
+ rescue Exception => e
+ message = "You have an error in your config file #{config_file_path}\n\n"
+ message << "#{e.class.name}: #{e.message}\n"
+ filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
+ filtered_trace.each {|bt_line| message << " " << bt_line << "\n" }
+ if !filtered_trace.empty?
+ line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
+ message << highlight_config_error(config_file_path, line_nr.to_i)
+ end
+ raise ChefConfig::ConfigurationError, message
+ end
+
+
+ def highlight_config_error(file, line)
+ config_file_lines = []
+ IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
+ if line == 1
+ lines = config_file_lines[0..3]
+ else
+ lines = config_file_lines[Range.new(line - 2, line)]
+ end
+ "Relevant file content:\n" + lines.join("\n") + "\n"
+ end
+
+ def logger
+ @logger
+ end
+
+ end
+end
diff --git a/spec/unit/workstation_config_loader_spec.rb b/chef-config/spec/unit/workstation_config_loader_spec.rb
index 72631f3dfa..9f24a4f11b 100644
--- a/spec/unit/workstation_config_loader_spec.rb
+++ b/chef-config/spec/unit/workstation_config_loader_spec.rb
@@ -18,9 +18,12 @@
require 'spec_helper'
require 'tempfile'
-require 'chef/workstation_config_loader'
-describe Chef::WorkstationConfigLoader do
+require 'chef-config/exceptions'
+require 'chef-config/windows'
+require 'chef-config/workstation_config_loader'
+
+RSpec.describe ChefConfig::WorkstationConfigLoader do
let(:explicit_config_location) { nil }
@@ -65,7 +68,7 @@ describe Chef::WorkstationConfigLoader do
let(:home) { "/Users/example.user" }
before do
- allow(Chef::Util::PathHelper).to receive(:home).with('.chef').and_yield(File.join(home, '.chef'))
+ allow(ChefConfig::PathHelper).to receive(:home).with('.chef').and_yield(File.join(home, '.chef'))
allow(config_loader).to receive(:path_exists?).with("#{home}/.chef/knife.rb").and_return(true)
end
@@ -88,7 +91,7 @@ describe Chef::WorkstationConfigLoader do
let(:env_pwd) { "/path/to/cwd" }
before do
- if Chef::Platform.windows?
+ if ChefConfig.windows?
env["CD"] = env_pwd
else
env["PWD"] = env_pwd
@@ -224,7 +227,7 @@ describe Chef::WorkstationConfigLoader do
let(:explicit_config_location) { "/nope/nope/nope/frab/jab/nab" }
it "raises a configuration error" do
- expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError)
+ expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
end
end
@@ -233,15 +236,20 @@ describe Chef::WorkstationConfigLoader do
let(:config_content) { "" }
+ # We need to keep a reference to the tempfile because while #close does
+ # not unlink the file, the object being GC'd will.
+ let(:tempfile) do
+ Tempfile.new("Chef-WorkstationConfigLoader-rspec-test").tap do |t|
+ t.print(config_content)
+ t.close
+ end
+ end
+
let(:explicit_config_location) do
- # could use described_class, but remove all ':' from the path if so.
- t = Tempfile.new("Chef-WorkstationConfigLoader-rspec-test")
- t.print(config_content)
- t.close
- t.path
+ tempfile.path
end
- after { File.unlink(explicit_config_location) if File.exists?(explicit_config_location) }
+ after { File.unlink(explicit_config_location) if File.exist?(explicit_config_location) }
context "and is valid" do
@@ -249,12 +257,12 @@ describe Chef::WorkstationConfigLoader do
it "loads the config" do
expect(config_loader.load).to be(true)
- expect(Chef::Config.config_file_evaluated).to be(true)
+ expect(ChefConfig::Config.config_file_evaluated).to be(true)
end
- it "sets Chef::Config.config_file" do
+ it "sets ChefConfig::Config.config_file" do
config_loader.load
- expect(Chef::Config.config_file).to eq(explicit_config_location)
+ expect(ChefConfig::Config.config_file).to eq(explicit_config_location)
end
end
@@ -263,7 +271,7 @@ describe Chef::WorkstationConfigLoader do
let(:config_content) { "{{{{{:{{" }
it "raises a ConfigurationError" do
- expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError)
+ expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
end
end
@@ -272,7 +280,7 @@ describe Chef::WorkstationConfigLoader do
let(:config_content) { ":foo\n:bar\nraise 'oops'\n:baz\n" }
it "raises a ConfigurationError" do
- expect { config_loader.load }.to raise_error(Chef::Exceptions::ConfigurationError)
+ expect { config_loader.load }.to raise_error(ChefConfig::ConfigurationError)
end
end
diff --git a/chef-windows.gemspec b/chef-windows.gemspec
index 167358c7d7..428174889f 100644
--- a/chef-windows.gemspec
+++ b/chef-windows.gemspec
@@ -10,7 +10,7 @@ gemspec.add_dependency "win32-eventlog", "~> 0.6.2"
gemspec.add_dependency "win32-mmap", "~> 0.4.1"
gemspec.add_dependency "win32-mutex", "~> 0.4.2"
gemspec.add_dependency "win32-process", "~> 0.7.5"
-gemspec.add_dependency "win32-service", "0.8.6"
+gemspec.add_dependency "win32-service", "~> 0.8.7"
gemspec.add_dependency "windows-api", "~> 0.4.4"
gemspec.add_dependency "windows-pr", "~> 1.2.4"
gemspec.add_dependency "wmi-lite", "~> 1.0"
diff --git a/chef.gemspec b/chef.gemspec
index f4f8a31207..f28cde21e7 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -20,13 +20,12 @@ Gem::Specification.new do |s|
s.add_dependency "mixlib-cli", "~> 1.4"
s.add_dependency "mixlib-log", "~> 1.3"
s.add_dependency "mixlib-authentication", "~> 1.3"
- s.add_dependency "mixlib-shellout", ">= 2.0.0.rc.0", "< 3.0"
- s.add_dependency "ohai", "~> 8.0"
+ s.add_dependency "mixlib-shellout", "~> 2.0"
+ s.add_dependency "ohai", ">= 8.6.0.alpha.1", "< 9"
s.add_dependency "ffi-yajl", "~> 2.2"
s.add_dependency "net-ssh", "~> 2.6"
s.add_dependency "net-ssh-multi", "~> 1.1"
- # CHEF-3027: The knife-cloud plugins require newer features from highline, core chef should not.
s.add_dependency "highline", "~> 1.6", ">= 1.6.9"
s.add_dependency "erubis", "~> 2.7"
s.add_dependency "diff-lcs", "~> 1.2", ">= 1.2.4"
diff --git a/kitchen-tests/Gemfile b/kitchen-tests/Gemfile
index 988d876417..5e1907ba1f 100644
--- a/kitchen-tests/Gemfile
+++ b/kitchen-tests/Gemfile
@@ -6,4 +6,5 @@ group :end_to_end do
gem 'kitchen-appbundle-updater', '~> 0.0.1'
gem "kitchen-vagrant", '~> 0.17.0'
gem 'kitchen-ec2', github: 'test-kitchen/kitchen-ec2'
+ gem 'vagrant-wrapper'
end
diff --git a/lib/chef.rb b/lib/chef.rb
index 6bce976439..1a0b802adb 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -31,5 +31,5 @@ require 'chef/daemon'
require 'chef/run_status'
require 'chef/handler'
require 'chef/handler/json_file'
-
+require 'chef/event_dispatch/dsl'
require 'chef/chef_class'
diff --git a/lib/chef/api_client.rb b/lib/chef/api_client.rb
index ad31fb7d7b..b7b9f7dc43 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -1,7 +1,7 @@
#
-# Author:: Adam Jacob (<adam@chef.io>)
-# Author:: Nuo Yan (<nuo@chef.io>)
-# Copyright:: Copyright (c) 2008, 2015 Chef Software, Inc.
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Nuo Yan (<nuo@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,18 +23,18 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
-require 'chef/exceptions'
-require 'chef/mixin/api_version_request_handling'
require 'chef/server_api'
+# DEPRECATION NOTE
+#
+# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1,
+# which will be moved to this namespace. New development should occur in
+# Chef::ApiClientV1 until the time before Chef 13.
class Chef
class ApiClient
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- include Chef::Mixin::ApiVersionRequestHandling
-
- SUPPORTED_API_VERSIONS = [0,1]
# Create a new Chef::ApiClient object.
def initialize
@@ -43,25 +43,6 @@ class Chef
@private_key = nil
@admin = false
@validator = false
- @create_key = nil
- end
-
- def chef_rest_v0
- @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
- end
-
- def chef_rest_v1
- @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1"})
- end
-
- # will default to the current version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- def http_api
- @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url])
- end
-
- # will default to the current version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- def self.http_api
- Chef::REST.new(Chef::Config[:chef_server_url])
end
# Gets or sets the client name.
@@ -113,8 +94,7 @@ class Chef
)
end
- # Private key. The server will return it as a string.
- # Set to true under API V0 to have the server regenerate the default key.
+ # Gets or sets the private key.
#
# @params [Optional String] The string representation of the private key.
# @return [String] The current value.
@@ -122,19 +102,7 @@ class Chef
set_or_return(
:private_key,
arg,
- :kind_of => [String, TrueClass, FalseClass]
- )
- end
-
- # Used to ask server to generate key pair under api V1
- #
- # @params [Optional True/False] Should be true or false - default is false.
- # @return [True/False] The current value
- def create_key(arg=nil)
- set_or_return(
- :create_key,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [String, FalseClass]
)
end
@@ -145,14 +113,13 @@ class Chef
def to_hash
result = {
"name" => @name,
+ "public_key" => @public_key,
"validator" => @validator,
"admin" => @admin,
'json_class' => self.class.name,
"chef_type" => "client"
}
- result["private_key"] = @private_key unless @private_key.nil?
- result["public_key"] = @public_key unless @public_key.nil?
- result["create_key"] = @create_key unless @create_key.nil?
+ result["private_key"] = @private_key if @private_key
result
end
@@ -166,11 +133,10 @@ class Chef
def self.from_hash(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.validator(o["validator"])
- client.private_key(o["private_key"]) if o.key?("private_key")
- client.public_key(o["public_key"]) if o.key?("public_key")
- client.create_key(o["create_key"]) if o.key?("create_key")
client
end
@@ -182,6 +148,10 @@ class Chef
from_hash(Chef::JSONCompat.parse(j))
end
+ def self.http_api
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
+ end
+
def self.reregister(name)
api_client = load(name)
api_client.reregister
@@ -218,11 +188,11 @@ class Chef
# Save this client via the REST API, returns a hash including the private key
def save
begin
- update
+ 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 == "404"
- create
+ http_api.post("clients", {:name => self.name, :admin => self.admin, :validator => self.validator })
else
raise e
end
@@ -230,95 +200,18 @@ class Chef
end
def reregister
- # Try API V0 and if it fails due to V0 not being supported, raise the proper error message.
- # reregister only supported in API V0 or lesser.
- reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
+ 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
- rescue Net::HTTPServerException => e
- # if there was a 406 related to versioning, give error explaining that
- # only API version 0 is supported for reregister command
- if e.response.code == "406" && e.response["x-ops-server-api-version"]
- version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
- min_version = version_header["min_version"]
- max_version = version_header["max_version"]
- error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
- raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
- else
- raise e
- end
- end
-
- # Updates the client via the REST API
- def update
- # NOTE: API V1 dropped support for updating client keys via update (aka PUT),
- # but this code never supported key updating in the first place. Since
- # it was never implemented, we will simply ignore that functionality
- # as it is being deprecated.
- # Delete this comment after V0 support is dropped.
- payload = { :name => name }
- payload[:validator] = validator unless validator.nil?
-
- # DEPRECATION
- # This field is ignored in API V1, but left for backwards-compat,
- # can remove after API V0 is no longer supported.
- payload[:admin] = admin unless admin.nil?
-
- begin
- new_client = chef_rest_v1.put("clients/#{name}", payload)
- rescue Net::HTTPServerException => e
- # rescue API V0 if 406 and the server supports V0
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
- new_client = chef_rest_v0.put("clients/#{name}", payload)
- end
-
- new_client
end
# Create the client via the REST API
def create
- payload = {
- :name => name,
- :validator => validator,
- # this field is ignored in API V1, but left for backwards-compat,
- # can remove after OSC 11 support is finished?
- :admin => admin
- }
- begin
- # try API V1
- raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil?
-
- payload[:public_key] = public_key unless public_key.nil?
- payload[:create_key] = create_key unless create_key.nil?
-
- new_client = chef_rest_v1.post("clients", payload)
-
- # get the private_key out of the chef_key hash if it exists
- if new_client['chef_key']
- if new_client['chef_key']['private_key']
- new_client['private_key'] = new_client['chef_key']['private_key']
- end
- new_client['public_key'] = new_client['chef_key']['public_key']
- new_client.delete('chef_key')
- end
-
- rescue Net::HTTPServerException => e
- # rescue API V0 if 406 and the server supports V0
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
-
- # under API V0, a key pair will always be created unless public_key is
- # passed on initial POST
- payload[:public_key] = public_key unless public_key.nil?
-
- new_client = chef_rest_v0.post("clients", payload)
- end
- Chef::ApiClient.from_hash(self.to_hash.merge(new_client))
+ http_api.post("clients", self)
end
# As a string
@@ -326,5 +219,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::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
+ end
+
end
end
diff --git a/lib/chef/api_client_v1.rb b/lib/chef/api_client_v1.rb
new file mode 100644
index 0000000000..80f0d2517c
--- /dev/null
+++ b/lib/chef/api_client_v1.rb
@@ -0,0 +1,325 @@
+#
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Nuo Yan (<nuo@chef.io>)
+# Copyright:: Copyright (c) 2008, 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/config'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/mash'
+require 'chef/json_compat'
+require 'chef/search/query'
+require 'chef/exceptions'
+require 'chef/mixin/api_version_request_handling'
+require 'chef/server_api'
+require 'chef/api_client'
+
+# COMPATIBILITY NOTE
+#
+# This ApiClientV1 code attempts to make API V1 requests and falls back to
+# API V0 requests when it fails. New development should occur here instead
+# of Chef::ApiClient as this will replace that namespace when Chef 13 is released.
+#
+# If you need to default to API V0 behavior (i.e. you need GET client to return
+# a public key, etc), please use Chef::ApiClient and update your code to support
+# API V1 before you pull in Chef 13.
+class Chef
+ class ApiClientV1
+
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+ include Chef::Mixin::ApiVersionRequestHandling
+
+ SUPPORTED_API_VERSIONS = [0,1]
+
+ # Create a new Chef::ApiClientV1 object.
+ def initialize
+ @name = ''
+ @public_key = nil
+ @private_key = nil
+ @admin = false
+ @validator = false
+ @create_key = nil
+ end
+
+ def chef_rest_v0
+ @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0", :inflate_json_class => false})
+ end
+
+ def chef_rest_v1
+ @chef_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false})
+ end
+
+ def self.http_api
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "1", :inflate_json_class => false})
+ end
+
+ # Gets or sets the client name.
+ #
+ # @params [Optional String] The name must be alpha-numeric plus - and _.
+ # @return [String] The current value of the name.
+ def name(arg=nil)
+ set_or_return(
+ :name,
+ arg,
+ :regex => /^[\-[:alnum:]_\.]+$/
+ )
+ end
+
+ # Gets or sets whether this client is an admin.
+ #
+ # @params [Optional True/False] Should be true or false - default is false.
+ # @return [True/False] The current value
+ def admin(arg=nil)
+ set_or_return(
+ :admin,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ # Gets or sets the public key.
+ #
+ # @params [Optional String] The string representation of the public key.
+ # @return [String] The current value.
+ def public_key(arg=nil)
+ set_or_return(
+ :public_key,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ # Gets or sets whether this client is a validator.
+ #
+ # @params [Boolean] whether or not the client is a validator. If
+ # `nil`, retrieves the already-set value.
+ # @return [Boolean] The current value
+ def validator(arg=nil)
+ set_or_return(
+ :validator,
+ arg,
+ :kind_of => [TrueClass, FalseClass]
+ )
+ end
+
+ # Private key. The server will return it as a string.
+ # Set to true under API V0 to have the server regenerate the default key.
+ #
+ # @params [Optional String] The string representation of the private key.
+ # @return [String] The current value.
+ def private_key(arg=nil)
+ set_or_return(
+ :private_key,
+ arg,
+ :kind_of => [String, TrueClass, FalseClass]
+ )
+ end
+
+ # Used to ask server to generate key pair under api V1
+ #
+ # @params [Optional True/False] Should be true or false - default is false.
+ # @return [True/False] The current value
+ def create_key(arg=nil)
+ set_or_return(
+ :create_key,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ # The hash representation of the object. Includes the name and public_key.
+ # Private key is included if available.
+ #
+ # @return [Hash]
+ def to_hash
+ result = {
+ "name" => @name,
+ "validator" => @validator,
+ "admin" => @admin,
+ "chef_type" => "client"
+ }
+ result["private_key"] = @private_key unless @private_key.nil?
+ result["public_key"] = @public_key unless @public_key.nil?
+ result["create_key"] = @create_key unless @create_key.nil?
+ result
+ end
+
+ # The JSON representation of the object.
+ #
+ # @return [String] the JSON string.
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def self.from_hash(o)
+ client = Chef::ApiClientV1.new
+ client.name(o["name"] || o["clientname"])
+ client.admin(o["admin"])
+ client.validator(o["validator"])
+ client.private_key(o["private_key"]) if o.key?("private_key")
+ client.public_key(o["public_key"]) if o.key?("public_key")
+ client.create_key(o["create_key"]) if o.key?("create_key")
+ client
+ end
+
+ def self.from_json(j)
+ Chef::ApiClientV1.from_hash(Chef::JSONCompat.from_json(j))
+ end
+
+ def self.reregister(name)
+ api_client = Chef::ApiClientV1.load(name)
+ api_client.reregister
+ end
+
+ def self.list(inflate=false)
+ if inflate
+ response = Hash.new
+ Chef::Search::Query.new.search(:client) do |n|
+ n = self.from_hash(n) if n.instance_of?(Hash)
+ response[n.name] = n
+ end
+ response
+ else
+ http_api.get("clients")
+ end
+ end
+
+ # Load a client by name via the API
+ def self.load(name)
+ response = http_api.get("clients/#{name}")
+ Chef::ApiClientV1.from_hash(response)
+ end
+
+ # Remove this client via the REST API
+ def destroy
+ chef_rest_v1.delete("clients/#{@name}")
+ end
+
+ # Save this client via the REST API, returns a hash including the private key
+ def save
+ begin
+ update
+ rescue Net::HTTPServerException => e
+ # If that fails, go ahead and try and update it
+ if e.response.code == "404"
+ create
+ else
+ raise e
+ end
+ end
+ end
+
+ def reregister
+ # Try API V0 and if it fails due to V0 not being supported, raise the proper error message.
+ # reregister only supported in API V0 or lesser.
+ reregistered_self = chef_rest_v0.put("clients/#{name}", { :name => name, :admin => admin, :validator => validator, :private_key => true })
+ if reregistered_self.respond_to?(:[])
+ private_key(reregistered_self["private_key"])
+ else
+ private_key(reregistered_self.private_key)
+ end
+ self
+ rescue Net::HTTPServerException => e
+ # if there was a 406 related to versioning, give error explaining that
+ # only API version 0 is supported for reregister command
+ if e.response.code == "406" && e.response["x-ops-server-api-version"]
+ version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
+ min_version = version_header["min_version"]
+ max_version = version_header["max_version"]
+ error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
+ raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
+ else
+ raise e
+ end
+ end
+
+ # Updates the client via the REST API
+ def update
+ # NOTE: API V1 dropped support for updating client keys via update (aka PUT),
+ # but this code never supported key updating in the first place. Since
+ # it was never implemented, we will simply ignore that functionality
+ # as it is being deprecated.
+ # Delete this comment after V0 support is dropped.
+ payload = { :name => name }
+ payload[:validator] = validator unless validator.nil?
+
+ # DEPRECATION
+ # This field is ignored in API V1, but left for backwards-compat,
+ # can remove after API V0 is no longer supported.
+ payload[:admin] = admin unless admin.nil?
+
+ begin
+ new_client = chef_rest_v1.put("clients/#{name}", payload)
+ rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+ new_client = chef_rest_v0.put("clients/#{name}", payload)
+ end
+
+ Chef::ApiClientV1.from_hash(new_client)
+ end
+
+ # Create the client via the REST API
+ def create
+ payload = {
+ :name => name,
+ :validator => validator,
+ # this field is ignored in API V1, but left for backwards-compat,
+ # can remove after OSC 11 support is finished?
+ :admin => admin
+ }
+ begin
+ # try API V1
+ raise Chef::Exceptions::InvalidClientAttribute, "You cannot set both public_key and create_key for create." if !create_key.nil? && !public_key.nil?
+
+ payload[:public_key] = public_key unless public_key.nil?
+ payload[:create_key] = create_key unless create_key.nil?
+
+ new_client = chef_rest_v1.post("clients", payload)
+
+ # get the private_key out of the chef_key hash if it exists
+ if new_client['chef_key']
+ if new_client['chef_key']['private_key']
+ new_client['private_key'] = new_client['chef_key']['private_key']
+ end
+ new_client['public_key'] = new_client['chef_key']['public_key']
+ new_client.delete('chef_key')
+ end
+
+ rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+
+ # under API V0, a key pair will always be created unless public_key is
+ # passed on initial POST
+ payload[:public_key] = public_key unless public_key.nil?
+
+ new_client = chef_rest_v0.post("clients", payload)
+ end
+ Chef::ApiClientV1.from_hash(self.to_hash.merge(new_client))
+ end
+
+ # As a string
+ def to_s
+ "client[#{@name}]"
+ end
+
+ end
+end
diff --git a/lib/chef/application.rb b/lib/chef/application.rb
index 0563822ede..970544c068 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -382,7 +382,7 @@ class Chef
def emit_warnings
if Chef::Config[:chef_gem_compile_time]
- Chef::Log.deprecation "setting chef_gem_compile_time to true is deprecated"
+ Chef.log_deprecation "setting chef_gem_compile_time to true is deprecated"
end
end
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index e9768b218c..243b441119 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -49,6 +49,24 @@ class Chef::Application::Apply < Chef::Application
:description => "Load attributes from a JSON file or URL",
:proc => nil
+ option :force_logger,
+ :long => "--force-logger",
+ :description => "Use logger output instead of formatter output",
+ :boolean => true,
+ :default => false
+
+ option :force_formatter,
+ :long => "--force-formatter",
+ :description => "Use formatter output instead of logger output",
+ :boolean => true,
+ :default => false
+
+ option :formatter,
+ :short => "-F FORMATTER",
+ :long => "--format FORMATTER",
+ :description => "output format to use",
+ :proc => lambda { |format| Chef::Config.add_formatter(format) }
+
option :log_level,
:short => "-l LEVEL",
:long => "--log_level LEVEL",
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index 409680b553..73eda81343 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -449,9 +449,9 @@ class Chef::Application::Client < Chef::Application
end
def audit_mode_settings_explanation
- "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `:audit_mode = :enabled` in your config file." +
- "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `:audit_mode = :disabled` in your config file." +
- "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `:audit_mode = :audit_only` in your config file." +
+ "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `audit_mode :enabled` in your config file." +
+ "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `audit_mode :disabled` in your config file." +
+ "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `audit_mode :audit_only` in your config file." +
"\nAudit mode is disabled by default."
end
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index dd09d65b42..5bb2a1ceb0 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -214,7 +214,7 @@ class Chef::Application::Solo < Chef::Application
FileUtils.mkdir_p(recipes_path)
tarball_path = File.join(recipes_path, 'recipes.tgz')
fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
- Chef::Mixin::Command.run_command(:command => "tar zxvf #{tarball_path} -C #{recipes_path}")
+ Mixlib::ShellOut.new("tar zxvf #{tarball_path} -C #{recipes_path}").run_command
end
# json_attribs shuld be fetched after recipe_url tarball is unpacked.
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
index de8ed657c2..44526c1720 100644
--- a/lib/chef/application/windows_service_manager.rb
+++ b/lib/chef/application/windows_service_manager.rb
@@ -78,7 +78,7 @@ class Chef
raise ArgumentError, "Service definition is not provided" if service_options.nil?
- required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path]
+ required_options = [:service_name, :service_display_name, :service_description, :service_file_path]
required_options.each do |req_option|
if !service_options.has_key?(req_option)
@@ -92,6 +92,8 @@ class Chef
@service_file_path = service_options[:service_file_path]
@service_start_name = service_options[:run_as_user]
@password = service_options[:run_as_password]
+ @delayed_start = service_options[:delayed_start]
+ @dependencies = service_options[:dependencies]
end
def run(params = ARGV)
@@ -113,17 +115,22 @@ class Chef
cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR)
::Win32::Service.new(
- :service_name => @service_name,
- :display_name => @service_display_name,
- :description => @service_description,
- # Prior to 0.8.5, win32-service creates interactive services by default,
- # and we don't want that, so we need to override the service type.
- :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
- :start_type => ::Win32::Service::SERVICE_AUTO_START,
- :binary_path_name => cmd,
- :service_start_name => @service_start_name,
- :password => @password,
- )
+ :service_name => @service_name,
+ :display_name => @service_display_name,
+ :description => @service_description,
+ # Prior to 0.8.5, win32-service creates interactive services by default,
+ # and we don't want that, so we need to override the service type.
+ :service_type => ::Win32::Service::SERVICE_WIN32_OWN_PROCESS,
+ :start_type => ::Win32::Service::SERVICE_AUTO_START,
+ :binary_path_name => cmd,
+ :service_start_name => @service_start_name,
+ :password => @password,
+ :dependencies => @dependencies
+ )
+ ::Win32::Service.configure(
+ :service_name => @service_name,
+ :delayed_start => @delayed_start
+ ) unless @delayed_start.nil?
puts "Service '#{@service_name}' has successfully been installed."
end
when 'status'
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
index f1dd797c04..c2cb9e2b24 100644
--- a/lib/chef/chef_class.rb
+++ b/lib/chef/chef_class.rb
@@ -28,6 +28,8 @@
require 'chef/platform/provider_priority_map'
require 'chef/platform/resource_priority_map'
+require 'chef/platform/provider_handler_map'
+require 'chef/platform/resource_handler_map'
class Chef
class << self
@@ -50,7 +52,14 @@ class Chef
#
attr_reader :run_context
+ # Register an event handler with user specified block
#
+ # @return[Chef::EventDispatch::Base] handler object
+ def event_handler(&block)
+ dsl = Chef::EventDispatch::DSL.new('Chef client DSL')
+ dsl.instance_eval(&block)
+ end
+
# Get the array of providers associated with a resource_name for the current node
#
# @param resource_name [Symbol] name of the resource as a symbol
@@ -58,7 +67,7 @@ class Chef
# @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
#
def get_provider_priority_array(resource_name)
- result = provider_priority_map.get_priority_array(node, resource_name)
+ result = provider_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -71,7 +80,7 @@ class Chef
# @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
#
def get_resource_priority_array(resource_name)
- result = resource_priority_map.get_priority_array(node, resource_name)
+ result = resource_priority_map.get_priority_array(node, resource_name.to_sym)
result = result.dup if result
result
end
@@ -86,7 +95,7 @@ class Chef
# @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
#
def set_provider_priority_array(resource_name, priority_array, *filter, &block)
- result = provider_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
@@ -101,7 +110,7 @@ class Chef
# @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
#
def set_resource_priority_array(resource_name, priority_array, *filter, &block)
- result = resource_priority_map.set_priority_array(resource_name, priority_array, *filter, &block)
+ result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
result = result.dup if result
result
end
@@ -160,19 +169,48 @@ class Chef
@node = nil
@provider_priority_map = nil
@resource_priority_map = nil
+ @provider_handler_map = nil
+ @resource_handler_map = nil
end
# @api private
def provider_priority_map
- @provider_priority_map ||= begin
- # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
- Chef::Platform::ProviderPriorityMap.instance
- end
+ # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
+ @provider_priority_map ||= Chef::Platform::ProviderPriorityMap.instance
end
# @api private
def resource_priority_map
- @resource_priority_map ||= begin
- Chef::Platform::ResourcePriorityMap.instance
+ @resource_priority_map ||= Chef::Platform::ResourcePriorityMap.instance
+ end
+ # @api private
+ def provider_handler_map
+ @provider_handler_map ||= Chef::Platform::ProviderHandlerMap.instance
+ end
+ # @api private
+ def resource_handler_map
+ @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance
+ end
+
+ #
+ # Emit a deprecation message.
+ #
+ # @param message The message to send.
+ # @param location The location. Defaults to the caller who called you (since
+ # generally the person who triggered the check is the one that needs to be
+ # fixed).
+ #
+ # @example
+ # Chef.deprecation("Deprecated!")
+ #
+ # @api private this will likely be removed in favor of an as-yet unwritten
+ # `Chef.log`
+ def log_deprecation(message, location=caller(2..2)[0])
+ # `run_context.events` is the primary deprecation target if we're in a
+ # run. If we are not yet in a run, print to `Chef::Log`.
+ if run_context && run_context.events
+ run_context.events.deprecation(message, location)
+ else
+ Chef::Log.deprecation(message, location)
end
end
end
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index 6666a3deee..40cbb36530 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -111,7 +111,7 @@ class Chef
#
def initialize(chef_config = Chef::Config, cwd = Dir.pwd, options = {}, ui = nil)
@chef_config = chef_config
- @cwd = cwd
+ @cwd = File.expand_path(cwd)
@cookbook_version = options[:cookbook_version]
if @chef_config[:repo_mode] == 'everything' && is_hosted? && !ui.nil?
@@ -166,34 +166,37 @@ class Chef
# server_path('/home/jkeiser/chef_repo/cookbooks/blah') == '/cookbooks/blah'
# server_path('/home/*/chef_repo/cookbooks/blah') == nil
#
- # If there are multiple paths (cookbooks, roles, data bags, etc. can all
- # have separate paths), and cwd+the path reaches into one of them, we will
- # return a path relative to that. Otherwise we will return a path to
- # chef_repo.
+ # If there are multiple different, manually specified paths to object locations
+ # (cookbooks, roles, data bags, etc. can all have separate paths), and cwd+the
+ # path reaches into one of them, we will return a path relative to the first
+ # one to match it. Otherwise we expect the path provided to be to the chef
+ # repo path itself. Paths that are not available on the server are not supported.
#
# Globs are allowed as well, but globs outside server paths are NOT
# (presently) supported. See above examples. TODO support that.
#
# If the path does not reach into ANY specified directory, nil is returned.
def server_path(file_path)
- pwd = File.expand_path(Dir.pwd)
- absolute_pwd = Chef::ChefFS::PathUtils.realest_path(File.expand_path(file_path, pwd))
+ target_path = Chef::ChefFS::PathUtils.realest_path(file_path, @cwd)
# Check all object paths (cookbooks_dir, data_bags_dir, etc.)
+ # These are either manually specified by the user or autogenerated relative
+ # to chef_repo_path.
object_paths.each_pair do |name, paths|
paths.each do |path|
- realest_path = Chef::ChefFS::PathUtils.realest_path(path)
- if PathUtils.descendant_of?(absolute_pwd, realest_path)
- relative_path = Chef::ChefFS::PathUtils::relative_to(absolute_pwd, realest_path)
- return relative_path == '.' ? "/#{name}" : "/#{name}/#{relative_path}"
+ object_abs_path = Chef::ChefFS::PathUtils.realest_path(path, @cwd)
+ if relative_path = PathUtils.descendant_path(target_path, object_abs_path)
+ return Chef::ChefFS::PathUtils.join("/#{name}", relative_path)
end
end
end
# Check chef_repo_path
Array(@chef_config[:chef_repo_path]).flatten.each do |chef_repo_path|
- realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path)
- if absolute_pwd == realest_chef_repo_path
+ # We're using realest_path here but we really don't need to - we can just expand the
+ # path and use realpath because a repo_path if provided *must* exist.
+ realest_chef_repo_path = Chef::ChefFS::PathUtils.realest_path(chef_repo_path, @cwd)
+ if Chef::ChefFS::PathUtils.os_path_eq?(target_path, realest_chef_repo_path)
return '/'
end
end
@@ -201,15 +204,10 @@ class Chef
nil
end
- # The current directory, relative to server root
+ # The current directory, relative to server root. This is a case-sensitive server path.
+ # It only exists if the current directory is a child of one of the recognized object_paths below.
def base_path
- @base_path ||= begin
- if @chef_config[:chef_repo_path]
- server_path(File.expand_path(@cwd))
- else
- nil
- end
- end
+ @base_path ||= server_path(@cwd)
end
# Print the given server path, relative to the current directory
@@ -217,10 +215,10 @@ class Chef
server_path = entry.path
if base_path && server_path[0,base_path.length] == base_path
if server_path == base_path
- return "."
- elsif server_path[base_path.length,1] == "/"
+ return '.'
+ elsif server_path[base_path.length,1] == '/'
return server_path[base_path.length + 1, server_path.length - base_path.length - 1]
- elsif base_path == "/" && server_path[0,1] == "/"
+ elsif base_path == '/' && server_path[0,1] == '/'
return server_path[1, server_path.length - 1]
end
end
diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb
index 134d22cbd5..b2351dac68 100644
--- a/lib/chef/chef_fs/file_pattern.rb
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -72,7 +72,7 @@ class Chef
def could_match_children?(path)
return false if path == '' # Empty string is not a path
- argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
return false if is_absolute != argument_is_absolute
path = path[1,path.length-1] if argument_is_absolute
@@ -111,7 +111,7 @@ class Chef
#
# This method assumes +could_match_children?(path)+ is +true+.
def exact_child_name_under(path)
- path = path[1,path.length-1] if !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ path = path[1,path.length-1] if Chef::ChefFS::PathUtils::is_absolute?(path)
dirs_in_path = Chef::ChefFS::PathUtils::split(path).length
return nil if exact_parts.length <= dirs_in_path
return exact_parts[dirs_in_path]
@@ -149,7 +149,7 @@ class Chef
# abc/*/def.match?('abc/foo/def') == true
# abc/*/def.match?('abc/foo') == false
def match?(path)
- argument_is_absolute = !!(path =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ argument_is_absolute = Chef::ChefFS::PathUtils::is_absolute?(path)
return false if is_absolute != argument_is_absolute
path = path[1,path.length-1] if argument_is_absolute
!!regexp.match(path)
@@ -160,17 +160,6 @@ class Chef
pattern
end
- # Given a relative file pattern and a directory, makes a new file pattern
- # starting with the directory.
- #
- # FilePattern.relative_to('/usr/local', 'bin/*grok') == FilePattern.new('/usr/local/bin/*grok')
- #
- # BUG: this does not support patterns starting with <tt>..</tt>
- def self.relative_to(dir, pattern)
- return FilePattern.new(pattern) if pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/
- FilePattern.new(Chef::ChefFS::PathUtils::join(dir, pattern))
- end
-
private
def regexp
@@ -195,7 +184,7 @@ class Chef
def calculate
if !@regexp
- @is_absolute = !!(@pattern =~ /^#{Chef::ChefFS::PathUtils::regexp_path_separator}/)
+ @is_absolute = Chef::ChefFS::PathUtils::is_absolute?(@pattern)
full_regexp_parts = []
normalized_parts = []
diff --git a/lib/chef/chef_fs/file_system/acl_dir.rb b/lib/chef/chef_fs/file_system/acl_dir.rb
index c2354d478d..9f68d7cda7 100644
--- a/lib/chef/chef_fs/file_system/acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/acl_dir.rb
@@ -28,10 +28,9 @@ class Chef
parent.parent.child(name).api_path
end
- def child(name)
+ def make_child_entry(name, exists = nil)
result = @children.select { |child| child.name == name }.first if @children
- result ||= can_have_child?(name, false) ?
- AclEntry.new(name, self) : NonexistentFSObject.new(name, self)
+ result || AclEntry.new(name, self, exists)
end
def can_have_child?(name, is_dir)
@@ -42,7 +41,7 @@ class Chef
if @children.nil?
# Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
- @children = names.map { |name| AclEntry.new(name, self, true) }
+ @children = names.map { |name| make_child_entry(name, true) }
end
@children
end
diff --git a/lib/chef/chef_fs/file_system/acls_dir.rb b/lib/chef/chef_fs/file_system/acls_dir.rb
index 938bf73fb2..a8c63726b7 100644
--- a/lib/chef/chef_fs/file_system/acls_dir.rb
+++ b/lib/chef/chef_fs/file_system/acls_dir.rb
@@ -40,8 +40,12 @@ class Chef
parent.api_path
end
+ def make_child_entry(name)
+ children.select { |child| child.name == name }.first
+ end
+
def can_have_child?(name, is_dir)
- is_dir ? ENTITY_TYPES.include(name) : name == 'organization.json'
+ is_dir ? ENTITY_TYPES.include?(name) : name == 'organization.json'
end
def children
diff --git a/lib/chef/chef_fs/file_system/base_fs_dir.rb b/lib/chef/chef_fs/file_system/base_fs_dir.rb
index 8cc277facc..47e33f961a 100644
--- a/lib/chef/chef_fs/file_system/base_fs_dir.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb
@@ -31,11 +31,6 @@ class Chef
true
end
- # Override child(name) to provide a child object by name without the network read
- def child(name)
- children.select { |child| child.name == name }.first || NonexistentFSObject.new(name, self)
- end
-
def can_have_child?(name, is_dir)
true
end
diff --git a/lib/chef/chef_fs/file_system/base_fs_object.rb b/lib/chef/chef_fs/file_system/base_fs_object.rb
index 43e6a513d7..916ab8297d 100644
--- a/lib/chef/chef_fs/file_system/base_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -95,7 +95,10 @@ class Chef
# directly perform a network request to retrieve the y.json data bag. No
# network request was necessary to retrieve
def child(name)
- NonexistentFSObject.new(name, self)
+ if can_have_child?(name, true) || can_have_child?(name, false)
+ result = make_child_entry(name)
+ end
+ result || NonexistentFSObject.new(name, self)
end
# Override children to report your *actual* list of children as an array.
@@ -171,7 +174,7 @@ class Chef
# Important directory attributes: name, parent, path, root
# Overridable attributes: dir?, child(name), path_for_printing
- # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to
+ # Abstract: read, write, delete, children, can_have_child?, create_child, compare_to, make_child_entry
end # class BaseFsObject
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
index a7f1d733b1..4391bdbfcd 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
@@ -58,14 +58,7 @@ class Chef
end
def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select { |entry| !(entry.dir? && entry.children.size == 0) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
end
def can_have_child?(name, is_dir)
@@ -99,7 +92,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
index 66709ccf68..914412f839 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
@@ -34,14 +34,7 @@ class Chef
attr_reader :recursive
def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select { |entry| !(entry.dir? && entry.children.size == 0) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
end
def can_have_child?(name, is_dir)
@@ -78,7 +71,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
index 7c60b51114..5b495666c3 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
@@ -37,21 +37,14 @@ class Chef
attr_reader :chefignore
def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select do |entry|
- # empty cookbooks and cookbook directories are ignored
- if !entry.can_upload?
- Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
- false
- else
- true
- end
- end
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ super.select do |entry|
+ # empty cookbooks and cookbook directories are ignored
+ if !entry.can_upload?
+ Chef::Log.warn("Cookbook '#{entry.name}' is empty or entirely chefignored at #{entry.path_for_printing}")
+ false
+ else
+ true
+ end
end
end
@@ -61,7 +54,7 @@ class Chef
def write_cookbook(cookbook_path, cookbook_version_json, from_fs)
cookbook_name = File.basename(cookbook_path)
- child = make_child(cookbook_name)
+ child = make_child_entry(cookbook_name)
# Use the copy/diff algorithm to copy it down so we don't destroy
# chefignored data. This is terribly un-thread-safe.
@@ -80,7 +73,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
ChefRepositoryFileSystemCookbookDir.new(child_name, self)
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
index 0b14750744..39172e7ab9 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
@@ -70,20 +70,9 @@ class Chef
Chef::JSONCompat.to_json_pretty(object)
end
- def children
- # Except cookbooks and data bag dirs, all things must be json files
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
ChefRepositoryFileSystemEntry.new(child_name, self)
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
index d03baf91fe..267fe30456 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
@@ -68,13 +68,13 @@ class Chef
attr_reader :child_paths
attr_reader :versioned_cookbooks
- CHILDREN = %w(invitations.json members.json org.json)
+ CHILDREN = %w(org.json invitations.json members.json)
def children
@children ||= begin
- result = child_paths.keys.sort.map { |name| make_child_entry(name) }.select { |child| !child.nil? }
- result += root_dir.children.select { |c| CHILDREN.include?(c.name) } if root_dir
- result.sort_by { |c| c.name }
+ result = child_paths.keys.sort.map { |name| make_child_entry(name) }
+ result += CHILDREN.map { |name| make_child_entry(name) }
+ result.select { |c| c && c.exists? }.sort_by { |c| c.name }
end
end
@@ -149,19 +149,23 @@ class Chef
# cookbooks from all of them when you list or grab them).
#
def make_child_entry(name)
- paths = child_paths[name].select do |path|
- File.exists?(path)
+ if CHILDREN.include?(name)
+ return nil if !root_dir
+ return root_dir.child(name)
end
+
+ paths = (child_paths[name] || []).select { |path| File.exists?(path) }
if paths.size == 0
- return nil
+ return NonexistentFSObject.new(name, self)
end
- if name == 'cookbooks'
+ case name
+ when 'cookbooks'
dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
- elsif name == 'data_bags'
+ when 'data_bags'
dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
- elsif name == 'policies'
+ when 'policies'
dirs = paths.map { |path| ChefRepositoryFileSystemPoliciesDir.new(name, self, path) }
- elsif name == 'acls'
+ when 'acls'
dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
else
data_handler = case name
diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
index 370308ee0a..e3ffd644ad 100644
--- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
@@ -90,11 +90,11 @@ class Chef
end
def rest
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true)
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0")
end
def get_json(path)
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key).get(path)
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :api_version => "0").get(path)
end
def chef_rest
@@ -110,7 +110,8 @@ class Chef
end
def can_have_child?(name, is_dir)
- is_dir && children.any? { |child| child.name == name }
+ result = children.select { |child| child.name == name }.first
+ result && !!result.dir? == !!is_dir
end
def org
@@ -124,6 +125,10 @@ class Chef
end
end
+ def make_child_entry(name)
+ children.select { |child| child.name == name }.first
+ end
+
def children
@children ||= begin
result = [
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
index 03652dc376..c0f0390e98 100644
--- a/lib/chef/chef_fs/file_system/cookbook_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_dir.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require 'chef/chef_fs/command_line'
require 'chef/chef_fs/file_system/rest_list_dir'
require 'chef/chef_fs/file_system/cookbook_subdir'
require 'chef/chef_fs/file_system/cookbook_file'
@@ -71,16 +72,15 @@ class Chef
"#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
end
- def child(name)
+ def make_child_entry(name)
# Since we're ignoring the rules and doing a network request here,
# we need to make sure we don't rethrow the exception. (child(name)
# is not supposed to fail.)
begin
- result = children.select { |child| child.name == name }.first
- return result if result
+ children.select { |child| child.name == name }.first
rescue Chef::ChefFS::FileSystem::NotFoundError
+ nil
end
- return NonexistentFSObject.new(name, self)
end
def can_have_child?(name, is_dir)
diff --git a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
index d6246f1e60..560ceb4886 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
@@ -31,7 +31,7 @@ class Chef
def children
if @children.nil?
names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
- @children = names.uniq.map { |name| AclEntry.new(name, self, true) }
+ @children = names.uniq.map { |name| make_child_entry(name, true) }
end
@children
end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
index 27bedd3827..6f49c28996 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb
+++ b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
@@ -36,17 +36,9 @@ class Chef
super("cookbooks", parent)
end
- def child(name)
- if @children
- result = self.children.select { |child| child.name == name }.first
- if result
- result
- else
- NonexistentFSObject.new(name, self)
- end
- else
- CookbookDir.new(name, self)
- end
+ def make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || CookbookDir.new(name, self)
end
def children
diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb
index 6d0685d3b7..1cb61bbd1a 100644
--- a/lib/chef/chef_fs/file_system/data_bags_dir.rb
+++ b/lib/chef/chef_fs/file_system/data_bags_dir.rb
@@ -27,16 +27,14 @@ class Chef
super("data_bags", parent, "data")
end
- def child(name)
+ def make_child_entry(name, exists = false)
result = @children.select { |child| child.name == name }.first if @children
- result || DataBagDir.new(name, self)
+ result || DataBagDir.new(name, self, exists)
end
def children
begin
- @children ||= root.get_json(api_path).keys.sort.map do |entry|
- DataBagDir.new(entry, self, true)
- end
+ @children ||= root.get_json(api_path).keys.sort.map { |entry| make_child_entry(entry, true) }
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout getting children: #{e}"
rescue Net::HTTPServerException => e
diff --git a/lib/chef/chef_fs/file_system/environments_dir.rb b/lib/chef/chef_fs/file_system/environments_dir.rb
index 559dd6af86..3aee3ee5af 100644
--- a/lib/chef/chef_fs/file_system/environments_dir.rb
+++ b/lib/chef/chef_fs/file_system/environments_dir.rb
@@ -30,7 +30,7 @@ class Chef
super("environments", parent, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new)
end
- def _make_child_entry(name, exists = nil)
+ def make_child_entry(name, exists = nil)
if name == '_default.json'
DefaultEnvironmentEntry.new(name, self, exists)
else
diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb
index 1af7e618de..8611aa2e0f 100644
--- a/lib/chef/chef_fs/file_system/file_system_entry.rb
+++ b/lib/chef/chef_fs/file_system/file_system_entry.rb
@@ -40,15 +40,18 @@ class Chef
end
def children
+ # Except cookbooks and data bag dirs, all things must be json files
begin
- Dir.entries(file_path).sort.select { |entry| entry != '.' && entry != '..' }.map { |entry| make_child(entry) }
+ Dir.entries(file_path).sort.
+ map { |child_name| make_child_entry(child_name) }.
+ select { |child| child && can_have_child?(child.name, child.dir?) }
rescue Errno::ENOENT
raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
end
end
def create_child(child_name, file_contents=nil)
- child = make_child(child_name)
+ child = make_child_entry(child_name)
if child.exists?
raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
end
@@ -80,7 +83,7 @@ class Chef
end
def exists?
- File.exists?(file_path)
+ File.exists?(file_path) && parent.can_have_child?(name, dir?)
end
def read
@@ -99,7 +102,7 @@ class Chef
protected
- def make_child(child_name)
+ def make_child_entry(child_name)
FileSystemEntry.new(child_name, self)
end
end
diff --git a/lib/chef/chef_fs/file_system/memory_dir.rb b/lib/chef/chef_fs/file_system/memory_dir.rb
index a7eda3c654..260a91693c 100644
--- a/lib/chef/chef_fs/file_system/memory_dir.rb
+++ b/lib/chef/chef_fs/file_system/memory_dir.rb
@@ -1,5 +1,4 @@
require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/nonexistent_fs_object'
require 'chef/chef_fs/file_system/memory_file'
class Chef
@@ -13,8 +12,8 @@ class Chef
attr_reader :children
- def child(name)
- @children.select { |child| child.name == name }.first || Chef::ChefFS::FileSystem::NonexistentFSObject.new(name, self)
+ def make_child_entry(name)
+ @children.select { |child| child.name == name }.first
end
def add_child(child)
diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
index 06d4af705d..70b827f85f 100644
--- a/lib/chef/chef_fs/file_system/multiplexed_dir.rb
+++ b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
@@ -35,6 +35,21 @@ class Chef
end
end
+ def make_child_entry(name)
+ result = nil
+ multiplexed_dirs.each do |dir|
+ child_entry = dir.child(name)
+ if child_entry.exists?
+ if result
+ Chef::Log.warn("Child with name '#{child_entry.name}' found in multiple directories: #{result.parent.path_for_printing} and #{child_entry.parent.path_for_printing}")
+ else
+ result = child_entry
+ end
+ end
+ end
+ result
+ end
+
def can_have_child?(name, is_dir)
write_dir.can_have_child?(name, is_dir)
end
diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb
index c3c48377cd..2610b06a82 100644
--- a/lib/chef/chef_fs/file_system/nodes_dir.rb
+++ b/lib/chef/chef_fs/file_system/nodes_dir.rb
@@ -33,7 +33,7 @@ class Chef
def children
begin
@children ||= root.get_json(env_api_path).keys.sort.map do |key|
- _make_child_entry("#{key}.json", true)
+ make_child_entry("#{key}.json", true)
end
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb
index 672fa444f1..0ac735a2c4 100644
--- a/lib/chef/chef_fs/file_system/rest_list_dir.rb
+++ b/lib/chef/chef_fs/file_system/rest_list_dir.rb
@@ -33,12 +33,6 @@ class Chef
attr_reader :api_path
attr_reader :data_handler
- def child(name)
- result = @children.select { |child| child.name == name }.first if @children
- result ||= can_have_child?(name, false) ?
- _make_child_entry(name) : NonexistentFSObject.new(name, self)
- end
-
def can_have_child?(name, is_dir)
name =~ /\.json$/ && !is_dir
end
@@ -46,7 +40,7 @@ class Chef
def children
begin
@children ||= root.get_json(api_path).keys.sort.map do |key|
- _make_child_entry("#{key}.json", true)
+ make_child_entry("#{key}.json", true)
end
rescue Timeout::Error => e
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
@@ -66,7 +60,7 @@ class Chef
raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Parse error reading JSON creating child '#{name}': #{e}"
end
- result = _make_child_entry(name, true)
+ result = make_child_entry(name, true)
if data_handler
object = data_handler.normalize_for_post(object, result)
@@ -106,7 +100,8 @@ class Chef
parent.rest
end
- def _make_child_entry(name, exists = nil)
+ def make_child_entry(name, exists = nil)
+ @children.select { |child| child.name == name }.first if @children
RestListEntry.new(name, self, exists)
end
end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 86872dab71..9101e455f8 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -17,6 +17,7 @@
#
require 'chef/knife'
+require 'pathname'
class Chef
module ChefFS
@@ -63,7 +64,7 @@ class Chef
# --chef-repo-path forcibly overrides all other paths
if config[:chef_repo_path]
Chef::Config[:chef_repo_path] = config[:chef_repo_path]
- %w(acl client cookbook container data_bag environment group node role user).each do |variable_name|
+ Chef::ChefFS::Config::INFLECTIONS.each_value do |variable_name|
Chef::Config.delete("#{variable_name}_path".to_sym)
end
end
@@ -98,14 +99,41 @@ class Chef
end
def pattern_arg_from(arg)
- # TODO support absolute file paths and not just patterns? Too much?
- # Could be super useful in a world with multiple repo paths
- if !@chef_fs_config.base_path && !Chef::ChefFS::PathUtils.is_absolute?(arg)
- # Check if chef repo path is specified to give a better error message
- ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path")
+ inferred_path = nil
+ if Chef::ChefFS::PathUtils.is_absolute?(arg)
+ # We should be able to use this as-is - but the user might have incorrectly provided
+ # us with a path that is based off of the OS root path instead of the Chef-FS root.
+ # Do a quick and dirty sanity check.
+ if possible_server_path = @chef_fs_config.server_path(arg)
+ ui.warn("The absolute path provided is suspicious: #{arg}")
+ ui.warn("If you wish to refer to a file location, please provide a path that is rooted at the chef-repo.")
+ ui.warn("Consider writing '#{possible_server_path}' instead of '#{arg}'")
+ end
+ # Use the original path because we can't be sure.
+ inferred_path = arg
+ elsif arg[0,1] == '~'
+ # Let's be nice and fix it if possible - but warn the user.
+ ui.warn("A path relative to a user home directory has been provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif Pathname.new(arg).absolute?
+ # It is definitely a system absolute path (such as C:\ or \\foo\bar) but it cannot be
+ # interpreted as a Chef-FS absolute path. Again attempt to be nice but warn the user.
+ ui.warn("An absolute file system path that isn't a server path was provided: #{arg}")
+ ui.warn("Paths provided need to be rooted at the chef-repo being considered or be relative paths.")
+ inferred_path = @chef_fs_config.server_path(arg)
+ ui.warn("Using '#{inferred_path}' as the path instead of '#{arg}'.")
+ elsif @chef_fs_config.base_path.nil?
+ # These are all relative paths. We can't resolve and root paths unless we are in the
+ # chef repo.
+ ui.error("Attempt to use relative path '#{arg}' when current directory is outside the repository path.")
+ ui.error("Current working directory is '#{@chef_fs_config.cwd}'.")
exit(1)
+ else
+ inferred_path = Chef::ChefFS::PathUtils::join(@chef_fs_config.base_path, arg)
end
- Chef::ChefFS::FilePattern.relative_to(@chef_fs_config.base_path, arg)
+ Chef::ChefFS::FilePattern.new(inferred_path)
end
def format_path(entry)
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
index 9ef75ce2e5..595f966378 100644
--- a/lib/chef/chef_fs/path_utils.rb
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -23,31 +23,31 @@ class Chef
module ChefFS
class PathUtils
- # If you are in 'source', this is what you would have to type to reach 'dest'
- # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
- # relative_to('/a/b', '/a/b') == '.'
- def self.relative_to(dest, source)
- # Skip past the common parts
- source_parts = Chef::ChefFS::PathUtils.split(source)
- dest_parts = Chef::ChefFS::PathUtils.split(dest)
- i = 0
- until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
- i+=1
- end
- # dot-dot up from 'source' to the common ancestor, then
- # descend to 'dest' from the common ancestor
- result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
- result == '' ? '.' : result
- end
+ # A Chef-FS path is a path in a chef-repository that can be used to address
+ # both files on a local file-system as well as objects on a chef server.
+ # These paths are stricter than file-system paths allowed on various OSes.
+ # Absolute Chef-FS paths begin with "/" (on windows, "\" is acceptable as well).
+ # "/" is used as the path element separator (on windows, "\" is acceptable as well).
+ # No directory/path element may contain a literal "\" character. Any such characters
+ # encountered are either dealt with as separators (on windows) or as escape
+ # characters (on POSIX systems). Relative Chef-FS paths may use ".." or "." but
+ # may never use these to back-out of the root of a Chef-FS path. Any such extraneous
+ # ".."s are ignored.
+ # Chef-FS paths are case sensitive (since the paths on the server are).
+ # On OSes with case insensitive paths, you may be unable to locally deal with two
+ # objects whose server paths only differ by case. OTOH, the case of path segments
+ # that are outside the Chef-FS root (such as when looking at a file-system absolute
+ # path to discover the Chef-FS root path) are handled in accordance to the rules
+ # of the local file-system and OS.
def self.join(*parts)
return "" if parts.length == 0
# Determine if it started with a slash
absolute = parts[0].length == 0 || parts[0].length > 0 && parts[0] =~ /^#{regexp_path_separator}/
# Remove leading and trailing slashes from each part so that the join will work (and the slash at the end will go away)
- parts = parts.map { |part| part.gsub(/^\/|\/$/, "") }
+ parts = parts.map { |part| part.gsub(/^#{regexp_path_separator}+|#{regexp_path_separator}+$/, '') }
# Don't join empty bits
- result = parts.select { |part| part != "" }.join("/")
+ result = parts.select { |part| part != '' }.join('/')
# Put the / back on
absolute ? "/#{result}" : result
end
@@ -60,36 +60,67 @@ class Chef
Chef::ChefFS::windows? ? '[\/\\\\]' : '/'
end
+ # Given a server path, determines if it is absolute.
+ def self.is_absolute?(path)
+ !!(path =~ /^#{regexp_path_separator}/)
+ end
# Given a path which may only be partly real (i.e. /x/y/z when only /x exists,
# or /x/y/*/blah when /x/y/z/blah exists), call File.realpath on the biggest
- # part that actually exists.
+ # part that actually exists. The paths operated on here are not Chef-FS paths.
+ # These are OS paths that may contain symlinks but may not also fully exist.
#
# If /x is a symlink to /blarghle, and has no subdirectories, then:
# PathUtils.realest_path('/x/y/z') == '/blarghle/y/z'
# PathUtils.realest_path('/x/*/z') == '/blarghle/*/z'
# PathUtils.realest_path('/*/y/z') == '/*/y/z'
- def self.realest_path(path)
- path = Pathname.new(path)
- begin
- path.realpath.to_s
- rescue Errno::ENOENT
- dirname = path.dirname
- if dirname
- PathUtils.join(realest_path(dirname), path.basename.to_s)
- else
- path.to_s
+ #
+ # TODO: Move this to wherever util/path_helper is these days.
+ def self.realest_path(path, cwd = Dir.pwd)
+ path = File.expand_path(path, cwd)
+ parent_path = File.dirname(path)
+ suffix = []
+
+ # File.dirname happens to return the path as its own dirname if you're
+ # at the root (such as at \\foo\bar, C:\ or /)
+ until parent_path == path do
+ # This can occur if a path such as "C:" is given. Ruby gives the parent as "C:."
+ # for reasons only it knows.
+ raise ArgumentError "Invalid path segment #{path}" if parent_path.length > path.length
+ begin
+ path = File.realpath(path)
+ break
+ rescue Errno::ENOENT
+ suffix << File.basename(path)
+ path = parent_path
+ parent_path = File.dirname(path)
end
end
+ File.join(path, *suffix.reverse)
end
- def self.descendant_of?(path, ancestor)
- path[0,ancestor.length] == ancestor &&
- (ancestor.length == path.length || path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/)
+ # Compares two path fragments according to the case-sentitivity of the host platform.
+ def self.os_path_eq?(left, right)
+ Chef::ChefFS::windows? ? left.casecmp(right) == 0 : left == right
end
- def self.is_absolute?(path)
- path =~ /^#{regexp_path_separator}/
+ # Given two general OS-dependent file paths, determines the relative path of the
+ # child with respect to the ancestor. Both child and ancestor must exist and be
+ # fully resolved - this is strictly a lexical comparison. No trailing slashes
+ # and other shenanigans are allowed.
+ #
+ # TODO: Move this to util/path_helper.
+ def self.descendant_path(path, ancestor)
+ candidate_fragment = path[0, ancestor.length]
+ return nil unless PathUtils.os_path_eq?(candidate_fragment, ancestor)
+ if ancestor.length == path.length
+ ''
+ elsif path[ancestor.length,1] =~ /#{PathUtils.regexp_path_separator}/
+ path[ancestor.length+1..-1]
+ else
+ nil
+ end
end
+
end
end
end
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 86e92585e3..621ce3d489 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -3,7 +3,7 @@
# Author:: Christopher Walters (<cw@opscode.com>)
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Tim Hinderliter (<tim@opscode.com>)
-# Copyright:: Copyright (c) 2008-2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -302,19 +302,24 @@ class Chef
@run_status = nil
run_context = nil
runlock.release
- GC.start
end
# Raise audit, converge, and other errors here so that we exit
# with the proper exit status code and everything gets raised
# as a RunFailedWrappingError
if run_error || converge_error || audit_error
- error = if run_error == converge_error
- Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- else
- Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
- end
- error.fill_backtrace
+ error = if Chef::Config[:audit_mode] == :disabled
+ run_error || converge_error
+ else
+ e = if run_error == converge_error
+ Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
+ else
+ Chef::Exceptions::RunFailedWrappingError.new(run_error, converge_error, audit_error)
+ end
+ e.fill_backtrace
+ e
+ end
+
Chef::Application.debug_stacktrace(error)
raise error
end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 9beb18b53e..6382af14c2 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -28,9 +28,21 @@ require 'chef-config/logger'
ChefConfig.logger = Chef::Log
require 'chef-config/config'
-
require 'chef/platform/query_helpers'
+# Ohai::Config defines its own log_level and log_location. When loaded, it will
+# override the default ChefConfig::Config values. We save them here before
+# loading ohai/config so that we can override them again inside Chef::Config.
+#
+# REMOVEME once these configurables are removed from the top level of Ohai.
+LOG_LEVEL = ChefConfig::Config[:log_level] unless defined? LOG_LEVEL
+LOG_LOCATION = ChefConfig::Config[:log_location] unless defined? LOG_LOCATION
+
+# Load the ohai config into the chef config. We can't have an empty ohai
+# configuration context because `ohai.plugins_path << some_path` won't work,
+# and providing default ohai config values here isn't DRY.
+require 'ohai/config'
+
class Chef
Config = ChefConfig::Config
@@ -49,5 +61,24 @@ class Chef
evt_loggers
end
+ # Override the default values that were set by Ohai.
+ #
+ # REMOVEME once these configurables are removed from the top level of Ohai.
+ default :log_level, LOG_LEVEL
+ default :log_location, LOG_LOCATION
+
+ # Ohai::Config[:log_level] is deprecated and warns when set. Unfortunately,
+ # there is no way to distinguish between setting log_level and setting
+ # Ohai::Config[:log_level]. Since log_level and log_location are used by
+ # chef-client and other tools (e.g., knife), we will mute the warnings here
+ # by redefining the config_attr_writer to not warn for these options.
+ #
+ # REMOVEME once the warnings for these configurables are removed from Ohai.
+ [ :log_level, :log_location ].each do |option|
+ config_attr_writer option do |value|
+ value
+ end
+ end
+
end
end
diff --git a/lib/chef/constants.rb b/lib/chef/constants.rb
new file mode 100644
index 0000000000..d39ce4c68d
--- /dev/null
+++ b/lib/chef/constants.rb
@@ -0,0 +1,27 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ NOT_PASSED = Object.new
+ def NOT_PASSED.to_s
+ "NOT_PASSED"
+ end
+ def NOT_PASSED.inspect
+ to_s
+ end
+ NOT_PASSED.freeze
+end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 01a98fda39..9822920a7d 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -54,12 +54,13 @@ class Chef
VERSION = 'version'.freeze
SOURCE_URL = 'source_url'.freeze
ISSUES_URL = 'issues_url'.freeze
+ PRIVACY = 'privacy'.freeze
COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer,
:maintainer_email, :license, :platforms, :dependencies,
:recommendations, :suggestions, :conflicting, :providing,
:replacing, :attributes, :groupings, :recipes, :version,
- :source_url, :issues_url ]
+ :source_url, :issues_url, :privacy ]
VERSION_CONSTRAINTS = {:depends => DEPENDENCIES,
:recommends => RECOMMENDATIONS,
@@ -116,6 +117,7 @@ class Chef
@version = Version.new("0.0.0")
@source_url = ''
@issues_url = ''
+ @privacy = false
@errors = []
end
@@ -454,7 +456,8 @@ class Chef
:recipes => { :kind_of => [ Array ], :default => [] },
:default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] },
:source_url => { :kind_of => String },
- :issues_url => { :kind_of => String }
+ :issues_url => { :kind_of => String },
+ :privacy => { :kind_of => [ TrueClass, FalseClass ] }
}
)
options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
@@ -498,7 +501,8 @@ class Chef
RECIPES => self.recipes,
VERSION => self.version,
SOURCE_URL => self.source_url,
- ISSUES_URL => self.issues_url
+ ISSUES_URL => self.issues_url,
+ PRIVACY => self.privacy
}
end
@@ -532,6 +536,7 @@ class Chef
@version = o[VERSION] if o.has_key?(VERSION)
@source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL)
@issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL)
+ @privacy = o[PRIVACY] if o.has_key?(PRIVACY)
self
end
@@ -590,6 +595,23 @@ class Chef
)
end
+ #
+ # Sets the cookbook's privacy flag, or returns it.
+ #
+ # === Parameters
+ # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+ #
+ # === Returns
+ # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+ #
+ def privacy(arg=nil)
+ set_or_return(
+ :privacy,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
private
def run_validation
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index 1b96d0510b..fc8e739d73 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -131,7 +131,7 @@ class Chef
files_remaining_by_cookbook[file.cookbook] -= 1
if files_remaining_by_cookbook[file.cookbook] == 0
- @events.synchronized_cookbook(file.cookbook.name)
+ @events.synchronized_cookbook(file.cookbook.name, file.cookbook)
end
end
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 8d302eeec2..bff3146572 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -51,12 +51,12 @@ class Chef
attr_accessor :metadata_filenames
def status=(new_status)
- Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+ Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1))
@status = new_status
end
def status
- Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+ Chef.log_deprecation("Deprecated method `status' called. This method will be removed.", caller(1..1))
@status
end
@@ -480,7 +480,7 @@ class Chef
# @deprecated This method was used by the Ruby Chef Server and is no longer
# needed. There is no replacement.
def generate_manifest_with_urls(&url_generator)
- Chef::Log.deprecation("Deprecated method #generate_manifest_with_urls called from #{caller(1).first}")
+ Chef.log_deprecation("Deprecated method #generate_manifest_with_urls.", caller(1..1))
rendered_manifest = manifest.dup
COOKBOOK_SEGMENTS.each do |segment|
diff --git a/lib/chef/delayed_evaluator.rb b/lib/chef/delayed_evaluator.rb
new file mode 100644
index 0000000000..9f18a53445
--- /dev/null
+++ b/lib/chef/delayed_evaluator.rb
@@ -0,0 +1,21 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+class Chef
+ class DelayedEvaluator < Proc
+ end
+end
diff --git a/lib/chef/deprecation/mixin/template.rb b/lib/chef/deprecation/mixin/template.rb
index 36d18ad90d..58a661c4bd 100644
--- a/lib/chef/deprecation/mixin/template.rb
+++ b/lib/chef/deprecation/mixin/template.rb
@@ -25,7 +25,7 @@ class Chef
# == Deprecation::Provider::Mixin::Template
# This module contains the deprecated functions of
# Chef::Mixin::Template. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module Template
@@ -46,4 +46,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/deprecation/provider/cookbook_file.rb b/lib/chef/deprecation/provider/cookbook_file.rb
index dfbf4a39a4..92f5ce3623 100644
--- a/lib/chef/deprecation/provider/cookbook_file.rb
+++ b/lib/chef/deprecation/provider/cookbook_file.rb
@@ -24,7 +24,7 @@ class Chef
# == Deprecation::Provider::CookbookFile
# This module contains the deprecated functions of
# Chef::Provider::CookbookFile. These functions are refactored to
- # different components. They are frozen and will be removed in Chef 12.
+ # different components. They are frozen and will be removed in Chef 13.
#
module CookbookFile
diff --git a/lib/chef/deprecation/provider/file.rb b/lib/chef/deprecation/provider/file.rb
index 125f31fe10..31038ab3d8 100644
--- a/lib/chef/deprecation/provider/file.rb
+++ b/lib/chef/deprecation/provider/file.rb
@@ -25,7 +25,7 @@ class Chef
# == Deprecation::Provider::File
# This module contains the deprecated functions of
# Chef::Provider::File. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module File
diff --git a/lib/chef/deprecation/provider/remote_file.rb b/lib/chef/deprecation/provider/remote_file.rb
index 4452de67cd..c06a5cc695 100644
--- a/lib/chef/deprecation/provider/remote_file.rb
+++ b/lib/chef/deprecation/provider/remote_file.rb
@@ -23,7 +23,7 @@ class Chef
# == Deprecation::Provider::RemoteFile
# This module contains the deprecated functions of
# Chef::Provider::RemoteFile. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module RemoteFile
@@ -83,4 +83,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/deprecation/provider/template.rb b/lib/chef/deprecation/provider/template.rb
index d7a228e97a..34e5f54b7e 100644
--- a/lib/chef/deprecation/provider/template.rb
+++ b/lib/chef/deprecation/provider/template.rb
@@ -25,7 +25,7 @@ class Chef
# == Deprecation::Provider::Template
# This module contains the deprecated functions of
# Chef::Provider::Template. These functions are refactored to different
- # components. They are frozen and will be removed in Chef 12.
+ # components. They are frozen and will be removed in Chef 13.
#
module Template
diff --git a/lib/chef/deprecation/warnings.rb b/lib/chef/deprecation/warnings.rb
index 34f468ff53..376629710e 100644
--- a/lib/chef/deprecation/warnings.rb
+++ b/lib/chef/deprecation/warnings.rb
@@ -25,10 +25,9 @@ class Chef
m = instance_method(name)
define_method(name) do |*args|
message = []
- message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12."
- message << "Please update your cookbooks accordingly. Accessed from:"
- caller[0..3].each {|l| message << l}
- Chef::Log.deprecation message
+ message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13."
+ message << "Please update your cookbooks accordingly."
+ Chef.log_deprecation(message, caller(0..3))
super(*args)
end
end
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb
index 7af67e94a5..c577118dd4 100644
--- a/lib/chef/dsl/reboot_pending.rb
+++ b/lib/chef/dsl/reboot_pending.rb
@@ -45,7 +45,7 @@ class Chef
registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') ||
# Vista + Server 2008 and newer may have reboots pending from CBS
- registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
+ registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') ||
# The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates
# http://support.microsoft.com/kb/832475
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index d69f0a8f11..26c0ec6768 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -19,12 +19,10 @@
require 'chef/mixin/convert_to_class_name'
require 'chef/exceptions'
-require 'chef/resource_builder'
require 'chef/mixin/shell_out'
require 'chef/mixin/powershell_out'
require 'chef/dsl/resources'
require 'chef/dsl/definitions'
-require 'chef/resource'
class Chef
module DSL
@@ -122,9 +120,9 @@ class Chef
def describe_self_for_error
if respond_to?(:name)
- %Q[`#{self.class.name} "#{name}"']
+ %Q[`#{self.class} "#{name}"']
elsif respond_to?(:recipe_name)
- %Q[`#{self.class.name} "#{recipe_name}"']
+ %Q[`#{self.class} "#{recipe_name}"']
else
to_s
end
@@ -142,8 +140,7 @@ class Chef
# method_missing manually. Not a fan. Not. A. Fan.
#
if respond_to?(method_symbol)
- Chef::Log.deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13.")
- Chef::Log.deprecation("Use public_send() or send() instead.")
+ Chef.log_deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.")
return send(method_symbol, *args, &block)
end
@@ -152,7 +149,7 @@ class Chef
# never called. DEPRECATED.
#
if run_context.definitions.has_key?(method_symbol.to_sym)
- Chef::Log.deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.")
+ Chef.log_deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.")
Chef::DSL::Definitions.add_definition(method_symbol)
return send(method_symbol, *args, &block)
end
@@ -176,10 +173,32 @@ class Chef
raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
end
end
+
+ module FullDSL
+ require 'chef/dsl/data_query'
+ require 'chef/dsl/platform_introspection'
+ require 'chef/dsl/include_recipe'
+ require 'chef/dsl/registry_helper'
+ require 'chef/dsl/reboot_pending'
+ require 'chef/dsl/audit'
+ require 'chef/dsl/powershell'
+ include Chef::DSL::DataQuery
+ include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::IncludeRecipe
+ include Chef::DSL::Recipe
+ include Chef::DSL::RegistryHelper
+ include Chef::DSL::RebootPending
+ include Chef::DSL::Audit
+ include Chef::DSL::Powershell
+ end
end
end
end
+# Avoid circular references for things that are only used in instance methods
+require 'chef/resource_builder'
+require 'chef/resource'
+
# **DEPRECATED**
# This used to be part of chef/mixin/recipe_definition_dsl_core. Load the file to activate the deprecation code.
require 'chef/mixin/recipe_definition_dsl_core'
diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb
index 1ce12ed0a0..49588ed516 100644
--- a/lib/chef/dsl/resources.rb
+++ b/lib/chef/dsl/resources.rb
@@ -10,14 +10,16 @@ class Chef
def self.add_resource_dsl(dsl_name)
begin
module_eval(<<-EOM, __FILE__, __LINE__+1)
- def #{dsl_name}(name=nil, created_at=nil, &block)
- declare_resource(#{dsl_name.inspect}, name, created_at || caller[0], &block)
+ def #{dsl_name}(*args, &block)
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1
+ declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block)
end
EOM
rescue SyntaxError
# Handle the case where dsl_name has spaces, etc.
- define_method(dsl_name.to_sym) do |name=nil, created_at=nil, &block|
- declare_resource(dsl_name, name, created_at || caller[0], &block)
+ define_method(dsl_name.to_sym) do |*args, &block|
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1
+ declare_resource(dsl_name, args[0], caller[0], &block)
end
end
end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 73fe25ec13..1c9a58be23 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -47,14 +47,19 @@ class Chef
def ohai_completed(node)
end
- # Already have a client key, assuming this node has registered.
+ # Announce that we're not going to register the client. Generally because
+ # we already have the private key, or because we're deliberately not using
+ # a key.
def skipping_registration(node_name, config)
end
- # About to attempt to register as +node_name+
+ # About to attempt to create a private key registered to the server with
+ # client +node_name+.
def registration_start(node_name, config)
end
+ # Successfully created the private key and registered this client with the
+ # server.
def registration_completed
end
@@ -118,8 +123,8 @@ class Chef
def cookbook_sync_start(cookbook_count)
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
end
# Called when an individual file in a cookbook has been updated
@@ -269,26 +274,37 @@ class Chef
# def notifications_resolved
# end
+ #
+ # Resource events and ordering:
+ #
+ # 1. Start the action
+ # - resource_action_start
+ # 2. Check the guard
+ # - resource_skipped: (goto 7) if only_if/not_if say to skip
+ # 3. Load the current resource
+ # - resource_current_state_loaded
+ # - resource_current_state_load_bypassed (if not why-run safe)
+ # 4. Check if why-run safe
+ # - resource_bypassed: (goto 7) if not why-run safe
+ # 5. During processing:
+ # - resource_update_applied: For each actual change (many per action)
+ # 6. Processing complete status:
+ # - resource_failed if the resource threw an exception while running
+ # - resource_failed_retriable: (goto 3) if resource failed and will be retried
+ # - resource_updated if the resource was updated (resource_update_applied will have been called)
+ # - resource_up_to_date if the resource was up to date (no resource_update_applied)
+ # 7. Processing complete:
+ # - resource_completed
+ #
+
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type=nil, notifier=nil)
end
- # Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
-
- # Called when a resource fails and will not be retried.
- def resource_failed(resource, action, exception)
- end
-
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
end
- # Called when a resource action has been completed
- def resource_completed(resource)
- end
-
# Called after #load_current_resource has run.
def resource_current_state_loaded(resource, action, current_resource)
end
@@ -302,21 +318,33 @@ class Chef
def resource_bypassed(resource, action, current_resource)
end
- # Called when a resource has no converge actions, e.g., it was already correct.
- def resource_up_to_date(resource, action)
- end
-
# Called when a change has been made to a resource. May be called multiple
# times per resource, e.g., a file may have its content updated, and then
# its permissions updated.
def resource_update_applied(resource, action, update)
end
+ # Called when a resource fails, but will retry.
+ def resource_failed_retriable(resource, action, retry_count, exception)
+ end
+
+ # Called when a resource fails and will not be retried.
+ def resource_failed(resource, action, exception)
+ end
+
# Called after a resource has been completely converged, but only if
# modifications were made.
def resource_updated(resource, action)
end
+ # Called when a resource has no converge actions, e.g., it was already correct.
+ def resource_up_to_date(resource, action)
+ end
+
+ # Called when a resource action has been completed
+ def resource_completed(resource)
+ end
+
# A stream has opened.
def stream_opened(stream, options = {})
end
@@ -352,8 +380,9 @@ class Chef
def whyrun_assumption(action, resource, message)
end
- ## TODO: deprecation warning. this way we can queue them up and present
- # them all at once.
+ # Emit a message about something being deprecated.
+ def deprecation(message, location=caller(2..2)[0])
+ end
# An uncategorized message. This supports the case that a user needs to
# pass output that doesn't fit into one of the callbacks above. Note that
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
index 370f8c51b4..966a3f32ec 100644
--- a/lib/chef/event_dispatch/dispatcher.rb
+++ b/lib/chef/event_dispatch/dispatcher.rb
@@ -25,18 +25,30 @@ class Chef
# define the forwarding in one go:
#
- # Define a method that will be forwarded to all
- def self.def_forwarding_method(method_name)
- define_method(method_name) do |*args|
- @subscribers.each { |s| s.send(method_name, *args) }
+ def call_subscribers(method_name, *args)
+ @subscribers.each do |s|
+ # Skip new/unsupported event names.
+ next if !s.respond_to?(method_name)
+ mth = s.method(method_name)
+ # Trim arguments to match what the subscriber expects to allow
+ # adding new arguments without breaking compat.
+ args = args.take(mth.arity) if mth.arity < args.size && mth.arity >= 0
+ mth.call(*args)
end
end
(Base.instance_methods - Object.instance_methods).each do |method_name|
- def_forwarding_method(method_name)
+ class_eval <<-EOM
+ def #{method_name}(*args)
+ call_subscribers(#{method_name.inspect}, *args)
+ end
+ EOM
end
+ # Special case deprecation, since it needs to know its caller
+ def deprecation(message, location=caller(2..2)[0])
+ call_subscribers(:deprecation, message, location)
+ end
end
end
end
-
diff --git a/lib/chef/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb
new file mode 100644
index 0000000000..c6f21c9b45
--- /dev/null
+++ b/lib/chef/event_dispatch/dsl.rb
@@ -0,0 +1,64 @@
+#
+# Author:: Ranjib Dey (<ranjib@linux.com>)
+# Copyright:: Copyright (c) 2015 Ranjib Dey
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'chef/event_dispatch/base'
+require 'chef/exceptions'
+require 'chef/config'
+
+class Chef
+ module EventDispatch
+ class DSL
+ attr_reader :handler
+
+ def initialize(name)
+ klass = Class.new(Chef::EventDispatch::Base) do
+ attr_reader :name
+ end
+ @handler = klass.new
+ @handler.instance_variable_set(:@name, name)
+
+ # Use event.register API to add anonymous handler if Chef.run_context
+ # and associated event dispatcher is set, else fallback to
+ # Chef::Config[:hanlder]
+ if Chef.run_context && Chef.run_context.events
+ Chef::Log.debug("Registering handler '#{name}' using events api")
+ Chef.run_context.events.register(handler)
+ else
+ Chef::Log.debug("Registering handler '#{name}' using global config")
+ Chef::Config[:event_handlers] << handler
+ end
+ end
+
+ # Adds a new event handler derived from base handler
+ # with user defined block against a chef event
+ #
+ # @return [Chef::EventDispatch::Base] a base handler object
+ def on(event_type, &block)
+ validate!(event_type)
+ handler.define_singleton_method(event_type) do |*args|
+ instance_exec(*args, &block)
+ end
+ end
+
+ private
+ def validate!(event_type)
+ all_event_types = (Chef::EventDispatch::Base.instance_methods - Object.instance_methods)
+ raise Chef::Exceptions::InvalidEventType, "Invalid event type: #{event_type}" unless all_event_types.include?(event_type)
+ end
+ end
+ end
+end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index dd0bac3cf9..e3649c068b 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -17,12 +17,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'chef-config/exceptions'
+
class Chef
# == Chef::Exceptions
# Chef's custom exceptions are all contained within the Chef::Exceptions
# namespace.
class Exceptions
+ ConfigurationError = ChefConfig::ConfigurationError
+
# Backcompat with Chef::ShellOut code:
require 'mixlib/shellout/exceptions'
@@ -68,7 +72,6 @@ class Chef
class DuplicateRole < RuntimeError; end
class ValidationFailed < ArgumentError; end
class InvalidPrivateKey < ArgumentError; end
- class ConfigurationError < ArgumentError; end
class MissingKeyAttribute < ArgumentError; end
class KeyCommandInputError < ArgumentError; end
class InvalidKeyArgument < ArgumentError; end
@@ -97,7 +100,14 @@ class Chef
class ConflictingMembersInGroup < ArgumentError; end
class InvalidResourceReference < RuntimeError; end
class ResourceNotFound < RuntimeError; end
+ class ProviderNotFound < RuntimeError; end
+ NoProviderAvailable = ProviderNotFound
class VerificationNotFound < RuntimeError; end
+ class InvalidEventType < ArgumentError; end
+ class MultipleIdentityError < RuntimeError; end
+ # Used in Resource::ActionProvider#load_current_resource to denote that
+ # the resource doesn't actually exist (for example, the file does not exist)
+ class CurrentValueDoesNotExist < RuntimeError; end
# Can't find a Resource of this type that is valid on this platform.
class NoSuchResourceType < NameError
@@ -119,6 +129,23 @@ class Chef
class EnclosingDirectoryDoesNotExist < ArgumentError; end
# Errors originating from calls to the Win32 API
class Win32APIError < RuntimeError; end
+
+ class Win32NetAPIError < Win32APIError
+ attr_reader :msg, :error_code
+ def initialize(msg, error_code)
+ @msg = msg
+ @error_code = error_code
+
+ formatted_message = ""
+ formatted_message << "---- Begin Win32 API output ----\n"
+ formatted_message << "Net Api Error Code: #{error_code}\n"
+ formatted_message << "Net Api Error Message: #{msg}\n"
+ formatted_message << "---- End Win32 API output ----\n"
+
+ super(formatted_message)
+ end
+ end
+
# Thrown when Win32 API layer binds to non-existent Win32 function. Occurs
# when older versions of Windows don't support newer Win32 API functions.
class Win32APIFunctionNotImplemented < NotImplementedError; end
@@ -218,8 +245,6 @@ class Chef
class ChildConvergeError < RuntimeError; end
- class NoProviderAvailable < RuntimeError; end
-
class DeprecatedFeatureError < RuntimeError;
def initalize(message)
super("#{message} (raising error due to treat_deprecation_warnings_as_errors being set)")
diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb
index c901068aa0..d3756ef00c 100644
--- a/lib/chef/formatters/base.rb
+++ b/lib/chef/formatters/base.rb
@@ -212,6 +212,9 @@ class Chef
file_load_failed(path, exception)
end
+ def deprecation(message, location=caller(2..2)[0])
+ Chef::Log.deprecation("#{message} at #{location}")
+ end
end
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index e76a940c38..614cc44e6d 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -22,6 +22,7 @@ class Chef
@failed_audits = 0
@start_time = Time.now
@end_time = @start_time
+ @skipped_resources = 0
end
def elapsed_time
@@ -33,7 +34,7 @@ class Chef
end
def total_resources
- @up_to_date_resources + @updated_resources
+ @up_to_date_resources + @updated_resources + @skipped_resources
end
def total_audits
@@ -42,6 +43,26 @@ class Chef
def run_completed(node)
@end_time = Time.now
+ # Print out deprecations.
+ if !deprecations.empty?
+ puts_line ""
+ puts_line "Deprecated features used!"
+ deprecations.each do |message, locations|
+ if locations.size == 1
+ puts_line " #{message} at #{locations.size} location:"
+ else
+ puts_line " #{message} at #{locations.size} locations:"
+ end
+ locations.each do |location|
+ prefix = " - "
+ Array(location).each do |line|
+ puts_line "#{prefix}#{line}"
+ prefix = " "
+ end
+ end
+ end
+ puts_line ""
+ end
if Chef::Config[:why_run]
puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated"
else
@@ -132,9 +153,9 @@ class Chef
indent
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
- puts_line "- #{cookbook_name}"
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
+ puts_line "- #{cookbook.name} (#{cookbook.version})"
end
# Called when an individual file in a cookbook has been updated
@@ -236,6 +257,7 @@ class Chef
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
+ @skipped_resources += 1
# TODO: more info about conditional
puts " (skipped due to #{conditional.short_description})", :stream => resource
unindent
@@ -334,6 +356,16 @@ class Chef
end
end
+ def deprecation(message, location=caller(2..2)[0])
+ if Chef::Config[:treat_deprecation_warnings_as_errors]
+ super
+ end
+
+ # Save deprecations to the screen until the end
+ deprecations[message] ||= Set.new
+ deprecations[message] << location
+ end
+
def indent
indent_by(2)
end
@@ -341,6 +373,12 @@ class Chef
def unindent
indent_by(-2)
end
+
+ protected
+
+ def deprecations
+ @deprecations ||= {}
+ end
end
end
end
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
index d64d5e7b01..fe418ed485 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -44,6 +44,18 @@ class Chef
error_description.section("Cookbook Trace:", traceback)
error_description.section("Relevant File Content:", context)
end
+
+ if exception_message_modifying_frozen?
+ msg = <<-MESSAGE
+ Chef calls the freeze method on certain ruby objects to prevent
+ pollution across multiple instances. Specifically, resource
+ properties have frozen default values to avoid modifying the
+ property for all instances of a resource. Try modifying the
+ particular instance variable or using an instance accessor instead.
+ MESSAGE
+
+ error_description.section("Additional information:", msg.gsub(/^ {6}/, ''))
+ end
end
def context
@@ -111,6 +123,10 @@ class Chef
end
end
+ def exception_message_modifying_frozen?
+ exception.message.include?("can't modify frozen")
+ end
+
end
end
diff --git a/lib/chef/formatters/minimal.rb b/lib/chef/formatters/minimal.rb
index a189cc67eb..3862951f76 100644
--- a/lib/chef/formatters/minimal.rb
+++ b/lib/chef/formatters/minimal.rb
@@ -109,8 +109,8 @@ class Chef
puts "Synchronizing cookbooks"
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
print "."
end
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index d4b386a15a..8cff3bc032 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -68,7 +68,10 @@ class Chef
run_action = action || @resource.action
begin
- @resource.run_action(run_action)
+ # Coerce to an array to be safe. This could happen with a legacy
+ # resource or something overriding the default_action code in a
+ # subclass.
+ Array(run_action).each {|action_to_run| @resource.run_action(action_to_run) }
resource_updated = @resource.updated
rescue Mixlib::ShellOut::ShellCommandFailed
resource_updated = nil
diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb
index 7582f4458f..1baf5724ae 100644
--- a/lib/chef/http/http_request.rb
+++ b/lib/chef/http/http_request.rb
@@ -40,7 +40,7 @@ class Chef
engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
- UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +http://opscode.com)"
+ UA_COMMON = "/#{::Chef::VERSION} (#{engine}-#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; ohai-#{Ohai::VERSION}; #{RUBY_PLATFORM}; +https://chef.io)"
DEFAULT_UA = "Chef Client" << UA_COMMON
USER_AGENT = "User-Agent".freeze
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index 4a93697a1b..46e968827e 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -87,6 +87,7 @@ class Chef
def self.inherited(subclass)
unless subclass.unnamed?
subcommands[subclass.snake_case_name] = subclass
+ subcommand_files[subclass.snake_case_name] += [caller[0].split(/:\d+/).first]
end
end
@@ -121,17 +122,29 @@ class Chef
end
def self.subcommand_loader
- @subcommand_loader ||= Knife::SubcommandLoader.new(chef_config_dir)
+ @subcommand_loader ||= Chef::Knife::SubcommandLoader.for_config(chef_config_dir)
end
def self.load_commands
@commands_loaded ||= subcommand_loader.load_commands
end
+ def self.guess_category(args)
+ subcommand_loader.guess_category(args)
+ end
+
+ def self.subcommand_class_from(args)
+ subcommand_loader.command_class_from(args) || subcommand_not_found!(args)
+ end
+
def self.subcommands
@@subcommands ||= {}
end
+ def self.subcommand_files
+ @@subcommand_files ||= Hash.new([])
+ end
+
def self.subcommands_by_category
unless @subcommands_by_category
@subcommands_by_category = Hash.new { |hash, key| hash[key] = [] }
@@ -142,30 +155,6 @@ class Chef
@subcommands_by_category
end
- # Print the list of subcommands knife knows about. If +preferred_category+
- # is given, only subcommands in that category are shown
- def self.list_commands(preferred_category=nil)
- load_commands
-
- category_desc = preferred_category ? preferred_category + " " : ''
- msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
-
- if preferred_category && subcommands_by_category.key?(preferred_category)
- commands_to_show = {preferred_category => subcommands_by_category[preferred_category]}
- else
- commands_to_show = subcommands_by_category
- end
-
- commands_to_show.sort.each do |category, commands|
- next if category =~ /deprecated/i
- msg "** #{category.upcase} COMMANDS **"
- commands.sort.each do |command|
- msg subcommands[command].banner if subcommands[command]
- end
- msg
- end
- end
-
# Shared with subclasses
@@chef_config_dir = nil
@@ -206,7 +195,6 @@ class Chef
Chef::Log.level(:debug)
end
- load_commands
subcommand_class = subcommand_class_from(args)
subcommand_class.options = options.merge!(subcommand_class.options)
subcommand_class.load_deps
@@ -215,34 +203,6 @@ class Chef
instance.run_with_pretty_exceptions
end
- def self.guess_category(args)
- category_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
- category_words.map! {|w| w.split('-')}.flatten!
- matching_category = nil
- while (!matching_category) && (!category_words.empty?)
- candidate_category = category_words.join(' ')
- matching_category = candidate_category if subcommands_by_category.key?(candidate_category)
- matching_category || category_words.pop
- end
- matching_category
- end
-
- def self.subcommand_class_from(args)
- command_words = args.select {|arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
-
- subcommand_class = nil
-
- while ( !subcommand_class ) && ( !command_words.empty? )
- snake_case_class_name = command_words.join("_")
- unless subcommand_class = subcommands[snake_case_class_name]
- command_words.pop
- end
- end
- # see if we got the command as e.g., knife node-list
- subcommand_class ||= subcommands[args.first.gsub('-', '_')]
- subcommand_class || subcommand_not_found!(args)
- end
-
def self.dependency_loaders
@dependency_loaders ||= []
end
@@ -265,7 +225,13 @@ class Chef
# Error out and print usage. probably because the arguments given by the
# user could not be resolved to a subcommand.
def self.subcommand_not_found!(args)
- ui.fatal("Cannot find sub command for: '#{args.join(' ')}'")
+ ui.fatal("Cannot find subcommand for: '#{args.join(' ')}'")
+
+ # Mention rehash when the subcommands cache(plugin_manifest.json) is used
+ if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader) ||
+ subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::CustomManifestLoader)
+ ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.")
+ end
if category_commands = guess_category(args)
list_commands(category_commands)
@@ -280,6 +246,20 @@ class Chef
exit 10
end
+ def self.list_commands(preferred_category=nil)
+ category_desc = preferred_category ? preferred_category + " " : ''
+ msg "Available #{category_desc}subcommands: (for details, knife SUB-COMMAND --help)\n\n"
+ subcommand_loader.list_commands(preferred_category).sort.each do |category, commands|
+ next if category =~ /deprecated/i
+ msg "** #{category.upcase} COMMANDS **"
+ commands.sort.each do |command|
+ subcommand_loader.load_command(command)
+ msg subcommands[command].banner if subcommands[command]
+ end
+ msg
+ end
+ end
+
def self.reset_config_path!
@@chef_config_dir = nil
end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index 5b29591fcc..f173b6b909 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -143,6 +143,12 @@ class Chef
:proc => lambda { |o| o.split(/[\s,]+/) },
:default => []
+ option :tags,
+ :long => "--tags TAGS",
+ :description => "Comma separated list of tags to apply to the node",
+ :proc => lambda { |o| o.split(/[\s,]+/) },
+ :default => []
+
option :first_boot_attributes,
:short => "-j JSON_ATTRIBS",
:long => "--json-attributes",
diff --git a/lib/chef/knife/bootstrap/chef_vault_handler.rb b/lib/chef/knife/bootstrap/chef_vault_handler.rb
index 749f61e6da..f658957499 100644
--- a/lib/chef/knife/bootstrap/chef_vault_handler.rb
+++ b/lib/chef/knife/bootstrap/chef_vault_handler.rb
@@ -15,6 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
+require 'chef/knife/bootstrap'
class Chef
class Knife
diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb
index b9c1d98bec..59b0cabd49 100644
--- a/lib/chef/knife/bootstrap/client_builder.rb
+++ b/lib/chef/knife/bootstrap/client_builder.rb
@@ -20,6 +20,7 @@ require 'chef/node'
require 'chef/rest'
require 'chef/api_client/registration'
require 'chef/api_client'
+require 'chef/knife/bootstrap'
require 'tmpdir'
class Chef
@@ -140,6 +141,9 @@ class Chef
node.run_list(normalized_run_list)
node.normal_attrs = first_boot_attributes if first_boot_attributes
node.environment(environment) if environment
+ (knife_config[:tags] || []).each do |tag|
+ node.tags << tag
+ end
node
end
end
diff --git a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb
deleted file mode 100644
index 55d2c0cc12..0000000000
--- a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb
+++ /dev/null
@@ -1,76 +0,0 @@
-bash -c '
-<%= "export http_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-
-if [ ! -f /usr/bin/chef-client ]; then
- pacman -Syy
- pacman -S --noconfirm ruby ntp base-devel
- ntpdate -u pool.ntp.org
- gem install ohai --no-user-install --no-document --verbose
- gem install chef --no-user-install --no-document --verbose <%= Chef::VERSION %>
-fi
-
-mkdir -p /etc/chef
-
-<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-<% end -%>
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<% unless trusted_certs.empty? -%>
-mkdir -p /etc/chef/trusted_certs
-<%= trusted_certs %>
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= Chef::JSONCompat.to_json(hash) %>
-EOP
-<% end -%>
-<% end -%>
-
-<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
-<%= ::File.read(::File.expand_path(client_pem)) %>
-EOP
-chmod 0600 /etc/chef/client.pem
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-log_level :info
-log_location STDOUT
-chef_server_url "<%= @chef_config[:chef_server_url] %>"
-validation_client_name "<%= @chef_config[:validation_client_name] %>"
-<% if @config[:chef_node_name] -%>
-node_name "<%= @config[:chef_node_name] %>"
-<% else -%>
-# Using default node name (fqdn)
-<% end -%>
-# ArchLinux follows the Filesystem Hierarchy Standard
-file_cache_path "/var/cache/chef"
-file_backup_path "/var/lib/chef/backup"
-pid_file "/var/run/chef/client.pid"
-cache_options({ :path => "/var/cache/chef/checksums", :skip_expires => true})
-<% if knife_config[:bootstrap_proxy] %>
-http_proxy "<%= knife_config[:bootstrap_proxy] %>"
-https_proxy "<%= knife_config[:bootstrap_proxy] %>"
-<% end -%>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= Chef::JSONCompat.to_json(first_boot) %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/templates/chef-aix.erb b/lib/chef/knife/bootstrap/templates/chef-aix.erb
deleted file mode 100644
index 45fbba7b48..0000000000
--- a/lib/chef/knife/bootstrap/templates/chef-aix.erb
+++ /dev/null
@@ -1,72 +0,0 @@
-ksh -c '
-
-function exists {
- if type $1 >/dev/null 2>&1
- then
- return 0
- else
- return 1
- fi
-}
-
-if ! exists /usr/bin/chef-client; then
- <% if @chef_config[:aix_package] -%>
- # Read the download URL/location from knife.rb with option aix_package
- rm -rf /tmp/chef_installer # ensure there no older pkg
- echo "<%= @chef_config[:aix_package] %>"
- perl -e '\''use LWP::Simple; getprint($ARGV[0]);'\'' <%= @chef_config[:aix_package] %> > /tmp/chef_installer
- installp -aYF -d /tmp/chef_installer chef
- <% else -%>
- echo ":aix_package location is not set in knife.rb"
- exit
- <% end -%>
-fi
-
-mkdir -p /etc/chef
-
-<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
-<%= ::File.read(::File.expand_path(client_pem)) %>
-EOP
-chmod 0600 /etc/chef/client.pem
-<% end -%>
-
-<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
-<%= validation_key %>
-EOP
-chmod 0600 /etc/chef/validation.pem
-<% end -%>
-
-<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
-<%= encrypted_data_bag_secret %>
-EOP
-chmod 0600 /etc/chef/encrypted_data_bag_secret
-<% end -%>
-
-<% unless trusted_certs.empty? -%>
-mkdir -p /etc/chef/trusted_certs
-<%= trusted_certs %>
-<% end -%>
-
-<%# Generate Ohai Hints -%>
-<% unless @chef_config[:knife][:hints].nil? || @chef_config[:knife][:hints].empty? -%>
-mkdir -p /etc/chef/ohai/hints
-
-<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
-<%= Chef::JSONCompat.to_json(hash) %>
-EOP
-<% end -%>
-<% end -%>
-
-cat > /etc/chef/client.rb <<'EOP'
-<%= config_content %>
-EOP
-
-cat > /etc/chef/first-boot.json <<'EOP'
-<%= Chef::JSONCompat.to_json(first_boot) %>
-EOP
-
-<%= start_chef %>'
diff --git a/lib/chef/knife/bootstrap/templates/chef-full.erb b/lib/chef/knife/bootstrap/templates/chef-full.erb
index 335b1f181c..575aec0f50 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -12,7 +12,7 @@ tmp_dir="$tmp/install.sh.$$"
(umask 077 && mkdir $tmp_dir) || exit 1
exists() {
- if command -v $1 &>/dev/null
+ if command -v $1 >/dev/null 2>&1
then
return 0
else
@@ -166,12 +166,12 @@ do_download() {
<%= knife_config[:bootstrap_install_command] %>
<% else %>
install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>"
- if ! exists /usr/bin/chef-client; then
+ if test -f /usr/bin/chef-client; then
+ echo "-----> Existing Chef installation detected"
+ else
echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)"
do_download ${install_sh} $tmp_dir/install.sh
sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %>
- else
- echo "-----> Existing Chef installation detected"
fi
<% end %>
@@ -226,6 +226,6 @@ cat > /etc/chef/first-boot.json <<EOP
<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
-echo "Starting first Chef Client run..."
+echo "Starting the first Chef Client run..."
<%= start_chef %>'
diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb
index f2be772759..b439e6f995 100644
--- a/lib/chef/knife/client_bulk_delete.rb
+++ b/lib/chef/knife/client_bulk_delete.rb
@@ -23,7 +23,7 @@ class Chef
class ClientBulkDelete < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -39,7 +39,7 @@ class Chef
ui.fatal("You must supply a regular expression to match the results against")
exit 42
end
- all_clients = Chef::ApiClient.list(true)
+ all_clients = Chef::ApiClientV1.list(true)
matcher = /#{name_args[0]}/
clients_to_delete = {}
diff --git a/lib/chef/knife/client_create.rb b/lib/chef/knife/client_create.rb
index 570c1ee950..fa9a1a7e32 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -23,7 +23,7 @@ class Chef
class ClientCreate < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -57,12 +57,12 @@ class Chef
banner "knife client create CLIENTNAME (options)"
def client
- @client_field ||= Chef::ApiClient.new
+ @client_field ||= Chef::ApiClientV1.new
end
def create_client(client)
# should not be using save :( bad behavior
- client.save
+ Chef::ApiClientV1.from_hash(client).save
end
def run
@@ -93,7 +93,7 @@ class Chef
output = edit_data(client)
final_client = create_client(output)
- ui.info("Created #{output}")
+ ui.info("Created #{final_client}")
# output private_key if one
if final_client.private_key
diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb
index d7d302ee1d..a49c0867a8 100644
--- a/lib/chef/knife/client_delete.rb
+++ b/lib/chef/knife/client_delete.rb
@@ -23,7 +23,7 @@ class Chef
class ClientDelete < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -43,8 +43,8 @@ class Chef
exit 1
end
- delete_object(Chef::ApiClient, @client_name, 'client') {
- object = Chef::ApiClient.load(@client_name)
+ delete_object(Chef::ApiClientV1, @client_name, 'client') {
+ object = Chef::ApiClientV1.load(@client_name)
if object.validator
unless config[:delete_validators]
ui.fatal("You must specify --delete-validators to delete the validator client #{@client_name}")
diff --git a/lib/chef/knife/client_edit.rb b/lib/chef/knife/client_edit.rb
index c81bce902a..5dcd8f212b 100644
--- a/lib/chef/knife/client_edit.rb
+++ b/lib/chef/knife/client_edit.rb
@@ -23,7 +23,7 @@ class Chef
class ClientEdit < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -38,7 +38,15 @@ class Chef
exit 1
end
- edit_object(Chef::ApiClient, @client_name)
+ original_data = Chef::ApiClientV1.load(@client_name).to_hash
+ edited_client = edit_data(original_data)
+ if original_data != edited_client
+ client = Chef::ApiClientV1.from_hash(edited_client)
+ client.save
+ ui.msg("Saved #{client}.")
+ else
+ ui.msg("Client unchanged, not saving.")
+ end
end
end
end
diff --git a/lib/chef/knife/client_list.rb b/lib/chef/knife/client_list.rb
index da0bf12dc3..d8a3698b6a 100644
--- a/lib/chef/knife/client_list.rb
+++ b/lib/chef/knife/client_list.rb
@@ -23,7 +23,7 @@ class Chef
class ClientList < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -35,7 +35,7 @@ class Chef
:description => "Show corresponding URIs"
def run
- output(format_list_for_display(Chef::ApiClient.list))
+ output(format_list_for_display(Chef::ApiClientV1.list))
end
end
end
diff --git a/lib/chef/knife/client_reregister.rb b/lib/chef/knife/client_reregister.rb
index 666fd09fd2..b94761e718 100644
--- a/lib/chef/knife/client_reregister.rb
+++ b/lib/chef/knife/client_reregister.rb
@@ -23,7 +23,7 @@ class Chef
class ClientReregister < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -43,7 +43,7 @@ class Chef
exit 1
end
- client = Chef::ApiClient.reregister(@client_name)
+ client = Chef::ApiClientV1.reregister(@client_name)
Chef::Log.debug("Updated client data: #{client.inspect}")
key = client.private_key
if config[:file]
diff --git a/lib/chef/knife/client_show.rb b/lib/chef/knife/client_show.rb
index 822848fdc2..bdac3f9758 100644
--- a/lib/chef/knife/client_show.rb
+++ b/lib/chef/knife/client_show.rb
@@ -25,7 +25,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
@@ -40,7 +40,7 @@ class Chef
exit 1
end
- client = Chef::ApiClient.load(@client_name)
+ client = Chef::ApiClientV1.load(@client_name)
output(format_for_display(client))
end
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 7197653489..867b6fe366 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -163,11 +163,14 @@ CONFIG
end
def first_boot
- (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list)
+ (@config[:first_boot_attributes] || {}).tap do |attributes|
+ attributes.merge!(:run_list => @run_list)
+ attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty?
+ end
end
private
-
+
# Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
# This string should contain both the commands necessary to both create the files, as well as their content
def trusted_certs_content
diff --git a/lib/chef/knife/core/custom_manifest_loader.rb b/lib/chef/knife/core/custom_manifest_loader.rb
new file mode 100644
index 0000000000..c19e749f32
--- /dev/null
+++ b/lib/chef/knife/core/custom_manifest_loader.rb
@@ -0,0 +1,69 @@
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/version'
+class Chef
+ class Knife
+ class SubcommandLoader
+
+ #
+ # Load a subcommand from a user-supplied
+ # manifest file
+ #
+ class CustomManifestLoader < Chef::Knife::SubcommandLoader
+ attr_accessor :manifest
+ def initialize(chef_config_dir, plugin_manifest)
+ super(chef_config_dir)
+ @manifest = plugin_manifest
+ end
+
+ # If the user has created a ~/.chef/plugin_manifest.json file, we'll use
+ # that instead of inspecting the on-system gems to find the plugins. The
+ # file format is expected to look like:
+ #
+ # { "plugins": {
+ # "knife-ec2": {
+ # "paths": [
+ # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
+ # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
+ # ]
+ # }
+ # }
+ # }
+ #
+ # Extraneous content in this file is ignored. This is intentional so that we
+ # can adapt the file format for potential behavior changes to knife in
+ # the future.
+ def find_subcommands_via_manifest
+ # Format of subcommand_files is "relative_path" (something you can
+ # Kernel.require()) => full_path. The relative path isn't used
+ # currently, so we just map full_path => full_path.
+ subcommand_files = {}
+ manifest["plugins"].each do |plugin_name, plugin_manifest|
+ plugin_manifest["paths"].each do |cmd_path|
+ subcommand_files[cmd_path] = cmd_path
+ end
+ end
+ subcommand_files.merge(find_subcommands_via_dirglob)
+ end
+
+ def subcommand_files
+ subcommand_files ||= (find_subcommands_via_manifest.values + site_subcommands).flatten.uniq
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/gem_glob_loader.rb b/lib/chef/knife/core/gem_glob_loader.rb
new file mode 100644
index 0000000000..d09131aacb
--- /dev/null
+++ b/lib/chef/knife/core/gem_glob_loader.rb
@@ -0,0 +1,138 @@
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2009-2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/version'
+require 'chef/util/path_helper'
+class Chef
+ class Knife
+ class SubcommandLoader
+ class GemGlobLoader < Chef::Knife::SubcommandLoader
+ MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
+ MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
+
+ def subcommand_files
+ @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+ end
+
+ # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
+ # If rubygems is not installed, falls back to globbing the knife directory.
+ # The Hash is of the form {"relative/path" => "/absolute/path"}
+ #--
+ # Note: the "right" way to load the plugins is to require the relative path, i.e.,
+ # require 'chef/knife/command'
+ # but we're getting frustrated by bugs at every turn, and it's slow besides. So
+ # subcommand loader has been modified to load the plugins by using Kernel.load
+ # with the absolute path.
+ def gem_and_builtin_subcommands
+ require 'rubygems'
+ find_subcommands_via_rubygems
+ rescue LoadError
+ find_subcommands_via_dirglob
+ end
+
+ def find_subcommands_via_dirglob
+ # The "require paths" of the core knife subcommands bundled with chef
+ files = Dir[File.join(Chef::Util::PathHelper.escape_glob(File.expand_path('../../../knife', __FILE__)), '*.rb')]
+ subcommand_files = {}
+ files.each do |knife_file|
+ rel_path = knife_file[/#{CHEF_ROOT}#{Regexp.escape(File::SEPARATOR)}(.*)\.rb/,1]
+ subcommand_files[rel_path] = knife_file
+ end
+ subcommand_files
+ end
+
+ def find_subcommands_via_rubygems
+ files = find_files_latest_gems 'chef/knife/*.rb'
+ subcommand_files = {}
+ files.each do |file|
+ rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
+
+ # When not installed as a gem (ChefDK/appbundler in particular), AND
+ # a different version of Chef is installed via gems, `files` will
+ # include some files from the 'other' Chef install. If this contains
+ # a knife command that doesn't exist in this version of Chef, we will
+ # get a LoadError later when we try to require it.
+ next if from_different_chef_version?(file)
+
+ subcommand_files[rel_path] = file
+ end
+
+ subcommand_files.merge(find_subcommands_via_dirglob)
+ end
+
+ private
+
+ def find_files_latest_gems(glob, check_load_path=true)
+ files = []
+
+ if check_load_path
+ files = $LOAD_PATH.map { |load_path|
+ Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob(load_path)}#{Gem.suffix_pattern}"]
+ }.flatten.select { |file| File.file? file.untaint }
+ end
+
+ gem_files = latest_gem_specs.map do |spec|
+ # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
+ if spec.respond_to? :matches_for_glob
+ spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+ else
+ check_spec_for_glob(spec, glob)
+ end
+ end.flatten
+
+ files.concat gem_files
+ files.uniq! if check_load_path
+
+ return files
+ end
+
+ def latest_gem_specs
+ @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
+ Gem::Specification.latest_specs(true) # find prerelease gems
+ else
+ Gem.source_index.latest_specs(true)
+ end
+ end
+
+ def check_spec_for_glob(spec, glob)
+ dirs = if spec.require_paths.size > 1 then
+ "{#{spec.require_paths.join(',')}}"
+ else
+ spec.require_paths.first
+ end
+
+ glob = File.join(Chef::Util::PathHelper.escape_glob(spec.full_gem_path, dirs), glob)
+
+ Dir[glob].map { |f| f.untaint }
+ end
+
+ def from_different_chef_version?(path)
+ matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
+ end
+
+ def matches_any_chef_gem?(path)
+ path =~ MATCHES_CHEF_GEM
+ end
+
+ def matches_this_chef_gem?(path)
+ path =~ MATCHES_THIS_CHEF_GEM
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/hashed_command_loader.rb b/lib/chef/knife/core/hashed_command_loader.rb
new file mode 100644
index 0000000000..6eb3635726
--- /dev/null
+++ b/lib/chef/knife/core/hashed_command_loader.rb
@@ -0,0 +1,80 @@
+# Author:: Steven Danna (<steve@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/version'
+class Chef
+ class Knife
+ class SubcommandLoader
+ #
+ # Load a subcommand from a pre-computed path
+ # for the given command.
+ #
+ class HashedCommandLoader < Chef::Knife::SubcommandLoader
+ KEY = '_autogenerated_command_paths'
+
+ attr_accessor :manifest
+ def initialize(chef_config_dir, plugin_manifest)
+ super(chef_config_dir)
+ @manifest = plugin_manifest
+ end
+
+ def guess_category(args)
+ category_words = positional_arguments(args)
+ category_words.map! { |w| w.split('-') }.flatten!
+ find_longest_key(manifest[KEY]["plugins_by_category"], category_words, ' ')
+ end
+
+ def list_commands(pref_category=nil)
+ if pref_category || manifest[KEY]["plugins_by_category"].key?(pref_category)
+ { pref_category => manifest[KEY]["plugins_by_category"][pref_category] }
+ else
+ manifest[KEY]["plugins_by_category"]
+ end
+ end
+
+ def subcommand_files
+ manifest[KEY]["plugins_paths"].values.flatten
+ end
+
+ def load_command(args)
+ paths = manifest[KEY]["plugins_paths"][subcommand_for_args(args)]
+ if paths.nil? || paths.empty? || (! paths.is_a? Array)
+ false
+ else
+ paths.each do |sc|
+ if File.exists?(sc)
+ Kernel.load sc
+ else
+ Chef::Log.error "The file #{sc} is missing for subcommand '#{subcommand_for_args(args)}'. Please rehash to update the subcommands cache."
+ return false
+ end
+ end
+ true
+ end
+ end
+
+ def subcommand_for_args(args)
+ if manifest[KEY]["plugins_paths"].key?(args)
+ args
+ else
+ find_longest_key(manifest[KEY]["plugins_paths"], args, "_")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/core/object_loader.rb b/lib/chef/knife/core/object_loader.rb
index 698b09ac84..97ca381471 100644
--- a/lib/chef/knife/core/object_loader.rb
+++ b/lib/chef/knife/core/object_loader.rb
@@ -18,6 +18,7 @@
require 'ffi_yajl'
require 'chef/util/path_helper'
+require 'chef/data_bag_item'
class Chef
class Knife
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index a8705c724f..808e053c40 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -18,105 +18,120 @@
require 'chef/version'
require 'chef/util/path_helper'
+require 'chef/knife/core/gem_glob_loader'
+require 'chef/knife/core/hashed_command_loader'
+require 'chef/knife/core/custom_manifest_loader'
+
class Chef
class Knife
+ #
+ # Public Methods of a Subcommand Loader
+ #
+ # load_commands - loads all available subcommands
+ # load_command(args) - loads subcommands for the given args
+ # list_commands(args) - lists all available subcommands,
+ # optionally filtering by category
+ # subcommand_files - returns an array of all subcommand files
+ # that could be loaded
+ # commnad_class_from(args) - returns the subcommand class for the
+ # user-requested command
+ #
class SubcommandLoader
-
- MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
- MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}(-\w+)?(-\w+)?/}.freeze
-
attr_reader :chef_config_dir
attr_reader :env
- def initialize(chef_config_dir, env=nil)
+ # A small factory method. Eventually, this is the only place
+ # where SubcommandLoader should know about its subclasses, but
+ # to maintain backwards compatibility many of the instance
+ # methods in this base class contain default implementations
+ # of the functions sub classes should otherwise provide
+ # or directly instantiate the appropriate subclass
+ def self.for_config(chef_config_dir)
+ if autogenerated_manifest?
+ Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}")
+ Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest)
+ elsif custom_manifest?
+ Chef.log_deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.")
+ Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest)
+ else
+ Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
+ end
+ end
+
+ def self.plugin_manifest?
+ plugin_manifest_path && File.exist?(plugin_manifest_path)
+ end
+
+ def self.autogenerated_manifest?
+ plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY)
+ end
+
+ def self.custom_manifest?
+ plugin_manifest? && plugin_manifest.key?("plugins")
+ end
+
+ def self.plugin_manifest
+ Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
+ end
+
+ def self.plugin_manifest_path
+ Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json')
+ end
+
+ def initialize(chef_config_dir, env = nil)
@chef_config_dir = chef_config_dir
- @forced_activate = {}
# Deprecated and un-used instance variable.
@env = env
unless env.nil?
- Chef::Log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
+ Chef.log_deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
end
end
# Load all the sub-commands
def load_commands
+ return true if @loaded
subcommand_files.each { |subcommand| Kernel.load subcommand }
- true
+ @loaded = true
end
- # Returns an Array of paths to knife commands located in chef_config_dir/plugins/knife/
- # and ~/.chef/plugins/knife/
- def site_subcommands
- user_specific_files = []
-
- if chef_config_dir
- user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
- end
-
- # finally search ~/.chef/plugins/knife/*.rb
- Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p|
- user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb'))
- end
+ def force_load
+ @loaded=false
+ load_commands
+ end
- user_specific_files
+ def load_command(_command_args)
+ load_commands
end
- # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
- # If rubygems is not installed, falls back to globbing the knife directory.
- # The Hash is of the form {"relative/path" => "/absolute/path"}
- #--
- # Note: the "right" way to load the plugins is to require the relative path, i.e.,
- # require 'chef/knife/command'
- # but we're getting frustrated by bugs at every turn, and it's slow besides. So
- # subcommand loader has been modified to load the plugins by using Kernel.load
- # with the absolute path.
- def gem_and_builtin_subcommands
- if have_plugin_manifest?
- find_subcommands_via_manifest
+ def list_commands(pref_cat = nil)
+ load_commands
+ if pref_cat && Chef::Knife.subcommands_by_category.key?(pref_cat)
+ { pref_cat => Chef::Knife.subcommands_by_category[pref_cat] }
else
- # search all gems for chef/knife/*.rb
- require 'rubygems'
- find_subcommands_via_rubygems
+ Chef::Knife.subcommands_by_category
end
- rescue LoadError
- find_subcommands_via_dirglob
end
- def subcommand_files
- @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+ def command_class_from(args)
+ cmd_words = positional_arguments(args)
+ load_command(cmd_words)
+ result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands,
+ cmd_words, '_')]
+ result || Chef::Knife.subcommands[args.first.gsub('-', '_')]
end
- # If the user has created a ~/.chef/plugin_manifest.json file, we'll use
- # that instead of inspecting the on-system gems to find the plugins. The
- # file format is expected to look like:
- #
- # { "plugins": {
- # "knife-ec2": {
- # "paths": [
- # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
- # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
- # ]
- # }
- # }
- # }
- #
- # Extraneous content in this file is ignored. This intentional so that we
- # can adapt the file format for potential behavior changes to knife in
- # the future.
- def find_subcommands_via_manifest
- # Format of subcommand_files is "relative_path" (something you can
- # Kernel.require()) => full_path. The relative path isn't used
- # currently, so we just map full_path => full_path.
- subcommand_files = {}
- plugin_manifest["plugins"].each do |plugin_name, plugin_manifest|
- plugin_manifest["paths"].each do |cmd_path|
- subcommand_files[cmd_path] = cmd_path
- end
- end
- subcommand_files.merge(find_subcommands_via_dirglob)
+ def guess_category(args)
+ category_words = positional_arguments(args)
+ category_words.map! { |w| w.split('-') }.flatten!
+ find_longest_key(Chef::Knife.subcommands_by_category,
+ category_words, ' ')
end
+
+ #
+ # This is shared between the custom_manifest_loader and the gem_glob_loader
+ #
def find_subcommands_via_dirglob
# The "require paths" of the core knife subcommands bundled with chef
files = Dir[File.join(Chef::Util::PathHelper.escape_glob(File.expand_path('../../../knife', __FILE__)), '*.rb')]
@@ -128,95 +143,65 @@ class Chef
subcommand_files
end
- def find_subcommands_via_rubygems
- files = find_files_latest_gems 'chef/knife/*.rb'
- subcommand_files = {}
- files.each do |file|
- rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
-
- # When not installed as a gem (ChefDK/appbundler in particular), AND
- # a different version of Chef is installed via gems, `files` will
- # include some files from the 'other' Chef install. If this contains
- # a knife command that doesn't exist in this version of Chef, we will
- # get a LoadError later when we try to require it.
- next if from_different_chef_version?(file)
-
- subcommand_files[rel_path] = file
- end
-
- subcommand_files.merge(find_subcommands_via_dirglob)
- end
-
- def have_plugin_manifest?
- plugin_manifest_path && File.exist?(plugin_manifest_path)
- end
-
- def plugin_manifest
- Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
- end
-
- def plugin_manifest_path
- Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json')
+ #
+ # Subclassses should define this themselves. Eventually, this will raise a
+ # NotImplemented error, but for now, we mimic the behavior the user was likely
+ # to get in the past.
+ #
+ def subcommand_files
+ Chef.log_deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated.
+Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)"
+ @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest?
+ Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files
+ else
+ Chef::Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir, env).subcommand_files
+ end
end
- private
-
- def find_files_latest_gems(glob, check_load_path=true)
- files = []
-
- if check_load_path
- files = $LOAD_PATH.map { |load_path|
- Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob(load_path)}#{Gem.suffix_pattern}"]
- }.flatten.select { |file| File.file? file.untaint }
- end
-
- gem_files = latest_gem_specs.map do |spec|
- # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
- if spec.respond_to? :matches_for_glob
- spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+ #
+ # Utility function for finding an element in a hash given an array
+ # of words and a separator. We find the the longest key in the
+ # hash composed of the given words joined by the separator.
+ #
+ def find_longest_key(hash, words, sep = '_')
+ match = nil
+ until match || words.empty?
+ candidate = words.join(sep)
+ if hash.key?(candidate)
+ match = candidate
else
- check_spec_for_glob(spec, glob)
+ words.pop
end
- end.flatten
-
- files.concat gem_files
- files.uniq! if check_load_path
-
- return files
- end
-
- def latest_gem_specs
- @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
- Gem::Specification.latest_specs(true) # find prerelease gems
- else
- Gem.source_index.latest_specs(true)
end
+ match
end
- def check_spec_for_glob(spec, glob)
- dirs = if spec.require_paths.size > 1 then
- "{#{spec.require_paths.join(',')}}"
- else
- spec.require_paths.first
- end
-
- glob = File.join(Chef::Util::PathHelper.escape_glob(spec.full_gem_path, dirs), glob)
-
- Dir[glob].map { |f| f.untaint }
+ #
+ # The positional arguments from the argument list provided by the
+ # users. Used to search for subcommands and categories.
+ #
+ # @return [Array<String>]
+ #
+ def positional_arguments(args)
+ args.select { |arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
end
- def from_different_chef_version?(path)
- matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
- end
+ # Returns an Array of paths to knife commands located in
+ # chef_config_dir/plugins/knife/ and ~/.chef/plugins/knife/
+ def site_subcommands
+ user_specific_files = []
- def matches_any_chef_gem?(path)
- path =~ MATCHES_CHEF_GEM
- end
+ if chef_config_dir
+ user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
+ end
- def matches_this_chef_gem?(path)
- path =~ MATCHES_THIS_CHEF_GEM
- end
+ # finally search ~/.chef/plugins/knife/*.rb
+ Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p|
+ user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb'))
+ end
+ user_specific_files
+ end
end
end
end
diff --git a/lib/chef/knife/node_run_list_remove.rb b/lib/chef/knife/node_run_list_remove.rb
index 4b8953a264..ef03c176b8 100644
--- a/lib/chef/knife/node_run_list_remove.rb
+++ b/lib/chef/knife/node_run_list_remove.rb
@@ -42,7 +42,18 @@ class Chef
entries = @name_args[1].split(',').map { |e| e.strip }
end
- entries.each { |e| node.run_list.remove(e) }
+ # iterate over the list of things to remove,
+ # warning if one of them was not found
+ entries.each do |e|
+ if node.run_list.find { |rli| e == rli.to_s }
+ node.run_list.remove(e)
+ else
+ ui.warn "#{e} is not in the run list"
+ unless e =~ /^(recipe|role)\[/
+ ui.warn '(did you forget recipe[] or role[] around it?)'
+ end
+ end
+ end
node.save
diff --git a/lib/chef/knife/null.rb b/lib/chef/knife/null.rb
new file mode 100644
index 0000000000..0b5058e8ea
--- /dev/null
+++ b/lib/chef/knife/null.rb
@@ -0,0 +1,10 @@
+class Chef
+ class Knife
+ class Null < Chef::Knife
+ banner "knife null"
+
+ def run
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_create.rb b/lib/chef/knife/osc_user_create.rb
index c368296040..6c3415473f 100644
--- a/lib/chef/knife/osc_user_create.rb
+++ b/lib/chef/knife/osc_user_create.rb
@@ -27,7 +27,7 @@ class Chef
class OscUserCreate < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -69,7 +69,7 @@ class Chef
exit 1
end
- user = Chef::OscUser.new
+ user = Chef::User.new
user.name(@user_name)
user.admin(config[:admin])
user.password config[:user_password]
@@ -79,7 +79,7 @@ class Chef
end
output = edit_data(user)
- user = Chef::OscUser.from_hash(output).create
+ user = Chef::User.from_hash(output).create
ui.info("Created #{user}")
if user.private_key
diff --git a/lib/chef/knife/osc_user_delete.rb b/lib/chef/knife/osc_user_delete.rb
index d6fbd4a6a9..5cd4f10413 100644
--- a/lib/chef/knife/osc_user_delete.rb
+++ b/lib/chef/knife/osc_user_delete.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserDelete < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -43,7 +43,7 @@ class Chef
exit 1
end
- delete_object(Chef::OscUser, @user_name)
+ delete_object(Chef::User, @user_name)
end
end
diff --git a/lib/chef/knife/osc_user_edit.rb b/lib/chef/knife/osc_user_edit.rb
index 4c38674d08..526475db05 100644
--- a/lib/chef/knife/osc_user_edit.rb
+++ b/lib/chef/knife/osc_user_edit.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserEdit < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -43,10 +43,10 @@ class Chef
exit 1
end
- original_user = Chef::OscUser.load(@user_name).to_hash
+ original_user = Chef::User.load(@user_name).to_hash
edited_user = edit_data(original_user)
if original_user != edited_user
- user = Chef::OscUser.from_hash(edited_user)
+ user = Chef::User.from_hash(edited_user)
user.update
ui.msg("Saved #{user}.")
else
diff --git a/lib/chef/knife/osc_user_list.rb b/lib/chef/knife/osc_user_list.rb
index 92f049cd19..84fca31899 100644
--- a/lib/chef/knife/osc_user_list.rb
+++ b/lib/chef/knife/osc_user_list.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserList < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -40,7 +40,7 @@ class Chef
:description => "Show corresponding URIs"
def run
- output(format_list_for_display(Chef::OscUser.list))
+ output(format_list_for_display(Chef::User.list))
end
end
end
diff --git a/lib/chef/knife/osc_user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb
index a71e0aa677..163b286fe0 100644
--- a/lib/chef/knife/osc_user_reregister.rb
+++ b/lib/chef/knife/osc_user_reregister.rb
@@ -28,7 +28,7 @@ class Chef
class OscUserReregister < Knife
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -48,7 +48,7 @@ class Chef
exit 1
end
- user = Chef::OscUser.load(@user_name).reregister
+ user = Chef::User.load(@user_name).reregister
Chef::Log.debug("Updated user data: #{user.inspect}")
key = user.private_key
if config[:file]
diff --git a/lib/chef/knife/osc_user_show.rb b/lib/chef/knife/osc_user_show.rb
index 6a41ddae88..cb3a77585a 100644
--- a/lib/chef/knife/osc_user_show.rb
+++ b/lib/chef/knife/osc_user_show.rb
@@ -30,7 +30,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/osc_user'
+ require 'chef/user'
require 'chef/json_compat'
end
@@ -45,7 +45,7 @@ class Chef
exit 1
end
- user = Chef::OscUser.load(@user_name)
+ user = Chef::User.load(@user_name)
output(format_for_display(user))
end
diff --git a/lib/chef/knife/rehash.rb b/lib/chef/knife/rehash.rb
new file mode 100644
index 0000000000..6f1fd91911
--- /dev/null
+++ b/lib/chef/knife/rehash.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Steven Danna <steve@chef.io>
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/core/subcommand_loader'
+
+class Chef
+ class Knife
+ class Rehash < Chef::Knife
+ banner "knife rehash"
+
+ def run
+ if ! Chef::Knife::SubcommandLoader.autogenerated_manifest?
+ ui.msg "Using knife-rehash will speed up knife's load time by caching the location of subcommands on disk."
+ ui.msg "However, you will need to update the cache by running `knife rehash` anytime you install a new knife plugin."
+ else
+ reload_plugins
+ end
+ write_hash(generate_hash)
+ end
+
+ def reload_plugins
+ Chef::Knife::SubcommandLoader::GemGlobLoader.new(@@chef_config_dir).load_commands
+ end
+
+ def generate_hash
+ output = if Chef::Knife::SubcommandLoader.plugin_manifest?
+ Chef::Knife::SubcommandLoader.plugin_manifest
+ else
+ { Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY => {}}
+ end
+ output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]['plugins_paths'] = Chef::Knife.subcommand_files
+ output[Chef::Knife::SubcommandLoader::HashedCommandLoader::KEY]['plugins_by_category'] = Chef::Knife.subcommands_by_category
+ output
+ end
+
+ def write_hash(data)
+ plugin_manifest_dir = File.expand_path('..', Chef::Knife::SubcommandLoader.plugin_manifest_path)
+ FileUtils.mkdir_p(plugin_manifest_dir) unless File.directory?(plugin_manifest_dir)
+ File.open(Chef::Knife::SubcommandLoader.plugin_manifest_path, 'w') do |f|
+ f.write(Chef::JSONCompat.to_json_pretty(data))
+ ui.msg "Knife subcommands are cached in #{Chef::Knife::SubcommandLoader.plugin_manifest_path}. Delete this file to disable the caching."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb
index c5fe4fc1aa..d71eacfc7e 100644
--- a/lib/chef/knife/ssl_check.rb
+++ b/lib/chef/knife/ssl_check.rb
@@ -73,11 +73,12 @@ class Chef
exit 1
end
-
def verify_peer_socket
@verify_peer_socket ||= begin
tcp_connection = TCPSocket.new(host, port)
- OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ ssl_client.hostname = host
+ ssl_client
end
end
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index e73f6be8b6..995573cd03 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -27,7 +27,7 @@ class Chef
attr_accessor :user_field
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -61,11 +61,11 @@ class Chef
banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
def user
- @user_field ||= Chef::User.new
+ @user_field ||= Chef::UserV1.new
end
def create_user_from_hash(hash)
- Chef::User.from_hash(hash).create
+ Chef::UserV1.from_hash(hash).create
end
def osc_11_warning
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
index 803be6b90c..828cd51588 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -23,7 +23,7 @@ class Chef
class UserDelete < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -55,7 +55,7 @@ EOF
if Kernel.block_given?
object = block.call
else
- object = Chef::User.load(user_name)
+ object = Chef::UserV1.load(user_name)
object.destroy
end
@@ -77,10 +77,10 @@ EOF
# Below is modification of Chef::Knife.delete_object to detect OSC 11 server.
# When OSC 11 is deprecated, simply delete all this and go back to:
#
- # delete_object(Chef::User, @user_name)
+ # delete_object(Chef::UserV1, @user_name)
#
# Also delete our override of delete_object above
- object = Chef::User.load(@user_name)
+ object = Chef::UserV1.load(@user_name)
# OSC 11 case
if object.username.nil?
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index dd2fc02743..c3a4326ee8 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -23,7 +23,7 @@ class Chef
class UserEdit < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -56,8 +56,7 @@ EOF
exit 1
end
- original_user = Chef::User.load(@user_name).to_hash
-
+ original_user = Chef::UserV1.load(@user_name).to_hash
# DEPRECATION NOTE
# Remove this if statement and corrosponding code post OSC 11 support.
#
@@ -69,11 +68,11 @@ EOF
else # EC / CS 12 user create
edited_user = edit_data(original_user)
if original_user != edited_user
- user = Chef::User.from_hash(edited_user)
+ user = Chef::UserV1.from_hash(edited_user)
user.update
ui.msg("Saved #{user}.")
else
- ui.msg("User unchaged, not saving.")
+ ui.msg("User unchanged, not saving.")
end
end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
index 7ae43dadc9..6a130392b9 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -25,7 +25,7 @@ class Chef
class UserList < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -37,7 +37,7 @@ class Chef
:description => "Show corresponding URIs"
def run
- output(format_list_for_display(Chef::User.list))
+ output(format_list_for_display(Chef::UserV1.list))
end
end
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
index eab2245025..09fd1cd2d6 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/user_reregister.rb
@@ -23,7 +23,7 @@ class Chef
class UserReregister < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -61,7 +61,7 @@ EOF
exit 1
end
- user = Chef::User.load(@user_name)
+ user = Chef::UserV1.load(@user_name)
# DEPRECATION NOTE
# Remove this if statement and corrosponding code post OSC 11 support.
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
index f5e81e9972..3a2443471a 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -25,7 +25,7 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -58,7 +58,7 @@ EOF
exit 1
end
- user = Chef::User.load(@user_name)
+ user = Chef::UserV1.load(@user_name)
# DEPRECATION NOTE
# Remove this if statement and corrosponding code post OSC 11 support.
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index 9b27778a40..bf846c2072 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -37,7 +37,11 @@ class Chef
end
end
- def self.deprecation(msg=nil, &block)
+ def self.deprecation(msg=nil, location=caller(2..2)[0], &block)
+ if msg
+ msg << " at #{Array(location).join("\n")}"
+ msg = msg.join("") if msg.respond_to?(:join)
+ end
if Chef::Config[:treat_deprecation_warnings_as_errors]
error(msg, &block)
raise Chef::Exceptions::DeprecatedFeatureError.new(msg)
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index a3eacf75cb..562af541bd 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -102,20 +102,20 @@ class Chef
def deprecated_attr_reader(name, alternative, level=:warn)
define_method(name) do
- Chef::Log.deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
- Chef::Log.deprecation(alternative)
- Chef::Log.deprecation("Called from:")
- caller[0..3].each {|c| Chef::Log.deprecation(c)}
+ Chef.log_deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
+ Chef.log_deprecation(alternative)
+ Chef.log_deprecation("Called from:")
+ caller[0..3].each {|c| Chef.log_deprecation(c)}
instance_variable_get("@#{name}")
end
end
def deprecated_attr_writer(name, alternative, level=:warn)
define_method("#{name}=") do |value|
- Chef::Log.deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
- Chef::Log.deprecation(alternative)
- Chef::Log.deprecation("Called from:")
- caller[0..3].each {|c| Chef::Log.deprecation(c)}
+ Chef.log_deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
+ Chef.log_deprecation(alternative)
+ Chef.log_deprecation("Called from:")
+ caller[0..3].each {|c| Chef.log_deprecation(c)}
instance_variable_set("@#{name}", value)
end
end
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 78d72dc801..e3c7657b1b 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -15,9 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'chef/constants'
+require 'chef/property'
+require 'chef/delayed_evaluator'
+
class Chef
- class DelayedEvaluator < Proc
- end
module Mixin
module ParamsValidate
@@ -32,20 +34,55 @@ class Chef
# Would raise an exception if the value of :one above is not a kind_of? string. Valid
# map options are:
#
- # :default:: Sets the default value for this parameter.
- # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
- # The key will be inserted into the error message if the Proc does not return true:
- # "Option #{key}'s value #{value} #{message}!"
- # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
- # that the value is one of those types.
- # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
- # method names.
- # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
- # by default, options are not required.
- # :regex:: Match the value of the parameter against a regular expression.
- # :equal_to:: Match the value of the parameter with ==. An array means it can be equal to any
- # of the values.
+ # @param opts [Hash<Symbol,Object>] Validation opts.
+ # @option opts [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.)
+ # @option opts [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`opts[:is].any? { |v| v == value }`) (See #_pv_equal_to.)
+ # @option opts [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # (See #_pv_regex)
+ # @option opts [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of. (See
+ # #_pv_kind_of.)
+ # @option opts [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches). (See
+ # #_pv_callbacks.)
+ # @option opts [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to. (See
+ # #_pv_respond_to.)
+ # @option opts [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty). (See #_pv_cannot_be.)
+ # @option opts [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # (See #_pv_coerce.) (See #_pv_coerce.)
+ # @option opts [Boolean] :required `true` if this property
+ # must be present and not `nil`; `false` otherwise. This is checked
+ # after the resource is fully initialized. (See #_pv_required.)
+ # @option opts [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set. (See
+ # #_pv_name_property.)
+ # @option opts [Boolean] :name_attribute Same as `name_property`.
+ # @option opts [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties). (See #_pv_default.)
+ #
def validate(opts, map)
+ map = map.validation_options if map.is_a?(Property)
+
#--
# validate works by taking the keys in the validation map, assuming it's a hash, and
# looking for _pv_:symbol as methods. Assuming it find them, it calls the right
@@ -65,7 +102,7 @@ class Chef
true
when Hash
validation.each do |check, carg|
- check_method = "_pv_#{check.to_s}"
+ check_method = "_pv_#{check}"
if self.respond_to?(check_method, true)
self.send(check_method, opts, key, carg)
else
@@ -81,162 +118,352 @@ class Chef
DelayedEvaluator.new(&block)
end
- def set_or_return(symbol, arg, validation)
- iv_symbol = "@#{symbol.to_s}".to_sym
- if arg == nil && self.instance_variable_defined?(iv_symbol) == true
- ivar = self.instance_variable_get(iv_symbol)
- if(ivar.is_a?(DelayedEvaluator))
- validate({ symbol => ivar.call }, { symbol => validation })[symbol]
- else
- ivar
- end
- else
- if(arg.is_a?(DelayedEvaluator))
- val = arg
- else
- val = validate({ symbol => arg }, { symbol => validation })[symbol]
+ def set_or_return(symbol, value, validation)
+ property = SetOrReturnProperty.new(name: symbol, **validation)
+ property.call(self, value)
+ end
- # Handle the case where the "default" was a DelayedEvaluator. In
- # this case, the block yields an optional parameter of +self+,
- # which is the equivalent of "new_resource"
- if val.is_a?(DelayedEvaluator)
- val = val.call(self)
- end
- end
- self.instance_variable_set(iv_symbol, val)
+ private
+
+ def explicitly_allows_nil?(key, validation)
+ validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
+ end
+
+ # Return the value of a parameter, or nil if it doesn't exist.
+ def _pv_opts_lookup(opts, key)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s]
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym]
+ else
+ nil
end
end
- private
+ # Raise an exception if the parameter is not found.
+ def _pv_required(opts, key, is_required=true, explicitly_allows_nil=false)
+ if is_required
+ return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
+ return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
+ raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!"
+ end
+ true
+ end
- # Return the value of a parameter, or nil if it doesn't exist.
- def _pv_opts_lookup(opts, key)
- if opts.has_key?(key.to_s)
- opts[key.to_s]
- elsif opts.has_key?(key.to_sym)
- opts[key.to_sym]
- else
- nil
+ #
+ # List of things values must be equal to.
+ #
+ # Uses Ruby's `==` to evaluate (equal_to == value). At least one must
+ # match for the value to be valid.
+ #
+ # `nil` passes this validation automatically.
+ #
+ # @return [Array,nil] List of things values must be equal to, or nil if
+ # equal_to is unspecified.
+ #
+ def _pv_equal_to(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value == tb
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
end
+ end
- # Raise an exception if the parameter is not found.
- def _pv_required(opts, key, is_required=true)
- if is_required
- if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
- (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
- true
- else
- raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
- end
+ #
+ # List of things values must be instances of.
+ #
+ # Uses value.kind_of?(kind_of) to evaluate. At least one must match for
+ # the value to be valid.
+ #
+ # `nil` automatically passes this validation.
+ #
+ def _pv_kind_of(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value.kind_of?(tb)
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
end
+ end
- def _pv_equal_to(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value == tb
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ #
+ # List of method names values must respond to.
+ #
+ # Uses value.respond_to?(respond_to) to evaluate. At least one must match
+ # for the value to be valid.
+ #
+ def _pv_respond_to(opts, key, method_name_list)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ Array(method_name_list).each do |method_name|
+ unless value.respond_to?(method_name)
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
end
end
end
+ end
- # Raise an exception if the parameter is not a kind_of?(to_be)
- def _pv_kind_of(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value.kind_of?(tb)
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ #
+ # List of things that must not be true about the value.
+ #
+ # Calls `value.<thing>?` All responses must be false for the value to be
+ # valid.
+ # Values which do not respond to <thing>? are considered valid (because if
+ # a value doesn't respond to `:readable?`, then it probably isn't
+ # readable.)
+ #
+ # @example
+ # ```ruby
+ # property :x, cannot_be: [ :nil, :empty ]
+ # x [ 1, 2 ] #=> valid
+ # x 1 #=> valid
+ # x [] #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(predicate_method_base_name).each do |method_name|
+ predicate_method = :"#{method_name}?"
+
+ if value.respond_to?(predicate_method)
+ if value.send(predicate_method)
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ end
end
end
end
+ end
- # Raise an exception if the parameter does not respond to a given set of methods.
- def _pv_respond_to(opts, key, method_name_list)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- Array(method_name_list).each do |method_name|
- unless value.respond_to?(method_name)
- raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
- end
- end
+ #
+ # The default value for a property.
+ #
+ # When the property is not assigned, this will be used.
+ #
+ # If this is a lazy value, it will either be passed the resource as a value,
+ # or if the lazy proc does not take parameters, it will be run in the
+ # context of the instance with instance_eval.
+ #
+ # @example
+ # ```ruby
+ # property :x, default: 10
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { x+2 }
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { |r| r.x+2 }
+ # ```
+ #
+ def _pv_default(opts, key, default_value)
+ value = _pv_opts_lookup(opts, key)
+ if value.nil?
+ default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator)
+ opts[key] = default_value
+ end
+ end
+
+ #
+ # List of regexes values that must match.
+ #
+ # Uses regex.match() to evaluate. At least one must match for the value to
+ # be valid.
+ #
+ # `nil` passes regex validation automatically.
+ #
+ # @example
+ # ```ruby
+ # property :x, regex: [ /abc/, /xyz/ ]
+ # ```
+ #
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(regex).flatten.each do |r|
+ return true if r.match(value.to_s)
end
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
end
+ end
- # Assert that parameter returns false when passed a predicate method.
- # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
- # error value.blank? returns a 'truthy' (not nil or false) value.
- #
- # Note, this will *PASS* if the object doesn't respond to the method.
- # So, to make sure a value is not nil and not blank, you need to do
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
- def _pv_cannot_be(opts, key, predicate_method_base_name)
- value = _pv_opts_lookup(opts, key)
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
-
- if value.respond_to?(predicate_method)
- if value.send(predicate_method)
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ #
+ # List of procs we pass the value to.
+ #
+ # All procs must return true for the value to be valid. If any procs do
+ # not return true, the key will be used for the message: `"Property x's
+ # value :y <message>"`.
+ #
+ # @example
+ # ```ruby
+ # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
+ # ```
+ #
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ callbacks.each do |message, zeproc|
+ unless zeproc.call(value)
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
end
end
end
+ end
- # Assign a default value to a parameter.
- def _pv_default(opts, key, default_value)
- value = _pv_opts_lookup(opts, key)
- if value == nil
- opts[key] = default_value
+ #
+ # Allows a parameter to default to the value of the resource name.
+ #
+ # @example
+ # ```ruby
+ # property :x, name_property: true
+ # ```
+ #
+ def _pv_name_property(opts, key, is_name_property=true)
+ if is_name_property
+ if opts[key].nil?
+ opts[key] = self.instance_variable_get(:"@name")
end
end
+ end
+ alias :_pv_name_attribute :_pv_name_property
- # Check a parameter against a regular expression.
- def _pv_regex(opts, key, regex)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- passes = false
- [ regex ].flatten.each do |r|
- if value != nil
- if r.match(value.to_s)
- passes = true
- end
- end
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
- end
+ #
+ # List of valid things values can be.
+ #
+ # Uses Ruby's `===` to evaluate (is === value). At least one must match
+ # for the value to be valid.
+ #
+ # If a proc is passed, it is instance_eval'd in the resource, passed the
+ # value, and must return a truthy or falsey value.
+ #
+ # @example Class
+ # ```ruby
+ # property :x, String
+ # x 'valid' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ #
+ # @example Value
+ # ```ruby
+ # property :x, [ :a, :b, :c, nil ]
+ # x :a #=> valid
+ # x nil #=> valid
+ # ```
+ #
+ # @example Regex
+ # ```ruby
+ # property :x, /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example Proc
+ # ```ruby
+ # property :x, proc { |x| x > y }
+ # property :y, default: 2
+ # x 3 #=> valid
+ # x 1 #=> invalid
+ # ```
+ #
+ # @example Property
+ # ```ruby
+ # type = Property.new(is: String)
+ # property :x, type
+ # x 'foo' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example RSpec Matcher
+ # ```ruby
+ # include RSpec::Matchers
+ # property :x, a_string_matching /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_is(opts, key, to_be, raise_error: true)
+ return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
+ value = _pv_opts_lookup(opts, key)
+ to_be = [ to_be ].flatten(1)
+ to_be.each do |tb|
+ case tb
+ when Proc
+ return true if instance_exec(value, &tb)
+ when Property
+ validate(opts, { key => tb.validation_options })
+ return true
+ else
+ return true if tb === value
end
end
- # Check a parameter against a hash of proc's.
- def _pv_callbacks(opts, key, callbacks)
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- callbacks.each do |message, zeproc|
- if zeproc.call(value) != true
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
- end
- end
+ if raise_error
+ raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ else
+ false
+ end
+ end
+
+ #
+ # Method to mess with a value before it is validated and stored.
+ #
+ # Allows you to transform values into a canonical form that is easy to
+ # work with.
+ #
+ # This is passed the value to transform, and is run in the context of the
+ # instance (so it has access to other resource properties). It must return
+ # the value that will be stored in the instance.
+ #
+ # @example
+ # ```ruby
+ # property :x, Integer, coerce: { |v| v.to_i }
+ # ```
+ #
+ def _pv_coerce(opts, key, coercer)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s] = instance_exec(opts[key], &coercer)
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym] = instance_exec(opts[key], &coercer)
+ end
+ end
+
+ # Used by #set_or_return to avoid emitting a deprecation warning for
+ # "value nil" and to keep default stickiness working exactly the same
+ # @api private
+ class SetOrReturnProperty < Chef::Property
+ def get(resource)
+ value = super
+ # All values are sticky, frozen or not
+ if !is_set?(resource)
+ set_value(resource, value)
end
+ value
end
- # Allow a parameter to default to @name
- def _pv_name_attribute(opts, key, is_name_attribute=true)
- if is_name_attribute
- if opts[key] == nil
- opts[key] = self.instance_variable_get("@name")
- end
+ def call(resource, value=NOT_PASSED)
+ # setting to nil does a get
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ get(resource)
+ else
+ super
end
end
+ end
end
end
end
-
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
index 9b35bbcc33..db9a2f6d91 100644
--- a/lib/chef/mixin/template.rb
+++ b/lib/chef/mixin/template.rb
@@ -44,6 +44,52 @@ class Chef
attr_reader :_extension_modules
+ #
+ # Helpers for adding context of which resource is rendering the template (CHEF-5012)
+ #
+
+ # name of the cookbook containing the template resource, e.g.:
+ # test
+ #
+ # @return [String] cookbook name
+ attr_reader :cookbook_name
+
+ # name of the recipe containing the template resource, e.g.:
+ # default
+ #
+ # @return [String] recipe name
+ attr_reader :recipe_name
+
+ # string representation of the line in the recipe containing the template resource, e.g.:
+ # /Users/lamont/solo/cookbooks/test/recipes/default.rb:2:in `from_file'
+ #
+ # @return [String] recipe line
+ attr_reader :recipe_line_string
+
+ # path to the recipe containing the template resource, e.g.:
+ # /Users/lamont/solo/cookbooks/test/recipes/default.rb
+ #
+ # @return [String] recipe path
+ attr_reader :recipe_path
+
+ # line in the recipe containing the template reosurce, e.g.:
+ # 2
+ #
+ # @return [String] recipe line
+ attr_reader :recipe_line
+
+ # name of the template source itself, e.g.:
+ # foo.erb
+ #
+ # @return [String] template name
+ attr_reader :template_name
+
+ # path to the template source itself, e.g.:
+ # /Users/lamont/solo/cookbooks/test/templates/default/foo.erb
+ #
+ # @return [String] template path
+ attr_reader :template_path
+
def initialize(variables)
super
@_extension_modules = []
@@ -62,6 +108,7 @@ class Chef
"include a node variable if you plan to use it."
end
+
#
# Takes the name of the partial, plus a hash of options. Returns a
# string that contains the result of the evaluation of the partial.
diff --git a/lib/chef/mixin/wide_string.rb b/lib/chef/mixin/wide_string.rb
new file mode 100644
index 0000000000..0c32b76365
--- /dev/null
+++ b/lib/chef/mixin/wide_string.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Jay Mundrawala(<jdm@chef.io>)
+# Copyright:: Copyright 2015 Chef Software
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module WideString
+
+ def wstring(str)
+ if str.nil? || str.encoding == Encoding::UTF_16LE
+ str
+ else
+ utf8_to_wide(str)
+ end
+ end
+
+ def utf8_to_wide(ustring)
+ # ensure it is actually UTF-8
+ # Ruby likes to mark binary data as ASCII-8BIT
+ ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
+
+ # ensure we have the double-null termination Windows Wide likes
+ ustring = ustring + "\000\000" if ustring.length == 0 or ustring[-1].chr != "\000"
+
+ # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
+ ustring = begin
+ if ustring.respond_to?(:encode)
+ ustring.encode('UTF-16LE')
+ else
+ require 'iconv'
+ Iconv.conv("UTF-16LE", "UTF-8", ustring)
+ end
+ end
+ ustring
+ end
+
+ def wide_to_utf8(wstring)
+ # ensure it is actually UTF-16LE
+ # Ruby likes to mark binary data as ASCII-8BIT
+ wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding)
+
+ # encode it all as UTF-8
+ wstring = begin
+ if wstring.respond_to?(:encode)
+ wstring.encode('UTF-8')
+ else
+ require 'iconv'
+ Iconv.conv("UTF-8", "UTF-16LE", wstring)
+ end
+ end
+ # remove trailing CRLF and NULL characters
+ wstring.strip!
+ wstring
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/windows_architecture_helper.rb b/lib/chef/mixin/windows_architecture_helper.rb
index c5f3e1bd79..744001f8a2 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -19,19 +19,13 @@
require 'chef/exceptions'
require 'chef/platform/query_helpers'
-require 'win32/api' if Chef::Platform.windows?
-require 'chef/win32/api/process' if Chef::Platform.windows?
-require 'chef/win32/api/error' if Chef::Platform.windows?
+require 'chef/win32/process' if Chef::Platform.windows?
+require 'chef/win32/system' if Chef::Platform.windows?
class Chef
module Mixin
module WindowsArchitectureHelper
- if Chef::Platform.windows?
- include Chef::ReservedNames::Win32::API::Process
- include Chef::ReservedNames::Win32::API::Error
- end
-
def node_windows_architecture(node)
node[:kernel][:machine].to_sym
end
@@ -42,6 +36,16 @@ class Chef
is_i386_process_on_x86_64_windows?
end
+ def forced_32bit_override_required?(node, desired_architecture)
+ desired_architecture == :i386 &&
+ node_windows_architecture(node) == :x86_64 &&
+ !is_i386_process_on_x86_64_windows?
+ end
+
+ def wow64_directory
+ Chef::ReservedNames::Win32::System.get_system_wow64_directory
+ end
+
def with_os_architecture(node, architecture: nil)
node ||= begin
os_arch = ENV['PROCESSOR_ARCHITEW6432'] ||
@@ -88,49 +92,21 @@ class Chef
def is_i386_process_on_x86_64_windows?
if Chef::Platform.windows?
- is_64_bit_process_result = FFI::MemoryPointer.new(:int)
-
- # The return value of IsWow64Process is nonzero value if the API call succeeds.
- # The result data are returned in the last parameter, not the return value.
- call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result)
-
- # The result is nonzero if IsWow64Process's calling process, in the case here
- # this process, is running under WOW64, i.e. the result is nonzero if this
- # process is 32-bit (aka :i386).
- result = (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0)
+ Chef::ReservedNames::Win32::Process.is_wow64_process
else
false
end
end
def disable_wow64_file_redirection( node )
- original_redirection_state = ['0'].pack('P')
-
if ( ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
- win32_wow_64_disable_wow_64_fs_redirection =
- ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32')
-
- succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state)
-
- if succeeded == 0
- raise Win32APIError "Failed to disable Wow64 file redirection"
- end
-
+ Chef::ReservedNames::Win32::System.wow64_disable_wow64_fs_redirection
end
-
- original_redirection_state
end
def restore_wow64_file_redirection( node, original_redirection_state )
if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
- win32_wow_64_revert_wow_64_fs_redirection =
- ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32')
-
- succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state)
-
- if succeeded == 0
- raise Win32APIError "Failed to revert Wow64 file redirection"
- end
+ Chef::ReservedNames::Win32::System.wow64_revert_wow64_fs_redirection(original_redirection_state)
end
end
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
index a126801a28..cd12b4254a 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -18,6 +18,7 @@
require 'chef/exceptions'
+require 'chef/mixin/wide_string'
require 'chef/platform/query_helpers'
require 'chef/win32/error' if Chef::Platform.windows?
require 'chef/win32/api/system' if Chef::Platform.windows?
@@ -26,6 +27,8 @@ require 'chef/win32/api/unicode' if Chef::Platform.windows?
class Chef
module Mixin
module WindowsEnvHelper
+ include Chef::Mixin::WideString
+
if Chef::Platform.windows?
include Chef::ReservedNames::Win32::API::System
end
@@ -45,7 +48,7 @@ class Chef
Chef::ReservedNames::Win32::Error.raise!
end
if ( SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string(
- Chef::ReservedNames::Win32::Unicode.utf8_to_wide('Environment')
+ utf8_to_wide('Environment')
).address, flags, 5000, nil) == 0 )
Chef::ReservedNames::Win32::Error.raise!
end
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index d5078371c5..22c7d5bd8e 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -315,6 +315,7 @@ class Chef
# Consumes the combined run_list and other attributes in +attrs+
def consume_attributes(attrs)
normal_attrs_to_merge = consume_run_list(attrs)
+ normal_attrs_to_merge = consume_chef_environment(normal_attrs_to_merge)
Chef::Log.debug("Applying attributes from json file")
self.normal_attrs = Chef::Mixin::DeepMerge.merge(normal_attrs,normal_attrs_to_merge)
self.tags # make sure they're defined
@@ -347,6 +348,24 @@ class Chef
attrs
end
+ # chef_environment when set in -j JSON will take precedence over
+ # -E ENVIRONMENT. Ideally, IMO, the order of precedence should be (lowest to
+ # highest):
+ # config_file
+ # -j JSON
+ # -E ENVIRONMENT
+ # so that users could reuse their JSON and override the chef_environment
+ # configured within it with -E ENVIRONMENT. Because command line options are
+ # merged with Chef::Config there is currently no way to distinguish between
+ # an environment set via config from an environment set via command line.
+ def consume_chef_environment(attrs)
+ attrs = attrs ? attrs.dup : {}
+ if env = attrs.delete("chef_environment")
+ chef_environment(env)
+ end
+ attrs
+ end
+
# Clear defaults and overrides, so that any deleted attributes
# between runs are still gone.
def reset_defaults_and_overrides
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index f547018a38..751f9576f6 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -20,13 +20,6 @@ class Chef
class NodeMap
#
- # Create a new NodeMap
- #
- def initialize
- @map = {}
- end
-
- #
# Set a key/value pair on the map with a filter. The filter must be true
# when applied to the node in order to retrieve the value.
#
@@ -38,30 +31,34 @@ class Chef
#
# @return [NodeMap] Returns self for possible chaining
#
- def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, &block)
- Chef::Log.deprecation "The on_platform option to node_map has been deprecated" if on_platform
- Chef::Log.deprecation "The on_platforms option to node_map has been deprecated" if on_platforms
+ def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block)
+ Chef.log_deprecation("The on_platform option to node_map has been deprecated") if on_platform
+ Chef.log_deprecation("The on_platforms option to node_map has been deprecated") if on_platforms
platform ||= on_platform || on_platforms
- filters = { platform: platform, platform_version: platform_version, platform_family: platform_family, os: os }
- new_matcher = { filters: filters, block: block, value: value, canonical: canonical }
- @map[key] ||= []
- # Decide where to insert the matcher; the new value is preferred over
- # anything more specific (see `priority_of`) and is preferred over older
- # values of the same specificity. (So all other things being equal,
- # newest wins.)
+ filters = {}
+ filters[:platform] = platform if platform
+ filters[:platform_version] = platform_version if platform_version
+ filters[:platform_family] = platform_family if platform_family
+ filters[:os] = os if os
+ new_matcher = { value: value, filters: filters }
+ new_matcher[:block] = block if block
+ new_matcher[:canonical] = canonical if canonical
+ new_matcher[:override] = override if override
+
+ # The map is sorted in order of preference already; we just need to find
+ # our place in it (just before the first value with the same preference level).
insert_at = nil
- @map[key].each_with_index do |matcher, index|
- if specificity(new_matcher) >= specificity(matcher)
- insert_at = index
- break
- end
+ map[key] ||= []
+ map[key].each_with_index do |matcher,index|
+ cmp = compare_matchers(key, new_matcher, matcher)
+ insert_at ||= index if cmp && cmp <= 0
end
if insert_at
- @map[key].insert(insert_at, new_matcher)
+ map[key].insert(insert_at, new_matcher)
else
- @map[key] << new_matcher
+ map[key] << new_matcher
end
- self
+ map
end
#
@@ -95,8 +92,8 @@ class Chef
#
def list(node, key, canonical: nil)
raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
- return [] unless @map.has_key?(key)
- @map[key].select do |matcher|
+ return [] unless map.has_key?(key)
+ map[key].select do |matcher|
node_matches?(node, matcher) && canonical_matches?(canonical, matcher)
end.map { |matcher| matcher[:value] }
end
@@ -105,41 +102,18 @@ class Chef
# @return remaining
# @api private
def delete_canonical(key, value)
- remaining = @map[key]
+ remaining = map[key]
if remaining
remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) }
if remaining.empty?
- @map.delete(key)
+ map.delete(key)
remaining = nil
end
end
remaining
end
- private
-
- #
- # Gives a value for "how specific" the matcher is.
- # Things which specify more specific filters get a higher number
- # (platform_version > platform > platform_family > os); things
- # with a block have higher specificity than similar things without
- # a block.
- #
- def specificity(matcher)
- if matcher[:filters][:platform_version]
- specificity = 8
- elsif matcher[:filters][:platform]
- specificity = 6
- elsif matcher[:filters][:platform_family]
- specificity = 4
- elsif matcher[:filters][:os]
- specificity = 2
- else
- specificity = 0
- end
- specificity += 1 if matcher[:block]
- specificity
- end
+ protected
#
# Succeeds if:
@@ -197,5 +171,52 @@ class Chef
return true if canonical.nil?
!!canonical == !!matcher[:canonical]
end
+
+ def compare_matchers(key, new_matcher, matcher)
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
+ return cmp if cmp != 0
+ # If all things are identical, return 0
+ 0
+ end
+
+ def compare_matcher_properties(new_matcher, matcher)
+ a = yield(new_matcher)
+ b = yield(matcher)
+
+ # Check for blcacklists ('!windows'). Those always come *after* positive
+ # whitelists.
+ a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ if a_negated != b_negated
+ return 1 if a_negated
+ return -1 if b_negated
+ end
+
+ # We treat false / true and nil / not-nil with the same comparison
+ a = nil if a == false
+ b = nil if b == false
+ cmp = a <=> b
+ # This is the case where one is non-nil, and one is nil. The one that is
+ # nil is "greater" (i.e. it should come last).
+ if cmp.nil?
+ return 1 if a.nil?
+ return -1 if b.nil?
+ end
+ cmp
+ end
+
+ def map
+ @map ||= {}
+ end
end
end
diff --git a/lib/chef/osc_user.rb b/lib/chef/osc_user.rb
deleted file mode 100644
index 52bfd11108..0000000000
--- a/lib/chef/osc_user.rb
+++ /dev/null
@@ -1,194 +0,0 @@
-#
-# Author:: Steven Danna (steve@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'
-
-# TODO
-# DEPRECATION NOTE
-# This class was previously Chef::User. It is the code to support the User object
-# corrosponding to the Open Source Chef Server 11 and only still exists to support
-# users still on OSC 11.
-#
-# Chef::User now supports Chef Server 12.
-#
-# New development should occur in Chef::User.
-# This file and corrosponding osc_user knife files
-# should be removed once client support for Open Source Chef Server 11 expires.
-class Chef
- class OscUser
-
- 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)
- Chef::JSONCompat.to_json(to_hash, *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::OscUser.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::OscUser.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::OscUser name:'#{name}' admin:'#{admin.inspect}'" +
- "public_key:'#{public_key}' private_key:#{private_key}"
- end
-
- # Class Methods
-
- def self.from_hash(user_hash)
- user = Chef::OscUser.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::OscUser.from_hash(Chef::JSONCompat.from_json(json))
- end
-
- class << self
- alias_method :json_create, :from_json
- end
-
- def self.list(inflate=false)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
- users = if response.is_a?(Array)
- transform_ohc_list_response(response) # OHC/OPC
- else
- response # OSC
- end
- if inflate
- users.inject({}) do |user_map, (name, _url)|
- user_map[name] = Chef::OscUser.load(name)
- user_map
- end
- else
- users
- end
- end
-
- def self.load(name)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}")
- Chef::OscUser.from_hash(response)
- end
-
- # 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
-
- private_class_method :transform_ohc_list_response
- end
-end
diff --git a/lib/chef/platform/handler_map.rb b/lib/chef/platform/handler_map.rb
new file mode 100644
index 0000000000..a9551a344b
--- /dev/null
+++ b/lib/chef/platform/handler_map.rb
@@ -0,0 +1,40 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/node_map'
+
+class Chef
+ class Platform
+ class HandlerMap < Chef::NodeMap
+ #
+ # "provides" lines with identical filters sort by class name (ascending).
+ #
+ def compare_matchers(key, new_matcher, matcher)
+ cmp = super
+ if cmp == 0
+ # Sort by class name (ascending) as well, if all other properties
+ # are exactly equal
+ if new_matcher[:value].is_a?(Class) && !new_matcher[:override]
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:value].name }
+ end
+ end
+ cmp
+ end
+ end
+ end
+end
diff --git a/lib/chef/platform/priority_map.rb b/lib/chef/platform/priority_map.rb
new file mode 100644
index 0000000000..0b050deb59
--- /dev/null
+++ b/lib/chef/platform/priority_map.rb
@@ -0,0 +1,41 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/node_map'
+
+class Chef
+ class Platform
+ class PriorityMap < Chef::NodeMap
+ def priority(resource_name, priority_array, *filter)
+ set_priority_array(resource_name.to_sym, priority_array, *filter)
+ end
+
+ # @api private
+ def get_priority_array(node, key)
+ get(node, key)
+ end
+
+ # @api private
+ def set_priority_array(key, priority_array, *filter, &block)
+ priority_array = Array(priority_array)
+ set(key, priority_array, *filter, &block)
+ priority_array
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/wstring.rb b/lib/chef/platform/provider_handler_map.rb
index bb6fdf4884..4549d7994e 100644
--- a/lib/chef/mixin/wstring.rb
+++ b/lib/chef/platform/provider_handler_map.rb
@@ -1,6 +1,6 @@
#
-# Author:: Jay Mundrawala(<jdm@chef.io>)
-# Copyright:: Copyright 2015 Chef Software
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,14 @@
# limitations under the License.
#
+require 'singleton'
+require 'chef/platform/handler_map'
+
class Chef
- module Mixin
- module WideString
- def wstring(str)
- if str.nil? || str.encoding == Encoding::UTF_16LE
- str
- else
- str.to_wstring
- end
- end
+ class Platform
+ # @api private
+ class ProviderHandlerMap < Chef::Platform::HandlerMap
+ include Singleton
end
end
end
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index e3a894c8ac..9b511f0237 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -176,7 +176,7 @@ class Chef
platform_provider(platform, version, resource_type) ||
resource_matching_provider(platform, version, resource_type)
- raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
+ raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
provider_klass
end
@@ -197,16 +197,16 @@ class Chef
def resource_matching_provider(platform, version, resource_type)
if resource_type.kind_of?(Chef::Resource)
- class_name = resource_type.class.to_s.split('::').last
+ class_name = resource_type.class.name ? resource_type.class.name.split('::').last :
+ convert_to_class_name(resource_type.resource_name.to_s)
- begin
- result = Chef::Provider.const_get(class_name)
+ if Chef::Provider.const_defined?(class_name)
Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.")
- Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to provide DSL.")
- rescue NameError
+ Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to use the resource's DSL.")
+ return Chef::Provider.const_get(class_name)
end
end
- result
+ nil
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 9d703c9178..5599c74c2d 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,29 +1,11 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ProviderPriorityMap
+ # @api private
+ class ProviderPriorityMap < Chef::Platform::PriorityMap
include Singleton
-
- def get_priority_array(node, resource_name)
- priority_map.get(node, resource_name.to_sym)
- end
-
- def set_priority_array(resource_name, priority_array, *filter, &block)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
- # @api private
- def list_handlers(node, resource_name)
- priority_map.list(node, resource_name.to_sym).flatten(1).uniq
- end
-
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index b3948eac21..e64189fbd6 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -25,13 +25,10 @@ class Chef
end
def windows_server_2003?
+ # WMI startup shouldn't be performed unless we're on Windows.
return false unless windows?
require 'wmi-lite/wmi'
- # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
- # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
- # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
-
wmi = WmiLite::Wmi.new
host = wmi.first_of('Win32_OperatingSystem')
is_server_2003 = (host['version'] && host['version'].start_with?("5.2"))
diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb
index b46f0e394c..b78ac38f0c 100644
--- a/lib/chef/platform/rebooter.rb
+++ b/lib/chef/platform/rebooter.rb
@@ -32,7 +32,7 @@ class Chef
cmd = if Chef::Platform.windows?
# should this do /f as well? do we then need a minimum delay to let apps quit?
- "shutdown /r /t #{reboot_info[:delay_mins]} /c \"#{reboot_info[:reason]}\""
+ "shutdown /r /t #{reboot_info[:delay_mins]*60} /c \"#{reboot_info[:reason]}\""
else
# probably Linux-only.
"shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\""
diff --git a/lib/chef/platform/resource_handler_map.rb b/lib/chef/platform/resource_handler_map.rb
new file mode 100644
index 0000000000..27a7bb1342
--- /dev/null
+++ b/lib/chef/platform/resource_handler_map.rb
@@ -0,0 +1,29 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'singleton'
+require 'chef/platform/handler_map'
+
+class Chef
+ class Platform
+ # @api private
+ class ResourceHandlerMap < Chef::Platform::HandlerMap
+ include Singleton
+ end
+ end
+end
diff --git a/lib/chef/platform/resource_priority_map.rb b/lib/chef/platform/resource_priority_map.rb
index fb08debc53..5cc86fd2e7 100644
--- a/lib/chef/platform/resource_priority_map.rb
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -1,34 +1,11 @@
require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ResourcePriorityMap
+ # @api private
+ class ResourcePriorityMap < Chef::Platform::PriorityMap
include Singleton
-
- def get_priority_array(node, resource_name, canonical: nil)
- priority_map.get(node, resource_name.to_sym, canonical: canonical)
- end
-
- def set_priority_array(resource_name, priority_array, *filter, &block)
- priority_map.set(resource_name.to_sym, Array(priority_array), *filter, &block)
- end
-
- # @api private
- def delete_canonical(resource_name, resource_class)
- priority_map.delete_canonical(resource_name, resource_class)
- end
-
- # @api private
- def list_handlers(*args)
- priority_map.list(*args).flatten(1).uniq
- end
-
- private
-
- def priority_map
- require 'chef/node_map'
- @priority_map ||= Chef::NodeMap.new
- end
end
end
end
diff --git a/lib/chef/property.rb b/lib/chef/property.rb
new file mode 100644
index 0000000000..09198d90f1
--- /dev/null
+++ b/lib/chef/property.rb
@@ -0,0 +1,536 @@
+#
+# Author:: John Keiser <jkeiser@chef.io>
+# Copyright:: Copyright (c) 2015 John Keiser.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/exceptions'
+require 'chef/delayed_evaluator'
+
+class Chef
+ #
+ # Type and validation information for a property on a resource.
+ #
+ # A property named "x" manipulates the "@x" instance variable on a
+ # resource. The *presence* of the variable (`instance_variable_defined?(@x)`)
+ # tells whether the variable is defined; it may have any actual value,
+ # constrained only by validation.
+ #
+ # Properties may have validation, defaults, and coercion, and have full
+ # support for lazy values.
+ #
+ # @see Chef::Resource.property
+ # @see Chef::DelayedEvaluator
+ #
+ class Property
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. See Chef::Resource.property for
+ # the list of options.
+ #
+ # @example
+ # Property.derive(default: 'hi')
+ #
+ def self.derive(**options)
+ new(**options)
+ end
+
+ #
+ # Create a new property.
+ #
+ # @param options [Hash<Symbol,Object>] Property options, including
+ # control options here, as well as validation options (see
+ # Chef::Mixin::ParamsValidate#validate for a description of validation
+ # options).
+ # @option options [Symbol] :name The name of this property.
+ # @option options [Class] :declared_in The class this property comes from.
+ # @option options [Symbol] :instance_variable_name The instance variable
+ # tied to this property. Must include a leading `@`. Defaults to `@<name>`.
+ # `nil` means the property is opaque and not tied to a specific instance
+ # variable.
+ # @option options [Boolean] :desired_state `true` if this property is part of desired
+ # state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property is part of object
+ # identity. Defaults to `false`.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties) and cached. If not, the value will be frozen with Object#freeze
+ # to prevent users from modifying it in an instance.
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ #
+ def initialize(**options)
+ options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
+ options[:name_property] = options.delete(:name_attribute) if options.has_key?(:name_attribute) && !options.has_key?(:name_property)
+ @options = options
+
+ options[:name] = options[:name].to_sym if options[:name]
+ options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name]
+ end
+
+ #
+ # The name of this property.
+ #
+ # @return [String]
+ #
+ def name
+ options[:name]
+ end
+
+ #
+ # The class this property was defined in.
+ #
+ # @return [Class]
+ #
+ def declared_in
+ options[:declared_in]
+ end
+
+ #
+ # The instance variable associated with this property.
+ #
+ # Defaults to `@<name>`
+ #
+ # @return [Symbol]
+ #
+ def instance_variable_name
+ if options.has_key?(:instance_variable_name)
+ options[:instance_variable_name]
+ elsif name
+ :"@#{name}"
+ end
+ end
+
+ #
+ # The raw default value for this resource.
+ #
+ # Does not coerce or validate the default. Does not evaluate lazy values.
+ #
+ # Defaults to `lazy { name }` if name_property is true; otherwise defaults to
+ # `nil`
+ #
+ def default
+ return options[:default] if options.has_key?(:default)
+ return Chef::DelayedEvaluator.new { name } if name_property?
+ nil
+ end
+
+ #
+ # Whether this is part of the resource's natural identity or not.
+ #
+ # @return [Boolean]
+ #
+ def identity?
+ options[:identity]
+ end
+
+ #
+ # Whether this is part of desired state or not.
+ #
+ # Defaults to true.
+ #
+ # @return [Boolean]
+ #
+ def desired_state?
+ return true if !options.has_key?(:desired_state)
+ options[:desired_state]
+ end
+
+ #
+ # Whether this is name_property or not.
+ #
+ # @return [Boolean]
+ #
+ def name_property?
+ options[:name_property]
+ end
+
+ #
+ # Whether this property has a default value.
+ #
+ # @return [Boolean]
+ #
+ def has_default?
+ options.has_key?(:default) || name_property?
+ end
+
+ #
+ # Whether this property is required or not.
+ #
+ # @return [Boolean]
+ #
+ def required?
+ options[:required]
+ end
+
+ #
+ # Validation options. (See Chef::Mixin::ParamsValidate#validate.)
+ #
+ # @return [Hash<Symbol,Object>]
+ #
+ def validation_options
+ @validation_options ||= options.reject { |k,v|
+ [:declared_in,:name,:instance_variable_name,:desired_state,:identity,:default,:name_property,:coerce,:required].include?(k)
+ }
+ end
+
+ #
+ # Handle the property being called.
+ #
+ # The base implementation does the property get-or-set:
+ #
+ # ```ruby
+ # resource.myprop # get
+ # resource.myprop value # set
+ # ```
+ #
+ # Subclasses may implement this with any arguments they want, as long as
+ # the corresponding DSL calls it correctly.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ # @param value The value to set (or NOT_PASSED if it is a get).
+ #
+ # @return The current value of the property. If it is a `set`, lazy values
+ # will be returned without running, validating or coercing. If it is a
+ # `get`, the non-lazy, coerced, validated value will always be returned.
+ #
+ def call(resource, value=NOT_PASSED)
+ if value == NOT_PASSED
+ return get(resource)
+ end
+
+ # myprop nil is sometimes a get (backcompat)
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ # If you say "my_property nil" and the property explicitly accepts
+ # nil values, we consider this a get.
+ Chef.log_deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.")
+ return get(resource)
+ end
+
+ # Anything else (myprop value) is a set
+ set(resource, value)
+ end
+
+ #
+ # Get the property value from the resource, handling lazy values,
+ # defaults, and validation.
+ #
+ # - If the property's value is lazy, it is evaluated, coerced and validated.
+ # - If the property has no value, and is required, raises ValidationFailed.
+ # - If the property has no value, but has a lazy default, it is evaluated,
+ # coerced and validated. If the evaluated value is frozen, the resulting
+ # - If the property has no value, but has a default, the default value
+ # will be returned and frozen. If the default value is lazy, it will be
+ # evaluated, coerced and validated, and the result stored in the property.
+ # - If the property has no value, but is name_property, `resource.name`
+ # is retrieved, coerced, validated and stored in the property.
+ # - Otherwise, `nil` is returned.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return The value of the property.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property, or if the value is required and not set.
+ #
+ def get(resource)
+ if is_set?(resource)
+ value = get_value(resource)
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+
+ else
+ if has_default?
+ value = default
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ end
+
+ value = coerce(resource, value)
+
+ # We don't validate defaults
+
+ # If the value is mutable (non-frozen), we set it on the instance
+ # so that people can mutate it. (All constant default values are
+ # frozen.)
+ if !value.frozen? && !value.nil?
+ set_value(resource, value)
+ end
+
+ value
+
+ elsif required?
+ raise Chef::Exceptions::ValidationFailed, "#{name} is required"
+ end
+ end
+ end
+
+ #
+ # Set the value of this property in the given resource.
+ #
+ # Non-lazy values are coerced and validated before being set. Coercion
+ # and validation of lazy values is delayed until they are first retrieved.
+ #
+ # @param resource [Chef::Resource] The resource to set this property in.
+ # @param value The value to set.
+ #
+ # @return The value that was set, after coercion (if lazy, still returns
+ # the lazy value)
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def set(resource, value)
+ unless value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ set_value(resource, value)
+ end
+
+ #
+ # Find out whether this property has been set.
+ #
+ # This will be true if:
+ # - The user explicitly set the value
+ # - The property has a default, and the value was retrieved.
+ #
+ # From this point of view, it is worth looking at this as "what does the
+ # user think this value should be." In order words, if the user grabbed
+ # the value, even if it was a default, they probably based calculations on
+ # it. If they based calculations on it and the value changes, the rest of
+ # the world gets inconsistent.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return [Boolean]
+ #
+ def is_set?(resource)
+ value_is_set?(resource)
+ end
+
+ #
+ # Reset the value of this property so that is_set? will return false and the
+ # default will be returned in the future.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ def reset(resource)
+ reset_value(resource)
+ end
+
+ #
+ # Coerce an input value into canonical form for the property.
+ #
+ # After coercion, the value is suitable for storage in the resource.
+ # You must validate values after coercion, however.
+ #
+ # Does no special handling for lazy values.
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ # @param value The value to coerce.
+ #
+ # @return The coerced value.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def coerce(resource, value)
+ if options.has_key?(:coerce)
+ value = exec_in_resource(resource, options[:coerce], value)
+ end
+ value
+ end
+
+ #
+ # Validate a value.
+ #
+ # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as
+ # options.
+ #
+ # @param resource [Chef::Resource] The resource we're validating against
+ # (to provide context for the validate).
+ # @param value The value to validate.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def validate(resource, value)
+ resource.validate({ name => value }, { name => validation_options })
+ end
+
+ #
+ # Derive a new Property that is just like this one, except with some added or
+ # changed options.
+ #
+ # @param options [Hash<Symbol,Object>] List of options that would be passed
+ # to #initialize.
+ #
+ # @return [Property] The new property type.
+ #
+ def derive(**modified_options)
+ Property.new(**options.merge(**modified_options))
+ end
+
+ #
+ # Emit the DSL for this property into the resource class (`declared_in`).
+ #
+ # Creates a getter and setter for the property.
+ #
+ def emit_dsl
+ # We don't create the getter/setter if it's a custom property; we will
+ # be using the existing getter/setter to manipulate it instead.
+ return if !instance_variable_name
+
+ # We prefer this form because the property name won't show up in the
+ # stack trace if you use `define_method`.
+ declared_in.class_eval <<-EOM, __FILE__, __LINE__+1
+ def #{name}(value=NOT_PASSED)
+ self.class.properties[#{name.inspect}].call(self, value)
+ end
+ def #{name}=(value)
+ self.class.properties[#{name.inspect}].set(self, value)
+ end
+ EOM
+ rescue SyntaxError
+ # If the name is not a valid ruby name, we use define_method.
+ resource_class.define_method(name) do |value=NOT_PASSED|
+ self.class.properties[name].call(self, value)
+ end
+ resource_class.define_method("#{name}=") do |value|
+ self.class.properties[name].set(self, value)
+ end
+ end
+
+ protected
+
+ #
+ # The options this Property will use for get/set behavior and validation.
+ #
+ # @see #initialize for a list of valid options.
+ #
+ attr_reader :options
+
+ #
+ # Find out whether this type accepts nil explicitly.
+ #
+ # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply
+ # an empty type.
+ #
+ # These examples accept nil explicitly:
+ # ```ruby
+ # property :a, [ String, nil ]
+ # property :a, [ String, NilClass ]
+ # property :a, [ String, proc { |v| v.nil? } ]
+ # ```
+ #
+ # This does not (because the "is" doesn't exist or doesn't have nil):
+ #
+ # ```ruby
+ # property :x, String
+ # ```
+ #
+ # These do not, even though nil would validate fine (because they do not
+ # have "is"):
+ #
+ # ```ruby
+ # property :a
+ # property :a, equal_to: [ 1, 2, 3, nil ]
+ # property :a, kind_of: [ String, NilClass ]
+ # property :a, respond_to: [ ]
+ # property :a, callbacks: { "a" => proc { |v| v.nil? } }
+ # ```
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ #
+ # @return [Boolean] Whether this value explicitly accepts nil.
+ #
+ # @api private
+ def explicitly_accepts_nil?(resource)
+ options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false)
+ end
+
+ def get_value(resource)
+ if instance_variable_name
+ resource.instance_variable_get(instance_variable_name)
+ else
+ resource.send(name)
+ end
+ end
+
+ def set_value(resource, value)
+ if instance_variable_name
+ resource.instance_variable_set(instance_variable_name, value)
+ else
+ resource.send(name, value)
+ end
+ end
+
+ def value_is_set?(resource)
+ if instance_variable_name
+ resource.instance_variable_defined?(instance_variable_name)
+ else
+ true
+ end
+ end
+
+ def reset_value(resource)
+ if instance_variable_name
+ if value_is_set?(resource)
+ resource.remove_instance_variable(instance_variable_name)
+ end
+ else
+ raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset"
+ end
+ end
+
+ def exec_in_resource(resource, proc, *args)
+ if resource
+ if proc.arity > args.size
+ value = proc.call(resource, *args)
+ else
+ value = resource.instance_exec(*args, &proc)
+ end
+ else
+ value = proc.call
+ end
+
+ if value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+ end
+ end
+end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index e50e374804..3138704a55 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -26,6 +26,7 @@ require 'chef/mixin/powershell_out'
require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
+require 'forwardable'
class Chef
class Provider
@@ -65,6 +66,7 @@ class Chef
@recipe_name = nil
@cookbook_name = nil
+ self.class.include_resource_dsl_module(new_resource)
end
def whyrun_mode?
@@ -119,11 +121,11 @@ class Chef
check_resource_semantics!
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
- if !whyrun_mode? || whyrun_supported?
+ if whyrun_mode? && !whyrun_supported?
+ events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
+ else
load_current_resource
events.resource_current_state_loaded(@new_resource, @action, @current_resource)
- elsif whyrun_mode? && !whyrun_supported?
- events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
end
define_resource_requirements
@@ -136,9 +138,7 @@ class Chef
# we can't execute the action.
# in non-whyrun mode, this will still cause the action to be
# executed normally.
- if whyrun_supported? && !requirements.action_blocked?(@action)
- send("action_#{@action}")
- elsif whyrun_mode?
+ if whyrun_mode? && (!whyrun_supported? || requirements.action_blocked?(@action))
events.resource_bypassed(@new_resource, @action, self)
else
send("action_#{@action}")
@@ -175,14 +175,221 @@ class Chef
converge_actions.add_action(descriptions, &block)
end
+ #
+ # Handle patchy convergence safely.
+ #
+ # - Does *not* call the block if the current_resource's properties match
+ # the properties the user specified on the resource.
+ # - Calls the block if current_resource does not exist
+ # - Calls the block if the user has specified any properties in the resource
+ # whose values are *different* from current_resource.
+ # - Does *not* call the block if why-run is enabled (just prints out text).
+ # - Prints out automatic green text saying what properties have changed.
+ #
+ # @param properties An optional list of property names (symbols). If not
+ # specified, `new_resource.class.state_properties` will be used.
+ # @param converge_block The block to do the converging in.
+ #
+ # @return [Boolean] whether the block was executed.
+ #
+ def converge_if_changed(*properties, &converge_block)
+ if !converge_block
+ raise ArgumentError, "converge_if_changed must be passed a block!"
+ end
+
+ properties = new_resource.class.state_properties.map { |p| p.name } if properties.empty?
+ properties = properties.map { |p| p.to_sym }
+ if current_resource
+ # Collect the list of modified properties
+ specified_properties = properties.select { |property| new_resource.property_is_set?(property) }
+ modified = specified_properties.select { |p| new_resource.send(p) != current_resource.send(p) }
+ if modified.empty?
+ Chef::Log.debug("Skipping update of #{new_resource.to_s}: has not changed any of the specified properties #{specified_properties.map { |p| "#{p}=#{new_resource.send(p).inspect}" }.join(", ")}.")
+ return false
+ end
+
+ # Print the pretty green text and run the block
+ property_size = modified.map { |p| p.size }.max
+ modified = modified.map { |p| " set #{p.to_s.ljust(property_size)} to #{new_resource.send(p).inspect} (was #{current_resource.send(p).inspect})" }
+ converge_by([ "update #{current_resource.identity}" ] + modified, &converge_block)
+
+ else
+ # The resource doesn't exist. Mark that we are *creating* this, and
+ # write down any properties we are setting.
+ property_size = properties.map { |p| p.size }.max
+ created = []
+ properties.each do |property|
+ if new_resource.property_is_set?(property)
+ created << " set #{property.to_s.ljust(property_size)} to #{new_resource.send(property).inspect}"
+ else
+ created << " set #{property.to_s.ljust(property_size)} to #{new_resource.send(property).inspect} (default value)"
+ end
+ end
+
+ converge_by([ "create #{new_resource.identity}" ] + created, &converge_block)
+ end
+ true
+ end
+
def self.provides(short_name, opts={}, &block)
- Chef.set_provider_priority_array(short_name, self, opts, &block)
+ Chef.provider_handler_map.set(short_name, self, opts, &block)
end
def self.provides?(node, resource)
Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
end
+ #
+ # Include attributes, public and protected methods from this Resource in
+ # the provider.
+ #
+ # If this is set to true, delegate methods are included in the provider so
+ # that you can call (for example) `attrname` and it will call
+ # `new_resource.attrname`.
+ #
+ # The actual include does not happen until the first time the Provider
+ # is instantiated (so that we don't have to worry about load order issues).
+ #
+ # @param include_resource_dsl [Boolean] Whether to include resource DSL or
+ # not (defaults to `false`).
+ #
+ def self.include_resource_dsl(include_resource_dsl)
+ @include_resource_dsl = include_resource_dsl
+ end
+
+ # Create the resource DSL module that forwards resource methods to new_resource
+ #
+ # @api private
+ def self.include_resource_dsl_module(resource)
+ if @include_resource_dsl && !defined?(@included_resource_dsl_module)
+ provider_class = self
+ @included_resource_dsl_module = Module.new do
+ extend Forwardable
+ define_singleton_method(:to_s) { "#{resource_class} forwarder module" }
+ define_singleton_method(:inspect) { to_s }
+ # Add a delegator for each explicit property that will get the *current* value
+ # of the property by default instead of the *actual* value.
+ resource.class.properties.each do |name, property|
+ class_eval(<<-EOM, __FILE__, __LINE__)
+ def #{name}(*args, &block)
+ # If no arguments were passed, we process "get" by defaulting
+ # the value to current_resource, not new_resource. This helps
+ # avoid issues where resources accidentally overwrite perfectly
+ # valid stuff with default values.
+ if args.empty? && !block
+ if !new_resource.property_is_set?(__method__) && current_resource
+ return current_resource.public_send(__method__)
+ end
+ end
+ new_resource.public_send(__method__, *args, &block)
+ end
+ EOM
+ end
+ dsl_methods =
+ resource.class.public_instance_methods +
+ resource.class.protected_instance_methods -
+ provider_class.instance_methods -
+ resource.class.properties.keys
+ def_delegators(:new_resource, *dsl_methods)
+ end
+ include @included_resource_dsl_module
+ end
+ end
+
+ # Enables inline evaluation of resources in provider actions.
+ #
+ # Without this option, any resources declared inside the Provider are added
+ # to the resource collection after the current position at the time the
+ # action is executed. Because they are added to the primary resource
+ # collection for the chef run, they can notify other resources outside
+ # the Provider, and potentially be notified by resources outside the Provider
+ # (but this is complicated by the fact that they don't exist until the
+ # provider executes). In this mode, it is impossible to correctly set the
+ # updated_by_last_action flag on the parent Provider resource, since it
+ # executes and returns before its component resources are run.
+ #
+ # With this option enabled, each action creates a temporary run_context
+ # with its own resource collection, evaluates the action's code in that
+ # context, and then converges the resources created. If any resources
+ # were updated, then this provider's new_resource will be marked updated.
+ #
+ # In this mode, resources created within the Provider cannot interact with
+ # external resources via notifies, though notifications to other
+ # resources within the Provider will work. Delayed notifications are executed
+ # at the conclusion of the provider's action, *not* at the end of the
+ # main chef run.
+ #
+ # This mode of evaluation is experimental, but is believed to be a better
+ # set of tradeoffs than the append-after mode, so it will likely become
+ # the default in a future major release of Chef.
+ #
+ def self.use_inline_resources
+ extend InlineResources::ClassMethods
+ include InlineResources
+ end
+
+ # Chef::Provider::InlineResources
+ # Implementation of inline resource convergence for providers. See
+ # Provider.use_inline_resources for a longer explanation.
+ #
+ # This code is restricted to a module so that it can be selectively
+ # applied to providers on an opt-in basis.
+ #
+ # @api private
+ module InlineResources
+
+ # Our run context is a child of the main run context; that gives us a
+ # whole new resource collection and notification set.
+ def initialize(resource, run_context)
+ super(resource, run_context.create_child)
+ end
+
+ # Class methods for InlineResources. Overrides the `action` DSL method
+ # with one that enables inline resource convergence.
+ #
+ # @api private
+ module ClassMethods
+ # Defines an action method on the provider, running the block to
+ # compile the resources, converging them, and then checking if any
+ # were updated (and updating new-resource if so)
+ def action(name, &block)
+ # We first try to create the method using "def method_name", which is
+ # preferred because it actually shows up in stack traces. If that
+ # fails, we try define_method.
+ begin
+ class_eval <<-EOM, __FILE__, __LINE__+1
+ def action_#{name}
+ return_value = compile_action_#{name}
+ Chef::Runner.new(run_context).converge
+ return_value
+ ensure
+ if run_context.resource_collection.any? {|r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ end
+ EOM
+ rescue SyntaxError
+ define_method("action_#{name}") do
+ begin
+ return_value = send("compile_action_#{name}")
+ Chef::Runner.new(run_context).converge
+ return_value
+ ensure
+ if run_context.resource_collection.any? {|r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ end
+ end
+ end
+ # We put the action in its own method so that super() works.
+ define_method("compile_action_#{name}", &block)
+ end
+ end
+
+ require 'chef/dsl/recipe'
+ include Chef::DSL::Recipe::FullDSL
+ end
+
protected
def converge_actions
@@ -200,19 +407,21 @@ class Chef
# manipulating notifies.
converge_by ("evaluate block and run any associated actions") do
- saved_run_context = @run_context
- @run_context = @run_context.dup
- @run_context.resource_collection = Chef::ResourceCollection.new
- instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- @run_context = saved_run_context
+ saved_run_context = run_context
+ begin
+ @run_context = run_context.create_child
+ instance_eval(&block)
+ Chef::Runner.new(run_context).converge
+ ensure
+ @run_context = saved_run_context
+ end
end
end
module DeprecatedLWRPClass
def const_missing(class_name)
if deprecated_constants[class_name.to_sym]
- Chef::Log.deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.")
+ Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.")
deprecated_constants[class_name.to_sym]
else
raise NameError, "uninitialized constant Chef::Provider::#{class_name}"
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
index b6b386e5a8..5f0134443d 100644
--- a/lib/chef/provider/batch.rb
+++ b/lib/chef/provider/batch.rb
@@ -28,6 +28,14 @@ class Chef
super(new_resource, run_context, '.bat')
end
+ def command
+ basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+ interpreter_path = Chef::Util::PathHelper.join(basepath, interpreter)
+
+ "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\""
+ end
+
def flags
@new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c'
end
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 19e7c01ab1..77a0410593 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -201,7 +201,7 @@ class Chef
converge_by("execute migration command #{@new_resource.migration_command}") do
Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
- run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info))
+ shell_out!(@new_resource.migration_command,run_options(:cwd=>release_path, :log_level => :info))
end
end
end
@@ -221,7 +221,7 @@ class Chef
else
converge_by("restart app using command #{@new_resource.restart_command}") do
Chef::Log.info("#{@new_resource} restarting app")
- run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path))
+ shell_out!(@new_resource.restart_command,run_options(:cwd=>@new_resource.current_path))
end
end
end
@@ -373,11 +373,9 @@ class Chef
end
def gem_resource_collection_runner
- gems_collection = Chef::ResourceCollection.new
- gem_packages.each { |rbgem| gems_collection.insert(rbgem) }
- gems_run_context = run_context.dup
- gems_run_context.resource_collection = gems_collection
- Chef::Runner.new(gems_run_context)
+ child_context = run_context.create_child
+ gem_packages.each { |rbgem| child_context.resource_collection.insert(rbgem) }
+ Chef::Runner.new(child_context)
end
def gem_packages
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 4d5423d0e8..8892d3a73d 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -64,7 +64,13 @@ class Chef
is_parent_writable = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
if ::File.exists?(base_dir)
- Chef::FileAccessControl.writable?(base_dir)
+ if Chef::FileAccessControl.writable?(base_dir)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(base_dir, node)
+ Chef::Util::PathHelper.writable_sip_path?(base_dir)
+ else
+ false
+ end
else
is_parent_writable.call(base_dir)
end
@@ -74,7 +80,13 @@ class Chef
# in why run mode & parent directory does not exist no permissions check is required
# If not in why run, permissions must be valid and we rely on prior assertion that dir exists
if !whyrun_mode? || ::File.exists?(parent_directory)
- Chef::FileAccessControl.writable?(parent_directory)
+ if Chef::FileAccessControl.writable?(parent_directory)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node)
+ Chef::Util::PathHelper.writable_sip_path?(@new_resource.path)
+ else
+ false
+ end
else
true
end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 5fa84a21e9..379369ba6e 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -53,7 +53,7 @@ class Chef
requirements.assert(:run) do |a|
a.assertion { supports_dsc_invoke_resource? }
err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."]
- a.failure_message Chef::Exceptions::NoProviderAvailable,
+ a.failure_message Chef::Exceptions::ProviderNotFound,
err
a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."]
a.block_action!
@@ -63,7 +63,7 @@ class Chef
meta_configuration['RefreshMode'] == 'Disabled'
}
err = ["The LCM must have its RefreshMode set to Disabled. "]
- a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
a.block_action!
end
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index a75e68a475..b2432132b7 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -70,7 +70,7 @@ class Chef
"Powershell 4.0 or higher was not detected on your system and is required to use the dsc_script resource.",
]
a.assertion { supports_dsc? }
- a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
a.whyrun err + ["Assuming a previous resource installs Powershell 4.0 or higher."]
a.block_action!
end
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index f877ed2424..5b5c8136f1 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -109,7 +109,7 @@ class Chef
else
# Append is not set so we're resetting the membership of
# the group to the given members.
- members_to_be_added = @new_resource.members
+ members_to_be_added = @new_resource.members.dup
@current_resource.members.each do |member|
# No need to re-add a member if it's present in the new
# list of members
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 468e1ec639..7869917307 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -194,7 +194,7 @@ class Chef
private
def add_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
command << " netmask #{@new_resource.mask}" if @new_resource.mask
command << " metric #{@new_resource.metric}" if @new_resource.metric
command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
@@ -202,7 +202,7 @@ class Chef
end
def enable_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
command << " netmask #{@new_resource.mask}" if @new_resource.mask
command << " metric #{@new_resource.metric}" if @new_resource.metric
command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index b5efbb284d..a96c382a01 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -28,52 +28,10 @@ class Chef
# Base class from which LWRP providers inherit.
class LWRPBase < Provider
- # Chef::Provider::LWRPBase::InlineResources
- # Implementation of inline resource convergence for LWRP providers. See
- # Provider::LWRPBase.use_inline_resources for a longer explanation.
- #
- # This code is restricted to a module so that it can be selectively
- # applied to providers on an opt-in basis.
- module InlineResources
-
- # Class methods for InlineResources. Overrides the `action` DSL method
- # with one that enables inline resource convergence.
- module ClassMethods
- # Defines an action method on the provider, using
- # recipe_eval_with_update_check to execute the given block.
- def action(name, &block)
- define_method("action_#{name}") do
- recipe_eval_with_update_check(&block)
- end
- end
- end
-
- # Executes the given block in a temporary run_context with its own
- # resource collection. After the block is executed, any resources
- # declared inside are converged, and if any are updated, the
- # new_resource will be marked updated.
- def recipe_eval_with_update_check(&block)
- saved_run_context = @run_context
- temp_run_context = @run_context.dup
- @run_context = temp_run_context
- @run_context.resource_collection = Chef::ResourceCollection.new
-
- return_value = instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- return_value
- ensure
- @run_context = saved_run_context
- if temp_run_context.resource_collection.any? {|r| r.updated? }
- new_resource.updated_by_last_action(true)
- end
- end
-
- end
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
- # They are not included by its replacment, Chef::DSL::Recipe, but
+ # They are not included by its replacement, Chef::DSL::Recipe, but
# they may be used in existing LWRPs.
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
@@ -122,38 +80,6 @@ class Chef
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 use_inline_resources
- extend InlineResources::ClassMethods
- include InlineResources
- end
-
# DSL for defining a provider's actions.
def action(name, &block)
define_method("action_#{name}") do
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 2039e9ae51..6bdfd5b867 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -42,13 +42,17 @@ class Chef
end
def action_mount
- unless current_resource.mounted
+ if current_resource.mounted
+ if mount_options_unchanged?
+ Chef::Log.debug("#{new_resource} is already mounted")
+ else
+ action_remount
+ end
+ else
converge_by("mount #{current_resource.device} to #{current_resource.mount_point}") do
mount_fs
Chef::Log.info("#{new_resource} mounted")
end
- else
- Chef::Log.debug("#{new_resource} is already mounted")
end
end
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 4ad7b24c15..510dfde46d 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -32,7 +32,7 @@ class Chef
@new_resource.options.clear
end
if @new_resource.fstype == "auto"
- @new_resource.fstype = nil
+ @new_resource.send(:clear_fstype)
end
end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 9d534ec414..880104bff7 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -142,7 +142,7 @@ class Chef
def action_remove
if removing_package?
description = @new_resource.version ? "version #{@new_resource.version} of " : ""
- converge_by("remove #{description} package #{@current_resource.package_name}") do
+ 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
@@ -491,37 +491,6 @@ class Chef
end
end
- # Set provider priority
- require 'chef/chef_class'
- require 'chef/provider/package/dpkg'
- require 'chef/provider/package/homebrew'
- require 'chef/provider/package/macports'
- require 'chef/provider/package/apt'
- require 'chef/provider/package/yum'
- require 'chef/provider/package/zypper'
- require 'chef/provider/package/portage'
- require 'chef/provider/package/pacman'
- require 'chef/provider/package/ips'
- require 'chef/provider/package/solaris'
- require 'chef/provider/package/smartos'
- require 'chef/provider/package/aix'
- require 'chef/provider/package/paludis'
-
- Chef.set_provider_priority_array :package, [ Homebrew, Macports ], os: "darwin"
-
- Chef.set_provider_priority_array :package, Apt, platform_family: "debian"
- Chef.set_provider_priority_array :package, Yum, platform_family: %w(rhel fedora)
- Chef.set_provider_priority_array :package, Zypper, platform_family: "suse"
- Chef.set_provider_priority_array :package, Portage, platform: "gentoo"
- Chef.set_provider_priority_array :package, Pacman, platform: "arch"
- Chef.set_provider_priority_array :package, Ips, platform: %w(openindiana opensolaris omnios solaris2)
- Chef.set_provider_priority_array :package, Solaris, platform: "nexentacore"
- Chef.set_provider_priority_array :package, Solaris, platform: "solaris2", platform_version: '< 5.11'
-
- Chef.set_provider_priority_array :package, SmartOS, platform: "smartos"
- Chef.set_provider_priority_array :package, Aix, platform: "aix"
- Chef.set_provider_priority_array :package, Paludis, platform: "exherbo"
-
private
def shell_out_with_timeout(*command_args)
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
index b97db9d061..5165f4b4ea 100644
--- a/lib/chef/provider/package/aix.rb
+++ b/lib/chef/provider/package/aix.rb
@@ -26,6 +26,7 @@ class Chef
class Package
class Aix < Chef::Provider::Package
+ provides :package, os: "aix"
provides :bff_package, os: "aix"
include Chef::Mixin::GetSourceFromPackage
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index bd6ed283bf..e109c9966a 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -25,6 +25,7 @@ class Chef
class Package
class Apt < Chef::Provider::Package
+ provides :package, platform_family: "debian"
provides :apt_package, os: "linux"
# return [Hash] mapping of package name to Boolean value
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index a262f1ab1a..67e9b903c6 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -25,8 +25,6 @@ class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
- # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
- DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~:-]+)/
DPKG_INSTALLED = /^Status: install ok installed/
DPKG_VERSION = /^Version: (.+)$/
@@ -54,26 +52,22 @@ class Chef
@source_exists = true
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @new_resource.version(nil)
if @new_resource.source
@source_exists = ::File.exists?(@new_resource.source)
if @source_exists
# Get information from the package if supplied
Chef::Log.debug("#{@new_resource} checking dpkg status")
-
- shell_out_with_timeout("dpkg-deb -W #{@new_resource.source}").stdout.each_line do |line|
- if pkginfo = DPKG_INFO.match(line)
- @current_resource.package_name(pkginfo[1])
- @new_resource.version(pkginfo[2])
- @candidate_version = pkginfo[2]
- end
+ status = shell_out_with_timeout("dpkg-deb -W #{@new_resource.source}")
+ pkginfo = status.stdout.split("\t")
+ unless pkginfo.empty?
+ @current_resource.package_name(pkginfo[0])
+ @candidate_version = pkginfo[1].strip
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
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index beede1c916..e5c45f0a62 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -26,6 +26,7 @@ class Chef
class Package
class Homebrew < Chef::Provider::Package
+ provides :package, os: "darwin", override: true
provides :homebrew_package
include Chef::Mixin::HomebrewUser
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index 4d7f4a3583..96c2e711d4 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -27,6 +27,7 @@ class Chef
class Package
class Ips < Chef::Provider::Package
+ provides :package, platform: %w(openindiana opensolaris omnios solaris2)
provides :ips_package, os: "solaris2"
attr_accessor :virtual
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index e945211540..c7ea71ac8c 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -3,6 +3,7 @@ class Chef
class Package
class Macports < Chef::Provider::Package
+ provides :package, os: "darwin"
provides :macports_package
def load_current_resource
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index f231101390..83fc09c8ae 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -31,6 +31,7 @@ class Chef
class Openbsd < Chef::Provider::Package
provides :package, os: "openbsd"
+ provides :openbsd_package
include Chef::Mixin::ShellOut
include Chef::Mixin::GetSourceFromPackage
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index bf03e54656..01e3a9cc01 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -25,6 +25,7 @@ class Chef
class Package
class Pacman < Chef::Provider::Package
+ provides :package, platform: "arch"
provides :pacman_package, os: "linux"
def load_current_resource
diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb
index 407e0d0110..2d6302515b 100644
--- a/lib/chef/provider/package/paludis.rb
+++ b/lib/chef/provider/package/paludis.rb
@@ -24,6 +24,7 @@ class Chef
class Package
class Paludis < Chef::Provider::Package
+ provides :package, platform: "exherbo"
provides :paludis_package, os: "linux"
def load_current_resource
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index 4ba0160bb0..95782a6774 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -25,6 +25,8 @@ class Chef
class Provider
class Package
class Portage < Chef::Provider::Package
+
+ provides :package, platform: "gentoo"
provides :portage_package
PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index 21c39752d1..c5d52a8384 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -61,7 +61,7 @@ class Chef
Chef::Log.debug("#{@new_resource} checking rpm status")
shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
@current_resource.package_name($1)
@new_resource.version($2)
@candidate_version = $2
@@ -78,7 +78,7 @@ class Chef
@rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
@rpm_status.stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
Chef::Log.debug("#{@new_resource} current version is #{$2}")
@current_resource.version($2)
end
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index b5f7dbdd80..729f755b2a 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -394,7 +394,7 @@ class Chef
end
def is_omnibus?
- if RbConfig::CONFIG['bindir'] =~ %r!/opt/(opscode|chef)/embedded/bin!
+ if RbConfig::CONFIG['bindir'] =~ %r!/(opscode|chef|chefdk)/embedded/bin!
Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
# Omnibus installs to a static path because of linking on unix, find it.
true
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 0d5b801c96..71b8a9b9e1 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -29,6 +29,7 @@ class Chef
class SmartOS < Chef::Provider::Package
attr_accessor :is_virtual_package
+ provides :package, platform: "smartos"
provides :smartos_package, os: "solaris2", platform_family: "smartos"
def load_current_resource
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index 9b10403344..e62f37d27b 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -27,6 +27,8 @@ class Chef
include Chef::Mixin::GetSourceFromPackage
+ provides :package, platform: "nexentacore"
+ provides :package, platform: "solaris2", platform_version: '< 5.11'
provides :solaris_package, os: "solaris2"
# def initialize(*args)
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 85c2ba683c..81454380a3 100644
--- a/lib/chef/provider/package/yum.rb
+++ b/lib/chef/provider/package/yum.rb
@@ -1,6 +1,6 @@
# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +28,7 @@ class Chef
class Package
class Yum < Chef::Provider::Package
+ provides :package, platform_family: %w(rhel fedora)
provides :yum_package, os: "linux"
class RPMUtils
@@ -650,6 +651,8 @@ class Chef
include Chef::Mixin::ShellOut
include Singleton
+ attr_accessor :yum_binary
+
def initialize
@rpmdb = RPMDb.new
@@ -780,7 +783,7 @@ class Chef
end
def python_bin
- yum_executable = which("yum")
+ yum_executable = which(yum_binary)
if yum_executable && shabang?(yum_executable)
extract_interpreter(yum_executable)
else
@@ -979,6 +982,15 @@ class Chef
super
@yum = YumCache.instance
+ @yum.yum_binary = yum_binary
+ end
+
+ def yum_binary
+ @yum_binary ||=
+ begin
+ yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
+ yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
+ end
end
# Extra attributes
@@ -1025,6 +1037,7 @@ class Chef
end
def yum_command(command)
+ command = "#{yum_binary} #{command}"
Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
@@ -1232,7 +1245,7 @@ class Chef
end
pkg_string = pkg_string_bits.join(' ')
Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
else
raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
"and release? (version-release, e.g. 1.84-10.fc6)"
@@ -1241,7 +1254,7 @@ class Chef
def install_package(name, version)
if @new_resource.source
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
else
install_remote_package(name, version)
end
@@ -1289,7 +1302,7 @@ class Chef
"#{n}#{yum_arch(a)}"
end.join(' ')
end
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
if flush_cache[:after]
@yum.reload
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index c2a3ac4ba8..ac42304ffb 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -29,6 +29,7 @@ class Chef
class Package
class Zypper < Chef::Provider::Package
+ provides :package, platform_family: "suse"
provides :zypper_package, os: "linux"
def load_current_resource
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index ed44dee6ae..b876b6d8ee 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -30,8 +30,17 @@ class Chef
end
def action_run
- valid_syntax = validate_script_syntax!
- super if valid_syntax
+ validate_script_syntax!
+ super
+ end
+
+ def command
+ basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+ # Powershell.exe is always in "v1.0" folder (for backwards compatibility)
+ interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter)
+
+ "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\""
end
def flags
@@ -62,30 +71,46 @@ class Chef
def validate_script_syntax!
interpreter_arguments = default_interpreter_flags.join(' ')
Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file |
- user_script_file.puts("{#{@new_resource.code}}")
- user_script_file.close
+ # Wrap the user's code in a PowerShell script block so that
+ # it isn't executed. However, syntactically invalid script
+ # in that block will still trigger a syntax error which is
+ # exactly what we want here -- verify the syntax without
+ # actually running the script.
+ user_code_wrapped_in_powershell_script_block = <<-EOH
+{
+ #{@new_resource.code}
+}
+EOH
+ user_script_file.puts user_code_wrapped_in_powershell_script_block
+ # A .close or explicit .flush required to ensure the file is
+ # written to the file system at this point, which is required since
+ # the intent is to execute the code just written to it.
+ user_script_file.close
validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command #{user_script_file.path}"
- # For consistency with other script resources, allow even syntax errors
- # to be suppressed if the returns attribute would have suppressed it
- # at converge.
- valid_returns = [0]
- specified_returns = @new_resource.returns.is_a?(Integer) ?
- [@new_resource.returns] :
- @new_resource.returns
- valid_returns.concat([1]) if specified_returns.include?(1)
-
- result = shell_out!(validation_command, {returns: valid_returns})
- result.exitstatus == 0
+ # Note that other script providers like bash allow syntax errors
+ # to be suppressed by setting 'returns' to a value that the
+ # interpreter would return as a status code in the syntax
+ # error case. We explicitly don't do this here -- syntax
+ # errors will not be suppressed, since doing so could make
+ # it harder for users to detect / debug invalid scripts.
+
+ # Therefore, the only return value for a syntactically valid
+ # script is 0. If an exception is raised by shellout, this
+ # means a non-zero return and thus a syntactically invalid script.
+
+ with_os_architecture(node, architecture: new_resource.architecture) do
+ shell_out!(validation_command, {returns: [0]})
+ end
end
end
def default_interpreter_flags
- # 'Bypass' is preferable since it doesn't require user input confirmation
- # for files such as PowerShell modules downloaded from the
- # Internet. However, 'Bypass' is not supported prior to
- # PowerShell 3.0, so the fallback is 'Unrestricted'
+ # Execution policy 'Bypass' is preferable since it doesn't require
+ # user input confirmation for files such as PowerShell modules
+ # downloaded from the Internet. However, 'Bypass' is not supported
+ # prior to PowerShell 3.0, so the fallback is 'Unrestricted'
execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
[
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index cd62f7c56f..948fa6c63f 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -64,7 +64,7 @@ class Chef
def values_to_hash(values)
if values
- @name_hash = Hash[values.map { |val| [val[:name], val] }]
+ @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }]
else
@name_hash = {}
end
@@ -100,8 +100,8 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
- current_value = @name_hash[value[:name]]
+ if @name_hash.has_key?(value[:name].downcase)
+ current_value = @name_hash[value[:name].downcase]
unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
converge_by("set value #{value}") do
registry.set_value(@new_resource.key, value)
@@ -122,7 +122,7 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- unless @name_hash.has_key?(value[:name])
+ unless @name_hash.has_key?(value[:name].downcase)
converge_by("create value #{value}") do
registry.set_value(@new_resource.key, value)
end
@@ -133,7 +133,7 @@ class Chef
def action_delete
if registry.key_exists?(@new_resource.key)
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
+ if @name_hash.has_key?(value[:name].downcase)
converge_by("delete value #{value}") do
registry.delete_value(@new_resource.key, value)
end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index eaccce46cf..85ceb5cdae 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.rb
@@ -67,7 +67,7 @@ class Chef
::File::FNM_DOTMATCH)
# Remove current directory and previous directory
- files.reject! do |name|
+ files = files.reject do |name|
basename = Pathname.new(name).basename().to_s
['.', '..'].include?(basename)
end
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 9c523b5e66..e7bb2a76d7 100644
--- a/lib/chef/provider/service.rb
+++ b/lib/chef/provider/service.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,10 @@ class Chef
include Chef::Mixin::Command
+ def supports
+ @supports ||= new_resource.supports.dup
+ end
+
def initialize(new_resource, run_context)
super
@enabled = nil
@@ -34,6 +38,12 @@ class Chef
true
end
+ def load_current_resource
+ supports[:status] = false if supports[:status].nil?
+ supports[:reload] = false if supports[:reload].nil?
+ supports[:restart] = false if supports[:restart].nil?
+ end
+
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
@@ -50,7 +60,7 @@ class Chef
def define_resource_requirements
requirements.assert(:reload) do |a|
- a.assertion { @new_resource.supports[:reload] || @new_resource.reload_command }
+ a.assertion { supports[:reload] || @new_resource.reload_command }
a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
# if a service is not declared to support reload, that won't
# typically change during the course of a run - so no whyrun
@@ -188,29 +198,11 @@ class Chef
require 'chef/provider/service/upstart'
require 'chef/provider/service/debian'
require 'chef/provider/service/invokercd'
- require 'chef/provider/service/freebsd'
- require 'chef/provider/service/openbsd'
- require 'chef/provider/service/solaris'
- require 'chef/provider/service/macosx'
-
- def self.os(os, *providers)
- Chef.set_provider_priority_array(:service, providers, os: os)
- end
- def self.platform_family(platform_family, *providers)
- Chef.set_provider_priority_array(:service, providers, platform_family: platform_family)
- end
-
- os %w(freebsd netbsd), Freebsd
- os %w(openbsd), Openbsd
- os %w(solaris2), Solaris
- os %w(darwin), Macosx
- os %w(linux), Systemd, Insserv, Redhat
-
- platform_family %w(arch), Systemd, Arch
- platform_family %w(gentoo), Systemd, Gentoo
- platform_family %w(debian), Systemd, Upstart, Insserv, Debian, Invokercd
- platform_family %w(rhel fedora suse), Systemd, Insserv, Redhat
+ Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: 'arch'
+ Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: 'gentoo'
+ Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: 'debian'
+ Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w(rhel fedora suse)
end
end
end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 09ed4bbf01..0c95ce2c8e 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -116,7 +116,7 @@ class Chef
end
def is_resource_group?
- so = shell_out!("lssrc -g #{@new_resource.service_name}")
+ so = shell_out("lssrc -g #{@new_resource.service_name}")
if so.exitstatus == 0
Chef::Log.debug("#{@new_resource.service_name} is a group")
@is_resource_group = true
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 01505924cb..7d23e4ac77 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -22,15 +22,13 @@ class Chef
class Provider
class Service
class Debian < Chef::Provider::Service::Init
+ provides :service, platform_family: 'debian' do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
+ end
+
UPDATE_RC_D_ENABLED_MATCHES = /\/rc[\dS].d\/S|not installed/i
UPDATE_RC_D_PRIORITIES = /\/rc([\dS]).d\/([SK])(\d\d)/i
- provides :service, platform_family: "debian"
-
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:debian)
- end
-
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
end
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 6c78f86fe0..78ca0be235 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -99,7 +99,7 @@ class Chef
def restart_service
if new_resource.restart_command
super
- elsif new_resource.supports[:restart]
+ elsif supports[:restart]
shell_out_with_systems_locale!("#{init_command} fastrestart")
else
stop_service
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 3dab920f06..903c55af7a 100644
--- a/lib/chef/provider/service/gentoo.rb
+++ b/lib/chef/provider/service/gentoo.rb
@@ -1,7 +1,7 @@
#
# Author:: Lee Jensen (<ljensen@engineyard.com>)
# Author:: AJ Christensen (<aj@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,9 +26,9 @@ class Chef::Provider::Service::Gentoo < Chef::Provider::Service::Init
provides :service, platform_family: "gentoo"
def load_current_resource
+ supports[:status] = true if supports[:status].nil?
+ supports[:restart] = true if supports[:restart].nil?
- @new_resource.supports[:status] = true
- @new_resource.supports[:restart] = true
@found_script = false
super
diff --git a/lib/chef/provider/service/init.rb b/lib/chef/provider/service/init.rb
index 355e98a0eb..8fe5b0281f 100644
--- a/lib/chef/provider/service/init.rb
+++ b/lib/chef/provider/service/init.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -72,7 +72,7 @@ class Chef
def restart_service
if @new_resource.restart_command
super
- elsif @new_resource.supports[:restart]
+ elsif supports[:restart]
shell_out_with_systems_locale!("#{default_init_command} restart")
else
stop_service
@@ -84,7 +84,7 @@ class Chef
def reload_service
if @new_resource.reload_command
super
- elsif @new_resource.supports[:reload]
+ elsif supports[:reload]
shell_out_with_systems_locale!("#{default_init_command} reload")
end
end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 31965a4bc6..dd01f9ab87 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -24,10 +24,8 @@ class Chef
class Service
class Insserv < Chef::Provider::Service::Init
- provides :service, os: "linux"
-
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
+ provides :service, platform_family: %w(debian rhel fedora suse) do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:insserv)
end
def self.supports?(resource, action)
diff --git a/lib/chef/provider/service/invokercd.rb b/lib/chef/provider/service/invokercd.rb
index 5ff24e0dbb..2b045e0e60 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -23,10 +23,8 @@ class Chef
class Service
class Invokercd < Chef::Provider::Service::Init
- provides :service, platform_family: "debian"
-
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
+ provides :service, platform_family: 'debian', override: true do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:invokercd)
end
def self.supports?(resource, action)
diff --git a/lib/chef/provider/service/macosx.rb b/lib/chef/provider/service/macosx.rb
index 7324822eff..0a8fca4262 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -42,6 +42,10 @@ class Chef
PLIST_DIRS = gather_plist_dirs
+ def this_version_or_newer?(this_version)
+ Gem::Version.new(node['platform_version']) >= Gem::Version.new(this_version)
+ end
+
def load_current_resource
@current_resource = Chef::Resource::MacosxService.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
@@ -56,7 +60,7 @@ class Chef
@console_user = Etc.getlogin
Chef::Log.debug("#{new_resource} console_user: '#{@console_user}'")
cmd = "su "
- param = !node['platform_version'].include?('10.10') ? '-l ' : ''
+ param = this_version_or_newer?('10.10') ? '' : '-l '
@base_user_cmd = cmd + param + "#{@console_user} -c"
# Default LauchAgent session should be Aqua
@session_type = 'Aqua' if @session_type.nil?
diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb
index d509ee10ff..36c9e8141e 100644
--- a/lib/chef/provider/service/openbsd.rb
+++ b/lib/chef/provider/service/openbsd.rb
@@ -26,7 +26,7 @@ class Chef
class Service
class Openbsd < Chef::Provider::Service::Init
- provides :service, os: [ "openbsd" ]
+ provides :service, os: "openbsd"
include Chef::Mixin::ShellOut
@@ -40,11 +40,12 @@ class Chef
@rc_conf = ::File.read(RC_CONF_PATH) rescue ''
@rc_conf_local = ::File.read(RC_CONF_LOCAL_PATH) rescue ''
@init_command = ::File.exist?(rcd_script_path) ? rcd_script_path : nil
- new_resource.supports[:status] = true
new_resource.status_command("#{default_init_command} check")
end
def load_current_resource
+ supports[:status] = true if supports[:status].nil?
+
@current_resource = Chef::Resource::Service.new(new_resource.name)
current_resource.service_name(new_resource.service_name)
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 850953125e..33a9778715 100644
--- a/lib/chef/provider/service/redhat.rb
+++ b/lib/chef/provider/service/redhat.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,24 +23,32 @@ class Chef
class Service
class Redhat < Chef::Provider::Service::Init
- CHKCONFIG_ON = /\d:on/
- CHKCONFIG_MISSING = /No such/
-
- provides :service, platform_family: [ "rhel", "fedora", "suse" ]
+ # @api private
+ attr_accessor :service_missing
+ # @api private
+ attr_accessor :current_run_levels
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
+ provides :service, platform_family: %w(rhel fedora suse) do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
end
+ CHKCONFIG_ON = /\d:on/
+ CHKCONFIG_MISSING = /No such/
+
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
end
def initialize(new_resource, run_context)
super
- @init_command = "/sbin/service #{@new_resource.service_name}"
- @new_resource.supports[:status] = true
+ @init_command = "/sbin/service #{new_resource.service_name}"
@service_missing = false
+ @current_run_levels = []
+ end
+
+ # @api private
+ def run_levels
+ new_resource.run_levels
end
def define_resource_requirements
@@ -49,34 +57,60 @@ class Chef
requirements.assert(:all_actions) do |a|
chkconfig_file = "/sbin/chkconfig"
a.assertion { ::File.exists? chkconfig_file }
- a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!"
+ a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} dbleoes not exist!"
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
a.assertion { !@service_missing }
- a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!"
+ a.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
+ supports[:status] = true if supports[:status].nil?
+
super
if ::File.exists?("/sbin/chkconfig")
- chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1])
- @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+ chkconfig = shell_out!("/sbin/chkconfig --list #{current_resource.service_name}", :returns => [0,1])
+ unless run_levels.nil? or run_levels.empty?
+ all_levels_match = true
+ chkconfig.stdout.split(/\s+/)[1..-1].each do |level|
+ index = level.split(':').first
+ status = level.split(':').last
+ if level =~ CHKCONFIG_ON
+ @current_run_levels << index.to_i
+ all_levels_match = false unless run_levels.include?(index.to_i)
+ else
+ all_levels_match = false if run_levels.include?(index.to_i)
+ end
+ end
+ current_resource.enabled(all_levels_match)
+ else
+ current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+ end
@service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING)
end
- @current_resource
+ current_resource
+ end
+
+ # @api private
+ def levels
+ (run_levels.nil? or run_levels.empty?) ? "" : "--level #{run_levels.join('')} "
end
def enable_service()
- shell_out! "/sbin/chkconfig #{@new_resource.service_name} on"
+ unless run_levels.nil? or run_levels.empty?
+ disable_levels = current_run_levels - run_levels
+ shell_out! "/sbin/chkconfig --level #{disable_levels.join('')} #{new_resource.service_name} off" unless disable_levels.empty?
+ end
+ shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} on"
end
def disable_service()
- shell_out! "/sbin/chkconfig #{@new_resource.service_name} off"
+ shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} off"
end
end
end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index ee403ee163..d295513b42 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -76,7 +76,7 @@ class Chef
end
requirements.assert(:all_actions) do |a|
- a.assertion { @new_resource.status_command or @new_resource.supports[:status] or
+ a.assertion { @new_resource.status_command or supports[:status] or
(!ps_cmd.nil? and !ps_cmd.empty?) }
a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
end
@@ -127,7 +127,7 @@ class Chef
nil
end
- elsif @new_resource.supports[:status]
+ elsif supports[:status]
Chef::Log.debug("#{@new_resource} supports status, running")
begin
if shell_out("#{default_init_command} status").exitstatus == 0
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 9085ffde2e..d41f6248c2 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -24,14 +24,12 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple
include Chef::Mixin::Which
- provides :service, os: "linux"
+ provides :service, os: "linux" do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd)
+ end
attr_accessor :status_check_success
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:systemd)
- end
-
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:systemd)
end
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
index 8d4aa41035..c08a5f8636 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -25,14 +25,13 @@ class Chef
class Provider
class Service
class Upstart < Chef::Provider::Service::Simple
- UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
-
- provides :service, os: "linux"
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
+ provides :service, platform_family: 'debian', override: true do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
end
+ UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
+
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart)
end
@@ -107,7 +106,7 @@ class Chef
Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
begin
- if shell_out!(@new_resource.status_command) == 0
+ if shell_out!(@new_resource.status_command).exitstatus == 0
@current_resource.running true
end
rescue
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
index 7fc680ec85..a231bd509e 100644
--- a/lib/chef/provider/template/content.rb
+++ b/lib/chef/provider/template/content.rb
@@ -39,6 +39,16 @@ class Chef
context = TemplateContext.new(@new_resource.variables)
context[:node] = @run_context.node
context[:template_finder] = template_finder
+
+ # helper variables
+ context[:cookbook_name] = @new_resource.cookbook_name unless context.keys.include?(:coookbook_name)
+ context[:recipe_name] = @new_resource.recipe_name unless context.keys.include?(:recipe_name)
+ context[:recipe_line_string] = @new_resource.source_line unless context.keys.include?(:recipe_line_string)
+ context[:recipe_path] = @new_resource.source_line_file unless context.keys.include?(:recipe_path)
+ context[:recipe_line] = @new_resource.source_line_number unless context.keys.include?(:recipe_line)
+ context[:template_name] = @new_resource.source unless context.keys.include?(:template_name)
+ context[:template_path] = template_location unless context.keys.include?(:template_path)
+
context._extend_modules(@new_resource.helper_modules)
output = context.render_template(template_location)
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index ad92a72a0a..244b11db98 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -23,8 +23,6 @@ require 'etc'
class Chef
class Provider
class User < Chef::Provider
- provides :user
-
include Chef::Mixin::Command
attr_accessor :user_exists, :locked
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
index e600bb2837..62b49bd833 100644
--- a/lib/chef/provider/windows_script.rb
+++ b/lib/chef/provider/windows_script.rb
@@ -23,6 +23,8 @@ class Chef
class Provider
class WindowsScript < Chef::Provider::Script
+ attr_reader :is_forced_32bit
+
protected
include Chef::Mixin::WindowsArchitectureHelper
@@ -36,11 +38,7 @@ class Chef
@is_wow64 = wow64_architecture_override_required?(run_context.node, target_architecture)
- # if the user wants to run the script 32 bit && we are on a 64bit windows system && we are running a 64bit ruby ==> fail
- if ( target_architecture == :i386 ) && node_windows_architecture(run_context.node) == :x86_64 && !is_i386_process_on_x86_64_windows?
- raise Chef::Exceptions::Win32ArchitectureIncorrect,
- "Support for the i386 architecture from a 64-bit Ruby runtime is not yet implemented"
- end
+ @is_forced_32bit = forced_32bit_override_required?(run_context.node, target_architecture)
end
public
diff --git a/lib/chef/provider_resolver.rb b/lib/chef/provider_resolver.rb
index 5bfee343d1..82a24fc078 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -17,7 +17,7 @@
#
require 'chef/exceptions'
-require 'chef/platform/provider_priority_map'
+require 'chef/platform/priority_map'
class Chef
#
@@ -62,12 +62,47 @@ class Chef
maybe_chef_platform_lookup(resource)
end
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
def provided_by?(provider_class)
- prioritized_handlers.include?(provider_class)
+ potential_handlers.include?(provider_class)
+ end
+
+ def enabled_handlers
+ @enabled_handlers ||= potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) }
+ end
+
+ # TODO deprecate this and allow actions to be passed as a filter to
+ # `provides` so we don't have to have two separate things.
+ # @api private
+ def supported_handlers
+ enabled_handlers.select { |handler| handler.supports?(resource, action) }
end
private
+ def potential_handlers
+ handler_map.list(node, resource.resource_name).uniq
+ end
+
+ # The list of handlers, with any in the priority_map moved to the front
+ def prioritized_handlers
+ @prioritized_handlers ||= begin
+ supported_handlers = self.supported_handlers
+ if supported_handlers.empty?
+ # if none of the providers specifically support the resource, we still need to pick one of the providers that are
+ # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then.
+ Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway."
+ supported_handlers = enabled_handlers
+ end
+
+ prioritized = priority_map.list(node, resource.resource_name).flatten(1)
+ prioritized &= supported_handlers # Filter the priority map by the actual enabled handlers
+ prioritized |= supported_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set)
+ prioritized
+ end
+ end
+
# if resource.provider is set, just return one of those objects
def maybe_explicit_provider(resource)
return nil unless resource.provider
@@ -78,27 +113,7 @@ class Chef
def maybe_dynamic_provider_resolution(resource, action)
Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
- # Get all the handlers in the priority bucket
- handlers = prioritized_handlers
-
- # Narrow it down to handlers that return `true` to `provides?`
- # TODO deprecate this and don't bother calling--the fact that they said
- # `provides` should be enough. But we need to do it right now because
- # some classes implement additional handling.
- enabled_handlers = prioritized_handlers.select { |handler| handler.provides?(node, resource) }
-
- # Narrow it down to handlers that return `true` to `supports?`
- # TODO deprecate this and allow actions to be passed as a filter to
- # `provides` so we don't have to have two separate things.
- supported_handlers = enabled_handlers.select { |handler| handler.supports?(resource, action) }
- if supported_handlers.empty?
- # if none of the providers specifically support the resource, we still need to pick one of the providers that are
- # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then.
- Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway."
- handler = enabled_handlers.first
- else
- handler = supported_handlers.first
- end
+ handler = prioritized_handlers.first
if handler
Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}"
@@ -114,13 +129,16 @@ class Chef
Chef::Platform.find_provider_for_node(node, resource)
end
- def provider_priority_map
- Chef::Platform::ProviderPriorityMap.instance
+ def priority_map
+ Chef.provider_priority_map
end
- def prioritized_handlers
- @prioritized_handlers ||=
- provider_priority_map.list_handlers(node, resource.resource_name).flatten(1).uniq
+ def handler_map
+ Chef.provider_handler_map
+ end
+
+ def overrode_provides?(handler)
+ handler.method(:provides?).owner != Chef::Provider.method(:provides?).owner
end
module Deprecated
@@ -129,33 +147,21 @@ class Chef
@providers ||= Chef::Provider.descendants
end
- # this cut looks at if the provider can handle the resource type on the node
def enabled_handlers
- @enabled_handlers ||=
- providers.select do |klass|
- # NB: this is different from resource_resolver which must pass a resource_name
- # FIXME: deprecate this and normalize on passing resource_name here
- klass.provides?(node, resource)
- end.sort {|a,b| a.to_s <=> b.to_s }
- end
-
- # this cut looks at if the provider can handle the specific resource and action
- def supported_handlers
- @supported_handlers ||=
- enabled_handlers.select do |klass|
- klass.supports?(resource, action)
- end
- end
-
- # If there are no providers for a DSL, we search through the
- def prioritized_handlers
- @prioritized_handlers ||= super || begin
- result = providers.select { |handler| handler.provides?(node, resource) }.sort_by(:name)
- if !result.empty?
- Chef::Log.deprecation("#{resource.resource_name.to_sym} is marked as providing DSL #{method_symbol}, but provides #{resource.resource_name.to_sym.inspect} was never called!")
- Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ @enabled_handlers ||= begin
+ handlers = super
+ if handlers.empty?
+ # Look through all providers, and find ones that return true to provides.
+ # Don't bother with ones that don't override provides?, since they
+ # would have been in enabled_handlers already if that were so. (It's a
+ # perf concern otherwise.)
+ handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) }
+ handlers.each do |handler|
+ Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!")
+ Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ end
end
- result
+ handlers
end
end
end
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index b4d37c2d61..262560f754 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -36,14 +36,7 @@ class Chef
# A Recipe object is the context in which Chef recipes are evaluated.
class Recipe
- include Chef::DSL::DataQuery
- include Chef::DSL::PlatformIntrospection
- include Chef::DSL::IncludeRecipe
- include Chef::DSL::Recipe
- include Chef::DSL::RegistryHelper
- include Chef::DSL::RebootPending
- include Chef::DSL::Audit
- include Chef::DSL::Powershell
+ include Chef::DSL::Recipe::FullDSL
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 7fe8a52d95..ee75dec3b9 100644
--- a/lib/chef/resource.rb
+++ b/lib/chef/resource.rb
@@ -1,7 +1,8 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: John Keiser (<jkeiser@chef.io)
+# Copyright:: Copyright (c) 2008-2015 Chef, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,6 +18,7 @@
# limitations under the License.
#
+require 'chef/exceptions'
require 'chef/mixin/params_validate'
require 'chef/dsl/platform_introspection'
require 'chef/dsl/data_query'
@@ -27,6 +29,7 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/guard_interpreter/resource_guard_interpreter'
require 'chef/resource/conditional'
require 'chef/resource/conditional_action_not_nothing'
+require 'chef/resource/action_provider'
require 'chef/resource_collection'
require 'chef/node_map'
require 'chef/node'
@@ -58,8 +61,6 @@ class Chef
include Chef::Mixin::ShellOut
include Chef::Mixin::PowershellOut
- NULL_ARG = Object.new
-
#
# The node the current Chef run is using.
#
@@ -103,7 +104,7 @@ class Chef
# @param run_context The context of the Chef run. Corresponds to #run_context.
#
def initialize(name, run_context=nil)
- name(name)
+ name(name) unless name.nil?
@run_context = run_context
@noop = nil
@before = nil
@@ -132,37 +133,27 @@ class Chef
end
#
- # The name of this particular resource.
- #
- # This special resource attribute is set automatically from the declaration
- # of the resource, e.g.
+ # The list of properties defined on this resource.
#
- # execute 'Vitruvius' do
- # command 'ls'
- # end
+ # Everything defined with `property` is in this list.
#
- # Will set the name to "Vitruvius".
- #
- # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
- #
- # This is also used for resource notifications and subscribes in the same manner.
- #
- # This will coerce any object into a string via #to_s. Arrays are a special case
- # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
- # awkward `package[["foo", "bar"]]` that #to_s would produce.
+ # @param include_superclass [Boolean] `true` to include properties defined
+ # on superclasses; `false` or `nil` to return the list of properties
+ # directly on this class.
#
- # @param name [Object] The name to set, typically a String or Array
- # @return [String] The name of this Resource.
+ # @return [Hash<Symbol,Property>] The list of property names and types.
#
- def name(name=nil)
- if !name.nil?
- if name.is_a?(Array)
- @name = name.join(', ')
+ def self.properties(include_superclass=true)
+ @properties ||= {}
+ if include_superclass
+ if superclass.respond_to?(:properties)
+ superclass.properties.merge(@properties)
else
- @name = name.to_s
+ @properties.dup
end
+ else
+ @properties
end
- @name
end
#
@@ -171,27 +162,24 @@ class Chef
# @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`)
# @return [Array[Symbol]] the list of actions.
#
- attr_accessor :action
def action(arg=nil)
if arg
- if arg.is_a?(Array)
- arg = arg.map { |a| a.to_sym }
- else
- arg = arg.to_sym
- end
- Array(arg).each do |action|
+ arg = Array(arg).map(&:to_sym)
+ arg.each do |action|
validate(
{ action: action },
{ action: { kind_of: Symbol, equal_to: allowed_actions } }
)
end
- self.action = arg
+ @action = arg
else
- # Pull the action from the class if it's not set
- @action || self.class.default_action
+ @action
end
end
+ # Alias for normal assigment syntax.
+ alias_method :action=, :action
+
#
# Sets up a notification that will run a particular action on another resource
# if and when *this* resource is updated by an action.
@@ -480,13 +468,21 @@ class Chef
#
# Get the value of the state attributes in this resource as a hash.
#
+ # Does not include properties that are not set (unless they are identity
+ # properties).
+ #
# @return [Hash{Symbol => Object}] A Hash of attribute => value for the
# Resource class's `state_attrs`.
+ #
def state_for_resource_reporter
- self.class.state_attrs.inject({}) do |state_attrs, attr_name|
- state_attrs[attr_name] = send(attr_name)
- state_attrs
+ state = {}
+ state_properties = self.class.state_properties
+ state_properties.each do |property|
+ if property.identity? || property.is_set?(self)
+ state[property.name] = send(property.name)
+ end
end
+ state
end
#
@@ -499,17 +495,22 @@ class Chef
alias_method :state, :state_for_resource_reporter
#
- # The value of the identity attribute, if declared. Falls back to #name if
- # no identity attribute is declared.
+ # The value of the identity of this resource.
+ #
+ # - If there are no identity properties on the resource, `name` is returned.
+ # - If there is exactly one identity property on the resource, it is returned.
+ # - If there are more than one, they are returned in a hash.
#
- # @return The value of the identity attribute.
+ # @return [Object,Hash<Symbol,Object>] The identity of this resource.
#
def identity
- if identity_attr = self.class.identity_attr
- send(identity_attr)
- else
- name
+ result = {}
+ identity_properties = self.class.identity_properties
+ identity_properties.each do |property|
+ result[property.name] = send(property.name)
end
+ return result.values.first if identity_properties.size == 1
+ result
end
#
@@ -531,9 +532,7 @@ class Chef
#
# Equivalent to #ignore_failure.
#
- def epic_fail(arg=nil)
- ignore_failure(arg)
- end
+ alias :epic_fail :ignore_failure
#
# Make this resource into an exact (shallow) copy of the other resource.
@@ -688,66 +687,393 @@ class Chef
#
# The provider class for this resource.
#
+ # If `action :x do ... end` has been declared on this resource or its
+ # superclasses, this will return the `action_provider_class`.
+ #
# If this is not set, `provider_for_action` will dynamically determine the
# provider.
#
# @param arg [String, Symbol, Class] Sets the provider class for this resource.
# If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the
# provider based on the name.
+ #
# @return The provider class for this resource.
#
+ # @see Chef::Resource.action_provider_class
+ #
def provider(arg=nil)
klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
lookup_provider_constant(arg)
else
arg
end
- set_or_return(:provider, klass, kind_of: [ Class ])
+ set_or_return(:provider, klass, kind_of: [ Class ]) ||
+ self.class.action_provider_class
end
def provider=(arg)
provider(arg)
end
- # Set or return the list of "state attributes" implemented by the Resource
- # subclass. State attributes are attributes that describe the desired state
- # of the system, such as file permissions or ownership. In general, state
- # attributes are attributes that could be populated by examining the state
- # of the system (e.g., File.stat can tell you the permissions on an
- # existing file). Contrarily, attributes that are not "state attributes"
- # usually modify the way Chef itself behaves, for example by providing
- # additional options for a package manager to use when installing a
- # package.
+ #
+ # Create a property on this resource class.
+ #
+ # If a superclass has this property, or if this property has already been
+ # defined by this resource, this will *override* the previous value.
+ #
+ # @param name [Symbol] The name of the property.
+ # @param type [Object,Array<Object>] The type(s) of this property.
+ # If present, this is prepended to the `is` validation option.
+ # @param options [Hash<Symbol,Object>] Validation options.
+ # @option options [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`options[:is].any? { |v| v === value }`).
+ # @option options [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`options[:is].any? { |v| v == value }`)
+ # @option options [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # @option options [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of.
+ # @option options [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches).
+ # @option options [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to.
+ # @option options [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty).
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Boolean] :name_attribute Same as `name_property`.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties).
+ # @option options [Boolean] :desired_state `true` if this property is
+ # part of desired state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property
+ # is part of object identity. Defaults to `false`.
+ #
+ # @example Bare property
+ # property :x
+ #
+ # @example With just a type
+ # property :x, String
+ #
+ # @example With just options
+ # property :x, default: 'hi'
+ #
+ # @example With type and options
+ # property :x, String, default: 'hi'
+ #
+ def self.property(name, type=NOT_PASSED, **options)
+ name = name.to_sym
+
+ options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name)
+ options.merge!(name: name, declared_in: self)
+
+ if type == NOT_PASSED
+ # If a type is not passed, the property derives from the
+ # superclass property (if any)
+ if properties.has_key?(name)
+ property = properties[name].derive(**options)
+ else
+ property = property_type(**options)
+ end
+
+ # If a Property is specified, derive a new one from that.
+ elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property)
+ property = type.derive(**options)
+
+ # If a primitive type was passed, combine it with "is"
+ else
+ if options[:is]
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+ else
+ options[:is] = type
+ end
+ property = property_type(**options)
+ end
+
+ if !options[:default].frozen? && (options[:default].is_a?(Array) || options[:default].is_a?(Hash))
+ Chef.log_deprecation("Property #{self}.#{name} has an array or hash default (#{options[:default]}). This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes. Either freeze the constant with `.freeze` to prevent appending, or use lazy { #{options[:default].inspect} }.")
+ end
+
+ local_properties = properties(false)
+ local_properties[name] = property
+
+ property.emit_dsl
+ end
+
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. see #property for
+ # the list of options.
+ #
+ # @example
+ # property_type(default: 'hi')
+ #
+ def self.property_type(**options)
+ Property.derive(**options)
+ end
+
+ #
+ # The name of this particular resource.
+ #
+ # This special resource attribute is set automatically from the declaration
+ # of the resource, e.g.
+ #
+ # execute 'Vitruvius' do
+ # command 'ls'
+ # end
+ #
+ # Will set the name to "Vitruvius".
+ #
+ # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
+ #
+ # This is also used for resource notifications and subscribes in the same manner.
+ #
+ # This will coerce any object into a string via #to_s. Arrays are a special case
+ # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
+ # awkward `package[["foo", "bar"]]` that #to_s would produce.
+ #
+ # @param name [Object] The name to set, typically a String or Array
+ # @return [String] The name of this Resource.
+ #
+ property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(', ') : v.to_s }, desired_state: false
+
+ #
+ # Whether this property has been set (or whether it has a default that has
+ # been retrieved).
+ #
+ # @param name [Symbol] The name of the property.
+ # @return [Boolean] `true` if the property has been set.
+ #
+ def property_is_set?(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.is_set?(self)
+ end
+
+ #
+ # Clear this property as if it had never been set. It will thereafter return
+ # the default.
+ # been retrieved).
+ #
+ # @param name [Symbol] The name of the property.
+ #
+ def reset_property(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.reset(self)
+ end
+
+ #
+ # Create a lazy value for assignment to a default value.
+ #
+ # @param block The block to run when the value is retrieved.
+ #
+ # @return [Chef::DelayedEvaluator] The lazy value
+ #
+ def self.lazy(&block)
+ DelayedEvaluator.new(&block)
+ end
+
+ #
+ # Get or set the list of desired state properties for this resource.
+ #
+ # State properties are properties that describe the desired state
+ # of the system, such as file permissions or ownership.
+ # In general, state properties are properties that could be populated by
+ # examining the state of the system (e.g., File.stat can tell you the
+ # permissions on an existing file). Contrarily, properties that are not
+ # "state properties" usually modify the way Chef itself behaves, for example
+ # by providing additional options for a package manager to use when
+ # installing a package.
#
# This list is used by the Chef client auditing system to extract
# information from resources to describe changes made to the system.
- def self.state_attrs(*attr_names)
- @state_attrs ||= []
- @state_attrs = attr_names unless attr_names.empty?
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties are added to state_properties by default, and can be turned off
+ # with `desired_state: false`.
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Property>] All properties in desired state.
+ #
+ def self.state_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }.uniq
- # 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
+ local_properties = properties(false)
+ # Add new properties to the list.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, desired_state: true
+ elsif !property.desired_state?
+ self.property name, desired_state: true
+ end
+ end
+
+ # If state_attrs *excludes* something which is currently desired state,
+ # mark it as desired_state: false.
+ local_properties.each do |name,property|
+ if property.desired_state? && !names.include?(name)
+ self.property name, desired_state: false
+ end
+ end
end
+
+ properties.values.select { |property| property.desired_state? }
+ end
+
+ #
+ # Set or return the list of "state properties" implemented by the Resource
+ # subclass.
+ #
+ # Equivalent to calling #state_properties and getting `state_properties.keys`.
+ #
+ # @deprecated Use state_properties.keys instead. Note that when you declare
+ # properties with `property`: properties are added to state_properties by
+ # default, and can be turned off with `desired_state: false`
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Symbol>] All property names with desired state.
+ #
+ def self.state_attrs(*names)
+ state_properties(*names).map { |property| property.name }
end
- # Set or return the "identity attribute" for this resource class. This is
- # generally going to be the "name attribute" for this resource. In other
- # words, the resource type plus this attribute uniquely identify a given
- # bit of state that chef manages. For a File resource, this would be the
- # path, for a package resource, it will be the package name. This will show
- # up in chef-client's audit records as a searchable field.
- def self.identity_attr(attr_name=nil)
- @identity_attr ||= nil
- @identity_attr = attr_name if attr_name
+ #
+ # Set the identity of this resource to a particular set of properties.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # If no properties are marked as identity, "name" is considered the identity.
+ #
+ # @param names [Array<Symbol>] A list of property names to set as the identity.
+ #
+ # @return [Array<Property>] All identity properties.
+ #
+ def self.identity_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }
- # 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
+ # Add or change properties that are not part of the identity.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, identity: true
+ elsif !property.identity?
+ self.property name, identity: true
+ end
+ end
+
+ # If identity_properties *excludes* something which is currently part of
+ # the identity, mark it as identity: false.
+ properties.each do |name,property|
+ if property.identity? && !names.include?(name)
+ self.property name, identity: false
+ end
+ end
end
+
+ result = properties.values.select { |property| property.identity? }
+ result = [ properties[:name] ] if result.empty?
+ result
+ end
+
+ #
+ # Set the identity of this resource to a particular property.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # @param name [Symbol] A list of property names to set as the identity.
+ #
+ # @return [Symbol] The identity property if there is only one; or `nil` if
+ # there are more than one.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_property(name=nil)
+ result = identity_properties(*Array(name))
+ if result.size > 1
+ raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})."
+ end
+ result.first
+ end
+
+ #
+ # Set a property as the "identity attribute" for this resource.
+ #
+ # Identical to calling #identity_property.first.key.
+ #
+ # @param name [Symbol] The name of the property to set.
+ #
+ # @return [Symbol]
+ #
+ # @deprecated `identity_property` should be used instead.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_attr(name=nil)
+ property = identity_property(name)
+ return nil if !property
+ property.name
end
#
@@ -773,8 +1099,8 @@ class Chef
# have.
#
attr_accessor :allowed_actions
- def allowed_actions(value=NULL_ARG)
- if value != NULL_ARG
+ def allowed_actions(value=NOT_PASSED)
+ if value != NOT_PASSED
self.allowed_actions = value
end
@allowed_actions
@@ -885,7 +1211,7 @@ class Chef
# @deprecated Use resource_name instead.
#
def self.dsl_name
- Chef::Log.deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead."
+ Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead."
if name
name = self.name.split('::')[-1]
convert_to_snake_case(name)
@@ -908,29 +1234,23 @@ class Chef
#
# @return [Symbol] The name of this resource type (e.g. `:execute`).
#
- def self.resource_name(name=NULL_ARG)
+ def self.resource_name(name=NOT_PASSED)
# Setter
- if name != NULL_ARG
+ if name != NOT_PASSED
remove_canonical_dsl
# Set the resource_name and call provides
if name
name = name.to_sym
# If our class is not already providing this name, provide it.
- if !Chef::ResourceResolver.list(name).include?(self)
+ if !Chef::ResourceResolver.includes_handler?(name, self)
provides name, canonical: true
end
@resource_name = name
else
@resource_name = nil
end
- else
- # set resource_name automatically if it's not set
- if !instance_variable_defined?(:@resource_name) && self.name
- resource_name convert_to_snake_case(self.name.split('::')[-1])
- end
end
-
@resource_name
end
def self.resource_name=(name)
@@ -938,6 +1258,19 @@ class Chef
end
#
+ # Use the class name as the resource name.
+ #
+ # Munges the last part of the class name from camel case to snake case,
+ # and sets the resource_name to that:
+ #
+ # A::B::BlahDBlah -> blah_d_blah
+ #
+ def self.use_automatic_resource_name
+ automatic_name = convert_to_snake_case(self.name.split('::')[-1])
+ resource_name automatic_name
+ end
+
+ #
# The module where Chef should look for providers for this resource.
# The provider for `MyResource` will be looked up using
# `provider_base::MyResource`. Defaults to `Chef::Provider`.
@@ -955,7 +1288,7 @@ class Chef
#
def self.provider_base(arg=nil)
if arg
- Chef::Log.deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
+ Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
end
@provider_base ||= arg || Chef::Provider
end
@@ -965,7 +1298,7 @@ class Chef
#
# @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
#
- # @return [Arrau<Symbol>] The list of actions, as symbols.
+ # @return [Array<Symbol>] The list of actions, as symbols.
#
def self.allowed_actions(*actions)
@allowed_actions ||=
@@ -974,10 +1307,10 @@ class Chef
else
[ :nothing ]
end
- @allowed_actions |= actions
+ @allowed_actions |= actions.flatten
end
def self.allowed_actions=(value)
- @allowed_actions = value
+ @allowed_actions = value.uniq
end
#
@@ -986,22 +1319,17 @@ class Chef
# Setting default_action will automatially add the action to
# allowed_actions, if it isn't already there.
#
- # Defaults to :nothing.
+ # Defaults to [:nothing].
#
# @param action_name [Symbol,Array<Symbol>] The default action (or series
# of actions) to use.
#
- # @return [Symbol,Array<Symbol>] The default actions for the resource.
+ # @return [Array<Symbol>] The default actions for the resource.
#
- def self.default_action(action_name=NULL_ARG)
- unless action_name.equal?(NULL_ARG)
- if action_name.is_a?(Array)
- @default_action = action_name.map { |arg| arg.to_sym }
- else
- @default_action = action_name.to_sym
- end
-
- self.allowed_actions |= Array(@default_action)
+ def self.default_action(action_name=NOT_PASSED)
+ unless action_name.equal?(NOT_PASSED)
+ @default_action = Array(action_name).map(&:to_sym)
+ self.allowed_actions |= @default_action
end
if @default_action
@@ -1009,11 +1337,132 @@ class Chef
elsif superclass.respond_to?(:default_action)
superclass.default_action
else
- :nothing
+ [:nothing]
end
end
def self.default_action=(action_name)
- default_action(action_name)
+ default_action action_name
+ end
+
+ #
+ # Define an action on this resource.
+ #
+ # The action is defined as a *recipe* block that will be compiled and then
+ # converged when the action is taken (when Resource is converged). The recipe
+ # has access to the resource's attributes and methods, as well as the Chef
+ # recipe DSL.
+ #
+ # Resources in the action recipe may notify and subscribe to other resources
+ # within the action recipe, but cannot notify or subscribe to resources
+ # in the main Chef run.
+ #
+ # Resource actions are *inheritable*: if resource A defines `action :create`
+ # and B is a subclass of A, B gets all of A's actions. Additionally,
+ # resource B can define `action :create` and call `super()` to invoke A's
+ # action code.
+ #
+ # The first action defined (besides `:nothing`) will become the default
+ # action for the resource.
+ #
+ # @param name [Symbol] The action name to define.
+ # @param recipe_block The recipe to run when the action is taken. This block
+ # takes no parameters, and will be evaluated in a new context containing:
+ #
+ # - The resource's public and protected methods (including attributes)
+ # - The Chef Recipe DSL (file, etc.)
+ # - super() referring to the parent version of the action (if any)
+ #
+ # @return The Action class implementing the action
+ #
+ def self.action(action, &recipe_block)
+ action = action.to_sym
+ new_action_provider_class.action(action, &recipe_block)
+ self.allowed_actions += [ action ]
+ default_action action if Array(default_action) == [:nothing]
+ end
+
+ #
+ # Define a method to load up this resource's properties with the current
+ # actual values.
+ #
+ # @param load_block The block to load. Will be run in the context of a newly
+ # created resource with its identity values filled in.
+ #
+ def self.load_current_value(&load_block)
+ define_method(:load_current_value!, &load_block)
+ end
+
+ #
+ # Call this in `load_current_value` to indicate that the value does not
+ # exist and that `current_resource` should therefore be `nil`.
+ #
+ # @raise Chef::Exceptions::CurrentValueDoesNotExist
+ #
+ def current_value_does_not_exist!
+ raise Chef::Exceptions::CurrentValueDoesNotExist
+ end
+
+ #
+ # Get the current actual value of this resource.
+ #
+ # This does not cache--a new value will be returned each time.
+ #
+ # @return A new copy of the resource, with values filled in from the actual
+ # current value.
+ #
+ def current_resource
+ provider = provider_for_action(Array(action).first)
+ if provider.whyrun_mode? && !provider.whyrun_supported?
+ raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run"
+ end
+ provider.load_current_resource
+ provider.current_resource
+ end
+
+ #
+ # The action provider class is an automatic `Provider` created to handle
+ # actions declared by `action :x do ... end`.
+ #
+ # This class will be returned by `resource.provider` if `resource.provider`
+ # is not set. `provider_for_action` will also use this instead of calling
+ # out to `Chef::ProviderResolver`.
+ #
+ # If the user has not declared actions on this class or its superclasses
+ # using `action :x do ... end`, then there is no need for this class and
+ # `action_provider_class` will be `nil`.
+ #
+ # @api private
+ #
+ def self.action_provider_class
+ @action_provider_class ||
+ # If the superclass needed one, then we need one as well.
+ if superclass.respond_to?(:action_provider_class) && superclass.action_provider_class
+ new_action_provider_class
+ end
+ end
+
+ #
+ # Ensure the action provider class actually gets created. This is called
+ # when the user does `action :x do ... end`.
+ #
+ # @api private
+ def self.new_action_provider_class
+ return @action_provider_class if @action_provider_class
+
+ if superclass.respond_to?(:action_provider_class)
+ base_provider = superclass.action_provider_class
+ end
+ base_provider ||= Chef::Provider
+
+ resource_class = self
+ @action_provider_class = Class.new(base_provider) do
+ include ActionProvider
+ define_singleton_method(:to_s) { "#{resource_class} action provider" }
+ def self.inspect
+ to_s
+ end
+ end
+ @action_provider_class
end
#
@@ -1087,7 +1536,7 @@ class Chef
class << self
# back-compat
- # NOTE: that we do not support unregistering classes as descendents like
+ # NOTE: that we do not support unregistering classes as descendants like
# we used to for LWRP unloading because that was horrible and removed in
# Chef-12.
# @deprecated
@@ -1110,8 +1559,13 @@ class Chef
end
def self.inherited(child)
super
- @sorted_descendants = nil
- child.resource_name
+ @@sorted_descendants = nil
+ # set resource_name automatically if it's not set
+ if child.name && !child.resource_name
+ if child.name =~ /^Chef::Resource::(\w+)$/
+ child.resource_name(convert_to_snake_case($1))
+ end
+ end
end
@@ -1143,13 +1597,13 @@ class Chef
remove_canonical_dsl
end
- result = Chef.set_resource_priority_array(name, self, options, &block)
+ result = Chef.resource_handler_map.set(name, self, options, &block)
Chef::DSL::Resources.add_resource_dsl(name)
result
end
- def self.provides?(node, resource)
- Chef::ResourceResolver.resolve(resource, node: node).provided_by?(self)
+ def self.provides?(node, resource_name)
+ Chef::ResourceResolver.new(node, resource_name).provided_by?(self)
end
# Helper for #notifies
@@ -1173,16 +1627,31 @@ class Chef
run_context.delayed_notifications(self)
end
+ def source_line_file
+ if source_line
+ source_line.match(/(.*):(\d+):?.*$/).to_a[1]
+ else
+ nil
+ end
+ end
+
+ def source_line_number
+ if source_line
+ source_line.match(/(.*):(\d+):?.*$/).to_a[2]
+ else
+ nil
+ end
+ end
+
def defined_at
# The following regexp should match these two sourceline formats:
# /some/path/to/file.rb:80:in `wombat_tears'
# C:/some/path/to/file.rb:80 in 1`wombat_tears'
# extracting the path to the source file and the line number.
- (file, line_no) = source_line.match(/(.*):(\d+):?.*$/).to_a[1,2] if source_line
if cookbook_name && recipe_name && source_line
- "#{cookbook_name}::#{recipe_name} line #{line_no}"
+ "#{cookbook_name}::#{recipe_name} line #{source_line_number}"
elsif source_line
- "#{file} line #{line_no}"
+ "#{source_line_file} line #{source_line_number}"
else
"dynamically defined"
end
@@ -1208,7 +1677,8 @@ class Chef
end
def provider_for_action(action)
- provider = Chef::ProviderResolver.new(node, self, action).resolve.new(self, run_context)
+ provider_class = Chef::ProviderResolver.new(node, self, action).resolve
+ provider = provider_class.new(self, run_context)
provider.action = action
provider
end
@@ -1306,57 +1776,11 @@ class Chef
Chef::Resource.send(:remove_const, class_name)
end
- # In order to generate deprecation warnings when you use Chef::Resource::MyLwrp,
- # we make a special subclass (identical in nearly all respects) of the
- # actual LWRP. When you say any of these, a deprecation warning will be
- # generated:
- #
- # - Chef::Resource::MyLwrp.new(...)
- # - resource.is_a?(Chef::Resource::MyLwrp)
- # - resource.kind_of?(Chef::Resource::MyLwrp)
- # - case resource
- # when Chef::Resource::MyLwrp
- # end
- #
- resource_subclass = class_eval <<-EOM, __FILE__, __LINE__+1
- class Chef::Resource::#{class_name} < resource_class
- resource_name nil # we do not actually provide anything
- def initialize(*args, &block)
- Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
- super
- end
- def self.resource_name(*args)
- if args.empty?
- @resource_name ||= superclass.resource_name
- else
- super
- end
- end
- self
- end
- EOM
- # Make case, is_a and kind_of work with the new subclass, for backcompat.
- # Any subclass of Chef::Resource::ResourceClass is already a subclass of resource_class
- # Any subclass of resource_class is considered a subclass of Chef::Resource::ResourceClass
- resource_class.class_eval do
- define_method(:is_a?) do |other|
- other.is_a?(Module) && other === self
- end
- define_method(:kind_of?) do |other|
- other.is_a?(Module) && other === self
- end
+ if !Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Resource.const_set(class_name, resource_class)
+ deprecated_constants[class_name.to_sym] = resource_class
end
- resource_subclass.class_eval do
- define_singleton_method(:===) do |other|
- Chef::Log.deprecation("Using an LWRP by its name (#{class_name}) directly is no longer supported in Chef 13 and will be removed. Use Chef::Resource.resource_for_node(node, name) instead.")
- # resource_subclass is a superclass of all resource_class descendants.
- if self == resource_subclass && other.class <= resource_class
- return true
- end
- super(other)
- end
- end
- deprecated_constants[class_name.to_sym] = resource_subclass
+
end
def self.deprecated_constants
@@ -1380,7 +1804,7 @@ class Chef
def self.remove_canonical_dsl
if @resource_name
- remaining = Chef.resource_priority_map.delete_canonical(@resource_name, self)
+ remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self)
if !remaining
Chef::DSL::Resources.remove_resource_dsl(@resource_name)
end
diff --git a/lib/chef/resource/action_provider.rb b/lib/chef/resource/action_provider.rb
new file mode 100644
index 0000000000..d71b54ef4d
--- /dev/null
+++ b/lib/chef/resource/action_provider.rb
@@ -0,0 +1,69 @@
+#
+# Author:: John Keiser (<jkeiser@chef.io)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/exceptions'
+
+class Chef
+ class Resource
+ module ActionProvider
+ #
+ # If load_current_value! is defined on the resource, use that.
+ #
+ def load_current_resource
+ if new_resource.respond_to?(:load_current_value!)
+ # dup the resource and then reset desired-state properties.
+ current_resource = new_resource.dup
+
+ # We clear desired state in the copy, because it is supposed to be actual state.
+ # We keep identity properties and non-desired-state, which are assumed to be
+ # "control" values like `recurse: true`
+ current_resource.class.properties.each do |name,property|
+ if property.desired_state? && !property.identity? && !property.name_property?
+ property.reset(current_resource)
+ end
+ end
+
+ # Call the actual load_current_value! method. If it raises
+ # CurrentValueDoesNotExist, set current_resource to `nil`.
+ begin
+ # If the user specifies load_current_value do |desired_resource|, we
+ # pass in the desired resource as well as the current one.
+ if current_resource.method(:load_current_value!).arity > 0
+ current_resource.load_current_value!(new_resource)
+ else
+ current_resource.load_current_value!
+ end
+ rescue Chef::Exceptions::CurrentValueDoesNotExist
+ current_resource = nil
+ end
+ end
+
+ @current_resource = current_resource
+ end
+
+ def self.included(other)
+ other.extend(ClassMethods)
+ other.use_inline_resources
+ other.include_resource_dsl true
+ end
+
+ module ClassMethods
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb
index 0c2fdfa819..7e9d21ebd2 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -50,9 +50,9 @@ class Chef
# Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled
# Action could be an array of symbols, but probably won't (think install + enable for a package)
if compile_time.nil?
- Chef::Log.deprecation "#{self} chef_gem compile_time installation is deprecated"
- Chef::Log.deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
- Chef::Log.deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
+ Chef.log_deprecation "#{self} chef_gem compile_time installation is deprecated"
+ Chef.log_deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
+ Chef.log_deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
end
if compile_time || compile_time.nil?
diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb
index 3e5255bced..5df46fff60 100644
--- a/lib/chef/resource/deploy.rb
+++ b/lib/chef/resource/deploy.rb
@@ -27,6 +27,7 @@
# migration_command "rake db:migrate"
# environment "RAILS_ENV" => "production", "OTHER_ENV" => "foo"
# shallow_clone true
+# depth 1
# action :deploy # or :rollback
# restart_command "touch tmp/restart.txt"
# git_ssh_wrapper "wrap-ssh4git.sh"
@@ -74,6 +75,7 @@ class Chef
@remote = "origin"
@enable_submodules = false
@shallow_clone = false
+ @depth = nil
@scm_provider = Chef::Provider::Git
@svn_force_export = false
@additional_remotes = Hash[]
@@ -97,8 +99,12 @@ class Chef
@current_path ||= @deploy_to + "/current"
end
- def depth
- @shallow_clone ? "5" : nil
+ def depth(arg=@shallow_clone ? 5 : nil)
+ set_or_return(
+ :depth,
+ arg,
+ :kind_of => [ Integer ]
+ )
end
# note: deploy_to is your application "meta-root."
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index 2fcf183375..c3602fa60e 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -17,12 +17,14 @@
#
require 'chef/exceptions'
+require 'chef/dsl/powershell'
class Chef
class Resource
class DscScript < Chef::Resource
+ include Chef::DSL::Powershell
- provides :dsc_script, platform: "windows"
+ provides :dsc_script, os: "windows"
default_action :run
diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb
index f1ca0f1883..9b0788fad3 100644
--- a/lib/chef/resource/file/verification.rb
+++ b/lib/chef/resource/file/verification.rb
@@ -106,7 +106,14 @@ class Chef
# We reuse Chef::GuardInterpreter in order to support
# the same set of options that the not_if/only_if blocks do
def verify_command(path, opts)
- command = @command % {:file => path}
+ # First implementation interpolated `file`; docs & RFC claim `path`
+ # is interpolated. Until `file` can be deprecated, interpolate both.
+ Chef.log_deprecation(
+ '%{file} is deprecated in verify command and will not be '\
+ 'supported in Chef 13. Please use %{path} instead.',
+ caller(2..2)[0]
+ ) if @command.include?('%{file}')
+ command = @command % {:file => path, :path => path}
interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts)
interpreter.evaluate
end
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index 8d720dd411..2bf8e1dba8 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -23,6 +23,7 @@ class Chef
class Resource
class IpsPackage < ::Chef::Resource::Package
+ provides :package, os: "solaris2"
provides :ips_package, os: "solaris2"
allowed_actions :install, :remove, :upgrade
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index c486233020..443e0ed819 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -74,19 +74,14 @@ class Chef
resource_class
end
- # Define an attribute on this resource, including optional validation
- # parameters.
- def attribute(attr_name, validation_opts={})
- define_method(attr_name) do |arg=nil|
- set_or_return(attr_name.to_sym, arg, validation_opts)
- end
- end
+ alias :attribute :property
# Adds +action_names+ to the list of valid actions for this resource.
# Does not include superclass's action list when appending.
def actions(*action_names)
+ action_names = action_names.flatten
if !action_names.empty? && !@allowed_actions
- self.allowed_actions = action_names
+ self.allowed_actions = ([ :nothing ] + action_names).uniq
else
allowed_actions(*action_names)
end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 937839b6e1..5843016897 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -16,10 +16,11 @@
# limitations under the License.
#
+require 'chef/resource/package'
+
class Chef
class Resource
class MacportsPackage < Chef::Resource::Package
- provides :package, os: "darwin"
end
end
end
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 79986d127f..a5da0ba329 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -174,6 +174,14 @@ class Chef
)
end
+ private
+
+ # Used by the AIX provider to set fstype to nil.
+ # TODO use property to make nil a valid value for fstype
+ def clear_fstype
+ @fstype = nil
+ end
+
end
end
end
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index f91fdb37e0..9ae8813d69 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -29,17 +29,6 @@ class Chef
include Chef::Mixin::ShellOut
provides :package, os: "openbsd"
-
- def after_created
- assign_provider
- end
-
- private
-
- def assign_provider
- @provider = Chef::Provider::Package::Openbsd
- end
-
end
end
end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index 1c6da75678..5be1c34b89 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -100,8 +100,3 @@ class Chef
end
end
end
-
-require 'chef/chef_class'
-require 'chef/resource/homebrew_package'
-
-Chef.set_resource_priority_array :package, Chef::Resource::HomebrewPackage, os: "darwin"
diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb
index 4ed0d4a4e0..d2e5c4b94c 100644
--- a/lib/chef/resource/registry_key.rb
+++ b/lib/chef/resource/registry_key.rb
@@ -93,7 +93,7 @@ class Chef
raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless [:name,:type,:data].include?(key)
end
raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String)
- raise Argument Error "Type of type => #{v[:name]} should be symbol" unless v[:type].is_a?(Symbol)
+ raise ArgumentError, "Type of type => #{v[:type]} should be symbol" unless v[:type].is_a?(Symbol)
end
@unscrubbed_values = @values
elsif self.instance_variable_defined?(:@values)
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index aa59b543be..6d1b81f9cb 100644
--- a/lib/chef/resource/service.rb
+++ b/lib/chef/resource/service.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
# Author:: Tyler Cloke (<tyler@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -44,7 +44,8 @@ class Chef
@init_command = nil
@priority = nil
@timeout = nil
- @supports = { :restart => false, :reload => false, :status => false }
+ @run_levels = nil
+ @supports = { :restart => nil, :reload => nil, :status => nil }
end
def service_name(arg=nil)
@@ -174,6 +175,13 @@ class Chef
)
end
+ def run_levels(arg=nil)
+ set_or_return(
+ :run_levels,
+ arg,
+ :kind_of => [ Array ] )
+ end
+
def supports(args={})
if args.is_a? Array
args.each { |arg| @supports[arg] = true }
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index 2dc72d5c47..a98fb8b4fa 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -24,10 +24,7 @@ class Chef
class Resource
class SolarisPackage < Chef::Resource::Package
provides :package, os: "solaris2", platform_family: "nexentacore"
- provides :package, os: "solaris2", platform_family: "solaris2" do |node|
- # on >= Solaris 11 we default to IPS packages instead
- node[:platform_version].to_f <= 5.10
- end
+ provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10"
end
end
end
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index 4d54f6051f..50ba13ce65 100644
--- a/lib/chef/resource/yum_package.rb
+++ b/lib/chef/resource/yum_package.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -28,6 +28,7 @@ class Chef
super
@flush_cache = { :before => false, :after => false }
@allow_downgrade = false
+ @yum_binary = nil
end
# Install a specific arch
@@ -57,6 +58,14 @@ class Chef
)
end
+ def yum_binary(arg=nil)
+ set_or_return(
+ :yum_binary,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
end
end
end
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
index 31b39f7e24..67cf134c62 100644
--- a/lib/chef/resource_resolver.rb
+++ b/lib/chef/resource_resolver.rb
@@ -18,6 +18,7 @@
require 'chef/exceptions'
require 'chef/platform/resource_priority_map'
+require 'chef/mixin/convert_to_class_name'
class Chef
class ResourceResolver
@@ -55,7 +56,7 @@ class Chef
attr_reader :resource_name
# @api private
def resource
- Chef::Log.deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.")
+ Chef.log_deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.")
resource_name
end
# @api private
@@ -104,52 +105,80 @@ class Chef
#
# Whether this DSL is provided by the given resource_class.
#
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
+ #
# @api private
def provided_by?(resource_class)
- !prioritized_handlers.include?(resource_class)
+ potential_handlers.include?(resource_class)
+ end
+
+ #
+ # Whether the given handler attempts to provide the resource class at all.
+ #
+ # @api private
+ def self.includes_handler?(resource_name, resource_class)
+ handler_map.list(nil, resource_name).include?(resource_class)
end
protected
+ def self.priority_map
+ Chef.resource_priority_map
+ end
+
+ def self.handler_map
+ Chef.resource_handler_map
+ end
+
def priority_map
- Chef::Platform::ResourcePriorityMap.instance
+ Chef.resource_priority_map
+ end
+
+ def handler_map
+ Chef.resource_handler_map
+ end
+
+ # @api private
+ def potential_handlers
+ handler_map.list(node, resource_name, canonical: canonical).uniq
+ end
+
+ def enabled_handlers
+ potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource_name) }
end
def prioritized_handlers
- @prioritized_handlers ||=
- priority_map.list_handlers(node, resource_name, canonical: canonical)
+ @prioritized_handlers ||= begin
+ enabled_handlers = self.enabled_handlers
+
+ prioritized = priority_map.list(node, resource_name, canonical: canonical).flatten(1)
+ prioritized &= enabled_handlers # Filter the priority map by the actual enabled handlers
+ prioritized |= enabled_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set)
+ prioritized
+ end
+ end
+
+ def overrode_provides?(handler)
+ handler.method(:provides?).owner != Chef::Resource.method(:provides?).owner
end
module Deprecated
# return a deterministically sorted list of Chef::Resource subclasses
- # @deprecated Now prioritized_handlers does its own work (more efficiently)
def resources
Chef::Resource.sorted_descendants
end
- # A list of all handlers
- # @deprecated Now prioritized_handlers does its own work
def enabled_handlers
- Chef::Log.deprecation("enabled_handlers is deprecated. If you are implementing a ResourceResolver, use provided_handlers. If you are not, use Chef::ResourceResolver.list(#{resource_name.inspect}, node: <node>)")
- resources.select { |klass| klass.provides?(node, resource_name) }
- end
-
- protected
-
- # A list of all handlers for the given DSL. If there are no handlers in
- # the map, we still check all descendants of Chef::Resource for backwards
- # compatibility purposes.
- def prioritized_handlers
- @prioritized_handlers ||= super ||
- resources.select do |klass|
- # Don't bother calling provides? unless it's overridden. We already
- # know prioritized_handlers
- if klass.method(:provides?).owner != Chef::Resource && klass.provides?(node, resource_name)
- Chef::Log.deprecation("Resources #{provided.join(", ")} are marked as providing DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
- Chef::Log.deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
- true
- end
+ handlers = super
+ if handlers.empty?
+ handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) }
+ handlers.each do |handler|
+ Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
+ Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
end
+ end
+ handlers
end
end
prepend Deprecated
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 44b05f0cc0..0c8d3d1a48 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -25,118 +25,223 @@ require 'chef/log'
require 'chef/recipe'
require 'chef/run_context/cookbook_compiler'
require 'chef/event_dispatch/events_output_stream'
+require 'forwardable'
class Chef
# == Chef::RunContext
# Value object that loads and tracks the context of a Chef run
class RunContext
+ #
+ # Global state
+ #
- # Chef::Node object for this run
+ #
+ # The node for this run
+ #
+ # @return [Chef::Node]
+ #
attr_reader :node
- # Chef::CookbookCollection for this run
+ #
+ # The set of cookbooks involved in this run
+ #
+ # @return [Chef::CookbookCollection]
+ #
attr_reader :cookbook_collection
+ #
# Resource Definitions for this run. Populated when the files in
# +definitions/+ are evaluated (this is triggered by #load).
+ #
+ # @return [Array[Chef::ResourceDefinition]]
+ #
attr_reader :definitions
- ###
- # These need to be settable so deploy can run a resource_collection
- # independent of any cookbooks via +recipe_eval+
+ #
+ # Event dispatcher for this run.
+ #
+ # @return [Chef::EventDispatch::Dispatcher]
+ #
+ attr_reader :events
+
+ #
+ # Hash of factoids for a reboot request.
+ #
+ # @return [Hash]
+ #
+ attr_accessor :reboot_info
+
+ #
+ # Scoped state
+ #
- # The Chef::ResourceCollection for this run. Populated by evaluating
- # recipes, which is triggered by #load. (See also: CookbookCompiler)
- attr_accessor :resource_collection
+ #
+ # The parent run context.
+ #
+ # @return [Chef::RunContext] The parent run context, or `nil` if this is the
+ # root context.
+ #
+ attr_reader :parent_run_context
+ #
+ # The collection of resources intended to be converged (and able to be
+ # notified).
+ #
+ # @return [Chef::ResourceCollection]
+ #
+ # @see CookbookCompiler
+ #
+ attr_reader :resource_collection
+
+ #
# The list of control groups to execute during the audit phase
- attr_accessor :audits
+ #
+ attr_reader :audits
+
+ #
+ # Notification handling
+ #
+ #
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
- attr_accessor :immediate_notification_collection
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :immediate_notification_collection
+ #
# A Hash containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
- attr_accessor :delayed_notification_collection
-
- # Event dispatcher for this run.
- attr_reader :events
-
- # Hash of factoids for a reboot request.
- attr_reader :reboot_info
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :delayed_notification_collection
# Creates a new Chef::RunContext object and populates its fields. This object gets
# used by the Chef Server to generate a fully compiled recipe list for a node.
#
- # === Returns
- # object<Chef::RunContext>:: Duh. :)
+ # @param node [Chef::Node] The node to run against.
+ # @param cookbook_collection [Chef::CookbookCollection] The cookbooks
+ # involved in this run.
+ # @param events [EventDispatch::Dispatcher] The event dispatcher for this
+ # run.
+ #
def initialize(node, cookbook_collection, events)
@node = node
@cookbook_collection = cookbook_collection
- @resource_collection = Chef::ResourceCollection.new
- @audits = {}
- @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
- @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
- @definitions = Hash.new
- @loaded_recipes = {}
- @loaded_attributes = {}
@events = events
- @reboot_info = {}
- @node.run_context = self
- @node.set_cookbook_attribute
+ node.run_context = self
+ node.set_cookbook_attribute
+
+ @definitions = Hash.new
+ @loaded_recipes_hash = {}
+ @loaded_attributes_hash = {}
+ @reboot_info = {}
@cookbook_compiler = nil
+
+ initialize_child_state
end
- # Triggers the compile phase of the chef run. Implemented by
- # Chef::RunContext::CookbookCompiler
+ #
+ # Triggers the compile phase of the chef run.
+ #
+ # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
+ # @see Chef::RunContext::CookbookCompiler
+ #
def load(run_list_expansion)
@cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
- @cookbook_compiler.compile
+ cookbook_compiler.compile
end
- # Adds an immediate notification to the
- # +immediate_notification_collection+. The notification should be a
- # Chef::Resource::Notification or duck type.
+ #
+ # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
+ #
+ def initialize_child_state
+ @audits = {}
+ @resource_collection = Chef::ResourceCollection.new
+ @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
+ @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
+ end
+
+ #
+ # Adds an immediate notification to the +immediate_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_immediately(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @immediate_notification_collection[nr.name] << notification
+ immediate_notification_collection[nr.name] << notification
else
- @immediate_notification_collection[nr.declared_key] << notification
+ immediate_notification_collection[nr.declared_key] << notification
end
end
- # Adds a delayed notification to the +delayed_notification_collection+. The
- # notification should be a Chef::Resource::Notification or duck type.
+ #
+ # Adds a delayed notification to the +delayed_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_delayed(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @delayed_notification_collection[nr.name] << notification
+ delayed_notification_collection[nr.name] << notification
else
- @delayed_notification_collection[nr.declared_key] << notification
+ delayed_notification_collection[nr.declared_key] << notification
end
end
+ #
+ # Get the list of immediate notifications sent by the given resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def immediate_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @immediate_notification_collection[resource.name]
+ return immediate_notification_collection[resource.name]
else
- return @immediate_notification_collection[resource.declared_key]
+ return immediate_notification_collection[resource.declared_key]
end
end
+ #
+ # Get the list of delayed (end of run) notifications sent by the given
+ # resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def delayed_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @delayed_notification_collection[resource.name]
+ return delayed_notification_collection[resource.name]
else
- return @delayed_notification_collection[resource.declared_key]
+ return delayed_notification_collection[resource.declared_key]
end
end
+ #
+ # Cookbook and recipe loading
+ #
+
+ #
# Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe
+ #
+ # @param recipe_names [Array[String]] The list of recipe names (e.g.
+ # 'my_cookbook' or 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @see DSL::IncludeRecipe#include_recipe
+ #
def include_recipe(*recipe_names, current_cookbook: nil)
result_recipes = Array.new
recipe_names.flatten.each do |recipe_name|
@@ -147,7 +252,21 @@ class Chef
result_recipes
end
+ #
# Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe
+ #
+ # TODO I am sort of confused why we have both this and include_recipe ...
+ # I don't see anything different beyond accepting and returning an
+ # array of recipes.
+ #
+ # @param recipe_names [Array[String]] The recipe name (e.g 'my_cookbook' or
+ # 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @return A truthy value if the load occurred; `false` if already loaded.
+ #
+ # @see DSL::IncludeRecipe#load_recipe
+ #
def load_recipe(recipe_name, current_cookbook: nil)
Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
@@ -175,6 +294,15 @@ ERROR_MESSAGE
end
end
+ #
+ # Load the given recipe from a filename.
+ #
+ # @param recipe_file [String] The recipe filename.
+ #
+ # @return [Chef::Recipe] The loaded recipe.
+ #
+ # @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist.
+ #
def load_recipe_file(recipe_file)
if !File.exist?(recipe_file)
raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
@@ -186,8 +314,19 @@ ERROR_MESSAGE
recipe
end
- # Looks up an attribute file given the +cookbook_name+ and
- # +attr_file_name+. Used by DSL::IncludeAttribute
+ #
+ # Look up an attribute filename.
+ #
+ # @param cookbook_name [String] The cookbook name of the attribute file.
+ # @param attr_file_name [String] The attribute file's name (not path).
+ #
+ # @return [String] The filename.
+ #
+ # @see DSL::IncludeAttribute#include_attribute
+ #
+ # @raise [Chef::Exceptions::CookbookNotFound] If the cookbook could not be found.
+ # @raise [Chef::Exceptions::AttributeNotFound] If the attribute file could not be found.
+ #
def resolve_attribute(cookbook_name, attr_file_name)
cookbook = cookbook_collection[cookbook_name]
raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook
@@ -198,76 +337,152 @@ ERROR_MESSAGE
attribute_filename
end
- # An Array of all recipes that have been loaded. This is stored internally
- # as a Hash, so ordering is predictable.
#
- # Recipe names are given in fully qualified form, e.g., the recipe "nginx"
- # will be given as "nginx::default"
+ # A list of all recipes that have been loaded.
+ #
+ # This is stored internally as a Hash, so ordering is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of recipes in fully qualified form, e.g.
+ # the recipe "nginx" will be given as "nginx::default".
+ #
+ # @see #loaded_recipe? To determine if a particular recipe has been loaded.
#
- # To determine if a particular recipe has been loaded, use #loaded_recipe?
def loaded_recipes
- @loaded_recipes.keys
+ loaded_recipes_hash.keys
end
- # An Array of all attributes files that have been loaded. Stored internally
- # using a Hash, so order is predictable.
#
- # Attribute file names are given in fully qualified form, e.g.,
- # "nginx::default" instead of "nginx".
+ # A list of all attributes files that have been loaded.
+ #
+ # Stored internally using a Hash, so order is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of attribute file names in fully qualified
+ # form, e.g. the "nginx" will be given as "nginx::default".
+ #
def loaded_attributes
- @loaded_attributes.keys
+ loaded_attributes_hash.keys
end
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_recipe?(cookbook, recipe)
- @loaded_recipes.has_key?("#{cookbook}::#{recipe}")
+ loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
end
- # Returns true if +recipe+ has been loaded, false otherwise. Default recipe
- # names are expanded, so `loaded_recipe?("nginx")` and
- # `loaded_recipe?("nginx::default")` are valid and give identical results.
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param recipe [String] Recipe name. "nginx" and "nginx::default" yield
+ # the same results.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_recipe?(recipe)
cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
loaded_fully_qualified_recipe?(cookbook, recipe_name)
end
+ #
+ # Mark a given recipe as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ def loaded_recipe(cookbook, recipe)
+ loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
+ end
+
+ #
+ # Find out if a given attribute file has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_attribute?(cookbook, attribute_file)
- @loaded_attributes.has_key?("#{cookbook}::#{attribute_file}")
+ loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
end
+ #
+ # Mark a given attribute file as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
def loaded_attribute(cookbook, attribute_file)
- @loaded_attributes["#{cookbook}::#{attribute_file}"] = true
+ loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true
end
##
# Cookbook File Introspection
+ #
+ # Find out if the cookbook has the given template.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param template_name [String] Template name.
+ #
+ # @return [Boolean] `true` if the template is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_template_for_node?
+ #
def has_template_in_cookbook?(cookbook, template_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_template_for_node?(node, template_name)
end
+ #
+ # Find out if the cookbook has the given file.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param cb_file_name [String] File name.
+ #
+ # @return [Boolean] `true` if the file is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_cookbook_file_for_node?
+ #
def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_cookbook_file_for_node?(node, cb_file_name)
end
- # Delegates to CookbookCompiler#unreachable_cookbook?
- # Used to raise an error when attempting to load a recipe belonging to a
- # cookbook that is not in the dependency graph. See also: CHEF-4367
+ #
+ # Find out whether the given cookbook is in the cookbook dependency graph.
+ #
+ # @param cookbook_name [String] Cookbook name.
+ #
+ # @return [Boolean] `true` if the cookbook is reachable, `false` otherwise.
+ #
+ # @see Chef::CookbookCompiler#unreachable_cookbook?
def unreachable_cookbook?(cookbook_name)
- @cookbook_compiler.unreachable_cookbook?(cookbook_name)
+ cookbook_compiler.unreachable_cookbook?(cookbook_name)
end
+ #
# Open a stream object that can be printed into and will dispatch to events
#
- # == Arguments
- # options is a hash with these possible options:
- # - name: a string that identifies the stream to the user. Preferably short.
+ # @param name [String] The name of the stream.
+ # @param options [Hash] Other options for the stream.
+ #
+ # @return [EventDispatch::EventsOutputStream] The created stream.
+ #
+ # @yield If a block is passed, it will be run and the stream will be closed
+ # afterwards.
+ # @yieldparam stream [EventDispatch::EventsOutputStream] The created stream.
#
- # Pass a block and the stream will be yielded to it, and close on its own
- # at the end of the block.
- def open_stream(options = {})
- stream = EventDispatch::EventsOutputStream.new(events, options)
+ def open_stream(name: nil, **options)
+ stream = EventDispatch::EventsOutputStream.new(events, name: name, **options)
if block_given?
begin
yield stream
@@ -280,31 +495,137 @@ ERROR_MESSAGE
end
# there are options for how to handle multiple calls to these functions:
- # 1. first call always wins (never change @reboot_info once set).
- # 2. last call always wins (happily change @reboot_info whenever).
+ # 1. first call always wins (never change reboot_info once set).
+ # 2. last call always wins (happily change reboot_info whenever).
# 3. raise an exception on the first conflict.
# 4. disable reboot after this run if anyone ever calls :cancel.
# 5. raise an exception on any second call.
# 6. ?
def request_reboot(reboot_info)
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to #{reboot_info.inspect}"
+ Chef::Log::info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
@reboot_info = reboot_info
end
def cancel_reboot
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to {}"
+ Chef::Log::info "Changing reboot status from #{reboot_info.inspect} to {}"
@reboot_info = {}
end
def reboot_requested?
- @reboot_info.size > 0
+ reboot_info.size > 0
end
- private
+ #
+ # Create a child RunContext.
+ #
+ def create_child
+ ChildRunContext.new(self)
+ end
- def loaded_recipe(cookbook, recipe)
- @loaded_recipes["#{cookbook}::#{recipe}"] = true
+ protected
+
+ attr_reader :cookbook_compiler
+ attr_reader :loaded_attributes_hash
+ attr_reader :loaded_recipes_hash
+
+ module Deprecated
+ ###
+ # These need to be settable so deploy can run a resource_collection
+ # independent of any cookbooks via +recipe_eval+
+ def resource_collection=(value)
+ Chef.log_deprecation("Setting run_context.resource_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @resource_collection = value
+ end
+
+ def audits=(value)
+ Chef.log_deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @audits = value
+ end
+
+ def immediate_notification_collection=(value)
+ Chef.log_deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @immediate_notification_collection = value
+ end
+
+ def delayed_notification_collection=(value)
+ Chef.log_deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @delayed_notification_collection = value
+ end
end
+ prepend Deprecated
+
+
+ #
+ # A child run context. Delegates all root context calls to its parent.
+ #
+ # @api private
+ #
+ class ChildRunContext < RunContext
+ extend Forwardable
+ def_delegators :parent_run_context, *%w(
+ cancel_reboot
+ config
+ cookbook_collection
+ cookbook_compiler
+ definitions
+ events
+ has_cookbook_file_in_cookbook?
+ has_template_in_cookbook?
+ load
+ loaded_attribute
+ loaded_attributes
+ loaded_attributes_hash
+ loaded_fully_qualified_attribute?
+ loaded_fully_qualified_recipe?
+ loaded_recipe
+ loaded_recipe?
+ loaded_recipes
+ loaded_recipes_hash
+ node
+ open_stream
+ reboot_info
+ reboot_info=
+ reboot_requested?
+ request_reboot
+ resolve_attribute
+ unreachable_cookbook?
+ )
+
+ def initialize(parent_run_context)
+ @parent_run_context = parent_run_context
+
+ # We don't call super, because we don't bother initializing stuff we're
+ # going to delegate to the parent anyway. Just initialize things that
+ # every instance needs.
+ initialize_child_state
+ end
+ CHILD_STATE = %w(
+ audits
+ audits=
+ create_child
+ delayed_notification_collection
+ delayed_notification_collection=
+ delayed_notifications
+ immediate_notification_collection
+ immediate_notification_collection=
+ immediate_notifications
+ include_recipe
+ initialize_child_state
+ load_recipe
+ load_recipe_file
+ notifies_immediately
+ notifies_delayed
+ parent_run_context
+ resource_collection
+ resource_collection=
+ ).map { |x| x.to_sym }
+
+ # Verify that we didn't miss any methods
+ missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE
+ if !missing_methods.empty?
+ raise "ERROR: not all methods of RunContext accounted for in ChildRunContext! All methods must be marked as child methods with CHILD_STATE or delegated to the parent_run_context. Missing #{missing_methods.join(", ")}."
+ end
+ end
end
end
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 7cce6fa48c..2824f08f31 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -70,15 +70,16 @@ class Chef
# @return [Array] Array of strings with fully-qualified recipe names
def with_fully_qualified_names_and_version_constraints
self.map do |recipe_name|
- ret = if recipe_name.include?('::')
+ qualified_recipe = if recipe_name.include?('::')
recipe_name
else
"#{recipe_name}::default"
end
- if @versions[recipe_name]
- ret << "@#{@versions[recipe_name]}"
- end
- ret
+
+ version = @versions[recipe_name]
+ qualified_recipe = "#{qualified_recipe}@#{version}" if version
+
+ qualified_recipe
end
end
end
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 717deb63c3..31ebeda86f 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -21,85 +21,45 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
-require 'chef/mixin/api_version_request_handling'
-require 'chef/exceptions'
require 'chef/server_api'
-# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends)
+# TODO
+# DEPRECATION NOTE
+# This class will be replaced by Chef::UserV1 in Chef 13. It is the code to support the User object
+# corrosponding to the Open Source Chef Server 11 and only still exists to support
+# users still on OSC 11.
#
-# In general, Chef::User is no longer expected to support Open Source Chef 11 Server requests.
-# The object that handles those requests has been moved to the Chef::OscUser namespace.
+# Chef::UserV1 now supports Chef Server 12 and will be moved to this namespace in Chef 13.
#
-# Exception: self.list is backwards compatible with OSC 11
+# New development should occur in Chef::UserV1.
+# This file and corrosponding osc_user knife files
+# should be removed once client support for Open Source Chef Server 11 expires.
class Chef
class User
include Chef::Mixin::FromFile
include Chef::Mixin::ParamsValidate
- include Chef::Mixin::ApiVersionRequestHandling
-
- SUPPORTED_API_VERSIONS = [0,1]
def initialize
- @username = nil
- @display_name = nil
- @first_name = nil
- @middle_name = nil
- @last_name = nil
- @email = nil
- @password = nil
+ @name = ''
@public_key = nil
@private_key = nil
- @create_key = nil
@password = nil
+ @admin = false
end
- def chef_root_rest_v0
- @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "0"})
- end
-
- def chef_root_rest_v1
- @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "1"})
+ def chef_rest_v0
+ @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
end
- def username(arg=nil)
- set_or_return(:username, arg,
+ def name(arg=nil)
+ set_or_return(:name, arg,
:regex => /^[a-z0-9\-_]+$/)
end
- def display_name(arg=nil)
- set_or_return(:display_name,
- arg, :kind_of => String)
- end
-
- def first_name(arg=nil)
- set_or_return(:first_name,
- arg, :kind_of => String)
- end
-
- def middle_name(arg=nil)
- set_or_return(:middle_name,
- arg, :kind_of => String)
- end
-
- def last_name(arg=nil)
- set_or_return(:last_name,
- arg, :kind_of => String)
- end
-
- def email(arg=nil)
- set_or_return(:email,
- arg, :kind_of => String)
- end
-
- def password(arg=nil)
- set_or_return(:password,
- arg, :kind_of => String)
- end
-
- def create_key(arg=nil)
- set_or_return(:create_key, arg,
- :kind_of => [TrueClass, FalseClass])
+ def admin(arg=nil)
+ set_or_return(:admin,
+ arg, :kind_of => [TrueClass, FalseClass])
end
def public_key(arg=nil)
@@ -119,17 +79,12 @@ class Chef
def to_hash
result = {
- "username" => @username
+ "name" => @name,
+ "public_key" => @public_key,
+ "admin" => @admin
}
- result["display_name"] = @display_name unless @display_name.nil?
- result["first_name"] = @first_name unless @first_name.nil?
- result["middle_name"] = @middle_name unless @middle_name.nil?
- result["last_name"] = @last_name unless @last_name.nil?
- result["email"] = @email unless @email.nil?
- result["password"] = @password unless @password.nil?
- result["public_key"] = @public_key unless @public_key.nil?
- result["private_key"] = @private_key unless @private_key.nil?
- result["create_key"] = @create_key unless @create_key.nil?
+ result["private_key"] = @private_key if @private_key
+ result["password"] = @password if @password
result
end
@@ -138,86 +93,21 @@ class Chef
end
def destroy
- # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}")
+ chef_rest_v0.delete("users/#{@name}")
end
def create
- # try v1, fail back to v0 if v1 not supported
- begin
- payload = {
- :username => @username,
- :display_name => @display_name,
- :first_name => @first_name,
- :last_name => @last_name,
- :email => @email,
- :password => @password
- }
- payload[:public_key] = @public_key unless @public_key.nil?
- payload[:create_key] = @create_key unless @create_key.nil?
- payload[:middle_name] = @middle_name unless @middle_name.nil?
- raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil?
- new_user = chef_root_rest_v1.post("users", payload)
-
- # get the private_key out of the chef_key hash if it exists
- if new_user['chef_key']
- if new_user['chef_key']['private_key']
- new_user['private_key'] = new_user['chef_key']['private_key']
- end
- new_user['public_key'] = new_user['chef_key']['public_key']
- new_user.delete('chef_key')
- end
- rescue Net::HTTPServerException => e
- # rescue API V0 if 406 and the server supports V0
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
- payload = {
- :username => @username,
- :display_name => @display_name,
- :first_name => @first_name,
- :last_name => @last_name,
- :email => @email,
- :password => @password
- }
- payload[:middle_name] = @middle_name unless @middle_name.nil?
- payload[:public_key] = @public_key unless @public_key.nil?
- # under API V0, the server will create a key pair if public_key isn't passed
- new_user = chef_root_rest_v0.post("users", payload)
- end
-
+ payload = {:name => self.name, :admin => self.admin, :password => self.password }
+ payload[:public_key] = public_key if public_key
+ new_user = chef_rest_v0.post("users", payload)
Chef::User.from_hash(self.to_hash.merge(new_user))
end
def update(new_key=false)
- begin
- payload = {:username => username}
- payload[:display_name] = display_name unless display_name.nil?
- payload[:first_name] = first_name unless first_name.nil?
- payload[:middle_name] = middle_name unless middle_name.nil?
- payload[:last_name] = last_name unless last_name.nil?
- payload[:email] = email unless email.nil?
- payload[:password] = password unless password.nil?
-
- # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned
- payload[:public_key] = public_key unless public_key.nil?
- payload[:private_key] = new_key if new_key
-
- updated_user = chef_root_rest_v1.put("users/#{username}", payload)
- rescue Net::HTTPServerException => e
- if e.response.code == "400"
- # if a 400 is returned but the error message matches the error related to private / public key fields, try V0
- # else, raise the 400
- error = Chef::JSONCompat.from_json(e.response.body)["error"].first
- error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error)
- if error_match.nil?
- raise e
- end
- else # for other types of errors, test for API versioning errors right away
- supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
- raise e unless supported_versions && supported_versions.include?(0)
- end
- updated_user = chef_root_rest_v0.put("users/#{username}", payload)
- end
+ payload = {:name => name, :admin => admin}
+ payload[:private_key] = new_key if new_key
+ payload[:password] = password if password
+ updated_user = chef_rest_v0.put("users/#{name}", payload)
Chef::User.from_hash(self.to_hash.merge(updated_user))
end
@@ -233,47 +123,30 @@ class Chef
end
end
- # Note: remove after API v0 no longer supported by client (and knife command).
def reregister
- begin
- payload = self.to_hash.merge({"private_key" => true})
- reregistered_self = chef_root_rest_v0.put("users/#{username}", payload)
- private_key(reregistered_self["private_key"])
- # only V0 supported for reregister
- rescue Net::HTTPServerException => e
- # if there was a 406 related to versioning, give error explaining that
- # only API version 0 is supported for reregister command
- if e.response.code == "406" && e.response["x-ops-server-api-version"]
- version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
- min_version = version_header["min_version"]
- max_version = version_header["max_version"]
- error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
- raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
- else
- raise e
- end
- end
+ reregistered_self = chef_rest_v0.put("users/#{name}", { :name => name, :admin => admin, :private_key => true })
+ private_key(reregistered_self["private_key"])
self
end
def to_s
- "user[#{@username}]"
+ "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.username user_hash['username']
- user.display_name user_hash['display_name'] if user_hash.key?('display_name')
- user.first_name user_hash['first_name'] if user_hash.key?('first_name')
- user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name')
- user.last_name user_hash['last_name'] if user_hash.key?('last_name')
- user.email user_hash['email'] if user_hash.key?('email')
- user.password user_hash['password'] if user_hash.key?('password')
- user.public_key user_hash['public_key'] if user_hash.key?('public_key')
+ user.name user_hash['name']
user.private_key user_hash['private_key'] if user_hash.key?('private_key')
- user.create_key user_hash['create_key'] if user_hash.key?('create_key')
+ user.password user_hash['password'] if user_hash.key?('password')
+ user.public_key user_hash['public_key']
+ user.admin user_hash['admin']
user
end
@@ -286,19 +159,12 @@ class Chef
end
def self.list(inflate=false)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users')
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get('users')
users = if response.is_a?(Array)
- # EC 11 / CS 12 V0, V1
- # GET /organizations/<org>/users
- transform_list_response(response)
- else
- # OSC 11
- # GET /users
- # EC 11 / CS 12 V0, V1
- # GET /users
- response # OSC
- end
-
+ transform_ohc_list_response(response) # OHC/OPC
+ else
+ response # OSC
+ end
if inflate
users.inject({}) do |user_map, (name, _url)|
user_map[name] = Chef::User.load(name)
@@ -309,9 +175,8 @@ class Chef
end
end
- def self.load(username)
- # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}")
+ def self.load(name)
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get("users/#{name}")
Chef::User.from_hash(response)
end
@@ -319,7 +184,7 @@ class Chef
# [ { "user" => { "username" => USERNAME }}, ...]
# into the form
# { "USERNAME" => "URI" }
- def self.transform_list_response(response)
+ def self.transform_ohc_list_response(response)
new_response = Hash.new
response.each do |u|
name = u['user']['username']
@@ -328,7 +193,6 @@ class Chef
new_response
end
- private_class_method :transform_list_response
-
+ private_class_method :transform_ohc_list_response
end
end
diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb
new file mode 100644
index 0000000000..31cb0576a2
--- /dev/null
+++ b/lib/chef/user_v1.rb
@@ -0,0 +1,335 @@
+#
+# Author:: Steven Danna (steve@opscode.com)
+# Copyright:: Copyright 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'chef/config'
+require 'chef/mixin/params_validate'
+require 'chef/mixin/from_file'
+require 'chef/mash'
+require 'chef/json_compat'
+require 'chef/search/query'
+require 'chef/mixin/api_version_request_handling'
+require 'chef/exceptions'
+require 'chef/server_api'
+
+# OSC 11 BACKWARDS COMPATIBILITY NOTE (remove after OSC 11 support ends)
+#
+# In general, Chef::UserV1 is no longer expected to support Open Source Chef 11 Server requests.
+# The object that handles those requests remain in the Chef::User namespace.
+# This code will be moved to the Chef::User namespace as of Chef 13.
+#
+# Exception: self.list is backwards compatible with OSC 11
+class Chef
+ class UserV1
+
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+ include Chef::Mixin::ApiVersionRequestHandling
+
+ SUPPORTED_API_VERSIONS = [0,1]
+
+ def initialize
+ @username = nil
+ @display_name = nil
+ @first_name = nil
+ @middle_name = nil
+ @last_name = nil
+ @email = nil
+ @password = nil
+ @public_key = nil
+ @private_key = nil
+ @create_key = nil
+ @password = nil
+ end
+
+ def chef_root_rest_v0
+ @chef_root_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "0"})
+ end
+
+ def chef_root_rest_v1
+ @chef_root_rest_v1 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root], {:api_version => "1"})
+ end
+
+ def username(arg=nil)
+ set_or_return(:username, arg,
+ :regex => /^[a-z0-9\-_]+$/)
+ end
+
+ def display_name(arg=nil)
+ set_or_return(:display_name,
+ arg, :kind_of => String)
+ end
+
+ def first_name(arg=nil)
+ set_or_return(:first_name,
+ arg, :kind_of => String)
+ end
+
+ def middle_name(arg=nil)
+ set_or_return(:middle_name,
+ arg, :kind_of => String)
+ end
+
+ def last_name(arg=nil)
+ set_or_return(:last_name,
+ arg, :kind_of => String)
+ end
+
+ def email(arg=nil)
+ set_or_return(:email,
+ arg, :kind_of => String)
+ end
+
+ def password(arg=nil)
+ set_or_return(:password,
+ arg, :kind_of => String)
+ end
+
+ def create_key(arg=nil)
+ set_or_return(:create_key, arg,
+ :kind_of => [TrueClass, FalseClass])
+ end
+
+ def public_key(arg=nil)
+ set_or_return(:public_key,
+ arg, :kind_of => String)
+ end
+
+ def private_key(arg=nil)
+ set_or_return(:private_key,
+ arg, :kind_of => String)
+ end
+
+ def password(arg=nil)
+ set_or_return(:password,
+ arg, :kind_of => String)
+ end
+
+ def to_hash
+ result = {
+ "username" => @username
+ }
+ result["display_name"] = @display_name unless @display_name.nil?
+ result["first_name"] = @first_name unless @first_name.nil?
+ result["middle_name"] = @middle_name unless @middle_name.nil?
+ result["last_name"] = @last_name unless @last_name.nil?
+ result["email"] = @email unless @email.nil?
+ result["password"] = @password unless @password.nil?
+ result["public_key"] = @public_key unless @public_key.nil?
+ result["private_key"] = @private_key unless @private_key.nil?
+ result["create_key"] = @create_key unless @create_key.nil?
+ result
+ end
+
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def destroy
+ # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
+ Chef::REST.new(Chef::Config[:chef_server_url]).delete("users/#{@username}")
+ end
+
+ def create
+ # try v1, fail back to v0 if v1 not supported
+ begin
+ payload = {
+ :username => @username,
+ :display_name => @display_name,
+ :first_name => @first_name,
+ :last_name => @last_name,
+ :email => @email,
+ :password => @password
+ }
+ payload[:public_key] = @public_key unless @public_key.nil?
+ payload[:create_key] = @create_key unless @create_key.nil?
+ payload[:middle_name] = @middle_name unless @middle_name.nil?
+ raise Chef::Exceptions::InvalidUserAttribute, "You cannot set both public_key and create_key for create." if !@create_key.nil? && !@public_key.nil?
+ new_user = chef_root_rest_v1.post("users", payload)
+
+ # get the private_key out of the chef_key hash if it exists
+ if new_user['chef_key']
+ if new_user['chef_key']['private_key']
+ new_user['private_key'] = new_user['chef_key']['private_key']
+ end
+ new_user['public_key'] = new_user['chef_key']['public_key']
+ new_user.delete('chef_key')
+ end
+ rescue Net::HTTPServerException => e
+ # rescue API V0 if 406 and the server supports V0
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+ payload = {
+ :username => @username,
+ :display_name => @display_name,
+ :first_name => @first_name,
+ :last_name => @last_name,
+ :email => @email,
+ :password => @password
+ }
+ payload[:middle_name] = @middle_name unless @middle_name.nil?
+ payload[:public_key] = @public_key unless @public_key.nil?
+ # under API V0, the server will create a key pair if public_key isn't passed
+ new_user = chef_root_rest_v0.post("users", payload)
+ end
+
+ Chef::UserV1.from_hash(self.to_hash.merge(new_user))
+ end
+
+ def update(new_key=false)
+ begin
+ payload = {:username => username}
+ payload[:display_name] = display_name unless display_name.nil?
+ payload[:first_name] = first_name unless first_name.nil?
+ payload[:middle_name] = middle_name unless middle_name.nil?
+ payload[:last_name] = last_name unless last_name.nil?
+ payload[:email] = email unless email.nil?
+ payload[:password] = password unless password.nil?
+
+ # API V1 will fail if these key fields are defined, and try V0 below if relevant 400 is returned
+ payload[:public_key] = public_key unless public_key.nil?
+ payload[:private_key] = new_key if new_key
+
+ updated_user = chef_root_rest_v1.put("users/#{username}", payload)
+ rescue Net::HTTPServerException => e
+ if e.response.code == "400"
+ # if a 400 is returned but the error message matches the error related to private / public key fields, try V0
+ # else, raise the 400
+ error = Chef::JSONCompat.from_json(e.response.body)["error"].first
+ error_match = /Since Server API v1, all keys must be updated via the keys endpoint/.match(error)
+ if error_match.nil?
+ raise e
+ end
+ else # for other types of errors, test for API versioning errors right away
+ supported_versions = server_client_api_version_intersection(e, SUPPORTED_API_VERSIONS)
+ raise e unless supported_versions && supported_versions.include?(0)
+ end
+ updated_user = chef_root_rest_v0.put("users/#{username}", payload)
+ end
+ Chef::UserV1.from_hash(self.to_hash.merge(updated_user))
+ end
+
+ def save(new_key=false)
+ begin
+ create
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ update(new_key)
+ else
+ raise e
+ end
+ end
+ end
+
+ # Note: remove after API v0 no longer supported by client (and knife command).
+ def reregister
+ begin
+ payload = self.to_hash.merge({"private_key" => true})
+ reregistered_self = chef_root_rest_v0.put("users/#{username}", payload)
+ private_key(reregistered_self["private_key"])
+ # only V0 supported for reregister
+ rescue Net::HTTPServerException => e
+ # if there was a 406 related to versioning, give error explaining that
+ # only API version 0 is supported for reregister command
+ if e.response.code == "406" && e.response["x-ops-server-api-version"]
+ version_header = Chef::JSONCompat.from_json(e.response["x-ops-server-api-version"])
+ min_version = version_header["min_version"]
+ max_version = version_header["max_version"]
+ error_msg = reregister_only_v0_supported_error_msg(max_version, min_version)
+ raise Chef::Exceptions::OnlyApiVersion0SupportedForAction.new(error_msg)
+ else
+ raise e
+ end
+ end
+ self
+ end
+
+ def to_s
+ "user[#{@username}]"
+ end
+
+ # Class Methods
+
+ def self.from_hash(user_hash)
+ user = Chef::UserV1.new
+ user.username user_hash['username']
+ user.display_name user_hash['display_name'] if user_hash.key?('display_name')
+ user.first_name user_hash['first_name'] if user_hash.key?('first_name')
+ user.middle_name user_hash['middle_name'] if user_hash.key?('middle_name')
+ user.last_name user_hash['last_name'] if user_hash.key?('last_name')
+ user.email user_hash['email'] if user_hash.key?('email')
+ user.password user_hash['password'] if user_hash.key?('password')
+ user.public_key user_hash['public_key'] if user_hash.key?('public_key')
+ user.private_key user_hash['private_key'] if user_hash.key?('private_key')
+ user.create_key user_hash['create_key'] if user_hash.key?('create_key')
+ user
+ end
+
+ def self.from_json(json)
+ Chef::UserV1.from_hash(Chef::JSONCompat.from_json(json))
+ end
+
+ class << self
+ alias_method :json_create, :from_json
+ end
+
+ def self.list(inflate=false)
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get('users')
+ users = if response.is_a?(Array)
+ # EC 11 / CS 12 V0, V1
+ # GET /organizations/<org>/users
+ transform_list_response(response)
+ else
+ # OSC 11
+ # GET /users
+ # EC 11 / CS 12 V0, V1
+ # GET /users
+ response # OSC
+ end
+
+ if inflate
+ users.inject({}) do |user_map, (name, _url)|
+ user_map[name] = Chef::UserV1.load(name)
+ user_map
+ end
+ else
+ users
+ end
+ end
+
+ def self.load(username)
+ # will default to the current API version (Chef::Authenticator::DEFAULT_SERVER_API_VERSION)
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get("users/#{username}")
+ Chef::UserV1.from_hash(response)
+ end
+
+ # Gross. Transforms an API response in the form of:
+ # [ { "user" => { "username" => USERNAME }}, ...]
+ # into the form
+ # { "USERNAME" => "URI" }
+ def self.transform_list_response(response)
+ new_response = Hash.new
+ response.each do |u|
+ name = u['user']['username']
+ new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}"
+ end
+ new_response
+ end
+
+ private_class_method :transform_list_response
+
+ end
+end
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/util/powershell/ps_credential.rb
index 01f8c27b6c..3f4558a77c 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/lib/chef/util/powershell/ps_credential.rb
@@ -29,6 +29,10 @@ class Chef::Util::Powershell
"New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
end
+ def to_s
+ to_psobject
+ end
+
private
def encrypt(str)
diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb
index 924bd392f9..2085747eb9 100644
--- a/lib/chef/util/windows/net_group.rb
+++ b/lib/chef/util/windows/net_group.rb
@@ -1,106 +1,85 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright (c) 2010 VMware, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/util/windows'
-
-#wrapper around a subset of the NetGroup* APIs.
-#nothing Chef specific, but not complete enough to be its own gem, so util for now.
-class Chef::Util::Windows::NetGroup < Chef::Util::Windows
-
- private
-
- def pack_str(s)
- [str_to_ptr(s)].pack('L')
- end
-
- def modify_members(members, func)
- buffer = 0.chr * (members.size * PTR_SIZE)
- members.each_with_index do |member,offset|
- buffer[offset*PTR_SIZE,PTR_SIZE] = pack_str(multi_to_wide(member))
- end
- rc = func.call(nil, @name, 3, buffer, members.size)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
- end
-
- public
-
- def initialize(groupname)
- @name = multi_to_wide(groupname)
- end
-
- def local_get_members
- group_members = []
- handle = 0.chr * PTR_SIZE
- rc = ERROR_MORE_DATA
-
- while rc == ERROR_MORE_DATA
- ptr = 0.chr * PTR_SIZE
- nread = 0.chr * PTR_SIZE
- total = 0.chr * PTR_SIZE
-
- rc = NetLocalGroupGetMembers.call(nil, @name, 0, ptr, -1,
- nread, total, handle)
- if (rc == NERR_Success) || (rc == ERROR_MORE_DATA)
- ptr = ptr.unpack('L')[0]
- nread = nread.unpack('i')[0]
- members = 0.chr * (nread * PTR_SIZE ) #nread * sizeof(LOCALGROUP_MEMBERS_INFO_0)
- memcpy(members, ptr, members.size)
-
- # 1 pointer field in LOCALGROUP_MEMBERS_INFO_0, offset 0 is lgrmi0_sid
- nread.times do |i|
- sid_address = members[i * PTR_SIZE, PTR_SIZE].unpack('L')[0]
- sid_ptr = FFI::Pointer.new(sid_address)
- member_sid = Chef::ReservedNames::Win32::Security::SID.new(sid_ptr)
- group_members << member_sid.to_s
- end
- NetApiBufferFree(ptr)
- else
- raise ArgumentError, get_last_error(rc)
- end
- end
- group_members
- end
-
- def local_add
- rc = NetLocalGroupAdd.call(nil, 0, pack_str(@name), nil)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
- end
-
- def local_set_members(members)
- modify_members(members, NetLocalGroupSetMembers)
- end
-
- def local_add_members(members)
- modify_members(members, NetLocalGroupAddMembers)
- end
-
- def local_delete_members(members)
- modify_members(members, NetLocalGroupDelMembers)
- end
-
- def local_delete
- rc = NetLocalGroupDel.call(nil, @name)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
- end
-end
+#
+# Author:: Doug MacEachern (<dougm@vmware.com>)
+# Copyright:: Copyright (c) 2010 VMware, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/util/windows'
+require 'chef/win32/net'
+
+#wrapper around a subset of the NetGroup* APIs.
+class Chef::Util::Windows::NetGroup
+
+ private
+
+ def groupname
+ @groupname
+ end
+
+ public
+
+ def initialize(groupname)
+ @groupname = groupname
+ end
+
+ def local_get_members
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_get_members(nil, groupname)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_add
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_add(nil, groupname)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_set_members(members)
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_set_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_add_members(members)
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_add_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+
+ def local_delete_members(members)
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_del_members(nil, groupname, members)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+
+ end
+
+ def local_delete
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_del(nil, groupname)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
+ end
+ end
+end
diff --git a/lib/chef/util/windows/net_use.rb b/lib/chef/util/windows/net_use.rb
index 62d7e169dc..b94576e702 100644
--- a/lib/chef/util/windows/net_use.rb
+++ b/lib/chef/util/windows/net_use.rb
@@ -21,61 +21,18 @@
#see also cmd.exe: net use /?
require 'chef/util/windows'
+require 'chef/win32/net'
class Chef::Util::Windows::NetUse < Chef::Util::Windows
-
- private
-
- USE_NOFORCE = 0
- USE_FORCE = 1
- USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
-
- USE_INFO_2 = [
- [:local, nil],
- [:remote, nil],
- [:password, nil],
- [:status, 0],
- [:asg_type, 0],
- [:refcount, 0],
- [:usecount, 0],
- [:username, nil],
- [:domainname, nil]
- ]
-
- USE_INFO_2_TEMPLATE =
- USE_INFO_2.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join
-
- SIZEOF_USE_INFO_2 = #sizeof(USE_INFO_2)
- USE_INFO_2.inject(0) do |sum, item|
- sum + (item[1].class == Fixnum ? 4 : PTR_SIZE)
- end
-
- def use_info_2(args)
- USE_INFO_2.collect { |field|
- args.include?(field[0]) ? args[field[0]] : field[1]
- }
- end
-
- def use_info_2_pack(use)
- use.collect { |v|
- v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v))
- }.pack(USE_INFO_2_TEMPLATE)
+ def initialize(localname)
+ @use_name = localname
end
- def use_info_2_unpack(buffer)
- use = Hash.new
- USE_INFO_2.each_with_index do |field,offset|
- use[field[0]] = field[1].class == Fixnum ?
- dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset)
+ def to_ui2_struct(use_info)
+ use_info.inject({}) do |memo, (k,v)|
+ memo["ui2_#{k}".to_sym] = v
+ memo
end
- use
- end
-
- public
-
- def initialize(localname)
- @localname = localname
- @name = multi_to_wide(localname)
end
def add(args)
@@ -84,38 +41,45 @@ class Chef::Util::Windows::NetUse < Chef::Util::Windows
args = Hash.new
args[:remote] = remote
end
- args[:local] ||= @localname
- use = use_info_2(args)
- buffer = use_info_2_pack(use)
- rc = NetUseAdd.call(nil, 2, buffer, nil)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ args[:local] ||= use_name
+ ui2_hash = to_ui2_struct(args)
+
+ begin
+ Chef::ReservedNames::Win32::Net.net_use_add_l2(nil, ui2_hash)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
- def get_info
- ptr = 0.chr * PTR_SIZE
- rc = NetUseGetInfo.call(nil, @name, 2, ptr)
-
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ def from_use_info_struct(ui2_hash)
+ ui2_hash.inject({}) do |memo, (k,v)|
+ memo[k.to_s.sub('ui2_', '').to_sym] = v
+ memo
end
+ end
- ptr = ptr.unpack('L')[0]
- buffer = 0.chr * SIZEOF_USE_INFO_2
- memcpy(buffer, ptr, buffer.size)
- NetApiBufferFree(ptr)
- use_info_2_unpack(buffer)
+ def get_info
+ begin
+ ui2 = Chef::ReservedNames::Win32::Net.net_use_get_info_l2(nil, use_name)
+ from_use_info_struct(ui2)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
+ end
end
def device
get_info()[:remote]
end
- #XXX should we use some FORCE here?
+
def delete
- rc = NetUseDel.call(nil, @name, USE_NOFORCE)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ Chef::ReservedNames::Win32::Net.net_use_del(nil, use_name, :use_noforce)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
+
+ def use_name
+ @use_name
+ end
end
diff --git a/lib/chef/util/windows/volume.rb b/lib/chef/util/windows/volume.rb
index 08c3a53793..6e45594ba6 100644
--- a/lib/chef/util/windows/volume.rb
+++ b/lib/chef/util/windows/volume.rb
@@ -18,42 +18,42 @@
#simple wrapper around Volume APIs. might be possible with WMI, but possibly more complex.
+require 'chef/win32/api/file'
require 'chef/util/windows'
-require 'windows/volume'
class Chef::Util::Windows::Volume < Chef::Util::Windows
-
- private
- include Windows::Volume
- #XXX not defined in the current windows-pr release
- DeleteVolumeMountPoint =
- Windows::API.new('DeleteVolumeMountPoint', 'S', 'B') unless defined? DeleteVolumeMountPoint
-
- public
+ attr_reader :mount_point
def initialize(name)
name += "\\" unless name =~ /\\$/ #trailing slash required
- @name = name
+ @mount_point = name
end
def device
- buffer = 0.chr * 256
- if GetVolumeNameForVolumeMountPoint(@name, buffer, buffer.size)
- return buffer[0,buffer.size].unpack("Z*")[0]
- else
- raise ArgumentError, get_last_error
+ begin
+ Chef::ReservedNames::Win32::File.get_volume_name_for_volume_mount_point(mount_point)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
def delete
- unless DeleteVolumeMountPoint.call(@name)
- raise ArgumentError, get_last_error
+ begin
+ Chef::ReservedNames::Win32::File.delete_volume_mount_point(mount_point)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
def add(args)
- unless SetVolumeMountPoint(@name, args[:remote])
- raise ArgumentError, get_last_error
+ begin
+ Chef::ReservedNames::Win32::File.set_volume_mount_point(mount_point, args[:remote])
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
+
+ def mount_point
+ @mount_point
+ end
end
diff --git a/lib/chef/version.rb b/lib/chef/version.rb
index 80fd422c55..faa61aee54 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -21,7 +21,7 @@
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.4.0.rc.2'
+ VERSION = '12.5.0.current.0'
end
#
@@ -29,6 +29,6 @@ end
#
# NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs. The
# Chef::Version class is for _cookbooks_ only, and cannot handle
-# pre-release chef-client versions like "10.14.0.rc.2". Please
-# use Rubygem's Gem::Version class instead.
+# pre-release versions like "10.14.0.rc.2". Please use Rubygem's
+# Gem::Version class instead.
#
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index e9d273808a..4786222bd4 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -188,6 +188,7 @@ class Chef
host.typedef :pointer, :PCRYPTPROTECT_PROMPTSTRUCT # Pointer to a CRYPTOPROTECT_PROMPTSTRUCT.
host.typedef :pointer, :PDATA_BLOB # Pointer to a DATA_BLOB.
host.typedef :pointer, :PTSTR # A PWSTR if UNICODE is defined, a PSTR otherwise.
+ host.typedef :pointer, :PSID
host.typedef :pointer, :PUCHAR # Pointer to a UCHAR.
host.typedef :pointer, :PUHALF_PTR # Pointer to a UHALF_PTR.
host.typedef :pointer, :PUINT # Pointer to a UINT.
diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb
index 86b2b942c2..9ff1ad40d6 100644
--- a/lib/chef/win32/api/file.rb
+++ b/lib/chef/win32/api/file.rb
@@ -20,6 +20,7 @@
require 'chef/win32/api'
require 'chef/win32/api/security'
require 'chef/win32/api/system'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -450,6 +451,25 @@ BOOL WINAPI DeviceIoControl(
=end
safe_attach_function :DeviceIoControl, [:HANDLE, :DWORD, :LPVOID, :DWORD, :LPVOID, :DWORD, :LPDWORD, :pointer], :BOOL
+
+#BOOL WINAPI DeleteVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint
+#);
+ safe_attach_function :DeleteVolumeMountPointW, [:LPCTSTR], :BOOL
+
+#BOOL WINAPI SetVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint,
+ #_In_ LPCTSTR lpszVolumeName
+#);
+ safe_attach_function :SetVolumeMountPointW, [:LPCTSTR, :LPCTSTR], :BOOL
+
+#BOOL WINAPI GetVolumeNameForVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint,
+ #_Out_ LPTSTR lpszVolumeName,
+ #_In_ DWORD cchBufferLength
+#);
+ safe_attach_function :GetVolumeNameForVolumeMountPointW, [:LPCTSTR, :LPTSTR, :DWORD], :BOOL
+
###############################################
# Helpers
###############################################
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index 72caf46628..b173987a05 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -17,6 +17,7 @@
#
require 'chef/win32/api'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -40,6 +41,10 @@ class Chef
UF_NORMAL_ACCOUNT = 0x000200
UF_DONT_EXPIRE_PASSWD = 0x010000
+ USE_NOFORCE = 0
+ USE_FORCE = 1
+ USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
+
NERR_Success = 0
NERR_InvalidComputer = 2351
NERR_NotPrimary = 2226
@@ -49,41 +54,13 @@ class Chef
NERR_BadPassword = 2203
NERR_PasswordTooShort = 2245
NERR_UserNotFound = 2221
+ NERR_GroupNotFound = 2220
ERROR_ACCESS_DENIED = 5
+ ERROR_MORE_DATA = 234
ffi_lib "netapi32"
- class USER_INFO_3 < FFI::Struct
- layout :usri3_name, :LPWSTR,
- :usri3_password, :LPWSTR,
- :usri3_password_age, :DWORD,
- :usri3_priv, :DWORD,
- :usri3_home_dir, :LPWSTR,
- :usri3_comment, :LPWSTR,
- :usri3_flags, :DWORD,
- :usri3_script_path, :LPWSTR,
- :usri3_auth_flags, :DWORD,
- :usri3_full_name, :LPWSTR,
- :usri3_usr_comment, :LPWSTR,
- :usri3_parms, :LPWSTR,
- :usri3_workstations, :LPWSTR,
- :usri3_last_logon, :DWORD,
- :usri3_last_logoff, :DWORD,
- :usri3_acct_expires, :DWORD,
- :usri3_max_storage, :DWORD,
- :usri3_units_per_week, :DWORD,
- :usri3_logon_hours, :PBYTE,
- :usri3_bad_pw_count, :DWORD,
- :usri3_num_logons, :DWORD,
- :usri3_logon_server, :LPWSTR,
- :usri3_country_code, :DWORD,
- :usri3_code_page, :DWORD,
- :usri3_user_id, :DWORD,
- :usri3_primary_group_id, :DWORD,
- :usri3_profile, :LPWSTR,
- :usri3_home_dir_drive, :LPWSTR,
- :usri3_password_expired, :DWORD
-
+ module StructHelpers
def set(key, val)
val = if val.is_a? String
encoded = if val.encoding == Encoding::UTF_16LE
@@ -115,6 +92,47 @@ class Chef
end
end
+ def as_ruby
+ members.inject({}) do |memo, key|
+ memo[key] = get(key)
+ memo
+ end
+ end
+ end
+
+
+ class USER_INFO_3 < FFI::Struct
+ include StructHelpers
+ layout :usri3_name, :LPWSTR,
+ :usri3_password, :LPWSTR,
+ :usri3_password_age, :DWORD,
+ :usri3_priv, :DWORD,
+ :usri3_home_dir, :LPWSTR,
+ :usri3_comment, :LPWSTR,
+ :usri3_flags, :DWORD,
+ :usri3_script_path, :LPWSTR,
+ :usri3_auth_flags, :DWORD,
+ :usri3_full_name, :LPWSTR,
+ :usri3_usr_comment, :LPWSTR,
+ :usri3_parms, :LPWSTR,
+ :usri3_workstations, :LPWSTR,
+ :usri3_last_logon, :DWORD,
+ :usri3_last_logoff, :DWORD,
+ :usri3_acct_expires, :DWORD,
+ :usri3_max_storage, :DWORD,
+ :usri3_units_per_week, :DWORD,
+ :usri3_logon_hours, :PBYTE,
+ :usri3_bad_pw_count, :DWORD,
+ :usri3_num_logons, :DWORD,
+ :usri3_logon_server, :LPWSTR,
+ :usri3_country_code, :DWORD,
+ :usri3_code_page, :DWORD,
+ :usri3_user_id, :DWORD,
+ :usri3_primary_group_id, :DWORD,
+ :usri3_profile, :LPWSTR,
+ :usri3_home_dir_drive, :LPWSTR,
+ :usri3_password_expired, :DWORD
+
def usri3_logon_hours
val = self[:usri3_logon_hours]
if !val.nil? && !val.null?
@@ -123,19 +141,66 @@ class Chef
nil
end
end
+ end
- def as_ruby
- members.inject({}) do |memo, key|
- memo[key] = get(key)
- memo
- end
- end
+ class LOCALGROUP_MEMBERS_INFO_0 < FFI::Struct
+ layout :lgrmi0_sid, :PSID
end
class LOCALGROUP_MEMBERS_INFO_3 < FFI::Struct
layout :lgrmi3_domainandname, :LPWSTR
end
+ class LOCALGROUP_INFO_0 < FFI::Struct
+ layout :lgrpi0_name, :LPWSTR
+ end
+
+ class USE_INFO_2 < FFI::Struct
+ include StructHelpers
+
+ layout :ui2_local, :LMSTR,
+ :ui2_remote, :LMSTR,
+ :ui2_password, :LMSTR,
+ :ui2_status, :DWORD,
+ :ui2_asg_type, :DWORD,
+ :ui2_refcount, :DWORD,
+ :ui2_usecount, :DWORD,
+ :ui2_username, :LPWSTR,
+ :ui2_domainname, :LMSTR
+ end
+
+
+#NET_API_STATUS NetLocalGroupAdd(
+ #_In_ LPCWSTR servername,
+ #_In_ DWORD level,
+ #_In_ LPBYTE buf,
+ #_Out_ LPDWORD parm_err
+#);
+ safe_attach_function :NetLocalGroupAdd, [
+ :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDel(
+ #_In_ LPCWSTR servername,
+ #_In_ LPCWSTR groupname
+#);
+ safe_attach_function :NetLocalGroupDel, [:LPCWSTR, :LPCWSTR], :DWORD
+
+#NET_API_STATUS NetLocalGroupGetMembers(
+ #_In_ LPCWSTR servername,
+ #_In_ LPCWSTR localgroupname,
+ #_In_ DWORD level,
+ #_Out_ LPBYTE *bufptr,
+ #_In_ DWORD prefmaxlen,
+ #_Out_ LPDWORD entriesread,
+ #_Out_ LPDWORD totalentries,
+ #_Inout_ PDWORD_PTR resumehandle
+#);
+ safe_attach_function :NetLocalGroupGetMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD,
+ :LPDWORD, :LPDWORD, :PDWORD_PTR
+ ], :DWORD
+
# NET_API_STATUS NetUserEnum(
# _In_ LPCWSTR servername,
# _In_ DWORD level,
@@ -146,12 +211,15 @@ class Chef
# _Out_ LPDWORD totalentries,
# _Inout_ LPDWORD resume_handle
# );
- safe_attach_function :NetUserEnum, [ :LPCWSTR, :DWORD, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :LPDWORD ], :DWORD
+ safe_attach_function :NetUserEnum, [
+ :LPCWSTR, :DWORD, :DWORD, :LPBYTE,
+ :DWORD, :LPDWORD, :LPDWORD, :LPDWORD
+ ], :DWORD
# NET_API_STATUS NetApiBufferFree(
# _In_ LPVOID Buffer
# );
- safe_attach_function :NetApiBufferFree, [ :LPVOID ], :DWORD
+ safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
#NET_API_STATUS NetUserAdd(
#_In_ LMSTR servername,
@@ -159,7 +227,9 @@ class Chef
#_In_ LPBYTE buf,
#_Out_ LPDWORD parm_err
#);
- safe_attach_function :NetUserAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD ], :DWORD
+ safe_attach_function :NetUserAdd, [
+ :LMSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
#NET_API_STATUS NetLocalGroupAddMembers(
# _In_ LPCWSTR servername,
@@ -168,7 +238,31 @@ class Chef
# _In_ LPBYTE buf,
# _In_ DWORD totalentries
#);
- safe_attach_function :NetLocalGroupAddMembers, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD ], :DWORD
+ safe_attach_function :NetLocalGroupAddMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupSetMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupSetMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDelMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupDelMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
#NET_API_STATUS NetUserGetInfo(
# _In_ LPCWSTR servername,
@@ -176,7 +270,9 @@ class Chef
# _In_ DWORD level,
# _Out_ LPBYTE *bufptr
#);
- safe_attach_function :NetUserGetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE], :DWORD
+ safe_attach_function :NetUserGetInfo, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE
+ ], :DWORD
#NET_API_STATUS NetApiBufferFree(
# _In_ LPVOID Buffer
@@ -190,7 +286,9 @@ class Chef
# _In_ LPBYTE buf,
# _Out_ LPDWORD parm_err
#);
- safe_attach_function :NetUserSetInfo, [:LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD
+ safe_attach_function :NetUserSetInfo, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
#NET_API_STATUS NetUserDel(
# _In_ LPCWSTR servername,
@@ -198,6 +296,28 @@ class Chef
#);
safe_attach_function :NetUserDel, [:LPCWSTR, :LPCWSTR], :DWORD
+#NET_API_STATUS NetUseDel(
+ #_In_ LMSTR UncServerName,
+ #_In_ LMSTR UseName,
+ #_In_ DWORD ForceCond
+#);
+ safe_attach_function :NetUseDel, [:LMSTR, :LMSTR, :DWORD], :DWORD
+
+#NET_API_STATUS NetUseGetInfo(
+ #_In_ LMSTR UncServerName,
+ #_In_ LMSTR UseName,
+ #_In_ DWORD Level,
+ #_Out_ LPBYTE *BufPtr
+#);
+ safe_attach_function :NetUseGetInfo, [:LMSTR, :LMSTR, :DWORD, :pointer], :DWORD
+
+#NET_API_STATUS NetUseAdd(
+ #_In_ LMSTR UncServerName,
+ #_In_ DWORD Level,
+ #_In_ LPBYTE Buf,
+ #_Out_ LPDWORD ParmError
+#);
+ safe_attach_function :NetUseAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD
end
end
end
diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb
new file mode 100644
index 0000000000..45b91d7d32
--- /dev/null
+++ b/lib/chef/win32/api/registry.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Salim Alam (<salam@chef.io>)
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/api'
+
+class Chef
+ module ReservedNames::Win32
+ module API
+ module Registry
+ extend Chef::ReservedNames::Win32::API
+
+ ###############################################
+ # Win32 API Bindings
+ ###############################################
+
+ ffi_lib 'advapi32'
+
+ # LONG WINAPI RegDeleteKeyEx(
+ # _In_ HKEY hKey,
+ # _In_ LPCTSTR lpSubKey,
+ # _In_ REGSAM samDesired,
+ # _Reserved_ DWORD Reserved
+ # );
+ safe_attach_function :RegDeleteKeyExW, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
+ safe_attach_function :RegDeleteKeyExA, [ :HKEY, :LPCTSTR, :LONG, :DWORD ], :LONG
+
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb
index d57699acb4..a485f89708 100644
--- a/lib/chef/win32/api/system.rb
+++ b/lib/chef/win32/api/system.rb
@@ -187,6 +187,29 @@ int WINAPI GetSystemMetrics(
safe_attach_function :GetSystemMetrics, [:int], :int
=begin
+UINT WINAPI GetSystemWow64Directory(
+ _Out_ LPTSTR lpBuffer,
+ _In_ UINT uSize
+);
+=end
+ safe_attach_function :GetSystemWow64DirectoryW, [:LPTSTR, :UINT], :UINT
+ safe_attach_function :GetSystemWow64DirectoryA, [:LPTSTR, :UINT], :UINT
+
+=begin
+BOOL WINAPI Wow64DisableWow64FsRedirection(
+ _Out_ PVOID *OldValue
+);
+=end
+ safe_attach_function :Wow64DisableWow64FsRedirection, [:PVOID], :BOOL
+
+=begin
+BOOL WINAPI Wow64RevertWow64FsRedirection(
+ _In_ PVOID OldValue
+);
+=end
+ safe_attach_function :Wow64RevertWow64FsRedirection, [:PVOID], :BOOL
+
+=begin
LRESULT WINAPI SendMessageTimeout(
_In_ HWND hWnd,
_In_ UINT Msg,
diff --git a/lib/chef/win32/api/unicode.rb b/lib/chef/win32/api/unicode.rb
index 2e3a599f0a..2a9166aa99 100644
--- a/lib/chef/win32/api/unicode.rb
+++ b/lib/chef/win32/api/unicode.rb
@@ -129,49 +129,6 @@ int WideCharToMultiByte(
=end
safe_attach_function :WideCharToMultiByte, [:UINT, :DWORD, :LPCWSTR, :int, :LPSTR, :int, :LPCSTR, :LPBOOL], :int
- ###############################################
- # Helpers
- ###############################################
-
- def utf8_to_wide(ustring)
- # ensure it is actually UTF-8
- # Ruby likes to mark binary data as ASCII-8BIT
- ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
-
- # ensure we have the double-null termination Windows Wide likes
- ustring = ustring + "\000\000" if ustring.length == 0 or ustring[-1].chr != "\000"
-
- # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
- ustring = begin
- if ustring.respond_to?(:encode)
- ustring.encode('UTF-16LE')
- else
- require 'iconv'
- Iconv.conv("UTF-16LE", "UTF-8", ustring)
- end
- end
- ustring
- end
-
- def wide_to_utf8(wstring)
- # ensure it is actually UTF-16LE
- # Ruby likes to mark binary data as ASCII-8BIT
- wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding)
-
- # encode it all as UTF-8
- wstring = begin
- if wstring.respond_to?(:encode)
- wstring.encode('UTF-8')
- else
- require 'iconv'
- Iconv.conv("UTF-8", "UTF-16LE", wstring)
- end
- end
- # remove trailing CRLF and NULL characters
- wstring.strip!
- wstring
- end
-
end
end
end
diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb
index 79cf51b002..aa20c2dfd4 100644
--- a/lib/chef/win32/crypto.rb
+++ b/lib/chef/win32/crypto.rb
@@ -19,6 +19,7 @@
require 'chef/win32/error'
require 'chef/win32/api/memory'
require 'chef/win32/api/crypto'
+require 'chef/win32/unicode'
require 'digest'
class Chef
diff --git a/lib/chef/win32/file.rb b/lib/chef/win32/file.rb
index e6640caa3c..700ddb24d3 100644
--- a/lib/chef/win32/file.rb
+++ b/lib/chef/win32/file.rb
@@ -17,9 +17,11 @@
# limitations under the License.
#
+require 'chef/mixin/wide_string'
require 'chef/win32/api/file'
require 'chef/win32/api/security'
require 'chef/win32/error'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -27,6 +29,9 @@ class Chef
include Chef::ReservedNames::Win32::API::File
extend Chef::ReservedNames::Win32::API::File
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
# Creates a symbolic link called +new_name+ for the file or directory
# +old_name+.
#
@@ -157,9 +162,9 @@ class Chef
def self.file_access_check(path, desired_access)
security_descriptor = Chef::ReservedNames::Win32::Security.get_file_security(path)
- token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE |
+ token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE |
Chef::ReservedNames::Win32::Security::TOKEN_QUERY |
- Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE |
+ Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE |
Chef::ReservedNames::Win32::Security::STANDARD_RIGHTS_READ
token = Chef::ReservedNames::Win32::Security.open_process_token(
Chef::ReservedNames::Win32::Process.get_current_process,
@@ -172,10 +177,30 @@ class Chef
mapping[:GenericExecute] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_EXECUTE
mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS
- Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
+ Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
desired_access, mapping)
end
+ def self.delete_volume_mount_point(mount_point)
+ unless DeleteVolumeMountPointW(wstring(mount_point))
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ end
+
+ def self.set_volume_mount_point(mount_point, name)
+ unless SetVolumeMountPointW(wstring(mount_point), wstring(name))
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ end
+
+ def self.get_volume_name_for_volume_mount_point(mount_point)
+ buffer = FFI::MemoryPointer.new(2, 128)
+ unless GetVolumeNameForVolumeMountPointW(wstring(mount_point), buffer, buffer.size/buffer.type_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ buffer.read_wstring
+ end
+
# ::File compat
class << self
alias :stat :info
diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb
index 0b7d99f111..f4755e9019 100644
--- a/lib/chef/win32/mutex.rb
+++ b/lib/chef/win32/mutex.rb
@@ -17,6 +17,7 @@
#
require 'chef/win32/api/synchronization'
+require 'chef/win32/unicode'
class Chef
module ReservedNames::Win32
@@ -113,5 +114,3 @@ if the mutex is attempted to be acquired by other threads.")
end
end
end
-
-
diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb
index 1349091eb9..59f29c4d1b 100644
--- a/lib/chef/win32/net.rb
+++ b/lib/chef/win32/net.rb
@@ -18,11 +18,11 @@
require 'chef/win32/api/net'
require 'chef/win32/error'
-require 'chef/mixin/wstring'
+require 'chef/mixin/wide_string'
class Chef
module ReservedNames::Win32
- class NetUser
+ class Net
include Chef::ReservedNames::Win32::API::Error
extend Chef::ReservedNames::Win32::API::Error
@@ -91,19 +91,72 @@ The password is shorter than required. (The password could also be too
long, be too recent in its change history, not have enough unique characters,
or not meet another password policy requirement.)
END
+ when NERR_GroupNotFound
+ "The group name could not be found."
when ERROR_ACCESS_DENIED
"The user does not have access to the requested information."
else
"Received unknown error code (#{code})"
end
- formatted_message = ""
- formatted_message << "---- Begin Win32 API output ----\n"
- formatted_message << "Net Api Error Code: #{code}\n"
- formatted_message << "Net Api Error Message: #{msg}\n"
- formatted_message << "---- End Win32 API output ----\n"
+ raise Chef::Exceptions::Win32NetAPIError.new(msg, code)
+ end
+
+ def self.net_local_group_add(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = LOCALGROUP_INFO_0.new
+ buf[:lgrpi0_name] = FFI::MemoryPointer.from_string(group_name)
+
+ rc = NetLocalGroupAdd(server_name, 0, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_del(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
- raise Chef::Exceptions::Win32APIError, msg + "\n" + formatted_message
+ rc = NetLocalGroupDel(server_name, group_name)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_get_members(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = FFI::MemoryPointer.new(:pointer)
+ entries_read_ptr = FFI::MemoryPointer.new(:long)
+ total_read_ptr = FFI::MemoryPointer.new(:long)
+ resume_handle_ptr = FFI::MemoryPointer.new(:pointer)
+
+ rc = ERROR_MORE_DATA
+ group_members = []
+ while rc == ERROR_MORE_DATA
+ rc = NetLocalGroupGetMembers(
+ server_name, group_name, 0, buf, -1,
+ entries_read_ptr, total_read_ptr, resume_handle_ptr
+ )
+
+ nread = entries_read_ptr.read_long
+ nread.times do |i|
+ member = LOCALGROUP_MEMBERS_INFO_0.new(buf.read_pointer +
+ (i * LOCALGROUP_MEMBERS_INFO_0.size))
+ member_sid = Chef::ReservedNames::Win32::Security::SID.new(member[:lgrmi0_sid])
+ group_members << member_sid.to_s
+ end
+ NetApiBufferFree(buf.read_pointer)
+ end
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ group_members
end
def self.net_user_add_l3(server_name, args)
@@ -185,6 +238,107 @@ END
end
end
+ def self.members_to_lgrmi3(members)
+ buf = FFI::MemoryPointer.new(LOCALGROUP_MEMBERS_INFO_3, members.size)
+ members.size.times.collect do |i|
+ member_info = LOCALGROUP_MEMBERS_INFO_3.new(
+ buf + i * LOCALGROUP_MEMBERS_INFO_3.size)
+ member_info[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(wstring(members[i]))
+ member_info
+ end
+ end
+
+ def self.net_local_group_add_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupAddMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_set_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupSetMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_del_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupDelMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_use_del(server_name, use_name, force=:use_noforce)
+ server_name = wstring(server_name)
+ use_name = wstring(use_name)
+ force_const = case force
+ when :use_noforce
+ USE_NOFORCE
+ when :use_force
+ USE_FORCE
+ when :use_lots_of_force
+ USE_LOTS_OF_FORCE
+ else
+ raise ArgumentError, "force must be one of [:use_noforce, :use_force, or :use_lots_of_force]"
+ end
+
+ rc = NetUseDel(server_name, use_name, force_const)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_use_get_info_l2(server_name, use_name)
+ server_name = wstring(server_name)
+ use_name = wstring(use_name)
+ ui2_p = FFI::MemoryPointer.new(:pointer)
+
+ rc = NetUseGetInfo(server_name, use_name, 2, ui2_p)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ ui2 = USE_INFO_2.new(ui2_p.read_pointer).as_ruby
+ NetApiBufferFree(ui2_p.read_pointer)
+
+ ui2
+ end
+
+ def self.net_use_add_l2(server_name, ui2_hash)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = USE_INFO_2.new
+
+ ui2_hash.each do |(k,v)|
+ buf.set(k,v)
+ end
+
+ rc = NetUseAdd(server_name, 2, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
end
+ NetUser = Net # For backwards compatibility
end
end
diff --git a/lib/chef/win32/process.rb b/lib/chef/win32/process.rb
index 2df39bb918..767d4f390c 100644
--- a/lib/chef/win32/process.rb
+++ b/lib/chef/win32/process.rb
@@ -69,6 +69,19 @@ class Chef
result
end
+ def self.is_wow64_process
+ is_64_bit_process_result = FFI::MemoryPointer.new(:int)
+
+ # The return value of IsWow64Process is nonzero value if the API call succeeds.
+ # The result data are returned in the last parameter, not the return value.
+ call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result)
+
+ # The result is nonzero if IsWow64Process's calling process, in the case here
+ # this process, is running under WOW64, i.e. the result is nonzero if this
+ # process is 32-bit (aka :i386).
+ (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0)
+ end
+
# Must have PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION rights,
# AND the PROCESS_VM_READ right
def self.get_process_memory_info(handle)
diff --git a/lib/chef/win32/registry.rb b/lib/chef/win32/registry.rb
index 18f12d26b8..b25ce7937e 100644
--- a/lib/chef/win32/registry.rb
+++ b/lib/chef/win32/registry.rb
@@ -17,8 +17,11 @@
# limitations under the License.
#
require 'chef/reserved_names'
+require 'chef/win32/api'
+require 'chef/mixin/wide_string'
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ require 'chef/win32/api/registry'
require 'win32/registry'
require 'win32/api'
end
@@ -27,6 +30,14 @@ class Chef
class Win32
class Registry
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ include Chef::ReservedNames::Win32::API::Registry
+ extend Chef::ReservedNames::Win32::API::Registry
+ end
+
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
attr_accessor :run_context
attr_accessor :architecture
@@ -142,9 +153,8 @@ class Chef
#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)
+ RegDeleteKeyExW(hive_num, wstring(key), ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0) == 0
end
def key_exists?(key_path)
@@ -203,7 +213,7 @@ class Chef
key_exists!(key_path)
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
- return true if reg.any? {|val| val == value[:name] }
+ return true if reg.any? {|val| safely_downcase(val) == safely_downcase(value[:name]) }
end
return false
end
@@ -213,7 +223,7 @@ class Chef
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
reg.each do |val_name, val_type, val_data|
- if val_name == value[:name] &&
+ if safely_downcase(val_name) == safely_downcase(value[:name]) &&
val_type == get_type_from_name(value[:type]) &&
val_data == value[:data]
return true
@@ -289,6 +299,14 @@ class Chef
private
+
+ def safely_downcase(val)
+ if val.is_a? String
+ return val.downcase
+ end
+ return val
+ end
+
def node
run_context && run_context.node
end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index 5c83180bc0..bc80517d80 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -22,7 +22,7 @@ require 'chef/win32/memory'
require 'chef/win32/process'
require 'chef/win32/unicode'
require 'chef/win32/security/token'
-require 'chef/mixin/wstring'
+require 'chef/mixin/wide_string'
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/security/token.rb b/lib/chef/win32/security/token.rb
index 9e494a73b9..8d4e54ad8c 100644
--- a/lib/chef/win32/security/token.rb
+++ b/lib/chef/win32/security/token.rb
@@ -18,7 +18,7 @@
require 'chef/win32/security'
require 'chef/win32/api/security'
-
+require 'chef/win32/unicode'
require 'ffi'
class Chef
diff --git a/lib/chef/win32/system.rb b/lib/chef/win32/system.rb
new file mode 100755
index 0000000000..cdd063f174
--- /dev/null
+++ b/lib/chef/win32/system.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Salim Alam (<salam@chef.io>)
+# Copyright:: Copyright 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/api/system'
+require 'chef/win32/error'
+require 'ffi'
+
+class Chef
+ module ReservedNames::Win32
+ class System
+ include Chef::ReservedNames::Win32::API::System
+ extend Chef::ReservedNames::Win32::API::System
+
+ def self.get_system_wow64_directory
+ ptr = FFI::MemoryPointer.new(:char, 255, true)
+ succeeded = GetSystemWow64DirectoryA(ptr, 255)
+
+ if succeeded == 0
+ raise Win32APIError, "Failed to get Wow64 system directory"
+ end
+
+ ptr.read_string.strip
+ end
+
+ def self.wow64_disable_wow64_fs_redirection
+ original_redirection_state = FFI::MemoryPointer.new(:pointer)
+
+ succeeded = Wow64DisableWow64FsRedirection(original_redirection_state)
+
+ if succeeded == 0
+ raise Win32APIError, "Failed to disable Wow64 file redirection"
+ end
+
+ original_redirection_state
+ end
+
+ def self.wow64_revert_wow64_fs_redirection(original_redirection_state)
+ succeeded = Wow64RevertWow64FsRedirection(original_redirection_state)
+
+ if succeeded == 0
+ raise Win32APIError, "Failed to revert Wow64 file redirection"
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/win32/unicode.rb b/lib/chef/win32/unicode.rb
index e7399d5255..562301a040 100644
--- a/lib/chef/win32/unicode.rb
+++ b/lib/chef/win32/unicode.rb
@@ -17,6 +17,7 @@
# limitations under the License.
#
+require 'chef/mixin/wide_string'
require 'chef/win32/api/unicode'
class Chef
@@ -30,6 +31,8 @@ end
module FFI
class Pointer
+ include Chef::Mixin::WideString
+
def read_wstring(num_wchars = nil)
if num_wchars.nil?
# Find the length of the string
@@ -43,13 +46,42 @@ module FFI
num_wchars = length
end
- Chef::ReservedNames::Win32::Unicode.wide_to_utf8(self.get_bytes(0, num_wchars*2))
+ wide_to_utf8(self.get_bytes(0, num_wchars*2))
end
end
end
class String
+ include Chef::Mixin::WideString
+
def to_wstring
- Chef::ReservedNames::Win32::Unicode.utf8_to_wide(self)
+ utf8_to_wide(self)
end
end
+
+# https://bugs.ruby-lang.org/issues/11439
+if RUBY_VERSION =~ /^2\.1/
+ module Win32
+ class Registry
+ def write(name, type, data)
+ case type
+ when REG_SZ, REG_EXPAND_SZ
+ data = data.to_s.encode(WCHAR) + WCHAR_NUL
+ when REG_MULTI_SZ
+ data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL
+ when REG_BINARY
+ data = data.to_s
+ when REG_DWORD
+ data = API.packdw(data.to_i)
+ when REG_DWORD_BIG_ENDIAN
+ data = [data.to_i].pack('N')
+ when REG_QWORD
+ data = API.packqw(data.to_i)
+ else
+ raise TypeError, "Unsupported type #{type}"
+ end
+ API.SetValue(@hkey, name, type, data, data.bytesize)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index 17c27e4780..6a7a65b01b 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -122,10 +122,6 @@ class Chef
# WMI always returns the truth. See article at
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
- # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
- # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
- # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
-
wmi = WmiLite::Wmi.new
os_info = wmi.first_of('Win32_OperatingSystem')
os_version = os_info['version']
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb
index 2454c9cccf..8398c5d616 100644
--- a/lib/chef/workstation_config_loader.rb
+++ b/lib/chef/workstation_config_loader.rb
@@ -1,5 +1,5 @@
#
-# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Author:: Claire McQuin (<claire@chef.io>)
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
@@ -16,163 +16,8 @@
# limitations under the License.
#
-require 'chef/config_fetcher'
-require 'chef/config'
-require 'chef/null_logger'
-require 'chef/util/path_helper'
+require 'chef-config/workstation_config_loader'
class Chef
-
- class WorkstationConfigLoader
-
- # Path to a config file requested by user, (e.g., via command line option). Can be nil
- attr_accessor :explicit_config_file
-
- # TODO: initialize this with a logger for Chef and Knife
- def initialize(explicit_config_file, logger=nil)
- @explicit_config_file = explicit_config_file
- @config_location = nil
- @logger = logger || NullLogger.new
- end
-
- def no_config_found?
- config_location.nil?
- end
-
- def config_location
- @config_location ||= (explicit_config_file || locate_local_config)
- end
-
- def chef_config_dir
- if @chef_config_dir.nil?
- @chef_config_dir = false
- full_path = working_directory.split(File::SEPARATOR)
- (full_path.length - 1).downto(0) do |i|
- candidate_directory = File.join(full_path[0..i] + [".chef" ])
- if File.exist?(candidate_directory) && File.directory?(candidate_directory)
- @chef_config_dir = candidate_directory
- break
- end
- end
- end
- @chef_config_dir
- end
-
- def load
- # Ignore it if there's no explicit_config_file and can't find one at a
- # default path.
- return false if config_location.nil?
-
- if explicit_config_file && !path_exists?(config_location)
- raise Exceptions::ConfigurationError, "Specified config file #{config_location} does not exist"
- end
-
- # Have to set Chef::Config.config_file b/c other config is derived from it.
- Chef::Config.config_file = config_location
- read_config(IO.read(config_location), config_location)
- end
-
- # (Private API, public for test purposes)
- def env
- ENV
- end
-
- # (Private API, public for test purposes)
- def path_exists?(path)
- Pathname.new(path).expand_path.exist?
- end
-
- private
-
- def have_config?(path)
- if path_exists?(path)
- logger.info("Using config at #{path}")
- true
- else
- logger.debug("Config not found at #{path}, trying next option")
- false
- end
- end
-
- def locate_local_config
- candidate_configs = []
-
- # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
- if env['KNIFE_HOME']
- candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb')
- candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb')
- end
- # Look for $PWD/knife.rb
- if Dir.pwd
- candidate_configs << File.join(Dir.pwd, 'config.rb')
- candidate_configs << File.join(Dir.pwd, 'knife.rb')
- end
- # Look for $UPWARD/.chef/knife.rb
- if chef_config_dir
- candidate_configs << File.join(chef_config_dir, 'config.rb')
- candidate_configs << File.join(chef_config_dir, 'knife.rb')
- end
- # Look for $HOME/.chef/knife.rb
- Chef::Util::PathHelper.home('.chef') do |dot_chef_dir|
- candidate_configs << File.join(dot_chef_dir, 'config.rb')
- candidate_configs << File.join(dot_chef_dir, 'knife.rb')
- end
-
- candidate_configs.find do | candidate_config |
- have_config?(candidate_config)
- end
- end
-
- def working_directory
- a = if Chef::Platform.windows?
- env['CD']
- else
- env['PWD']
- end || Dir.pwd
-
- a
- end
-
- def read_config(config_content, config_file_path)
- Chef::Config.from_string(config_content, config_file_path)
- rescue SignalException
- raise
- rescue SyntaxError => e
- message = ""
- message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
- message << "#{e.class.name}: #{e.message}\n"
- if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
- line = file_line[/:([\d]+)$/, 1].to_i
- message << highlight_config_error(config_file_path, line)
- end
- raise Exceptions::ConfigurationError, message
- rescue Exception => e
- message = "You have an error in your config file #{config_file_path}\n\n"
- message << "#{e.class.name}: #{e.message}\n"
- filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
- filtered_trace.each {|bt_line| message << " " << bt_line << "\n" }
- if !filtered_trace.empty?
- line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
- message << highlight_config_error(config_file_path, line_nr.to_i)
- end
- raise Exceptions::ConfigurationError, message
- end
-
-
- def highlight_config_error(file, line)
- config_file_lines = []
- IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
- if line == 1
- lines = config_file_lines[0..3]
- else
- lines = config_file_lines[Range.new(line - 2, line)]
- end
- "Relevant file content:\n" + lines.join("\n") + "\n"
- end
-
- def logger
- @logger
- end
-
- end
+ WorkstationConfigLoader = ChefConfig::WorkstationConfigLoader
end
diff --git a/spec/data/cookbooks/openldap/templates/default/helpers.erb b/spec/data/cookbooks/openldap/templates/default/helpers.erb
new file mode 100644
index 0000000000..b973a5287c
--- /dev/null
+++ b/spec/data/cookbooks/openldap/templates/default/helpers.erb
@@ -0,0 +1,14 @@
+<%= @cookbook_name %>
+<%= @recipe_name %>
+<%= @recipe_line_string %>
+<%= @recipe_path %>
+<%= @recipe_line %>
+<%= @template_name %>
+<%= @template_path %>
+<%= cookbook_name %>
+<%= recipe_name %>
+<%= recipe_line_string %>
+<%= recipe_path %>
+<%= recipe_line %>
+<%= template_name %>
+<%= template_path %>
diff --git a/spec/data/dsc_lcm.pfx b/spec/data/dsc_lcm.pfx
new file mode 100644
index 0000000000..3912ed3753
--- /dev/null
+++ b/spec/data/dsc_lcm.pfx
Binary files differ
diff --git a/spec/data/run_context/cookbooks/include/recipes/default.rb b/spec/data/run_context/cookbooks/include/recipes/default.rb
new file mode 100644
index 0000000000..8d22994252
--- /dev/null
+++ b/spec/data/run_context/cookbooks/include/recipes/default.rb
@@ -0,0 +1,24 @@
+module ::RanResources
+ def self.resources
+ @resources ||= []
+ end
+end
+class RunContextCustomResource < Chef::Resource
+ action :create do
+ ruby_block '4' do
+ block { RanResources.resources << 4 }
+ end
+ recipe_eval do
+ ruby_block '1' do
+ block { RanResources.resources << 1 }
+ end
+ include_recipe 'include::includee'
+ ruby_block '3' do
+ block { RanResources.resources << 3 }
+ end
+ end
+ ruby_block '5' do
+ block { RanResources.resources << 5 }
+ end
+ end
+end
diff --git a/spec/data/run_context/cookbooks/include/recipes/includee.rb b/spec/data/run_context/cookbooks/include/recipes/includee.rb
new file mode 100644
index 0000000000..87bb7f114e
--- /dev/null
+++ b/spec/data/run_context/cookbooks/include/recipes/includee.rb
@@ -0,0 +1,3 @@
+ruby_block '2' do
+ block { RanResources.resources << 2 }
+end
diff --git a/spec/data/trusted_certs/opscode.pem b/spec/data/trusted_certs/opscode.pem
index 37a3dd1ef2..e421a4e6e9 100644
--- a/spec/data/trusted_certs/opscode.pem
+++ b/spec/data/trusted_certs/opscode.pem
@@ -1,60 +1,57 @@
-----BEGIN CERTIFICATE-----
-MIIFrDCCBJSgAwIBAgIQB1O/fCb6cEytJ4BP3HTbCTANBgkqhkiG9w0BAQUFADBI
-MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSIwIAYDVQQDExlE
-aWdpQ2VydCBTZWN1cmUgU2VydmVyIENBMB4XDTE0MDYxMDAwMDAwMFoXDTE1MDcw
-MTEyMDAwMFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
-BgNVBAcTB1NlYXR0bGUxGzAZBgNVBAoTEkNoZWYgU29mdHdhcmUsIEluYzEWMBQG
-A1UEAwwNKi5vcHNjb2RlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAMm+rf2RcPGBlZoM+hI4BxlaHbdRg1GZJ/T46UWFOBnZFVP++TX/pyjDsvns
-xymcQywtoN/26+UIys6oWX1um9ikEokvf67LdsUeemQGFHFky8X1Ka2hVtKnxBhi
-XZfvyHDR4IyFWU9AwmhnqySzxqCtynUu8Gktx7JVfqbRFMZ186pDcSw8LoaqjTVG
-SzO7eNH2sM3doMueAHj7ITc2wUzmfa0Pdh+K8UoCn/HopU5LzycziJVPYvUkLT2m
-YCV7VWRc+kObZseHhZAbyaDk3RgPQ/eRMhytAgbruBHWDqNesNw+ZA70w856Oj2Y
-geO7JF+5V6WvkywrF8vydaoM2l8CAwEAAaOCAm8wggJrMB8GA1UdIwQYMBaAFJBx
-2zfrc8jv3NUeErY0uitaoKaSMB0GA1UdDgQWBBQK5zjZwbcmcMNLnI2h1ioAldEV
-ujCBygYDVR0RBIHCMIG/gg0qLm9wc2NvZGUuY29tghBjb3JwLm9wc2NvZGUuY29t
-ghIqLmNvcnAub3BzY29kZS5jb22CDyoubGVhcm5jaGVmLmNvbYISKi5jb3JwLmdl
-dGNoZWYuY29tgg0qLmdldGNoZWYuY29tggwqLm9wc2NvZGUudXOCC2dldGNoZWYu
-Y29tggtvcHNjb2RlLmNvbYIRYXBpLmJlcmtzaGVsZi5jb22CDWxlYXJuY2hlZi5j
-b22CCm9wc2NvZGUudXMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF
-BwMBBggrBgEFBQcDAjBhBgNVHR8EWjBYMCqgKKAmhiRodHRwOi8vY3JsMy5kaWdp
-Y2VydC5jb20vc3NjYS1nNi5jcmwwKqAooCaGJGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0
-LmNvbS9zc2NhLWc2LmNybDBCBgNVHSAEOzA5MDcGCWCGSAGG/WwBATAqMCgGCCsG
-AQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMHgGCCsGAQUFBwEB
-BGwwajAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEIGCCsG
-AQUFBzAChjZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTZWN1
-cmVTZXJ2ZXJDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQUFAAOCAQEA
-kgBpJ2t+St7SmWfeNU9EWAhy0NuUnRIi1jnqXdapfPmS6V/M0i2wP/p+crMty78e
-+3ieuF5s0GJBLs85Hikcl3SlrrbIBJxozov1TY6zeOi6+TCsdXer6t6iQKz36zno
-+k+T6lnMCyo9+pk1PhcAWyfo1Fz4xVOBVec/71VovFkkGD2//KB+sbDs+yh21N9M
-ReO7duj16rQSctfO9R2h65djBNlgz6hXY2nlw8/x3uFfZobXOxDrTcH6Z8HIslkE
-MiTXGix6zdqJaFRCWi+prnAztWs+jEy+v95VSEHPj3xpwZ9WjsxQN0kFA2EX61v/
-kGunmyhehGjblQRt7bpyiA==
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIIEjzCCA3egAwIBAgIQBp4dt3/PHfupevXlyaJANzANBgkqhkiG9w0BAQUFADBh
+MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
-QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaMEgxCzAJBgNVBAYTAlVT
-MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxIjAgBgNVBAMTGURpZ2lDZXJ0IFNlY3Vy
-ZSBTZXJ2ZXIgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7V+Qh
-qdWbYDd+jqFhf4HiGsJ1ZNmRUAvkNkQkbjDSm3on+sJqrmpwCTi5IArIZRBKiKwx
-8tyS8mOhXYBjWYCSIxzm73ZKUDXJ2HE4ue3w5kKu0zgmeTD5IpTG26Y/QXiQ2N5c
-fml9+JAVOtChoL76srIZodgr0c6/a91Jq6OS/rWryME+7gEA2KlEuEJziMNh9atK
-gygK0tRJ+mqxzd9XLJTl4sqDX7e6YlwvaKXwwLn9K9HpH9gaYhW9/z2m98vv5ttl
-LyU47PvmIGZYljQZ0hXOIdMkzNkUb9j+Vcfnb7YPGoxJvinyulqagSY3JG/XSBJs
-Lln1nBi72fZo4t9FAgMBAAGjggFaMIIBVjASBgNVHRMBAf8ECDAGAQH/AgEAMA4G
-A1UdDwEB/wQEAwIBhjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6
-Ly9vY3NwLmRpZ2ljZXJ0LmNvbTB7BgNVHR8EdDByMDegNaAzhjFodHRwOi8vY3Js
-My5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3JsMDegNaAzhjFo
-dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxSb290Q0EuY3Js
-MD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5k
-aWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBSQcds363PI79zVHhK2NLorWqCmkjAf
-BgNVHSMEGDAWgBQD3lA1VtFMu2bwo+IbG8OXsj3RVTANBgkqhkiG9w0BAQUFAAOC
-AQEAMM7RlVEArgYLoQ4CwBestn+PIPZAdXQczHixpE/q9NDEnaLegQcmH0CIUfAf
-z7dMQJnQ9DxxmHOIlywZ126Ej6QfnFog41FcsMWemWpPyGn3EP9OrRnZyVizM64M
-2ZYpnnGycGOjtpkWQh1l8/egHn3F1GUUsmKE1GxcCAzYbJMrtHZZitF//wPYwl24
-LyLWOPD2nGt9RuuZdPfrSg6ppgTre87wXGuYMVqYQOtpxAX0IKjKCDplbDgV9Vws
-slXkLGtB8L5cRspKKaBIXiDSRf8F3jSvcEuBOeLKB1d8tjHcISnivpcOd5AUUUDh
-v+PMGxmcJcqnBrJT3yOyzxIZow==
+QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg
+U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83
+nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd
+KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f
+/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX
+kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0
+/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY
+aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6
+Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1
+oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD
+QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
+d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh
+xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB
+CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl
+5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA
+8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC
+2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit
+c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0
+j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIFDTCCA/WgAwIBAgIQBZ8R1sZP2Lbc8x554UUQ2DANBgkqhkiG9w0BAQsFADBN
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
+aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTQxMTEwMDAwMDAwWhcN
+MTcxMTE0MTIwMDAwWjBlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
+bjEQMA4GA1UEBxMHU2VhdHRsZTEbMBkGA1UEChMSQ2hlZiBTb2Z0d2FyZSwgSW5j
+MRIwEAYDVQQDDAkqLmNoZWYuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC3xCIczkV10O5jTDpbd4YlPLC6kfnVoOkno2N/OOlcLQu3ulj/Lj1j4r6e
+2XthJLcFgTO+y+1/IKnnpLKDfkx1YngWEBXEBP+MrrpDUKKs053s45/bI9QBPISA
+tXgnYxMH9Glo6FWWd13TUq++OKGw1p1wazH64XK4MAf5y/lkmWXIWumNuO35ZqtB
+ME3wJISwVHzHB2CQjlDklt+Mb0APEiIFIZflgu9JNBYzLdvUtxiz15FUZQI7SsYL
+TfXOD1KBNMWqN8snG2e5gRAzB2D161DFvAZt8OiYUe+3QurNlTYVzeHv1ok6UqgM
+ZcLzg8m801rRip0D7FCGvMCU/ktdAgMBAAGjggHPMIIByzAfBgNVHSMEGDAWgBQP
+gGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUwldjw4Pb4HV+wxGZ7MSSRh+d
+pm4wHQYDVR0RBBYwFIIJKi5jaGVmLmlvggdjaGVmLmlvMA4GA1UdDwEB/wQEAwIF
+oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYDVR0fBGQwYjAvoC2g
+K4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hhMi1nMy5jcmwwL6At
+oCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNoYTItZzMuY3JsMEIG
+A1UdIAQ7MDkwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3
+LmRpZ2ljZXJ0LmNvbS9DUFMwfAYIKwYBBQUHAQEEcDBuMCQGCCsGAQUFBzABhhho
+dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wRgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNl
+cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQw
+DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAvcTWenNuvvrhX2omm8LQ
+zWOuu8jqpoflACwD4lOSZ4TgOe4pQGCjXq8aRBD5k+goqQrPVf9lHnelUHFQac0Q
+5WT4YUmisUbF0S4uY5OGQymM52MvUWG4ODL4gaWhFvN+HAXrDPP/9iitsjV0QOnl
+CDq7Q4/XYRYW3opu5nLLbfW6v4QvF5yzZagEACGs7Vt32p6l391UcU8f6wiB3uMD
+eioCvjpv/+2YOUNlDPCM3uBubjUhHOwO817wBxXkzdk1OSRe4jzcw/uX6wL7birt
+fbaSkpilvVX529pSzB2Lvi9xWOoGMM578dpQ0h3PwhmmvKhhCWP+pI05k3oSkYCP
+ng==
-----END CERTIFICATE-----
diff --git a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
index b3c2333e9a..2b582feb05 100644
--- a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
+++ b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
@@ -43,7 +43,7 @@ describe Chef::Resource::WhyrunSafeRubyBlock do
end
it "updates the evil laugh, even in why-run mode" do
- new_resource.run_action(new_resource.action)
+ Array(new_resource.action).each {|action| new_resource.run_action(action) }
expect($evil_global_evil_laugh).to eq(:mwahahaha)
expect(new_resource).to be_updated
end
diff --git a/spec/functional/rebooter_spec.rb b/spec/functional/rebooter_spec.rb
index 485e98f247..a0e2665de5 100644
--- a/spec/functional/rebooter_spec.rb
+++ b/spec/functional/rebooter_spec.rb
@@ -43,7 +43,7 @@ describe Chef::Platform::Rebooter do
let(:expected) do
{
- :windows => 'shutdown /r /t 5 /c "rebooter spec test"',
+ :windows => 'shutdown /r /t 300 /c "rebooter spec test"',
:linux => 'shutdown -r +5 "rebooter spec test"'
}
end
diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb
index e5f5341fcd..4bce309a51 100644
--- a/spec/functional/resource/deploy_revision_spec.rb
+++ b/spec/functional/resource/deploy_revision_spec.rb
@@ -819,7 +819,7 @@ describe Chef::Resource::DeployRevision, :unix_only => true do
end
before do
- expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Chef::Exceptions::Exec)
+ expect { deploy_that_fails.run_action(:deploy) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
deploy_to_latest_with_callback_tracking.run_action(:deploy)
end
diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb
index a736949c6b..dc7704481f 100644
--- a/spec/functional/resource/dsc_script_spec.rb
+++ b/spec/functional/resource/dsc_script_spec.rb
@@ -19,6 +19,7 @@
require 'spec_helper'
require 'chef/mixin/shell_out'
require 'chef/mixin/windows_architecture_helper'
+require 'support/shared/integration/integration_helper'
describe Chef::Resource::DscScript, :windows_powershell_dsc_only do
include Chef::Mixin::WindowsArchitectureHelper
@@ -67,8 +68,7 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do
node = Chef::Node.new
node.automatic['platform'] = 'windows'
node.automatic['platform_version'] = '6.1'
- node.automatic['kernel'][:machine] =
- is_i386_process_on_x86_64_windows? ? :x86_64 : :i386
+ node.automatic['kernel'][:machine] = :x86_64 # Only 64-bit architecture is supported
node.automatic[:languages][:powershell][:version] = '4.0'
empty_events = Chef::EventDispatch::Dispatcher.new
Chef::RunContext.new(node, {}, empty_events)
@@ -379,4 +379,93 @@ EOH
it_behaves_like 'a dsc_script with configuration data that takes parameters'
it_behaves_like 'a dsc_script without configuration data that takes parameters'
end
+
+ context 'when using ps_credential' do
+ include IntegrationSupport
+
+ before(:each) do
+ delete_user(dsc_user)
+ ohai_reader = Ohai::System.new
+ ohai_reader.all_plugins(["platform", "os", "languages/powershell"])
+ dsc_test_run_context.node.consume_external_attrs(ohai_reader.data,{})
+ end
+
+ let(:configuration_data_path) { 'C:\\configurationdata.psd1' }
+
+ let(:self_signed_cert_path) do
+ File.join(CHEF_SPEC_DATA, 'dsc_lcm.pfx')
+ end
+
+ let(:dsc_configuration_script) do
+ <<-MYCODE
+cd c:\\
+configuration LCM
+{
+ param ($thumbprint)
+ localconfigurationmanager
+ {
+ RebootNodeIfNeeded = $false
+ ConfigurationMode = 'ApplyOnly'
+ CertificateID = $thumbprint
+ }
+}
+$cert = ls Cert:\\LocalMachine\\My\\ |
+ Where-Object {$_.Subject -match "ChefTest"} |
+ Select -first 1
+
+if($cert -eq $null) {
+ $pfxpath = '#{self_signed_cert_path}'
+ $password = ''
+ $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxpath, $password, ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset))
+ $store = New-Object System.Security.Cryptography.X509Certificates.X509Store "My", ([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
+ $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
+ $store.Add($cert)
+ $store.Close()
+}
+
+lcm -thumbprint $cert.thumbprint
+set-dsclocalconfigurationmanager -path ./LCM
+$ConfigurationData = @"
+@{
+AllNodes = @(
+ @{
+ NodeName = "localhost";
+ CertificateID = '$($cert.thumbprint)';
+ };
+);
+}
+"@
+$ConfigurationData | out-file '#{configuration_data_path}' -force
+ MYCODE
+ end
+
+ let(:powershell_script_resource) do
+ Chef::Resource::PowershellScript.new('configure-lcm', dsc_test_run_context).tap do |r|
+ r.code(dsc_configuration_script)
+ r.architecture(:x86_64)
+ end
+ end
+
+ let(:dsc_script_resource) do
+ dsc_test_resource_base.tap do |r|
+ r.code <<-EOF
+User dsctestusercreate
+{
+ UserName = '#{dsc_user}'
+ Password = #{r.ps_credential('jf9a8m49jrajf4#')}
+ Ensure = "Present"
+}
+EOF
+ r.configuration_data_script(configuration_data_path)
+ end
+ end
+
+ it 'allows the use of ps_credential' do
+ expect(user_exists?(dsc_user)).to eq(false)
+ powershell_script_resource.run_action(:run)
+ expect(File).to exist(configuration_data_path)
+ dsc_script_resource.run_action(:run)
+ expect(user_exists?(dsc_user)).to eq(true)
+ end
+ end
end
diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb
index 529af52d4e..0862b8e15f 100644
--- a/spec/functional/resource/group_spec.rb
+++ b/spec/functional/resource/group_spec.rb
@@ -95,7 +95,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
def create_user(username)
user(username).run_action(:create) if ! windows_domain_user?(username)
- # TODO: User shouldn't exist
+ # TODO: User should exist
end
def remove_user(username)
@@ -135,44 +135,75 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
group_should_not_exist(group_name)
end
- describe "when append is not set" do
- let(:included_members) { [spec_members[1]] }
-
- before do
- create_user(spec_members[1])
- create_user(spec_members[0])
- add_members_to_group([spec_members[0]])
- end
+ # dscl doesn't perform any error checking and will let you add users that don't exist.
+ describe "when no users exist", :not_supported_on_mac_osx do
+ describe "when append is not set" do
+ # excluded_members can only be used when append is set. It is ignored otherwise.
+ let(:excluded_members) { [] }
- after do
- remove_user(spec_members[1])
- remove_user(spec_members[0])
+ it "should raise an error" do
+ expect { group_resource.run_action(tested_action) }.to raise_error()
+ end
end
- it "should remove the existing users and add the new users to the group" do
- group_resource.run_action(tested_action)
+ describe "when append is set" do
+ before do
+ group_resource.append(true)
+ end
- expect(user_exist_in_group?(spec_members[1])).to eq(true)
- expect(user_exist_in_group?(spec_members[0])).to eq(false)
+ it "should raise an error" do
+ expect { group_resource.run_action(tested_action) }.to raise_error()
+ end
end
end
- describe "when append is set" do
- before(:each) do
- group_resource.append(true)
+ describe "when the users exist" do
+ before do
+ (spec_members).each do |member|
+ create_user(member)
+ end
end
- describe "when the users exist" do
- before do
- (included_members + excluded_members).each do |member|
- create_user(member)
+ after do
+ (spec_members).each do |member|
+ remove_user(member)
+ end
+ end
+
+ describe "when append is not set" do
+ it "should set the group to to contain given members" do
+ group_resource.run_action(tested_action)
+
+ included_members.each do |member|
+ expect(user_exist_in_group?(member)).to eq(true)
+ end
+ (spec_members - included_members).each do |member|
+ expect(user_exist_in_group?(member)).to eq(false)
end
end
- after do
- (included_members + excluded_members).each do |member|
- remove_user(member)
+ describe "when group already contains some users" do
+ before do
+ add_members_to_group([included_members[0]])
+ add_members_to_group(spec_members - included_members)
end
+
+ it "should remove all existing users and only add the new users to the group" do
+ group_resource.run_action(tested_action)
+
+ included_members.each do |member|
+ expect(user_exist_in_group?(member)).to eq(true)
+ end
+ (spec_members - included_members).each do |member|
+ expect(user_exist_in_group?(member)).to eq(false)
+ end
+ end
+ end
+ end
+
+ describe "when append is set" do
+ before(:each) do
+ group_resource.append(true)
end
it "should add included members to the group" do
@@ -186,9 +217,9 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
end
end
- describe "when group contains some users" do
+ describe "when group already contains some users" do
before(:each) do
- add_members_to_group([ spec_members[0], spec_members[2] ])
+ add_members_to_group([included_members[0], excluded_members[0]])
end
it "should add the included users and remove excluded users" do
@@ -203,20 +234,6 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
end
end
end
-
- describe "when the users doesn't exist" do
- describe "when append is not set" do
- it "should raise an error" do
- expect { @grp_resource.run_action(tested_action) }.to raise_error
- end
- end
-
- describe "when append is set" do
- it "should raise an error" do
- expect { @grp_resource.run_action(tested_action) }.to raise_error
- end
- end
- end
end
end
@@ -231,6 +248,12 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
group_should_exist(group_name)
end
+ after(:each) do
+ group_resource.run_action(:remove)
+ end
+
+ # TODO: The ones below might actually return ArgumentError now - but I don't have
+ # a way to verify that. Change it and delete this comment if that's the case.
describe "when updating membership" do
it "raises an error for a non well-formed domain name" do
group_resource.members [invalid_domain_user_name]
@@ -256,7 +279,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
end
end
- let(:group_name) { "t-#{SecureRandom.random_number(9999)}" }
+ let(:group_name) { "group#{SecureRandom.random_number(9999)}" }
let(:included_members) { nil }
let(:excluded_members) { nil }
let(:group_resource) {
@@ -300,7 +323,7 @@ theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\
downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" }
it "should not create a group" do
- expect { group_resource.run_action(:create) }.to raise_error
+ expect { group_resource.run_action(:create) }.to raise_error(ArgumentError)
group_should_not_exist(group_name)
end
end
diff --git a/spec/functional/resource/package_spec.rb b/spec/functional/resource/package_spec.rb
index 5c17ca0107..8d37b072e8 100644
--- a/spec/functional/resource/package_spec.rb
+++ b/spec/functional/resource/package_spec.rb
@@ -386,5 +386,3 @@ describe Chef::Resource::Package, metadata do
end
end
-
-
diff --git a/spec/functional/resource/powershell_spec.rb b/spec/functional/resource/powershell_script_spec.rb
index 17ae8cbd2a..be744e748b 100644
--- a/spec/functional/resource/powershell_spec.rb
+++ b/spec/functional/resource/powershell_script_spec.rb
@@ -125,16 +125,16 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
expect { resource.run_action(:run) }.not_to raise_error
end
- it "raises an error if the script is not syntactically correct and returns is not set to 1" do
+ it "raises a Mixlib::ShellOut::ShellCommandFailed error if the script is not syntactically correct" do
resource.code('if({)')
resource.returns(0)
expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
end
- it "returns 1 if the script provided to the code attribute is not syntactically correct" do
+ it "raises an error if the script is not syntactically correct even if returns is set to 1 which is what powershell.exe returns for syntactically invalid scripts" do
resource.code('if({)')
resource.returns(1)
- expect { resource.run_action(:run) }.not_to raise_error
+ expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
end
# This somewhat ambiguous case, two failures of different types,
@@ -227,10 +227,25 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
expect { resource.should_skip?(:run) }.to raise_error(ArgumentError, /guard_interpreter does not support blocks/)
end
+ context "when dsc is supported", :windows_powershell_dsc_only do
+ it "can execute LCM configuration code" do
+ resource.code <<-EOF
+configuration LCM
+{
+ param ($thumbprint)
+ localconfigurationmanager
+ {
+ RebootNodeIfNeeded = $false
+ ConfigurationMode = 'ApplyOnly'
+ }
+}
+ EOF
+ expect { resource.run_action(:run) }.not_to raise_error
+ end
+ end
end
- context "when running on a 32-bit version of Windows", :windows32_only do
-
+ context "when running on a 32-bit version of Ruby", :ruby32_only do
it "executes a script with a 32-bit process if process architecture :i386 is specified" do
resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
resource.architecture(:i386)
@@ -240,15 +255,28 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
expect(source_contains_case_insensitive_content?( get_script_output, 'x86' )).to eq(true)
end
- it "raises an exception if :x86_64 process architecture is specified" do
- begin
- expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect
- rescue Chef::Exceptions::Win32ArchitectureIncorrect
+ context "when running on a 64-bit version of Windows", :windows64_only do
+ it "executes a script with a 64-bit process if :x86_64 arch is specified" do
+ resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
+ resource.architecture(:x86_64)
+ resource.returns(0)
+ resource.run_action(:run)
+
+ expect(source_contains_case_insensitive_content?( get_script_output, 'AMD64' )).to eq(true)
+ end
+ end
+
+ context "when running on a 32-bit version of Windows", :windows32_only do
+ it "raises an exception if :x86_64 process architecture is specified" do
+ begin
+ expect(resource.architecture(:x86_64)).to raise_error Chef::Exceptions::Win32ArchitectureIncorrect
+ rescue Chef::Exceptions::Win32ArchitectureIncorrect
+ end
end
end
end
- context "when running on a 64-bit version of Windows", :windows64_only do
+ context "when running on a 64-bit version of Ruby", :ruby64_only do
it "executes a script with a 64-bit process if :x86_64 arch is specified" do
resource.code(processor_architecture_script_content + " | out-file -encoding ASCII #{script_output_path}")
resource.architecture(:x86_64)
diff --git a/spec/functional/win32/registry_helper_spec.rb b/spec/functional/win32/registry_helper_spec.rb
index 7b070e6fe1..9ef6fd006f 100644
--- a/spec/functional/win32/registry_helper_spec.rb
+++ b/spec/functional/win32/registry_helper_spec.rb
@@ -130,6 +130,9 @@ describe 'Chef::Win32::Registry', :windows_only do
it "returns true if the value exists" do
expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true)
end
+ it "returns true if the value exists with a case mismatch on the value name" do
+ expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true)
+ end
it "returns false if the value does not exist" do
expect(@registry.value_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})).to eq(false)
end
@@ -145,6 +148,9 @@ describe 'Chef::Win32::Registry', :windows_only do
it "returns true if the value exists" do
expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals"})).to eq(true)
end
+ it "returns true if the value exists with a case mismatch on the value name" do
+ expect(@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals"})).to eq(true)
+ end
it "throws an exception if the value does not exist" do
expect {@registry.value_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"FOOBAR"})}.to raise_error(Chef::Exceptions::Win32RegValueMissing)
end
@@ -160,6 +166,9 @@ describe 'Chef::Win32::Registry', :windows_only do
it "returns true if all the data matches" do
expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
end
+ it "returns true if all the data matches with a case mismatch on the data name" do
+ expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
+ end
it "returns false if the name does not exist" do
expect(@registry.data_exists?("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(false)
end
@@ -181,6 +190,9 @@ describe 'Chef::Win32::Registry', :windows_only do
it "returns true if all the data matches" do
expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"Petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
end
+ it "returns true if all the data matches with a case mismatch on the data name" do
+ expect(@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"petals", :type=>:multi_string, :data=>["Pink", "Delicate"]})).to eq(true)
+ end
it "throws an exception if the name does not exist" do
expect {@registry.data_exists!("HKCU\\Software\\Root\\Branch\\Flower", {:name=>"slateP", :type=>:multi_string, :data=>["Pink", "Delicate"]})}.to raise_error(Chef::Exceptions::Win32RegDataMissing)
end
diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb
index d2474deace..a1ce36146f 100644
--- a/spec/functional/win32/service_manager_spec.rb
+++ b/spec/functional/win32/service_manager_spec.rb
@@ -33,7 +33,7 @@ end
# directories.
#
-describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only do
+describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only, :appveyor_only do
include_context "using Win32::Service"
@@ -43,7 +43,7 @@ describe "Chef::Application::WindowsServiceManager", :windows_only, :system_wind
end
it "throws an error with required missing options" do
- test_service.each do |key,value|
+ [:service_name, :service_display_name, :service_description, :service_file_path].each do |key|
service_def = test_service.dup
service_def.delete(key)
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index 8c72048965..1a030c130b 100644
--- a/spec/integration/client/client_spec.rb
+++ b/spec/integration/client/client_spec.rb
@@ -303,6 +303,59 @@ EOM
end
+ when_the_repository "has a cookbook that generates deprecation warnings" do
+ before do
+ file 'cookbooks/x/recipes/default.rb', <<-EOM
+ class ::MyResource < Chef::Resource
+ use_automatic_resource_name
+ property :x, default: []
+ property :y, default: {}
+ end
+
+ my_resource 'blah' do
+ 1.upto(10) do
+ x nil
+ end
+ x nil
+ end
+ EOM
+ end
+
+ def match_indices(regex, str)
+ result = []
+ pos = 0
+ while match = regex.match(str, pos)
+ result << match.begin(0)
+ pos = match.end(0) + 1
+ end
+ result
+ end
+
+ it "should output each deprecation warning only once, at the end of the run" do
+ file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+# Mimick what happens when you are on the console
+formatters << :doc
+log_level :warn
+EOM
+
+ ENV.delete('CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS')
+
+ result = shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+ expect(result.error?).to be_falsey
+
+ # Search to the end of the client run in the output
+ run_complete = result.stdout.index("Running handlers complete")
+ expect(run_complete).to be >= 0
+
+ # Make sure there is exactly one result for each, and that it occurs *after* the complete message.
+ expect(match_indices(/MyResource.x has an array or hash default/, result.stdout)).to match([ be > run_complete ])
+ expect(match_indices(/MyResource.y has an array or hash default/, result.stdout)).to match([ be > run_complete ])
+ expect(match_indices(/nil currently does not overwrite the value of/, result.stdout)).to match([ be > run_complete ])
+ end
+ end
+
when_the_repository "has a cookbook with only an audit recipe" do
before do
diff --git a/spec/integration/knife/chef_repo_path_spec.rb b/spec/integration/knife/chef_repo_path_spec.rb
index 874b33901f..908657e5f7 100644
--- a/spec/integration/knife/chef_repo_path_spec.rb
+++ b/spec/integration/knife/chef_repo_path_spec.rb
@@ -24,6 +24,8 @@ describe 'chef_repo_path tests', :workstation do
include IntegrationSupport
include KnifeSupport
+ let(:error_rel_path_outside_repo) { /^ERROR: Attempt to use relative path '' when current directory is outside the repository path/ }
+
# TODO alternate repo_path / *_path
context 'alternate *_path' do
when_the_repository 'has clients and clients2, cookbooks and cookbooks2, etc.' do
@@ -109,14 +111,14 @@ EOM
context 'when cwd is at the top level' do
before { cwd '.' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
context 'when cwd is inside the data_bags directory' do
before { cwd 'data_bags' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
@@ -192,14 +194,14 @@ EOM
context 'when cwd is inside the data_bags directory' do
before { cwd 'data_bags' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
context 'when cwd is inside chef_repo2' do
before { cwd 'chef_repo2' }
it 'knife list -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
@@ -225,14 +227,14 @@ EOM
context 'when cwd is at the top level' do
before { cwd '.' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
context 'when cwd is inside the data_bags directory' do
before { cwd 'data_bags' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
@@ -445,7 +447,7 @@ EOM
context 'when cwd is at the top level' do
before { cwd '.' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
@@ -621,14 +623,14 @@ EOM
context 'when cwd is at the top level' do
before { cwd '.' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
context 'when cwd is inside the data_bags directory' do
before { cwd 'data_bags' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
@@ -782,7 +784,7 @@ EOM
context 'when cwd is at the top level' do
before { cwd '.' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
@@ -823,7 +825,7 @@ EOM
context 'when cwd is inside chef_repo2/data_bags' do
before { cwd 'chef_repo2/data_bags' }
it 'knife list --local -Rfp fails' do
- knife('list --local -Rfp').should_fail("ERROR: Attempt to use relative path '' when current directory is outside the repository path\n")
+ knife('list --local -Rfp').should_fail(error_rel_path_outside_repo)
end
end
end
diff --git a/spec/integration/recipes/lwrp_spec.rb b/spec/integration/recipes/lwrp_spec.rb
index e93763fddc..7ecdfc7c3a 100644
--- a/spec/integration/recipes/lwrp_spec.rb
+++ b/spec/integration/recipes/lwrp_spec.rb
@@ -45,12 +45,8 @@ log_level :warn
EOM
result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'l-w-r-p::default'", :cwd => chef_dir)
- actual = result.stdout.lines.map { |l| l.chomp }.join("\n")
- expected = <<EOM
- * l_w_r_p_foo[me] action create (up to date)
-EOM
- expected = expected.lines.map { |l| l.chomp }.join("\n")
- expect(actual).to include(expected)
+ expect(result.stdout).to match(/\* l_w_r_p_foo\[me\] action create \(up to date\)/)
+ expect(result.stdout).not_to match(/WARN: You are overriding l_w_r_p_foo/)
result.error!
end
end
diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb
index 3f4bf9fd5f..52bca87c99 100644
--- a/spec/integration/recipes/recipe_dsl_spec.rb
+++ b/spec/integration/recipes/recipe_dsl_spec.rb
@@ -11,19 +11,15 @@ describe "Recipe DSL methods" do
before(:all) { Namer.current_index = 1 }
before { Namer.current_index += 1 }
- context "With resource 'base_thingy' declared as BaseThingy" do
+ context "with resource 'base_thingy' declared as BaseThingy" do
before(:context) {
class BaseThingy < Chef::Resource
- def initialize(*args, &block)
- super
- @allowed_actions = [ :create ]
- @action = :create
- end
-
resource_name 'base_thingy'
+ default_action :create
class<<self
+ attr_accessor :created_name
attr_accessor :created_resource
attr_accessor :created_provider
end
@@ -35,6 +31,7 @@ describe "Recipe DSL methods" do
def load_current_resource
end
def action_create
+ BaseThingy.created_name = new_resource.name
BaseThingy.created_resource = new_resource.class
BaseThingy.created_provider = self.class
end
@@ -52,20 +49,42 @@ describe "Recipe DSL methods" do
BaseThingy.created_provider = nil
end
+ it "creates base_thingy when you call base_thingy in a recipe" do
+ recipe = converge {
+ base_thingy 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_name).to eq 'blah'
+ expect(BaseThingy.created_resource).to eq BaseThingy
+ end
+
+ it "errors out when you call base_thingy do ... end in a recipe" do
+ expect_converge {
+ base_thingy do; end
+ }.to raise_error(ArgumentError, 'You must supply a name when declaring a base_thingy resource')
+ end
+
+ it "emits a warning when you call base_thingy 'foo', 'bar' do ... end in a recipe" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ recipe = converge {
+ base_thingy 'foo', 'bar' do
+ end
+ }
+ expect(recipe.logged_warnings).to match(/Cannot create resource base_thingy with more than one argument. All arguments except the name \("foo"\) will be ignored. This will cause an error in Chef 13. Arguments: \["foo", "bar"\]/)
+ expect(BaseThingy.created_name).to eq 'foo'
+ expect(BaseThingy.created_resource).to eq BaseThingy
+ end
+
context "Deprecated automatic resource DSL" do
before do
Chef::Config[:treat_deprecation_warnings_as_errors] = false
end
- context "With a resource 'backcompat_thingy' declared in Chef::Resource and Chef::Provider" do
+ context "with a resource 'backcompat_thingy' declared in Chef::Resource and Chef::Provider" do
before(:context) {
class Chef::Resource::BackcompatThingy < Chef::Resource
- def initialize(*args, &block)
- super
- @allowed_actions = [ :create ]
- @action = :create
- end
+ default_action :create
end
class Chef::Provider::BackcompatThingy < Chef::Provider
def load_current_resource
@@ -100,13 +119,13 @@ describe "Recipe DSL methods" do
recipe = converge {
backcompat_thingy 'blah' do; end
}
- expect(recipe.logged_warnings).to eq ''
+ expect(recipe.logged_warnings).to match(/Class Chef::Provider::BackcompatThingy does not declare 'provides :backcompat_thingy'./)
expect(BaseThingy.created_resource).not_to be_nil
end
end
end
- context "With a resource named RecipeDSLSpecNamespace::Bar::BarThingy" do
+ context "with a resource named RecipeDSLSpecNamespace::Bar::BarThingy" do
before(:context) {
class RecipeDSLSpecNamespace::Bar::BarThingy < BaseThingy
@@ -114,19 +133,17 @@ describe "Recipe DSL methods" do
}
- it "bar_thingy works" do
- recipe = converge {
+ it "bar_thingy does not work" do
+ expect_converge {
bar_thingy 'blah' do; end
- }
- expect(recipe.logged_warnings).to eq ''
- expect(BaseThingy.created_resource).to eq(RecipeDSLSpecNamespace::Bar::BarThingy)
+ }.to raise_error(NoMethodError)
end
end
- context "With a resource named NoNameThingy with resource_name nil" do
+ context "with a resource named Chef::Resource::NoNameThingy with resource_name nil" do
before(:context) {
- class NoNameThingy < BaseThingy
+ class Chef::Resource::NoNameThingy < BaseThingy
resource_name nil
end
@@ -134,12 +151,12 @@ describe "Recipe DSL methods" do
it "no_name_thingy does not work" do
expect_converge {
- thingy 'blah' do; end
+ no_name_thingy 'blah' do; end
}.to raise_error(NoMethodError)
end
end
- context "With a resource named AnotherNoNameThingy with resource_name :another_thingy_name" do
+ context "with a resource named AnotherNoNameThingy with resource_name :another_thingy_name" do
before(:context) {
class AnotherNoNameThingy < BaseThingy
@@ -163,7 +180,7 @@ describe "Recipe DSL methods" do
end
end
- context "With a resource named AnotherNoNameThingy2 with resource_name :another_thingy_name2; resource_name :another_thingy_name3" do
+ context "with a resource named AnotherNoNameThingy2 with resource_name :another_thingy_name2; resource_name :another_thingy_name3" do
before(:context) {
class AnotherNoNameThingy2 < BaseThingy
@@ -195,10 +212,11 @@ describe "Recipe DSL methods" do
end
context "provides overriding resource_name" do
- context "With a resource named AnotherNoNameThingy3 with provides :another_no_name_thingy3, os: 'blarghle'" do
+ context "with a resource named AnotherNoNameThingy3 with provides :another_no_name_thingy3, os: 'blarghle'" do
before(:context) {
class AnotherNoNameThingy3 < BaseThingy
+ resource_name :another_no_name_thingy_3
provides :another_no_name_thingy3, os: 'blarghle'
end
@@ -223,10 +241,11 @@ describe "Recipe DSL methods" do
end
end
- context "With a resource named AnotherNoNameThingy4 with two provides" do
+ context "with a resource named AnotherNoNameThingy4 with two provides" do
before(:context) {
class AnotherNoNameThingy4 < BaseThingy
+ resource_name :another_no_name_thingy_4
provides :another_no_name_thingy4, os: 'blarghle'
provides :another_no_name_thingy4, platform_family: 'foo'
end
@@ -262,7 +281,7 @@ describe "Recipe DSL methods" do
end
end
- context "With a resource named AnotherNoNameThingy5, a different resource_name, and a provides with the original resource_name" do
+ context "with a resource named AnotherNoNameThingy5, a different resource_name, and a provides with the original resource_name" do
before(:context) {
class AnotherNoNameThingy5 < BaseThingy
@@ -299,7 +318,7 @@ describe "Recipe DSL methods" do
end
end
- context "With a resource named AnotherNoNameThingy6, a provides with the original resource name, and a different resource_name" do
+ context "with a resource named AnotherNoNameThingy6, a provides with the original resource name, and a different resource_name" do
before(:context) {
class AnotherNoNameThingy6 < BaseThingy
@@ -336,7 +355,7 @@ describe "Recipe DSL methods" do
end
end
- context "With a resource named AnotherNoNameThingy7, a new resource_name, and provides with that new resource name" do
+ context "with a resource named AnotherNoNameThingy7, a new resource_name, and provides with that new resource name" do
before(:context) {
class AnotherNoNameThingy7 < BaseThingy
@@ -374,7 +393,7 @@ describe "Recipe DSL methods" do
end
# opposite order from the previous test (provides, then resource_name)
- context "With a resource named AnotherNoNameThingy8, a provides with a new resource name, and resource_name with that new resource name" do
+ context "with a resource named AnotherNoNameThingy8, a provides with a new resource name, and resource_name with that new resource name" do
before(:context) {
class AnotherNoNameThingy8 < BaseThingy
@@ -410,116 +429,6 @@ describe "Recipe DSL methods" do
}.to raise_error(NoMethodError)
end
end
-
- context "With a resource TwoClassesOneDsl" do
- let(:class_name) { "TwoClassesOneDsl#{Namer.current_index}" }
- let(:dsl_method) { :"two_classes_one_dsl#{Namer.current_index}" }
-
- before {
- eval <<-EOM, nil, __FILE__, __LINE__+1
- class #{class_name} < BaseThingy
- end
- EOM
- }
- context "and resource BlahModule::TwoClassesOneDsl" do
- before {
- eval <<-EOM, nil, __FILE__, __LINE__+1
- module BlahModule
- class #{class_name} < BaseThingy
- end
- end
- EOM
- }
- it "two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl (last declared)" do
- dsl_method = self.dsl_method
- recipe = converge {
- instance_eval("#{dsl_method} 'blah' do; end")
- }
- expect(recipe.logged_warnings).to eq ''
- expect(BaseThingy.created_resource).to eq eval("BlahModule::#{class_name}")
- end
- it "resource_matching_short_name returns BlahModule::TwoClassesOneDsl" do
- expect(Chef::Resource.resource_matching_short_name(dsl_method)).to eq eval("BlahModule::#{class_name}")
- end
- end
- context "and resource BlahModule::TwoClassesOneDsl with resource_name nil" do
- before {
- eval <<-EOM, nil, __FILE__, __LINE__+1
- module BlahModule
- class BlahModule::#{class_name} < BaseThingy
- resource_name nil
- end
- end
- EOM
- }
- it "two_classes_one_dsl resolves to ::TwoClassesOneDsl" do
- dsl_method = self.dsl_method
- recipe = converge {
- instance_eval("#{dsl_method} 'blah' do; end")
- }
- expect(recipe.logged_warnings).to eq ''
- expect(BaseThingy.created_resource).to eq eval("::#{class_name}")
- end
- it "resource_matching_short_name returns ::TwoClassesOneDsl" do
- expect(Chef::Resource.resource_matching_short_name(dsl_method)).to eq eval("::#{class_name}")
- end
- end
- context "and resource BlahModule::TwoClassesOneDsl with resource_name :argh" do
- before {
- eval <<-EOM, nil, __FILE__, __LINE__+1
- module BlahModule
- class BlahModule::#{class_name} < BaseThingy
- resource_name :argh
- end
- end
- EOM
- }
- it "two_classes_one_dsl resolves to ::TwoClassesOneDsl" do
- dsl_method = self.dsl_method
- recipe = converge {
- instance_eval("#{dsl_method} 'blah' do; end")
- }
- expect(recipe.logged_warnings).to eq ''
- expect(BaseThingy.created_resource).to eq eval("::#{class_name}")
- end
- it "resource_matching_short_name returns ::TwoClassesOneDsl" do
- expect(Chef::Resource.resource_matching_short_name(dsl_method)).to eq eval("::#{class_name}")
- end
- end
- context "and resource BlahModule::TwoClassesOneDsl with provides :two_classes_one_dsl, os: 'blarghle'" do
- before {
- eval <<-EOM, nil, __FILE__, __LINE__+1
- module BlahModule
- class BlahModule::#{class_name} < BaseThingy
- provides #{dsl_method.inspect}, os: 'blarghle'
- end
- end
- EOM
- }
-
- it "and os = blarghle, two_classes_one_dsl resolves to BlahModule::TwoClassesOneDsl" do
- dsl_method = self.dsl_method
- recipe = converge {
- # this is an ugly way to test, make Cheffish expose node attrs
- run_context.node.automatic[:os] = 'blarghle'
- instance_eval("#{dsl_method} 'blah' do; end")
- }
- expect(recipe.logged_warnings).to eq ''
- expect(BaseThingy.created_resource).to eq eval("BlahModule::#{class_name}")
- end
-
- it "and os = linux, two_classes_one_dsl resolves to ::TwoClassesOneDsl" do
- dsl_method = self.dsl_method
- recipe = converge {
- # this is an ugly way to test, make Cheffish expose node attrs
- run_context.node.automatic[:os] = 'linux'
- instance_eval("#{dsl_method} 'blah' do; end")
- }
- expect(recipe.logged_warnings).to eq ''
- expect(BaseThingy.created_resource).to eq eval("::#{class_name}")
- end
- end
- end
end
end
@@ -572,11 +481,11 @@ describe "Recipe DSL methods" do
}
- it "thingy3 works in a recipe and yields Foo::Thingy4 (the explicit one)" do
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
recipe = converge {
thingy3 'blah' do; end
}
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy4
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
end
it "thingy4 does not work in a recipe" do
@@ -586,16 +495,19 @@ describe "Recipe DSL methods" do
end
it "resource_matching_short_name returns Thingy4" do
- expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy4
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
end
end
end
- context "when Thingy5 has resource_name :thingy5" do
+ context "when Thingy5 has resource_name :thingy5 and provides :thingy5reverse, :thingy5_2 and :thingy5_2reverse" do
before(:context) {
class RecipeDSLSpecNamespace::Thingy5 < BaseThingy
resource_name :thingy5
+ provides :thingy5reverse
+ provides :thingy5_2
+ provides :thingy5_2reverse
end
}
@@ -611,6 +523,7 @@ describe "Recipe DSL methods" do
before(:context) {
class RecipeDSLSpecNamespace::Thingy6 < BaseThingy
+ resource_name :thingy6
provides :thingy5
end
@@ -623,23 +536,151 @@ describe "Recipe DSL methods" do
expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6
end
- it "thingy5 works in a recipe and yields Foo::Thingy6 (the later one)" do
+ it "thingy5 works in a recipe and yields Foo::Thingy5 (the alphabetical one)" do
recipe = converge {
thingy5 'blah' do; end
}
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
end
it "resource_matching_short_name returns Thingy5" do
expect(Chef::Resource.resource_matching_short_name(:thingy5)).to eq RecipeDSLSpecNamespace::Thingy5
end
+
+ context "and AThingy5 provides :thingy5reverse" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::AThingy5 < BaseThingy
+ resource_name :thingy5reverse
+ end
+
+ }
+
+ it "thingy5reverse works in a recipe and yields AThingy5 (the alphabetical one)" do
+ recipe = converge {
+ thingy5reverse 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::AThingy5
+ end
+ end
+
+ context "and ZRecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+ before(:context) {
+
+ module ZRecipeDSLSpecNamespace
+ class Thingy5 < BaseThingy
+ resource_name :thingy5_2
+ end
+ end
+
+ }
+
+ it "thingy5_2 works in a recipe and yields the RecipeDSLSpaceNamespace one (the alphabetical one)" do
+ recipe = converge {
+ thingy5_2 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ end
+ end
+
+ context "and ARecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+ before(:context) {
+
+ module ARecipeDSLSpecNamespace
+ class Thingy5 < BaseThingy
+ resource_name :thingy5_2reverse
+ end
+ end
+
+ }
+
+ it "thingy5_2reverse works in a recipe and yields the ARecipeDSLSpaceNamespace one (the alphabetical one)" do
+ recipe = converge {
+ thingy5_2reverse 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq ARecipeDSLSpecNamespace::Thingy5
+ end
+ end
+ end
+
+ context "when Thingy3 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy3 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe" do
+ expect_recipe {
+ thingy3 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ context "and Thingy4 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+ recipe = converge {
+ thingy3 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ it "thingy4 does not work in a recipe" do
+ expect_converge {
+ thingy4 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "resource_matching_short_name returns Thingy4" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+ end
+
+ context "and Thingy4 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+ recipe = converge {
+ thingy3 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ it "thingy4 does not work in a recipe" do
+ expect_converge {
+ thingy4 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "resource_matching_short_name returns Thingy4" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+ end
end
+
end
context "when Thingy7 provides :thingy8" do
before(:context) {
class RecipeDSLSpecNamespace::Thingy7 < BaseThingy
+ resource_name :thingy7
provides :thingy8
end
@@ -661,11 +702,11 @@ describe "Recipe DSL methods" do
expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
end
- it "thingy8 works in a recipe and yields Thingy8 (the later one)" do
+ it "thingy8 works in a recipe and yields Thingy7 (alphabetical)" do
recipe = converge {
thingy8 'blah' do; end
}
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy8
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
end
it "resource_matching_short_name returns Thingy8" do
@@ -674,40 +715,40 @@ describe "Recipe DSL methods" do
end
end
- context "when Thingy5 provides :thingy5, :twizzle and :twizzle2" do
+ context "when Thingy12 provides :thingy12, :twizzle and :twizzle2" do
before(:context) {
- class RecipeDSLSpecNamespace::Thingy5 < BaseThingy
- resource_name :thingy5
+ class RecipeDSLSpecNamespace::Thingy12 < BaseThingy
+ resource_name :thingy12
provides :twizzle
provides :twizzle2
end
}
- it "thingy5 works in a recipe and yields Thingy5" do
+ it "thingy12 works in a recipe and yields Thingy12" do
expect_recipe {
- thingy5 'blah' do; end
+ thingy12 'blah' do; end
}.to emit_no_warnings_or_errors
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
end
- it "twizzle works in a recipe and yields Thingy5" do
+ it "twizzle works in a recipe and yields Thingy12" do
expect_recipe {
twizzle 'blah' do; end
}.to emit_no_warnings_or_errors
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
end
- it "twizzle2 works in a recipe and yields Thingy5" do
+ it "twizzle2 works in a recipe and yields Thingy12" do
expect_recipe {
twizzle2 'blah' do; end
}.to emit_no_warnings_or_errors
- expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
end
end
- context "With platform-specific resources 'my_super_thingy_foo' and 'my_super_thingy_bar'" do
+ context "with platform-specific resources 'my_super_thingy_foo' and 'my_super_thingy_bar'" do
before(:context) {
class MySuperThingyFoo < BaseThingy
resource_name :my_super_thingy_foo
@@ -752,6 +793,700 @@ describe "Recipe DSL methods" do
}.to raise_error(Chef::Exceptions::NoSuchResourceType)
end
end
+
+ context "when Thingy10 provides :thingy10" do
+ before(:context) {
+ class RecipeDSLSpecNamespace::Thingy10 < BaseThingy
+ resource_name :thingy10
+ end
+ }
+
+ it "declaring a resource providing the same :thingy10 with override: true does not produce a warning" do
+ expect(Chef::Log).not_to receive(:warn)
+ class RecipeDSLSpecNamespace::Thingy10AlternateProvider < BaseThingy
+ provides :thingy10, override: true
+ end
+ end
+ end
+
+ context "when Thingy11 provides :thingy11" do
+ before(:context) {
+ class RecipeDSLSpecNamespace::Thingy11 < BaseThingy
+ resource_name :thingy10
+ end
+ }
+
+ it "declaring a resource providing the same :thingy11 with os: 'linux' does not produce a warning" do
+ expect(Chef::Log).not_to receive(:warn)
+ class RecipeDSLSpecNamespace::Thingy11AlternateProvider < BaseThingy
+ provides :thingy11, os: 'linux'
+ end
+ end
+ end
+ end
+
+ context "with a resource named 'B' with resource name :two_classes_one_dsl" do
+ let(:two_classes_one_dsl) { :"two_classes_one_dsl#{Namer.current_index}" }
+ let(:resource_class) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "B"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result
+ }
+ before { resource_class } # pull on it so it gets defined before the recipe runs
+
+ context "and another resource named 'A' with resource_name :two_classes_one_dsl" do
+ let(:resource_class_a) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "A"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result
+ }
+ before { resource_class_a } # pull on it so it gets defined before the recipe runs
+
+ it "two_classes_one_dsl resolves to A (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_a
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class_a
+ end
+ end
+
+ context "and another resource named 'Z' with resource_name :two_classes_one_dsl" do
+ let(:resource_class_z) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "Z"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result
+ }
+ before { resource_class_z } # pull on it so it gets defined before the recipe runs
+
+ it "two_classes_one_dsl resolves to B (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+
+ context "and a priority array [ Z, B ]" do
+ before do
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z, resource_class ])
+ end
+
+ it "two_classes_one_dsl resolves to Z (respects the priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_z
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+
+ context "when Z provides(:two_classes_one_dsl) { false }" do
+ before do
+ resource_class_z.provides(two_classes_one_dsl) { false }
+ end
+
+ it "two_classes_one_dsl resolves to B (picks the next thing in the priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+ end
+ end
+
+ context "and priority arrays [ B ] and [ Z ]" do
+ before do
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class ])
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ])
+ end
+
+ it "two_classes_one_dsl resolves to Z (respects the most recent priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_z
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+
+ context "when Z provides(:two_classes_one_dsl) { false }" do
+ before do
+ resource_class_z.provides(two_classes_one_dsl) { false }
+ end
+
+ it "two_classes_one_dsl resolves to B (picks the first match from the other priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+ end
+ end
+
+ context "and a priority array [ Z ]" do
+ before do
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ])
+ end
+
+ context "when Z provides(:two_classes_one_dsl) { false }" do
+ before do
+ resource_class_z.provides(two_classes_one_dsl) { false }
+ end
+
+ it "two_classes_one_dsl resolves to B (picks the first match outside the priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+ end
+ end
+
+ end
+
+ context "and a provider named 'B' which provides :two_classes_one_dsl" do
+ before do
+ resource_class.send(:define_method, :provider) { nil }
+ end
+
+ let(:provider_class) {
+ result = Class.new(BaseThingy::Provider) do
+ def self.name
+ "B"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.provides two_classes_one_dsl
+ result
+ }
+ before { provider_class } # pull on it so it gets defined before the recipe runs
+
+ context "and another provider named 'A'" do
+ let(:provider_class_a) {
+ result = Class.new(BaseThingy::Provider) do
+ def self.name
+ "A"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result
+ }
+ context "which provides :two_classes_one_dsl" do
+ before { provider_class_a.provides two_classes_one_dsl }
+
+ it "two_classes_one_dsl resolves to A (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class_a
+ end
+ end
+ context "which provides(:two_classes_one_dsl) { false }" do
+ before { provider_class_a.provides(two_classes_one_dsl) { false } }
+
+ it "two_classes_one_dsl resolves to B (since A declined)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+ end
+
+ context "and another provider named 'Z'" do
+ let(:provider_class_z) {
+ result = Class.new(BaseThingy::Provider) do
+ def self.name
+ "Z"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result
+ }
+ before { provider_class_z } # pull on it so it gets defined before the recipe runs
+
+ context "which provides :two_classes_one_dsl" do
+ before { provider_class_z.provides two_classes_one_dsl }
+
+ it "two_classes_one_dsl resolves to B (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+
+ context "with a priority array [ Z, B ]" do
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] }
+
+ it "two_classes_one_dsl resolves to Z (respects the priority map)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class_z
+ end
+ end
+ end
+
+ context "which provides(:two_classes_one_dsl) { false }" do
+ before { provider_class_z.provides(two_classes_one_dsl) { false } }
+
+ context "with a priority array [ Z, B ]" do
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] }
+
+ it "two_classes_one_dsl resolves to B (the next one in the priority map)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+
+ context "with priority arrays [ B ] and [ Z ]" do
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z ] }
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class ] }
+
+ it "two_classes_one_dsl resolves to B (the one in the next priority map)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+ end
+ end
+ end
+
+ context "and another resource Blarghle with provides :two_classes_one_dsl, os: 'blarghle'" do
+ let(:resource_class_blarghle) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "Blarghle"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result.provides two_classes_one_dsl, os: 'blarghle'
+ result
+ }
+ before { resource_class_blarghle } # pull on it so it gets defined before the recipe runs
+
+ it "on os = blarghle, two_classes_one_dsl resolves to Blarghle" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ instance_eval("#{two_classes_one_dsl} 'blah' do; end")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_blarghle
+ end
+
+ it "on os = linux, two_classes_one_dsl resolves to B" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ instance_eval("#{two_classes_one_dsl} 'blah' do; end")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+ end
+ end
+
+ context "with a resource MyResource" do
+ let(:resource_class) { Class.new(BaseThingy) do
+ def self.called_provides
+ @called_provides
+ end
+ def to_s
+ "MyResource"
+ end
+ end }
+ let(:my_resource) { :"my_resource#{Namer.current_index}" }
+ let(:blarghle_blarghle_little_star) { :"blarghle_blarghle_little_star#{Namer.current_index}" }
+
+ context "with resource_name :my_resource" do
+ before {
+ resource_class.resource_name my_resource
+ }
+
+ context "with provides? returning true to my_resource" do
+ before {
+ my_resource = self.my_resource
+ resource_class.define_singleton_method(:provides?) do |node, resource_name|
+ @called_provides = true
+ resource_name == my_resource
+ end
+ }
+
+ it "my_resource returns the resource and calls provides?, but does not emit a warning" do
+ dsl_name = self.my_resource
+ recipe = converge {
+ instance_eval("#{dsl_name} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ expect(resource_class.called_provides).to be_truthy
+ end
+ end
+
+ context "with provides? returning true to blarghle_blarghle_little_star and not resource_name" do
+ before do
+ blarghle_blarghle_little_star = self.blarghle_blarghle_little_star
+ resource_class.define_singleton_method(:provides?) do |node, resource_name|
+ @called_provides = true
+ resource_name == blarghle_blarghle_little_star
+ end
+ end
+
+ it "my_resource does not return the resource" do
+ dsl_name = self.my_resource
+ expect_converge {
+ instance_eval("#{dsl_name} 'foo'")
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ expect(resource_class.called_provides).to be_truthy
+ end
+
+ it "blarghle_blarghle_little_star 'foo' returns the resource and emits a warning" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ dsl_name = self.blarghle_blarghle_little_star
+ recipe = converge {
+ instance_eval("#{dsl_name} 'foo'")
+ }
+ expect(recipe.logged_warnings).to include "WARN: #{resource_class}.provides? returned true when asked if it provides DSL #{dsl_name}, but provides :#{dsl_name} was never called!"
+ expect(BaseThingy.created_resource).to eq resource_class
+ expect(resource_class.called_provides).to be_truthy
+ end
+ end
+
+ context "and a provider" do
+ let(:provider_class) do
+ Class.new(BaseThingy::Provider) do
+ def self.name
+ "MyProvider"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ def self.called_provides
+ @called_provides
+ end
+ end
+ end
+
+ before do
+ resource_class.send(:define_method, :provider) { nil }
+ end
+
+ context "that provides :my_resource" do
+ before do
+ provider_class.provides my_resource
+ end
+
+ context "with supports? returning true" do
+ before do
+ provider_class.define_singleton_method(:supports?) { |resource,action| true }
+ end
+
+ it "my_resource runs the provider and does not emit a warning" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+
+ context "and another provider supporting :my_resource with supports? false" do
+ let(:provider_class2) do
+ Class.new(BaseThingy::Provider) do
+ def self.name
+ "MyProvider2"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ def self.called_provides
+ @called_provides
+ end
+ provides my_resource
+ def self.supports?(resource, action)
+ false
+ end
+ end
+ end
+
+ it "my_resource runs the first provider" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+ end
+
+ context "with supports? returning false" do
+ before do
+ provider_class.define_singleton_method(:supports?) { |resource,action| false }
+ end
+
+ # TODO no warning? ick
+ it "my_resource runs the provider anyway" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+
+ context "and another provider supporting :my_resource with supports? true" do
+ let(:provider_class2) do
+ my_resource = self.my_resource
+ Class.new(BaseThingy::Provider) do
+ def self.name
+ "MyProvider2"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ def self.called_provides
+ @called_provides
+ end
+ provides my_resource
+ def self.supports?(resource, action)
+ true
+ end
+ end
+ end
+ before { provider_class2 } # make sure the provider class shows up
+
+ it "my_resource runs the other provider" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class2
+ end
+ end
+ end
+ end
+
+ context "with provides? returning true" do
+ before {
+ my_resource = self.my_resource
+ provider_class.define_singleton_method(:provides?) do |node, resource|
+ @called_provides = true
+ resource.declared_type == my_resource
+ end
+ }
+
+ context "that provides :my_resource" do
+ before {
+ provider_class.provides my_resource
+ }
+
+ it "my_resource calls the provider (and calls provides?), but does not emit a warning" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+
+ context "that does not call provides :my_resource" do
+ it "my_resource calls the provider (and calls provides?), and emits a warning" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to include("WARN: #{provider_class}.provides? returned true when asked if it provides DSL #{my_resource}, but provides :#{my_resource} was never called!")
+ expect(BaseThingy.created_provider).to eq provider_class
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+ end
+
+ context "with provides? returning false to my_resource" do
+ before {
+ my_resource = self.my_resource
+ provider_class.define_singleton_method(:provides?) do |node, resource|
+ @called_provides = true
+ false
+ end
+ }
+
+ context "that provides :my_resource" do
+ before {
+ provider_class.provides my_resource
+ }
+
+ it "my_resource fails to find a provider (and calls provides)" do
+ my_resource = self.my_resource
+ expect_converge {
+ instance_eval("#{my_resource} 'foo'")
+ }.to raise_error(Chef::Exceptions::ProviderNotFound)
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+
+ context "that does not provide :my_resource" do
+ it "my_resource fails to find a provider (and calls provides)" do
+ my_resource = self.my_resource
+ expect_converge {
+ instance_eval("#{my_resource} 'foo'")
+ }.to raise_error(Chef::Exceptions::ProviderNotFound)
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ before(:all) { Namer.current_index = 0 }
+ before { Namer.current_index += 1 }
+
+ context "with an LWRP that declares actions" do
+ let(:resource_class) {
+ Class.new(Chef::Resource::LWRPBase) do
+ provides :"recipe_dsl_spec#{Namer.current_index}"
+ actions :create
+ end
+ }
+ let(:resource) {
+ resource_class.new("blah", run_context)
+ }
+ it "The actions are part of actions along with :nothing" do
+ expect(resource_class.actions).to eq [ :nothing, :create ]
+ end
+ it "The actions are part of allowed_actions along with :nothing" do
+ expect(resource.allowed_actions).to eq [ :nothing, :create ]
+ end
+
+ context "and a subclass that declares more actions" do
+ let(:subresource_class) {
+ Class.new(Chef::Resource::LWRPBase) do
+ provides :"recipe_dsl_spec_sub#{Namer.current_index}"
+ actions :delete
+ end
+ }
+ let(:subresource) {
+ subresource_class.new("subblah", run_context)
+ }
+
+ it "The parent class actions are not part of actions" do
+ expect(subresource_class.actions).to eq [ :nothing, :delete ]
+ end
+ it "The parent class actions are not part of allowed_actions" do
+ expect(subresource.allowed_actions).to eq [ :nothing, :delete ]
+ end
+ it "The parent class actions do not change" do
+ expect(resource_class.actions).to eq [ :nothing, :create ]
+ expect(resource.allowed_actions).to eq [ :nothing, :create ]
+ end
+ end
+ end
+
+ context "with a dynamically defined resource and regular provider" do
+ before(:context) do
+ Class.new(Chef::Resource) do
+ resource_name :lw_resource_with_hw_provider_test_case
+ default_action :create
+ attr_accessor :created_provider
+ end
+ class Chef::Provider::LwResourceWithHwProviderTestCase < Chef::Provider
+ def load_current_resource
+ end
+ def action_create
+ new_resource.created_provider = self.class
+ end
+ end
+ end
+
+ it "looks up the provider in Chef::Provider converting the resource name from snake case to camel case" do
+ resource = nil
+ recipe = converge {
+ resource = lw_resource_with_hw_provider_test_case 'blah' do; end
+ }
+ expect(resource.created_provider).to eq(Chef::Provider::LwResourceWithHwProviderTestCase)
end
end
end
diff --git a/spec/integration/recipes/resource_action_spec.rb b/spec/integration/recipes/resource_action_spec.rb
new file mode 100644
index 0000000000..53611c144f
--- /dev/null
+++ b/spec/integration/recipes/resource_action_spec.rb
@@ -0,0 +1,356 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource.action" do
+ include IntegrationSupport
+
+ shared_context "ActionJackson" do
+ it "The default action is the first declared action" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "The action can access recipe DSL" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_recipe_dsl
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "The action can access attributes" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_attribute
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq 'foo!'
+ end
+
+ it "The action can access public methods" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_method
+ expect(ActionJackson.succeeded).to eq 'foo_public!'
+ end
+
+ it "The action can access protected methods" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_protected_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_protected_method
+ expect(ActionJackson.succeeded).to eq 'foo_protected!'
+ end
+
+ it "The action cannot access private methods" do
+ expect {
+ converge(<<-EOM, __FILE__, __LINE__+1)
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_private_method
+ end
+ EOM
+ }.to raise_error(NameError)
+ expect(ActionJackson.ran_action).to eq :access_private_method
+ end
+
+ it "The action cannot access resource instance variables" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_instance_variable
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_instance_variable
+ expect(ActionJackson.succeeded).to be_nil
+ end
+
+ it "The action does not compile until the prior resource has converged" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ ruby_block 'wow' do
+ block do
+ ActionJackson.ruby_block_converged = 'ruby_block_converged!'
+ end
+ end
+
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_class_method
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_class_method
+ expect(ActionJackson.succeeded).to eq 'ruby_block_converged!'
+ end
+
+ it "The action's resources converge before the next resource converges" do
+ converge <<-EOM, __FILE__, __LINE__+1
+ #{resource_dsl} 'hi' do
+ foo 'foo!'
+ action :access_attribute
+ end
+
+ ruby_block 'wow' do
+ block do
+ ActionJackson.ruby_block_converged = ActionJackson.succeeded
+ end
+ end
+ EOM
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq 'foo!'
+ expect(ActionJackson.ruby_block_converged).to eq 'foo!'
+ end
+ end
+
+ context "With resource 'action_jackson'" do
+ before(:context) {
+ class ActionJackson < Chef::Resource
+ use_automatic_resource_name
+ def foo(value=nil)
+ @foo = value if value
+ @foo
+ end
+ def blarghle(value=nil)
+ @blarghle = value if value
+ @blarghle
+ end
+
+ class <<self
+ attr_accessor :ran_action
+ attr_accessor :succeeded
+ attr_accessor :ruby_block_converged
+ end
+
+ public
+ def foo_public
+ 'foo_public!'
+ end
+ protected
+ def foo_protected
+ 'foo_protected!'
+ end
+ private
+ def foo_private
+ 'foo_private!'
+ end
+
+ public
+ action :access_recipe_dsl do
+ ActionJackson.ran_action = :access_recipe_dsl
+ ruby_block 'hi there' do
+ block do
+ ActionJackson.succeeded = true
+ end
+ end
+ end
+ action :access_attribute do
+ ActionJackson.ran_action = :access_attribute
+ ActionJackson.succeeded = foo
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+ end
+ action :access_attribute2 do
+ ActionJackson.ran_action = :access_attribute2
+ ActionJackson.succeeded = foo
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
+ end
+ action :access_method do
+ ActionJackson.ran_action = :access_method
+ ActionJackson.succeeded = foo_public
+ end
+ action :access_protected_method do
+ ActionJackson.ran_action = :access_protected_method
+ ActionJackson.succeeded = foo_protected
+ end
+ action :access_private_method do
+ ActionJackson.ran_action = :access_private_method
+ ActionJackson.succeeded = foo_private
+ end
+ action :access_instance_variable do
+ ActionJackson.ran_action = :access_instance_variable
+ ActionJackson.succeeded = @foo
+ end
+ action :access_class_method do
+ ActionJackson.ran_action = :access_class_method
+ ActionJackson.succeeded = ActionJackson.ruby_block_converged
+ end
+ end
+ }
+ before(:each) {
+ ActionJackson.ran_action = :error
+ ActionJackson.succeeded = :error
+ ActionJackson.ruby_block_converged = :error
+ }
+
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackson }
+ end
+
+ context "And 'action_jackgrandson' inheriting from ActionJackson and changing nothing" do
+ before(:context) {
+ class ActionJackgrandson < ActionJackson
+ use_automatic_resource_name
+ end
+ }
+
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackgrandson }
+ end
+ end
+
+ context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute and action" do
+ before(:context) {
+ class ActionJackalope < ActionJackson
+ use_automatic_resource_name
+
+ def foo(value=nil)
+ @foo = "#{value}alope" if value
+ @foo
+ end
+ def bar(value=nil)
+ @bar = "#{value}alope" if value
+ @bar
+ end
+ class <<self
+ attr_accessor :jackalope_ran
+ end
+ action :access_jackalope do
+ ActionJackalope.jackalope_ran = :access_jackalope
+ ActionJackalope.succeeded = "#{foo} #{blarghle} #{bar}"
+ end
+ action :access_attribute do
+ super()
+ ActionJackalope.jackalope_ran = :access_attribute
+ ActionJackalope.succeeded = ActionJackson.succeeded
+ end
+ end
+ }
+ before do
+ ActionJackalope.jackalope_ran = nil
+ end
+
+ context "action_jackson still behaves the same" do
+ it_behaves_like "ActionJackson" do
+ let(:resource_dsl) { :action_jackson }
+ end
+ end
+
+ it "The default action remains the same even though new actions were specified first" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
+ expect(ActionJackson.succeeded).to eq true
+ end
+
+ it "new actions run, and can access overridden, new, and overridden attributes" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_jackalope
+ end
+ }
+ expect(ActionJackalope.jackalope_ran).to eq :access_jackalope
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+ end
+
+ it "overridden actions run, call super, and can access overridden, new, and overridden attributes" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_attribute
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_attribute
+ expect(ActionJackson.succeeded).to eq "foo!alope blarghle! bar!alope"
+ expect(ActionJackalope.jackalope_ran).to eq :access_attribute
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
+ end
+
+ it "non-overridden actions run and can access overridden and non-overridden variables (but not necessarily new ones)" do
+ converge {
+ action_jackalope 'hi' do
+ foo 'foo!'
+ bar 'bar!'
+ blarghle 'blarghle!'
+ action :access_attribute2
+ end
+ }
+ expect(ActionJackson.ran_action).to eq :access_attribute2
+ expect(ActionJackson.succeeded).to eq("foo!alope blarghle! bar!alope").or(eq("foo!alope blarghle!"))
+ end
+ end
+ end
+
+ context "With a resource with no actions" do
+ before(:context) {
+ class NoActionJackson < Chef::Resource
+ use_automatic_resource_name
+
+ def foo(value=nil)
+ @foo = value if value
+ @foo
+ end
+
+ class <<self
+ attr_accessor :action_was
+ end
+ end
+ }
+ it "The default action is :nothing" do
+ converge {
+ no_action_jackson 'hi' do
+ foo 'foo!'
+ NoActionJackson.action_was = action
+ end
+ }
+ expect(NoActionJackson.action_was).to eq [:nothing]
+ end
+ end
+
+ context "With a resource with action a-b-c d" do
+ before(:context) {
+ class WeirdActionJackson < Chef::Resource
+ use_automatic_resource_name
+
+ class <<self
+ attr_accessor :action_was
+ end
+
+ action "a-b-c d" do
+ WeirdActionJackson.action_was = action
+ end
+ end
+ }
+
+ it "Running the action works" do
+ expect_recipe {
+ weird_action_jackson 'hi'
+ }.to be_up_to_date
+ expect(WeirdActionJackson.action_was).to eq :"a-b-c d"
+ end
+ end
+end
diff --git a/spec/integration/recipes/resource_converge_if_changed_spec.rb b/spec/integration/recipes/resource_converge_if_changed_spec.rb
new file mode 100644
index 0000000000..d00252a717
--- /dev/null
+++ b/spec/integration/recipes/resource_converge_if_changed_spec.rb
@@ -0,0 +1,423 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource::ActionProvider#converge_if_changed" do
+ include IntegrationSupport
+
+ module Namer
+ extend self
+ attr_accessor :current_index
+ def incrementing_value
+ @incrementing_value += 1
+ @incrementing_value
+ end
+ attr_writer :incrementing_value
+ end
+
+ before(:all) { Namer.current_index = 1 }
+ before { Namer.current_index += 1 }
+ before { Namer.incrementing_value = 0 }
+
+ context "when the resource has identity, state and control properties" do
+ let(:resource_name) { :"converge_if_changed_dsl#{Namer.current_index}" }
+ let(:resource_class) {
+ result = Class.new(Chef::Resource) do
+ def self.to_s; resource_name; end
+ def self.inspect; resource_name.inspect; end
+ property :identity1, identity: true, default: 'default_identity1'
+ property :control1, desired_state: false, default: 'default_control1'
+ property :state1, default: 'default_state1'
+ property :state2, default: 'default_state2'
+ attr_accessor :converged
+ def initialize(*args)
+ super
+ @converged = 0
+ end
+ end
+ result.resource_name resource_name
+ result
+ }
+ let(:converged_recipe) { converge(converge_recipe) }
+ let(:resource) { converged_recipe.resources.first }
+
+ context "and converge_if_changed with no parameters" do
+ before :each do
+ resource_class.action :create do
+ converge_if_changed do
+ new_resource.converged += 1
+ end
+ end
+ end
+
+ context "and current_resource with state1=current, state2=current" do
+ before :each do
+ resource_class.load_current_value do
+ state1 'current_state1'
+ state2 'current_state2'
+ end
+ end
+
+ context "and nothing is set" do
+ let(:converge_recipe) { "#{resource_name} 'blah'" }
+
+ it "the resource updates nothing" do
+ expect(resource.converged).to eq 0
+ expect(resource.updated?).to be_falsey
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+ EOM
+ end
+ end
+
+ context "and state1 is set to a new value" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'new_state1'
+ end
+ EOM
+ }
+
+ it "the resource updates state1" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - update default_identity1
+ - set state1 to "new_state1" (was "current_state1")
+ EOM
+ end
+ end
+
+ context "and state1 and state2 are set to new values" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'new_state1'
+ state2 'new_state2'
+ end
+ EOM
+ }
+
+ it "the resource updates state1 and state2" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - update default_identity1
+ - set state1 to "new_state1" (was "current_state1")
+ - set state2 to "new_state2" (was "current_state2")
+EOM
+ end
+ end
+
+ context "and state1 is set to its current value but state2 is set to a new value" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'current_state1'
+ state2 'new_state2'
+ end
+ EOM
+ }
+
+ it "the resource updates state2" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - update default_identity1
+ - set state2 to "new_state2" (was "current_state2")
+EOM
+ end
+ end
+
+ context "and state1 and state2 are set to their current values" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'current_state1'
+ state2 'current_state2'
+ end
+ EOM
+ }
+
+ it "the resource updates nothing" do
+ expect(resource.converged).to eq 0
+ expect(resource.updated?).to be_falsey
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+ end
+ end
+
+ context "and identity1 and control1 are set to new values" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ identity1 'new_identity1'
+ control1 'new_control1'
+ end
+ EOM
+ }
+
+ # Because the identity value is copied over to the new resource, by
+ # default they do not register as "changed"
+ it "the resource updates nothing" do
+ expect(resource.converged).to eq 0
+ expect(resource.updated?).to be_falsey
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+ end
+ end
+ end
+
+ context "and current_resource with identity1=current, control1=current" do
+ before :each do
+ resource_class.load_current_value do
+ identity1 'current_identity1'
+ control1 'current_control1'
+ end
+ end
+
+ context "and identity1 and control1 are set to new values" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ identity1 'new_identity1'
+ control1 'new_control1'
+ end
+ EOM
+ }
+
+ # Control values are not desired state and are therefore not considered
+ # a reason for converging.
+ it "the resource updates identity1" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - update current_identity1
+ - set identity1 to "new_identity1" (was "current_identity1")
+ EOM
+ end
+ end
+ end
+
+ context "and has no current_resource" do
+ before :each do
+ resource_class.load_current_value do
+ current_value_does_not_exist!
+ end
+ end
+
+ context "and nothing is set" do
+ let(:converge_recipe) { "#{resource_name} 'blah'" }
+
+ it "the resource is created" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - create default_identity1
+ - set identity1 to "default_identity1" (default value)
+ - set state1 to "default_state1" (default value)
+ - set state2 to "default_state2" (default value)
+EOM
+ end
+ end
+
+ context "and state1 and state2 are set" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'new_state1'
+ state2 'new_state2'
+ end
+ EOM
+ }
+
+ it "the resource is created" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - create default_identity1
+ - set identity1 to "default_identity1" (default value)
+ - set state1 to "new_state1"
+ - set state2 to "new_state2"
+EOM
+ end
+ end
+ end
+ end
+
+ context "and separate converge_if_changed :state1 and converge_if_changed :state2" do
+ before :each do
+ resource_class.action :create do
+ converge_if_changed :state1 do
+ new_resource.converged += 1
+ end
+ converge_if_changed :state2 do
+ new_resource.converged += 1
+ end
+ end
+ end
+
+ context "and current_resource with state1=current, state2=current" do
+ before :each do
+ resource_class.load_current_value do
+ state1 'current_state1'
+ state2 'current_state2'
+ end
+ end
+
+ context "and nothing is set" do
+ let(:converge_recipe) { "#{resource_name} 'blah'" }
+
+ it "the resource updates nothing" do
+ expect(resource.converged).to eq 0
+ expect(resource.updated?).to be_falsey
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+ end
+ end
+
+ context "and state1 is set to a new value" do
+
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'new_state1'
+ end
+ EOM
+ }
+
+ it "the resource updates state1" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - update default_identity1
+ - set state1 to "new_state1" (was "current_state1")
+EOM
+ end
+ end
+
+ context "and state1 and state2 are set to new values" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'new_state1'
+ state2 'new_state2'
+ end
+ EOM
+ }
+
+ it "the resource updates state1 and state2" do
+ expect(resource.converged).to eq 2
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - update default_identity1
+ - set state1 to "new_state1" (was "current_state1")
+ - update default_identity1
+ - set state2 to "new_state2" (was "current_state2")
+EOM
+ end
+ end
+
+ context "and state1 is set to its current value but state2 is set to a new value" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'current_state1'
+ state2 'new_state2'
+ end
+ EOM
+ }
+
+ it "the resource updates state2" do
+ expect(resource.converged).to eq 1
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - update default_identity1
+ - set state2 to "new_state2" (was "current_state2")
+EOM
+ end
+ end
+
+ context "and state1 and state2 are set to their current values" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'current_state1'
+ state2 'current_state2'
+ end
+ EOM
+ }
+
+ it "the resource updates nothing" do
+ expect(resource.converged).to eq 0
+ expect(resource.updated?).to be_falsey
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create (up to date)
+EOM
+ end
+ end
+ end
+
+ context "and no current_resource" do
+ before :each do
+ resource_class.load_current_value do
+ current_value_does_not_exist!
+ end
+ end
+
+ context "and nothing is set" do
+ let(:converge_recipe) {
+ "#{resource_name} 'blah'"
+ }
+
+ it "the resource is created" do
+ expect(resource.converged).to eq 2
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - create default_identity1
+ - set state1 to "default_state1" (default value)
+ - create default_identity1
+ - set state2 to "default_state2" (default value)
+EOM
+ end
+ end
+
+ context "and state1 and state2 are set to new values" do
+ let(:converge_recipe) {
+ <<-EOM
+ #{resource_name} 'blah' do
+ state1 'new_state1'
+ state2 'new_state2'
+ end
+ EOM
+ }
+
+ it "the resource is created" do
+ expect(resource.converged).to eq 2
+ expect(resource.updated?).to be_truthy
+ expect(converged_recipe.stdout).to eq <<-EOM
+* #{resource_name}[blah] action create
+ - create default_identity1
+ - set state1 to "new_state1"
+ - create default_identity1
+ - set state2 to "new_state2"
+EOM
+ end
+ end
+ end
+ end
+
+ end
+end
diff --git a/spec/integration/recipes/resource_load_spec.rb b/spec/integration/recipes/resource_load_spec.rb
new file mode 100644
index 0000000000..c29b877b59
--- /dev/null
+++ b/spec/integration/recipes/resource_load_spec.rb
@@ -0,0 +1,206 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Resource.load_current_value" do
+ include IntegrationSupport
+
+ module Namer
+ extend self
+ attr_accessor :current_index
+ def incrementing_value
+ @incrementing_value += 1
+ @incrementing_value
+ end
+ attr_writer :incrementing_value
+ end
+
+ before(:all) { Namer.current_index = 1 }
+ before { Namer.current_index += 1 }
+ before { Namer.incrementing_value = 0 }
+
+ let(:resource_name) { :"load_current_value_dsl#{Namer.current_index}" }
+ let(:resource_class) {
+ result = Class.new(Chef::Resource) do
+ def self.to_s; resource_name; end
+ def self.inspect; resource_name.inspect; end
+ property :x, default: lazy { "default #{Namer.incrementing_value}" }
+ def self.created_x=(value)
+ @created = value
+ end
+ def self.created_x
+ @created
+ end
+ action :create do
+ new_resource.class.created_x = x
+ end
+ end
+ result.resource_name resource_name
+ result
+ }
+
+ # Pull on resource_class to initialize it
+ before { resource_class }
+
+ context "with a resource with load_current_value" do
+ before :each do
+ resource_class.load_current_value do
+ x "loaded #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+ select { |name,p| p.is_set?(self) }.
+ map { |name,p| "#{name}=#{p.get(self)}" }.
+ join(", ") })"
+ end
+ end
+
+ context "and a resource with x set to a desired value" do
+ let(:resource) do
+ e = self
+ r = nil
+ converge {
+ r = public_send(e.resource_name, 'blah') do
+ x 'desired'
+ end
+ }
+ r
+ end
+
+ it "current_resource is passed name but not x" do
+ expect(resource.current_resource.x).to eq 'loaded 2 (name=blah)'
+ end
+
+ it "resource.current_resource returns a different resource" do
+ expect(resource.current_resource.x).to eq 'loaded 2 (name=blah)'
+ expect(resource.x).to eq 'desired'
+ end
+
+ it "resource.current_resource constructs the resource anew each time" do
+ expect(resource.current_resource.x).to eq 'loaded 2 (name=blah)'
+ expect(resource.current_resource.x).to eq 'loaded 3 (name=blah)'
+ end
+
+ it "the provider accesses the current value of x" do
+ expect(resource.class.created_x).to eq 'desired'
+ end
+
+ context "and identity: :i and :d with desired_state: false" do
+ before {
+ resource_class.class_eval do
+ property :i, identity: true
+ property :d, desired_state: false
+ end
+ }
+
+ before {
+ resource.i 'desired_i'
+ resource.d 'desired_d'
+ }
+
+ it "i, name and d are passed to load_current_value, but not x" do
+ expect(resource.current_resource.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)'
+ end
+ end
+
+ context "and name_property: :i and :d with desired_state: false" do
+ before {
+ resource_class.class_eval do
+ property :i, name_property: true
+ property :d, desired_state: false
+ end
+ }
+
+ before {
+ resource.i 'desired_i'
+ resource.d 'desired_d'
+ }
+
+ it "i, name and d are passed to load_current_value, but not x" do
+ expect(resource.current_resource.x).to eq 'loaded 2 (d=desired_d, i=desired_i, name=blah)'
+ end
+ end
+ end
+
+ context "and a resource with no values set" do
+ let(:resource) do
+ e = self
+ r = nil
+ converge {
+ r = public_send(e.resource_name, 'blah') do
+ end
+ }
+ r
+ end
+
+ it "the provider accesses values from load_current_value" do
+ expect(resource.class.created_x).to eq 'loaded 1 (name=blah)'
+ end
+ end
+
+ let (:subresource_name) {
+ :"load_current_value_subresource_dsl#{Namer.current_index}"
+ }
+ let (:subresource_class) {
+ r = Class.new(resource_class) do
+ property :y, default: lazy { "default_y #{Namer.incrementing_value}" }
+ end
+ r.resource_name subresource_name
+ r
+ }
+
+ # Pull on subresource_class to initialize it
+ before { subresource_class }
+
+ let(:subresource) do
+ e = self
+ r = nil
+ converge {
+ r = public_send(e.subresource_name, 'blah') do
+ x 'desired'
+ end
+ }
+ r
+ end
+
+ context "and a child resource class with no load_current_value" do
+ it "the parent load_current_value is used" do
+ expect(subresource.current_resource.x).to eq 'loaded 2 (name=blah)'
+ end
+ it "load_current_value yields a copy of the child class" do
+ expect(subresource.current_resource).to be_kind_of(subresource_class)
+ end
+ end
+
+ context "And a child resource class with load_current_value" do
+ before {
+ subresource_class.load_current_value do
+ y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+ select { |name,p| p.is_set?(self) }.
+ map { |name,p| "#{name}=#{p.get(self)}" }.
+ join(", ") })"
+ end
+ }
+
+ it "the overridden load_current_value is used" do
+ current_resource = subresource.current_resource
+ expect(current_resource.x).to eq 'default 3'
+ expect(current_resource.y).to eq 'loaded_y 2 (name=blah)'
+ end
+ end
+
+ context "and a child resource class with load_current_value calling super()" do
+ before {
+ subresource_class.load_current_value do
+ super()
+ y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name,p| name }.
+ select { |name,p| p.is_set?(self) }.
+ map { |name,p| "#{name}=#{p.get(self)}" }.
+ join(", ") })"
+ end
+ }
+
+ it "the original load_current_value is called as well as the child one" do
+ current_resource = subresource.current_resource
+ expect(current_resource.x).to eq 'loaded 3 (name=blah)'
+ expect(current_resource.y).to eq 'loaded_y 4 (name=blah, x=loaded 3 (name=blah))'
+ end
+ end
+ end
+
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index dcf244c3cc..aadf55f64b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -87,12 +87,14 @@ Dir["spec/support/**/*.rb"].
OHAI_SYSTEM = Ohai::System.new
OHAI_SYSTEM.all_plugins("platform")
-TEST_PLATFORM =
- (OHAI_SYSTEM['platform'] ||
- 'unknown_test_platform').dup.freeze
-TEST_PLATFORM_VERSION =
- (OHAI_SYSTEM['platform_version'] ||
- 'unknown_platform_version').dup.freeze
+test_node = Chef::Node.new
+test_node.automatic['os'] = (OHAI_SYSTEM['os'] || 'unknown_os').dup.freeze
+test_node.automatic['platform_family'] = (OHAI_SYSTEM['platform_family'] || 'unknown_platform_family').dup.freeze
+test_node.automatic['platform'] = (OHAI_SYSTEM['platform'] || 'unknown_platform').dup.freeze
+test_node.automatic['platform_version'] = (OHAI_SYSTEM['platform_version'] || 'unknown_platform_version').dup.freeze
+TEST_NODE = test_node.freeze
+TEST_PLATFORM = TEST_NODE['platform']
+TEST_PLATFORM_VERSION = TEST_NODE['platform_version']
RSpec.configure do |config|
config.include(Matchers)
@@ -116,6 +118,7 @@ RSpec.configure do |config|
config.filter_run_excluding :volatile_from_verify => false
config.filter_run_excluding :skip_appveyor => true if ENV["APPVEYOR"]
+ config.filter_run_excluding :appveyor_only => true unless ENV["APPVEYOR"]
config.filter_run_excluding :windows_only => true unless windows?
config.filter_run_excluding :not_supported_on_mac_osx_106 => true if mac_osx_106?
@@ -127,6 +130,8 @@ RSpec.configure do |config|
config.filter_run_excluding :windows_2008r2_or_later => true unless windows_2008r2_or_later?
config.filter_run_excluding :windows64_only => true unless windows64?
config.filter_run_excluding :windows32_only => true unless windows32?
+ config.filter_run_excluding :ruby64_only => true unless ruby_64bit?
+ config.filter_run_excluding :ruby32_only => true unless ruby_32bit?
config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc?
config.filter_run_excluding :windows_powershell_no_dsc_only => true unless ! windows_powershell_dsc?
config.filter_run_excluding :windows_domain_joined_only => true unless windows_domain_joined?
@@ -162,13 +167,17 @@ RSpec.configure do |config|
config.filter_run_excluding :provider => lambda {|criteria|
type, target_provider = criteria.first
- platform = TEST_PLATFORM.dup
- platform_version = TEST_PLATFORM_VERSION.dup
-
- begin
- provider_for_running_platform = Chef::Platform.find_provider(platform, platform_version, type)
- provider_for_running_platform != target_provider
- rescue ArgumentError # no provider for platform
+ node = TEST_NODE.dup
+ resource_class = Chef::ResourceResolver.resolve(type, node: node)
+ if resource_class
+ resource = resource_class.new('test', Chef::RunContext.new(node, nil, nil))
+ begin
+ provider = resource.provider_for_action(Array(resource_class.default_action).first)
+ provider.class != target_provider
+ rescue Chef::Exceptions::ProviderNotFound # no provider for platform
+ true
+ end
+ else
true
end
}
@@ -176,6 +185,8 @@ RSpec.configure do |config|
config.run_all_when_everything_filtered = true
config.before(:each) do
+ Chef.reset!
+
Chef::Config.reset
# By default, treat deprecation warnings as errors in tests.
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
index da0313758b..1cfad05172 100644
--- a/spec/support/platform_helpers.rb
+++ b/spec/support/platform_helpers.rb
@@ -26,6 +26,14 @@ def ruby_20?
!!(RUBY_VERSION =~ /^2.0/)
end
+def ruby_64bit?
+ !!(RbConfig::CONFIG['host_cpu'] =~ /x86_64/)
+end
+
+def ruby_32bit?
+ !!(RbConfig::CONFIG['host_cpu'] =~ /i686/)
+end
+
def windows?
!!(RUBY_PLATFORM =~ /mswin|mingw|windows/)
end
diff --git a/spec/support/shared/functional/win32_service.rb b/spec/support/shared/functional/win32_service.rb
index 7dd1920418..2ee1a8ad88 100644
--- a/spec/support/shared/functional/win32_service.rb
+++ b/spec/support/shared/functional/win32_service.rb
@@ -46,7 +46,8 @@ shared_context "using Win32::Service" do
:service_name => "spec-service",
:service_display_name => "Spec Test Service",
:service_description => "Service for testing Chef::Application::WindowsServiceManager.",
- :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../platforms/win32/spec_service.rb'))
+ :service_file_path => File.expand_path(File.join(File.dirname(__FILE__), '../../platforms/win32/spec_service.rb')),
+ :delayed_start => true
}
}
diff --git a/spec/support/shared/shared_examples.rb b/spec/support/shared/shared_examples.rb
index b20c65f8b6..550fa2eb68 100644
--- a/spec/support/shared/shared_examples.rb
+++ b/spec/support/shared/shared_examples.rb
@@ -1,7 +1,7 @@
# For storing any examples shared between multiple tests
# Any object which defines a .to_json should import this test
-shared_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+shared_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) {
raise "You must define the subject when including this test"
diff --git a/spec/support/shared/unit/api_versioning.rb b/spec/support/shared/unit/api_versioning.rb
index a4f353de60..05a4117f8e 100644
--- a/spec/support/shared/unit/api_versioning.rb
+++ b/spec/support/shared/unit/api_versioning.rb
@@ -26,7 +26,7 @@ shared_examples_for "version handling" do
allow(rest_v1).to receive(http_verb).and_raise(exception_406)
end
- context "when the server does not support the min or max server API version that Chef::User supports" do
+ context "when the server does not support the min or max server API version that Chef::UserV1 supports" do
before do
allow(object).to receive(:server_client_api_version_intersection).and_return([])
end
@@ -34,7 +34,7 @@ shared_examples_for "version handling" do
it "raises the original exception" do
expect{ object.send(method) }.to raise_error(exception_406)
end
- end # when the server does not support the min or max server API version that Chef::User supports
+ end # when the server does not support the min or max server API version that Chef::UserV1 supports
end # version handling
shared_examples_for "user and client reregister" do
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
index ba0eca3284..a0e399b470 100644
--- a/spec/unit/api_client_spec.rb
+++ b/spec/unit/api_client_spec.rb
@@ -21,6 +21,11 @@ require 'spec_helper'
require 'chef/api_client'
require 'tempfile'
+# DEPRECATION NOTE
+#
+# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1,
+# which will be moved to this namespace. New development should occur in
+# Chef::ApiClientV1 until the time before Chef 13.
describe Chef::ApiClient do
before(:each) do
@client = Chef::ApiClient.new
@@ -53,20 +58,6 @@ describe Chef::ApiClient do
expect { @client.admin(Hash.new) }.to raise_error(ArgumentError)
end
- it "has an create_key flag attribute" do
- @client.create_key(true)
- expect(@client.create_key).to be_truthy
- end
-
- it "create_key defaults to false" do
- expect(@client.create_key).to be_falsey
- end
-
- it "allows only boolean values for the create_key flag" do
- expect { @client.create_key(false) }.not_to raise_error
- expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError)
- end
-
it "has a 'validator' flag attribute" do
@client.validator(true)
expect(@client.validator).to be_truthy
@@ -129,12 +120,6 @@ describe Chef::ApiClient do
expect(@json).to include(%q{"validator":false})
end
- it "includes the 'create_key' flag when present" do
- @client.create_key(true)
- @json = @client.to_json
- expect(@json).to include(%q{"create_key":true})
- end
-
it "includes the private key when present" do
@client.private_key("monkeypants")
expect(@client.to_json).to include(%q{"private_key":"monkeypants"})
@@ -143,15 +128,11 @@ describe Chef::ApiClient do
it "does not include the private key if not present" do
expect(@json).not_to include("private_key")
end
-
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
- let(:jsonable) { @client }
- end
end
describe "when deserializing from JSON (string) using ApiClient#from_json" do
let(:client_string) do
- "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}"
+ "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true}"
end
let(:client) do
@@ -178,10 +159,6 @@ describe Chef::ApiClient do
expect(client.admin).to be_truthy
end
- it "preserves the create_key status" do
- expect(client.create_key).to be_truthy
- end
-
it "preserves the 'validator' status" do
expect(client.validator).to be_truthy
end
@@ -199,7 +176,6 @@ describe Chef::ApiClient do
"private_key" => "monkeypants",
"admin" => true,
"validator" => true,
- "create_key" => true,
"json_class" => "Chef::ApiClient"
}
end
@@ -224,10 +200,6 @@ describe Chef::ApiClient do
expect(client.admin).to be_truthy
end
- it "preserves the create_key status" do
- expect(client.create_key).to be_truthy
- end
-
it "preserves the 'validator' status" do
expect(client.validator).to be_truthy
end
@@ -243,18 +215,16 @@ describe Chef::ApiClient do
before(:each) do
client = {
- "name" => "black",
- "clientname" => "black",
- "public_key" => "crowes",
- "private_key" => "monkeypants",
- "admin" => true,
- "create_key" => true,
- "validator" => true,
- "json_class" => "Chef::ApiClient"
+ "name" => "black",
+ "clientname" => "black",
+ "public_key" => "crowes",
+ "private_key" => "monkeypants",
+ "admin" => true,
+ "validator" => true,
+ "json_class" => "Chef::ApiClient"
}
-
- @http_client = double("Chef::REST mock")
- allow(Chef::REST).to receive(:new).and_return(@http_client)
+ @http_client = double("Chef::ServerAPI mock")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
expect(@http_client).to receive(:get).with("clients/black").and_return(client)
@client = Chef::ApiClient.load(client['name'])
end
@@ -275,10 +245,6 @@ describe Chef::ApiClient do
expect(@client.admin).to be_a_kind_of(TrueClass)
end
- it "preserves the create_key status" do
- expect(@client.create_key).to be_a_kind_of(TrueClass)
- end
-
it "preserves the 'validator' status" do
expect(@client.validator).to be_a_kind_of(TrueClass)
end
@@ -304,18 +270,13 @@ describe Chef::ApiClient do
File.open(Chef::Config[:client_key], "r") {|f| f.read.chomp }
end
- it "has an HTTP client configured with default credentials" do
- expect(@client.http_api).to be_a_kind_of(Chef::REST)
- expect(@client.http_api.client_name).to eq("silent-bob")
- expect(@client.http_api.signing_key.to_s).to eq(private_key_data)
- end
end
describe "when requesting a new key" do
before do
@http_client = double("Chef::REST mock")
- allow(Chef::REST).to receive(:new).and_return(@http_client)
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
end
context "and the client does not exist on the server" do
@@ -332,34 +293,24 @@ describe Chef::ApiClient do
end
context "and the client exists" do
- let(:chef_rest_v0_mock) { double('chef rest root v0 object') }
- let(:payload) {
- {:name => "lost-my-key", :admin => false, :validator => false, :private_key => true}
- }
-
before do
@api_client_without_key = Chef::ApiClient.new
@api_client_without_key.name("lost-my-key")
- allow(@api_client_without_key).to receive(:chef_rest_v0).and_return(chef_rest_v0_mock)
- #allow(@api_client_with_key).to receive(:http_api).and_return(_api_mock)
-
- allow(chef_rest_v0_mock).to receive(:put).with("clients/lost-my-key", payload).and_return(@api_client_with_key)
- allow(chef_rest_v0_mock).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key)
- allow(@http_client).to receive(:get).with("clients/lost-my-key").and_return(@api_client_without_key)
+ expect(@http_client).to 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")
- allow(@api_client_with_key).to receive(:chef_rest_v0).and_return(chef_rest_v0_mock)
+ expect(@http_client).to receive(:put).
+ with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true).
+ and_return(@api_client_with_key)
end
it "returns an ApiClient with a private key" do
- expect(chef_rest_v0_mock).to receive(:put).with("clients/lost-my-key", payload).
- and_return(@api_client_with_key)
-
response = Chef::ApiClient.reregister("lost-my-key")
# no sane == method for ApiClient :'(
expect(response).to eq(@api_client_without_key)
@@ -372,7 +323,7 @@ describe Chef::ApiClient do
context "and the client exists on a Chef 10-like server" do
before do
@api_client_with_key = {"name" => "lost-my-key", "private_key" => "the new private key"}
- expect(chef_rest_v0_mock).to receive(:put).
+ expect(@http_client).to receive(:put).
with("clients/lost-my-key", :name => "lost-my-key", :admin => false, :validator => false, :private_key => true).
and_return(@api_client_with_key)
end
@@ -390,134 +341,4 @@ describe Chef::ApiClient do
end
end
-
- describe "Versioned API Interactions" do
- let(:response_406) { OpenStruct.new(:code => '406') }
- let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
- let(:payload) {
- {
- :name => "some_name",
- :validator => true,
- :admin => true
- }
- }
-
- before do
- @client = Chef::ApiClient.new
- allow(@client).to receive(:chef_rest_v0).and_return(double('chef rest root v0 object'))
- allow(@client).to receive(:chef_rest_v1).and_return(double('chef rest root v1 object'))
- @client.name "some_name"
- @client.validator true
- @client.admin true
- end
-
- describe "create" do
-
- # from spec/support/shared/unit/user_and_client_shared.rb
- it_should_behave_like "user or client create" do
- let(:object) { @client }
- let(:error) { Chef::Exceptions::InvalidClientAttribute }
- let(:rest_v0) { @client.chef_rest_v0 }
- let(:rest_v1) { @client.chef_rest_v1 }
- let(:url) { "clients" }
- end
-
- context "when API V1 is not supported by the server" do
- # from spec/support/shared/unit/api_versioning.rb
- it_should_behave_like "version handling" do
- let(:object) { @client }
- let(:method) { :create }
- let(:http_verb) { :post }
- let(:rest_v1) { @client.chef_rest_v1 }
- end
- end
-
- end # create
-
- describe "update" do
- context "when a valid client is defined" do
-
- shared_examples_for "client updating" do
- it "updates the client" do
- expect(rest). to receive(:put).with("clients/some_name", payload)
- @client.update
- end
-
- context "when only the name field exists" do
-
- before do
- # needed since there is no way to set to nil via code
- @client.instance_variable_set(:@validator, nil)
- @client.instance_variable_set(:@admin, nil)
- end
-
- after do
- @client.validator true
- @client.admin true
- end
-
- it "updates the client with only the name" do
- expect(rest). to receive(:put).with("clients/some_name", {:name => "some_name"})
- @client.update
- end
- end
-
- end
-
- context "when API V1 is supported by the server" do
-
- it_should_behave_like "client updating" do
- let(:rest) { @client.chef_rest_v1 }
- end
-
- end # when API V1 is supported by the server
-
- context "when API V1 is not supported by the server" do
- context "when no version is supported" do
- # from spec/support/shared/unit/api_versioning.rb
- it_should_behave_like "version handling" do
- let(:object) { @client }
- let(:method) { :create }
- let(:http_verb) { :post }
- let(:rest_v1) { @client.chef_rest_v1 }
- end
- end # when no version is supported
-
- context "when API V0 is supported" do
-
- before do
- allow(@client.chef_rest_v1).to receive(:put).and_raise(exception_406)
- allow(@client).to receive(:server_client_api_version_intersection).and_return([0])
- end
-
- it_should_behave_like "client updating" do
- let(:rest) { @client.chef_rest_v0 }
- end
-
- end
-
- end # when API V1 is not supported by the server
- end # when a valid client is defined
- end # update
-
- # DEPRECATION
- # This can be removed after API V0 support is gone
- describe "reregister" do
- context "when server API V0 is valid on the Chef Server receiving the request" do
- it "creates a new object via the API" do
- expect(@client.chef_rest_v0).to receive(:put).with("clients/#{@client.name}", payload.merge({:private_key => true})).and_return({})
- @client.reregister
- end
- end # when server API V0 is valid on the Chef Server receiving the request
-
- context "when server API V0 is not supported by the Chef Server" do
- # from spec/support/shared/unit/api_versioning.rb
- it_should_behave_like "user and client reregister" do
- let(:object) { @client }
- let(:rest_v0) { @client.chef_rest_v0 }
- end
- end # when server API V0 is not supported by the Chef Server
- end # reregister
-
- end
end
diff --git a/spec/unit/api_client_v1_spec.rb b/spec/unit/api_client_v1_spec.rb
new file mode 100644
index 0000000000..17aba8c3af
--- /dev/null
+++ b/spec/unit/api_client_v1_spec.rb
@@ -0,0 +1,457 @@
+#
+# Author:: Adam Jacob (<adam@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_v1'
+require 'tempfile'
+
+describe Chef::ApiClientV1 do
+ before(:each) do
+ @client = Chef::ApiClientV1.new
+ end
+
+ it "has a name attribute" do
+ @client.name("ops_master")
+ expect(@client.name).to eq("ops_master")
+ end
+
+ it "does not allow spaces in the name" do
+ expect { @client.name "ops master" }.to raise_error(ArgumentError)
+ end
+
+ it "only allows string values for the name" do
+ expect { @client.name Hash.new }.to raise_error(ArgumentError)
+ end
+
+ it "has an admin flag attribute" do
+ @client.admin(true)
+ expect(@client.admin).to be_truthy
+ end
+
+ it "defaults to non-admin" do
+ expect(@client.admin).to be_falsey
+ end
+
+ it "allows only boolean values for the admin flag" do
+ expect { @client.admin(false) }.not_to raise_error
+ expect { @client.admin(Hash.new) }.to raise_error(ArgumentError)
+ end
+
+ it "has an create_key flag attribute" do
+ @client.create_key(true)
+ expect(@client.create_key).to be_truthy
+ end
+
+ it "create_key defaults to false" do
+ expect(@client.create_key).to be_falsey
+ end
+
+ it "allows only boolean values for the create_key flag" do
+ expect { @client.create_key(false) }.not_to raise_error
+ expect { @client.create_key(Hash.new) }.to raise_error(ArgumentError)
+ end
+
+ it "has a 'validator' flag attribute" do
+ @client.validator(true)
+ expect(@client.validator).to be_truthy
+ end
+
+ it "defaults to non-validator" do
+ expect(@client.validator).to be_falsey
+ end
+
+ it "allows only boolean values for the 'validator' flag" do
+ expect { @client.validator(false) }.not_to raise_error
+ expect { @client.validator(Hash.new) }.to raise_error(ArgumentError)
+ end
+
+ it "has a public key attribute" do
+ @client.public_key("super public")
+ expect(@client.public_key).to eq("super public")
+ end
+
+ it "accepts only String values for the public key" do
+ expect { @client.public_key "" }.not_to raise_error
+ expect { @client.public_key Hash.new }.to raise_error(ArgumentError)
+ end
+
+
+ it "has a private key attribute" do
+ @client.private_key("super private")
+ expect(@client.private_key).to eq("super private")
+ end
+
+ it "accepts only String values for the private key" do
+ expect { @client.private_key "" }.not_to raise_error
+ expect { @client.private_key Hash.new }.to 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
+ expect(@json).to match(/^\{.+\}$/)
+ end
+
+ it "includes the name value" do
+ expect(@json).to include(%q{"name":"black"})
+ end
+
+ it "includes the public key value" do
+ expect(@json).to include(%{"public_key":"crowes"})
+ end
+
+ it "includes the 'admin' flag" do
+ expect(@json).to include(%q{"admin":false})
+ end
+
+ it "includes the 'validator' flag" do
+ expect(@json).to include(%q{"validator":false})
+ end
+
+ it "includes the 'create_key' flag when present" do
+ @client.create_key(true)
+ @json = @client.to_json
+ expect(@json).to include(%q{"create_key":true})
+ end
+
+ it "includes the private key when present" do
+ @client.private_key("monkeypants")
+ expect(@client.to_json).to include(%q{"private_key":"monkeypants"})
+ end
+
+ it "does not include the private key if not present" do
+ expect(@json).not_to include("private_key")
+ end
+
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
+ let(:jsonable) { @client }
+ end
+ end
+
+ describe "when deserializing from JSON (string) using ApiClient#from_json" do
+ let(:client_string) do
+ "{\"name\":\"black\",\"public_key\":\"crowes\",\"private_key\":\"monkeypants\",\"admin\":true,\"validator\":true,\"create_key\":true}"
+ end
+
+ let(:client) do
+ Chef::ApiClientV1.from_json(client_string)
+ end
+
+ it "does not require a 'json_class' string" do
+ expect(Chef::JSONCompat.parse(client_string)["json_class"]).to eq(nil)
+ end
+
+ it "should deserialize to a Chef::ApiClientV1 object" do
+ expect(client).to be_a_kind_of(Chef::ApiClientV1)
+ end
+
+ it "preserves the name" do
+ expect(client.name).to eq("black")
+ end
+
+ it "preserves the public key" do
+ expect(client.public_key).to eq("crowes")
+ end
+
+ it "preserves the admin status" do
+ expect(client.admin).to be_truthy
+ end
+
+ it "preserves the create_key status" do
+ expect(client.create_key).to be_truthy
+ end
+
+ it "preserves the 'validator' status" do
+ expect(client.validator).to be_truthy
+ end
+
+ it "includes the private key if present" do
+ expect(client.private_key).to eq("monkeypants")
+ end
+ end
+
+ describe "when deserializing from JSON (hash) using ApiClientV1#from_json" do
+ let(:client_hash) do
+ {
+ "name" => "black",
+ "public_key" => "crowes",
+ "private_key" => "monkeypants",
+ "admin" => true,
+ "validator" => true,
+ "create_key" => true
+ }
+ end
+
+ let(:client) do
+ Chef::ApiClientV1.from_json(Chef::JSONCompat.to_json(client_hash))
+ end
+
+ it "should deserialize to a Chef::ApiClientV1 object" do
+ expect(client).to be_a_kind_of(Chef::ApiClientV1)
+ end
+
+ it "preserves the name" do
+ expect(client.name).to eq("black")
+ end
+
+ it "preserves the public key" do
+ expect(client.public_key).to eq("crowes")
+ end
+
+ it "preserves the admin status" do
+ expect(client.admin).to be_truthy
+ end
+
+ it "preserves the create_key status" do
+ expect(client.create_key).to be_truthy
+ end
+
+ it "preserves the 'validator' status" do
+ expect(client.validator).to be_truthy
+ end
+
+ it "includes the private key if present" do
+ expect(client.private_key).to eq("monkeypants")
+ end
+ end
+
+ describe "when loading from JSON" do
+ before do
+ end
+
+ before(:each) do
+ client = {
+ "name" => "black",
+ "clientname" => "black",
+ "public_key" => "crowes",
+ "private_key" => "monkeypants",
+ "admin" => true,
+ "create_key" => true,
+ "validator" => true
+ }
+
+ @http_client = double("Chef::ServerAPI mock")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
+ expect(@http_client).to receive(:get).with("clients/black").and_return(client)
+ @client = Chef::ApiClientV1.load(client['name'])
+ end
+
+ it "should deserialize to a Chef::ApiClientV1 object" do
+ expect(@client).to be_a_kind_of(Chef::ApiClientV1)
+ end
+
+ it "preserves the name" do
+ expect(@client.name).to eq("black")
+ end
+
+ it "preserves the public key" do
+ expect(@client.public_key).to eq("crowes")
+ end
+
+ it "preserves the admin status" do
+ expect(@client.admin).to be_a_kind_of(TrueClass)
+ end
+
+ it "preserves the create_key status" do
+ expect(@client.create_key).to be_a_kind_of(TrueClass)
+ end
+
+ it "preserves the 'validator' status" do
+ expect(@client.validator).to be_a_kind_of(TrueClass)
+ end
+
+ it "includes the private key if present" do
+ expect(@client.private_key).to eq("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
+
+ end
+
+
+ describe "when requesting a new key" do
+ before do
+ @http_client = double("Chef::ServerAPI mock")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
+ end
+
+ context "and the client does not exist on the server" do
+ 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)
+
+ expect(@http_client).to receive(:get).with("clients/lost-my-key").and_raise(@a_404_exception)
+ end
+
+ it "raises a 404 error" do
+ expect { Chef::ApiClientV1.reregister("lost-my-key") }.to raise_error(Net::HTTPServerException)
+ end
+ end
+ end
+
+ describe "Versioned API Interactions" do
+ let(:response_406) { OpenStruct.new(:code => '406') }
+ let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+ let(:payload) {
+ {
+ :name => "some_name",
+ :validator => true,
+ :admin => true
+ }
+ }
+
+ before do
+ @client = Chef::ApiClientV1.new
+ allow(@client).to receive(:chef_rest_v0).and_return(double('chef rest root v0 object'))
+ allow(@client).to receive(:chef_rest_v1).and_return(double('chef rest root v1 object'))
+ @client.name "some_name"
+ @client.validator true
+ @client.admin true
+ end
+
+ describe "create" do
+
+ # from spec/support/shared/unit/user_and_client_shared.rb
+ it_should_behave_like "user or client create" do
+ let(:object) { @client }
+ let(:error) { Chef::Exceptions::InvalidClientAttribute }
+ let(:rest_v0) { @client.chef_rest_v0 }
+ let(:rest_v1) { @client.chef_rest_v1 }
+ let(:url) { "clients" }
+ end
+
+ context "when API V1 is not supported by the server" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "version handling" do
+ let(:object) { @client }
+ let(:method) { :create }
+ let(:http_verb) { :post }
+ let(:rest_v1) { @client.chef_rest_v1 }
+ end
+ end
+
+ end # create
+
+ describe "update" do
+ context "when a valid client is defined" do
+
+ shared_examples_for "client updating" do
+ it "updates the client" do
+ expect(rest). to receive(:put).with("clients/some_name", payload).and_return(payload)
+ @client.update
+ end
+
+ context "when only the name field exists" do
+
+ before do
+ # needed since there is no way to set to nil via code
+ @client.instance_variable_set(:@validator, nil)
+ @client.instance_variable_set(:@admin, nil)
+ end
+
+ after do
+ @client.validator true
+ @client.admin true
+ end
+
+ it "updates the client with only the name" do
+ expect(rest). to receive(:put).with("clients/some_name", {:name => "some_name"}).and_return({:name => "some_name"})
+ @client.update
+ end
+ end
+
+ end
+
+ context "when API V1 is supported by the server" do
+
+ it_should_behave_like "client updating" do
+ let(:rest) { @client.chef_rest_v1 }
+ end
+
+ end # when API V1 is supported by the server
+
+ context "when API V1 is not supported by the server" do
+ context "when no version is supported" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "version handling" do
+ let(:object) { @client }
+ let(:method) { :create }
+ let(:http_verb) { :post }
+ let(:rest_v1) { @client.chef_rest_v1 }
+ end
+ end # when no version is supported
+
+ context "when API V0 is supported" do
+
+ before do
+ allow(@client.chef_rest_v1).to receive(:put).and_raise(exception_406)
+ allow(@client).to receive(:server_client_api_version_intersection).and_return([0])
+ end
+
+ it_should_behave_like "client updating" do
+ let(:rest) { @client.chef_rest_v0 }
+ end
+
+ end
+
+ end # when API V1 is not supported by the server
+ end # when a valid client is defined
+ end # update
+
+ # DEPRECATION
+ # This can be removed after API V0 support is gone
+ describe "reregister" do
+ context "when server API V0 is valid on the Chef Server receiving the request" do
+ it "creates a new object via the API" do
+ expect(@client.chef_rest_v0).to receive(:put).with("clients/#{@client.name}", payload.merge({:private_key => true})).and_return({})
+ @client.reregister
+ end
+ end # when server API V0 is valid on the Chef Server receiving the request
+
+ context "when server API V0 is not supported by the Chef Server" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "user and client reregister" do
+ let(:object) { @client }
+ let(:rest_v0) { @client.chef_rest_v0 }
+ end
+ end # when server API V0 is not supported by the Chef Server
+ end # reregister
+
+ end
+end
diff --git a/spec/unit/application/solo_spec.rb b/spec/unit/application/solo_spec.rb
index 1785ecfc86..7013bfa0bc 100644
--- a/spec/unit/application/solo_spec.rb
+++ b/spec/unit/application/solo_spec.rb
@@ -106,7 +106,8 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
describe "when the recipe_url configuration option is specified" do
let(:tarfile) { StringIO.new("remote_tarball_content") }
let(:target_file) { StringIO.new }
-
+ let(:shellout) { double(run_command: nil, error!: nil, stdout: '') }
+
before do
Chef::Config[:cookbook_path] = "#{Dir.tmpdir}/chef-solo/cookbooks"
Chef::Config[:recipe_url] = "http://junglist.gen.nz/recipes.tgz"
@@ -117,7 +118,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
allow(app).to receive(:open).with("http://junglist.gen.nz/recipes.tgz").and_yield(tarfile)
allow(File).to receive(:open).with("#{Dir.tmpdir}/chef-solo/recipes.tgz", "wb").and_yield(target_file)
- allow(Chef::Mixin::Command).to receive(:run_command).and_return(true)
+ allow(Mixlib::ShellOut).to receive(:new).and_return(shellout)
end
it "should create the recipes path based on the parent of the cookbook path" do
@@ -136,7 +137,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
end
it "should untar the target file to the parent of the cookbook path" do
- expect(Chef::Mixin::Command).to receive(:run_command).with({:command => "tar zxvf #{Dir.tmpdir}/chef-solo/recipes.tgz -C #{Dir.tmpdir}/chef-solo"}).and_return(true)
+ expect(Mixlib::ShellOut).to receive(:new).with("tar zxvf #{Dir.tmpdir}/chef-solo/recipes.tgz -C #{Dir.tmpdir}/chef-solo")
app.reconfigure
end
end
diff --git a/spec/unit/chef_class_spec.rb b/spec/unit/chef_class_spec.rb
index 2528246be6..f1b877520c 100644
--- a/spec/unit/chef_class_spec.rb
+++ b/spec/unit/chef_class_spec.rb
@@ -46,10 +46,6 @@ describe "Chef class" do
Chef.set_provider_priority_map(provider_priority_map)
end
- after do
- Chef.reset!
- end
-
context "priority maps" do
context "#get_provider_priority_array" do
it "should use the current node to get the right priority_map" do
@@ -88,4 +84,27 @@ describe "Chef class" do
expect(Chef.node).to eql(node)
end
end
+
+ context '#event_handler' do
+ it 'adds a new handler' do
+ x = 1
+ Chef.event_handler do
+ on :converge_start do
+ x = 2
+ end
+ end
+ expect(Chef::Config[:event_handlers]).to_not be_empty
+ Chef::Config[:event_handlers].first.send(:converge_start)
+ expect(x).to eq(2)
+ end
+
+ it 'raise error if unknown event type is passed' do
+ expect do
+ Chef.event_handler do
+ on :yolo do
+ end
+ end
+ end.to raise_error(Chef::Exceptions::InvalidEventType)
+ end
+ end
end
diff --git a/spec/unit/chef_fs/path_util_spec.rb b/spec/unit/chef_fs/path_util_spec.rb
new file mode 100644
index 0000000000..42eb126dfb
--- /dev/null
+++ b/spec/unit/chef_fs/path_util_spec.rb
@@ -0,0 +1,108 @@
+#
+# Author:: Kartik Null Cating-Subramanian (<ksubramanian@chef.io>)
+# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/chef_fs/path_utils'
+
+describe Chef::ChefFS::PathUtils do
+ context 'invoking join' do
+ it 'joins well-behaved distinct path elements' do
+ expect(Chef::ChefFS::PathUtils.join('a', 'b', 'c')).to eq('a/b/c')
+ end
+
+ it 'strips extraneous slashes in the middle of paths' do
+ expect(Chef::ChefFS::PathUtils.join('a/', '/b', '/c/')).to eq('a/b/c')
+ expect(Chef::ChefFS::PathUtils.join('a/', '/b', '///c/')).to eq('a/b/c')
+ end
+
+ it 'preserves the whether the first element was absolute or not' do
+ expect(Chef::ChefFS::PathUtils.join('/a/', '/b', 'c/')).to eq('/a/b/c')
+ expect(Chef::ChefFS::PathUtils.join('///a/', '/b', 'c/')).to eq('/a/b/c')
+ end
+ end
+
+ context 'invoking is_absolute?' do
+ it 'confirms that paths starting with / are absolute' do
+ expect(Chef::ChefFS::PathUtils.is_absolute?('/foo/bar/baz')).to be true
+ expect(Chef::ChefFS::PathUtils.is_absolute?('/foo')).to be true
+ end
+
+ it 'confirms that paths starting with // are absolute even though that looks like some windows network path' do
+ expect(Chef::ChefFS::PathUtils.is_absolute?('//foo/bar/baz')).to be true
+ end
+
+ it 'confirms that root is indeed absolute' do
+ expect(Chef::ChefFS::PathUtils.is_absolute?('/')).to be true
+ end
+
+ it 'confirms that paths starting without / are relative' do
+ expect(Chef::ChefFS::PathUtils.is_absolute?('foo/bar/baz')).to be false
+ expect(Chef::ChefFS::PathUtils.is_absolute?('a')).to be false
+ end
+
+ it 'returns false for an empty path.' do
+ expect(Chef::ChefFS::PathUtils.is_absolute?('')).to be false
+ end
+ end
+
+ context 'invoking realest_path' do
+ let(:good_path) { File.dirname(__FILE__) }
+ let(:parent_path) { File.dirname(good_path) }
+
+ it 'handles paths with no wildcards or globs' do
+ expect(Chef::ChefFS::PathUtils.realest_path(good_path)).to eq(File.expand_path(good_path))
+ end
+
+ it 'handles paths with .. and .' do
+ expect(Chef::ChefFS::PathUtils.realest_path(good_path+'/../.')).to eq(File.expand_path(parent_path))
+ end
+
+ it 'handles paths with *' do
+ expect(Chef::ChefFS::PathUtils.realest_path(good_path + '/*/foo')).to eq(File.expand_path(good_path + '/*/foo'))
+ end
+
+ it 'handles directories that do not exist' do
+ expect(Chef::ChefFS::PathUtils.realest_path(good_path + '/something/or/other')).to eq(File.expand_path(good_path + '/something/or/other'))
+ end
+
+ it 'handles root correctly' do
+ if Chef::Platform.windows?
+ expect(Chef::ChefFS::PathUtils.realest_path('C:/')).to eq('C:/')
+ else
+ expect(Chef::ChefFS::PathUtils.realest_path('/')).to eq('/')
+ end
+ end
+ end
+
+ context 'invoking descendant_path' do
+ it 'handles paths with various casing on windows' do
+ allow(Chef::ChefFS).to receive(:windows?) { true }
+ expect(Chef::ChefFS::PathUtils.descendant_path('C:/ab/b/c', 'C:/AB/B')).to eq('c')
+ expect(Chef::ChefFS::PathUtils.descendant_path('C:/ab/b/c', 'c:/ab/B')).to eq('c')
+ end
+
+ it 'returns nil if the path does not have the given ancestor' do
+ expect(Chef::ChefFS::PathUtils.descendant_path('/D/E/F', '/A/B/C')).to be_nil
+ expect(Chef::ChefFS::PathUtils.descendant_path('/A/B/D', '/A/B/C')).to be_nil
+ end
+
+ it 'returns blank if the ancestor equals the path' do
+ expect(Chef::ChefFS::PathUtils.descendant_path('/A/B/D', '/A/B/D')).to eq('')
+ end
+ end
+end
diff --git a/spec/unit/client_spec.rb b/spec/unit/client_spec.rb
index 1e4bbb5c56..8146774764 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -238,23 +238,24 @@ describe Chef::Client do
describe "when converge completes successfully" do
include_context "a client run"
include_context "converge completed"
-
- describe "when audit phase errors" do
- include_context "audit phase failed with error"
- include_examples "a completed run with audit failure" do
- let(:run_errors) { [audit_error] }
+ context 'when audit mode is enabled' do
+ describe "when audit phase errors" do
+ include_context "audit phase failed with error"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
+ end
end
- end
- describe "when audit phase completed" do
- include_context "audit phase completed"
- include_examples "a completed run"
- end
+ describe "when audit phase completed" do
+ include_context "audit phase completed"
+ include_examples "a completed run"
+ end
- describe "when audit phase completed with failed controls" do
- include_context "audit phase completed with failed controls"
- include_examples "a completed run with audit failure" do
- let(:run_errors) { [audit_error] }
+ describe "when audit phase completed with failed controls" do
+ include_context "audit phase completed with failed controls"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
+ end
end
end
end
@@ -512,11 +513,26 @@ describe Chef::Client do
allow_any_instance_of(Chef::RunLock).to receive(:save_pid).and_raise(NoMethodError)
end
- it "should run exception handlers on early fail" do
- expect(subject).to receive(:run_failed)
- expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq 1
- expect(error.wrapped_errors).to include(NoMethodError)
+ context 'when audit mode is enabled' do
+ before do
+ Chef::Config[:audit_mode] = :enabled
+ end
+ it "should run exception handlers on early fail" do
+ expect(subject).to receive(:run_failed)
+ expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq 1
+ expect(error.wrapped_errors).to include(NoMethodError)
+ end
+ end
+ end
+
+ context 'when audit mode is disabled' do
+ before do
+ Chef::Config[:audit_mode] = :disabled
+ end
+ it "should run exception handlers on early fail" do
+ expect(subject).to receive(:run_failed)
+ expect { subject.run }.to raise_error(NoMethodError)
end
end
end
diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb
new file mode 100644
index 0000000000..8d155c61ab
--- /dev/null
+++ b/spec/unit/config_spec.rb
@@ -0,0 +1,31 @@
+
+require 'spec_helper'
+
+require 'chef/config'
+
+RSpec.describe Chef::Config do
+
+ shared_examples_for "deprecated by ohai but not deprecated" do
+ it "does not emit a deprecation warning when set" do
+ expect(Chef::Log).to_not receive(:warn).
+ with(/Ohai::Config\[:#{option}\] is deprecated/)
+ Chef::Config[option] = value
+ expect(Chef::Config[option]).to eq(value)
+ end
+ end
+
+ describe ":log_level" do
+ include_examples "deprecated by ohai but not deprecated" do
+ let(:option) { :log_level }
+ let(:value) { :debug }
+ end
+ end
+
+ describe ":log_location" do
+ include_examples "deprecated by ohai but not deprecated" do
+ let(:option) { :log_location }
+ let(:value) { "path/to/log" }
+ end
+ end
+
+end
diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb
index d2954726e8..1b30286f51 100644
--- a/spec/unit/cookbook/metadata_spec.rb
+++ b/spec/unit/cookbook/metadata_spec.rb
@@ -30,7 +30,7 @@ describe Chef::Cookbook::Metadata do
:maintainer_email, :license, :platforms, :dependencies,
:recommendations, :suggestions, :conflicting, :providing,
:replacing, :attributes, :groupings, :recipes, :version,
- :source_url, :issues_url ]
+ :source_url, :issues_url, :privacy ]
end
it "does not depend on object identity for equality" do
@@ -148,6 +148,10 @@ describe Chef::Cookbook::Metadata do
it "has an empty issues_url string" do
expect(metadata.issues_url).to eq('')
end
+
+ it "is not private" do
+ expect(metadata.privacy).to eq(false)
+ end
end
describe "validation" do
@@ -198,7 +202,8 @@ describe Chef::Cookbook::Metadata do
:long_description => "Much Longer\nSeriously",
:version => "0.6.0",
:source_url => "http://example.com",
- :issues_url => "http://example.com/issues"
+ :issues_url => "http://example.com/issues",
+ :privacy => true
}
params.sort { |a,b| a.to_s <=> b.to_s }.each do |field, field_value|
describe field do
@@ -360,7 +365,8 @@ describe Chef::Cookbook::Metadata do
"recipes" => [ "mysql::server", "mysql::master" ],
"default" => [ ],
"source_url" => "http://example.com",
- "issues_url" => "http://example.com/issues"
+ "issues_url" => "http://example.com/issues",
+ "privacy" => true
}
expect(metadata.attribute("/db/mysql/databases", attrs)).to eq(attrs)
end
@@ -401,6 +407,18 @@ describe Chef::Cookbook::Metadata do
}.to raise_error(ArgumentError)
end
+ it "should not accept anything but true or false for the privacy flag" do
+ expect {
+ metadata.attribute("db/mysql/databases", :privacy => true)
+ }.not_to raise_error
+ expect {
+ metadata.attribute("db/mysql/databases", :privacy => false)
+ }.not_to raise_error
+ expect {
+ metadata.attribute("db/mysql/databases", :privacy => 'true')
+ }.to raise_error(ArgumentError)
+ end
+
it "should not accept anything but an array of strings for choice" do
expect {
metadata.attribute("db/mysql/databases", :choice => ['dedicated', 'shared'])
@@ -699,6 +717,7 @@ describe Chef::Cookbook::Metadata do
version
source_url
issues_url
+ privacy
}.each do |t|
it "should include '#{t}'" do
expect(deserialized_metadata[t]).to eq(metadata.send(t.to_sym))
@@ -734,6 +753,7 @@ describe Chef::Cookbook::Metadata do
version
source_url
issues_url
+ privacy
}.each do |t|
it "should match '#{t}'" do
expect(deserialized_metadata.send(t.to_sym)).to eq(metadata.send(t.to_sym))
diff --git a/spec/unit/cookbook/syntax_check_spec.rb b/spec/unit/cookbook/syntax_check_spec.rb
index 318c2a7e85..efdb5b7926 100644
--- a/spec/unit/cookbook/syntax_check_spec.rb
+++ b/spec/unit/cookbook/syntax_check_spec.rb
@@ -53,6 +53,7 @@ describe Chef::Cookbook::SyntaxCheck do
@ruby_files = @attr_files + @libr_files + @defn_files + @recipes + [File.join(cookbook_path, "metadata.rb")]
basenames = %w{ helpers_via_partial_test.erb
helper_test.erb
+ helpers.erb
openldap_stuff.conf.erb
nested_openldap_partials.erb
nested_partial.erb
diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb
index 4990aef004..2bccddcaec 100644
--- a/spec/unit/cookbook_version_spec.rb
+++ b/spec/unit/cookbook_version_spec.rb
@@ -336,7 +336,7 @@ describe Chef::CookbookVersion do
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') }
end
diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb
index 4348252388..497817ecf1 100644
--- a/spec/unit/data_bag_item_spec.rb
+++ b/spec/unit/data_bag_item_spec.rb
@@ -193,7 +193,7 @@ describe Chef::DataBagItem do
expect(deserial["snooze"]).to eq({ "finally" => "world_will" })
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { data_bag_item }
end
end
diff --git a/spec/unit/data_bag_spec.rb b/spec/unit/data_bag_spec.rb
index bd9a99a1de..13b835d120 100644
--- a/spec/unit/data_bag_spec.rb
+++ b/spec/unit/data_bag_spec.rb
@@ -73,7 +73,7 @@ describe Chef::DataBag do
expect(@deserial.send(t.to_sym)).to eq(@data_bag.send(t.to_sym))
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @data_bag }
end
end
diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb
index 2e1f3c39f3..674de5ec1d 100644
--- a/spec/unit/deprecation_spec.rb
+++ b/spec/unit/deprecation_spec.rb
@@ -65,19 +65,16 @@ describe Chef::Deprecation do
end
context 'deprecation warning messages' do
- before(:each) do
- @warning_output = [ ]
- allow(Chef::Log).to receive(:warn) { |msg| @warning_output << msg }
- end
+ RSpec::Matchers.define_negated_matcher :a_non_empty_array, :be_empty
it 'should be enabled for deprecated methods' do
+ expect(Chef::Log).to receive(:warn).with(a_non_empty_array)
TestClass.new.deprecated_method(10)
- expect(@warning_output).not_to be_empty
end
it 'should contain stack trace' do
+ expect(Chef::Log).to receive(:warn).with(a_string_including(".rb"))
TestClass.new.deprecated_method(10)
- expect(@warning_output.join("").include?(".rb")).to be_truthy
end
end
diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb
index 0f2288740f..a55f91d5e6 100644
--- a/spec/unit/dsl/reboot_pending_spec.rb
+++ b/spec/unit/dsl/reboot_pending_spec.rb
@@ -46,7 +46,7 @@ describe Chef::DSL::RebootPending do
end
it 'should return true if key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired" exists' do
- allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired').and_return(true)
+ allow(recipe).to receive(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending').and_return(true)
expect(recipe.reboot_pending?).to be_truthy
end
diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb
index ee3b8b21e1..64617e0888 100644
--- a/spec/unit/environment_spec.rb
+++ b/spec/unit/environment_spec.rb
@@ -208,7 +208,7 @@ describe Chef::Environment do
expect(@json).to match(/"chef_type":"environment"/)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @environment }
end
end
diff --git a/spec/unit/event_dispatch/dispatcher_spec.rb b/spec/unit/event_dispatch/dispatcher_spec.rb
index 7e43b1933f..1014feea89 100644
--- a/spec/unit/event_dispatch/dispatcher_spec.rb
+++ b/spec/unit/event_dispatch/dispatcher_spec.rb
@@ -47,14 +47,33 @@ describe Chef::EventDispatch::Dispatcher do
expect(event_sink).to receive(:run_start).with("12.4.0")
dispatcher.run_start("12.4.0")
- expect(event_sink).to receive(:synchronized_cookbook).with("apache2")
- dispatcher.synchronized_cookbook("apache2")
+ cookbook_version = double("cookbook_version")
+ expect(event_sink).to receive(:synchronized_cookbook).with("apache2", cookbook_version)
+ dispatcher.synchronized_cookbook("apache2", cookbook_version)
exception = StandardError.new("foo")
expect(event_sink).to receive(:recipe_file_load_failed).with("/path/to/file.rb", exception)
dispatcher.recipe_file_load_failed("/path/to/file.rb", exception)
end
+ context "when an event sink has fewer arguments for an event" do
+ # Can't use a double because they don't report arity correctly.
+ let(:event_sink) do
+ Class.new(Chef::EventDispatch::Base) do
+ attr_reader :synchronized_cookbook_args
+ def synchronized_cookbook(cookbook_name)
+ @synchronized_cookbook_args = [cookbook_name]
+ end
+ end.new
+ end
+
+ it "trims the arugment list" do
+ cookbook_version = double("cookbook_version")
+ dispatcher.synchronized_cookbook("apache2", cookbook_version)
+ expect(event_sink.synchronized_cookbook_args).to eq ["apache2"]
+ end
+ end
+
end
end
diff --git a/spec/unit/event_dispatch/dsl_spec.rb b/spec/unit/event_dispatch/dsl_spec.rb
new file mode 100644
index 0000000000..0f7adce7a8
--- /dev/null
+++ b/spec/unit/event_dispatch/dsl_spec.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Ranjib Dey (<ranjib@linux.com>)
+#
+# Copyright:: Copyright (c) 2015 Ranjib Dey
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'spec_helper'
+require 'chef/event_dispatch/dsl'
+
+describe Chef::EventDispatch::DSL do
+ let(:events) do
+ Chef::EventDispatch::Dispatcher.new
+ end
+
+ let(:run_context) do
+ Chef::RunContext.new(Chef::Node.new, nil, events)
+ end
+
+ before do
+ Chef.set_run_context(run_context)
+ end
+
+ subject{ described_class.new('test') }
+
+ it 'set handler name' do
+ subject.on(:run_started) {}
+ expect(events.subscribers.first.name).to eq('test')
+ end
+
+ it 'raise error when invalid event type is supplied' do
+ expect do
+ subject.on(:foo_bar) {}
+ end.to raise_error(Chef::Exceptions::InvalidEventType)
+ end
+
+ it 'register user hooks against valid event type' do
+ subject.on(:run_failed) {'testhook'}
+ expect(events.subscribers.first.run_failed).to eq('testhook')
+ end
+
+ it 'preserve state across event hooks' do
+ calls = []
+ Chef.event_handler do
+ on :resource_updated do
+ calls << :updated
+ end
+ on :resource_action_start do
+ calls << :started
+ end
+ end
+ resource = Chef::Resource::RubyBlock.new('foo', run_context)
+ resource.block { }
+ resource.run_action(:run)
+ expect(calls).to eq([:started, :updated])
+ end
+
+ it 'preserve instance variables across handler callbacks' do
+ Chef.event_handler do
+ on :resource_action_start do
+ @ivar = [1]
+ end
+ on :resource_updated do
+ @ivar << 2
+ end
+ end
+ resource = Chef::Resource::RubyBlock.new('foo', run_context)
+ resource.block { }
+ resource.run_action(:run)
+ expect(events.subscribers.first.instance_variable_get(:@ivar)).to eq([1, 2])
+ end
+end
diff --git a/spec/unit/exceptions_spec.rb b/spec/unit/exceptions_spec.rb
index fd90aeab71..85c54aa693 100644
--- a/spec/unit/exceptions_spec.rb
+++ b/spec/unit/exceptions_spec.rb
@@ -76,7 +76,7 @@ describe Chef::Exceptions do
end
if exception.methods.include?(:to_json)
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { exception }
end
end
diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb
index d018207f49..eb98f5abd3 100644
--- a/spec/unit/formatters/doc_spec.rb
+++ b/spec/unit/formatters/doc_spec.rb
@@ -43,4 +43,10 @@ describe Chef::Formatters::Base do
expect(out.string).to include("Using policy 'jenkins' at revision '613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073'")
end
+ it "prints cookbook name and version" do
+ cookbook_version = double(name: "apache2", version: "1.2.3")
+ formatter.synchronized_cookbook("apache2", cookbook_version)
+ expect(out.string).to include("- apache2 (1.2.3")
+ end
+
end
diff --git a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
index 5f95beb259..3c8d5dfa29 100644
--- a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
+++ b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
@@ -110,6 +110,32 @@ describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do
end
end
+ context "when the error is a RuntimeError about frozen object" do
+ let(:exception) do
+ e = RuntimeError.new("can't modify frozen Array")
+ e.set_backtrace(trace)
+ e
+ end
+
+ let(:path_to_failed_file) { "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb" }
+
+ let(:trace) do
+ [
+ "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:2:in `block in from_file'",
+ "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:1:in `from_file'"
+ ]
+ end
+
+ describe "when explaining a runtime error in the compile phase" do
+ it "correctly detects RuntimeError for frozen objects" do
+ expect(inspector.exception_message_modifying_frozen?).to be(true)
+ end
+
+ # could also test for description.section to be called, but would have
+ # to adjust every other test to begin using a test double for description
+ end
+ end
+
context "when the error does not contain any lines from cookbooks" do
let(:trace) do
diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb
index 7482ba8a28..fd6469c146 100644
--- a/spec/unit/json_compat_spec.rb
+++ b/spec/unit/json_compat_spec.rb
@@ -67,13 +67,11 @@ describe Chef::JSONCompat do
expect(Chef::JSONCompat.to_json_pretty(f)).to eql("{\n \"foo\": 1234,\n \"bar\": {\n \"baz\": 5678\n }\n}\n")
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { Foo.new }
end
end
- # On FreeBSD 10.1 i386 rspec fails with a SystemStackError loading the expect line with more that 252 entries
- # https://github.com/chef/chef/issues/3101
describe "with the file with 252 or less nested entries" do
let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'nested.json')) }
let(:hash) { Chef::JSONCompat.from_json(json) }
@@ -84,7 +82,10 @@ describe Chef::JSONCompat do
end
it "should has 'test' as a 252 nested value" do
- expect(hash['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['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']['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']['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']).to eq('test')
+ v = 252.times.inject(hash) do |memo, _|
+ memo['key']
+ end
+ expect(v).to eq('test')
end
end
end
diff --git a/spec/unit/knife/bootstrap/client_builder_spec.rb b/spec/unit/knife/bootstrap/client_builder_spec.rb
index e6aa307c7e..930ae8c9d3 100644
--- a/spec/unit/knife/bootstrap/client_builder_spec.rb
+++ b/spec/unit/knife/bootstrap/client_builder_spec.rb
@@ -149,6 +149,22 @@ describe Chef::Knife::Bootstrap::ClientBuilder do
client_builder.run
end
+ it "does not add tags by default" do
+ allow(node).to receive(:run_list).with([])
+ expect(node).to_not receive(:tags)
+ client_builder.run
+ end
+
+ it "adds tags to the node when given" do
+ tag_receiver = []
+
+ knife_config[:tags] = %w[foo bar]
+ allow(node).to receive(:run_list).with([])
+ allow(node).to receive(:tags).and_return(tag_receiver)
+ client_builder.run
+ expect(tag_receiver).to eq %w[foo bar]
+ end
+
it "builds a node when the run_list is a string" do
knife_config[:run_list] = "role[base],role[app]"
expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb
index 45bb4dd16c..1a6317ac00 100644
--- a/spec/unit/knife/client_bulk_delete_spec.rb
+++ b/spec/unit/knife/client_bulk_delete_spec.rb
@@ -45,7 +45,7 @@ describe Chef::Knife::ClientBulkDelete do
clients = Hash.new
nonvalidator_client_names.each do |client_name|
- client = Chef::ApiClient.new()
+ client = Chef::ApiClientV1.new()
client.name(client_name)
allow(client).to receive(:destroy).and_return(true)
clients[client_name] = client
@@ -59,7 +59,7 @@ describe Chef::Knife::ClientBulkDelete do
clients = Hash.new
validator_client_names.each do |validator_client_name|
- validator_client = Chef::ApiClient.new()
+ validator_client = Chef::ApiClientV1.new()
validator_client.name(validator_client_name)
allow(validator_client).to receive(:validator).and_return(true)
allow(validator_client).to receive(:destroy).and_return(true)
@@ -75,7 +75,7 @@ describe Chef::Knife::ClientBulkDelete do
}
before(:each) do
- allow(Chef::ApiClient).to receive(:list).and_return(clients)
+ allow(Chef::ApiClientV1).to receive(:list).and_return(clients)
end
describe "run" do
@@ -89,7 +89,7 @@ describe Chef::Knife::ClientBulkDelete do
describe "with any clients" do
it "should get the list of the clients" do
- expect(Chef::ApiClient).to receive(:list)
+ expect(Chef::ApiClientV1).to receive(:list)
knife.run
end
diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb
index 8fecfc885f..a1dcc564e2 100644
--- a/spec/unit/knife/client_create_spec.rb
+++ b/spec/unit/knife/client_create_spec.rb
@@ -34,7 +34,7 @@ describe Chef::Knife::ClientCreate do
end
let(:client) do
- Chef::ApiClient.new
+ Chef::ApiClientV1.new
end
let(:knife) do
diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb
index 0fb5e0bab7..619009979b 100644
--- a/spec/unit/knife/client_delete_spec.rb
+++ b/spec/unit/knife/client_delete_spec.rb
@@ -30,7 +30,7 @@ describe Chef::Knife::ClientDelete do
describe 'run' do
it 'should delete the client' do
- expect(@knife).to receive(:delete_object).with(Chef::ApiClient, 'adam', 'client')
+ expect(@knife).to receive(:delete_object).with(Chef::ApiClientV1, 'adam', 'client')
@knife.run
end
@@ -46,8 +46,8 @@ describe Chef::Knife::ClientDelete do
before(:each) do
allow(Chef::Knife::UI).to receive(:confirm).and_return(true)
allow(@knife).to receive(:confirm).and_return(true)
- @client = Chef::ApiClient.new
- expect(Chef::ApiClient).to receive(:load).and_return(@client)
+ @client = Chef::ApiClientV1.new
+ expect(Chef::ApiClientV1).to receive(:load).and_return(@client)
end
it 'should delete non-validator client if --delete-validators is not set' do
diff --git a/spec/unit/knife/client_edit_spec.rb b/spec/unit/knife/client_edit_spec.rb
index c040c5e2f2..ad56d9212d 100644
--- a/spec/unit/knife/client_edit_spec.rb
+++ b/spec/unit/knife/client_edit_spec.rb
@@ -17,16 +17,29 @@
#
require 'spec_helper'
+require 'chef/api_client_v1'
describe Chef::Knife::ClientEdit do
before(:each) do
@knife = Chef::Knife::ClientEdit.new
@knife.name_args = [ 'adam' ]
+ @knife.config[:disable_editing] = true
end
describe 'run' do
+ let(:data) {
+ {
+ "name" => "adam",
+ "validator" => false,
+ "admin" => false,
+ "chef_type" => "client",
+ "create_key" => true
+ }
+ }
+
it 'should edit the client' do
- expect(@knife).to receive(:edit_object).with(Chef::ApiClient, 'adam')
+ allow(Chef::ApiClientV1).to receive(:load).with('adam').and_return(data)
+ expect(@knife).to receive(:edit_data).with(data).and_return(data)
@knife.run
end
diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/knife/client_list_spec.rb
index eff01da4e9..ce0fa4f5e8 100644
--- a/spec/unit/knife/client_list_spec.rb
+++ b/spec/unit/knife/client_list_spec.rb
@@ -26,7 +26,7 @@ describe Chef::Knife::ClientList do
describe 'run' do
it 'should list the clients' do
- expect(Chef::ApiClient).to receive(:list)
+ expect(Chef::ApiClientV1).to receive(:list)
expect(@knife).to receive(:format_list_for_display)
@knife.run
end
diff --git a/spec/unit/knife/client_reregister_spec.rb b/spec/unit/knife/client_reregister_spec.rb
index f1be4ed570..7e763242e4 100644
--- a/spec/unit/knife/client_reregister_spec.rb
+++ b/spec/unit/knife/client_reregister_spec.rb
@@ -41,7 +41,7 @@ describe Chef::Knife::ClientReregister do
context 'when not configured for file output' do
it 'reregisters the client and prints the key' do
- expect(Chef::ApiClient).to receive(:reregister).with('adam').and_return(@client_mock)
+ expect(Chef::ApiClientV1).to receive(:reregister).with('adam').and_return(@client_mock)
@knife.run
expect(@stdout.string).to match( /foo_key/ )
end
@@ -49,7 +49,7 @@ describe Chef::Knife::ClientReregister do
context 'when configured for file output' do
it 'should write the private key to a file' do
- expect(Chef::ApiClient).to receive(:reregister).with('adam').and_return(@client_mock)
+ expect(Chef::ApiClientV1).to receive(:reregister).with('adam').and_return(@client_mock)
@knife.config[:file] = '/tmp/monkeypants'
filehandle = StringIO.new
diff --git a/spec/unit/knife/client_show_spec.rb b/spec/unit/knife/client_show_spec.rb
index 8404e8d019..73a876cee0 100644
--- a/spec/unit/knife/client_show_spec.rb
+++ b/spec/unit/knife/client_show_spec.rb
@@ -27,7 +27,7 @@ describe Chef::Knife::ClientShow do
describe 'run' do
it 'should list the client' do
- expect(Chef::ApiClient).to receive(:load).with('adam').and_return(@client_mock)
+ expect(Chef::ApiClientV1).to receive(:load).with('adam').and_return(@client_mock)
expect(@knife).to receive(:format_for_display).with(@client_mock)
@knife.run
end
@@ -37,7 +37,7 @@ describe Chef::Knife::ClientShow do
@stdout = StringIO.new
allow(@knife.ui).to receive(:stdout).and_return(@stdout)
fake_client_contents = {"foo"=>"bar", "baz"=>"qux"}
- expect(Chef::ApiClient).to receive(:load).with('adam').and_return(fake_client_contents)
+ expect(Chef::ApiClientV1).to receive(:load).with('adam').and_return(fake_client_contents)
@knife.run
expect(@stdout.string).to eql("{\n \"foo\": \"bar\",\n \"baz\": \"qux\"\n}\n")
end
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
index 3718cb228c..268b61c756 100644
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -97,6 +97,13 @@ EXPECTED
end
end
+ describe "when tags are given" do
+ let(:config) { {:tags => [ "unicorn" ] } }
+ it "adds the attributes to first_boot" do
+ expect(Chef::JSONCompat.to_json(bootstrap_context.first_boot)).to eq(Chef::JSONCompat.to_json({:run_list => run_list, :tags => ["unicorn"]}))
+ end
+ end
+
describe "when JSON attributes are given" do
let(:config) { {:first_boot_attributes => {:baz => :quux}} }
it "adds the attributes to first_boot" do
diff --git a/spec/unit/knife/core/custom_manifest_loader_spec.rb b/spec/unit/knife/core/custom_manifest_loader_spec.rb
new file mode 100644
index 0000000000..1edbedd3c8
--- /dev/null
+++ b/spec/unit/knife/core/custom_manifest_loader_spec.rb
@@ -0,0 +1,41 @@
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::SubcommandLoader::CustomManifestLoader do
+ let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" }
+ let(:manifest_content) do
+ { "plugins" => {
+ "knife-ec2" => {
+ "paths" => [
+ ec2_server_create_plugin
+ ]
+ }
+ }
+ }
+ end
+ let(:loader) do
+ Chef::Knife::SubcommandLoader::CustomManifestLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'),
+ manifest_content)
+ end
+
+ it "uses paths from the manifest instead of searching gems" do
+ expect(Gem::Specification).not_to receive(:latest_specs).and_call_original
+ expect(loader.subcommand_files).to include(ec2_server_create_plugin)
+ end
+end
diff --git a/spec/unit/knife/core/gem_glob_loader_spec.rb b/spec/unit/knife/core/gem_glob_loader_spec.rb
new file mode 100644
index 0000000000..465eea2656
--- /dev/null
+++ b/spec/unit/knife/core/gem_glob_loader_spec.rb
@@ -0,0 +1,210 @@
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::SubcommandLoader::GemGlobLoader do
+ let(:loader) { Chef::Knife::SubcommandLoader::GemGlobLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) }
+ let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') }
+ let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') }
+
+ before do
+ allow(ChefConfig).to receive(:windows?) { false }
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
+ end
+
+ after do
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
+ end
+
+ it "builds a list of the core subcommand file require paths" do
+ expect(loader.subcommand_files).not_to be_empty
+ loader.subcommand_files.each do |require_path|
+ expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
+ end
+ end
+
+ it "finds files installed via rubygems" do
+ expect(loader.find_subcommands_via_rubygems).to include('chef/knife/node_create')
+ loader.find_subcommands_via_rubygems.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])}
+ end
+
+ it "finds files from latest version of installed gems" do
+ gems = [ double('knife-ec2-0.5.12') ]
+ gem_files = [
+ '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb',
+ '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb'
+ ]
+ expect($LOAD_PATH).to receive(:map).and_return([])
+ if Gem::Specification.respond_to? :latest_specs
+ expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems)
+ expect(gems[0]).to receive(:matches_for_glob).with(/chef\/knife\/\*\.rb\{(.*),\.rb,(.*)\}/).and_return(gem_files)
+ else
+ expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems)
+ expect(gems[0]).to receive(:require_paths).twice.and_return(['lib'])
+ expect(gems[0]).to receive(:full_gem_path).and_return('/usr/lib/ruby/gems/knife-ec2-0.5.12')
+ expect(Dir).to receive(:[]).with('/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb').and_return(gem_files)
+ end
+ expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
+ expect(loader.subcommand_files.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files)
+ end
+
+ it "finds files using a dirglob when rubygems is not available" do
+ expect(loader.find_subcommands_via_dirglob).to include('chef/knife/node_create')
+ loader.find_subcommands_via_dirglob.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])}
+ end
+
+ it "finds user-specific subcommands in the user's ~/.chef directory" do
+ expected_command = File.join(home, '.chef', 'plugins', 'knife', 'example_home_subcommand.rb')
+ expect(loader.site_subcommands).to include(expected_command)
+ end
+
+ it "finds repo specific subcommands by searching for a .chef directory" do
+ expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb')
+ expect(loader.site_subcommands).to include(expected_command)
+ end
+
+ # https://github.com/opscode/chef-dk/issues/227
+ #
+ # `knife` in ChefDK isn't from a gem install, it's directly run from a clone
+ # of the source, but there can be one or more versions of chef also installed
+ # as a gem. If the gem install contains a command that doesn't exist in the
+ # source tree of the "primary" chef install, it can be loaded and cause an
+ # error. We also want to ensure that we only load builtin commands from the
+ # "primary" chef install.
+ context "when a different version of chef is also installed as a gem" do
+
+ let(:all_found_commands) do
+ [
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
+
+ # We use the fake version 1.0.0 because that version doesn't exist,
+ # which ensures it won't ever equal "chef-#{Chef::VERSION}"
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb",
+
+ # Test that we don't accept a version number that is different only in
+ # trailing characters, e.g. we are running Chef 12.0.0 but there is a
+ # Chef 12.0.0.rc.0 gem also:
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
+
+ # Test that we ignore the platform suffix when checking for different
+ # gem versions.
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
+ # ...but don't ignore the .rc / .dev parts in the case when we have
+ # platform suffixes
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb",
+
+ # This command is "extra" compared to what's in the embedded/apps/chef install:
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
+
+ # These are fake commands that have names designed to test that the
+ # regex is strict enough
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
+
+ # In a real scenario, we'd use rubygems APIs to only select the most
+ # recent gem, but for this test we want to check that we're doing the
+ # right thing both when the plugin version matches and does not match
+ # the current chef version. Looking at
+ # `SubcommandLoader::MATCHES_THIS_CHEF_GEM` and
+ # `SubcommandLoader::MATCHES_CHEF_GEM` should make it clear why we want
+ # to test these two cases.
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb"
+ ]
+ end
+
+ let(:expected_valid_commands) do
+ [
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
+ "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
+ "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb"
+ ]
+ end
+
+ before do
+ expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands)
+ expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
+ end
+
+ it "ignores commands from the non-matching gem install" do
+ expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands)
+ end
+
+ end
+
+ describe "finding 3rd party plugins" do
+ let(:env_home) { "/home/alice" }
+ let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" }
+
+ before do
+ env_dup = ENV.to_hash
+ allow(ENV).to receive(:[]) { |key| env_dup[key] }
+ allow(ENV).to receive(:[]).with("HOME").and_return(env_home)
+ end
+
+
+ it "searches rubygems for plugins" do
+ if Gem::Specification.respond_to?(:latest_specs)
+ expect(Gem::Specification).to receive(:latest_specs).and_call_original
+ else
+ expect(Gem.source_index).to receive(:latest_specs).and_call_original
+ end
+ loader.subcommand_files.each do |require_path|
+ expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
+ end
+ end
+
+ context "and HOME environment variable is not set" do
+ before do
+ allow(ENV).to receive(:[]).with("HOME").and_return(nil)
+ end
+
+ it "searches rubygems for plugins" do
+ if Gem::Specification.respond_to?(:latest_specs)
+ expect(Gem::Specification).to receive(:latest_specs).and_call_original
+ else
+ expect(Gem.source_index).to receive(:latest_specs).and_call_original
+ end
+ loader.subcommand_files.each do |require_path|
+ expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/knife/core/hashed_command_loader_spec.rb b/spec/unit/knife/core/hashed_command_loader_spec.rb
new file mode 100644
index 0000000000..00e7ba377b
--- /dev/null
+++ b/spec/unit/knife/core/hashed_command_loader_spec.rb
@@ -0,0 +1,93 @@
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Knife::SubcommandLoader::HashedCommandLoader do
+ before do
+ allow(ChefConfig).to receive(:windows?) { false }
+ end
+
+ let(:plugin_manifest) {
+ {
+ "_autogenerated_command_paths" => {
+ "plugins_paths" => {
+ "cool_a" => ["/file/for/plugin/a"],
+ "cooler_b" => ["/file/for/plugin/b"]
+ },
+ "plugins_by_category" => {
+ "cool" => [
+ "cool_a"
+ ],
+ "cooler" => [
+ "cooler_b"
+ ]
+ }
+ }
+ }
+ }
+
+ let(:loader) { Chef::Knife::SubcommandLoader::HashedCommandLoader.new(
+ File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'),
+ plugin_manifest)}
+
+ describe "#list_commands" do
+ it "lists all commands by category when no argument is given" do
+ expect(loader.list_commands).to eq({"cool" => ["cool_a"], "cooler" => ["cooler_b"]})
+ end
+
+ it "lists only commands in the given category when a category is given" do
+ expect(loader.list_commands("cool")).to eq({"cool" => ["cool_a"]})
+ end
+ end
+
+ describe "#subcommand_files" do
+ it "lists all the files" do
+ expect(loader.subcommand_files).to eq(["/file/for/plugin/a", "/file/for/plugin/b"])
+ end
+ end
+
+ describe "#load_commands" do
+ before do
+ allow(Kernel).to receive(:load).and_return(true)
+ end
+
+ it "returns false for non-existant commands" do
+ expect(loader.load_command(["nothere"])).to eq(false)
+ end
+
+ it "loads the correct file and returns true if the command exists" do
+ allow(File).to receive(:exists?).and_return(true)
+ expect(Kernel).to receive(:load).with("/file/for/plugin/a").and_return(true)
+ expect(loader.load_command(["cool_a"])).to eq(true)
+ end
+ end
+
+ describe "#subcommand_for_args" do
+ it "returns the subcommands for an exact match" do
+ expect(loader.subcommand_for_args(["cooler_b"])).to eq("cooler_b")
+ end
+
+ it "finds the right subcommand even when _'s are elided" do
+ expect(loader.subcommand_for_args(["cooler", "b"])).to eq("cooler_b")
+ end
+
+ it "returns nil if the the subcommand isn't in our manifest" do
+ expect(loader.subcommand_for_args(["cooler c"])).to eq(nil)
+ end
+ end
+end
diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb
index 219a1f2906..2386465c75 100644
--- a/spec/unit/knife/core/subcommand_loader_spec.rb
+++ b/spec/unit/knife/core/subcommand_loader_spec.rb
@@ -1,6 +1,5 @@
#
-# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,209 +31,34 @@ describe Chef::Knife::SubcommandLoader do
Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
end
- it "builds a list of the core subcommand file require paths" do
- expect(loader.subcommand_files).not_to be_empty
- loader.subcommand_files.each do |require_path|
- expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
- end
- end
-
- it "finds files installed via rubygems" do
- expect(loader.find_subcommands_via_rubygems).to include('chef/knife/node_create')
- loader.find_subcommands_via_rubygems.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])}
- end
-
- it "finds files from latest version of installed gems" do
- gems = [ double('knife-ec2-0.5.12') ]
- gem_files = [
- '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_base.rb',
- '/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_otherstuff.rb'
- ]
- expect($LOAD_PATH).to receive(:map).and_return([])
- if Gem::Specification.respond_to? :latest_specs
- expect(Gem::Specification).to receive(:latest_specs).with(true).and_return(gems)
- expect(gems[0]).to receive(:matches_for_glob).with(/chef\/knife\/\*\.rb\{(.*),\.rb,(.*)\}/).and_return(gem_files)
- else
- expect(Gem.source_index).to receive(:latest_specs).with(true).and_return(gems)
- expect(gems[0]).to receive(:require_paths).twice.and_return(['lib'])
- expect(gems[0]).to receive(:full_gem_path).and_return('/usr/lib/ruby/gems/knife-ec2-0.5.12')
- expect(Dir).to receive(:[]).with('/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/*.rb').and_return(gem_files)
- end
- expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
- expect(loader.find_subcommands_via_rubygems.values.select { |file| file =~ /knife-ec2/ }.sort).to eq(gem_files)
- end
-
- it "finds files using a dirglob when rubygems is not available" do
- expect(loader.find_subcommands_via_dirglob).to include('chef/knife/node_create')
- loader.find_subcommands_via_dirglob.each {|rel_path, abs_path| expect(abs_path).to match(%r[chef/knife/.+])}
- end
-
- it "finds user-specific subcommands in the user's ~/.chef directory" do
- expected_command = File.join(home, '.chef', 'plugins', 'knife', 'example_home_subcommand.rb')
- expect(loader.site_subcommands).to include(expected_command)
- end
-
- it "finds repo specific subcommands by searching for a .chef directory" do
- expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb')
- expect(loader.site_subcommands).to include(expected_command)
- end
-
- # https://github.com/opscode/chef-dk/issues/227
- #
- # `knife` in ChefDK isn't from a gem install, it's directly run from a clone
- # of the source, but there can be one or more versions of chef also installed
- # as a gem. If the gem install contains a command that doesn't exist in the
- # source tree of the "primary" chef install, it can be loaded and cause an
- # error. We also want to ensure that we only load builtin commands from the
- # "primary" chef install.
- context "when a different version of chef is also installed as a gem" do
-
- let(:all_found_commands) do
- [
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
-
- # We use the fake version 1.0.0 because that version doesn't exist,
- # which ensures it won't ever equal "chef-#{Chef::VERSION}"
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb",
-
- # Test that we don't accept a version number that is different only in
- # trailing characters, e.g. we are running Chef 12.0.0 but there is a
- # Chef 12.0.0.rc.0 gem also:
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
-
- # Test that we ignore the platform suffix when checking for different
- # gem versions.
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
- # ...but don't ignore the .rc / .dev parts in the case when we have
- # platform suffixes
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0-x86-mingw32/lib/chef/knife/invalid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev-mswin32/lib/chef/knife/invalid-too.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.dev.0-x86-mingw64/lib/chef/knife/still-invalid.rb",
-
- # This command is "extra" compared to what's in the embedded/apps/chef install:
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
-
- # These are fake commands that have names designed to test that the
- # regex is strict enough
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
+ let(:config_dir) { File.join(CHEF_SPEC_DATA, 'knife-site-subcommands') }
- # In a real scenario, we'd use rubygems APIs to only select the most
- # recent gem, but for this test we want to check that we're doing the
- # right thing both when the plugin version matches and does not match
- # the current chef version. Looking at
- # `SubcommandLoader::MATCHES_THIS_CHEF_GEM` and
- # `SubcommandLoader::MATCHES_CHEF_GEM` should make it clear why we want
- # to test these two cases.
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb"
- ]
- end
-
- let(:expected_valid_commands) do
- [
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-x86-mingw32/lib/chef/knife/valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-i386-mingw64/lib/chef/knife/valid-too.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-mswin32/lib/chef/knife/also-valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}-universal-mingw32/lib/chef/knife/universal-is-valid.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb"
- ]
- end
-
- before do
- expect(loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands)
- expect(loader).to receive(:find_subcommands_via_dirglob).and_return({})
- end
-
- it "ignores commands from the non-matching gem install" do
- expect(loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands)
- end
-
- end
-
- describe "finding 3rd party plugins" do
- let(:home) { "/home/alice" }
- let(:manifest_path) { home + "/.chef/plugin_manifest.json" }
-
- context "when there is not a ~/.chef/plugin_manifest.json file" do
+ describe "#for_config" do
+ context "when ~/.chef/plugin_manifest.json exists" do
before do
- allow(File).to receive(:exist?).with(manifest_path).and_return(false)
+ allow(File).to receive(:exist?).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return(true)
end
- it "searches rubygems for plugins" do
- if Gem::Specification.respond_to?(:latest_specs)
- expect(Gem::Specification).to receive(:latest_specs).and_call_original
- else
- expect(Gem.source_index).to receive(:latest_specs).and_call_original
- end
- loader.subcommand_files.each do |require_path|
- expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
- end
+ it "creates a HashedCommandLoader with the manifest has _autogenerated_command_paths" do
+ allow(File).to receive(:read).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return("{ \"_autogenerated_command_paths\": {}}")
+ expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::HashedCommandLoader
end
- context "and HOME environment variable is not set" do
- before do
- allow(Chef::Util::PathHelper).to receive(:home).and_return(nil)
- end
-
- it "searches rubygems for plugins" do
- if Gem::Specification.respond_to?(:latest_specs)
- expect(Gem::Specification).to receive(:latest_specs).and_call_original
- else
- expect(Gem.source_index).to receive(:latest_specs).and_call_original
- end
- loader.subcommand_files.each do |require_path|
- expect(require_path).to match(/chef\/knife\/.*|plugins\/knife\/.*/)
- end
- end
+ it "creates a CustomManifestLoader with then manifest has a key other than _autogenerated_command_paths" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ allow(File).to receive(:read).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return("{ \"plugins\": {}}")
+ expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::CustomManifestLoader
end
-
end
- context "when there is a ~/.chef/plugin_manifest.json file" do
- let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" }
-
- let(:manifest_content) do
- { "plugins" => {
- "knife-ec2" => {
- "paths" => [
- ec2_server_create_plugin
- ]
- }
- }
- }
- end
-
- let(:manifest_json) { Chef::JSONCompat.to_json(manifest_content) }
-
+ context "when ~/.chef/plugin_manifest.json does not exist" do
before do
- allow(File).to receive(:exist?).with(manifest_path).and_return(true)
- allow(File).to receive(:read).with(manifest_path).and_return(manifest_json)
+ allow(File).to receive(:exist?).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return(false)
end
- it "uses paths from the manifest instead of searching gems" do
- expect(Gem::Specification).not_to receive(:latest_specs).and_call_original
- expect(loader.subcommand_files).to include(ec2_server_create_plugin)
+ it "creates a GemGlobLoader" do
+ expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader
end
-
end
end
-
end
diff --git a/spec/unit/knife/node_run_list_remove_spec.rb b/spec/unit/knife/node_run_list_remove_spec.rb
index ceceef7178..a279a59635 100644
--- a/spec/unit/knife/node_run_list_remove_spec.rb
+++ b/spec/unit/knife/node_run_list_remove_spec.rb
@@ -84,6 +84,23 @@ describe Chef::Knife::NodeRunListRemove do
expect(@node.run_list).not_to include('role[monkey]')
expect(@node.run_list).not_to include('recipe[duck::type]')
end
+
+ it "should warn when the thing to remove is not in the runlist" do
+ @node.run_list << 'role[blah]'
+ @node.run_list << 'recipe[duck::type]'
+ @knife.name_args = [ 'adam', 'role[blork]' ]
+ expect(@knife.ui).to receive(:warn).with("role[blork] is not in the run list")
+ @knife.run
+ end
+
+ it "should warn even more when the thing to remove is not in the runlist and unqualified" do
+ @node.run_list << 'role[blah]'
+ @node.run_list << 'recipe[duck::type]'
+ @knife.name_args = [ 'adam', 'blork' ]
+ expect(@knife.ui).to receive(:warn).with("blork is not in the run list")
+ expect(@knife.ui).to receive(:warn).with(/did you forget recipe\[\] or role\[\]/)
+ @knife.run
+ end
end
end
end
diff --git a/spec/unit/knife/osc_user_create_spec.rb b/spec/unit/knife/osc_user_create_spec.rb
index 1b17d0d22f..e4ed78fe2b 100644
--- a/spec/unit/knife/osc_user_create_spec.rb
+++ b/spec/unit/knife/osc_user_create_spec.rb
@@ -36,19 +36,19 @@ describe Chef::Knife::OscUserCreate do
@knife.name_args = [ 'a_user' ]
@knife.config[:user_password] = "foobar"
- @user = Chef::OscUser.new
+ @user = Chef::User.new
@user.name "a_user"
- @user_with_private_key = Chef::OscUser.new
+ @user_with_private_key = Chef::User.new
@user_with_private_key.name "a_user"
@user_with_private_key.private_key 'private_key'
allow(@user).to receive(:create).and_return(@user_with_private_key)
- allow(Chef::OscUser).to receive(:new).and_return(@user)
- allow(Chef::OscUser).to receive(:from_hash).and_return(@user)
+ allow(Chef::User).to receive(:new).and_return(@user)
+ allow(Chef::User).to receive(:from_hash).and_return(@user)
allow(@knife).to receive(:edit_data).and_return(@user.to_hash)
end
it "creates a new user" do
- expect(Chef::OscUser).to receive(:new).and_return(@user)
+ expect(Chef::User).to receive(:new).and_return(@user)
expect(@user).to receive(:create)
@knife.run
expect(@stderr.string).to match /created user.+a_user/i
diff --git a/spec/unit/knife/osc_user_delete_spec.rb b/spec/unit/knife/osc_user_delete_spec.rb
index 0e16393ffe..4a3ec4228f 100644
--- a/spec/unit/knife/osc_user_delete_spec.rb
+++ b/spec/unit/knife/osc_user_delete_spec.rb
@@ -31,7 +31,7 @@ describe Chef::Knife::OscUserDelete do
end
it 'deletes the user' do
- expect(@knife).to receive(:delete_object).with(Chef::OscUser, 'my_user')
+ expect(@knife).to receive(:delete_object).with(Chef::User, 'my_user')
@knife.run
end
diff --git a/spec/unit/knife/osc_user_edit_spec.rb b/spec/unit/knife/osc_user_edit_spec.rb
index 71a9192389..279f2e30ef 100644
--- a/spec/unit/knife/osc_user_edit_spec.rb
+++ b/spec/unit/knife/osc_user_edit_spec.rb
@@ -38,7 +38,7 @@ describe Chef::Knife::OscUserEdit do
it 'loads and edits the user' do
data = { :name => "my_user" }
- allow(Chef::OscUser).to receive(:load).with("my_user").and_return(data)
+ allow(Chef::User).to receive(:load).with("my_user").and_return(data)
expect(@knife).to receive(:edit_data).with(data).and_return(data)
@knife.run
end
diff --git a/spec/unit/knife/osc_user_list_spec.rb b/spec/unit/knife/osc_user_list_spec.rb
index 59a15be058..f496a414b8 100644
--- a/spec/unit/knife/osc_user_list_spec.rb
+++ b/spec/unit/knife/osc_user_list_spec.rb
@@ -30,7 +30,7 @@ describe Chef::Knife::OscUserList do
end
it 'lists the users' do
- expect(Chef::OscUser).to receive(:list)
+ expect(Chef::User).to receive(:list)
expect(@knife).to receive(:format_list_for_display)
@knife.run
end
diff --git a/spec/unit/knife/osc_user_reregister_spec.rb b/spec/unit/knife/osc_user_reregister_spec.rb
index 406bbf1f3e..989eb180f1 100644
--- a/spec/unit/knife/osc_user_reregister_spec.rb
+++ b/spec/unit/knife/osc_user_reregister_spec.rb
@@ -29,7 +29,7 @@ describe Chef::Knife::OscUserReregister do
@knife = Chef::Knife::OscUserReregister.new
@knife.name_args = [ 'a_user' ]
@user_mock = double('user_mock', :private_key => "private_key")
- allow(Chef::OscUser).to receive(:load).and_return(@user_mock)
+ allow(Chef::User).to receive(:load).and_return(@user_mock)
@stdout = StringIO.new
allow(@knife.ui).to receive(:stdout).and_return(@stdout)
end
diff --git a/spec/unit/knife/osc_user_show_spec.rb b/spec/unit/knife/osc_user_show_spec.rb
index 67b9b45809..18d2086099 100644
--- a/spec/unit/knife/osc_user_show_spec.rb
+++ b/spec/unit/knife/osc_user_show_spec.rb
@@ -32,7 +32,7 @@ describe Chef::Knife::OscUserShow do
end
it 'loads and displays the user' do
- expect(Chef::OscUser).to receive(:load).with('my_user').and_return(@user_mock)
+ expect(Chef::User).to receive(:load).with('my_user').and_return(@user_mock)
expect(@knife).to receive(:format_for_display).with(@user_mock)
@knife.run
end
diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb
index 8eda555108..fd46c47d99 100644
--- a/spec/unit/knife/ssl_check_spec.rb
+++ b/spec/unit/knife/ssl_check_spec.rb
@@ -163,6 +163,7 @@ E
expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs (no warn)
expect(ssl_socket).to receive(:connect) # no error
expect(ssl_socket).to receive(:post_connection_check).with("foo.example.com") # no error
+ expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
end
it "prints a success message" do
@@ -197,6 +198,7 @@ E
expect(ssl_socket).to receive(:post_connection_check).
with("foo.example.com").
and_raise(OpenSSL::SSL::SSLError)
+ expect(ssl_socket).to receive(:hostname=).with("foo.example.com") # no error
expect(ssl_socket_for_debug).to receive(:connect)
expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
end
@@ -215,6 +217,8 @@ E
expect(ssl_check).to receive(:verify_X509).and_return(true) # X509 valid certs
expect(ssl_socket).to receive(:connect).
and_raise(OpenSSL::SSL::SSLError)
+ expect(ssl_socket).to receive(:hostname=).
+ with("foo.example.com") # no error
expect(ssl_socket_for_debug).to receive(:connect)
expect(ssl_socket_for_debug).to receive(:peer_cert).and_return(self_signed_crt)
end
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
index 49d62cc2d7..fa5c8324b4 100644
--- a/spec/unit/knife/user_create_spec.rb
+++ b/spec/unit/knife/user_create_spec.rb
@@ -186,7 +186,7 @@ describe Chef::Knife::UserCreate do
context "when a private_key is returned" do
before do
- allow(knife).to receive(:create_user_from_hash).and_return(Chef::User.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"})))
+ allow(knife).to receive(:create_user_from_hash).and_return(Chef::UserV1.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"})))
end
context "when --file is passed" do
diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb
index e49c781358..a24160624a 100644
--- a/spec/unit/knife/user_delete_spec.rb
+++ b/spec/unit/knife/user_delete_spec.rb
@@ -26,7 +26,7 @@ describe Chef::Knife::UserDelete do
before(:each) do
Chef::Knife::UserDelete.load_deps
knife.name_args = [ 'my_user' ]
- allow(Chef::User).to receive(:load).and_return(user)
+ allow(Chef::UserV1).to receive(:load).and_return(user)
allow(user).to receive(:username).and_return('my_user')
allow(knife.ui).to receive(:stderr).and_return(stdout)
allow(knife.ui).to receive(:stdout).and_return(stdout)
@@ -51,7 +51,7 @@ describe Chef::Knife::UserDelete do
end
it 'deletes the user' do
- #expect(knife).to receive(:delete_object).with(Chef::User, 'my_user')
+ #expect(knife).to receive(:delete_object).with(Chef::UserV1, 'my_user')
expect(knife).to receive(:delete_object).with('my_user')
knife.run
end
diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb
index 15a7726b20..a21d982d29 100644
--- a/spec/unit/knife/user_edit_spec.rb
+++ b/spec/unit/knife/user_edit_spec.rb
@@ -36,7 +36,7 @@ describe Chef::Knife::UserEdit do
context "when the username field is not supported by the server" do
before do
allow(knife).to receive(:run_osc_11_user_edit).and_raise(SystemExit)
- allow(Chef::User).to receive(:load).and_return({"username" => nil})
+ allow(Chef::UserV1).to receive(:load).and_return({"username" => nil})
end
it "displays the osc warning" do
@@ -52,7 +52,7 @@ describe Chef::Knife::UserEdit do
it 'loads and edits the user' do
data = { "username" => "my_user" }
- allow(Chef::User).to receive(:load).with("my_user").and_return(data)
+ allow(Chef::UserV1).to receive(:load).with("my_user").and_return(data)
expect(knife).to receive(:edit_data).with(data).and_return(data)
knife.run
end
diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb
index 9990cc802d..fa2bac426e 100644
--- a/spec/unit/knife/user_list_spec.rb
+++ b/spec/unit/knife/user_list_spec.rb
@@ -29,7 +29,7 @@ describe Chef::Knife::UserList do
end
it 'lists the users' do
- expect(Chef::User).to receive(:list)
+ expect(Chef::UserV1).to receive(:list)
expect(knife).to receive(:format_list_for_display)
knife.run
end
diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/user_reregister_spec.rb
index 412a6ec374..89aa6726cd 100644
--- a/spec/unit/knife/user_reregister_spec.rb
+++ b/spec/unit/knife/user_reregister_spec.rb
@@ -26,7 +26,7 @@ describe Chef::Knife::UserReregister do
before do
Chef::Knife::UserReregister.load_deps
knife.name_args = [ 'a_user' ]
- allow(Chef::User).to receive(:load).and_return(user_mock)
+ allow(Chef::UserV1).to receive(:load).and_return(user_mock)
allow(knife.ui).to receive(:stdout).and_return(stdout)
allow(knife.ui).to receive(:stderr).and_return(stdout)
allow(user_mock).to receive(:username).and_return('a_user')
diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb
index 43392a3a5c..7c39e428c0 100644
--- a/spec/unit/knife/user_show_spec.rb
+++ b/spec/unit/knife/user_show_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Knife::UserShow do
context "when the username field is not supported by the server" do
before do
allow(knife).to receive(:run_osc_11_user_show).and_raise(SystemExit)
- allow(Chef::User).to receive(:load).with('my_user').and_return(user_mock)
+ allow(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock)
allow(user_mock).to receive(:username).and_return(nil)
end
@@ -51,7 +51,7 @@ describe Chef::Knife::UserShow do
end
it 'loads and displays the user' do
- expect(Chef::User).to receive(:load).with('my_user').and_return(user_mock)
+ expect(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock)
expect(knife).to receive(:format_for_display).with(user_mock)
knife.run
end
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
index 34c6f6f1c5..bcb64cb21e 100644
--- a/spec/unit/lwrp_spec.rb
+++ b/spec/unit/lwrp_spec.rb
@@ -163,7 +163,7 @@ describe "LWRP" do
it "Should load the old content, and not the new" do
resource = Chef::ResourceResolver.resolve(:lwrp_foo)
expect(resource).to eq @original_resource
- expect(resource.default_action).to eq(:pass_buck)
+ expect(resource.default_action).to eq([:pass_buck])
expect(Chef.method_defined?(:method_created_by_override_lwrp_foo)).to be_falsey
end
end
@@ -177,10 +177,6 @@ describe "LWRP" do
end
end
- it "should load the resource into a properly-named class and emit a warning when it is initialized" do
- expect { Chef::Resource::LwrpFoo.new('hi') }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
it "should be resolvable with Chef::ResourceResolver.resolve(:lwrp_foo)" do
expect(Chef::ResourceResolver.resolve(:lwrp_foo, node: Chef::Node.new)).to eq(get_lwrp(:lwrp_foo))
end
@@ -202,7 +198,7 @@ describe "LWRP" do
end
it "should set the specified action as the default action" do
- expect(get_lwrp(:lwrp_foo).new("blah").action).to eq(:pass_buck)
+ expect(get_lwrp(:lwrp_foo).new("blah").action).to eq([:pass_buck])
end
it "should create a method for each attribute" do
@@ -228,127 +224,6 @@ describe "LWRP" do
expect(cls.node[:penguin_name]).to eql("jackass")
end
- context "resource class created" do
- before do
- @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
- Chef::Config[:treat_deprecation_warnings_as_errors] = false
- end
- after do
- Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors
- end
-
- it "should load the resource into a properly-named class" do
- expect(Chef::Resource::LwrpFoo).to be_kind_of(Class)
- expect(Chef::Resource::LwrpFoo <= Chef::Resource::LWRPBase).to be_truthy
- end
-
- it "get_lwrp(:lwrp_foo).new is a Chef::Resource::LwrpFoo" do
- lwrp = get_lwrp(:lwrp_foo).new('hi')
- expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy
- expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy
- expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy
- expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy
- end
-
- it "Chef::Resource::LwrpFoo.new is a get_lwrp(:lwrp_foo)" do
- lwrp = Chef::Resource::LwrpFoo.new('hi')
- expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy
- expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy
- expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy
- expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy
- end
-
- it "works even if LwrpFoo exists in the top level" do
- module ::LwrpFoo
- end
- expect(Chef::Resource::LwrpFoo).not_to eq(::LwrpFoo)
- end
-
- context "with a subclass of get_lwrp(:lwrp_foo)" do
- let(:subclass) do
- Class.new(get_lwrp(:lwrp_foo))
- end
-
- it "subclass.new is a subclass" do
- lwrp = subclass.new('hi')
- expect(lwrp.kind_of?(subclass)).to be_truthy
- expect(lwrp.is_a?(subclass)).to be_truthy
- expect(subclass === lwrp).to be_truthy
- expect(lwrp.class === subclass)
- end
- it "subclass.new is a Chef::Resource::LwrpFoo" do
- lwrp = subclass.new('hi')
- expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy
- expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy
- expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy
- expect(lwrp.class === Chef::Resource::LwrpFoo)
- end
- it "subclass.new is a get_lwrp(:lwrp_foo)" do
- lwrp = subclass.new('hi')
- expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy
- expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy
- expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy
- expect(lwrp.class === get_lwrp(:lwrp_foo))
- end
- it "Chef::Resource::LwrpFoo.new is *not* a subclass" do
- lwrp = Chef::Resource::LwrpFoo.new('hi')
- expect(lwrp.kind_of?(subclass)).to be_falsey
- expect(lwrp.is_a?(subclass)).to be_falsey
- expect(subclass === lwrp.class).to be_falsey
- expect(subclass === Chef::Resource::LwrpFoo).to be_falsey
- end
- it "get_lwrp(:lwrp_foo).new is *not* a subclass" do
- lwrp = get_lwrp(:lwrp_foo).new('hi')
- expect(lwrp.kind_of?(subclass)).to be_falsey
- expect(lwrp.is_a?(subclass)).to be_falsey
- expect(subclass === lwrp.class).to be_falsey
- expect(subclass === get_lwrp(:lwrp_foo)).to be_falsey
- end
- end
-
- context "with a subclass of Chef::Resource::LwrpFoo" do
- let(:subclass) do
- Class.new(Chef::Resource::LwrpFoo)
- end
-
- it "subclass.new is a subclass" do
- lwrp = subclass.new('hi')
- expect(lwrp.kind_of?(subclass)).to be_truthy
- expect(lwrp.is_a?(subclass)).to be_truthy
- expect(subclass === lwrp).to be_truthy
- expect(lwrp.class === subclass)
- end
- it "subclass.new is a Chef::Resource::LwrpFoo" do
- lwrp = subclass.new('hi')
- expect(lwrp.kind_of?(Chef::Resource::LwrpFoo)).to be_truthy
- expect(lwrp.is_a?(Chef::Resource::LwrpFoo)).to be_truthy
- expect(Chef::Resource::LwrpFoo === lwrp).to be_truthy
- expect(lwrp.class === Chef::Resource::LwrpFoo)
- end
- it "subclass.new is a get_lwrp(:lwrp_foo)" do
- lwrp = subclass.new('hi')
- expect(lwrp.kind_of?(get_lwrp(:lwrp_foo))).to be_truthy
- expect(lwrp.is_a?(get_lwrp(:lwrp_foo))).to be_truthy
- expect(get_lwrp(:lwrp_foo) === lwrp).to be_truthy
- expect(lwrp.class === get_lwrp(:lwrp_foo))
- end
- it "Chef::Resource::LwrpFoo.new is *not* a subclass" do
- lwrp = Chef::Resource::LwrpFoo.new('hi')
- expect(lwrp.kind_of?(subclass)).to be_falsey
- expect(lwrp.is_a?(subclass)).to be_falsey
- expect(subclass === lwrp.class).to be_falsey
- expect(subclass === Chef::Resource::LwrpFoo).to be_falsey
- end
- it "get_lwrp(:lwrp_foo).new is *not* a subclass" do
- lwrp = get_lwrp(:lwrp_foo).new('hi')
- expect(lwrp.kind_of?(subclass)).to be_falsey
- expect(lwrp.is_a?(subclass)).to be_falsey
- expect(subclass === lwrp.class).to be_falsey
- expect(subclass === get_lwrp(:lwrp_foo)).to be_falsey
- end
- end
- end
-
context "resource_name" do
let(:klass) { Class.new(Chef::Resource::LWRPBase) }
@@ -409,17 +284,17 @@ describe "LWRP" do
end
end
- context "when the child does not defined the methods" do
+ context "when the child does not define the methods" do
let(:child) do
Class.new(parent)
end
it "delegates #actions to the parent" do
- expect(child.actions).to eq([:eat, :sleep])
+ expect(child.actions).to eq([:nothing, :eat, :sleep])
end
it "delegates #default_action to the parent" do
- expect(child.default_action).to eq(:eat)
+ expect(child.default_action).to eq([:eat])
end
end
@@ -432,11 +307,11 @@ describe "LWRP" do
end
it "does not delegate #actions to the parent" do
- expect(child.actions).to eq([:dont_eat, :dont_sleep])
+ expect(child.actions).to eq([:nothing, :dont_eat, :dont_sleep])
end
it "does not delegate #default_action to the parent" do
- expect(child.default_action).to eq(:dont_eat)
+ expect(child.default_action).to eq([:dont_eat])
end
end
@@ -457,11 +332,50 @@ describe "LWRP" do
it "amends actions when they are already defined" do
raise_if_deprecated!
- expect(child.actions).to eq([:eat, :sleep, :drink])
+ expect(child.actions).to eq([:nothing, :eat, :sleep, :drink])
+ end
+ end
+ end
+
+ describe "when actions is set to an array" do
+ let(:resource_class) do
+ Class.new(Chef::Resource::LWRPBase) do
+ actions [ :eat, :sleep ]
end
end
+ let(:resource) do
+ resource_class.new('blah')
+ end
+ it "actions includes those actions" do
+ expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "allowed_actions includes those actions" do
+ expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "resource.allowed_actions includes those actions" do
+ expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
end
+ describe "when allowed_actions is set to an array" do
+ let(:resource_class) do
+ Class.new(Chef::Resource::LWRPBase) do
+ allowed_actions [ :eat, :sleep ]
+ end
+ end
+ let(:resource) do
+ resource_class.new('blah')
+ end
+ it "actions includes those actions" do
+ expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "allowed_actions includes those actions" do
+ expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "resource.allowed_actions includes those actions" do
+ expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ end
end
describe "Lightweight Chef::Provider" do
@@ -525,7 +439,7 @@ describe "LWRP" do
end
it "sets itself as a provider for a resource of the same name" do
- found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :lwrp_buck_passer)
+ found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :lwrp_buck_passer)
# we bypass the per-file loading to get the file to load each time,
# which creates the LWRP class repeatedly. New things get prepended to
# the list of providers.
@@ -537,7 +451,7 @@ describe "LWRP" do
let(:lwrp_cookbok_name) { "l_w_r_p" }
it "sets itself as a provider for a resource of the same name" do
- found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer)
+ found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer)
expect(found_providers.size).to eq(1)
expect(found_providers.last).to eq(get_lwrp_provider(:l_w_r_p_buck_passer))
end
@@ -548,10 +462,10 @@ describe "LWRP" do
let(:lwrp_cookbok_name) { "l-w-r-p" }
it "sets itself as a provider for a resource of the same name" do
- incorrect_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :'l-w-r-p_buck_passer')
+ incorrect_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :'l-w-r-p_buck_passer')
expect(incorrect_providers).to eq([])
- found_providers = Chef::Platform::ProviderPriorityMap.instance.list_handlers(node, :l_w_r_p_buck_passer)
+ found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer)
expect(found_providers.first).to eq(get_lwrp_provider(:l_w_r_p_buck_passer))
end
end
@@ -665,7 +579,144 @@ describe "LWRP" do
end
end
-
end
+ context "resource class created" do
+ before(:context) do
+ @tmpdir = Dir.mktmpdir("lwrp_test")
+ resource_path = File.join(@tmpdir, "once.rb")
+ IO.write(resource_path, "default_action :create")
+
+ @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil)
+ end
+
+ after(:context) do
+ FileUtils.remove_entry @tmpdir
+ Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors
+ end
+
+ it "should load the resource into a properly-named class" do
+ expect(Chef::Resource::LwrpOnce).to be_kind_of(Class)
+ expect(Chef::Resource::LwrpOnce <= Chef::Resource::LWRPBase).to be_truthy
+ end
+
+ it "get_lwrp(:lwrp_once).new is a Chef::Resource::LwrpOnce" do
+ lwrp = get_lwrp(:lwrp_once).new('hi')
+ expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ end
+
+ it "Chef::Resource::LwrpOnce.new is a get_lwrp(:lwrp_once)" do
+ lwrp = Chef::Resource::LwrpOnce.new('hi')
+ expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ end
+
+ it "works even if LwrpOnce exists in the top level" do
+ module ::LwrpOnce
+ end
+ expect(Chef::Resource::LwrpOnce).not_to eq(::LwrpOnce)
+ end
+
+ it "allows monkey patching of the lwrp through Chef::Resource" do
+ monkey = Module.new do
+ def issue_3607
+ end
+ end
+ Chef::Resource::LwrpOnce.send(:include, monkey)
+ expect { get_lwrp(:lwrp_once).new("blah").issue_3607 }.not_to raise_error
+ end
+
+ context "with a subclass of get_lwrp(:lwrp_once)" do
+ let(:subclass) do
+ Class.new(get_lwrp(:lwrp_once))
+ end
+
+ it "subclass.new is a subclass" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_truthy
+ expect(lwrp.is_a?(subclass)).to be_truthy
+ expect(subclass === lwrp).to be_truthy
+ expect(lwrp.class === subclass)
+ end
+ it "subclass.new is a Chef::Resource::LwrpOnce" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ expect(lwrp.class === Chef::Resource::LwrpOnce)
+ end
+ it "subclass.new is a get_lwrp(:lwrp_once)" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(lwrp.class === get_lwrp(:lwrp_once))
+ end
+ it "Chef::Resource::LwrpOnce.new is *not* a subclass" do
+ lwrp = Chef::Resource::LwrpOnce.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === Chef::Resource::LwrpOnce).to be_falsey
+ end
+ it "get_lwrp(:lwrp_once).new is *not* a subclass" do
+ lwrp = get_lwrp(:lwrp_once).new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === get_lwrp(:lwrp_once)).to be_falsey
+ end
+ end
+
+ context "with a subclass of Chef::Resource::LwrpOnce" do
+ let(:subclass) do
+ Class.new(Chef::Resource::LwrpOnce)
+ end
+
+ it "subclass.new is a subclass" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_truthy
+ expect(lwrp.is_a?(subclass)).to be_truthy
+ expect(subclass === lwrp).to be_truthy
+ expect(lwrp.class === subclass)
+ end
+ it "subclass.new is a Chef::Resource::LwrpOnce" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ expect(lwrp.class === Chef::Resource::LwrpOnce)
+ end
+ it "subclass.new is a get_lwrp(:lwrp_once)" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(lwrp.class === get_lwrp(:lwrp_once))
+ end
+ it "Chef::Resource::LwrpOnce.new is *not* a subclass" do
+ lwrp = Chef::Resource::LwrpOnce.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === Chef::Resource::LwrpOnce).to be_falsey
+ end
+ it "get_lwrp(:lwrp_once).new is *not* a subclass" do
+ lwrp = get_lwrp(:lwrp_once).new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === get_lwrp(:lwrp_once)).to be_falsey
+ end
+ end
+ end
end
+
+
diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb
index 85e1c1abab..3724bbf583 100644
--- a/spec/unit/mixin/params_validate_spec.rb
+++ b/spec/unit/mixin/params_validate_spec.rb
@@ -21,6 +21,8 @@ require 'spec_helper'
class TinyClass
include Chef::Mixin::ParamsValidate
+ attr_reader :name
+
def music(is_good=true)
is_good
end
@@ -331,11 +333,11 @@ describe Chef::Mixin::ParamsValidate do
it "asserts that a value returns false from a predicate method" do
expect do
@vo.validate({:not_blank => "should pass"},
- {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+ {:not_blank => {:cannot_be => [ :nil, :empty ]}})
end.not_to raise_error
expect do
@vo.validate({:not_blank => ""},
- {:not_blank => {:cannot_be => :nil, :cannot_be => :empty}})
+ {:not_blank => {:cannot_be => [ :nil, :empty ]}})
end.to raise_error(Chef::Exceptions::ValidationFailed)
end
diff --git a/spec/unit/mixin/windows_architecture_helper_spec.rb b/spec/unit/mixin/windows_architecture_helper_spec.rb
index 3803d69371..55eca28dc2 100644
--- a/spec/unit/mixin/windows_architecture_helper_spec.rb
+++ b/spec/unit/mixin/windows_architecture_helper_spec.rb
@@ -60,23 +60,28 @@ describe Chef::Mixin::WindowsArchitectureHelper do
end
end
- it "returns true for each supported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture" do
- enumerate_architecture_node_combinations(true)
+ it "returns true only for supported desired architecture passed to node_supports_windows_architecture" do
+ with_node_architecture_combinations do | node, desired_arch |
+ expect(node_supports_windows_architecture?(node, desired_arch)).to be true if (node_windows_architecture(node) == :x86_64 || desired_arch == :i386 )
+ expect(node_supports_windows_architecture?(node, desired_arch)).to be false if (node_windows_architecture(node) == :i386 && desired_arch == :x86_64 )
+ end
end
- it "returns false for each unsupported desired architecture for all nodes with each valid architecture passed to node_supports_windows_architecture?" do
- enumerate_architecture_node_combinations(true)
+ it "returns true only when forced_32bit_override_required? has 64-bit node architecture and 32-bit desired architecture" do
+ with_node_architecture_combinations do | node, desired_arch |
+ expect(forced_32bit_override_required?(node, desired_arch)).to be true if ((node_windows_architecture(node) == :x86_64) && (desired_arch == :i386) && !is_i386_process_on_x86_64_windows?)
+ expect(forced_32bit_override_required?(node, desired_arch)).to be false if ! ((node_windows_architecture(node) == :x86_64) && (desired_arch == :i386))
+ end
end
- def enumerate_architecture_node_combinations(only_valid_combinations)
+ def with_node_architecture_combinations
@valid_architectures.each do | node_architecture |
new_node = Chef::Node.new
new_node.default["kernel"] = Hash.new
new_node.default["kernel"][:machine] = node_architecture.to_s
- @valid_architectures.each do | supported_architecture |
- expect(node_supports_windows_architecture?(new_node, supported_architecture)).to eq(true) if only_valid_combinations && (supported_architecture != :x86_64 && node_architecture != :i386 )
- expect(node_supports_windows_architecture?(new_node, supported_architecture)).to eq(false) if ! only_valid_combinations && (supported_architecture == :x86_64 && node_architecture == :i386 )
+ @valid_architectures.each do | architecture |
+ yield new_node, architecture if block_given?
end
end
end
diff --git a/spec/unit/node_map_spec.rb b/spec/unit/node_map_spec.rb
index 9b5ff5e8c6..7b37ea59f4 100644
--- a/spec/unit/node_map_spec.rb
+++ b/spec/unit/node_map_spec.rb
@@ -131,6 +131,18 @@ describe Chef::NodeMap do
allow(node).to receive(:[]).with(:platform_version).and_return("6.0")
expect(node_map.get(node, :thing)).to eql(nil)
end
+
+ context "when there is a less specific definition" do
+ before do
+ node_map.set(:thing, :bar, platform_family: "rhel")
+ end
+
+ it "returns the value when the node matches" do
+ allow(node).to receive(:[]).with(:platform_family).and_return("rhel")
+ allow(node).to receive(:[]).with(:platform_version).and_return("7.0")
+ expect(node_map.get(node, :thing)).to eql(:foo)
+ end
+ end
end
describe "resource back-compat testing" do
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index 5939403ce6..b7752eb734 100644
--- a/spec/unit/node_spec.rb
+++ b/spec/unit/node_spec.rb
@@ -672,6 +672,13 @@ describe Chef::Node do
expect(node.run_list).to eq([ "role[base]", "recipe[chef::server]" ])
end
+ it "sets the node chef_environment" do
+ attrs = { "chef_environment" => "foo_environment", "bar" => "baz" }
+ expect(node.consume_chef_environment(attrs)).to eq({ "bar" => "baz" })
+ expect(node.chef_environment).to eq("foo_environment")
+ expect(node['chef_environment']).to be nil
+ end
+
it "should overwrites the run list with the run list it consumes" do
node.consume_run_list "recipes" => [ "one", "two" ]
node.consume_run_list "recipes" => [ "three" ]
@@ -1106,7 +1113,7 @@ describe Chef::Node do
expect(serialized_node.run_list).to eq(node.run_list)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) {
node.from_file(File.expand_path("nodes/test.example.com.rb", CHEF_SPEC_DATA))
node
diff --git a/spec/unit/osc_user_spec.rb b/spec/unit/osc_user_spec.rb
deleted file mode 100644
index 678486a16d..0000000000
--- a/spec/unit/osc_user_spec.rb
+++ /dev/null
@@ -1,276 +0,0 @@
-#
-# Author:: Steven Danna (steve@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.
-#
-
-# DEPRECATION NOTE
-# This code only remains to support users still operating with
-# Open Source Chef Server 11 and should be removed once support
-# for OSC 11 ends. New development should occur in user_spec.rb.
-
-require 'spec_helper'
-
-require 'chef/osc_user'
-require 'tempfile'
-
-describe Chef::OscUser do
- before(:each) do
- @user = Chef::OscUser.new
- end
-
- describe "initialize" do
- it "should be a Chef::OscUser" do
- expect(@user).to be_a_kind_of(Chef::OscUser)
- end
- end
-
- describe "name" do
- it "should let you set the name to a string" do
- expect(@user.name("ops_master")).to eq("ops_master")
- end
-
- it "should return the current name" do
- @user.name "ops_master"
- expect(@user.name).to eq("ops_master")
- end
-
- # It is not feasible to check all invalid characters. Here are a few
- # that we probably care about.
- it "should not accept invalid characters" do
- # capital letters
- expect { @user.name "Bar" }.to raise_error(ArgumentError)
- # slashes
- expect { @user.name "foo/bar" }.to raise_error(ArgumentError)
- # ?
- expect { @user.name "foo?" }.to raise_error(ArgumentError)
- # &
- expect { @user.name "foo&" }.to raise_error(ArgumentError)
- end
-
-
- it "should not accept spaces" do
- expect { @user.name "ops master" }.to raise_error(ArgumentError)
- end
-
- it "should throw an ArgumentError if you feed it anything but a string" do
- expect { @user.name Hash.new }.to raise_error(ArgumentError)
- end
- end
-
- describe "admin" do
- it "should let you set the admin bit" do
- expect(@user.admin(true)).to eq(true)
- end
-
- it "should return the current admin value" do
- @user.admin true
- expect(@user.admin).to eq(true)
- end
-
- it "should default to false" do
- expect(@user.admin).to eq(false)
- end
-
- it "should throw an ArgumentError if you feed it anything but true or false" do
- expect { @user.name Hash.new }.to raise_error(ArgumentError)
- end
- end
-
- describe "public_key" do
- it "should let you set the public key" do
- expect(@user.public_key("super public")).to eq("super public")
- end
-
- it "should return the current public key" do
- @user.public_key("super public")
- expect(@user.public_key).to eq("super public")
- end
-
- it "should throw an ArgumentError if you feed it something lame" do
- expect { @user.public_key Hash.new }.to raise_error(ArgumentError)
- end
- end
-
- describe "private_key" do
- it "should let you set the private key" do
- expect(@user.private_key("super private")).to eq("super private")
- end
-
- it "should return the private key" do
- @user.private_key("super private")
- expect(@user.private_key).to eq("super private")
- end
-
- it "should throw an ArgumentError if you feed it something lame" do
- expect { @user.private_key Hash.new }.to 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
- expect(@json).to match(/^\{.+\}$/)
- end
-
- it "includes the name value" do
- expect(@json).to include(%q{"name":"black"})
- end
-
- it "includes the public key value" do
- expect(@json).to include(%{"public_key":"crowes"})
- end
-
- it "includes the 'admin' flag" do
- expect(@json).to include(%q{"admin":false})
- end
-
- it "includes the private key when present" do
- @user.private_key("monkeypants")
- expect(@user.to_json).to include(%q{"private_key":"monkeypants"})
- end
-
- it "does not include the private key if not present" do
- expect(@json).not_to include("private_key")
- end
-
- it "includes the password if present" do
- @user.password "password"
- expect(@user.to_json).to include(%q{"password":"password"})
- end
-
- it "does not include the password if not present" do
- expect(@json).not_to include("password")
- end
-
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
- let(:jsonable) { @user }
- 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::OscUser.from_json(Chef::JSONCompat.to_json(user))
- end
-
- it "should deserialize to a Chef::OscUser object" do
- expect(@user).to be_a_kind_of(Chef::OscUser)
- end
-
- it "preserves the name" do
- expect(@user.name).to eq("mr_spinks")
- end
-
- it "preserves the public key" do
- expect(@user.public_key).to eq("turtles")
- end
-
- it "preserves the admin status" do
- expect(@user.admin).to be_truthy
- end
-
- it "includes the private key if present" do
- expect(@user.private_key).to eq("pandas")
- end
-
- it "includes the password if present" do
- expect(@user.password).to eq("password")
- end
-
- end
-
- describe "API Interactions" do
- before (:each) do
- @user = Chef::OscUser.new
- @user.name "foobar"
- @http_client = double("Chef::REST mock")
- allow(Chef::REST).to receive(:new).and_return(@http_client)
- end
-
- describe "list" do
- before(:each) do
- Chef::Config[:chef_server_url] = "http://www.example.com"
- @osc_response = { "admin" => "http://www.example.com/users/admin"}
- @ohc_response = [ { "user" => { "username" => "admin" }} ]
- allow(Chef::OscUser).to receive(:load).with("admin").and_return(@user)
- @osc_inflated_response = { "admin" => @user }
- end
-
- it "lists all clients on an OSC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
- expect(Chef::OscUser.list).to eq(@osc_response)
- end
-
- it "inflate all clients on an OSC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
- expect(Chef::OscUser.list(true)).to eq(@osc_inflated_response)
- end
-
- it "lists all clients on an OHC/OPC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
- # We expect that Chef::OscUser.list will give a consistent response
- # so OHC API responses should be transformed to OSC-style output.
- expect(Chef::OscUser.list).to eq(@osc_response)
- end
-
- it "inflate all clients on an OHC/OPC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
- expect(Chef::OscUser.list(true)).to eq(@osc_inflated_response)
- end
- end
-
- describe "create" do
- it "creates a new user via the API" do
- @user.password "password"
- expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({})
- @user.create
- end
- end
-
- describe "read" do
- it "loads a named user from the API" do
- expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
- user = Chef::OscUser.load("foobar")
- expect(user.name).to eq("foobar")
- expect(user.admin).to eq(true)
- expect(user.public_key).to eq("pubkey")
- end
- end
-
- describe "update" do
- it "updates an existing user on via the API" do
- expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({})
- @user.update
- end
- end
-
- describe "destroy" do
- it "deletes the specified user via the API" do
- expect(@http_client).to receive(:delete_rest).with("users/foobar")
- @user.destroy
- end
- end
- end
-end
diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb
index 36325d5411..34b46f657f 100644
--- a/spec/unit/platform_spec.rb
+++ b/spec/unit/platform_spec.rb
@@ -103,7 +103,7 @@ describe Chef::Platform do
end
it "should raise an exception if a provider cannot be found for a resource type" do
- expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(ArgumentError)
+ expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(Chef::Exceptions::ProviderNotFound)
end
it "should look up a provider for a resource with a Chef::Resource object" do
diff --git a/spec/unit/property/state_spec.rb b/spec/unit/property/state_spec.rb
new file mode 100644
index 0000000000..e7fee0387f
--- /dev/null
+++ b/spec/unit/property/state_spec.rb
@@ -0,0 +1,506 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource#identity and #state" do
+ include IntegrationSupport
+
+ class NewResourceNamer
+ @i = 0
+ def self.next
+ "chef_resource_property_spec_#{@i += 1}"
+ end
+ end
+
+ def self.new_resource_name
+ NewResourceNamer.next
+ end
+
+ let(:resource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(Chef::Resource) do
+ resource_name new_resource_name
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' if values.size == 0
+ return values[0].inspect if values.size == 1
+ "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+ end
+
+ def self.with_property(*properties, &block)
+ tags_index = properties.find_index { |p| !p.is_a?(String)}
+ if tags_index
+ properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+ else
+ tags = []
+ end
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(property_str, __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ # identity
+ context "Chef::Resource#identity_properties" do
+ with_property ":x" do
+ it "name is the default identity" do
+ expect(resource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ]
+ expect(Chef::Resource.properties[:name].identity?).to be_falsey
+ expect(resource.name).to eq 'blah'
+ expect(resource.identity).to eq 'blah'
+ end
+
+ it "identity_properties :x changes the identity" do
+ expect(resource_class.identity_properties :x).to eq [ resource_class.properties[:x] ]
+ expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+ expect(Chef::Resource.properties[:name].identity?).to be_falsey
+ expect(resource_class.properties[:x].identity?).to be_truthy
+
+ expect(resource.x 'woo').to eq 'woo'
+ expect(resource.x).to eq 'woo'
+
+ expect(resource.name).to eq 'blah'
+ expect(resource.identity).to eq 'woo'
+ end
+
+ with_property ":y, identity: true" do
+ context "and identity_properties :x" do
+ before do
+ resource_class.class_eval do
+ identity_properties :x
+ end
+ end
+
+ it "only returns :x as identity" do
+ resource.x 'foo'
+ resource.y 'bar'
+ expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+ expect(resource.identity).to eq 'foo'
+ end
+ it "does not flip y.desired_state off" do
+ resource.x 'foo'
+ resource.y 'bar'
+ expect(resource_class.state_properties).to eq [
+ resource_class.properties[:x],
+ resource_class.properties[:y]
+ ]
+ expect(resource.state_for_resource_reporter).to eq(x: 'foo', y: 'bar')
+ end
+ end
+ end
+
+ context "With a subclass" do
+ let(:subresource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(resource_class) do
+ resource_name new_resource_name
+ end
+ end
+ let(:subresource) do
+ subresource_class.new('sub')
+ end
+
+ it "name is the default identity on the subclass" do
+ expect(subresource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ]
+ expect(Chef::Resource.properties[:name].identity?).to be_falsey
+ expect(subresource.name).to eq 'sub'
+ expect(subresource.identity).to eq 'sub'
+ end
+
+ context "With identity_properties :x on the superclass" do
+ before do
+ resource_class.class_eval do
+ identity_properties :x
+ end
+ end
+
+ it "The subclass inherits :x as identity" do
+ expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x] ]
+ expect(Chef::Resource.properties[:name].identity?).to be_falsey
+ expect(subresource_class.properties[:x].identity?).to be_truthy
+
+ subresource.x 'foo'
+ expect(subresource.identity).to eq 'foo'
+ end
+
+ context "With property :y, identity: true on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :y, identity: true
+ end
+ end
+ it "The subclass's identity includes both x and y" do
+ expect(subresource_class.identity_properties).to eq [
+ subresource_class.properties[:x],
+ subresource_class.properties[:y]
+ ]
+ subresource.x 'foo'
+ subresource.y 'bar'
+ expect(subresource.identity).to eq(x: 'foo', y: 'bar')
+ end
+ end
+
+ with_property ":y, String" do
+ context "With identity_properties :y on the subclass" do
+ before do
+ subresource_class.class_eval do
+ identity_properties :y
+ end
+ end
+ it "y is part of state" do
+ subresource.x 'foo'
+ subresource.y 'bar'
+ expect(subresource.state_for_resource_reporter).to eq(x: 'foo', y: 'bar')
+ expect(subresource_class.state_properties).to eq [
+ subresource_class.properties[:x],
+ subresource_class.properties[:y]
+ ]
+ end
+ it "y is the identity" do
+ expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:y] ]
+ subresource.x 'foo'
+ subresource.y 'bar'
+ expect(subresource.identity).to eq 'bar'
+ end
+ it "y still has validation" do
+ expect { subresource.y 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ end
+ end
+ end
+ end
+
+ with_property ":string_only, String, identity: true", ":string_only2, String" do
+ it "identity_properties does not change validation" do
+ resource_class.identity_properties :string_only
+ expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ with_property ":x, desired_state: false" do
+ it "identity_properties does not change desired_state" do
+ resource_class.identity_properties :x
+ resource.x 'hi'
+ expect(resource.identity).to eq 'hi'
+ expect(resource_class.properties[:x].desired_state?).to be_falsey
+ expect(resource_class.state_properties).to eq []
+ expect(resource.state_for_resource_reporter).to eq({})
+ end
+ end
+
+ context "With custom property custom_property defined only as methods, using different variables for storage" do
+ before do
+ resource_class.class_eval do
+ def custom_property
+ @blarghle ? @blarghle*3 : nil
+ end
+ def custom_property=(x)
+ @blarghle = x*2
+ end
+ end
+ end
+
+ context "And identity_properties :custom_property" do
+ before do
+ resource_class.class_eval do
+ identity_properties :custom_property
+ end
+ end
+
+ it "identity_properties comes back as :custom_property" do
+ expect(resource_class.properties[:custom_property].identity?).to be_truthy
+ expect(resource_class.identity_properties).to eq [ resource_class.properties[:custom_property] ]
+ end
+ it "custom_property becomes part of desired_state" do
+ resource.custom_property = 1
+ expect(resource.state_for_resource_reporter).to eq(custom_property: 6)
+ expect(resource_class.properties[:custom_property].desired_state?).to be_truthy
+ expect(resource_class.state_properties).to eq [
+ resource_class.properties[:custom_property]
+ ]
+ end
+ it "identity_properties does not change custom_property's getter or setter" do
+ resource.custom_property = 1
+ expect(resource.custom_property).to eq 6
+ end
+ it "custom_property is returned as the identity" do
+ expect(resource.identity).to be_nil
+ resource.custom_property = 1
+ expect(resource.identity).to eq 6
+ end
+ end
+ end
+ end
+
+ context "Property#identity" do
+ with_property ":x, identity: true" do
+ it "name is only part of the identity if an identity attribute is defined" do
+ expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+ resource.x 'woo'
+ expect(resource.identity).to eq 'woo'
+ end
+ end
+
+ with_property ":x, identity: true, default: 'xxx'",
+ ":y, identity: true, default: 'yyy'",
+ ":z, identity: true, default: 'zzz'" do
+ it "identity_property raises an error if multiple identity values are defined" do
+ expect { resource_class.identity_property }.to raise_error Chef::Exceptions::MultipleIdentityError
+ end
+ it "identity_attr raises an error if multiple identity values are defined" do
+ expect { resource_class.identity_attr }.to raise_error Chef::Exceptions::MultipleIdentityError
+ end
+ it "identity returns all identity values in a hash if multiple are defined" do
+ resource.x 'foo'
+ resource.y 'bar'
+ resource.z 'baz'
+ expect(resource.identity).to eq(x: 'foo', y: 'bar', z: 'baz')
+ end
+ it "identity returns all values whether any value is set or not" do
+ expect(resource.identity).to eq(x: 'xxx', y: 'yyy', z: 'zzz')
+ end
+ it "identity_properties wipes out any other identity attributes if multiple are defined" do
+ resource_class.identity_properties :y
+ resource.x 'foo'
+ resource.y 'bar'
+ resource.z 'baz'
+ expect(resource.identity).to eq 'bar'
+ end
+ end
+
+ with_property ":x, identity: true, name_property: true" do
+ it "identity when x is not defined returns the value of x" do
+ expect(resource.identity).to eq 'blah'
+ end
+ it "state when x is not defined returns the value of x" do
+ expect(resource.state_for_resource_reporter).to eq(x: 'blah')
+ end
+ end
+ end
+
+ # state_properties
+ context "Chef::Resource#state_properties" do
+ it "state_properties is empty by default" do
+ expect(Chef::Resource.state_properties).to eq []
+ expect(resource.state_for_resource_reporter).to eq({})
+ end
+
+ with_property ":x", ":y", ":z" do
+ it "x, y and z are state attributes" do
+ resource.x 1
+ resource.y 2
+ resource.z 3
+ expect(resource_class.state_properties).to eq [
+ resource_class.properties[:x],
+ resource_class.properties[:y],
+ resource_class.properties[:z]
+ ]
+ expect(resource.state_for_resource_reporter).to eq(x: 1, y: 2, z: 3)
+ end
+ it "values that are not set are not included in state" do
+ resource.x 1
+ expect(resource.state_for_resource_reporter).to eq(x: 1)
+ end
+ it "when no values are set, nothing is included in state" do
+ end
+ end
+
+ with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do
+ it "x and z are state attributes, and y is not" do
+ resource.x 1
+ resource.y 2
+ resource.z 3
+ expect(resource_class.state_properties).to eq [
+ resource_class.properties[:x],
+ resource_class.properties[:z]
+ ]
+ expect(resource.state_for_resource_reporter).to eq(x: 1, z: 3)
+ end
+ end
+
+ with_property ":x, name_property: true" do
+ # it "Unset values with name_property are included in state" do
+ # expect(resource.state_for_resource_reporter).to eq({ x: 'blah' })
+ # end
+ it "Set values with name_property are included in state" do
+ resource.x 1
+ expect(resource.state_for_resource_reporter).to eq(x: 1)
+ end
+ end
+
+ with_property ":x, default: 1" do
+ it "Unset values with defaults are not included in state" do
+ expect(resource.state_for_resource_reporter).to eq({})
+ end
+ it "Set values with defaults are included in state" do
+ resource.x 1
+ expect(resource.state_for_resource_reporter).to eq(x: 1)
+ end
+ end
+
+ context "With a class with a normal getter and setter" do
+ before do
+ resource_class.class_eval do
+ def x
+ @blah*3
+ end
+ def x=(value)
+ @blah = value*2
+ end
+ end
+ end
+ it "state_properties(:x) causes the value to be included in properties" do
+ resource_class.state_properties(:x)
+ resource.x = 1
+
+ expect(resource.x).to eq 6
+ expect(resource.state_for_resource_reporter).to eq(x: 6)
+ end
+ end
+
+ context "When state_properties happens before properties are declared" do
+ before do
+ resource_class.class_eval do
+ state_properties :x
+ property :x
+ end
+ end
+ it "the property works and is in state_properties" do
+ expect(resource_class.state_properties).to include(resource_class.properties[:x])
+ resource.x = 1
+ expect(resource.x).to eq 1
+ expect(resource.state_for_resource_reporter).to eq(x: 1)
+ end
+ end
+
+ with_property ":x, Integer, identity: true" do
+ it "state_properties(:x) leaves the property in desired_state" do
+ resource_class.state_properties(:x)
+ resource.x 10
+
+ expect(resource_class.properties[:x].desired_state?).to be_truthy
+ expect(resource_class.state_properties).to eq [
+ resource_class.properties[:x]
+ ]
+ expect(resource.state_for_resource_reporter).to eq(x: 10)
+ end
+ it "state_properties(:x) does not turn off validation" do
+ resource_class.state_properties(:x)
+ expect { resource.x 'ouch' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "state_properties(:x) does not turn off identity" do
+ resource_class.state_properties(:x)
+ resource.x 10
+
+ expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ]
+ expect(resource_class.properties[:x].identity?).to be_truthy
+ expect(resource.identity).to eq 10
+ end
+ end
+
+ with_property ":x, Integer, identity: true, desired_state: false" do
+ before do
+ resource_class.class_eval do
+ def y
+ 20
+ end
+ end
+ end
+
+ it "state_properties(:x) leaves x identical" do
+ old_value = resource_class.properties[:y]
+ resource_class.state_properties(:x)
+ resource.x 10
+
+ expect(resource_class.properties[:y].object_id).to eq old_value.object_id
+
+ expect(resource_class.properties[:x].desired_state?).to be_truthy
+ expect(resource_class.properties[:x].identity?).to be_truthy
+ expect(resource_class.identity_properties).to eq [
+ resource_class.properties[:x]
+ ]
+ expect(resource.identity).to eq(10)
+ expect(resource_class.state_properties).to eq [
+ resource_class.properties[:x]
+ ]
+ expect(resource.state_for_resource_reporter).to eq(x: 10)
+ end
+
+ it "state_properties(:y) adds y to desired state" do
+ old_value = resource_class.properties[:x]
+ resource_class.state_properties(:y)
+ resource.x 10
+
+ expect(resource_class.properties[:x].object_id).to eq old_value.object_id
+ expect(resource_class.properties[:x].desired_state?).to be_falsey
+ expect(resource_class.properties[:y].desired_state?).to be_truthy
+ expect(resource_class.state_properties).to eq [
+ resource_class.properties[:y]
+ ]
+ expect(resource.state_for_resource_reporter).to eq(y: 20)
+ end
+
+ context "With a subclassed resource" do
+ let(:subresource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(resource_class) do
+ resource_name new_resource_name
+ end
+ end
+ let(:subresource) do
+ subresource_class.new('blah')
+ end
+
+ it "state_properties(:x) adds x to desired state" do
+ old_value = resource_class.properties[:y]
+ subresource_class.state_properties(:x)
+ subresource.x 10
+
+ expect(subresource_class.properties[:y].object_id).to eq old_value.object_id
+
+ expect(subresource_class.properties[:x].desired_state?).to be_truthy
+ expect(subresource_class.properties[:x].identity?).to be_truthy
+ expect(subresource_class.identity_properties).to eq [
+ subresource_class.properties[:x]
+ ]
+ expect(subresource.identity).to eq(10)
+ expect(subresource_class.state_properties).to eq [
+ subresource_class.properties[:x]
+ ]
+ expect(subresource.state_for_resource_reporter).to eq(x: 10)
+ end
+
+ it "state_properties(:y) adds y to desired state" do
+ old_value = resource_class.properties[:x]
+ subresource_class.state_properties(:y)
+ subresource.x 10
+
+ expect(subresource_class.properties[:x].object_id).to eq old_value.object_id
+ expect(subresource_class.properties[:y].desired_state?).to be_truthy
+ expect(subresource_class.state_properties).to eq [
+ subresource_class.properties[:y]
+ ]
+ expect(subresource.state_for_resource_reporter).to eq(y: 20)
+
+ expect(subresource_class.properties[:x].identity?).to be_truthy
+ expect(subresource_class.identity_properties).to eq [
+ subresource_class.properties[:x]
+ ]
+ expect(subresource.identity).to eq(10)
+ end
+ end
+ end
+ end
+
+end
diff --git a/spec/unit/property/validation_spec.rb b/spec/unit/property/validation_spec.rb
new file mode 100644
index 0000000000..31bb3f0739
--- /dev/null
+++ b/spec/unit/property/validation_spec.rb
@@ -0,0 +1,663 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property validation" do
+ include IntegrationSupport
+
+ module Namer
+ @i = 0
+ def self.next_resource_name
+ "chef_resource_property_spec_#{@i += 1}"
+ end
+ def self.reset_index
+ @current_index = 0
+ end
+ def self.current_index
+ @current_index
+ end
+ def self.next_index
+ @current_index += 1
+ end
+ end
+
+ def lazy(&block)
+ Chef::DelayedEvaluator.new(&block)
+ end
+
+ before do
+ Namer.reset_index
+ end
+
+ def self.new_resource_name
+ Namer.next_resource_name
+ end
+
+ let(:resource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(Chef::Resource) do
+ resource_name new_resource_name
+ def blah
+ Namer.next_index
+ end
+ def self.blah
+ "class#{Namer.next_index}"
+ end
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' if values.size == 0
+ return values[0].inspect if values.size == 1
+ "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+ end
+
+ def self.with_property(*properties, &block)
+ tags_index = properties.find_index { |p| !p.is_a?(String)}
+ if tags_index
+ properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+ else
+ tags = []
+ end
+ properties = properties.map { |property| "property #{property}" }
+ context "With properties #{english_join(properties)}", *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval(property_str, __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ def self.validation_test(validation, success_values, failure_values, getter_values=[], *tags)
+ with_property ":x, #{validation}", *tags do
+ it "gets nil when retrieving the initial (non-set) value" do
+ expect(resource.x).to be_nil
+ end
+ success_values.each do |v|
+ it "value #{v.inspect} is valid" do
+ resource.instance_eval { @x = 'default' }
+ expect(resource.x v).to eq v
+ expect(resource.x).to eq v
+ end
+ end
+ failure_values.each do |v|
+ it "value #{v.inspect} is invalid" do
+ expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed
+ resource.instance_eval { @x = 'default' }
+ expect { resource.x v }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ getter_values.each do |v|
+ it "setting value to #{v.inspect} does not change the value" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.instance_eval { @x = 'default' }
+ expect(resource.x v).to eq 'default'
+ expect(resource.x).to eq 'default'
+ end
+ end
+ end
+ end
+
+ context "basic get, set, and nil set" do
+ with_property ":x, kind_of: String" do
+ context "when the variable already has a value" do
+ before do
+ resource.instance_eval { @x = 'default' }
+ end
+ it "get succeeds" do
+ expect(resource.x).to eq 'default'
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "set to nil emits a deprecation warning and does a get" do
+ expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 'str'
+ expect(resource.x nil).to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ end
+ context "when the variable does not have an initial value" do
+ it "get succeeds" do
+ expect(resource.x).to be_nil
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "set to nil emits a deprecation warning and does a get" do
+ expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 'str'
+ expect(resource.x nil).to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ end
+ end
+ with_property ":x, [ String, nil ]" do
+ context "when the variable already has a value" do
+ before do
+ resource.instance_eval { @x = 'default' }
+ end
+ it "get succeeds" do
+ expect(resource.x).to eq 'default'
+ end
+ it "set(nil) sets the value" do
+ expect(resource.x nil).to be_nil
+ expect(resource.x).to be_nil
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ context "when the variable does not have an initial value" do
+ it "get succeeds" do
+ expect(resource.x).to be_nil
+ end
+ it "set(nil) sets the value" do
+ expect(resource.x nil).to be_nil
+ expect(resource.x).to be_nil
+ end
+ it "set to valid value succeeds" do
+ expect(resource.x 'str').to eq 'str'
+ expect(resource.x).to eq 'str'
+ end
+ it "set to invalid value raises ValidationFailed" do
+ expect { resource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+ end
+ end
+
+ # Bare types
+ context "bare types" do
+ validation_test 'String',
+ [ 'hi' ],
+ [ 10 ],
+ [ nil ]
+
+ validation_test ':a',
+ [ :a ],
+ [ :b ],
+ [ nil ]
+
+ validation_test ':a, is: :b',
+ [ :a, :b ],
+ [ :c ],
+ [ nil ]
+
+ validation_test ':a, is: [ :b, :c ]',
+ [ :a, :b, :c ],
+ [ :d ],
+ [ nil ]
+
+ validation_test '[ :a, :b ], is: :c',
+ [ :a, :b, :c ],
+ [ :d ],
+ [ nil ]
+
+ validation_test '[ :a, :b ], is: [ :c, :d ]',
+ [ :a, :b, :c, :d ],
+ [ :e ],
+ [ nil ]
+
+ validation_test 'nil',
+ [ nil ],
+ [ :a ]
+
+ validation_test '[ nil ]',
+ [ nil ],
+ [ :a ]
+
+ validation_test '[]',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # is
+ context "is" do
+ # Class
+ validation_test 'is: String',
+ [ 'a', '' ],
+ [ :a, 1 ],
+ [ nil ]
+
+ # Value
+ validation_test 'is: :a',
+ [ :a ],
+ [ :b ],
+ [ nil ]
+
+ validation_test 'is: [ :a, :b ]',
+ [ :a, :b ],
+ [ [ :a, :b ] ],
+ [ nil ]
+
+ validation_test 'is: [ [ :a, :b ] ]',
+ [ [ :a, :b ] ],
+ [ :a, :b ],
+ [ nil ]
+
+ # Regex
+ validation_test 'is: /abc/',
+ [ 'abc', 'wowabcwow' ],
+ [ '', 'abac' ],
+ [ nil ]
+
+ # Property
+ validation_test 'is: Chef::Property.new(is: :a)',
+ [ :a ],
+ [ :b, nil ]
+
+ # RSpec Matcher
+ class Globalses
+ extend RSpec::Matchers
+ end
+
+ validation_test "is: Globalses.eq(10)",
+ [ 10 ],
+ [ 1 ],
+ [ nil ]
+
+ # Proc
+ validation_test 'is: proc { |x| x }',
+ [ true, 1 ],
+ [ false ],
+ [ nil ]
+
+ validation_test 'is: proc { |x| x > blah }',
+ [ 10 ],
+ [ -1 ]
+
+ validation_test 'is: nil',
+ [ nil ],
+ [ 'a' ]
+
+ validation_test 'is: [ String, nil ]',
+ [ 'a', nil ],
+ [ :b ]
+
+ validation_test 'is: []',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # Combination
+ context "combination" do
+ validation_test 'kind_of: String, equal_to: "a"',
+ [ 'a' ],
+ [ 'b' ],
+ [ nil ]
+ end
+
+ # equal_to
+ context "equal_to" do
+ # Value
+ validation_test 'equal_to: :a',
+ [ :a ],
+ [ :b ],
+ [ nil ]
+
+ validation_test 'equal_to: [ :a, :b ]',
+ [ :a, :b ],
+ [ [ :a, :b ] ],
+ [ nil ]
+
+ validation_test 'equal_to: [ [ :a, :b ] ]',
+ [ [ :a, :b ] ],
+ [ :a, :b ],
+ [ nil ]
+
+ validation_test 'equal_to: nil',
+ [ ],
+ [ 'a' ],
+ [ nil ]
+
+ validation_test 'equal_to: [ "a", nil ]',
+ [ 'a' ],
+ [ 'b' ],
+ [ nil ]
+
+ validation_test 'equal_to: [ nil, "a" ]',
+ [ 'a' ],
+ [ 'b' ],
+ [ nil ]
+
+ validation_test 'equal_to: []',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # kind_of
+ context "kind_of" do
+ validation_test 'kind_of: String',
+ [ 'a' ],
+ [ :b ],
+ [ nil ]
+
+ validation_test 'kind_of: [ String, Symbol ]',
+ [ 'a', :b ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'kind_of: [ Symbol, String ]',
+ [ 'a', :b ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'kind_of: NilClass',
+ [ ],
+ [ 'a' ],
+ [ nil ]
+
+ validation_test 'kind_of: [ NilClass, String ]',
+ [ 'a' ],
+ [ :a ],
+ [ nil ]
+
+ validation_test 'kind_of: []',
+ [],
+ [ :a ],
+ [ nil ]
+
+ validation_test 'kind_of: nil',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # regex
+ context "regex" do
+ validation_test 'regex: /abc/',
+ [ 'xabcy' ],
+ [ 'gbh', 123 ],
+ [ nil ]
+
+ validation_test 'regex: [ /abc/, /z/ ]',
+ [ 'xabcy', 'aza' ],
+ [ 'gbh', 123 ],
+ [ nil ]
+
+ validation_test 'regex: [ /z/, /abc/ ]',
+ [ 'xabcy', 'aza' ],
+ [ 'gbh', 123 ],
+ [ nil ]
+
+ validation_test 'regex: [ [ /z/, /abc/ ], [ /n/ ] ]',
+ [ 'xabcy', 'aza', 'ana' ],
+ [ 'gbh', 123 ],
+ [ nil ]
+
+ validation_test 'regex: []',
+ [],
+ [ :a ],
+ [ nil ]
+
+ validation_test 'regex: nil',
+ [],
+ [ :a ],
+ [ nil ]
+ end
+
+ # callbacks
+ context "callbacks" do
+ validation_test 'callbacks: { "a" => proc { |x| x > 10 }, "b" => proc { |x| x%2 == 0 } }',
+ [ 12 ],
+ [ 11, 4 ]
+
+ validation_test 'callbacks: { "a" => proc { |x| x%2 == 0 }, "b" => proc { |x| x > 10 } }',
+ [ 12 ],
+ [ 11, 4 ]
+
+ validation_test 'callbacks: { "a" => proc { |x| x.nil? } }',
+ [ ],
+ [ 'a' ],
+ [ nil ]
+
+ validation_test 'callbacks: {}',
+ [ :a ],
+ [],
+ [ nil ]
+ end
+
+ # respond_to
+ context "respond_to" do
+ validation_test 'respond_to: :split',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: "split"',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: :to_s',
+ [ :a ],
+ [],
+ [ nil ]
+
+ validation_test 'respond_to: [ :split, :to_s ]',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: %w(split to_s)',
+ [ 'hi' ],
+ [ 1 ],
+ [ nil ]
+
+ validation_test 'respond_to: [ :to_s, :split ]',
+ [ 'hi' ],
+ [ 1, ],
+ [ nil ]
+
+ validation_test 'respond_to: []',
+ [ :a ],
+ [],
+ [ nil ]
+
+ validation_test 'respond_to: nil',
+ [ :a ],
+ [],
+ [ nil ]
+ end
+
+ context "cannot_be" do
+ validation_test 'cannot_be: :empty',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: "empty"',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ :empty, :nil ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ "empty", "nil" ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ :nil, :empty ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: [ :empty, :nil, :blahblah ]',
+ [ 1, [1,2], { a: 10 } ],
+ [ [] ],
+ [ nil ]
+
+ validation_test 'cannot_be: []',
+ [ :a ],
+ [],
+ [ nil ]
+
+ validation_test 'cannot_be: nil',
+ [ :a ],
+ [],
+ [ nil ]
+
+ end
+
+ context "required" do
+ with_property ':x, required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ it "value nil emits a deprecation warning and does a get" do
+ expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 1
+ expect(resource.x nil).to eq 1
+ expect(resource.x).to eq 1
+ end
+ end
+
+ with_property ':x, [String, nil], required: true' do
+ it "if x is not specified, retrieval fails" do
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ it "value nil is valid" do
+ expect(resource.x nil).to be_nil
+ expect(resource.x).to be_nil
+ end
+ it "value '1' is valid" do
+ expect(resource.x '1').to eq '1'
+ expect(resource.x).to eq '1'
+ end
+ it "value 1 is invalid" do
+ expect { resource.x 1 }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ with_property ':x, name_property: true, required: true' do
+ it "if x is not specified, the name property is returned" do
+ expect(resource.x).to eq 'blah'
+ end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ it "value nil emits a deprecation warning and does a get" do
+ expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 1
+ expect(resource.x nil).to eq 1
+ expect(resource.x).to eq 1
+ end
+ end
+
+ with_property ':x, default: 10, required: true' do
+ it "if x is not specified, the default is returned" do
+ expect(resource.x).to eq 10
+ end
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+ it "value nil is invalid" do
+ expect { resource.x nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 1
+ expect(resource.x nil).to eq 1
+ expect(resource.x).to eq 1
+ end
+ end
+ end
+
+ context "custom validators (def _pv_blarghle)" do
+ before do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ with_property ':x, blarghle: 1' do
+ context "and a class that implements _pv_blarghle" do
+ before do
+ resource_class.class_eval do
+ def _pv_blarghle(opts, key, value)
+ if _pv_opts_lookup(opts, key) != value
+ raise Chef::Exceptions::ValidationFailed, "ouch"
+ end
+ end
+ end
+ end
+
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+
+ it "value '1' is invalid" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ it "value nil does a get" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ resource.x 1
+ resource.x nil
+ expect(resource.x).to eq 1
+ end
+ end
+ end
+
+ with_property ':x, blarghle: 1' do
+ context "and a class that implements _pv_blarghle" do
+ before do
+ resource_class.class_eval do
+ def _pv_blarghle(opts, key, value)
+ if _pv_opts_lookup(opts, key) != value
+ raise Chef::Exceptions::ValidationFailed, "ouch"
+ end
+ end
+ end
+ end
+
+ it "value 1 is valid" do
+ expect(resource.x 1).to eq 1
+ expect(resource.x).to eq 1
+ end
+
+ it "value '1' is invalid" do
+ expect { resource.x '1' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ it "value nil does a get" do
+ resource.x 1
+ resource.x nil
+ expect(resource.x).to eq 1
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/property_spec.rb b/spec/unit/property_spec.rb
new file mode 100644
index 0000000000..50764aa7a2
--- /dev/null
+++ b/spec/unit/property_spec.rb
@@ -0,0 +1,972 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Chef::Resource.property" do
+ include IntegrationSupport
+
+ module Namer
+ @i = 0
+ def self.next_resource_name
+ "chef_resource_property_spec_#{@i += 1}"
+ end
+ def self.reset_index
+ @current_index = 0
+ end
+ def self.current_index
+ @current_index
+ end
+ def self.next_index
+ @current_index += 1
+ end
+ end
+
+ def lazy(&block)
+ Chef::DelayedEvaluator.new(&block)
+ end
+
+ before do
+ Namer.reset_index
+ end
+
+ def self.new_resource_name
+ Namer.next_resource_name
+ end
+
+ let(:resource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(Chef::Resource) do
+ resource_name new_resource_name
+ def next_index
+ Namer.next_index
+ end
+ end
+ end
+
+ let(:resource) do
+ resource_class.new("blah")
+ end
+
+ def self.english_join(values)
+ return '<nothing>' if values.size == 0
+ return values[0].inspect if values.size == 1
+ "#{values[0..-2].map { |v| v.inspect }.join(", ")} and #{values[-1].inspect}"
+ end
+
+ def self.with_property(*properties, &block)
+ tags_index = properties.find_index { |p| !p.is_a?(String)}
+ if tags_index
+ properties, tags = properties[0..tags_index-1], properties[tags_index..-1]
+ else
+ tags = []
+ end
+ if properties.size == 1
+ description = "With property #{properties.first}"
+ else
+ description = "With properties #{english_join(properties.map { |property| "#{property.inspect}" })}"
+ end
+ context description, *tags do
+ before do
+ properties.each do |property_str|
+ resource_class.class_eval("property #{property_str}", __FILE__, __LINE__)
+ end
+ end
+ instance_eval(&block)
+ end
+ end
+
+ # Basic properties
+ with_property ':bare_property' do
+ it "can be set" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property).to eq 10
+ end
+ it "emits a deprecation warning and does a get, if set to nil" do
+ expect(resource.bare_property 10).to eq 10
+ expect { resource.bare_property nil }.to raise_error Chef::Exceptions::DeprecatedFeatureError
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ expect(resource.bare_property nil).to eq 10
+ expect(resource.bare_property).to eq 10
+ end
+ it "can be updated" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property 20).to eq 20
+ expect(resource.bare_property).to eq 20
+ end
+ it "can be set with =" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property).to eq 10
+ end
+ it "can be set to nil with =" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property = nil).to be_nil
+ expect(resource.bare_property).to be_nil
+ end
+ it "can be updated with =" do
+ expect(resource.bare_property 10).to eq 10
+ expect(resource.bare_property = 20).to eq 20
+ expect(resource.bare_property).to eq 20
+ end
+ end
+
+ with_property ":x, Integer" do
+ context "and subclass" do
+ let(:subresource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(resource_class) do
+ resource_name new_resource_name
+ end
+ end
+ let(:subresource) do
+ subresource_class.new('blah')
+ end
+
+ it "x is inherited" do
+ expect(subresource.x 10).to eq 10
+ expect(subresource.x).to eq 10
+ expect(subresource.x = 20).to eq 20
+ expect(subresource.x).to eq 20
+ expect(subresource_class.properties[:x]).not_to be_nil
+ end
+
+ it "x's validation is inherited" do
+ expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+
+ context "with property :y on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :y
+ end
+ end
+
+ it "x is still there" do
+ expect(subresource.x 10).to eq 10
+ expect(subresource.x).to eq 10
+ expect(subresource.x = 20).to eq 20
+ expect(subresource.x).to eq 20
+ expect(subresource_class.properties[:x]).not_to be_nil
+ end
+ it "y is there" do
+ expect(subresource.y 10).to eq 10
+ expect(subresource.y).to eq 10
+ expect(subresource.y = 20).to eq 20
+ expect(subresource.y).to eq 20
+ expect(subresource_class.properties[:y]).not_to be_nil
+ end
+ it "y is not on the superclass" do
+ expect { resource_class.y 10 }.to raise_error
+ expect(resource_class.properties[:y]).to be_nil
+ end
+ end
+
+ context "with property :x on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :x
+ end
+ end
+
+ it "x is still there" do
+ expect(subresource.x 10).to eq 10
+ expect(subresource.x).to eq 10
+ expect(subresource.x = 20).to eq 20
+ expect(subresource.x).to eq 20
+ expect(subresource_class.properties[:x]).not_to be_nil
+ expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+ end
+
+ it "x's validation is inherited" do
+ expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ context "with property :x, default: 80 on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :x, default: 80
+ end
+ end
+
+ it "x is still there" do
+ expect(subresource.x 10).to eq 10
+ expect(subresource.x).to eq 10
+ expect(subresource.x = 20).to eq 20
+ expect(subresource.x).to eq 20
+ expect(subresource_class.properties[:x]).not_to be_nil
+ expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+ end
+
+ it "x defaults to 80" do
+ expect(subresource.x).to eq 80
+ end
+
+ it "x's validation is inherited" do
+ expect { subresource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+ end
+ end
+
+ context "with property :x, String on the subclass" do
+ before do
+ subresource_class.class_eval do
+ property :x, String
+ end
+ end
+
+ it "x is still there" do
+ expect(subresource.x "10").to eq "10"
+ expect(subresource.x).to eq "10"
+ expect(subresource.x = "20").to eq "20"
+ expect(subresource.x).to eq "20"
+ expect(subresource_class.properties[:x]).not_to be_nil
+ expect(subresource_class.properties[:x]).not_to eq resource_class.properties[:x]
+ end
+
+ it "x's validation is overwritten" do
+ expect { subresource.x 10 }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(subresource.x 'ohno').to eq 'ohno'
+ expect(subresource.x).to eq 'ohno'
+ end
+
+ it "the superclass's validation for x is still there" do
+ expect { resource.x 'ohno' }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(resource.x 10).to eq 10
+ expect(resource.x).to eq 10
+ end
+ end
+ end
+ end
+
+ context "Chef::Resource::Property#reset_property" do
+ it "when a resource is newly created, reset_property(:name) sets property to nil" do
+ expect(resource.property_is_set?(:name)).to be_truthy
+ resource.reset_property(:name)
+ expect(resource.property_is_set?(:name)).to be_falsey
+ expect(resource.name).to be_nil
+ end
+
+ it "when referencing an undefined property, reset_property(:x) raises an error" do
+ expect { resource.reset_property(:x) }.to raise_error(ArgumentError)
+ end
+
+ with_property ':x' do
+ it "when the resource is newly created, reset_property(:x) does nothing" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to be_nil
+ end
+ it "when x is set, reset_property resets it" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to be_nil
+ end
+ end
+
+ with_property ':x, Integer' do
+ it "when the resource is newly created, reset_property(:x) does nothing" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to be_nil
+ end
+ it "when x is set, reset_property resets it even though `nil` is technically invalid" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to be_nil
+ end
+ end
+
+ with_property ':x, default: 10' do
+ it "when the resource is newly created, reset_property(:x) does nothing" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to eq 10
+ end
+ it "when x is set, reset_property resets it and it returns the default" do
+ resource.x 20
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to eq 10
+ end
+ end
+
+ with_property ':x, default: lazy { 10 }' do
+ it "when the resource is newly created, reset_property(:x) does nothing" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to eq 10
+ end
+ it "when x is set, reset_property resets it and it returns the default" do
+ resource.x 20
+ resource.reset_property(:x)
+ expect(resource.property_is_set?(:x)).to be_falsey
+ expect(resource.x).to eq 10
+ end
+ end
+ end
+
+ context "Chef::Resource::Property#property_is_set?" do
+ it "when a resource is newly created, property_is_set?(:name) is true" do
+ expect(resource.property_is_set?(:name)).to be_truthy
+ end
+
+ it "when referencing an undefined property, property_is_set?(:x) raises an error" do
+ expect { resource.property_is_set?(:x) }.to raise_error(ArgumentError)
+ end
+
+ with_property ':x' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set to a lazy value, property_is_set?(:x) is true" do
+ resource.x lazy { 10 }
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is false" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ end
+
+ with_property ':x, default: 10' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set to a lazy value, property_is_set?(:x) is true" do
+ resource.x lazy { 10 }
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is false" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ end
+
+ with_property ':x, default: nil' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set to a lazy value, property_is_set?(:x) is true" do
+ resource.x lazy { 10 }
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is false" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ end
+
+ with_property ':x, default: lazy { 10 }' do
+ it "when the resource is newly created, property_is_set?(:x) is false" do
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ it "when x is set, property_is_set?(:x) is true" do
+ resource.x 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is set with =, property_is_set?(:x) is true" do
+ resource.x = 10
+ expect(resource.property_is_set?(:x)).to be_truthy
+ end
+ it "when x is retrieved, property_is_set?(:x) is false" do
+ resource.x
+ expect(resource.property_is_set?(:x)).to be_falsey
+ end
+ end
+ end
+
+ context "Chef::Resource::Property#default" do
+ with_property ':x, default: 10' do
+ it "when x is set, it returns its value" do
+ expect(resource.x 20).to eq 20
+ expect(resource.property_is_set?(:x)).to be_truthy
+ expect(resource.x).to eq 20
+ end
+ it "when x is not set, it returns 10" do
+ expect(resource.x).to eq 10
+ end
+ it "when x is not set, it is not included in state" do
+ expect(resource.state_for_resource_reporter).to eq({})
+ end
+ it "when x is set to nil, it returns nil" do
+ resource.instance_eval { @x = nil }
+ expect(resource.x).to be_nil
+ end
+
+ context "With a subclass" do
+ let(:subresource_class) do
+ new_resource_name = self.class.new_resource_name
+ Class.new(resource_class) do
+ resource_name new_resource_name
+ end
+ end
+ let(:subresource) { subresource_class.new('blah') }
+ it "The default is inherited" do
+ expect(subresource.x).to eq 10
+ end
+ end
+ end
+
+ with_property ':x, default: 10, identity: true' do
+ it "when x is not set, it is included in identity" do
+ expect(resource.identity).to eq(10)
+ end
+ end
+
+ with_property ':x, default: 1, identity: true', ':y, default: 2, identity: true' do
+ it "when x is not set, it is still included in identity" do
+ resource.y 20
+ expect(resource.identity).to eq(x: 1, y: 20)
+ end
+ end
+
+ with_property ':x, default: nil' do
+ it "when x is not set, it returns nil" do
+ expect(resource.x).to be_nil
+ end
+ end
+
+ with_property ':x' do
+ it "when x is not set, it returns nil" do
+ expect(resource.x).to be_nil
+ end
+ end
+
+ context "hash default" do
+ context "(deprecations allowed)" do
+ before { Chef::Config[:treat_deprecation_warnings_as_errors] = false }
+
+ with_property ':x, default: {}' do
+ it "when x is not set, it returns {}" do
+ expect(resource.x).to eq({})
+ end
+ it "The same exact value is returned multiple times in a row" do
+ value = resource.x
+ expect(value).to eq({})
+ expect(resource.x.object_id).to eq(value.object_id)
+ end
+ it "Multiple instances of x receive the exact same value" do
+ expect(resource.x.object_id).to eq(resource_class.new('blah2').x.object_id)
+ end
+ end
+ end
+
+ it "when a property is declared with default: {}, a warning is issued" do
+ expect(Chef::Log).to receive(:deprecation).with( /^Property .+\.x has an array or hash default \(\{\}\)\. This means that if one resource modifies or appends to it, all other resources of the same type will also see the changes\. Either freeze the constant with \`\.freeze\` to prevent appending, or use lazy \{ \{\} \}\.$/, /property_spec\.rb/ )
+ resource_class.class_eval("property :x, default: {}", __FILE__, __LINE__)
+ expect(resource.x).to eq({})
+ end
+
+ with_property ':x, default: lazy { {} }' do
+ it "when x is not set, it returns {}" do
+ expect(resource.x).to eq({})
+ end
+ # it "The value is different each time it is called" do
+ # value = resource.x
+ # expect(value).to eq({})
+ # expect(resource.x.object_id).not_to eq(value.object_id)
+ # end
+ it "Multiple instances of x receive different values" do
+ expect(resource.x.object_id).not_to eq(resource_class.new('blah2').x.object_id)
+ end
+ end
+ end
+
+ context "with a class with 'blah' as both class and instance methods" do
+ before do
+ resource_class.class_eval do
+ def self.blah
+ 'class'
+ end
+ def blah
+ "#{name}#{next_index}"
+ end
+ end
+ end
+
+ with_property ':x, default: lazy { blah }' do
+ it "x is run in context of the instance" do
+ expect(resource.x).to eq "blah1"
+ end
+ it "x is run in the context of each instance it is run in" do
+ expect(resource.x).to eq "blah1"
+ expect(resource_class.new('another').x).to eq "another2"
+ # expect(resource.x).to eq "blah3"
+ end
+ end
+
+ with_property ':x, default: lazy { |x| "#{blah}#{x.blah}" }' do
+ it "x is run in context of the class (where it was defined) and passed the instance" do
+ expect(resource.x).to eq "classblah1"
+ end
+ it "x is passed the value of each instance it is run in" do
+ expect(resource.x).to eq "classblah1"
+ expect(resource_class.new('another').x).to eq "classanother2"
+ # expect(resource.x).to eq "classblah3"
+ end
+ end
+ end
+
+ context "validation of defaults" do
+ with_property ':x, String, default: 10' do
+ it "when the resource is created, no error is raised" do
+ resource
+ end
+ it "when x is set, no error is raised" do
+ expect(resource.x 'hi').to eq 'hi'
+ expect(resource.x).to eq 'hi'
+ end
+ it "when x is retrieved, no validation error is raised" do
+ expect(resource.x).to eq 10
+ end
+ # it "when x is retrieved, a validation error is raised" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+
+ with_property ":x, String, default: lazy { Namer.next_index }" do
+ it "when the resource is created, no error is raised" do
+ resource
+ end
+ it "when x is set, no error is raised" do
+ expect(resource.x 'hi').to eq 'hi'
+ expect(resource.x).to eq 'hi'
+ end
+ it "when x is retrieved, no validation error is raised" do
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 1
+ end
+ # it "when x is retrieved, a validation error is raised" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # expect(Namer.current_index).to eq 1
+ # end
+ end
+
+ with_property ":x, default: lazy { Namer.next_index.to_s }, is: proc { |v| Namer.next_index; true }" do
+ it "validation is not run at all on the default value" do
+ expect(resource.x).to eq '1'
+ expect(Namer.current_index).to eq 1
+ end
+ # it "validation is run each time" do
+ # expect(resource.x).to eq '1'
+ # expect(Namer.current_index).to eq 2
+ # expect(resource.x).to eq '1'
+ # expect(Namer.current_index).to eq 2
+ # end
+ end
+
+ with_property ":x, default: lazy { Namer.next_index.to_s.freeze }, is: proc { |v| Namer.next_index; true }" do
+ it "validation is not run at all on the default value" do
+ expect(resource.x).to eq '1'
+ expect(Namer.current_index).to eq 1
+ end
+ # it "validation is only run the first time" do
+ # expect(resource.x).to eq '1'
+ # expect(Namer.current_index).to eq 2
+ # expect(resource.x).to eq '1'
+ # expect(Namer.current_index).to eq 2
+ # end
+ end
+ end
+
+ context "coercion of defaults" do
+ # Frozen default, non-frozen coerce
+ with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+ it "when the resource is created, the proc is not yet run" do
+ resource
+ expect(Namer.current_index).to eq 0
+ end
+ it "when x is set, coercion is run" do
+ expect(resource.x 'hi').to eq 'hi1'
+ expect(resource.x).to eq 'hi1'
+ expect(Namer.current_index).to eq 1
+ end
+ it "when x is retrieved, coercion is run exactly once" do
+ expect(resource.x).to eq '101'
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ # Frozen default, frozen coerce
+ with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: 10' do
+ it "when the resource is created, the proc is not yet run" do
+ resource
+ expect(Namer.current_index).to eq 0
+ end
+ it "when x is set, coercion is run" do
+ expect(resource.x 'hi').to eq 'hi1'
+ expect(resource.x).to eq 'hi1'
+ expect(Namer.current_index).to eq 1
+ end
+ it "when x is retrieved, coercion is run each time" do
+ expect(resource.x).to eq '101'
+ expect(resource.x).to eq '102'
+ expect(Namer.current_index).to eq 2
+ end
+ end
+
+ # Frozen lazy default, non-frozen coerce
+ with_property ':x, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "when the resource is created, the proc is not yet run" do
+ resource
+ expect(Namer.current_index).to eq 0
+ end
+ it "when x is set, coercion is run" do
+ expect(resource.x 'hi').to eq 'hi1'
+ expect(resource.x).to eq 'hi1'
+ expect(Namer.current_index).to eq 1
+ end
+ it "when x is retrieved, coercion is run exactly once" do
+ expect(resource.x).to eq '101'
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ # Non-frozen lazy default, frozen coerce
+ with_property ':x, coerce: proc { |v| "#{v}#{next_index}".freeze }, default: lazy { "10" }' do
+ it "when the resource is created, the proc is not yet run" do
+ resource
+ expect(Namer.current_index).to eq 0
+ end
+ it "when x is set, coercion is run" do
+ expect(resource.x 'hi').to eq 'hi1'
+ expect(resource.x).to eq 'hi1'
+ expect(Namer.current_index).to eq 1
+ end
+ it "when x is retrieved, coercion is run each time" do
+ expect(resource.x).to eq '101'
+ expect(resource.x).to eq '102'
+ expect(Namer.current_index).to eq 2
+ end
+ end
+
+ with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "coercion is only run the first time x is retrieved, and validation is not run" do
+ expect(Namer.current_index).to eq 0
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ context "validation and coercion of defaults" do
+ with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+ it "when x is retrieved, it is coerced before validating and passes" do
+ expect(resource.x).to eq '101'
+ end
+ end
+ with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: 10' do
+ it "when x is retrieved, it is coerced and not validated" do
+ expect(resource.x).to eq '101'
+ end
+ # it "when x is retrieved, it is coerced before validating and fails" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+ with_property ':x, String, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "when x is retrieved, it is coerced before validating and passes" do
+ expect(resource.x).to eq '101'
+ end
+ end
+ with_property ':x, Integer, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "when x is retrieved, it is coerced and not validated" do
+ expect(resource.x).to eq '101'
+ end
+ # it "when x is retrieved, it is coerced before validating and fails" do
+ # expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ # end
+ end
+ with_property ':x, proc { |v| Namer.next_index; true }, coerce: proc { |v| "#{v}#{next_index}" }, default: lazy { 10 }' do
+ it "coercion is only run the first time x is retrieved, and validation is not run" do
+ expect(Namer.current_index).to eq 0
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ expect(resource.x).to eq '101'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+ end
+ end
+ end
+
+ context "Chef::Resource#lazy" do
+ with_property ':x' do
+ it "setting x to a lazy value does not run it immediately" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "you can set x to a lazy value in the instance" do
+ resource.instance_eval do
+ x lazy { Namer.next_index }
+ end
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 1
+ end
+ it "retrieving a lazy value pops it open" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 1
+ end
+ it "retrieving a lazy value twice evaluates it twice" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq 1
+ expect(resource.x).to eq 2
+ expect(Namer.current_index).to eq 2
+ end
+ it "setting the same lazy value on two different instances runs it on each instancee" do
+ resource2 = resource_class.new("blah2")
+ l = lazy { Namer.next_index }
+ resource.x l
+ resource2.x l
+ expect(resource2.x).to eq 1
+ expect(resource.x).to eq 2
+ expect(resource2.x).to eq 3
+ end
+
+ context "when the class has a class and instance method named blah" do
+ before do
+ resource_class.class_eval do
+ def self.blah
+ "class"
+ end
+ def blah
+ "#{name}#{Namer.next_index}"
+ end
+ end
+ end
+ def blah
+ "example"
+ end
+ # it "retrieving lazy { blah } gets the instance variable" do
+ # resource.x lazy { blah }
+ # expect(resource.x).to eq "blah1"
+ # end
+ # it "retrieving lazy { blah } from two different instances gets two different instance variables" do
+ # resource2 = resource_class.new("another")
+ # l = lazy { blah }
+ # resource2.x l
+ # resource.x l
+ # expect(resource2.x).to eq "another1"
+ # expect(resource.x).to eq "blah2"
+ # expect(resource2.x).to eq "another3"
+ # end
+ it 'retrieving lazy { |x| "#{blah}#{x.blah}" } gets the example and instance variables' do
+ resource.x lazy { |x| "#{blah}#{x.blah}" }
+ expect(resource.x).to eq "exampleblah1"
+ end
+ it 'retrieving lazy { |x| "#{blah}#{x.blah}" } from two different instances gets two different instance variables' do
+ resource2 = resource_class.new("another")
+ l = lazy { |x| "#{blah}#{x.blah}" }
+ resource2.x l
+ resource.x l
+ expect(resource2.x).to eq "exampleanother1"
+ expect(resource.x).to eq "exampleblah2"
+ expect(resource2.x).to eq "exampleanother3"
+ end
+ end
+ end
+
+ with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "lazy values are not coerced on set" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "lazy values are coerced on get" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 2
+ end
+ it "lazy values are coerced on each access" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq "34"
+ expect(Namer.current_index).to eq 4
+ end
+ end
+
+ with_property ':x, String' do
+ it "lazy values are not validated on set" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "lazy values are validated on get" do
+ resource.x lazy { Namer.next_index }
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(Namer.current_index).to eq 1
+ end
+ end
+
+ with_property ':x, is: proc { |v| Namer.next_index; true }' do
+ it "lazy values are validated on each access" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq 1
+ expect(Namer.current_index).to eq 2
+ expect(resource.x).to eq 3
+ expect(Namer.current_index).to eq 4
+ end
+ end
+
+ with_property ':x, Integer, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "lazy values are not validated or coerced on set" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ end
+ it "lazy values are coerced before being validated, which fails" do
+ resource.x lazy { Namer.next_index }
+ expect(Namer.current_index).to eq 0
+ expect { resource.x }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(Namer.current_index).to eq 2
+ end
+ end
+
+ with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }, is: proc { |v| Namer.next_index; true }' do
+ it "lazy values are coerced and validated exactly once" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 3
+ expect(resource.x).to eq "45"
+ expect(Namer.current_index).to eq 6
+ end
+ end
+
+ with_property ':x, String, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "lazy values are coerced before being validated, which succeeds" do
+ resource.x lazy { Namer.next_index }
+ expect(resource.x).to eq "12"
+ expect(Namer.current_index).to eq 2
+ end
+ end
+ end
+
+ context "Chef::Resource::Property#coerce" do
+ with_property ':x, coerce: proc { |v| "#{v}#{Namer.next_index}" }' do
+ it "coercion runs on set" do
+ expect(resource.x 10).to eq "101"
+ expect(Namer.current_index).to eq 1
+ end
+ it "coercion sets the value (and coercion does not run on get)" do
+ expect(resource.x 10).to eq "101"
+ expect(resource.x).to eq "101"
+ expect(Namer.current_index).to eq 1
+ end
+ it "coercion runs each time set happens" do
+ expect(resource.x 10).to eq "101"
+ expect(Namer.current_index).to eq 1
+ expect(resource.x 10).to eq "102"
+ expect(Namer.current_index).to eq 2
+ end
+ end
+ with_property ':x, coerce: proc { |x| Namer.next_index; raise "hi" if x == 10; x }, is: proc { |x| Namer.next_index; x != 10 }' do
+ it "failed coercion fails to set the value" do
+ resource.x 20
+ expect(resource.x).to eq 20
+ expect(Namer.current_index).to eq 2
+ expect { resource.x 10 }.to raise_error 'hi'
+ expect(resource.x).to eq 20
+ expect(Namer.current_index).to eq 3
+ end
+ it "validation does not run if coercion fails" do
+ expect { resource.x 10 }.to raise_error 'hi'
+ expect(Namer.current_index).to eq 1
+ end
+ end
+ end
+
+ context "Chef::Resource::Property validation" do
+ with_property ':x, is: proc { |v| Namer.next_index; v.is_a?(Integer) }' do
+ it "validation runs on set" do
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 1
+ end
+ it "validation sets the value (and validation does not run on get)" do
+ expect(resource.x 10).to eq 10
+ expect(resource.x).to eq 10
+ expect(Namer.current_index).to eq 1
+ end
+ it "validation runs each time set happens" do
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 1
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 2
+ end
+ it "failed validation fails to set the value" do
+ expect(resource.x 10).to eq 10
+ expect(Namer.current_index).to eq 1
+ expect { resource.x 'blah' }.to raise_error Chef::Exceptions::ValidationFailed
+ expect(resource.x).to eq 10
+ expect(Namer.current_index).to eq 2
+ end
+ end
+ end
+
+ [ 'name_attribute', 'name_property' ].each do |name|
+ context "Chef::Resource::Property##{name}" do
+ with_property ":x, #{name}: true" do
+ it "defaults x to resource.name" do
+ expect(resource.x).to eq 'blah'
+ end
+ it "does not pick up resource.name if set" do
+ expect(resource.x 10).to eq 10
+ expect(resource.x).to eq 10
+ end
+ it "binds to the latest resource.name when run" do
+ resource.name 'foo'
+ expect(resource.x).to eq 'foo'
+ end
+ it "caches resource.name" do
+ expect(resource.x).to eq 'blah'
+ resource.name 'foo'
+ expect(resource.x).to eq 'blah'
+ end
+ end
+ with_property ":x, default: 10, #{name}: true" do
+ it "chooses default over #{name}" do
+ expect(resource.x).to eq 10
+ end
+ end
+ with_property ":x, #{name}: true, default: 10" do
+ it "chooses default over #{name}" do
+ expect(resource.x).to eq 10
+ end
+ end
+ with_property ":x, #{name}: true, required: true" do
+ it "defaults x to resource.name" do
+ expect(resource.x).to eq 'blah'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb
index 63658ac601..e6a7125e32 100644
--- a/spec/unit/provider/deploy_spec.rb
+++ b/spec/unit/provider/deploy_spec.rb
@@ -362,7 +362,7 @@ describe Chef::Provider::Deploy do
it "skips the migration when resource.migrate => false but runs symlinks before migration" do
@resource.migrate false
- expect(@provider).not_to receive :run_command
+ expect(@provider).not_to receive :shell_out!
expect(@provider).to receive :run_symlinks_before_migrate
@provider.migrate
end
@@ -378,7 +378,7 @@ describe Chef::Provider::Deploy do
allow(STDOUT).to receive(:tty?).and_return(true)
allow(Chef::Log).to receive(:info?).and_return(true)
- expect(@provider).to receive(:run_command).with(:command => "migration_foo", :cwd => @expected_release_dir,
+ expect(@provider).to receive(:shell_out!).with("migration_foo",:cwd => @expected_release_dir,
:user => "deployNinja", :group => "deployNinjas",
:log_level => :info, :live_stream => STDOUT,
:log_tag => "deploy[/my/deploy/dir]",
@@ -445,13 +445,13 @@ describe Chef::Provider::Deploy do
end
it "does nothing for restart if restart_command is empty" do
- expect(@provider).not_to receive(:run_command)
+ expect(@provider).not_to receive(:shell_out!)
@provider.restart
end
it "runs the restart command in the current application dir when the resource has a restart_command" do
@resource.restart_command "restartcmd"
- expect(@provider).to receive(:run_command).with(:command => "restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug)
+ expect(@provider).to receive(:shell_out!).with("restartcmd", :cwd => "/my/deploy/dir/current", :log_tag => "deploy[/my/deploy/dir]", :log_level => :debug)
@provider.restart
end
@@ -509,7 +509,7 @@ describe Chef::Provider::Deploy do
it "shouldn't give a no method error on migrate if the environment is nil" do
allow(@provider).to receive(:enforce_ownership)
allow(@provider).to receive(:run_symlinks_before_migrate)
- allow(@provider).to receive(:run_command)
+ allow(@provider).to receive(:shell_out!)
@provider.migrate
end
@@ -622,7 +622,7 @@ describe Chef::Provider::Deploy do
gems = @provider.send(:gem_packages)
- expect(gems.map { |g| g.action }).to eq([:install])
+ expect(gems.map { |g| g.action }).to eq([%i{install}])
expect(gems.map { |g| g.name }).to eq(%w{eventmachine})
expect(gems.map { |g| g.version }).to eq(%w{0.12.9})
end
diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb
index 38d6db8320..4fad8c8906 100644
--- a/spec/unit/provider/directory_spec.rb
+++ b/spec/unit/provider/directory_spec.rb
@@ -197,6 +197,41 @@ describe Chef::Provider::Directory do
expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
end
end
+
+ describe "on OS X" do
+ before do
+ allow(node).to receive(:[]).with("platform").and_return('mac_os_x')
+ new_resource.path "/usr/bin/chef_test"
+ new_resource.recursive false
+ allow_any_instance_of(Chef::Provider::File).to receive(:do_selinux)
+ end
+
+ it "os x 10.10 can write to sip locations" do
+ allow(node).to receive(:[]).with("platform_version").and_return('10.10')
+ allow(Dir).to receive(:mkdir).and_return([true], [])
+ allow(::File).to receive(:directory?).and_return(true)
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(true)
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
+ end
+
+ it "os x 10.11 cannot write to sip locations" do
+ allow(node).to receive(:[]).with("platform_version").and_return('10.11')
+ allow(::File).to receive(:directory?).and_return(true)
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+ expect {directory.run_action(:create) }.to raise_error(Chef::Exceptions::InsufficientPermissions)
+ end
+
+ it "os x 10.11 can write to sip exlcusions" do
+ new_resource.path "/usr/local/chef_test"
+ allow(node).to receive(:[]).with("platform_version").and_return('10.11')
+ allow(::File).to receive(:directory?).and_return(true)
+ allow(Dir).to receive(:mkdir).and_return([true], [])
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
+ end
+ end
end
describe "#run_action(:create)" do
diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb
index 0a6c22bdcf..65c1c019f0 100644
--- a/spec/unit/provider/dsc_resource_spec.rb
+++ b/spec/unit/provider/dsc_resource_spec.rb
@@ -35,10 +35,10 @@ describe Chef::Provider::DscResource do
node
}
- it 'raises a NoProviderAvailable exception' do
+ it 'raises a ProviderNotFound exception' do
expect(provider).not_to receive(:meta_configuration)
expect{provider.run_action(:run)}.to raise_error(
- Chef::Exceptions::NoProviderAvailable, /5\.0\.10018\.0/)
+ Chef::Exceptions::ProviderNotFound, /5\.0\.10018\.0/)
end
end
@@ -56,7 +56,7 @@ describe Chef::Provider::DscResource do
expect(provider).to receive(:meta_configuration).and_return(
meta_configuration)
expect { provider.run_action(:run) }.to raise_error(
- Chef::Exceptions::NoProviderAvailable, /Disabled/)
+ Chef::Exceptions::ProviderNotFound, /Disabled/)
end
end
diff --git a/spec/unit/provider/dsc_script_spec.rb b/spec/unit/provider/dsc_script_spec.rb
index d4b2eb3b22..76589e71c1 100644
--- a/spec/unit/provider/dsc_script_spec.rb
+++ b/spec/unit/provider/dsc_script_spec.rb
@@ -158,14 +158,14 @@ describe Chef::Provider::DscScript do
expect {
provider.run_action(:run)
- }.to raise_error(Chef::Exceptions::NoProviderAvailable)
+ }.to raise_error(Chef::Exceptions::ProviderNotFound)
end
end
it 'raises an exception if Powershell is not present' do
expect {
provider.run_action(:run)
- }.to raise_error(Chef::Exceptions::NoProviderAvailable)
+ }.to raise_error(Chef::Exceptions::ProviderNotFound)
end
end
diff --git a/spec/unit/provider/ifconfig_spec.rb b/spec/unit/provider/ifconfig_spec.rb
index d290ab7066..4940f19a45 100644
--- a/spec/unit/provider/ifconfig_spec.rb
+++ b/spec/unit/provider/ifconfig_spec.rb
@@ -46,7 +46,7 @@ describe Chef::Provider::Ifconfig do
allow(@provider).to receive(:shell_out).and_return(@status)
@provider.load_current_resource
end
- it "should track state of ifconfig failure." do
+ it "should track state of ifconfig failure" do
expect(@provider.instance_variable_get("@status").exitstatus).not_to eq(0)
end
it "should thrown an exception when ifconfig fails" do
@@ -68,6 +68,16 @@ describe Chef::Provider::Ifconfig do
expect(@new_resource).to be_updated
end
+ it "should set the address to target if specified" do
+ allow(@provider).to receive(:load_current_resource)
+ @new_resource.target "172.16.32.2"
+ command = "ifconfig eth0 172.16.32.2 netmask 255.255.254.0 metric 1 mtu 1500"
+ expect(@provider).to receive(:run_command).with(:command => command)
+
+ @provider.run_action(:add)
+ expect(@new_resource).to be_updated
+ end
+
it "should not add an interface if it already exists" do
allow(@provider).to receive(:load_current_resource)
expect(@provider).not_to receive(:run_command)
@@ -85,7 +95,7 @@ describe Chef::Provider::Ifconfig do
describe Chef::Provider::Ifconfig, "action_enable" do
- it "should enable interface if does not exist" do
+ it "should enable interface if it does not exist" do
allow(@provider).to receive(:load_current_resource)
@current_resource.inet_addr nil
command = "ifconfig eth0 10.0.0.1 netmask 255.255.254.0 metric 1 mtu 1500"
@@ -96,6 +106,16 @@ describe Chef::Provider::Ifconfig do
expect(@new_resource).to be_updated
end
+ it "should set the address to target if specified" do
+ allow(@provider).to receive(:load_current_resource)
+ @new_resource.target "172.16.32.2"
+ command = "ifconfig eth0 172.16.32.2 netmask 255.255.254.0 metric 1 mtu 1500"
+ expect(@provider).to receive(:run_command).with(:command => command)
+
+ @provider.run_action(:enable)
+ expect(@new_resource).to be_updated
+ end
+
it "should not enable interface if it already exists" do
allow(@provider).to receive(:load_current_resource)
expect(@provider).not_to receive(:run_command)
diff --git a/spec/unit/provider/mount/aix_spec.rb b/spec/unit/provider/mount/aix_spec.rb
index ca0ddd006c..e232592275 100644
--- a/spec/unit/provider/mount/aix_spec.rb
+++ b/spec/unit/provider/mount/aix_spec.rb
@@ -126,9 +126,10 @@ ENABLED
@provider.run_action(:mount)
end
- it "should not mount resource if it is already mounted" do
+ it "should not mount resource if it is already mounted and the options have not changed" do
stub_mounted_enabled(@provider, @mounted_output, "")
+ allow(@provider).to receive(:mount_options_unchanged?).and_return(true)
expect(@provider).not_to receive(:mount_fs)
@provider.run_action(:mount)
diff --git a/spec/unit/provider/mount/mount_spec.rb b/spec/unit/provider/mount/mount_spec.rb
index 7a37ffe74e..dd13a62796 100644
--- a/spec/unit/provider/mount/mount_spec.rb
+++ b/spec/unit/provider/mount/mount_spec.rb
@@ -323,6 +323,12 @@ describe Chef::Provider::Mount::Mount do
@provider.mount_fs()
end
+ it "should not mount the filesystem if it is mounted and the options have not changed" do
+ allow(@current_resource).to receive(:mounted).and_return(true)
+ expect(@provider).to_not receive(:shell_out!)
+ @provider.mount_fs()
+ end
+
end
describe "umount_fs" do
diff --git a/spec/unit/provider/mount/windows_spec.rb b/spec/unit/provider/mount/windows_spec.rb
index 467d923c6a..2de6f11d43 100644
--- a/spec/unit/provider/mount/windows_spec.rb
+++ b/spec/unit/provider/mount/windows_spec.rb
@@ -111,6 +111,20 @@ describe Chef::Provider::Mount::Windows do
allow(@current_resource).to receive(:mounted).and_return(true)
@provider.mount_fs
end
+
+ it "should remount the filesystem if it is mounted and the options have changed" do
+ expect(@vol).to receive(:add).with(:remote => @new_resource.device,
+ :username => @new_resource.username,
+ :domainname => @new_resource.domain,
+ :password => @new_resource.password)
+ @provider.mount_fs
+ end
+
+ it "should not mount the filesystem if it is mounted and the options have not changed" do
+ expect(@vol).to_not receive(:add)
+ allow(@current_resource).to receive(:mounted).and_return(true)
+ @provider.mount_fs
+ end
end
describe "when unmounting a file system" do
diff --git a/spec/unit/provider/mount_spec.rb b/spec/unit/provider/mount_spec.rb
index e9fe3fa050..cc2a456440 100644
--- a/spec/unit/provider/mount_spec.rb
+++ b/spec/unit/provider/mount_spec.rb
@@ -61,8 +61,19 @@ describe Chef::Provider::Mount do
expect(new_resource).to be_updated_by_last_action
end
- it "should not mount the filesystem if it is mounted" do
+ it "should remount the filesystem if it is mounted and the options have changed" do
allow(current_resource).to receive(:mounted).and_return(true)
+ allow(provider).to receive(:mount_options_unchanged?).and_return(false)
+ expect(provider).to receive(:umount_fs).and_return(true)
+ expect(provider).to receive(:wait_until_unmounted)
+ expect(provider).to receive(:mount_fs).and_return(true)
+ provider.run_action(:mount)
+ expect(new_resource).to be_updated_by_last_action
+ end
+
+ it "should not mount the filesystem if it is mounted and the options have not changed" do
+ allow(current_resource).to receive(:mounted).and_return(true)
+ expect(provider).to receive(:mount_options_unchanged?).and_return(true)
expect(provider).not_to receive(:mount_fs)
provider.run_action(:mount)
expect(new_resource).not_to be_updated_by_last_action
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
index 4974cff934..b868128147 100644
--- a/spec/unit/provider/package/dpkg_spec.rb
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -54,7 +54,6 @@ describe Chef::Provider::Package::Dpkg do
allow(@provider).to receive(:shell_out).with("dpkg-deb -W #{@new_resource.source}", timeout: 900).and_return(@status)
@provider.load_current_resource
expect(@provider.current_resource.package_name).to eq("wget")
- expect(@new_resource.version).to eq(version)
expect(@provider.candidate_version).to eq(version)
end
@@ -83,6 +82,14 @@ describe Chef::Provider::Package::Dpkg do
expect(@provider.current_resource.package_name).to eq("f.o.o-pkg++2")
end
+ it "gets the source package version from dpkg-deb correctly when the package version has `~', `-', `+' or `.' characters" do
+ stdout = "b.a.r-pkg++1\t1.2.3+3141592-1ubuntu1~lucid"
+ status = double(:stdout => stdout, :exitstatus => 1)
+ allow(@provider).to receive(:shell_out).and_return(status)
+ @provider.load_current_resource
+ expect(@provider.candidate_version).to eq('1.2.3+3141592-1ubuntu1~lucid')
+ 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
diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb
index 48164b145c..e0e45d0b4f 100644
--- a/spec/unit/provider/package/rpm_spec.rb
+++ b/spec/unit/provider/package/rpm_spec.rb
@@ -23,183 +23,394 @@ describe Chef::Provider::Package::Rpm do
let(:node) { Chef::Node.new }
let(:events) { Chef::EventDispatch::Dispatcher.new }
let(:run_context) { Chef::RunContext.new(node, {}, events) }
+
+ let(:package_source) { "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+ let(:package_name) { "ImageMagick-c++" }
+
let(:new_resource) do
- Chef::Resource::Package.new("ImageMagick-c++").tap do |resource|
- resource.source "/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
+ Chef::Resource::Package.new(package_name).tap do |resource|
+ resource.source(package_source)
end
end
- let(:exitstatus) { 0 }
- let(:stdout) { String.new('') }
- let(:status) { double('Process::Status', exitstatus: exitstatus, stdout: stdout) }
+
+ # `rpm -qp [stuff] $source`
+ let(:rpm_qp_status) { instance_double('Mixlib::ShellOut', exitstatus: rpm_qp_exitstatus, stdout: rpm_qp_stdout) }
+
+ # `rpm -q [stuff] $package_name`
+ let(:rpm_q_status) { instance_double('Mixlib::ShellOut', exitstatus: rpm_q_exitstatus, stdout: rpm_q_stdout) }
before(:each) do
- allow(::File).to receive(:exists?).and_return(true)
- allow(provider).to receive(:shell_out!).and_return(status)
+ allow(::File).to receive(:exists?).with("PLEASE STUB File.exists? EXACTLY").and_return(true)
+
+ # Ensure all shell out usage is stubbed with exact arguments
+ allow(provider).to receive(:shell_out!).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil)
+ allow(provider).to receive(:shell_out).with("PLEASE STUB YOUR SHELLOUT CALLS").and_return(nil)
end
- describe "when determining the current state of the package" do
- it "should create a current resource with the name of new_resource" do
- provider.load_current_resource
- expect(provider.current_resource.name).to eq("ImageMagick-c++")
- end
+ describe "when the package source is not valid" do
- it "should set the current reource package name to the new resource package name" do
- provider.load_current_resource
- expect(provider.current_resource.package_name).to eq('ImageMagick-c++')
- end
+ context "when source is not defiend" do
+ let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
- it "should raise an exception if a source is supplied but not found" do
- allow(::File).to receive(:exists?).and_return(false)
- expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
+ it "should raise an exception when attempting any action" do
+ expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
+ end
end
- context "installation exists" do
- let(:stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+ context "when the source is a file that doesn't exist" do
- it "should get the source package version from rpm if provided" do
- expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900).and_return(status)
- expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++", timeout: 900).and_return(status)
- provider.load_current_resource
- expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
- expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+ it "should raise an exception when attempting any action" do
+ allow(::File).to receive(:exists?).with(package_source).and_return(false)
+ expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
end
+ end
- it "should return the current version installed if found by rpm" do
- expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900).and_return(status)
- expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++", timeout: 900).and_return(status)
- provider.load_current_resource
- expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+ context "when the source is an unsupported URI scheme" do
+
+ let(:package_source) { "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+ it "should raise an exception if an uri formed source is non-supported scheme" do
+ allow(::File).to receive(:exists?).with(package_source).and_return(false)
+
+ # verify let bindings are as we expect
+ expect(new_resource.source).to eq("foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ expect(provider.load_current_resource).to be_nil
+ expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
end
end
- context "source is uri formed" do
- before(:each) do
- allow(::File).to receive(:exists?).and_return(false)
+ end
+
+ describe "when the package source is valid" do
+
+ before do
+ expect(provider).to receive(:shell_out!).
+ with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_source}", timeout: 900).
+ and_return(rpm_qp_status)
+
+ expect(provider).to receive(:shell_out).
+ with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_name}", timeout: 900).
+ and_return(rpm_q_status)
+ end
+
+ context "when rpm fails when querying package installed state" do
+
+ before do
+ allow(::File).to receive(:exists?).with(package_source).and_return(true)
end
- %w(http HTTP https HTTPS ftp FTP).each do |scheme|
- it "should accept uri formed source (#{scheme})" do
- new_resource.source "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
- expect(provider.load_current_resource).not_to be_nil
+ let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+ let(:rpm_q_stdout) { "" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { -1 }
+
+ it "raises an exception when attempting any action" do
+ expected_message = "Unable to determine current version due to RPM failure."
+
+ expect { provider.run_action(:install) }.to raise_error do |error|
+ expect(error).to be_a_kind_of(Chef::Exceptions::Package)
+ expect(error.to_s).to include(expected_message)
end
end
+ end
+
+
+ context "when the package is installed" do
+
+ let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+ let(:rpm_q_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
- %w(file FILE).each do |scheme|
- it "should accept uri formed source (#{scheme})" do
- new_resource.source "#{scheme}:///ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
- expect(provider.load_current_resource).not_to be_nil
+ let(:action) { :install }
+
+ context "when the source is a file system path" do
+
+ before do
+ allow(::File).to receive(:exists?).with(package_source).and_return(true)
+
+ provider.action = action
+
+ provider.load_current_resource
+ provider.define_resource_requirements
+ provider.process_resource_requirements
end
- end
- it "should raise an exception if an uri formed source is non-supported scheme" do
- new_resource.source "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
- expect(provider.load_current_resource).to be_nil
- expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
- end
- end
+ it "should get the source package version from rpm if provided" do
+ expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
+ expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
- context "source is not defiend" do
- let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+ it "should return the current version installed if found by rpm" do
+ expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
+
+ describe "action install" do
+
+ context "when at the desired version already" do
+ it "does nothing when the correct version is installed" do
+ expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+
+ provider.action_install
+ end
+ end
+
+ context "when a newer version is desired" do
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" }
+
+ it "runs rpm -u with the package source to upgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_install
+ end
+ end
+
+ context "when an older version is desired" do
+ let(:new_resource) do
+ Chef::Resource::RpmPackage.new(package_name).tap do |r|
+ r.source(package_source)
+ r.allow_downgrade(true)
+ end
+ end
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" }
+
+ it "should run rpm -u --oldpackage with the package source to downgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_install
+ end
+
+ end
+
+ end
+
+ describe "action upgrade" do
+
+ let(:action) { :upgrade }
+
+ context "when at the desired version already" do
+ it "does nothing when the correct version is installed" do
+ expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+
+ provider.action_upgrade
+ end
+ end
+
+ context "when a newer version is desired" do
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" }
+
+ it "runs rpm -u with the package source to upgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_upgrade
+ end
+ end
+
+ context "when an older version is desired" do
+ let(:new_resource) do
+ Chef::Resource::RpmPackage.new(package_name).tap do |r|
+ r.source(package_source)
+ r.allow_downgrade(true)
+ end
+ end
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" }
+
+ it "should run rpm -u --oldpackage with the package source to downgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_upgrade
+ end
+
+ end
+ end
+
+ describe "action :remove" do
+
+ let(:action) { :remove }
+
+ it "should remove the package" do
+ expect(provider).to receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900)
+ provider.action_remove
+ end
+ end
+
+
+ context "when the package name contains a tilde (chef#3503)" do
+
+ let(:package_name) { "supermarket" }
+
+ let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" }
+
+ let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+ let(:rpm_q_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
+
+ it "should correctly determine the candidate version and installed version" do
+ expect(provider.current_resource.package_name).to eq("supermarket")
+ expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5")
+ end
+ end
- it "should raise an exception if the source is not set but we are installing" do
- expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
end
- end
- context "installation does not exist" do
- let(:stdout) { String.new("package openssh-askpass is not installed") }
- let(:exitstatus) { -1 }
- let(:new_resource) do
- Chef::Resource::Package.new("openssh-askpass").tap do |resource|
- resource.source "openssh-askpass"
+ context "when the source is given as an URI" do
+ before(:each) do
+ allow(::File).to receive(:exists?).with(package_source).and_return(false)
+
+ provider.action = action
+
+ provider.load_current_resource
+ provider.define_resource_requirements
+ provider.process_resource_requirements
+ end
+
+ %w(http HTTP https HTTPS ftp FTP file FILE).each do |scheme|
+
+ context "when the source URI uses protocol scheme '#{scheme}'" do
+
+ let(:package_source) { "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+ it "should get the source package version from rpm if provided" do
+ expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
+ expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
+
+ it "should return the current version installed if found by rpm" do
+ expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
+
+ end
end
+
end
- it "should raise an exception if rpm fails to run" do
- allow(provider).to receive(:shell_out).and_return(status)
- expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ context "when the package is not installed" do
+
+ let(:package_name) { "openssh-askpass" }
+
+ let(:package_source) { "/tmp/openssh-askpass-1.2.3-4.el6_5.x86_64.rpm" }
+
+ let(:rpm_qp_stdout) { "openssh-askpass 1.2.3-4.el6_5" }
+ let(:rpm_q_stdout) { "package openssh-askpass is not installed" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
+
+ let(:action) { :install }
+
+ before do
+ allow(File).to receive(:exists?).with(package_source).and_return(true)
+
+ provider.action = action
+
+ provider.load_current_resource
+ provider.define_resource_requirements
+ provider.process_resource_requirements
end
it "should not detect the package name as version when not installed" do
- expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass", timeout: 900).and_return(status)
- expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass", timeout: 900).and_return(status)
- provider.load_current_resource
expect(provider.current_resource.version).to be_nil
end
- end
- end
- describe "after the current resource is loaded" do
- let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
- let(:provider) do
- Chef::Provider::Package::Rpm.new(new_resource, run_context).tap do |provider|
- provider.current_resource = current_resource
- end
- end
+ context "when the package name contains a tilde (chef#3503)" do
- describe "when installing or upgrading" do
- it "should run rpm -i with the package source to install" do
- expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
- provider.install_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ let(:package_name) { "supermarket" }
- it "should run rpm -U with the package source to upgrade" do
- current_resource.version("21.4-19.el5")
- expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
- provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" }
- it "should install package if missing and set to upgrade" do
- current_resource.version("ImageMagick-c++")
- expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
- provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+ let(:rpm_q_stdout) { "package supermarket is not installed" }
- context "allowing downgrade" do
- let(:new_resource) { Chef::Resource::RpmPackage.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") }
- let(:current_resource) { Chef::Resource::RpmPackage.new("ImageMagick-c++") }
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
- it "should run rpm -U --oldpackage with the package source to downgrade" do
- new_resource.allow_downgrade(true)
- current_resource.version("21.4-19.el5")
- expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
- provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
+ it "should correctly determine the candidate version" do
+ expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5")
end
end
- context "installing when the name is a path" do
- let(:new_resource) { Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") }
- let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+ describe "managing the package" do
+
+ describe "action install" do
+
+ it "installs the package" do
+ expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900)
- it "should install from a path when the package is a path and the source is nil" do
- expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.current_resource = current_resource
- expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
- provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+ provider.action_install
+ end
+
+ context "when custom resource options are given" do
+ it "installs with custom options specified in the resource" do
+ new_resource.options("--dbpath /var/lib/rpm")
+ expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i #{package_source}", timeout: 900)
+ provider.action_install
+ end
+ end
end
- it "should uprgrade from a path when the package is a path and the source is nil" do
- expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- current_resource.version("21.4-19.el5")
- provider.current_resource = current_resource
- expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
- provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+ describe "action upgrade" do
+
+ let(:action) { :upgrade }
+
+ it "installs the package" do
+ expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900)
+
+ provider.action_upgrade
+ end
+ end
+
+ describe "when removing the package" do
+
+ let(:action) { :remove }
+
+ it "should do nothing" do
+ expect(provider).to_not receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900)
+ provider.action_remove
+ end
end
- end
- it "installs with custom options specified in the resource" do
- provider.candidate_version = '11'
- new_resource.options("--dbpath /var/lib/rpm")
- expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
- provider.install_package(new_resource.name, provider.candidate_version)
end
+
+
end
+ end
- describe "when removing the package" do
- it "should run rpm -e to remove the package" do
- expect(provider).to receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900)
- provider.remove_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ context "when the resource name is the path to the package" do
+
+ let(:new_resource) do
+ # When we pass a source in as the name, then #initialize in the
+ # provider will call File.exists?. Because of the ordering in our
+ # let() bindings and such, we have to set the stub here and not in a
+ # before block.
+ allow(::File).to receive(:exists?).with(package_source).and_return(true)
+ Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ end
+
+ let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+
+ it "should install from a path when the package is a path and the source is nil" do
+ expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ provider.current_resource = current_resource
+ expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+ end
+
+ it "should uprgrade from a path when the package is a path and the source is nil" do
+ expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ current_resource.version("21.4-19.el5")
+ provider.current_resource = current_resource
+ expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
end
end
+
+
end
+
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
index 67ffb7bb9e..f790bdb1ce 100644
--- a/spec/unit/provider/package/rubygems_spec.rb
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -369,6 +369,24 @@ describe Chef::Provider::Package::Rubygems do
expect(provider.gem_env.gem_binary_location).to eq('/usr/weird/bin/gem')
end
+ it "recognizes chef as omnibus" do
+ allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ expect(provider.is_omnibus?).to be true
+ end
+
+ it "recognizes opscode as omnibus" do
+ allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/opscode/embedded/bin")
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ expect(provider.is_omnibus?).to be true
+ end
+
+ it "recognizes chefdk as omnibus" do
+ allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chefdk/embedded/bin")
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ expect(provider.is_omnibus?).to be true
+ end
+
it "searches for a gem binary when running on Omnibus on Unix" do
platform_mock :unix do
allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index e878b92621..3fc0b807c9 100644
--- a/spec/unit/provider/package/yum_spec.rb
+++ b/spec/unit/provider/package/yum_spec.rb
@@ -1,6 +1,6 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@ describe Chef::Provider::Package::Yum do
@node = Chef::Node.new
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
- @new_resource = Chef::Resource::Package.new('cups')
+ @new_resource = Chef::Resource::YumPackage.new('cups')
@status = double("Status", :exitstatus => 0)
@yum_cache = double(
'Chef::Provider::Yum::YumCache',
@@ -39,6 +39,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@pid = double("PID")
end
@@ -88,6 +89,46 @@ describe Chef::Provider::Package::Yum do
end
end
+ describe "yum_binary accessor" do
+ it "when yum-deprecated exists" do
+ expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true)
+ expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ expect(@provider.yum_binary).to eql("yum-deprecated")
+ end
+
+ it "when yum-deprecated does not exist" do
+ expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false)
+ expect(@yum_cache).to receive(:yum_binary=).with("yum")
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ expect(@provider.yum_binary).to eql("yum")
+ end
+
+ it "when the yum_binary is set on the resource" do
+ @new_resource.yum_binary "/usr/bin/yum-something"
+ expect(File).not_to receive(:exist?)
+ expect(@yum_cache).to receive(:yum_binary=).with("/usr/bin/yum-something")
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ expect(@provider.yum_binary).to eql("/usr/bin/yum-something")
+ end
+
+ it "when the new_resource is a vanilla package class and yum-deprecated exists" do
+ @new_resource = Chef::Resource::Package.new('cups')
+ expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(true)
+ expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ expect(@provider.yum_binary).to eql("yum-deprecated")
+ end
+
+ it "when the new_resource is a vanilla package class and yum-deprecated does not exist" do
+ @new_resource = Chef::Resource::Package.new('cups')
+ expect(File).to receive(:exist?).with("/usr/bin/yum-deprecated").and_return(false)
+ expect(@yum_cache).to receive(:yum_binary=).with("yum")
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ expect(@provider.yum_binary).to eql("yum")
+ end
+ end
+
describe "when arch in package_name" do
it "should set the arch if no existing package_name is found and new_package_name+new_arch is available" do
@new_resource = Chef::Resource::YumPackage.new('testing.noarch')
@@ -109,6 +150,7 @@ describe Chef::Provider::Package::Yum do
allow(@yum_cache).to receive(:package_available?).and_return(true)
allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing")
@@ -163,6 +205,7 @@ describe Chef::Provider::Package::Yum do
allow(@yum_cache).to receive(:package_available?).and_return(true)
allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
# annoying side effect of the fun stub'ing above
@provider.load_current_resource
@@ -194,6 +237,7 @@ describe Chef::Provider::Package::Yum do
allow(@yum_cache).to receive(:package_available?).and_return(true)
allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing.beta3")
@@ -229,6 +273,7 @@ describe Chef::Provider::Package::Yum do
allow(@yum_cache).to receive(:package_available?).and_return(true)
allow(@yum_cache).to receive(:disable_extra_repo_control).and_return(true)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider.new_resource.package_name).to eq("testing.i386")
@@ -281,6 +326,7 @@ describe Chef::Provider::Package::Yum do
before do
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(yum_cache)
+ allow(yum_cache).to receive(:yum_binary=).with("yum")
@pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", [])
expect(yum_cache).to receive(:packages_from_require).and_return([@pkg])
end
@@ -352,6 +398,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", [])
expect(@yum_cache).to receive(:packages_from_require).and_return([pkg])
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@@ -373,6 +420,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
pkg = Chef::Provider::Package::Yum::RPMPackage.new("test-package", "2.0.1.el5", "x86_64", [])
expect(@yum_cache).to receive(:packages_from_require).and_return([pkg])
@new_resource = Chef::Resource::YumPackage.new('test-package = 2.0.1.el5')
@@ -395,6 +443,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
expect(@yum_cache).to receive(:packages_from_require).exactly(4).times.and_return([])
expect(@yum_cache).to receive(:reload_provides).twice
@@ -419,6 +468,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
expect(@yum_cache).to receive(:packages_from_require).twice.and_return([])
expect(@yum_cache).to receive(:reload_provides)
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@@ -438,6 +488,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
expect(@yum_cache).to receive(:packages_from_require).once.and_return([])
expect(@yum_cache).not_to receive(:reload_provides)
@@ -462,6 +513,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
expect(@yum_cache).to receive(:packages_from_require).twice.and_return([])
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
@@ -474,7 +526,7 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.19.el5"
+ "-d0 -e0 -y install cups-1.2.4-11.19.el5"
)
@provider.install_package("cups", "1.2.4-11.19.el5")
end
@@ -482,7 +534,7 @@ describe Chef::Provider::Package::Yum do
it "should run yum localinstall if given a path to an rpm" do
allow(@new_resource).to receive(:source).and_return("/tmp/emacs-21.4-20.el5.i386.rpm")
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+ "-d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
)
@provider.install_package("emacs", "21.4-20.el5")
end
@@ -493,7 +545,7 @@ describe Chef::Provider::Package::Yum do
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
expect(@new_resource.source).to eq("/tmp/emacs-21.4-20.el5.i386.rpm")
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
+ "-d0 -e0 -y localinstall /tmp/emacs-21.4-20.el5.i386.rpm"
)
@provider.install_package("/tmp/emacs-21.4-20.el5.i386.rpm", "21.4-20.el5")
end
@@ -503,7 +555,7 @@ describe Chef::Provider::Package::Yum do
allow(@new_resource).to receive(:arch).and_return("i386")
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.i386"
+ "-d0 -e0 -y install cups-1.2.4-11.19.el5.i386"
)
@provider.install_package("cups", "1.2.4-11.19.el5")
end
@@ -514,7 +566,7 @@ describe Chef::Provider::Package::Yum do
allow(@new_resource).to receive(:options).and_return("--disablerepo epmd")
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y --disablerepo epmd install cups-11"
+ "-d0 -e0 -y --disablerepo epmd install cups-11"
)
@provider.install_package(@new_resource.name, @provider.candidate_version)
end
@@ -531,6 +583,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
expect { @provider.install_package("lolcats", "0.99") }.to raise_error(Chef::Exceptions::Package, %r{Version .* not found})
end
@@ -549,6 +602,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect { @provider.install_package("cups", "1.2.4-11.15.el5") }.to raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
@@ -568,10 +622,11 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+ "-d0 -e0 -y install cups-1.2.4-11.15.el5"
)
@provider.install_package("cups", "1.2.4-11.15.el5")
end
@@ -591,10 +646,11 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y downgrade cups-1.2.4-11.15.el5"
+ "-d0 -e0 -y downgrade cups-1.2.4-11.15.el5"
)
@provider.install_package("cups", "1.2.4-11.15.el5")
end
@@ -604,7 +660,7 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+ "-d0 -e0 -y install cups-1.2.4-11.15.el5"
)
expect(@yum_cache).to receive(:reload).once
@provider.install_package("cups", "1.2.4-11.15.el5")
@@ -615,7 +671,7 @@ describe Chef::Provider::Package::Yum do
@provider.load_current_resource
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.15.el5"
+ "-d0 -e0 -y install cups-1.2.4-11.15.el5"
)
expect(@yum_cache).not_to receive(:reload)
@provider.install_package("cups", "1.2.4-11.15.el5")
@@ -628,7 +684,7 @@ describe Chef::Provider::Package::Yum do
allow(@provider).to receive(:candidate_version).and_return('11')
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-11"
+ "-d0 -e0 -y install cups-11"
)
@provider.upgrade_package(@new_resource.name, @provider.candidate_version)
end
@@ -639,7 +695,7 @@ describe Chef::Provider::Package::Yum do
allow(@provider).to receive(:candidate_version).and_return('11')
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-11"
+ "-d0 -e0 -y install cups-11"
)
@provider.upgrade_package(@new_resource.name, @provider.candidate_version)
end
@@ -657,6 +713,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@provider.load_current_resource
expect { @provider.upgrade_package("cups", "1.2.4-11.15.el5") }.to raise_error(Chef::Exceptions::Package, %r{is newer than candidate package})
@@ -706,7 +763,7 @@ describe Chef::Provider::Package::Yum do
describe "when removing a package" do
it "should run yum remove with the package name" do
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y remove emacs-1.0"
+ "-d0 -e0 -y remove emacs-1.0"
)
@provider.remove_package("emacs", "1.0")
end
@@ -714,7 +771,7 @@ describe Chef::Provider::Package::Yum do
it "should run yum remove with the package name and arch" do
allow(@new_resource).to receive(:arch).and_return("x86_64")
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y remove emacs-1.0.x86_64"
+ "-d0 -e0 -y remove emacs-1.0.x86_64"
)
@provider.remove_package("emacs", "1.0")
end
@@ -723,7 +780,7 @@ describe Chef::Provider::Package::Yum do
describe "when purging a package" do
it "should run yum remove with the package name" do
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y remove emacs-1.0"
+ "-d0 -e0 -y remove emacs-1.0"
)
@provider.purge_package("emacs", "1.0")
end
@@ -737,7 +794,7 @@ describe Chef::Provider::Package::Yum do
"yum -d0 -e0 -y install emacs-1.0",
{:timeout => Chef::Config[:yum_timeout]}
)
- @provider.yum_command("yum -d0 -e0 -y install emacs-1.0")
+ @provider.yum_command("-d0 -e0 -y install emacs-1.0")
end
it "should run yum once if it exits with a return code > 0 and no scriptlet failures" do
@@ -747,7 +804,7 @@ describe Chef::Provider::Package::Yum do
"yum -d0 -e0 -y install emacs-1.0",
{:timeout => Chef::Config[:yum_timeout]}
)
- expect { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+ expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
end
it "should run yum once if it exits with a return code of 1 and %pre scriptlet failures" do
@@ -759,7 +816,7 @@ describe Chef::Provider::Package::Yum do
{:timeout => Chef::Config[:yum_timeout]}
)
# will still raise an exception, can't stub out the subsequent call
- expect { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+ expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
end
it "should run yum twice if it exits with a return code of 1 and %post scriptlet failures" do
@@ -771,7 +828,20 @@ describe Chef::Provider::Package::Yum do
{:timeout => Chef::Config[:yum_timeout]}
)
# will still raise an exception, can't stub out the subsequent call
- expect { @provider.yum_command("yum -d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+ expect { @provider.yum_command("-d0 -e0 -y install emacs-1.0") }.to raise_error(Chef::Exceptions::Exec)
+ end
+
+ it "should pass the yum_binary to the command if its specified" do
+ @new_resource.yum_binary "yum-deprecated"
+ expect(@yum_cache).to receive(:yum_binary=).with("yum-deprecated")
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ @status = double("Status", :exitstatus => 0, :stdout => "", :stderr => "")
+ allow(@provider).to receive(:shell_out).and_return(@status)
+ expect(@provider).to receive(:shell_out).once.with(
+ "yum-deprecated -d0 -e0 -y install emacs-1.0",
+ {:timeout => Chef::Config[:yum_timeout]}
+ )
+ @provider.yum_command("-d0 -e0 -y install emacs-1.0")
end
end
end
@@ -1737,6 +1807,7 @@ EOF
Chef::Provider::Package::Yum::YumCache.reset_instance
@yc = Chef::Provider::Package::Yum::YumCache.instance
# load valid data
+ @yc.yum_binary = "yum"
allow(@yc).to receive(:shell_out!).and_return(@status)
allow_any_instance_of(described_class).to receive(:which).with("yum").and_return("/usr/bin/yum")
allow(::File).to receive(:open).with("/usr/bin/yum", "r") do |&block|
@@ -2053,6 +2124,8 @@ describe "Chef::Provider::Package::Yum - Multi" do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@pid = double("PID")
end
@@ -2134,7 +2207,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.19.el5 vim-1.0"
+ "-d0 -e0 -y install cups-1.2.4-11.19.el5 vim-1.0"
)
@provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
end
@@ -2144,7 +2217,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
allow(@new_resource).to receive(:arch).and_return("i386")
allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.i386 vim-1.0.i386"
+ "-d0 -e0 -y install cups-1.2.4-11.19.el5.i386 vim-1.0.i386"
)
@provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", "1.0"])
end
@@ -2155,7 +2228,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0"
+ "-d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0"
)
allow(@new_resource).to receive(:options).and_return("--disablerepo epmd")
@provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
@@ -2181,7 +2254,7 @@ describe "Chef::Provider::Package::Yum - Multi" do
@provider.load_current_resource
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y install cups-1.2.4-11.19.el5.x86_64 vim-1.0"
+ "-d0 -e0 -y install cups-1.2.4-11.19.el5.x86_64 vim-1.0"
)
@provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
end
diff --git a/spec/unit/provider/powershell_spec.rb b/spec/unit/provider/powershell_script_spec.rb
index 855c18af9b..855c18af9b 100644
--- a/spec/unit/provider/powershell_spec.rb
+++ b/spec/unit/provider/powershell_script_spec.rb
diff --git a/spec/unit/provider/registry_key_spec.rb b/spec/unit/provider/registry_key_spec.rb
index 79811fdab8..47543ffe39 100644
--- a/spec/unit/provider/registry_key_spec.rb
+++ b/spec/unit/provider/registry_key_spec.rb
@@ -77,6 +77,18 @@ shared_examples_for "a registry key" do
end
describe "action_create" do
+ context "when a case insensitive match for the key exists" do
+ before(:each) do
+ expect(@double_registry).to receive(:key_exists?).twice.with(keyname.downcase).and_return(true)
+ end
+ it "should do nothing if the if a case insensitive key and the value both exist" do
+ @provider.new_resource.key(keyname.downcase)
+ expect(@double_registry).to receive(:get_values).with(keyname.downcase).and_return( testval1 )
+ expect(@double_registry).not_to receive(:set_value)
+ @provider.load_current_resource
+ @provider.action_create
+ end
+ end
context "when the key exists" do
before(:each) do
expect(@double_registry).to receive(:key_exists?).twice.with(keyname).and_return(true)
diff --git a/spec/unit/provider/service/aix_service_spec.rb b/spec/unit/provider/service/aix_service_spec.rb
index a0c8bb3407..5cca7d6f0a 100644
--- a/spec/unit/provider/service/aix_service_spec.rb
+++ b/spec/unit/provider/service/aix_service_spec.rb
@@ -94,7 +94,7 @@ describe Chef::Provider::Service::Aix do
end
it "service is a group" do
- expect(@provider).to receive(:shell_out!).with("lssrc -g chef").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status)
@provider.load_current_resource
expect(@provider.instance_eval("@is_resource_group")).to be_truthy
end
@@ -106,7 +106,7 @@ describe Chef::Provider::Service::Aix do
end
it "service is a group" do
- expect(@provider).to receive(:shell_out!).with("lssrc -g chef").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status)
@provider.load_current_resource
expect(@provider.instance_eval("@is_resource_group")).to be_truthy
end
@@ -119,7 +119,7 @@ describe Chef::Provider::Service::Aix do
end
it "service is a subsystem" do
- expect(@provider).to receive(:shell_out!).with("lssrc -g chef").and_return(@group_status)
+ expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@group_status)
expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@service_status)
@provider.load_current_resource
expect(@provider.instance_eval("@is_resource_group")).to be_falsey
diff --git a/spec/unit/provider/service/gentoo_service_spec.rb b/spec/unit/provider/service/gentoo_service_spec.rb
index c08982acc3..0aa7bf4529 100644
--- a/spec/unit/provider/service/gentoo_service_spec.rb
+++ b/spec/unit/provider/service/gentoo_service_spec.rb
@@ -1,7 +1,7 @@
#
# Author:: Lee Jensen (<ljensen@engineyard.com>)
# Author:: AJ Christensen (<aj@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -108,17 +108,17 @@ describe Chef::Provider::Service::Gentoo do
it "should support the status command automatically" do
@provider.load_current_resource
- expect(@new_resource.supports[:status]).to be_truthy
+ expect(@provider.supports[:status]).to be true
end
it "should support the restart command automatically" do
@provider.load_current_resource
- expect(@new_resource.supports[:restart]).to be_truthy
+ expect(@provider.supports[:restart]).to be true
end
it "should not support the reload command automatically" do
@provider.load_current_resource
- expect(@new_resource.supports[:reload]).not_to be_truthy
+ expect(@provider.supports[:reload]).to be_falsey
end
end
diff --git a/spec/unit/provider/service/macosx_spec.rb b/spec/unit/provider/service/macosx_spec.rb
index 597845a558..54183bdc3d 100644
--- a/spec/unit/provider/service/macosx_spec.rb
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -60,15 +60,15 @@ XML
["Daemon", "Agent"].each do |service_type|
["redis-server", "io.redis.redis-server"].each do |service_name|
- ["10.9", "10.10"].each do |platform_version|
+ ["10.9", "10.10", "10.11"].each do |platform_version|
let(:plist) {'/Library/LaunchDaemons/io.redis.redis-server.plist'}
let(:session) { StringIO.new }
if service_type == 'Agent'
let(:plist) {'/Library/LaunchAgents/io.redis.redis-server.plist'}
let(:session) {'-S Aqua '}
- let(:su_cmd) {'su igor -c'}
- if platform_version != "10.10"
- let(:su_cmd) {'su -l igor -c'}
+ let(:su_cmd) {'su -l igor -c'}
+ if platform_version == "10.9"
+ let(:su_cmd) {'su igor -c'}
end
end
let(:service_label) {'io.redis.redis-server'}
diff --git a/spec/unit/provider/service/openbsd_service_spec.rb b/spec/unit/provider/service/openbsd_service_spec.rb
index 1b5206470e..d3c150a14b 100644
--- a/spec/unit/provider/service/openbsd_service_spec.rb
+++ b/spec/unit/provider/service/openbsd_service_spec.rb
@@ -35,10 +35,12 @@ describe Chef::Provider::Service::Openbsd do
node
end
+ let(:supports) { {:status => false} }
+
let(:new_resource) do
new_resource = Chef::Resource::Service.new("sndiod")
new_resource.pattern("sndiod")
- new_resource.supports({:status => false})
+ new_resource.supports(supports)
new_resource
end
@@ -106,9 +108,7 @@ describe Chef::Provider::Service::Openbsd do
context "when the service supports status" do
let(:status) { double(:stdout => "", :exitstatus => 0) }
- before do
- new_resource.supports({:status => true})
- end
+ let(:supports) { { :status => true } }
it "should run '/etc/rc.d/service_name status'" do
expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status)
@@ -305,10 +305,12 @@ describe Chef::Provider::Service::Openbsd do
end
describe Chef::Provider::Service::Openbsd, "restart_service" do
- it "should call 'restart' on the service_name if the resource supports it" do
- new_resource.supports({:restart => true})
- expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} restart")
- provider.restart_service()
+ context "when the new_resource supports restart" do
+ let(:supports) { { restart: true } }
+ it "should call 'restart' on the service_name if the resource supports it" do
+ expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} restart")
+ provider.restart_service()
+ end
end
it "should call the restart_command if one has been specified" do
diff --git a/spec/unit/provider/service/redhat_spec.rb b/spec/unit/provider/service/redhat_spec.rb
index 73cfec8a6f..5aaf54d9f5 100644
--- a/spec/unit/provider/service/redhat_spec.rb
+++ b/spec/unit/provider/service/redhat_spec.rb
@@ -64,24 +64,76 @@ describe "Chef::Provider::Service::Redhat" do
end
describe "load current resource" do
- it "sets the current enabled status to true if the service is enabled for any run level" do
+ before do
status = double("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
- expect(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status)
+ allow(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status)
+ end
+
+ it "sets supports[:status] to true by default" do
chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:on 6:off", :stderr => "")
expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
- expect(@provider.instance_variable_get("@service_missing")).to be_falsey
+ expect(@provider.service_missing).to be false
@provider.load_current_resource
- expect(@current_resource.enabled).to be_truthy
+ expect(@provider.supports[:status]).to be true
+ end
+
+ it "lets the user override supports[:status] in the new_resource" do
+ @new_resource.supports( { status: false } )
+ @new_resource.pattern "myservice"
+ chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:on 6:off", :stderr => "")
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ foo_out = double("ps_command", :exitstatus => 0, :stdout => "a line that matches myservice", :stderr => "")
+ expect(@provider).to receive(:shell_out!).with("foo").and_return(foo_out)
+ expect(@provider.service_missing).to be false
+ expect(@provider).not_to receive(:shell_out).with("/sbin/service chef status")
+ @provider.load_current_resource
+ expect(@provider.supports[:status]).to be false
+ end
+
+ it "sets the current enabled status to true if the service is enabled for any run level" do
+ chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:on 6:off", :stderr => "")
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ expect(@provider.service_missing).to be false
+ @provider.load_current_resource
+ expect(@current_resource.enabled).to be true
end
it "sets the current enabled status to false if the regex does not match" do
- status = double("Status", :exitstatus => 0, :stdout => "" , :stderr => "")
- expect(@provider).to receive(:shell_out).with("/sbin/service chef status").and_return(status)
chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:off 2:off 3:off 4:off 5:off 6:off", :stderr => "")
expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
- expect(@provider.instance_variable_get("@service_missing")).to be_falsey
+ expect(@provider.service_missing).to be false
expect(@provider.load_current_resource).to eql(@current_resource)
- expect(@current_resource.enabled).to be_falsey
+ expect(@current_resource.enabled).to be false
+ end
+
+ it "sets the current enabled status to true if the service is enabled at specified run levels" do
+ @new_resource.run_levels([1, 2])
+ chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:on 2:on 3:off 4:off 5:off 6:off", :stderr => "")
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ expect(@provider.service_missing).to be false
+ @provider.load_current_resource
+ expect(@current_resource.enabled).to be true
+ expect(@provider.current_run_levels).to eql([1, 2])
+ end
+
+ it "sets the current enabled status to false if the service is enabled at a run level it should not" do
+ @new_resource.run_levels([1, 2])
+ chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:on 2:on 3:on 4:off 5:off 6:off", :stderr => "")
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ expect(@provider.service_missing).to be false
+ @provider.load_current_resource
+ expect(@current_resource.enabled).to be false
+ expect(@provider.current_run_levels).to eql([1, 2, 3])
+ end
+
+ it "sets the current enabled status to false if the service is not enabled at specified run levels" do
+ @new_resource.run_levels([ 2 ])
+ chkconfig = double("Chkconfig", :exitstatus => 0, :stdout => "chef 0:off 1:on 2:off 3:off 4:off 5:off 6:off", :stderr => "")
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --list chef", :returns => [0,1]).and_return(chkconfig)
+ expect(@provider.service_missing).to be false
+ @provider.load_current_resource
+ expect(@current_resource.enabled).to be false
+ expect(@provider.current_run_levels).to eql([1])
end
end
@@ -144,6 +196,28 @@ describe "Chef::Provider::Service::Redhat" do
expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} on")
@provider.enable_service
end
+
+ it "should call chkconfig to add 'service_name' at specified run_levels" do
+ allow(@provider).to receive(:run_levels).and_return([1, 2])
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on")
+ @provider.enable_service
+ end
+
+ it "should call chkconfig to add 'service_name' at specified run_levels when run_levels do not match" do
+ allow(@provider).to receive(:run_levels).and_return([1, 2])
+ allow(@provider).to receive(:current_run_levels).and_return([1, 3])
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on")
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 3 #{@new_resource.service_name} off")
+ @provider.enable_service
+ end
+
+ it "should call chkconfig to add 'service_name' at specified run_levels if there is an extra run_level" do
+ allow(@provider).to receive(:run_levels).and_return([1, 2])
+ allow(@provider).to receive(:current_run_levels).and_return([1, 2, 3])
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} on")
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 3 #{@new_resource.service_name} off")
+ @provider.enable_service
+ end
end
describe "disable_service" do
@@ -151,6 +225,12 @@ describe "Chef::Provider::Service::Redhat" do
expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig #{@new_resource.service_name} off")
@provider.disable_service
end
+
+ it "should call chkconfig to del 'service_name' at specified run_levels" do
+ allow(@provider).to receive(:run_levels).and_return([1, 2])
+ expect(@provider).to receive(:shell_out!).with("/sbin/chkconfig --level 12 #{@new_resource.service_name} off")
+ @provider.disable_service
+ end
end
end
diff --git a/spec/unit/provider/service/upstart_service_spec.rb b/spec/unit/provider/service/upstart_service_spec.rb
index ca7ce8f930..1c8e304cb7 100644
--- a/spec/unit/provider/service/upstart_service_spec.rb
+++ b/spec/unit/provider/service/upstart_service_spec.rb
@@ -19,6 +19,10 @@
require 'spec_helper'
describe Chef::Provider::Service::Upstart do
+ let(:shell_out_success) do
+ double('shell_out_with_systems_locale', :exitstatus => 0, :error? => false)
+ end
+
before(:each) do
@node =Chef::Node.new
@node.name('upstarter')
@@ -173,7 +177,7 @@ describe Chef::Provider::Service::Upstart do
end
it "should run the services status command if one has been specified" do
- allow(@provider).to receive(:shell_out!).with("/bin/chefhasmonkeypants status").and_return(0)
+ allow(@provider).to receive(:shell_out!).with("/bin/chefhasmonkeypants status").and_return(shell_out_success)
expect(@current_resource).to receive(:running).with(true)
@provider.load_current_resource
end
@@ -246,7 +250,7 @@ describe Chef::Provider::Service::Upstart do
end
it "should call '/sbin/start service_name' if no start command is specified" do
- expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(0)
+ expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(shell_out_success)
@provider.start_service()
end
@@ -261,7 +265,7 @@ describe Chef::Provider::Service::Upstart do
@new_resource.parameters({ "OSD_ID" => "2" })
@provider = Chef::Provider::Service::Upstart.new(@new_resource, @run_context)
@provider.current_resource = @current_resource
- expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start rsyslog OSD_ID=2").and_return(0)
+ expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start rsyslog OSD_ID=2").and_return(shell_out_success)
@provider.start_service()
end
@@ -274,13 +278,13 @@ describe Chef::Provider::Service::Upstart do
it "should call '/sbin/restart service_name' if no restart command is specified" do
allow(@current_resource).to receive(:running).and_return(true)
- expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/restart #{@new_resource.service_name}").and_return(0)
+ expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/restart #{@new_resource.service_name}").and_return(shell_out_success)
@provider.restart_service()
end
it "should call '/sbin/start service_name' if restart_service is called for a stopped service" do
allow(@current_resource).to receive(:running).and_return(false)
- expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(0)
+ expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/start #{@new_resource.service_name}").and_return(shell_out_success)
@provider.restart_service()
end
@@ -293,7 +297,7 @@ describe Chef::Provider::Service::Upstart do
it "should call '/sbin/reload service_name' if no reload command is specified" do
allow(@current_resource).to receive(:running).and_return(true)
- expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/reload #{@new_resource.service_name}").and_return(0)
+ expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/reload #{@new_resource.service_name}").and_return(shell_out_success)
@provider.reload_service()
end
@@ -306,7 +310,7 @@ describe Chef::Provider::Service::Upstart do
it "should call '/sbin/stop service_name' if no stop command is specified" do
allow(@current_resource).to receive(:running).and_return(true)
- expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}").and_return(0)
+ expect(@provider).to receive(:shell_out_with_systems_locale!).with("/sbin/stop #{@new_resource.service_name}").and_return(shell_out_success)
@provider.stop_service()
end
diff --git a/spec/unit/provider/template/content_spec.rb b/spec/unit/provider/template/content_spec.rb
index 4b88a3aea5..3d6e822c00 100644
--- a/spec/unit/provider/template/content_spec.rb
+++ b/spec/unit/provider/template/content_spec.rb
@@ -23,6 +23,10 @@ describe Chef::Provider::Template::Content do
let(:new_resource) do
double("Chef::Resource::Template (new)",
:cookbook_name => 'openldap',
+ :recipe_name => 'default',
+ :source_line => "/Users/lamont/solo/cookbooks/openldap/recipes/default.rb:2:in `from_file'",
+ :source_line_file => "/Users/lamont/solo/cookbooks/openldap/recipes/default.rb",
+ :source_line_number => "2",
:source => 'openldap_stuff.conf.erb',
:local => false,
:cookbook => nil,
@@ -75,4 +79,41 @@ describe Chef::Provider::Template::Content do
expect(IO.read(content.tempfile.path)).to eq("slappiness is a warm gun")
end
+ describe "when using location helpers" do
+ let(:new_resource) do
+ double("Chef::Resource::Template (new)",
+ :cookbook_name => 'openldap',
+ :recipe_name => 'default',
+ :source_line => CHEF_SPEC_DATA + "/cookbooks/openldap/recipes/default.rb:2:in `from_file'",
+ :source_line_file => CHEF_SPEC_DATA + "/cookbooks/openldap/recipes/default.rb",
+ :source_line_number => "2",
+ :source => 'helpers.erb',
+ :local => false,
+ :cookbook => nil,
+ :variables => {},
+ :inline_helper_blocks => {},
+ :inline_helper_modules => [],
+ :helper_modules => [])
+ end
+
+ it "creates the template with the rendered content" do
+ expect(IO.read(content.tempfile.path)).to eql <<EOF
+openldap
+default
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb:2:in `from_file'
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb
+2
+helpers.erb
+#{CHEF_SPEC_DATA}/cookbooks/openldap/templates/default/helpers.erb
+openldap
+default
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb:2:in `from_file'
+#{CHEF_SPEC_DATA}/cookbooks/openldap/recipes/default.rb
+2
+helpers.erb
+#{CHEF_SPEC_DATA}/cookbooks/openldap/templates/default/helpers.erb
+EOF
+ end
+
+ end
end
diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb
index e18d69bc19..88df4a20cc 100644
--- a/spec/unit/provider_resolver_spec.rb
+++ b/spec/unit/provider_resolver_spec.rb
@@ -28,30 +28,37 @@ include Chef::Mixin::ConvertToClassName
describe Chef::ProviderResolver do
+ let(:resource_name) { :service }
+ let(:provider) { nil }
+ let(:action) { :start }
+
let(:node) do
node = Chef::Node.new
- allow(node).to receive(:[]).with(:os).and_return(os)
- allow(node).to receive(:[]).with(:platform_family).and_return(platform_family)
- allow(node).to receive(:[]).with(:platform).and_return(platform)
- allow(node).to receive(:[]).with(:platform_version).and_return(platform_version)
- allow(node).to receive(:is_a?).and_return(Chef::Node)
+ node.automatic[:os] = os
+ node.automatic[:platform_family] = platform_family
+ node.automatic[:platform] = platform
+ node.automatic[:platform_version] = platform_version
+ node.automatic[:kernel] = { machine: 'i386' }
node
end
+ let(:run_context) { Chef::RunContext.new(node, nil, nil) }
let(:provider_resolver) { Chef::ProviderResolver.new(node, resource, action) }
+ let(:resolved_provider) do
+ begin
+ resource ? resource.provider_for_action(action).class : nil
+ rescue Chef::Exceptions::ProviderNotFound
+ nil
+ end
+ end
- let(:action) { :start }
-
- let(:resolved_provider) { provider_resolver.resolve }
-
- let(:provider) { nil }
-
- let(:resource_name) { :service }
-
- let(:resource) { double(Chef::Resource, provider: provider, resource_name: resource_name) }
-
- before do
- allow(resource).to receive(:is_a?).with(Chef::Resource).and_return(true)
+ let(:resource) do
+ resource_class = Chef::ResourceResolver.resolve(resource_name, node: node)
+ if resource_class
+ resource = resource_class.new('test', run_context)
+ resource.provider = provider if provider
+ end
+ resource
end
def self.on_platform(platform, *tags,
@@ -83,16 +90,41 @@ describe Chef::ProviderResolver do
end
def self.expect_providers(**providers)
- providers.each do |name, provider|
+ providers.each do |name, expected|
describe name.to_s do
let(:resource_name) { name }
- if provider
- it "resolves to a #{provider}" do
- expect(resolved_provider).to eql(provider)
+
+ tags = []
+ expected_provider = nil
+ expected_resource = nil
+ Array(expected).each do |p|
+ if p.is_a?(Class) && p <= Chef::Provider
+ expected_provider = p
+ elsif p.is_a?(Class) && p <= Chef::Resource
+ expected_resource = p
+ else
+ tags << p
+ end
+ end
+
+ if expected_resource && expected_provider
+ it "'#{name}' resolves to resource #{expected_resource} and provider #{expected_provider}", *tags do
+ expect(resource.class).to eql(expected_resource)
+ provider = double(expected_provider, class: expected_provider)
+ expect(provider).to receive(:action=).with(action)
+ expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider)
+ expect(resolved_provider).to eql(expected_provider)
+ end
+ elsif expected_provider
+ it "'#{name}' resolves to provider #{expected_provider}", *tags do
+ provider = double(expected_provider)
+ expect(provider).to receive(:action=).with(action)
+ expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider)
+ expect(resolved_provider).to eql(expected_provider)
end
else
- it "Fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})" do
- expect { resolved_provider }.to raise_error /Cannot find a provider/
+ it "'#{name}' fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})", *tags do
+ expect(resolved_provider).to be_nil
end
end
end
@@ -454,49 +486,48 @@ describe Chef::ProviderResolver do
PROVIDERS =
{
- bash: Chef::Provider::Script,
- breakpoint: Chef::Provider::Breakpoint,
- chef_gem: Chef::Provider::Package::Rubygems,
- cookbook_file: Chef::Provider::CookbookFile,
- csh: Chef::Provider::Script,
- deploy: Chef::Provider::Deploy::Timestamped,
- deploy_revision: Chef::Provider::Deploy::Revision,
- directory: Chef::Provider::Directory,
- easy_install_package: Chef::Provider::Package::EasyInstall,
- erl_call: Chef::Provider::ErlCall,
- execute: Chef::Provider::Execute,
- file: Chef::Provider::File,
- gem_package: Chef::Provider::Package::Rubygems,
- git: Chef::Provider::Git,
- group: Chef::Provider::Group::Gpasswd,
- homebrew_package: Chef::Provider::Package::Homebrew,
- http_request: Chef::Provider::HttpRequest,
- ifconfig: Chef::Provider::Ifconfig,
- link: Chef::Provider::Link,
- log: Chef::Provider::Log::ChefLog,
- macports_package: Chef::Provider::Package::Macports,
- mdadm: Chef::Provider::Mdadm,
- mount: Chef::Provider::Mount::Mount,
- perl: Chef::Provider::Script,
- portage_package: Chef::Provider::Package::Portage,
- python: Chef::Provider::Script,
- remote_directory: Chef::Provider::RemoteDirectory,
- route: Chef::Provider::Route,
- rpm_package: Chef::Provider::Package::Rpm,
- ruby: Chef::Provider::Script,
- ruby_block: Chef::Provider::RubyBlock,
- script: Chef::Provider::Script,
- subversion: Chef::Provider::Subversion,
- template: Chef::Provider::Template,
- timestamped_deploy: Chef::Provider::Deploy::Timestamped,
- user: Chef::Provider::User::Useradd,
- whyrun_safe_ruby_block: Chef::Provider::WhyrunSafeRubyBlock,
+ bash: [ Chef::Resource::Bash, Chef::Provider::Script ],
+ breakpoint: [ Chef::Resource::Breakpoint, Chef::Provider::Breakpoint ],
+ chef_gem: [ Chef::Resource::ChefGem, Chef::Provider::Package::Rubygems ],
+ cookbook_file: [ Chef::Resource::CookbookFile, Chef::Provider::CookbookFile ],
+ csh: [ Chef::Resource::Csh, Chef::Provider::Script ],
+ deploy: [ Chef::Resource::Deploy, Chef::Provider::Deploy::Timestamped ],
+ deploy_revision: [ Chef::Resource::DeployRevision, Chef::Provider::Deploy::Revision ],
+ directory: [ Chef::Resource::Directory, Chef::Provider::Directory ],
+ easy_install_package: [ Chef::Resource::EasyInstallPackage, Chef::Provider::Package::EasyInstall ],
+ erl_call: [ Chef::Resource::ErlCall, Chef::Provider::ErlCall ],
+ execute: [ Chef::Resource::Execute, Chef::Provider::Execute ],
+ file: [ Chef::Resource::File, Chef::Provider::File ],
+ gem_package: [ Chef::Resource::GemPackage, Chef::Provider::Package::Rubygems ],
+ git: [ Chef::Resource::Git, Chef::Provider::Git ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ],
+ homebrew_package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ],
+ http_request: [ Chef::Resource::HttpRequest, Chef::Provider::HttpRequest ],
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+ link: [ Chef::Resource::Link, Chef::Provider::Link ],
+ log: [ Chef::Resource::Log, Chef::Provider::Log::ChefLog ],
+ macports_package: [ Chef::Resource::MacportsPackage, Chef::Provider::Package::Macports ],
+ mdadm: [ Chef::Resource::Mdadm, Chef::Provider::Mdadm ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Mount ],
+ perl: [ Chef::Resource::Perl, Chef::Provider::Script ],
+ portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ],
+ python: [ Chef::Resource::Python, Chef::Provider::Script ],
+ remote_directory: [ Chef::Resource::RemoteDirectory, Chef::Provider::RemoteDirectory ],
+ route: [ Chef::Resource::Route, Chef::Provider::Route ],
+ ruby: [ Chef::Resource::Ruby, Chef::Provider::Script ],
+ ruby_block: [ Chef::Resource::RubyBlock, Chef::Provider::RubyBlock ],
+ script: [ Chef::Resource::Script, Chef::Provider::Script ],
+ subversion: [ Chef::Resource::Subversion, Chef::Provider::Subversion ],
+ template: [ Chef::Resource::Template, Chef::Provider::Template ],
+ timestamped_deploy: [ Chef::Resource::TimestampedDeploy, Chef::Provider::Deploy::Timestamped ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Useradd ],
+ whyrun_safe_ruby_block: [ Chef::Resource::WhyrunSafeRubyBlock, Chef::Provider::WhyrunSafeRubyBlock ],
# We want to check that these are unsupported:
apt_package: nil,
bff_package: nil,
- dsc_script: nil,
dpkg_package: nil,
+ dsc_script: nil,
ips_package: nil,
pacman_package: nil,
paludis_package: nil,
@@ -508,61 +539,62 @@ describe Chef::ProviderResolver do
windows_service: nil,
"linux" => {
- apt_package: Chef::Provider::Package::Apt,
- dpkg_package: Chef::Provider::Package::Dpkg,
- pacman_package: Chef::Provider::Package::Pacman,
- paludis_package: Chef::Provider::Package::Paludis,
- rpm_package: Chef::Provider::Package::Rpm,
- yum_package: Chef::Provider::Package::Yum,
+ apt_package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ],
+ dpkg_package: [ Chef::Resource::DpkgPackage, Chef::Provider::Package::Dpkg ],
+ pacman_package: [ Chef::Resource::PacmanPackage, Chef::Provider::Package::Pacman ],
+ paludis_package: [ Chef::Resource::PaludisPackage, Chef::Provider::Package::Paludis ],
+ rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ],
+ yum_package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ],
"debian" => {
- ifconfig: Chef::Provider::Ifconfig::Debian,
- package: Chef::Provider::Package::Apt,
-# service: Chef::Provider::Service::Debian,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Debian ],
+ package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ],
+# service: [ Chef::Resource::DebianService, Chef::Provider::Service::Debian ],
"debian" => {
"7.0" => {
},
"6.0" => {
- ifconfig: Chef::Provider::Ifconfig,
-# service: Chef::Provider::Service::Insserv,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+# service: [ Chef::Resource::InsservService, Chef::Provider::Service::Insserv ],
},
"5.0" => {
- ifconfig: Chef::Provider::Ifconfig,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
},
},
"gcel" => {
"3.1.4" => {
- ifconfig: Chef::Provider::Ifconfig,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
},
},
"linaro" => {
"3.1.4" => {
- ifconfig: Chef::Provider::Ifconfig,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
},
},
"linuxmint" => {
"3.1.4" => {
- ifconfig: Chef::Provider::Ifconfig,
-# service: Chef::Provider::Service::Upstart,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+# service: [ Chef::Resource::UpstartService, Chef::Provider::Service::Upstart ],
},
},
"raspbian" => {
"3.1.4" => {
- ifconfig: Chef::Provider::Ifconfig,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
},
},
"ubuntu" => {
"11.10" => {
},
"10.04" => {
- ifconfig: Chef::Provider::Ifconfig,
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
},
},
},
"arch" => {
- package: Chef::Provider::Package::Pacman,
+ # TODO should be Chef::Resource::PacmanPackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Pacman ],
"arch" => {
"3.1.4" => {
@@ -571,8 +603,8 @@ describe Chef::ProviderResolver do
},
"freebsd" => {
- group: Chef::Provider::Group::Pw,
- user: Chef::Provider::User::Pw,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Pw ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Pw ],
"freebsd" => {
"3.1.4" => {
@@ -580,30 +612,31 @@ describe Chef::ProviderResolver do
},
},
"suse" => {
- group: Chef::Provider::Group::Gpasswd,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ],
"suse" => {
"12.0" => {
},
%w(11.1 11.2 11.3) => {
- group: Chef::Provider::Group::Suse,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ],
},
},
"opensuse" => {
-# service: Chef::Provider::Service::Redhat,
- package: Chef::Provider::Package::Zypper,
- group: Chef::Provider::Group::Usermod,
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+ package: [ Chef::Resource::ZypperPackage, Chef::Provider::Package::Zypper ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
"12.3" => {
},
"12.2" => {
- group: Chef::Provider::Group::Suse,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ],
},
},
},
"gentoo" => {
- package: Chef::Provider::Package::Portage,
- portage_package: Chef::Provider::Package::Portage,
-# service: Chef::Provider::Service::Gentoo,
+ # TODO should be Chef::Resource::PortagePackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Portage ],
+ portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ],
+# service: [ Chef::Resource::GentooService, Chef::Provider::Service::Gentoo ],
"gentoo" => {
"3.1.4" => {
@@ -612,27 +645,27 @@ describe Chef::ProviderResolver do
},
"rhel" => {
-# service: Chef::Provider::Service::Systemd,
- package: Chef::Provider::Package::Yum,
- ifconfig: Chef::Provider::Ifconfig::Redhat,
+# service: [ Chef::Resource::SystemdService, Chef::Provider::Service::Systemd ],
+ package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ],
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Redhat ],
%w(amazon xcp xenserver ibm_powerkvm cloudlinux parallels) => {
"3.1.4" => {
-# service: Chef::Provider::Service::Redhat,
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
},
},
%w(redhat centos scientific oracle) => {
"7.0" => {
},
"6.0" => {
-# service: Chef::Provider::Service::Redhat,
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
},
},
"fedora" => {
"15.0" => {
},
"14.0" => {
-# service: Chef::Provider::Service::Redhat,
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
},
},
},
@@ -641,9 +674,9 @@ describe Chef::ProviderResolver do
"darwin" => {
%w(mac_os_x mac_os_x_server) => {
- group: Chef::Provider::Group::Dscl,
- package: Chef::Provider::Package::Homebrew,
- user: Chef::Provider::User::Dscl,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Dscl ],
+ package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Dscl ],
"mac_os_x" => {
"10.9.2" => {
@@ -653,17 +686,17 @@ describe Chef::ProviderResolver do
},
"windows" => {
- batch: Chef::Provider::Batch,
- dsc_script: Chef::Provider::DscScript,
- env: Chef::Provider::Env::Windows,
- group: Chef::Provider::Group::Windows,
- mount: Chef::Provider::Mount::Windows,
- package: Chef::Provider::Package::Windows,
- powershell_script: Chef::Provider::PowershellScript,
- service: Chef::Provider::Service::Windows,
- user: Chef::Provider::User::Windows,
- windows_package: Chef::Provider::Package::Windows,
- windows_service: Chef::Provider::Service::Windows,
+ batch: [ Chef::Resource::Batch, Chef::Provider::Batch ],
+ dsc_script: [ Chef::Resource::DscScript, Chef::Provider::DscScript ],
+ env: [ Chef::Resource::Env, Chef::Provider::Env::Windows ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Windows ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Windows ],
+ package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ],
+ powershell_script: [ Chef::Resource::PowershellScript, Chef::Provider::PowershellScript ],
+ service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Windows ],
+ windows_package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ],
+ windows_service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ],
"windows" => {
%w(mswin mingw32 windows) => {
@@ -674,15 +707,16 @@ describe Chef::ProviderResolver do
},
"aix" => {
- bff_package: Chef::Provider::Package::Aix,
- cron: Chef::Provider::Cron::Aix,
- group: Chef::Provider::Group::Aix,
- ifconfig: Chef::Provider::Ifconfig::Aix,
- mount: Chef::Provider::Mount::Aix,
- package: Chef::Provider::Package::Aix,
- rpm_package: Chef::Provider::Package::Rpm,
- user: Chef::Provider::User::Aix,
-# service: Chef::Provider::Service::Aix,
+ bff_package: [ Chef::Resource::BffPackage, Chef::Provider::Package::Aix ],
+ cron: [ Chef::Resource::Cron, Chef::Provider::Cron::Aix ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Aix ],
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Aix ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Aix ],
+ # TODO should be Chef::Resource::BffPackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Aix ],
+ rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Aix ],
+# service: [ Chef::Resource::AixService, Chef::Provider::Service::Aix ],
"aix" => {
"aix" => {
@@ -696,7 +730,7 @@ describe Chef::ProviderResolver do
"hpux" => {
"hpux" => {
"3.1.4" => {
- group: Chef::Provider::Group::Usermod
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ]
}
}
}
@@ -706,15 +740,15 @@ describe Chef::ProviderResolver do
"netbsd" => {
"netbsd" => {
"3.1.4" => {
- group: Chef::Provider::Group::Groupmod,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Groupmod ],
},
},
},
},
"openbsd" => {
- group: Chef::Provider::Group::Usermod,
- package: Chef::Provider::Package::Openbsd,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+ package: [ Chef::Resource::OpenbsdPackage, Chef::Provider::Package::Openbsd ],
"openbsd" => {
"openbsd" => {
@@ -725,15 +759,15 @@ describe Chef::ProviderResolver do
},
"solaris2" => {
- group: Chef::Provider::Group::Usermod,
- ips_package: Chef::Provider::Package::Ips,
- package: Chef::Provider::Package::Ips,
- mount: Chef::Provider::Mount::Solaris,
- solaris_package: Chef::Provider::Package::Solaris,
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+ ips_package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ],
+ package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Solaris ],
+ solaris_package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ],
"smartos" => {
- smartos_package: Chef::Provider::Package::SmartOS,
- package: Chef::Provider::Package::SmartOS,
+ smartos_package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ],
+ package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ],
"smartos" => {
"3.1.4" => {
@@ -744,12 +778,11 @@ describe Chef::ProviderResolver do
"solaris2" => {
"nexentacore" => {
"3.1.4" => {
- package: Chef::Provider::Package::Solaris,
},
},
"omnios" => {
"3.1.4" => {
- user: Chef::Provider::User::Solaris,
+ user: [ Chef::Resource::User, Chef::Provider::User::Solaris ],
}
},
"openindiana" => {
@@ -761,11 +794,11 @@ describe Chef::ProviderResolver do
},
},
"solaris2" => {
- user: Chef::Provider::User::Solaris,
+ user: [ Chef::Resource::User, Chef::Provider::User::Solaris ],
"5.11" => {
+ package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ],
},
"5.9" => {
- package: Chef::Provider::Package::Solaris,
},
},
},
@@ -785,7 +818,8 @@ describe Chef::ProviderResolver do
"exherbo" => {
"exherbo" => {
"3.1.4" => {
- package: Chef::Provider::Package::Paludis
+ # TODO should be Chef::Resource::PaludisPackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Paludis ]
}
}
}
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
index d7a34bc21b..97b88b1732 100644
--- a/spec/unit/provider_spec.rb
+++ b/spec/unit/provider_spec.rb
@@ -114,9 +114,7 @@ describe Chef::Provider do
end
it "does not re-load recipes when creating the temporary run context" do
- # we actually want to test that RunContext#load is never called, but we
- # can't stub all instances of an object with rspec's mocks. :/
- allow(Chef::RunContext).to receive(:new).and_raise("not supposed to happen")
+ expect_any_instance_of(Chef::RunContext).not_to receive(:load)
snitch = Proc.new {temporary_collection = @run_context.resource_collection}
@provider.send(:recipe_eval, &snitch)
end
diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb
index ee98e63c1f..ea3ab44c16 100644
--- a/spec/unit/recipe_spec.rb
+++ b/spec/unit/recipe_spec.rb
@@ -121,6 +121,7 @@ describe Chef::Recipe do
it "locate resource for particular platform" do
ShaunTheSheep = Class.new(Chef::Resource)
+ ShaunTheSheep.resource_name :shaun_the_sheep
ShaunTheSheep.provides :laughter, :platform => ["television"]
node.automatic[:platform] = "television"
node.automatic[:platform_version] = "123"
@@ -131,6 +132,7 @@ describe Chef::Recipe do
it "locate a resource for all platforms" do
YourMom = Class.new(Chef::Resource)
+ YourMom.resource_name :your_mom
YourMom.provides :love_and_caring
res = recipe.love_and_caring "mommy"
expect(res.name).to eql("mommy")
@@ -141,7 +143,9 @@ describe Chef::Recipe do
before do
node.automatic[:platform] = "nbc_sports"
Sounders = Class.new(Chef::Resource)
+ Sounders.resource_name :sounders
TottenhamHotspur = Class.new(Chef::Resource)
+ TottenhamHotspur.resource_name :tottenham_hotspur
end
after do
@@ -149,24 +153,20 @@ describe Chef::Recipe do
Object.send(:remove_const, :TottenhamHotspur)
end
- it "selects one if it is the last declared" do
- expect(Chef::Log).not_to receive(:warn)
-
+ it "selects the first one alphabetically" do
Sounders.provides :football, platform: "nbc_sports"
TottenhamHotspur.provides :football, platform: "nbc_sports"
res1 = recipe.football "club world cup"
expect(res1.name).to eql("club world cup")
- expect(res1).to be_a_kind_of(TottenhamHotspur)
+ expect(res1).to be_a_kind_of(Sounders)
end
- it "selects the other one if it is given priority" do
- expect(Chef::Log).not_to receive(:warn)
-
- TottenhamHotspur.provides :football, platform: "nbc_sports"
- Sounders.provides :football, platform: "nbc_sports"
+ it "selects the first one alphabetically even if the declaration order is reversed" do
+ TottenhamHotspur.provides :football2, platform: "nbc_sports"
+ Sounders.provides :football2, platform: "nbc_sports"
- res1 = recipe.football "club world cup"
+ res1 = recipe.football2 "club world cup"
expect(res1.name).to eql("club world cup")
expect(res1).to be_a_kind_of(Sounders)
end
@@ -405,8 +405,8 @@ describe Chef::Recipe do
end
it "does not copy the action from the first resource" do
- expect(original_resource.action).to eq(:score)
- expect(duplicated_resource.action).to eq(:nothing)
+ expect(original_resource.action).to eq([:score])
+ expect(duplicated_resource.action).to eq([:nothing])
end
it "does not copy the source location of the first resource" do
@@ -501,7 +501,7 @@ describe Chef::Recipe do
recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb"))
res = recipe.resources(:file => "/etc/nsswitch.conf")
expect(res.name).to eql("/etc/nsswitch.conf")
- expect(res.action).to eql(:create)
+ expect(res.action).to eql([:create])
expect(res.owner).to eql("root")
expect(res.group).to eql("root")
expect(res.mode).to eql(0644)
diff --git a/spec/unit/registry_helper_spec.rb b/spec/unit/registry_helper_spec.rb
index 036a0834db..b2d0b7b125 100644
--- a/spec/unit/registry_helper_spec.rb
+++ b/spec/unit/registry_helper_spec.rb
@@ -21,6 +21,7 @@ require 'spec_helper'
describe Chef::Provider::RegistryKey do
let(:value1) { { :name => "one", :type => :string, :data => "1" } }
+ let(:value1_upcase_name) { {:name => "ONE", :type => :string, :data => "1"} }
let(:key_path) { 'HKCU\Software\OpscodeNumbers' }
let(:key) { 'Software\OpscodeNumbers' }
let(:key_parent) { 'Software' }
@@ -71,7 +72,20 @@ describe Chef::Provider::RegistryKey do
expect(@registry).to receive(:data_exists?).with(key_path, value1).and_return(true)
@registry.set_value(key_path, value1)
end
-
+ it "does nothing if case insensitive key and hive and value exist" do
+ expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true)
+ expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key])
+ expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1).and_return(true)
+ expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1).and_return(true)
+ @registry.set_value(key_path.downcase, value1)
+ end
+ it "does nothing if key and hive and value with a case insensitive name exist" do
+ expect(@registry).to receive(:key_exists!).with(key_path.downcase).and_return(true)
+ expect(@registry).to receive(:get_hive_and_key).with(key_path.downcase).and_return([@hive_mock, key])
+ expect(@registry).to receive(:value_exists?).with(key_path.downcase, value1_upcase_name).and_return(true)
+ expect(@registry).to receive(:data_exists?).with(key_path.downcase, value1_upcase_name).and_return(true)
+ @registry.set_value(key_path.downcase, value1_upcase_name)
+ end
it "updates value if key and hive and value exist, but data is different" do
expect(@registry).to receive(:key_exists!).with(key_path).and_return(true)
expect(@registry).to receive(:get_hive_and_key).with(key_path).and_return([@hive_mock, key])
diff --git a/spec/unit/resource/breakpoint_spec.rb b/spec/unit/resource/breakpoint_spec.rb
index 9c867ebcc7..88ab34d568 100644
--- a/spec/unit/resource/breakpoint_spec.rb
+++ b/spec/unit/resource/breakpoint_spec.rb
@@ -37,7 +37,7 @@ describe Chef::Resource::Breakpoint do
end
it "defaults to the break action" do
- expect(@breakpoint.action).to eq(:break)
+ expect(@breakpoint.action).to eq([:break])
end
it "names itself after the line number of the file where it's created" do
diff --git a/spec/unit/resource/cron_spec.rb b/spec/unit/resource/cron_spec.rb
index 743552c1de..0978be6930 100644
--- a/spec/unit/resource/cron_spec.rb
+++ b/spec/unit/resource/cron_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Cron do
end
it "should have a default action of 'create'" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
it "should accept create or delete for action" do
diff --git a/spec/unit/resource/deploy_spec.rb b/spec/unit/resource/deploy_spec.rb
index 0403a7ba6b..5b6a452784 100644
--- a/spec/unit/resource/deploy_spec.rb
+++ b/spec/unit/resource/deploy_spec.rb
@@ -148,10 +148,16 @@ describe Chef::Resource::Deploy do
expect(@resource.current_path).to eql("/my/deploy/dir/current")
end
+ it "allows depth to be set via integer" do
+ expect(@resource.depth).to be_nil
+ @resource.depth 1
+ expect(@resource.depth).to eql(1)
+ end
+
it "gives #depth as 5 if shallow clone is true, nil otherwise" do
expect(@resource.depth).to be_nil
@resource.shallow_clone true
- expect(@resource.depth).to eql("5")
+ expect(@resource.depth).to eql(5)
end
it "aliases repo as repository" do
diff --git a/spec/unit/resource/directory_spec.rb b/spec/unit/resource/directory_spec.rb
index c452b2a914..e9e80806db 100644
--- a/spec/unit/resource/directory_spec.rb
+++ b/spec/unit/resource/directory_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Directory do
end
it "should have a default action of 'create'" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
it "should accept create or delete for action" do
diff --git a/spec/unit/resource/dsc_resource_spec.rb b/spec/unit/resource/dsc_resource_spec.rb
index ae15f56eaf..06769d86ce 100644
--- a/spec/unit/resource/dsc_resource_spec.rb
+++ b/spec/unit/resource/dsc_resource_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::DscResource do
}
it "has a default action of `:run`" do
- expect(dsc_test_resource.action).to eq(:run)
+ expect(dsc_test_resource.action).to eq([:run])
end
it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do
diff --git a/spec/unit/resource/dsc_script_spec.rb b/spec/unit/resource/dsc_script_spec.rb
index 71103ea590..1fa865a2d5 100644
--- a/spec/unit/resource/dsc_script_spec.rb
+++ b/spec/unit/resource/dsc_script_spec.rb
@@ -29,7 +29,7 @@ describe Chef::Resource::DscScript do
Chef::RunContext.new(node, {}, empty_events)
}
let(:dsc_test_resource) {
- Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
+ Chef::Resource::DscScript.new(dsc_test_resource_name, dsc_test_run_context)
}
let(:configuration_code) {'echo "This is supposed to create a configuration document."'}
let(:configuration_path) {'c:/myconfigs/formatc.ps1'}
@@ -38,7 +38,7 @@ describe Chef::Resource::DscScript do
let(:configuration_data_script) { 'c:/myconfigs/data/safedata.psd1' }
it "has a default action of `:run`" do
- expect(dsc_test_resource.action).to eq(:run)
+ expect(dsc_test_resource.action).to eq([:run])
end
it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do
@@ -70,6 +70,10 @@ describe Chef::Resource::DscScript do
expect(dsc_test_resource.configuration_data_script).to eq(configuration_data_script)
end
+ it "has the ps_credential helper method" do
+ expect(dsc_test_resource).to respond_to(:ps_credential)
+ end
+
context "when calling imports" do
let(:module_name) { 'FooModule' }
let(:module_name_b) { 'BarModule' }
diff --git a/spec/unit/resource/env_spec.rb b/spec/unit/resource/env_spec.rb
index 566827a27e..9bee07c593 100644
--- a/spec/unit/resource/env_spec.rb
+++ b/spec/unit/resource/env_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Env do
end
it "should have a default action of 'create'" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
{ :create => false, :delete => false, :modify => false, :flibber => true }.each do |action,bad_value|
diff --git a/spec/unit/resource/erl_call_spec.rb b/spec/unit/resource/erl_call_spec.rb
index 008d27372a..9abf2e7812 100644
--- a/spec/unit/resource/erl_call_spec.rb
+++ b/spec/unit/resource/erl_call_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::ErlCall do
end
it "should have a default action of run" do
- expect(@resource.action).to eql(:run)
+ expect(@resource.action).to eql([:run])
end
it "should accept run as an action" do
diff --git a/spec/unit/resource/file/verification_spec.rb b/spec/unit/resource/file/verification_spec.rb
index 3609d9d482..6b929789c8 100644
--- a/spec/unit/resource/file/verification_spec.rb
+++ b/spec/unit/resource/file/verification_spec.rb
@@ -69,12 +69,40 @@ describe Chef::Resource::File::Verification do
end
context "with a verification command(String)" do
+ before(:each) do
+ allow(Chef::Log).to receive(:deprecation).and_return(nil)
+ end
+
+ def platform_specific_verify_command(variable_name)
+ if windows?
+ "if \"#{temp_path}\" == \"%{#{variable_name}}\" (exit 0) else (exit 1)"
+ else
+ "test #{temp_path} = %{#{variable_name}}"
+ end
+ end
+
it "substitutes \%{file} with the path" do
- test_command = if windows?
- "if \"#{temp_path}\" == \"%{file}\" (exit 0) else (exit 1)"
- else
- "test #{temp_path} = %{file}"
- end
+ test_command = platform_specific_verify_command('file')
+ v = Chef::Resource::File::Verification.new(parent_resource, test_command, {})
+ expect(v.verify(temp_path)).to eq(true)
+ end
+
+ it "warns about deprecation when \%{file} is used" do
+ expect(Chef::Log).to receive(:deprecation).with(/%{file} is deprecated/, /verification_spec\.rb/)
+ test_command = platform_specific_verify_command('file')
+ Chef::Resource::File::Verification.new(parent_resource, test_command, {})
+ .verify(temp_path)
+ end
+
+ it "does not warn about deprecation when \%{file} is not used" do
+ expect(Chef::Log).to_not receive(:deprecation)
+ test_command = platform_specific_verify_command('path')
+ Chef::Resource::File::Verification.new(parent_resource, test_command, {})
+ .verify(temp_path)
+ end
+
+ it "substitutes \%{path} with the path" do
+ test_command = platform_specific_verify_command('path')
v = Chef::Resource::File::Verification.new(parent_resource, test_command, {})
expect(v.verify(temp_path)).to eq(true)
end
diff --git a/spec/unit/resource/file_spec.rb b/spec/unit/resource/file_spec.rb
index dd20f5f03a..76beaf15e1 100644
--- a/spec/unit/resource/file_spec.rb
+++ b/spec/unit/resource/file_spec.rb
@@ -29,7 +29,7 @@ describe Chef::Resource::File do
end
it "should have a default action of 'create'" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
it "should have a default content of nil" do
diff --git a/spec/unit/resource/group_spec.rb b/spec/unit/resource/group_spec.rb
index bcf9205f7e..a4029fc911 100644
--- a/spec/unit/resource/group_spec.rb
+++ b/spec/unit/resource/group_spec.rb
@@ -50,7 +50,7 @@ describe Chef::Resource::Group, "initialize" do
end
it "should set action to :create" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
%w{create remove modify manage}.each do |action|
diff --git a/spec/unit/resource/link_spec.rb b/spec/unit/resource/link_spec.rb
index 51221e0472..0246fcd13b 100644
--- a/spec/unit/resource/link_spec.rb
+++ b/spec/unit/resource/link_spec.rb
@@ -36,7 +36,7 @@ describe Chef::Resource::Link do
end
it "should have a default action of 'create'" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
{ :create => false, :delete => false, :blues => true }.each do |action,bad_value|
diff --git a/spec/unit/resource/mdadm_spec.rb b/spec/unit/resource/mdadm_spec.rb
index 866309ec5b..6ca99c58e5 100644
--- a/spec/unit/resource/mdadm_spec.rb
+++ b/spec/unit/resource/mdadm_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Mdadm do
end
it "should have a default action of create" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
it "should accept create, assemble, stop as actions" do
diff --git a/spec/unit/resource/mount_spec.rb b/spec/unit/resource/mount_spec.rb
index ad95c06e04..acce26dcab 100644
--- a/spec/unit/resource/mount_spec.rb
+++ b/spec/unit/resource/mount_spec.rb
@@ -38,7 +38,7 @@ describe Chef::Resource::Mount do
end
it "should have a default action of mount" do
- expect(@resource.action).to eql(:mount)
+ expect(@resource.action).to eql([:mount])
end
it "should accept mount, umount and remount as actions" do
diff --git a/spec/unit/resource/ohai_spec.rb b/spec/unit/resource/ohai_spec.rb
index fe29755abf..3bc21a41d2 100644
--- a/spec/unit/resource/ohai_spec.rb
+++ b/spec/unit/resource/ohai_spec.rb
@@ -34,7 +34,7 @@ describe Chef::Resource::Ohai do
end
it "should have a default action of create" do
- expect(@resource.action).to eql(:reload)
+ expect(@resource.action).to eql([:reload])
end
it "should allow you to set the plugin attribute" do
diff --git a/spec/unit/resource/powershell_spec.rb b/spec/unit/resource/powershell_script_spec.rb
index 2505c4a3d7..2505c4a3d7 100644
--- a/spec/unit/resource/powershell_spec.rb
+++ b/spec/unit/resource/powershell_script_spec.rb
diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb
index e2a864d73a..2e2811d026 100644
--- a/spec/unit/resource/registry_key_spec.rb
+++ b/spec/unit/resource/registry_key_spec.rb
@@ -45,7 +45,7 @@ describe Chef::Resource::RegistryKey, "initialize" do
end
it "should set action to :create" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
%w{create create_if_missing delete delete_key}.each do |action|
diff --git a/spec/unit/resource/route_spec.rb b/spec/unit/resource/route_spec.rb
index ffb9304511..ec1d369932 100644
--- a/spec/unit/resource/route_spec.rb
+++ b/spec/unit/resource/route_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Resource::Route do
end
it "should have a default action of 'add'" do
- expect(@resource.action).to eql(:add)
+ expect(@resource.action).to eql([:add])
end
it "should accept add or delete for action" do
diff --git a/spec/unit/resource/ruby_block_spec.rb b/spec/unit/resource/ruby_block_spec.rb
index 5d83f7e367..8664564ac5 100644
--- a/spec/unit/resource/ruby_block_spec.rb
+++ b/spec/unit/resource/ruby_block_spec.rb
@@ -30,8 +30,8 @@ describe Chef::Resource::RubyBlock do
expect(@resource).to be_a_kind_of(Chef::Resource::RubyBlock)
end
- it "should have a default action of 'create'" do
- expect(@resource.action).to eql(:run)
+ it "should have a default action of 'run'" do
+ expect(@resource.action).to eql([:run])
end
it "should have a resource name of :ruby_block" do
@@ -46,7 +46,7 @@ describe Chef::Resource::RubyBlock do
it "allows the action to be 'create'" do
@resource.action :create
- expect(@resource.action).to eq(:create)
+ expect(@resource.action).to eq([:create])
end
describe "when it has been initialized with block code" do
diff --git a/spec/unit/resource/service_spec.rb b/spec/unit/resource/service_spec.rb
index eb6f444e93..b9e3757255 100644
--- a/spec/unit/resource/service_spec.rb
+++ b/spec/unit/resource/service_spec.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@hjksolutions.com>)
# Author:: Tyler Cloke (<tyler@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -139,14 +139,14 @@ describe Chef::Resource::Service do
expect { @resource.send(attrib, "poop") }.to raise_error(ArgumentError)
end
- it "should default all the feature support to false" do
- support_hash = { :status => false, :restart => false, :reload=> false }
+ it "should default all the feature support to nil" do
+ support_hash = { :status => nil, :restart => nil, :reload=> nil }
expect(@resource.supports).to eq(support_hash)
end
it "should allow you to set what features this resource supports as a array" do
support_array = [ :status, :restart ]
- support_hash = { :status => true, :restart => true, :reload => false }
+ support_hash = { :status => true, :restart => true, :reload => nil }
@resource.supports(support_array)
expect(@resource.supports).to eq(support_hash)
end
diff --git a/spec/unit/resource/user_spec.rb b/spec/unit/resource/user_spec.rb
index f05de94fe0..3bf7e6187b 100644
--- a/spec/unit/resource/user_spec.rb
+++ b/spec/unit/resource/user_spec.rb
@@ -43,7 +43,7 @@ describe Chef::Resource::User, "initialize" do
end
it "should set action to :create" do
- expect(@resource.action).to eql(:create)
+ expect(@resource.action).to eql([:create])
end
it "should set supports[:manage_home] to false" do
diff --git a/spec/unit/resource/windows_service_spec.rb b/spec/unit/resource/windows_service_spec.rb
index 8866cad1bf..45a295c24e 100644
--- a/spec/unit/resource/windows_service_spec.rb
+++ b/spec/unit/resource/windows_service_spec.rb
@@ -44,6 +44,6 @@ describe Chef::Resource::WindowsService, "initialize" do
it "allows the action to be 'configure_startup'" do
resource.action :configure_startup
- expect(resource.action).to eq(:configure_startup)
+ expect(resource.action).to eq([:configure_startup])
end
end
diff --git a/spec/unit/resource/yum_package_spec.rb b/spec/unit/resource/yum_package_spec.rb
index e01b87c580..f24f1e3405 100644
--- a/spec/unit/resource/yum_package_spec.rb
+++ b/spec/unit/resource/yum_package_spec.rb
@@ -1,6 +1,6 @@
#
# Author:: AJ Christensen (<aj@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -78,3 +78,12 @@ describe Chef::Resource::YumPackage, "allow_downgrade" do
expect { @resource.allow_downgrade "monkey" }.to raise_error(ArgumentError)
end
end
+
+describe Chef::Resource::YumPackage, "yum_binary" do
+ let(:resource) { Chef::Resource::YumPackage.new("foo") }
+
+ it "should allow you to specify the yum_binary" do
+ resource.yum_binary "/usr/bin/yum-something"
+ expect(resource.yum_binary).to eql("/usr/bin/yum-something")
+ end
+end
diff --git a/spec/unit/resource_collection_spec.rb b/spec/unit/resource_collection_spec.rb
index b43b012dfc..d52e7e2c26 100644
--- a/spec/unit/resource_collection_spec.rb
+++ b/spec/unit/resource_collection_spec.rb
@@ -252,7 +252,7 @@ describe Chef::ResourceCollection do
expect(json).to match(/instance_vars/)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { rc }
end
end
diff --git a/spec/unit/resource_resolver_spec.rb b/spec/unit/resource_resolver_spec.rb
new file mode 100644
index 0000000000..b3bda9d945
--- /dev/null
+++ b/spec/unit/resource_resolver_spec.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Ranjib Dey
+# Copyright:: Copyright (c) 2015 Ranjib Dey <ranjib@linux.com>.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/resource_resolver'
+
+
+describe Chef::ResourceResolver do
+ it '#resolve' do
+ expect(described_class.resolve(:execute)).to eq(Chef::Resource::Execute)
+ end
+
+ it '#list' do
+ expect(described_class.list(:package)).to_not be_empty
+ end
+
+ context 'instance methods' do
+ let(:resolver) do
+ described_class.new(Chef::Node.new, 'execute')
+ end
+
+ it '#resolve' do
+ expect(resolver.resolve).to eq Chef::Resource::Execute
+ end
+
+ it '#list' do
+ expect(resolver.list).to eq [ Chef::Resource::Execute ]
+ end
+
+ it '#provided_by? returns true when resource class is in the list' do
+ expect(resolver.provided_by?(Chef::Resource::Execute)).to be_truthy
+ end
+
+ it '#provided_by? returns false when resource class is not in the list' do
+ expect(resolver.provided_by?(Chef::Resource::File)).to be_falsey
+ end
+ end
+end
diff --git a/spec/unit/resource_spec.rb b/spec/unit/resource_spec.rb
index 8ba45d9350..b9ba80068b 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -59,8 +59,8 @@ describe Chef::Resource do
end
describe "when declaring the identity attribute" do
- it "has no identity attribute by default" do
- expect(Chef::Resource.identity_attr).to be_nil
+ it "has :name as identity attribute by default" do
+ expect(Chef::Resource.identity_attr).to eq(:name)
end
it "sets an identity attribute" do
@@ -344,6 +344,7 @@ describe Chef::Resource do
expect(r.resource_name).to be_nil
expect(r.declared_type).to eq :d
end
+
it "and there are no provides lines, @resource_name is used" do
c = Class.new(Chef::Resource) do
def initialize(*args, &block)
@@ -358,6 +359,20 @@ describe Chef::Resource do
expect(r.resource_name).to eq :blah
expect(r.declared_type).to eq :d
end
+
+ it "and the resource class gets a late-bound name, resource_name is nil" do
+ c = Class.new(Chef::Resource) do
+ def self.name
+ "ResourceSpecNameTest"
+ end
+ end
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to be_nil
+ expect(r.resource_name).to be_nil
+ expect(r.declared_type).to eq :d
+ end
end
it "resource_name without provides is honored" do
@@ -416,7 +431,7 @@ describe Chef::Resource do
expect(json).to match(/instance_vars/)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @resource }
end
end
@@ -795,21 +810,21 @@ describe Chef::Resource do
end
it 'adds mappings for a single platform' do
- expect(Chef).to receive(:set_resource_priority_array).with(
+ expect(Chef.resource_handler_map).to receive(:set).with(
:dinobot, Chef::Resource::Klz, { platform: ['autobots'] }
)
klz.provides :dinobot, platform: ['autobots']
end
it 'adds mappings for multiple platforms' do
- expect(Chef).to receive(:set_resource_priority_array).with(
+ expect(Chef.resource_handler_map).to receive(:set).with(
:energy, Chef::Resource::Klz, { platform: ['autobots', 'decepticons']}
)
klz.provides :energy, platform: ['autobots', 'decepticons']
end
it 'adds mappings for all platforms' do
- expect(Chef).to receive(:set_resource_priority_array).with(
+ expect(Chef.resource_handler_map).to receive(:set).with(
:tape_deck, Chef::Resource::Klz, {}
)
klz.provides :tape_deck
@@ -962,4 +977,90 @@ describe Chef::Resource do
end
end
+
+ describe "#action" do
+ let(:resource_class) do
+ Class.new(described_class) do
+ allowed_actions(%i{one two})
+ end
+ end
+ let(:resource) { resource_class.new('test', nil) }
+ subject { resource.action }
+
+ context "with a no action" do
+ it { is_expected.to eq [:nothing] }
+ end
+
+ context "with a default action" do
+ let(:resource_class) do
+ Class.new(described_class) do
+ default_action(:one)
+ end
+ end
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with a symbol action" do
+ before { resource.action(:one) }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with a string action" do
+ before { resource.action('two') }
+ it { is_expected.to eq [:two] }
+ end
+
+ context "with an array action" do
+ before { resource.action([:two, :one]) }
+ it { is_expected.to eq [:two, :one] }
+ end
+
+ context "with an assignment" do
+ before { resource.action = :one }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with an array assignment" do
+ before { resource.action = [:two, :one] }
+ it { is_expected.to eq [:two, :one] }
+ end
+
+ context "with an invalid action" do
+ it { expect { resource.action(:three) }.to raise_error Chef::Exceptions::ValidationFailed }
+ end
+
+ context "with an invalid assignment action" do
+ it { expect { resource.action = :three }.to raise_error Chef::Exceptions::ValidationFailed }
+ end
+ end
+
+ describe ".default_action" do
+ let(:default_action) { }
+ let(:resource_class) do
+ actions = default_action
+ Class.new(described_class) do
+ default_action(actions) if actions
+ end
+ end
+ subject { resource_class.default_action }
+
+ context "with no default actions" do
+ it { is_expected.to eq [:nothing] }
+ end
+
+ context "with a symbol default action" do
+ let(:default_action) { :one }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with a string default action" do
+ let(:default_action) { 'one' }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with an array default action" do
+ let(:default_action) { [:two, :one] }
+ it { is_expected.to eq [:two, :one] }
+ end
+ end
end
diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb
index f120ca6da6..ecc7945a08 100644
--- a/spec/unit/role_spec.rb
+++ b/spec/unit/role_spec.rb
@@ -217,7 +217,7 @@ describe Chef::Role do
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @role }
end
end
diff --git a/spec/unit/run_context/child_run_context_spec.rb b/spec/unit/run_context/child_run_context_spec.rb
new file mode 100644
index 0000000000..63586e459c
--- /dev/null
+++ b/spec/unit/run_context/child_run_context_spec.rb
@@ -0,0 +1,133 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Tim Hinderliter (<tim@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'support/lib/library_load_order'
+
+describe Chef::RunContext::ChildRunContext do
+ context "with a run context with stuff in it" do
+ let(:chef_repo_path) { File.expand_path(File.join(CHEF_SPEC_DATA, "run_context", "cookbooks")) }
+ let(:cookbook_collection) {
+ cl = Chef::CookbookLoader.new(chef_repo_path)
+ cl.load_cookbooks
+ Chef::CookbookCollection.new(cl)
+ }
+ let(:node) {
+ node = Chef::Node.new
+ node.run_list << "test" << "test::one" << "test::two"
+ node
+ }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, cookbook_collection, events) }
+
+ context "and a child run context" do
+ let(:child) { run_context.create_child }
+
+ it "parent_run_context is set to the parent" do
+ expect(child.parent_run_context).to eq run_context
+ end
+
+ it "audits is not the same as the parent" do
+ expect(child.audits.object_id).not_to eq run_context.audits.object_id
+ child.audits['hi'] = 'lo'
+ expect(child.audits['hi']).to eq('lo')
+ expect(run_context.audits['hi']).not_to eq('lo')
+ end
+
+ it "resource_collection is not the same as the parent" do
+ expect(child.resource_collection.object_id).not_to eq run_context.resource_collection.object_id
+ f = Chef::Resource::File.new('hi', child)
+ child.resource_collection.insert(f)
+ expect(child.resource_collection).to include f
+ expect(run_context.resource_collection).not_to include f
+ end
+
+ it "immediate_notification_collection is not the same as the parent" do
+ expect(child.immediate_notification_collection.object_id).not_to eq run_context.immediate_notification_collection.object_id
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_immediately(notification)
+ expect(child.immediate_notification_collection['file[hi]']).to eq([notification])
+ expect(run_context.immediate_notification_collection['file[hi]']).not_to eq([notification])
+ end
+
+ it "immediate_notifications is not the same as the parent" do
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_immediately(notification)
+ expect(child.immediate_notifications(src)).to eq([notification])
+ expect(run_context.immediate_notifications(src)).not_to eq([notification])
+ end
+
+ it "delayed_notification_collection is not the same as the parent" do
+ expect(child.delayed_notification_collection.object_id).not_to eq run_context.delayed_notification_collection.object_id
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_delayed(notification)
+ expect(child.delayed_notification_collection['file[hi]']).to eq([notification])
+ expect(run_context.delayed_notification_collection['file[hi]']).not_to eq([notification])
+ end
+
+ it "delayed_notifications is not the same as the parent" do
+ src = Chef::Resource::File.new('hi', child)
+ dest = Chef::Resource::File.new('argh', child)
+ notification = Chef::Resource::Notification.new(dest, :create, src)
+ child.notifies_delayed(notification)
+ expect(child.delayed_notifications(src)).to eq([notification])
+ expect(run_context.delayed_notifications(src)).not_to eq([notification])
+ end
+
+ it "create_child creates a child-of-child" do
+ c = child.create_child
+ expect(c.parent_run_context).to eq child
+ end
+
+ context "after load('include::default')" do
+ before do
+ run_list = Chef::RunList.new('include::default').expand('_default')
+ # TODO not sure why we had to do this to get everything to work ...
+ node.automatic_attrs[:recipes] = []
+ child.load(run_list)
+ end
+
+ it "load_recipe loads into the child" do
+ expect(child.resource_collection).to be_empty
+ child.load_recipe("include::includee")
+ expect(child.resource_collection).not_to be_empty
+ end
+
+ it "include_recipe loads into the child" do
+ expect(child.resource_collection).to be_empty
+ child.include_recipe("include::includee")
+ expect(child.resource_collection).not_to be_empty
+ end
+
+ it "load_recipe_file loads into the child" do
+ expect(child.resource_collection).to be_empty
+ child.load_recipe_file(File.expand_path("include/recipes/includee.rb", chef_repo_path))
+ expect(child.resource_collection).not_to be_empty
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/run_context_spec.rb b/spec/unit/run_context_spec.rb
index e20ba63b72..99801575ef 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -68,6 +68,9 @@ describe Chef::RunContext do
"dependency2" => {
"version" => "0.0.0",
},
+ "include" => {
+ "version" => "0.0.0",
+ },
"no-default-attr" => {
"version" => "0.0.0",
},
@@ -84,6 +87,10 @@ describe Chef::RunContext do
)
end
+ it "has a nil parent_run_context" do
+ expect(run_context.parent_run_context).to be_nil
+ end
+
describe "loading cookbooks for a run list" do
before do
diff --git a/spec/unit/run_list/versioned_recipe_list_spec.rb b/spec/unit/run_list/versioned_recipe_list_spec.rb
index 209ac37fc1..9c3ecaa0dd 100644
--- a/spec/unit/run_list/versioned_recipe_list_spec.rb
+++ b/spec/unit/run_list/versioned_recipe_list_spec.rb
@@ -26,98 +26,165 @@ describe Chef::RunList::VersionedRecipeList do
end
end
+ let(:list) { described_class.new }
+
+ let(:versioned_recipes) { [] }
+
+ let(:recipes) { [] }
+
+ before do
+ recipes.each { |r| list << r }
+ versioned_recipes.each {|r| list.add_recipe r[:name], r[:version]}
+ end
+
describe "add_recipe" do
- before(:each) do
- @list = Chef::RunList::VersionedRecipeList.new
- @list << "apt"
- @list << "god"
- @list << "apache2"
- end
+
+ let(:recipes) { %w[ apt god apache2 ] }
it "should append the recipe to the end of the list" do
- @list.add_recipe "rails"
- expect(@list).to eq(["apt", "god", "apache2", "rails"])
+ list.add_recipe "rails"
+ expect(list).to eq(["apt", "god", "apache2", "rails"])
end
it "should not duplicate entries" do
- @list.add_recipe "apt"
- expect(@list).to eq(["apt", "god", "apache2"])
+ list.add_recipe "apt"
+ expect(list).to eq(["apt", "god", "apache2"])
end
it "should allow you to specify a version" do
- @list.add_recipe "rails", "1.0.0"
- expect(@list).to eq(["apt", "god", "apache2", "rails"])
- expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"})
+ list.add_recipe "rails", "1.0.0"
+ expect(list).to eq(["apt", "god", "apache2", "rails"])
+ expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"})
end
it "should allow you to specify a version for a recipe that already exists" do
- @list.add_recipe "apt", "1.2.3"
- expect(@list).to eq(["apt", "god", "apache2"])
- expect(@list.with_versions).to include({:name => "apt", :version => "1.2.3"})
+ list.add_recipe "apt", "1.2.3"
+ expect(list).to eq(["apt", "god", "apache2"])
+ expect(list.with_versions).to include({:name => "apt", :version => "1.2.3"})
end
it "should allow you to specify the same version of a recipe twice" do
- @list.add_recipe "rails", "1.0.0"
- @list.add_recipe "rails", "1.0.0"
- expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"})
+ list.add_recipe "rails", "1.0.0"
+ list.add_recipe "rails", "1.0.0"
+ expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"})
end
it "should allow you to spcify no version, even when a version already exists" do
- @list.add_recipe "rails", "1.0.0"
- @list.add_recipe "rails"
- expect(@list.with_versions).to include({:name => "rails", :version => "1.0.0"})
+ list.add_recipe "rails", "1.0.0"
+ list.add_recipe "rails"
+ expect(list.with_versions).to include({:name => "rails", :version => "1.0.0"})
end
it "should not allow multiple versions of the same recipe" do
- @list.add_recipe "rails", "1.0.0"
- expect {@list.add_recipe "rails", "0.1.0"}.to raise_error Chef::Exceptions::CookbookVersionConflict
+ list.add_recipe "rails", "1.0.0"
+ expect {list.add_recipe "rails", "0.1.0"}.to raise_error Chef::Exceptions::CookbookVersionConflict
end
end
describe "with_versions" do
- before(:each) do
- @recipes = [
+
+ let(:versioned_recipes) do
+ [
{:name => "apt", :version => "1.0.0"},
{:name => "god", :version => nil},
{:name => "apache2", :version => "0.0.1"}
]
- @list = Chef::RunList::VersionedRecipeList.new
- @recipes.each {|i| @list.add_recipe i[:name], i[:version]}
end
-
it "should return an array of hashes with :name and :version" do
- expect(@list.with_versions).to eq(@recipes)
+ expect(list.with_versions).to eq(versioned_recipes)
end
it "should retain the same order as the version-less list" do
- with_versions = @list.with_versions
- @list.each_with_index do |item, index|
+ with_versions = list.with_versions
+ list.each_with_index do |item, index|
expect(with_versions[index][:name]).to eq(item)
end
end
end
describe "with_version_constraints" do
- before(:each) do
- @recipes = [
- {:name => "apt", :version => "~> 1.2.0"},
- {:name => "god", :version => nil},
- {:name => "apache2", :version => "0.0.1"}
- ]
- @list = Chef::RunList::VersionedRecipeList.new
- @recipes.each {|i| @list.add_recipe i[:name], i[:version]}
- @constraints = @recipes.map do |x|
- { :name => x[:name],
- :version_constraint => Chef::VersionConstraint.new(x[:version])
- }
- end
+
+ let(:versioned_recipes) do
+ [
+ {:name => "apt", :version => "~> 1.2.0"},
+ {:name => "god", :version => nil},
+ {:name => "apache2", :version => "0.0.1"}
+ ]
end
+
it "should return an array of hashes with :name and :version_constraint" do
- @list.with_version_constraints.each do |x|
- expect(x).to have_key :name
- expect(x[:version_constraint]).not_to be nil
+ list.with_version_constraints.each_with_index do |recipe_spec, i|
+
+ expected_recipe = versioned_recipes[i]
+
+ expect(recipe_spec[:name]).to eq(expected_recipe[:name])
+ expect(recipe_spec[:version_constraint]).to eq(Chef::VersionConstraint.new(expected_recipe[:version]))
end
end
end
+
+ describe "with_fully_qualified_names_and_version_constraints" do
+
+ let(:fq_names) { list.with_fully_qualified_names_and_version_constraints }
+
+ context "with bare cookbook names" do
+
+ let(:recipes) { %w[ apache2 ] }
+
+ it "gives $cookbook_name::default" do
+ expect(fq_names).to eq( %w[ apache2::default ] )
+ end
+
+ end
+
+ context "with qualified recipe names but no versions" do
+
+ let(:recipes) { %w[ mysql::server ] }
+
+ it "returns the qualified recipe names" do
+ expect(fq_names).to eq( %w[ mysql::server ] )
+ end
+
+ end
+
+ context "with unqualified names that have version constraints" do
+
+ let(:versioned_recipes) do
+ [
+ {:name => "apt", :version => "~> 1.2.0"},
+ ]
+ end
+
+ it "gives qualified names with their versions" do
+ expect(fq_names).to eq([ "apt::default@~> 1.2.0" ])
+ end
+
+ it "does not mutate the recipe name" do
+ expect(fq_names).to eq([ "apt::default@~> 1.2.0" ])
+ expect(list).to eq( [ "apt" ] )
+ end
+
+ end
+
+ context "with fully qualified names that have version constraints" do
+
+ let(:versioned_recipes) do
+ [
+ {:name => "apt::cacher", :version => "~> 1.2.0"},
+ ]
+ end
+
+ it "gives qualified names with their versions" do
+ expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ])
+ end
+
+ it "does not mutate the recipe name" do
+ expect(fq_names).to eq([ "apt::cacher@~> 1.2.0" ])
+ expect(list).to eq( [ "apt::cacher" ] )
+ end
+
+ end
+ end
+
end
diff --git a/spec/unit/run_list_spec.rb b/spec/unit/run_list_spec.rb
index bf996de8c1..e150579431 100644
--- a/spec/unit/run_list_spec.rb
+++ b/spec/unit/run_list_spec.rb
@@ -307,7 +307,7 @@ describe Chef::RunList do
expect(Chef::JSONCompat.to_json(@run_list)).to eq(Chef::JSONCompat.to_json(["recipe[nagios::client]", "role[production]", "recipe[apache2]"]))
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @run_list }
end
diff --git a/spec/unit/runner_spec.rb b/spec/unit/runner_spec.rb
index 82e57e068c..b30f818da1 100644
--- a/spec/unit/runner_spec.rb
+++ b/spec/unit/runner_spec.rb
@@ -273,8 +273,8 @@ describe Chef::Runner do
expected_message =<<-E
Multiple failures occurred:
-* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
-* FailureProvider::ChefClientFail occurred in delayed notification: failure_resource[explode again] (dynamically defined) had an error: FailureProvider::ChefClientFail: chef had an error of some sort
+* 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
expect(exception.message).to eq(expected_message)
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
index 57822df7e3..97cc32eb3e 100644
--- a/spec/unit/user_spec.rb
+++ b/spec/unit/user_spec.rb
@@ -16,6 +16,11 @@
# limitations under the License.
#
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_spec.rb.
+
require 'spec_helper'
require 'chef/user'
@@ -26,141 +31,98 @@ describe Chef::User do
@user = Chef::User.new
end
- shared_examples_for "string fields with no contraints" do
- it "should let you set the public key" do
- expect(@user.send(method, "some_string")).to eq("some_string")
- end
-
- it "should return the current public key" do
- @user.send(method, "some_string")
- expect(@user.send(method)).to eq("some_string")
- end
-
- it "should throw an ArgumentError if you feed it something lame" do
- expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
- end
- end
-
- shared_examples_for "boolean fields with no constraints" do
- it "should let you set the field" do
- expect(@user.send(method, true)).to eq(true)
- end
-
- it "should return the current field value" do
- @user.send(method, true)
- expect(@user.send(method)).to eq(true)
- end
-
- it "should return the false value when false" do
- @user.send(method, false)
- expect(@user.send(method)).to eq(false)
- end
-
- it "should throw an ArgumentError if you feed it anything but true or false" do
- expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
- end
- end
-
describe "initialize" do
it "should be a Chef::User" do
expect(@user).to be_a_kind_of(Chef::User)
end
end
- describe "username" do
- it "should let you set the username to a string" do
- expect(@user.username("ops_master")).to eq("ops_master")
+ describe "name" do
+ it "should let you set the name to a string" do
+ expect(@user.name("ops_master")).to eq("ops_master")
end
- it "should return the current username" do
- @user.username "ops_master"
- expect(@user.username).to eq("ops_master")
+ it "should return the current name" do
+ @user.name "ops_master"
+ expect(@user.name).to eq("ops_master")
end
# It is not feasible to check all invalid characters. Here are a few
# that we probably care about.
it "should not accept invalid characters" do
# capital letters
- expect { @user.username "Bar" }.to raise_error(ArgumentError)
+ expect { @user.name "Bar" }.to raise_error(ArgumentError)
# slashes
- expect { @user.username "foo/bar" }.to raise_error(ArgumentError)
+ expect { @user.name "foo/bar" }.to raise_error(ArgumentError)
# ?
- expect { @user.username "foo?" }.to raise_error(ArgumentError)
+ expect { @user.name "foo?" }.to raise_error(ArgumentError)
# &
- expect { @user.username "foo&" }.to raise_error(ArgumentError)
+ expect { @user.name "foo&" }.to raise_error(ArgumentError)
end
it "should not accept spaces" do
- expect { @user.username "ops master" }.to raise_error(ArgumentError)
+ expect { @user.name "ops master" }.to raise_error(ArgumentError)
end
it "should throw an ArgumentError if you feed it anything but a string" do
- expect { @user.username Hash.new }.to raise_error(ArgumentError)
+ expect { @user.name Hash.new }.to raise_error(ArgumentError)
end
end
- describe "boolean fields" do
- describe "create_key" do
- it_should_behave_like "boolean fields with no constraints" do
- let(:method) { :create_key }
- end
+ describe "admin" do
+ it "should let you set the admin bit" do
+ expect(@user.admin(true)).to eq(true)
end
- end
- describe "string fields" do
- describe "public_key" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :public_key }
- end
+ it "should return the current admin value" do
+ @user.admin true
+ expect(@user.admin).to eq(true)
end
- describe "private_key" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :private_key }
- end
+ it "should default to false" do
+ expect(@user.admin).to eq(false)
end
- describe "display_name" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :display_name }
- end
+ it "should throw an ArgumentError if you feed it anything but true or false" do
+ expect { @user.name Hash.new }.to raise_error(ArgumentError)
end
+ end
- describe "first_name" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :first_name }
- end
+ describe "public_key" do
+ it "should let you set the public key" do
+ expect(@user.public_key("super public")).to eq("super public")
end
- describe "middle_name" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :middle_name }
- end
+ it "should return the current public key" do
+ @user.public_key("super public")
+ expect(@user.public_key).to eq("super public")
end
- describe "last_name" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :last_name }
- end
+ it "should throw an ArgumentError if you feed it something lame" do
+ expect { @user.public_key Hash.new }.to raise_error(ArgumentError)
end
+ end
- describe "email" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :email }
- end
+ describe "private_key" do
+ it "should let you set the private key" do
+ expect(@user.private_key("super private")).to eq("super private")
end
- describe "password" do
- it_should_behave_like "string fields with no contraints" do
- let(:method) { :password }
- end
+ it "should return the private key" do
+ @user.private_key("super private")
+ expect(@user.private_key).to eq("super private")
+ end
+
+ it "should throw an ArgumentError if you feed it something lame" do
+ expect { @user.private_key Hash.new }.to raise_error(ArgumentError)
end
end
describe "when serializing to JSON" do
before(:each) do
- @user.username("black")
+ @user.name("black")
+ @user.public_key("crowes")
@json = @user.to_json
end
@@ -168,62 +130,16 @@ describe Chef::User do
expect(@json).to match(/^\{.+\}$/)
end
- it "includes the username value" do
- expect(@json).to include(%q{"username":"black"})
- end
-
- it "includes the display name when present" do
- @user.display_name("get_displayed")
- expect(@user.to_json).to include(%{"display_name":"get_displayed"})
- end
-
- it "does not include the display name if not present" do
- expect(@json).not_to include("display_name")
- end
-
- it "includes the first name when present" do
- @user.first_name("char")
- expect(@user.to_json).to include(%{"first_name":"char"})
- end
-
- it "does not include the first name if not present" do
- expect(@json).not_to include("first_name")
- end
-
- it "includes the middle name when present" do
- @user.middle_name("man")
- expect(@user.to_json).to include(%{"middle_name":"man"})
- end
-
- it "does not include the middle name if not present" do
- expect(@json).not_to include("middle_name")
- end
-
- it "includes the last name when present" do
- @user.last_name("der")
- expect(@user.to_json).to include(%{"last_name":"der"})
- end
-
- it "does not include the last name if not present" do
- expect(@json).not_to include("last_name")
- end
-
- it "includes the email when present" do
- @user.email("charmander@pokemon.poke")
- expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"})
- end
-
- it "does not include the email if not present" do
- expect(@json).not_to include("email")
+ it "includes the name value" do
+ expect(@json).to include(%q{"name":"black"})
end
- it "includes the public key when present" do
- @user.public_key("crowes")
- expect(@user.to_json).to include(%{"public_key":"crowes"})
+ it "includes the public key value" do
+ expect(@json).to include(%{"public_key":"crowes"})
end
- it "does not include the public key if not present" do
- expect(@json).not_to include("public_key")
+ it "includes the 'admin' flag" do
+ expect(@json).to include(%q{"admin":false})
end
it "includes the private key when present" do
@@ -244,25 +160,18 @@ describe Chef::User do
expect(@json).not_to include("password")
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @user }
end
end
describe "when deserializing from JSON" do
before(:each) do
- user = {
- "username" => "mr_spinks",
- "display_name" => "displayed",
- "first_name" => "char",
- "middle_name" => "man",
- "last_name" => "der",
- "email" => "charmander@pokemon.poke",
- "password" => "password",
+ user = { "name" => "mr_spinks",
"public_key" => "turtles",
"private_key" => "pandas",
- "create_key" => false
- }
+ "password" => "password",
+ "admin" => true }
@user = Chef::User.from_json(Chef::JSONCompat.to_json(user))
end
@@ -270,277 +179,34 @@ describe Chef::User do
expect(@user).to be_a_kind_of(Chef::User)
end
- it "preserves the username" do
- expect(@user.username).to eq("mr_spinks")
- end
-
- it "preserves the display name if present" do
- expect(@user.display_name).to eq("displayed")
+ it "preserves the name" do
+ expect(@user.name).to eq("mr_spinks")
end
- it "preserves the first name if present" do
- expect(@user.first_name).to eq("char")
- end
-
- it "preserves the middle name if present" do
- expect(@user.middle_name).to eq("man")
+ it "preserves the public key" do
+ expect(@user.public_key).to eq("turtles")
end
- it "preserves the last name if present" do
- expect(@user.last_name).to eq("der")
+ it "preserves the admin status" do
+ expect(@user.admin).to be_truthy
end
- it "preserves the email if present" do
- expect(@user.email).to eq("charmander@pokemon.poke")
+ it "includes the private key if present" do
+ expect(@user.private_key).to eq("pandas")
end
it "includes the password if present" do
expect(@user.password).to eq("password")
end
- it "preserves the public key if present" do
- expect(@user.public_key).to eq("turtles")
- end
-
- it "includes the private key if present" do
- expect(@user.private_key).to eq("pandas")
- end
-
- it "includes the create key status if not nil" do
- expect(@user.create_key).to be_falsey
- end
end
- describe "Versioned API Interactions" do
- let(:response_406) { OpenStruct.new(:code => '406') }
- let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
-
- before (:each) do
- @user = Chef::User.new
- allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object'))
- allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object'))
- end
-
- describe "update" do
- before do
- # populate all fields that are valid between V0 and V1
- @user.username "some_username"
- @user.display_name "some_display_name"
- @user.first_name "some_first_name"
- @user.middle_name "some_middle_name"
- @user.last_name "some_last_name"
- @user.email "some_email"
- @user.password "some_password"
- end
-
- let(:payload) {
- {
- :username => "some_username",
- :display_name => "some_display_name",
- :first_name => "some_first_name",
- :middle_name => "some_middle_name",
- :last_name => "some_last_name",
- :email => "some_email",
- :password => "some_password"
- }
- }
-
- context "when server API V1 is valid on the Chef Server receiving the request" do
- context "when the user submits valid data" do
- it "properly updates the user" do
- expect(@user.chef_root_rest_v1).to receive(:put).with("users/some_username", payload).and_return({})
- @user.update
- end
- end
- end
-
- context "when server API V1 is not valid on the Chef Server receiving the request" do
- let(:payload) {
- {
- :username => "some_username",
- :display_name => "some_display_name",
- :first_name => "some_first_name",
- :middle_name => "some_middle_name",
- :last_name => "some_last_name",
- :email => "some_email",
- :password => "some_password",
- :public_key => "some_public_key"
- }
- }
-
- before do
- @user.public_key "some_public_key"
- allow(@user.chef_root_rest_v1).to receive(:put)
- end
-
- context "when the server returns a 400" do
- let(:response_400) { OpenStruct.new(:code => '400') }
- let(:exception_400) { Net::HTTPServerException.new("400 Bad Request", response_400) }
-
- context "when the 400 was due to public / private key fields no longer being supported" do
- let(:response_body_400) { '{"error":["Since Server API v1, all keys must be updated via the keys endpoint. "]}' }
-
- before do
- allow(response_400).to receive(:body).and_return(response_body_400)
- allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400)
- end
-
- it "proceeds with the V0 PUT since it can handle public / private key fields" do
- expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({})
- @user.update
- end
-
- it "does not call server_client_api_version_intersection, since we know to proceed with V0 in this case" do
- expect(@user).to_not receive(:server_client_api_version_intersection)
- allow(@user.chef_root_rest_v0).to receive(:put).and_return({})
- @user.update
- end
- end # when the 400 was due to public / private key fields
-
- context "when the 400 was NOT due to public / private key fields no longer being supported" do
- let(:response_body_400) { '{"error":["Some other error. "]}' }
-
- before do
- allow(response_400).to receive(:body).and_return(response_body_400)
- allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400)
- end
-
- it "will not proceed with the V0 PUT since the original bad request was not key related" do
- expect(@user.chef_root_rest_v0).to_not receive(:put).with("users/some_username", payload)
- expect { @user.update }.to raise_error(exception_400)
- end
-
- it "raises the original error" do
- expect { @user.update }.to raise_error(exception_400)
- end
-
- end
- end # when the server returns a 400
-
- context "when the server returns a 406" do
- # from spec/support/shared/unit/api_versioning.rb
- it_should_behave_like "version handling" do
- let(:object) { @user }
- let(:method) { :update }
- let(:http_verb) { :put }
- let(:rest_v1) { @user.chef_root_rest_v1 }
- end
-
- context "when the server supports API V0" do
- before do
- allow(@user).to receive(:server_client_api_version_intersection).and_return([0])
- allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_406)
- end
-
- it "properly updates the user" do
- expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({})
- @user.update
- end
- end # when the server supports API V0
- end # when the server returns a 406
-
- end # when server API V1 is not valid on the Chef Server receiving the request
- end # update
-
- describe "create" do
- let(:payload) {
- {
- :username => "some_username",
- :display_name => "some_display_name",
- :first_name => "some_first_name",
- :last_name => "some_last_name",
- :email => "some_email",
- :password => "some_password"
- }
- }
- before do
- @user.username "some_username"
- @user.display_name "some_display_name"
- @user.first_name "some_first_name"
- @user.last_name "some_last_name"
- @user.email "some_email"
- @user.password "some_password"
- end
-
- # from spec/support/shared/unit/user_and_client_shared.rb
- it_should_behave_like "user or client create" do
- let(:object) { @user }
- let(:error) { Chef::Exceptions::InvalidUserAttribute }
- let(:rest_v0) { @user.chef_root_rest_v0 }
- let(:rest_v1) { @user.chef_root_rest_v1 }
- let(:url) { "users" }
- end
-
- context "when handling API V1" do
- it "creates a new user via the API with a middle_name when it exists" do
- @user.middle_name "some_middle_name"
- expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
- @user.create
- end
- end # when server API V1 is valid on the Chef Server receiving the request
-
- context "when API V1 is not supported by the server" do
- # from spec/support/shared/unit/api_versioning.rb
- it_should_behave_like "version handling" do
- let(:object) { @user }
- let(:method) { :create }
- let(:http_verb) { :post }
- let(:rest_v1) { @user.chef_root_rest_v1 }
- end
- end
-
- context "when handling API V0" do
- before do
- allow(@user).to receive(:server_client_api_version_intersection).and_return([0])
- allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406)
- end
-
- it "creates a new user via the API with a middle_name when it exists" do
- @user.middle_name "some_middle_name"
- expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
- @user.create
- end
- end # when server API V1 is not valid on the Chef Server receiving the request
-
- end # create
-
- # DEPRECATION
- # This can be removed after API V0 support is gone
- describe "reregister" do
- let(:payload) {
- {
- "username" => "some_username",
- }
- }
-
- before do
- @user.username "some_username"
- end
-
- context "when server API V0 is valid on the Chef Server receiving the request" do
- it "creates a new object via the API" do
- expect(@user.chef_root_rest_v0).to receive(:put).with("users/#{@user.username}", payload.merge({"private_key" => true})).and_return({})
- @user.reregister
- end
- end # when server API V0 is valid on the Chef Server receiving the request
-
- context "when server API V0 is not supported by the Chef Server" do
- # from spec/support/shared/unit/api_versioning.rb
- it_should_behave_like "user and client reregister" do
- let(:object) { @user }
- let(:rest_v0) { @user.chef_root_rest_v0 }
- end
- end # when server API V0 is not supported by the Chef Server
- end # reregister
-
- end # Versioned API Interactions
-
describe "API Interactions" do
before (:each) do
@user = Chef::User.new
- @user.username "foobar"
- @http_client = double("Chef::REST mock")
- allow(Chef::REST).to receive(:new).and_return(@http_client)
+ @user.name "foobar"
+ @http_client = double("Chef::ServerAPI mock")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
end
describe "list" do
@@ -552,6 +218,16 @@ describe Chef::User do
@osc_inflated_response = { "admin" => @user }
end
+ it "lists all clients on an OSC server" do
+ allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
+ expect(Chef::User.list).to eq(@osc_response)
+ end
+
+ it "inflate all clients on an OSC server" do
+ allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
+ expect(Chef::User.list(true)).to eq(@osc_inflated_response)
+ end
+
it "lists all clients on an OHC/OPC server" do
allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
# We expect that Chef::User.list will give a consistent response
@@ -565,15 +241,31 @@ describe Chef::User do
end
end
+ describe "create" do
+ it "creates a new user via the API" do
+ @user.password "password"
+ expect(@http_client).to receive(:post).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({})
+ @user.create
+ end
+ end
+
describe "read" do
it "loads a named user from the API" do
- expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"})
+ expect(@http_client).to receive(:get).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
user = Chef::User.load("foobar")
- expect(user.username).to eq("foobar")
+ expect(user.name).to eq("foobar")
+ expect(user.admin).to eq(true)
expect(user.public_key).to eq("pubkey")
end
end
+ describe "update" do
+ it "updates an existing user on via the API" do
+ expect(@http_client).to receive(:put).with("users/foobar", {:name => "foobar", :admin => false}).and_return({})
+ @user.update
+ end
+ end
+
describe "destroy" do
it "deletes the specified user via the API" do
expect(@http_client).to receive(:delete).with("users/foobar")
diff --git a/spec/unit/user_v1_spec.rb b/spec/unit/user_v1_spec.rb
new file mode 100644
index 0000000000..8fd370a010
--- /dev/null
+++ b/spec/unit/user_v1_spec.rb
@@ -0,0 +1,584 @@
+#
+# Author:: Steven Danna (steve@opscode.com)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'chef/user_v1'
+require 'tempfile'
+
+describe Chef::UserV1 do
+ before(:each) do
+ @user = Chef::UserV1.new
+ end
+
+ shared_examples_for "string fields with no contraints" do
+ it "should let you set the public key" do
+ expect(@user.send(method, "some_string")).to eq("some_string")
+ end
+
+ it "should return the current public key" do
+ @user.send(method, "some_string")
+ expect(@user.send(method)).to eq("some_string")
+ end
+
+ it "should throw an ArgumentError if you feed it something lame" do
+ expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
+ end
+ end
+
+ shared_examples_for "boolean fields with no constraints" do
+ it "should let you set the field" do
+ expect(@user.send(method, true)).to eq(true)
+ end
+
+ it "should return the current field value" do
+ @user.send(method, true)
+ expect(@user.send(method)).to eq(true)
+ end
+
+ it "should return the false value when false" do
+ @user.send(method, false)
+ expect(@user.send(method)).to eq(false)
+ end
+
+ it "should throw an ArgumentError if you feed it anything but true or false" do
+ expect { @user.send(method, Hash.new) }.to raise_error(ArgumentError)
+ end
+ end
+
+ describe "initialize" do
+ it "should be a Chef::UserV1" do
+ expect(@user).to be_a_kind_of(Chef::UserV1)
+ end
+ end
+
+ describe "username" do
+ it "should let you set the username to a string" do
+ expect(@user.username("ops_master")).to eq("ops_master")
+ end
+
+ it "should return the current username" do
+ @user.username "ops_master"
+ expect(@user.username).to eq("ops_master")
+ end
+
+ # It is not feasible to check all invalid characters. Here are a few
+ # that we probably care about.
+ it "should not accept invalid characters" do
+ # capital letters
+ expect { @user.username "Bar" }.to raise_error(ArgumentError)
+ # slashes
+ expect { @user.username "foo/bar" }.to raise_error(ArgumentError)
+ # ?
+ expect { @user.username "foo?" }.to raise_error(ArgumentError)
+ # &
+ expect { @user.username "foo&" }.to raise_error(ArgumentError)
+ end
+
+
+ it "should not accept spaces" do
+ expect { @user.username "ops master" }.to raise_error(ArgumentError)
+ end
+
+ it "should throw an ArgumentError if you feed it anything but a string" do
+ expect { @user.username Hash.new }.to raise_error(ArgumentError)
+ end
+ end
+
+ describe "boolean fields" do
+ describe "create_key" do
+ it_should_behave_like "boolean fields with no constraints" do
+ let(:method) { :create_key }
+ end
+ end
+ end
+
+ describe "string fields" do
+ describe "public_key" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :public_key }
+ end
+ end
+
+ describe "private_key" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :private_key }
+ end
+ end
+
+ describe "display_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :display_name }
+ end
+ end
+
+ describe "first_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :first_name }
+ end
+ end
+
+ describe "middle_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :middle_name }
+ end
+ end
+
+ describe "last_name" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :last_name }
+ end
+ end
+
+ describe "email" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :email }
+ end
+ end
+
+ describe "password" do
+ it_should_behave_like "string fields with no contraints" do
+ let(:method) { :password }
+ end
+ end
+ end
+
+ describe "when serializing to JSON" do
+ before(:each) do
+ @user.username("black")
+ @json = @user.to_json
+ end
+
+ it "serializes as a JSON object" do
+ expect(@json).to match(/^\{.+\}$/)
+ end
+
+ it "includes the username value" do
+ expect(@json).to include(%q{"username":"black"})
+ end
+
+ it "includes the display name when present" do
+ @user.display_name("get_displayed")
+ expect(@user.to_json).to include(%{"display_name":"get_displayed"})
+ end
+
+ it "does not include the display name if not present" do
+ expect(@json).not_to include("display_name")
+ end
+
+ it "includes the first name when present" do
+ @user.first_name("char")
+ expect(@user.to_json).to include(%{"first_name":"char"})
+ end
+
+ it "does not include the first name if not present" do
+ expect(@json).not_to include("first_name")
+ end
+
+ it "includes the middle name when present" do
+ @user.middle_name("man")
+ expect(@user.to_json).to include(%{"middle_name":"man"})
+ end
+
+ it "does not include the middle name if not present" do
+ expect(@json).not_to include("middle_name")
+ end
+
+ it "includes the last name when present" do
+ @user.last_name("der")
+ expect(@user.to_json).to include(%{"last_name":"der"})
+ end
+
+ it "does not include the last name if not present" do
+ expect(@json).not_to include("last_name")
+ end
+
+ it "includes the email when present" do
+ @user.email("charmander@pokemon.poke")
+ expect(@user.to_json).to include(%{"email":"charmander@pokemon.poke"})
+ end
+
+ it "does not include the email if not present" do
+ expect(@json).not_to include("email")
+ end
+
+ it "includes the public key when present" do
+ @user.public_key("crowes")
+ expect(@user.to_json).to include(%{"public_key":"crowes"})
+ end
+
+ it "does not include the public key if not present" do
+ expect(@json).not_to include("public_key")
+ end
+
+ it "includes the private key when present" do
+ @user.private_key("monkeypants")
+ expect(@user.to_json).to include(%q{"private_key":"monkeypants"})
+ end
+
+ it "does not include the private key if not present" do
+ expect(@json).not_to include("private_key")
+ end
+
+ it "includes the password if present" do
+ @user.password "password"
+ expect(@user.to_json).to include(%q{"password":"password"})
+ end
+
+ it "does not include the password if not present" do
+ expect(@json).not_to include("password")
+ end
+
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
+ let(:jsonable) { @user }
+ end
+ end
+
+ describe "when deserializing from JSON" do
+ before(:each) do
+ user = {
+ "username" => "mr_spinks",
+ "display_name" => "displayed",
+ "first_name" => "char",
+ "middle_name" => "man",
+ "last_name" => "der",
+ "email" => "charmander@pokemon.poke",
+ "password" => "password",
+ "public_key" => "turtles",
+ "private_key" => "pandas",
+ "create_key" => false
+ }
+ @user = Chef::UserV1.from_json(Chef::JSONCompat.to_json(user))
+ end
+
+ it "should deserialize to a Chef::UserV1 object" do
+ expect(@user).to be_a_kind_of(Chef::UserV1)
+ end
+
+ it "preserves the username" do
+ expect(@user.username).to eq("mr_spinks")
+ end
+
+ it "preserves the display name if present" do
+ expect(@user.display_name).to eq("displayed")
+ end
+
+ it "preserves the first name if present" do
+ expect(@user.first_name).to eq("char")
+ end
+
+ it "preserves the middle name if present" do
+ expect(@user.middle_name).to eq("man")
+ end
+
+ it "preserves the last name if present" do
+ expect(@user.last_name).to eq("der")
+ end
+
+ it "preserves the email if present" do
+ expect(@user.email).to eq("charmander@pokemon.poke")
+ end
+
+ it "includes the password if present" do
+ expect(@user.password).to eq("password")
+ end
+
+ it "preserves the public key if present" do
+ expect(@user.public_key).to eq("turtles")
+ end
+
+ it "includes the private key if present" do
+ expect(@user.private_key).to eq("pandas")
+ end
+
+ it "includes the create key status if not nil" do
+ expect(@user.create_key).to be_falsey
+ end
+ end
+
+ describe "Versioned API Interactions" do
+ let(:response_406) { OpenStruct.new(:code => '406') }
+ let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+
+ before (:each) do
+ @user = Chef::UserV1.new
+ allow(@user).to receive(:chef_root_rest_v0).and_return(double('chef rest root v0 object'))
+ allow(@user).to receive(:chef_root_rest_v1).and_return(double('chef rest root v1 object'))
+ end
+
+ describe "update" do
+ before do
+ # populate all fields that are valid between V0 and V1
+ @user.username "some_username"
+ @user.display_name "some_display_name"
+ @user.first_name "some_first_name"
+ @user.middle_name "some_middle_name"
+ @user.last_name "some_last_name"
+ @user.email "some_email"
+ @user.password "some_password"
+ end
+
+ let(:payload) {
+ {
+ :username => "some_username",
+ :display_name => "some_display_name",
+ :first_name => "some_first_name",
+ :middle_name => "some_middle_name",
+ :last_name => "some_last_name",
+ :email => "some_email",
+ :password => "some_password"
+ }
+ }
+
+ context "when server API V1 is valid on the Chef Server receiving the request" do
+ context "when the user submits valid data" do
+ it "properly updates the user" do
+ expect(@user.chef_root_rest_v1).to receive(:put).with("users/some_username", payload).and_return({})
+ @user.update
+ end
+ end
+ end
+
+ context "when server API V1 is not valid on the Chef Server receiving the request" do
+ let(:payload) {
+ {
+ :username => "some_username",
+ :display_name => "some_display_name",
+ :first_name => "some_first_name",
+ :middle_name => "some_middle_name",
+ :last_name => "some_last_name",
+ :email => "some_email",
+ :password => "some_password",
+ :public_key => "some_public_key"
+ }
+ }
+
+ before do
+ @user.public_key "some_public_key"
+ allow(@user.chef_root_rest_v1).to receive(:put)
+ end
+
+ context "when the server returns a 400" do
+ let(:response_400) { OpenStruct.new(:code => '400') }
+ let(:exception_400) { Net::HTTPServerException.new("400 Bad Request", response_400) }
+
+ context "when the 400 was due to public / private key fields no longer being supported" do
+ let(:response_body_400) { '{"error":["Since Server API v1, all keys must be updated via the keys endpoint. "]}' }
+
+ before do
+ allow(response_400).to receive(:body).and_return(response_body_400)
+ allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400)
+ end
+
+ it "proceeds with the V0 PUT since it can handle public / private key fields" do
+ expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({})
+ @user.update
+ end
+
+ it "does not call server_client_api_version_intersection, since we know to proceed with V0 in this case" do
+ expect(@user).to_not receive(:server_client_api_version_intersection)
+ allow(@user.chef_root_rest_v0).to receive(:put).and_return({})
+ @user.update
+ end
+ end # when the 400 was due to public / private key fields
+
+ context "when the 400 was NOT due to public / private key fields no longer being supported" do
+ let(:response_body_400) { '{"error":["Some other error. "]}' }
+
+ before do
+ allow(response_400).to receive(:body).and_return(response_body_400)
+ allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_400)
+ end
+
+ it "will not proceed with the V0 PUT since the original bad request was not key related" do
+ expect(@user.chef_root_rest_v0).to_not receive(:put).with("users/some_username", payload)
+ expect { @user.update }.to raise_error(exception_400)
+ end
+
+ it "raises the original error" do
+ expect { @user.update }.to raise_error(exception_400)
+ end
+
+ end
+ end # when the server returns a 400
+
+ context "when the server returns a 406" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "version handling" do
+ let(:object) { @user }
+ let(:method) { :update }
+ let(:http_verb) { :put }
+ let(:rest_v1) { @user.chef_root_rest_v1 }
+ end
+
+ context "when the server supports API V0" do
+ before do
+ allow(@user).to receive(:server_client_api_version_intersection).and_return([0])
+ allow(@user.chef_root_rest_v1).to receive(:put).and_raise(exception_406)
+ end
+
+ it "properly updates the user" do
+ expect(@user.chef_root_rest_v0).to receive(:put).with("users/some_username", payload).and_return({})
+ @user.update
+ end
+ end # when the server supports API V0
+ end # when the server returns a 406
+
+ end # when server API V1 is not valid on the Chef Server receiving the request
+ end # update
+
+ describe "create" do
+ let(:payload) {
+ {
+ :username => "some_username",
+ :display_name => "some_display_name",
+ :first_name => "some_first_name",
+ :last_name => "some_last_name",
+ :email => "some_email",
+ :password => "some_password"
+ }
+ }
+ before do
+ @user.username "some_username"
+ @user.display_name "some_display_name"
+ @user.first_name "some_first_name"
+ @user.last_name "some_last_name"
+ @user.email "some_email"
+ @user.password "some_password"
+ end
+
+ # from spec/support/shared/unit/user_and_client_shared.rb
+ it_should_behave_like "user or client create" do
+ let(:object) { @user }
+ let(:error) { Chef::Exceptions::InvalidUserAttribute }
+ let(:rest_v0) { @user.chef_root_rest_v0 }
+ let(:rest_v1) { @user.chef_root_rest_v1 }
+ let(:url) { "users" }
+ end
+
+ context "when handling API V1" do
+ it "creates a new user via the API with a middle_name when it exists" do
+ @user.middle_name "some_middle_name"
+ expect(@user.chef_root_rest_v1).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
+ @user.create
+ end
+ end # when server API V1 is valid on the Chef Server receiving the request
+
+ context "when API V1 is not supported by the server" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "version handling" do
+ let(:object) { @user }
+ let(:method) { :create }
+ let(:http_verb) { :post }
+ let(:rest_v1) { @user.chef_root_rest_v1 }
+ end
+ end
+
+ context "when handling API V0" do
+ before do
+ allow(@user).to receive(:server_client_api_version_intersection).and_return([0])
+ allow(@user.chef_root_rest_v1).to receive(:post).and_raise(exception_406)
+ end
+
+ it "creates a new user via the API with a middle_name when it exists" do
+ @user.middle_name "some_middle_name"
+ expect(@user.chef_root_rest_v0).to receive(:post).with("users", payload.merge({:middle_name => "some_middle_name"})).and_return({})
+ @user.create
+ end
+ end # when server API V1 is not valid on the Chef Server receiving the request
+
+ end # create
+
+ # DEPRECATION
+ # This can be removed after API V0 support is gone
+ describe "reregister" do
+ let(:payload) {
+ {
+ "username" => "some_username",
+ }
+ }
+
+ before do
+ @user.username "some_username"
+ end
+
+ context "when server API V0 is valid on the Chef Server receiving the request" do
+ it "creates a new object via the API" do
+ expect(@user.chef_root_rest_v0).to receive(:put).with("users/#{@user.username}", payload.merge({"private_key" => true})).and_return({})
+ @user.reregister
+ end
+ end # when server API V0 is valid on the Chef Server receiving the request
+
+ context "when server API V0 is not supported by the Chef Server" do
+ # from spec/support/shared/unit/api_versioning.rb
+ it_should_behave_like "user and client reregister" do
+ let(:object) { @user }
+ let(:rest_v0) { @user.chef_root_rest_v0 }
+ end
+ end # when server API V0 is not supported by the Chef Server
+ end # reregister
+
+ end # Versioned API Interactions
+
+ describe "API Interactions" do
+ before (:each) do
+ @user = Chef::UserV1.new
+ @user.username "foobar"
+ @http_client = double("Chef::REST mock")
+ allow(Chef::REST).to receive(:new).and_return(@http_client)
+ end
+
+ describe "list" do
+ before(:each) do
+ Chef::Config[:chef_server_url] = "http://www.example.com"
+ @osc_response = { "admin" => "http://www.example.com/users/admin"}
+ @ohc_response = [ { "user" => { "username" => "admin" }} ]
+ allow(Chef::UserV1).to receive(:load).with("admin").and_return(@user)
+ @osc_inflated_response = { "admin" => @user }
+ end
+
+ it "lists all clients on an OHC/OPC server" do
+ allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
+ # We expect that Chef::UserV1.list will give a consistent response
+ # so OHC API responses should be transformed to OSC-style output.
+ expect(Chef::UserV1.list).to eq(@osc_response)
+ end
+
+ it "inflate all clients on an OHC/OPC server" do
+ allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
+ expect(Chef::UserV1.list(true)).to eq(@osc_inflated_response)
+ end
+ end
+
+ describe "read" do
+ it "loads a named user from the API" do
+ expect(@http_client).to receive(:get).with("users/foobar").and_return({"username" => "foobar", "admin" => true, "public_key" => "pubkey"})
+ user = Chef::UserV1.load("foobar")
+ expect(user.username).to eq("foobar")
+ expect(user.public_key).to eq("pubkey")
+ end
+ end
+
+ describe "destroy" do
+ it "deletes the specified user via the API" do
+ expect(@http_client).to receive(:delete).with("users/foobar")
+ @user.destroy
+ end
+ end
+ end
+end