summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml22
-rw-r--r--CHANGELOG.md203
-rw-r--r--CHEF_MVPS.md158
-rw-r--r--DOC_CHANGES.md392
-rw-r--r--Gemfile4
-rw-r--r--MAINTAINERS.md133
-rw-r--r--MAINTAINERS.toml307
-rw-r--r--README.md8
-rw-r--r--RELEASE_NOTES.md187
-rw-r--r--ROADMAP.md16
-rw-r--r--Rakefile34
-rw-r--r--VERSION1
-rw-r--r--appveyor.yml10
-rwxr-xr-xbin/chef-service-manager5
-rw-r--r--chef-config/.gitignore9
-rw-r--r--chef-config/.rspec2
-rw-r--r--chef-config/.travis.yml31
-rw-r--r--chef-config/Gemfile4
-rw-r--r--chef-config/LICENSE201
-rw-r--r--chef-config/README.md4
-rw-r--r--chef-config/Rakefile14
-rw-r--r--chef-config/VERSION1
-rw-r--r--chef-config/chef-config.gemspec32
-rw-r--r--chef-config/lib/chef-config.rb20
-rw-r--r--chef-config/lib/chef-config/config.rb741
-rw-r--r--chef-config/lib/chef-config/exceptions.rb26
-rw-r--r--chef-config/lib/chef-config/logger.rb62
-rw-r--r--chef-config/lib/chef-config/package_task.rb223
-rw-r--r--chef-config/lib/chef-config/path_helper.rb264
-rw-r--r--chef-config/lib/chef-config/version.rb34
-rw-r--r--chef-config/lib/chef-config/windows.rb29
-rw-r--r--chef-config/lib/chef-config/workstation_config_loader.rb179
-rw-r--r--chef-config/spec/spec_helper.rb75
-rw-r--r--chef-config/spec/unit/config_spec.rb581
-rw-r--r--chef-config/spec/unit/path_helper_spec.rb291
-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.gemspec22
-rw-r--r--chef-x86-mingw32.gemspec23
-rw-r--r--chef.gemspec22
-rw-r--r--distro/common/markdown/man1/chef-shell.mkd8
-rw-r--r--distro/common/markdown/man1/knife-bootstrap.mkd2
-rw-r--r--distro/common/markdown/man1/knife-client.mkd2
-rw-r--r--distro/common/markdown/man1/knife-configure.mkd2
-rw-r--r--distro/common/markdown/man1/knife-cookbook-site.mkd2
-rw-r--r--distro/common/markdown/man1/knife-cookbook.mkd8
-rw-r--r--distro/common/markdown/man1/knife-data-bag.mkd2
-rw-r--r--distro/common/markdown/man1/knife-environment.mkd6
-rw-r--r--distro/common/markdown/man1/knife-exec.mkd2
-rw-r--r--distro/common/markdown/man1/knife-index.mkd2
-rw-r--r--distro/common/markdown/man1/knife-node.mkd2
-rw-r--r--distro/common/markdown/man1/knife-role.mkd6
-rw-r--r--distro/common/markdown/man1/knife-search.mkd4
-rw-r--r--distro/common/markdown/man1/knife-ssh.mkd2
-rw-r--r--distro/common/markdown/man1/knife-status.mkd2
-rw-r--r--distro/common/markdown/man1/knife-tag.mkd2
-rw-r--r--distro/common/markdown/man1/knife.mkd4
-rw-r--r--distro/common/markdown/man8/chef-client.mkd3
-rw-r--r--distro/common/markdown/man8/chef-expander.mkd3
-rw-r--r--distro/common/markdown/man8/chef-expanderctl.mkd3
-rw-r--r--distro/common/markdown/man8/chef-server-webui.mkd2
-rw-r--r--distro/common/markdown/man8/chef-server.mkd3
-rw-r--r--distro/common/markdown/man8/chef-solo.mkd4
-rw-r--r--distro/common/markdown/man8/chef-solr.mkd2
-rw-r--r--distro/powershell/chef/chef.psm1327
-rw-r--r--ext/win32-eventlog/Rakefile16
-rw-r--r--ext/win32-eventlog/chef-log.man30
-rw-r--r--external_tests/chef-rewind.gemfile5
-rw-r--r--external_tests/chef-sugar.gemfile6
-rw-r--r--external_tests/chefspec.gemfile7
-rw-r--r--external_tests/foodcritic.gemfile9
-rw-r--r--external_tests/halite.gemfile8
-rw-r--r--external_tests/poise.gemfile7
-rw-r--r--kitchen-tests/.kitchen.travis.yml41
-rw-r--r--kitchen-tests/.kitchen.yml8
-rw-r--r--kitchen-tests/Gemfile14
-rw-r--r--lib/chef.rb3
-rw-r--r--lib/chef/api_client.rb10
-rw-r--r--lib/chef/api_client_v1.rb325
-rw-r--r--lib/chef/application.rb3
-rw-r--r--lib/chef/application/apply.rb23
-rw-r--r--lib/chef/application/client.rb43
-rw-r--r--lib/chef/application/knife.rb6
-rw-r--r--lib/chef/application/solo.rb7
-rw-r--r--lib/chef/application/windows_service_manager.rb31
-rw-r--r--lib/chef/audit/audit_reporter.rb21
-rw-r--r--lib/chef/audit/logger.rb36
-rw-r--r--lib/chef/audit/runner.rb19
-rw-r--r--lib/chef/chef_class.rb219
-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.rb808
-rw-r--r--lib/chef/config.rb727
-rw-r--r--lib/chef/constants.rb27
-rw-r--r--lib/chef/cookbook/metadata.rb42
-rw-r--r--lib/chef/cookbook/remote_file_vendor.rb2
-rw-r--r--lib/chef/cookbook/synchronizer.rb2
-rw-r--r--lib/chef/cookbook_loader.rb2
-rw-r--r--lib/chef/cookbook_manifest.rb20
-rw-r--r--lib/chef/cookbook_site_streaming_uploader.rb20
-rw-r--r--lib/chef/cookbook_version.rb25
-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/definitions.rb44
-rw-r--r--lib/chef/dsl/powershell.rb29
-rw-r--r--lib/chef/dsl/reboot_pending.rb2
-rw-r--r--lib/chef/dsl/recipe.rb115
-rw-r--r--lib/chef/dsl/resources.rb31
-rw-r--r--lib/chef/event_dispatch/base.rb82
-rw-r--r--lib/chef/event_dispatch/dispatcher.rb26
-rw-r--r--lib/chef/event_dispatch/dsl.rb64
-rw-r--r--lib/chef/event_loggers/windows_eventlog.rb28
-rw-r--r--lib/chef/exceptions.rb61
-rw-r--r--lib/chef/file_access_control/unix.rb5
-rw-r--r--lib/chef/file_content_management/deploy/mv_windows.rb22
-rw-r--r--lib/chef/formatters/base.rb3
-rw-r--r--lib/chef/formatters/doc.rb72
-rw-r--r--lib/chef/formatters/error_inspectors/api_error_formatting.rb20
-rw-r--r--lib/chef/formatters/error_inspectors/compile_error_inspector.rb46
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb2
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb2
-rw-r--r--lib/chef/formatters/error_inspectors/node_load_error_inspector.rb2
-rw-r--r--lib/chef/formatters/error_inspectors/registration_error_inspector.rb4
-rw-r--r--lib/chef/formatters/error_inspectors/resource_failure_inspector.rb12
-rw-r--r--lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb2
-rw-r--r--lib/chef/formatters/minimal.rb4
-rw-r--r--lib/chef/guard_interpreter/default_guard_interpreter.rb2
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb10
-rw-r--r--lib/chef/http.rb16
-rw-r--r--lib/chef/http/authenticator.rb8
-rw-r--r--lib/chef/http/basic_client.rb41
-rw-r--r--lib/chef/http/http_request.rb2
-rw-r--r--lib/chef/http/json_input.rb7
-rw-r--r--lib/chef/http/socketless_chef_zero_client.rb207
-rw-r--r--lib/chef/key.rb271
-rw-r--r--lib/chef/knife.rb117
-rw-r--r--lib/chef/knife/bootstrap.rb11
-rw-r--r--lib/chef/knife/bootstrap/chef_vault_handler.rb1
-rw-r--r--lib/chef/knife/bootstrap/client_builder.rb1
-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.erb197
-rw-r--r--lib/chef/knife/client_bulk_delete.rb4
-rw-r--r--lib/chef/knife/client_create.rb88
-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_key_create.rb67
-rw-r--r--lib/chef/knife/client_key_delete.rb76
-rw-r--r--lib/chef/knife/client_key_edit.rb80
-rw-r--r--lib/chef/knife/client_key_list.rb69
-rw-r--r--lib/chef/knife/client_key_show.rb76
-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/custom_manifest_loader.rb69
-rw-r--r--lib/chef/knife/core/gem_glob_loader.rb138
-rw-r--r--lib/chef/knife/core/generic_presenter.rb2
-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/status_presenter.rb23
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb281
-rw-r--r--lib/chef/knife/exec.rb3
-rw-r--r--lib/chef/knife/key_create.rb108
-rw-r--r--lib/chef/knife/key_create_base.rb50
-rw-r--r--lib/chef/knife/key_delete.rb55
-rw-r--r--lib/chef/knife/key_edit.rb114
-rw-r--r--lib/chef/knife/key_edit_base.rb55
-rw-r--r--lib/chef/knife/key_list.rb88
-rw-r--r--lib/chef/knife/key_list_base.rb45
-rw-r--r--lib/chef/knife/key_show.rb53
-rw-r--r--lib/chef/knife/null.rb10
-rw-r--r--lib/chef/knife/osc_user_create.rb97
-rw-r--r--lib/chef/knife/osc_user_delete.rb51
-rw-r--r--lib/chef/knife/osc_user_edit.rb58
-rw-r--r--lib/chef/knife/osc_user_list.rb47
-rw-r--r--lib/chef/knife/osc_user_reregister.rb64
-rw-r--r--lib/chef/knife/osc_user_show.rb54
-rw-r--r--lib/chef/knife/rehash.rb62
-rw-r--r--lib/chef/knife/search.rb3
-rw-r--r--lib/chef/knife/ssh.rb59
-rw-r--r--lib/chef/knife/ssl_check.rb5
-rw-r--r--lib/chef/knife/status.rb39
-rw-r--r--lib/chef/knife/user_create.rb133
-rw-r--r--lib/chef/knife/user_delete.rb56
-rw-r--r--lib/chef/knife/user_edit.rb47
-rw-r--r--lib/chef/knife/user_key_create.rb69
-rw-r--r--lib/chef/knife/user_key_delete.rb76
-rw-r--r--lib/chef/knife/user_key_edit.rb80
-rw-r--r--lib/chef/knife/user_key_list.rb69
-rw-r--r--lib/chef/knife/user_key_show.rb76
-rw-r--r--lib/chef/knife/user_list.rb7
-rw-r--r--lib/chef/knife/user_reregister.rb49
-rw-r--r--lib/chef/knife/user_show.rb35
-rw-r--r--lib/chef/local_mode.rb16
-rw-r--r--lib/chef/log.rb8
-rw-r--r--lib/chef/log/syslog.rb46
-rw-r--r--lib/chef/log/winevt.rb99
-rw-r--r--lib/chef/mixin/api_version_request_handling.rb66
-rw-r--r--lib/chef/mixin/convert_to_class_name.rb14
-rw-r--r--lib/chef/mixin/deprecation.rb24
-rw-r--r--lib/chef/mixin/params_validate.rb504
-rw-r--r--lib/chef/mixin/powershell_out.rb98
-rw-r--r--lib/chef/mixin/powershell_type_coercions.rb82
-rw-r--r--lib/chef/mixin/provides.rb27
-rw-r--r--lib/chef/mixin/template.rb47
-rw-r--r--lib/chef/mixin/unformatter.rb32
-rw-r--r--lib/chef/mixin/uris.rb44
-rw-r--r--lib/chef/mixin/wide_string.rb72
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb69
-rw-r--r--lib/chef/mixin/windows_env_helper.rb14
-rw-r--r--lib/chef/node.rb44
-rw-r--r--lib/chef/node_map.rb242
-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.rb29
-rw-r--r--lib/chef/platform/provider_mapping.rb396
-rw-r--r--lib/chef/platform/provider_priority_map.rb80
-rw-r--r--lib/chef/platform/query_helpers.rb23
-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.rb11
-rw-r--r--lib/chef/platform/service_helpers.rb42
-rw-r--r--lib/chef/policy_builder/expand_node_object.rb14
-rw-r--r--lib/chef/policy_builder/policyfile.rb16
-rw-r--r--lib/chef/property.rb536
-rw-r--r--lib/chef/provider.rb310
-rw-r--r--lib/chef/provider/batch.rb10
-rw-r--r--lib/chef/provider/cron.rb2
-rw-r--r--lib/chef/provider/cron/aix.rb2
-rw-r--r--lib/chef/provider/cron/unix.rb3
-rw-r--r--lib/chef/provider/deploy.rb8
-rw-r--r--lib/chef/provider/directory.rb19
-rw-r--r--lib/chef/provider/dsc_resource.rb164
-rw-r--r--lib/chef/provider/dsc_script.rb2
-rw-r--r--lib/chef/provider/env.rb2
-rw-r--r--lib/chef/provider/env/windows.rb2
-rw-r--r--lib/chef/provider/file.rb9
-rw-r--r--lib/chef/provider/group.rb10
-rw-r--r--lib/chef/provider/group/aix.rb1
-rw-r--r--lib/chef/provider/group/dscl.rb2
-rw-r--r--lib/chef/provider/group/gpasswd.rb1
-rw-r--r--lib/chef/provider/group/groupmod.rb2
-rw-r--r--lib/chef/provider/group/pw.rb3
-rw-r--r--lib/chef/provider/group/suse.rb2
-rw-r--r--lib/chef/provider/group/usermod.rb3
-rw-r--r--lib/chef/provider/group/windows.rb2
-rw-r--r--lib/chef/provider/ifconfig.rb6
-rw-r--r--lib/chef/provider/ifconfig/aix.rb1
-rw-r--r--lib/chef/provider/ifconfig/debian.rb2
-rw-r--r--lib/chef/provider/ifconfig/redhat.rb1
-rw-r--r--lib/chef/provider/lwrp_base.rb150
-rw-r--r--lib/chef/provider/mdadm.rb2
-rw-r--r--lib/chef/provider/mount.rb11
-rw-r--r--lib/chef/provider/mount/aix.rb3
-rw-r--r--lib/chef/provider/mount/mount.rb2
-rw-r--r--lib/chef/provider/mount/solaris.rb2
-rw-r--r--lib/chef/provider/mount/windows.rb2
-rw-r--r--lib/chef/provider/ohai.rb1
-rw-r--r--lib/chef/provider/package.rb37
-rw-r--r--lib/chef/provider/package/aix.rb16
-rw-r--r--lib/chef/provider/package/apt.rb7
-rw-r--r--lib/chef/provider/package/dpkg.rb22
-rw-r--r--lib/chef/provider/package/easy_install.rb10
-rw-r--r--lib/chef/provider/package/freebsd/base.rb4
-rw-r--r--lib/chef/provider/package/freebsd/pkg.rb12
-rw-r--r--lib/chef/provider/package/freebsd/pkgng.rb10
-rw-r--r--lib/chef/provider/package/freebsd/port.rb8
-rw-r--r--lib/chef/provider/package/homebrew.rb5
-rw-r--r--lib/chef/provider/package/ips.rb9
-rw-r--r--lib/chef/provider/package/macports.rb12
-rw-r--r--lib/chef/provider/package/openbsd.rb73
-rw-r--r--lib/chef/provider/package/pacman.rb9
-rw-r--r--lib/chef/provider/package/paludis.rb1
-rw-r--r--lib/chef/provider/package/portage.rb4
-rw-r--r--lib/chef/provider/package/rpm.rb19
-rw-r--r--lib/chef/provider/package/rubygems.rb33
-rw-r--r--lib/chef/provider/package/smartos.rb11
-rw-r--r--lib/chef/provider/package/solaris.rb16
-rw-r--r--lib/chef/provider/package/windows.rb96
-rw-r--r--lib/chef/provider/package/windows/msi.rb2
-rw-r--r--lib/chef/provider/package/yum.rb174
-rw-r--r--lib/chef/provider/package/zypper.rb31
-rw-r--r--lib/chef/provider/powershell_script.rb203
-rw-r--r--lib/chef/provider/reboot.rb1
-rw-r--r--lib/chef/provider/registry_key.rb12
-rw-r--r--lib/chef/provider/remote_directory.rb2
-rw-r--r--lib/chef/provider/remote_file.rb1
-rw-r--r--lib/chef/provider/remote_file/content.rb9
-rw-r--r--lib/chef/provider/remote_file/fetcher.rb30
-rw-r--r--lib/chef/provider/remote_file/local_file.rb14
-rw-r--r--lib/chef/provider/remote_file/network_file.rb48
-rw-r--r--lib/chef/provider/service.rb40
-rw-r--r--lib/chef/provider/service/aix.rb25
-rw-r--r--lib/chef/provider/service/debian.rb10
-rw-r--r--lib/chef/provider/service/freebsd.rb4
-rw-r--r--lib/chef/provider/service/gentoo.rb6
-rw-r--r--lib/chef/provider/service/init.rb7
-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.rb103
-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/service/windows.rb1
-rw-r--r--lib/chef/provider/template/content.rb10
-rw-r--r--lib/chef/provider/user.rb2
-rw-r--r--lib/chef/provider/user/aix.rb5
-rw-r--r--lib/chef/provider/user/dscl.rb8
-rw-r--r--lib/chef/provider/user/pw.rb1
-rw-r--r--lib/chef/provider/user/solaris.rb2
-rw-r--r--lib/chef/provider/user/useradd.rb1
-rw-r--r--lib/chef/provider/user/windows.rb2
-rw-r--r--lib/chef/provider/windows_script.rb8
-rw-r--r--lib/chef/provider_resolver.rb147
-rw-r--r--lib/chef/providers.rb2
-rw-r--r--lib/chef/recipe.rb9
-rw-r--r--lib/chef/resource.rb958
-rw-r--r--lib/chef/resource/action_provider.rb69
-rw-r--r--lib/chef/resource/apt_package.rb2
-rw-r--r--lib/chef/resource/bash.rb1
-rw-r--r--lib/chef/resource/batch.rb4
-rw-r--r--lib/chef/resource/bff_package.rb8
-rw-r--r--lib/chef/resource/breakpoint.rb8
-rw-r--r--lib/chef/resource/chef_gem.rb9
-rw-r--r--lib/chef/resource/cookbook_file.rb4
-rw-r--r--lib/chef/resource/cron.rb10
-rw-r--r--lib/chef/resource/csh.rb1
-rw-r--r--lib/chef/resource/deploy.rb290
-rw-r--r--lib/chef/resource/deploy_revision.rb14
-rw-r--r--lib/chef/resource/directory.rb6
-rw-r--r--lib/chef/resource/dpkg_package.rb5
-rw-r--r--lib/chef/resource/dsc_resource.rb82
-rw-r--r--lib/chef/resource/dsc_script.rb9
-rw-r--r--lib/chef/resource/easy_install_package.rb7
-rw-r--r--lib/chef/resource/env.rb8
-rw-r--r--lib/chef/resource/erl_call.rb6
-rw-r--r--lib/chef/resource/execute.rb5
-rw-r--r--lib/chef/resource/file.rb24
-rw-r--r--lib/chef/resource/file/verification.rb9
-rw-r--r--lib/chef/resource/freebsd_package.rb5
-rw-r--r--lib/chef/resource/gem_package.rb8
-rw-r--r--lib/chef/resource/git.rb3
-rw-r--r--lib/chef/resource/group.rb6
-rw-r--r--lib/chef/resource/homebrew_package.rb4
-rw-r--r--lib/chef/resource/http_request.rb6
-rw-r--r--lib/chef/resource/ifconfig.rb8
-rw-r--r--lib/chef/resource/ips_package.rb5
-rw-r--r--lib/chef/resource/link.rb10
-rw-r--r--lib/chef/resource/log.rb7
-rw-r--r--lib/chef/resource/lwrp_base.rb167
-rw-r--r--lib/chef/resource/macosx_service.rb58
-rw-r--r--lib/chef/resource/macports_package.rb10
-rw-r--r--lib/chef/resource/mdadm.rb7
-rw-r--r--lib/chef/resource/mount.rb14
-rw-r--r--lib/chef/resource/ohai.rb5
-rw-r--r--lib/chef/resource/openbsd_package.rb17
-rw-r--r--lib/chef/resource/package.rb9
-rw-r--r--lib/chef/resource/pacman_package.rb7
-rw-r--r--lib/chef/resource/paludis_package.rb5
-rw-r--r--lib/chef/resource/perl.rb2
-rw-r--r--lib/chef/resource/portage_package.rb2
-rw-r--r--lib/chef/resource/powershell_script.rb3
-rw-r--r--lib/chef/resource/python.rb2
-rw-r--r--lib/chef/resource/reboot.rb4
-rw-r--r--lib/chef/resource/registry_key.rb9
-rw-r--r--lib/chef/resource/remote_directory.rb8
-rw-r--r--lib/chef/resource/remote_file.rb13
-rw-r--r--lib/chef/resource/route.rb9
-rw-r--r--lib/chef/resource/rpm_package.rb2
-rw-r--r--lib/chef/resource/ruby.rb3
-rw-r--r--lib/chef/resource/ruby_block.rb5
-rw-r--r--lib/chef/resource/scm.rb7
-rw-r--r--lib/chef/resource/script.rb2
-rw-r--r--lib/chef/resource/service.rb19
-rw-r--r--lib/chef/resource/smartos_package.rb9
-rw-r--r--lib/chef/resource/solaris_package.rb15
-rw-r--r--lib/chef/resource/subversion.rb3
-rw-r--r--lib/chef/resource/template.rb4
-rw-r--r--lib/chef/resource/timestamped_deploy.rb4
-rw-r--r--lib/chef/resource/user.rb7
-rw-r--r--lib/chef/resource/whyrun_safe_ruby_block.rb6
-rw-r--r--lib/chef/resource/windows_package.rb32
-rw-r--r--lib/chef/resource/windows_script.rb5
-rw-r--r--lib/chef/resource/windows_service.rb6
-rw-r--r--lib/chef/resource/yum_package.rb16
-rw-r--r--lib/chef/resource/zypper_package.rb27
-rw-r--r--lib/chef/resource_builder.rb7
-rw-r--r--lib/chef/resource_definition.rb1
-rw-r--r--lib/chef/resource_reporter.rb15
-rw-r--r--lib/chef/resource_resolver.rb186
-rw-r--r--lib/chef/resources.rb2
-rw-r--r--lib/chef/rest.rb10
-rw-r--r--lib/chef/run_context.rb488
-rw-r--r--lib/chef/run_list/versioned_recipe_list.rb19
-rw-r--r--lib/chef/run_status.rb6
-rw-r--r--lib/chef/search/query.rb2
-rw-r--r--lib/chef/server_api.rb3
-rw-r--r--lib/chef/shell.rb14
-rw-r--r--lib/chef/user.rb29
-rw-r--r--lib/chef/user_v1.rb335
-rw-r--r--lib/chef/util/backup.rb10
-rw-r--r--lib/chef/util/dsc/resource_store.rb110
-rw-r--r--lib/chef/util/path_helper.rb132
-rw-r--r--lib/chef/util/powershell/cmdlet.rb48
-rw-r--r--lib/chef/util/powershell/cmdlet_result.rb21
-rw-r--r--lib/chef/util/powershell/ps_credential.rb42
-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/net_user.rb191
-rw-r--r--lib/chef/util/windows/volume.rb38
-rw-r--r--lib/chef/version.rb16
-rw-r--r--lib/chef/win32/api.rb6
-rw-r--r--lib/chef/win32/api/crypto.rb63
-rw-r--r--lib/chef/win32/api/file.rb20
-rw-r--r--lib/chef/win32/api/installer.rb4
-rw-r--r--lib/chef/win32/api/net.rb241
-rw-r--r--lib/chef/win32/api/registry.rb45
-rw-r--r--lib/chef/win32/api/security.rb24
-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.rb50
-rw-r--r--lib/chef/win32/eventlog.rb31
-rw-r--r--lib/chef/win32/file.rb31
-rw-r--r--lib/chef/win32/mutex.rb3
-rw-r--r--lib/chef/win32/net.rb344
-rw-r--r--lib/chef/win32/process.rb13
-rw-r--r--lib/chef/win32/registry.rb26
-rw-r--r--lib/chef/win32/security.rb53
-rw-r--r--lib/chef/win32/security/sid.rb17
-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.rb160
-rw-r--r--pedant.gemfile1
-rw-r--r--rubygems-pkg/rubygems-update-2.4.6.gembin0 -> 451072 bytes
-rw-r--r--spec/data/big_json_plus_one.json2
-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/lwrp/providers/buck_passer.rb23
-rw-r--r--spec/data/lwrp/providers/buck_passer_2.rb20
-rw-r--r--spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb16
-rw-r--r--spec/data/lwrp/resources/bar.rb2
-rw-r--r--spec/data/lwrp_override/resources/foo.rb5
-rw-r--r--spec/data/nested.json (renamed from spec/data/big_json.json)4
-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/audit/runner_spec.rb64
-rw-r--r--spec/functional/event_loggers/windows_eventlog_spec.rb14
-rw-r--r--spec/functional/file_content_management/deploy_strategies_spec.rb2
-rw-r--r--spec/functional/knife/ssh_spec.rb4
-rw-r--r--spec/functional/mixin/powershell_out_spec.rb43
-rw-r--r--spec/functional/provider/whyrun_safe_ruby_block_spec.rb2
-rw-r--r--spec/functional/rebooter_spec.rb4
-rwxr-xr-xspec/functional/resource/aixinit_service_spec.rb2
-rw-r--r--spec/functional/resource/cookbook_file_spec.rb2
-rw-r--r--spec/functional/resource/directory_spec.rb2
-rw-r--r--spec/functional/resource/dsc_resource_spec.rb93
-rw-r--r--spec/functional/resource/dsc_script_spec.rb93
-rwxr-xr-xspec/functional/resource/env_spec.rb7
-rw-r--r--spec/functional/resource/execute_spec.rb13
-rw-r--r--spec/functional/resource/file_spec.rb27
-rw-r--r--spec/functional/resource/group_spec.rb116
-rw-r--r--spec/functional/resource/link_spec.rb16
-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)86
-rw-r--r--spec/functional/resource/remote_directory_spec.rb2
-rw-r--r--spec/functional/resource/remote_file_spec.rb2
-rw-r--r--spec/functional/resource/template_spec.rb2
-rw-r--r--spec/functional/resource/user/dscl_spec.rb3
-rw-r--r--spec/functional/resource/user/useradd_spec.rb68
-rw-r--r--spec/functional/resource/user/windows_spec.rb125
-rw-r--r--spec/functional/shell_spec.rb35
-rw-r--r--spec/functional/util/powershell/cmdlet_spec.rb6
-rw-r--r--spec/functional/win32/crypto_spec.rb57
-rw-r--r--spec/functional/win32/registry_helper_spec.rb12
-rw-r--r--spec/functional/win32/service_manager_spec.rb4
-rw-r--r--spec/functional/win32/sid_spec.rb55
-rw-r--r--spec/integration/client/client_spec.rb146
-rw-r--r--spec/integration/client/ipv6_spec.rb2
-rw-r--r--spec/integration/knife/chef_repo_path_spec.rb24
-rw-r--r--spec/integration/knife/common_options_spec.rb6
-rw-r--r--spec/integration/knife/deps_spec.rb22
-rw-r--r--spec/integration/knife/upload_spec.rb18
-rw-r--r--spec/integration/recipes/lwrp_inline_resources_spec.rb4
-rw-r--r--spec/integration/recipes/lwrp_spec.rb53
-rw-r--r--spec/integration/recipes/provider_choice.rb36
-rw-r--r--spec/integration/recipes/recipe_dsl_spec.rb1492
-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/integration/solo/solo_spec.rb12
-rw-r--r--spec/spec_helper.rb49
-rw-r--r--spec/support/key_helpers.rb104
-rw-r--r--spec/support/lib/chef/provider/openldap_includer.rb29
-rw-r--r--spec/support/lib/chef/resource/cat.rb1
-rw-r--r--spec/support/lib/chef/resource/one_two_three_four.rb6
-rw-r--r--spec/support/lib/chef/resource/openldap_includer.rb27
-rw-r--r--spec/support/lib/chef/resource/with_state.rb9
-rw-r--r--spec/support/lib/chef/resource/zen_follower.rb5
-rw-r--r--spec/support/lib/chef/resource/zen_master.rb7
-rw-r--r--spec/support/mock/platform.rb2
-rw-r--r--spec/support/platform_helpers.rb22
-rw-r--r--spec/support/shared/context/client.rb277
-rw-r--r--spec/support/shared/examples/client.rb53
-rw-r--r--spec/support/shared/functional/file_resource.rb4
-rw-r--r--spec/support/shared/functional/securable_resource.rb70
-rw-r--r--spec/support/shared/functional/securable_resource_with_reporting.rb18
-rw-r--r--spec/support/shared/functional/win32_service.rb3
-rw-r--r--spec/support/shared/functional/windows_script.rb2
-rw-r--r--spec/support/shared/integration/integration_helper.rb11
-rw-r--r--spec/support/shared/shared_examples.rb2
-rw-r--r--spec/support/shared/unit/api_versioning.rb77
-rw-r--r--spec/support/shared/unit/knife_shared.rb40
-rw-r--r--spec/support/shared/unit/provider/file.rb39
-rw-r--r--spec/support/shared/unit/user_and_client_shared.rb115
-rw-r--r--spec/unit/api_client_spec.rb20
-rw-r--r--spec/unit/api_client_v1_spec.rb457
-rw-r--r--spec/unit/application/client_spec.rb27
-rw-r--r--spec/unit/application/solo_spec.rb7
-rw-r--r--spec/unit/audit/audit_reporter_spec.rb74
-rw-r--r--spec/unit/audit/logger_spec.rb42
-rw-r--r--spec/unit/audit/runner_spec.rb4
-rw-r--r--spec/unit/chef_class_spec.rb110
-rw-r--r--spec/unit/chef_fs/file_pattern_spec.rb18
-rw-r--r--spec/unit/chef_fs/path_util_spec.rb108
-rw-r--r--spec/unit/client_spec.rb427
-rw-r--r--spec/unit/config_spec.rb551
-rw-r--r--spec/unit/cookbook/cookbook_version_loader_spec.rb2
-rw-r--r--spec/unit/cookbook/file_vendor_spec.rb36
-rw-r--r--spec/unit/cookbook/metadata_spec.rb41
-rw-r--r--spec/unit/cookbook/syntax_check_spec.rb3
-rw-r--r--spec/unit/cookbook_loader_spec.rb2
-rw-r--r--spec/unit/cookbook_manifest_spec.rb21
-rw-r--r--spec/unit/cookbook_site_streaming_uploader_spec.rb21
-rw-r--r--spec/unit/cookbook_spec.rb9
-rw-r--r--spec/unit/cookbook_uploader_spec.rb8
-rw-r--r--spec/unit/cookbook_version_spec.rb22
-rw-r--r--spec/unit/data_bag_item_spec.rb2
-rw-r--r--spec/unit/data_bag_spec.rb4
-rw-r--r--spec/unit/deprecation_spec.rb64
-rw-r--r--spec/unit/dsl/reboot_pending_spec.rb2
-rw-r--r--spec/unit/dsl/resources_spec.rb85
-rw-r--r--spec/unit/environment_spec.rb2
-rw-r--r--spec/unit/event_dispatch/dispatcher_spec.rb80
-rw-r--r--spec/unit/event_dispatch/dsl_spec.rb83
-rw-r--r--spec/unit/exceptions_spec.rb6
-rw-r--r--spec/unit/file_content_management/deploy/mv_windows_spec.rb60
-rw-r--r--spec/unit/formatters/doc_spec.rb52
-rw-r--r--spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb77
-rw-r--r--spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb285
-rw-r--r--spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb7
-rw-r--r--spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb10
-rw-r--r--spec/unit/http/authenticator_spec.rb78
-rw-r--r--spec/unit/http/basic_client_spec.rb65
-rw-r--r--spec/unit/http/socketless_chef_zero_client_spec.rb174
-rw-r--r--spec/unit/http_spec.rb14
-rw-r--r--spec/unit/json_compat_spec.rb30
-rw-r--r--spec/unit/key_spec.rb634
-rw-r--r--spec/unit/knife/bootstrap_spec.rb43
-rw-r--r--spec/unit/knife/client_bulk_delete_spec.rb8
-rw-r--r--spec/unit/knife/client_create_spec.rb173
-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/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/core/ui_spec.rb16
-rw-r--r--spec/unit/knife/data_bag_from_file_spec.rb2
-rw-r--r--spec/unit/knife/environment_from_file_spec.rb2
-rw-r--r--spec/unit/knife/key_create_spec.rb224
-rw-r--r--spec/unit/knife/key_delete_spec.rb135
-rw-r--r--spec/unit/knife/key_edit_spec.rb267
-rw-r--r--spec/unit/knife/key_helper.rb74
-rw-r--r--spec/unit/knife/key_list_spec.rb216
-rw-r--r--spec/unit/knife/key_show_spec.rb126
-rw-r--r--spec/unit/knife/osc_user_create_spec.rb93
-rw-r--r--spec/unit/knife/osc_user_delete_spec.rb44
-rw-r--r--spec/unit/knife/osc_user_edit_spec.rb52
-rw-r--r--spec/unit/knife/osc_user_list_spec.rb (renamed from spec/unit/provider/powershell_spec.rb)29
-rw-r--r--spec/unit/knife/osc_user_reregister_spec.rb58
-rw-r--r--spec/unit/knife/osc_user_show_spec.rb46
-rw-r--r--spec/unit/knife/ssh_spec.rb67
-rw-r--r--spec/unit/knife/ssl_check_spec.rb4
-rw-r--r--spec/unit/knife/status_spec.rb72
-rw-r--r--spec/unit/knife/user_create_spec.rb228
-rw-r--r--spec/unit/knife/user_delete_spec.rb42
-rw-r--r--spec/unit/knife/user_edit_spec.rb45
-rw-r--r--spec/unit/knife/user_list_spec.rb12
-rw-r--r--spec/unit/knife/user_reregister_spec.rb55
-rw-r--r--spec/unit/knife/user_show_spec.rb46
-rw-r--r--spec/unit/knife_spec.rb45
-rw-r--r--spec/unit/log/syslog_spec.rb53
-rw-r--r--spec/unit/log/winevt_spec.rb55
-rw-r--r--spec/unit/lwrp_spec.rb467
-rw-r--r--spec/unit/mixin/api_version_request_handling_spec.rb127
-rw-r--r--spec/unit/mixin/command_spec.rb3
-rw-r--r--spec/unit/mixin/params_validate_spec.rb142
-rw-r--r--spec/unit/mixin/path_sanity_spec.rb4
-rw-r--r--spec/unit/mixin/powershell_out_spec.rb70
-rw-r--r--spec/unit/mixin/powershell_type_coercions_spec.rb72
-rw-r--r--spec/unit/mixin/template_spec.rb4
-rw-r--r--spec/unit/mixin/unformatter_spec.rb61
-rw-r--r--spec/unit/mixin/uris_spec.rb57
-rw-r--r--spec/unit/mixin/windows_architecture_helper_spec.rb21
-rw-r--r--spec/unit/node_map_spec.rb17
-rw-r--r--spec/unit/node_spec.rb9
-rw-r--r--spec/unit/platform/query_helpers_spec.rb24
-rw-r--r--spec/unit/platform_spec.rb67
-rw-r--r--spec/unit/policy_builder/policyfile_spec.rb71
-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/revision_spec.rb2
-rw-r--r--spec/unit/provider/deploy_spec.rb4
-rw-r--r--spec/unit/provider/directory_spec.rb365
-rw-r--r--spec/unit/provider/dsc_resource_spec.rb84
-rw-r--r--spec/unit/provider/dsc_script_spec.rb4
-rw-r--r--spec/unit/provider/execute_spec.rb2
-rw-r--r--spec/unit/provider/ifconfig/debian_spec.rb10
-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/aix_spec.rb44
-rw-r--r--spec/unit/provider/package/dpkg_spec.rb13
-rw-r--r--spec/unit/provider/package/freebsd/pkg_spec.rb26
-rw-r--r--spec/unit/provider/package/freebsd/pkgng_spec.rb18
-rw-r--r--spec/unit/provider/package/freebsd/port_spec.rb14
-rw-r--r--spec/unit/provider/package/ips_spec.rb44
-rw-r--r--spec/unit/provider/package/macports_spec.rb20
-rw-r--r--spec/unit/provider/package/openbsd_spec.rb104
-rw-r--r--spec/unit/provider/package/pacman_spec.rb10
-rw-r--r--spec/unit/provider/package/rpm_spec.rb459
-rw-r--r--spec/unit/provider/package/rubygems_spec.rb87
-rw-r--r--spec/unit/provider/package/smartos_spec.rb90
-rw-r--r--spec/unit/provider/package/solaris_spec.rb22
-rw-r--r--spec/unit/provider/package/windows_spec.rb129
-rw-r--r--spec/unit/provider/package/yum_spec.rb245
-rw-r--r--spec/unit/provider/package/zypper_spec.rb215
-rw-r--r--spec/unit/provider/package_spec.rb40
-rw-r--r--spec/unit/provider/powershell_script_spec.rb80
-rw-r--r--spec/unit/provider/registry_key_spec.rb12
-rw-r--r--spec/unit/provider/remote_directory_spec.rb4
-rw-r--r--spec/unit/provider/remote_file/fetcher_spec.rb21
-rw-r--r--spec/unit/provider/remote_file/local_file_spec.rb31
-rw-r--r--spec/unit/provider/remote_file/network_file_spec.rb45
-rw-r--r--spec/unit/provider/service/aix_service_spec.rb37
-rw-r--r--spec/unit/provider/service/freebsd_service_spec.rb12
-rw-r--r--spec/unit/provider/service/gentoo_service_spec.rb8
-rw-r--r--spec/unit/provider/service/macosx_spec.rb439
-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/user/dscl_spec.rb2
-rw-r--r--spec/unit/provider/user_spec.rb6
-rw-r--r--spec/unit/provider_resolver_spec.rb747
-rw-r--r--spec/unit/provider_spec.rb24
-rw-r--r--spec/unit/recipe_spec.rb85
-rw-r--r--spec/unit/registry_helper_spec.rb16
-rw-r--r--spec/unit/resource/batch_spec.rb1
-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.rb35
-rw-r--r--spec/unit/resource/directory_spec.rb2
-rw-r--r--spec/unit/resource/dsc_resource_spec.rb85
-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/ifconfig_spec.rb16
-rw-r--r--spec/unit/resource/link_spec.rb17
-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)1
-rw-r--r--spec/unit/resource/registry_key_spec.rb2
-rw-r--r--spec/unit/resource/remote_file_spec.rb15
-rw-r--r--spec/unit/resource/ruby_block_spec.rb4
-rw-r--r--spec/unit/resource/service_spec.rb8
-rw-r--r--spec/unit/resource/template_spec.rb2
-rw-r--r--spec/unit/resource/timestamped_deploy_spec.rb3
-rw-r--r--spec/unit/resource/user_spec.rb2
-rw-r--r--spec/unit/resource/windows_package_spec.rb18
-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.rb275
-rw-r--r--spec/unit/rest_spec.rb39
-rw-r--r--spec/unit/role_spec.rb4
-rw-r--r--spec/unit/run_context/child_run_context_spec.rb133
-rw-r--r--spec/unit/run_context_spec.rb79
-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/search/query_spec.rb24
-rw-r--r--spec/unit/shell_spec.rb10
-rw-r--r--spec/unit/user_spec.rb27
-rw-r--r--spec/unit/user_v1_spec.rb584
-rw-r--r--spec/unit/util/dsc/resource_store.rb76
-rw-r--r--spec/unit/util/path_helper_spec.rb233
-rw-r--r--spec/unit/util/powershell/ps_credential_spec.rb37
-rw-r--r--tasks/external_tests.rb29
-rw-r--r--tasks/maintainers.rb69
-rw-r--r--tasks/rspec.rb15
742 files changed, 35129 insertions, 8811 deletions
diff --git a/.gitignore b/.gitignore
index ecba9f4030..39962d2f5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,10 +2,12 @@
coverage
.DS_Store
pkg
+tags
*/tags
*~
# you should check in your Gemfile.lock in applications, and not in gems
+external_tests/*.lock
Gemfile.lock
Gemfile.local
diff --git a/.travis.yml b/.travis.yml
index 699d8237ad..d7ad317e28 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,5 @@
language: ruby
+cache: bundler
sudo: false
# Early warning system to catch if Rubygems breaks something
@@ -10,17 +11,12 @@ branches:
- master
- 10-stable
- 11-stable
- - 12-stable
# do not run expensive spec tests on PRs, only on branches
script: "
echo '--color\n-fp' > .rspec;
-if [ ${TRAVIS_PULL_REQUEST} = 'false' ];
-then
- bundle exec rake spec:all;
-else
- bundle exec rake spec;
-fi"
+bundle exec rake spec;
+"
env:
global:
@@ -28,14 +24,13 @@ env:
matrix:
include:
- - rvm: 2.0.0
- - rvm: 2.1.5
- - rvm: 2.2.0
- - rvm: 2.1.5
+ - rvm: 2.1
+ - rvm: 2.2
+ - rvm: 2.1
gemfile: pedant.gemfile
script: bundle exec rake pedant
### START TEST KITCHEN ONLY ###
- - rvm: 2.1.5
+ - rvm: 2.1
gemfile: kitchen-tests/Gemfile
before_install:
- echo -n $DO_KEY_CHUNK_{0..30} >> ~/.ssh/id_aws.base64
@@ -45,6 +40,8 @@ matrix:
script:
# FIXME: we should fix centos-6 against AWS and then enable it here
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen test ubuntu; fi
+ after_failure:
+ - cat .kitchen/logs/kitchen.log
after_script:
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then bundle exec kitchen destroy ubuntu; fi
env:
@@ -80,6 +77,7 @@ matrix:
- secure: QHuMdtFCvttiIOx6iS+lH4bKXZMwsgVQ6FPsUW5zJ7uw6mAEWKEil9xNk4aYV9FywinwUs4fnFlnIW/Gj1gLkUjm4DtxdmRZIlRXIbgsNch6H916TCPg4Q2oPsW2nVdXPjW/2jhkfLUiSnuhL+ylami1NF8Up7vokXknh/jFNZU=
- secure: GTfrUVmMQSxho3Ia4Y1ONqKvVMD34GHF2/TJb8UdQV7iH+nVxVXpy3nWaCXa9ri7lRzMefkoVLy0gKK13YoVd7w3d2S3/IfNakC85XfN6VuOzK/FDkA0WoPrgKjcQ64I+3dQ6cgrMWWTieKwRZy+Ve24iRbnN055Hk+VRMu6OGw=
- secure: SOMYGVfHLkHsH6koxpw68YQ4ydEo6YXPhHbrYGQbehUbFa6+OZzBcAJRJbKjyhD2AZRvNr2jB8XnjYKvVyDGQRpkWhGYZ7CpHqINpDsqKBsbiMe3/+KmKQqS+UKxNGefquoOvyQ1N8Xy77dkWYokRtGMEuR12RkZLonxiDW8Qyg=
+ - secure: bSsDg+dJnPFdFiC/tbb61HdLh/Q0z2RVVAReT1wvV1BN4fN4NydvkUGbQmyFNyyunLulEs+X0oFma9L0497nUlTnan8UOg9sIleTSybPX6E9xSKKCItH1GgDw8bM9Igez5OOrrePBD3altVrH+FmGx0dlTQgM/KZMN50BJ79cXw=
### END TEST KITCHEN ONLY ###
notifications:
on_change: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd0615b080..22f02eee2f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,11 +1,205 @@
## 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
-* make deploy resource attributes nillable (`symlink_before_migrate nil`) works now
-* mixin the LWRP attribute DSL method into Chef::Resource directly
-* make all LWRP attributes nillable
-* windows service now has a configurable timeout
+* [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):
+ Fix multipackage and architectures
+* [**Igor Shpakov**](https://github.com/Igorshp):
+ Always run exception handlers
+ Prioritise manual ssh attribute over automatic ones for knife
+* [**Noah Kantrowitz**](https://github.com/coderanger):
+ Cache service\_resource\_providers for the duration of the run. #2953
+* [**Slava Kardakov**](https://github.com/ojab):
+ Fix installation of yum packages with version constraints #3155
+* [**Dave Eddy**](https://github.com/bahamas10):
+ fix smartos\_package for new "pkgin" output, fixes #3112 #3165
+* [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly):
+ Show Chef version on chef shell prompt
+* [**Jacob Minshall**](https://github.com/minshallj):
+ Ensure suid bit is preserved if group or owner changes
+* [**Tim Smith**](https://github.com/tas50):
+ Convert wiki links to point to docs.chef.io
+* [**SAWANOBORI Yukihiko**](https://github.com/sawanoboly):
+ Add Chef::Log::Syslog class for integrating sending logs to syslog
+* [**Pavel Yudin**](https://github.com/Kasen):
+ Ensure LWRP and HWRP @action variable is consistent #3156
+* [**Dan Bjorge**](https://github.com/dbjorge):
+ Fix bad Windows securable\_resource functional spec assumptions for default file owners/groups #3266
+* [**Yukihiko SAWANOBORI**](https://github.com/sawanoboly): Pass name by
+ knife cil attribute [pr#3195](https://github.com/chef/chef/pull/3195)
+* [**Torben Knerr**](https://github.com/tknerr):
+ Allow knife sub-command loader to match platform specific gems. [pr#3281](https://github.com/chef/chef/pull/3281)
+* [**Steve Lowe**](https://github.com/SteveLowe):
+ Fix copying ntfs dacl and sacl when they are nil. [pr#3066](https://github.com/chef/chef/pull/3066)
+
+* [pr#3339](https://github.com/chef/chef/pull/3339): Powershell command wrappers to make argument passing to knife/chef-client etc. easier.
+* [pr#3720](https://github.com/chef/chef/pull/3270): Extract chef's configuration to a separate gem. Code stays in the Chef git repo.
+* [pr#3321](https://github.com/chef/chef/pull/3321): Add an integration test of chef-client with empty ENV.
+* [pr#3278](https://github.com/chef/chef/pull/3278): Switch over Windows builds to universal builds.
+* [pr#2877](https://github.com/chef/chef/pull/2877): Convert bootstrap template to use sh.
+* [Issue #3316](https://github.com/chef/chef/issues/3316): Fix idempotency issues with the `windows_package` resource
+* [pr#3295](https://github.com/chef/chef/pull/3295): Stop mutating `new_resource.checksum` in file providers. Fixes some ChecksumMismatch exceptions like [issue#3168](https://github.com/chef/chef/issues/3168)
+* [pr#3320](https://github.com/chef/chef/pull/3320): Sanitize non-UTF8 characters in the node data before doing node.save(). Works around many UTF8 exception issues reported on node.save().
+* Implemented X-Ops-Server-API-Version with a API version of 0, as well as error handling when the Chef server does not support the API version that the client supports.
+* [pr#3327](https://github.com/chef/chef/pull/3327): Fix unreliable AIX service group parsing mechanism.
+* [pr#3333](https://github.com/chef/chef/pull/3333): Fix SSL errors when connecting to private Supermarkets
+* [pr#3340](https://github.com/chef/chef/pull/3340): Allow Event dispatch subscribers to be inspected.
+* [Issue #3055](https://github.com/chef/chef/issues/3055): Fix regex parsing for recipe failures on Windows
+* [pr#3345](https://github.com/chef/chef/pull/3345): Windows Event log logger
+* [pr#3336](https://github.com/chef/chef/pull/3336): Remote file understands UNC paths
+* [pr#3269](https://github.com/chef/chef/pull/3269): Deprecate automatic recipe DSL for classes in `Chef::Resource`
+* [pr#3360](https://github.com/chef/chef/pull/3360): Add check_resource_semantics! lifecycle method to provider
+* [pr#3344](https://github.com/chef/chef/pull/3344): Rewrite Windows user resouce code to use ffi instead of win32-api
+* [pr#3318](https://github.com/chef/chef/pull/3318): Modify Windows package provider to allow for url source
+* [pr#3381](https://github.com/chef/chef/pull/3381): warn on cookbook self-deps
+* [pr#2312](https://github.com/chef/chef/pull/2312): fix `node[:recipes]` duplication, add `node[:cookbooks]` and `node[:expanded_run_list]`
+* [pr#3325](https://github.com/chef/chef/pull/3325): enforce passing a node name with validatorless bootstrapping
+* [pr#3398](https://github.com/chef/chef/pull/3398): Allow spaces in files for the `remote_file` resource
+* [Issue #3010](https://github.com/chef/chef/issues/3010) Fixed `knife user` for use with current and future versions of Chef Server 12, with continued backwards compatible support for use with Open Source Server 11.
+* [pr#3438](https://github.com/chef/chef/pull/3438) Server API V1 support. Vast improvements to and testing expansion for Chef::User, Chef::ApiClient, and related knife commands. Deprecated Open Source Server 11 user support to the Chef::OscUser and knife osc_user namespace, but with backwards compatible support via knife user.
+* [Issue #2247](https://github.com/chef/chef/issues/2247): `powershell_script` returns 0 for scripts with syntax errors
+* [pr#3080](https://github.com/chef/chef/pull/3080): Issue 2247: `powershell_script` exit status should be nonzero for syntax errors
+* [pr#3441](https://github.com/chef/chef/pull/3441): Add `powershell_out` mixin to core chef
+* [pr#3448](https://github.com/chef/chef/pull/3448): Fix `dsc_resource` to work with wmf5 april preview
+* [pr#3392](https://github.com/chef/chef/pull/3392): Comment up `Chef::Client` and privatize/deprecate unused things
+* [pr#3419](https://github.com/chef/chef/pull/3419): Fix cli issue with `chef_repo_path` when ENV variable is unset
+* [pr#3358](https://github.com/chef/chef/pull/3358): Separate audit and converge failures
+* [pr#3431](https://github.com/chef/chef/pull/3431): Fix backups on windows for the file resource
+* [pr#3397](https://github.com/chef/chef/pull/3397): Validate owner exists in directory resources
+* [pr#3418](https://github.com/chef/chef/pull/3418): Add `shell_out` mixin to Chef::Resource class for use in `not_if`/`only_if` conditionals, etc.
+* [pr#3406](https://github.com/chef/chef/pull/3406): Add wide-char 'Environment' to `broadcast_env_change` mixin for setting windows environment variables
+* [pr#3442](https://github.com/chef/chef/pull/3442): Add `resource_name` to top-level Resource class to make defining resources easier.
+* [pr#3447](https://github.com/chef/chef/pull/3447): Add `allowed_actions` and `default_action` to top-level Resource class.
+* [pr#3475](https://github.com/chef/chef/pull/3475): Fix `shell_out` timeouts in all package providers to respect timeout property on the resource.
+* [pr#3477](https://github.com/chef/chef/pull/3477): Update `zypper_package` to look like the rest of our package classes.
+* [pr#3483](https://github.com/chef/chef/pull/3483): Allow `include_recipe` from LWRP providers.
+* [pr#3495](https://github.com/chef/chef/pull/3495): Make resource name automatically determined from class name, and provide DSL for it.
+* [pr#3497](https://github.com/chef/chef/pull/3497): Issue 3485: Corruption of node's run\_context when non-default guard\_interpreter is evaluated
+* [pr#3299](https://github.com/chef/chef/pull/3299): Remove experimental warning on audit mode
+
+## 12.3.0
+
+* [pr#3160](https://github.com/chef/chef/pull/3160): Use Chef Zero in
+ socketless mode for local mode, add `--no-listen` flag to disable port
+ binding
+* [**Nolan Davidson**](https://github.com/nsdavidson):
+ Removed after_created and added test to recipe_spec
+* [**Tim Sogard**](https://github.com/drags):
+ Reset $HOME to user running chef-client when running via sudo
+* [**Torben Knerr**](https://github.com/tknerr):
+ Allow for the chef gem installation to succeed without elevated privileges #3126
+* [**Mike Dodge**](https://github.com/mikedodge04)
+ MacOSX services: Load LaunchAgents as console user, adding plist and
+ session_type options.
+* [**Eric Herot**](https://github.com/eherot)
+ Ensure knife ssh doesn't use a non-existant field for hostname #3131
+* [**Tom Hughes**](https://github.com/tomhughes)
+ Ensure searches progress in the face of incomplete responses #3135
+
+* [pr#3162](https://github.com/chef/chef/pull/3162): Add
+ `--minimal-ohai` flag to client/solo/apply; restricts ohai to only the
+ bare minimum of plugins.
+* Ensure link's path attribute works with delayed #3130
+* gem_package, chef_gem should not shell out to using https://rubygems.org #2867
+* Add dynamic resource resolution similar to dynamic provider resolution
+* Add Chef class fascade to internal structures
+* Fix nil pointer for windows event logger #3200
+* Use partial search for knife status
+* Ensure chef/knife properly honours proxy config
+
+## 12.2.1
+* [Issue 3153](https://github.com/chef/chef/issues/3153): Fix bug where unset HOME would cause chef to crash
+
+## 12.2.0
+* Update policyfile API usage to match forthcoming Chef Server release
* `knife ssh` now has an --exit-on-error option that allows users to
fail-fast rather than moving on to the next machine.
+* migrate macosx, windows, openbsd, and netbsd resources to dynamic resolution
+* migrate cron and mdadm resources to dynamic resolution
+* [Issue 3096](https://github.com/chef/chef/issues/3096) Fix OpenBSD package provider installation issues
+* New `dsc_resource` resource to invoke Powershell DSC resources
+
+## 12.1.2
+* [Issue 3022](https://github.com/chef/chef/issues/3022): Homebrew Cask install fails
+ FIXME (remove on 12.2.0 release): 3022 was only merged to 12-stable and #3077 or its descendant should fix this
+* [Issue 3059](https://github.com/chef/chef/issues/3059): Chef 12.1.1 yum_package silently fails
+* [Issue 3078](https://github.com/chef/chef/issues/3078): Compat break in audit-mode changes
+
+## 12.1.1
+* [**Phil Dibowitz**](https://github.com/jaymzh):
+ [Issue 3008](https://github.com/chef/chef/issues/3008) Allow people to pass in `source` to package
+* [Issue 3011](https://github.com/chef/chef/issues/3011) `package` provider base should include
+ `Chef::Mixin::Command` as there are still providers that use it.
+* [**Ranjib Dey**](https://github.com/ranjib):
+ [Issue 3019](https://github.com/chef/chef/issues/3019) Fix data fetching when explicit attributes are passed
## 12.1.0
@@ -111,6 +305,7 @@
* Add declare_resource/build_resource comments, fix faulty ||=
* Knife bootstrap creates a client and ships it to the node to implement validatorless bootstraps
* Knife bootstrap can use the client it creates to setup chef-vault items for the node
+* windows service now has a configurable timeout
## 12.0.3
* [**Phil Dibowitz**](https://github.com/jaymzh):
diff --git a/CHEF_MVPS.md b/CHEF_MVPS.md
index 28dbe3c8e8..dd46ea9174 100644
--- a/CHEF_MVPS.md
+++ b/CHEF_MVPS.md
@@ -2,9 +2,11 @@
Every release of Chef we pick someone from the community to name as the Most Valuable Player for that release. It could be someone who provided a big feature, reported a security vulnerability, or someone doing great things in the community that we want to highlight.
+In addition to the Hall of Fame and MVP recipients, three individuals are awarded the distinction of "Awesome Community Chef" each year at ChefConf.
+
#### Hall of Fame
-After receiving three MVP awards, we add someone to the hall of fame. We want to express our gratitude to their continuing participation and give newer community members the opportunity to be reconignized.
+After receiving three MVP awards, we add someone to the hall of fame. We want to express our gratitude to their continuing participation and give newer community members the opportunity to be recognized.
* Matthew Kent
* Doug MacEachern
@@ -18,71 +20,89 @@ After receiving three MVP awards, we add someone to the hall of fame. We want to
| Release | Date | MVP |
|---------|------|-----|
-| [Client 11.16.0](http://www.getchef.com/blog/2014/09/08/release-chef-client-11-16-0-ohai-7-4-0/) | 2014-09-08 | Jesse Hu |
-| [Client 11.14.2](http://www.getchef.com/blog/2014/08/01/release-chef-client-11-14-2/) | 2014-08-01 | Nikhil Benesch |
-| [Client 11.12.0](http://www.getchef.com/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/) | 2014-04-08 | Chris Bandy |
-| [Client 11.10.4](http://www.getchef.com/blog/2014/02/20/chef-client-patch-release-11-10-4/) | 2014-02-20 | Jon Cowie |
-| [Client 11.10.2](http://www.getchef.com/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Eric Tucker |
-| [Client 11.10.0](http://www.getchef.com/blog/2014/02/06/chef-client-11-10-0-release/) | 2014-02-06 | Nikhil Benesch |
-| [Client 11.8.2](http://www.getchef.com/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | James Ogden |
-| [Client 11.8.0](http://www.opscode.com/blog/2013/10/31/release-chef-client-11-8-0-ohai-6-20-0/) | 2013-10-31 | Eric Saxby |
-| [Client 11.6.2](http://www.getchef.com/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine |
-| [Client 11.6.0](http://www.opscode.com/blog/2013/07/23/chef-client-11-6-0-ohai-6-18-0-and-more/) | 2013-07-23 | Jesse Campbell |
-| [Client 11.4.0](http://www.opscode.com/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Vaidas Jablonskis |
-| [Client 11.2.0](http://www.opscode.com/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Mike Javorski |
-| [Chef 11.0.0](http://www.opscode.com/blog/2013/02/04/chef-11-released/) | 2013-02-04 | Andrea Campi, Bryan Berry |
-| [Chef 10.32.2](http://www.getchef.com/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/) | 2014-04-08 | Phil Dibowitz |
-| [Chef 10.30.4](http://www.getchef.com/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Christopher Laco |
-| [Chef 10.30.2](http://www.getchef.com/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | Phil Dibowitz |
-| [Chef 10.28.2](http://www.getchef.com/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine |
-| [Chef 10.28.0](http://www.opscode.com/blog/2013/09/03/chef-10-28-0-released/) | 2013-09-03 | Jeff Blaine |
-| [Chef 10.26.0](http://www.opscode.com/blog/2013/05/08/chef-10-26-0-released/) | 2013-05-08 | Ranjib Dey |
-| [Chef 10.24.0](http://www.opscode.com/blog/2013/02/15/chef-server-11-0-6-and-10-24-0-released/) | 2013-02-15 | Anthony Goddard |
-| [Chef 10.22.0](http://www.opscode.com/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Brian Bianco |
-| [Chef 10.20.0](http://www.opscode.com/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Chris Roberts |
-| [Chef 10.18.2](http://www.opscode.com/blog/2013/01/18/chef-10-18-2-bugfix-release/) | 2013-01-18 | Fletcher Nichol |
-| [Chef 10.18.0](http://www.opscode.com/blog/2013/01/16/chef-10-18-0-released/) | 2013-01-16 | Xabier de Zuazo |
-| [Chef 10.16.6](http://www.opscode.com/blog/2013/01/11/chef-10-16-6-security-release/) | 2013-01-11 | Dan Kubb |
-| [Chef 10.16.4](http://www.opscode.com/blog/2012/12/26/chef-10-16-4-released/) | 2012-12-26 | Avishai Ish-Shalom |
-| [Chef 10.16.2](http://www.opscode.com/blog/2012/10/26/chef-10-16-2-released/) | 2012-10-26 | Jamie Winsor |
-| [Chef 10.16.0](http://www.opscode.com/blog/2012/10/22/chef-10-16-0-released/) | 2012-10-22 | John Dewey |
-| [Chef 10.14.4](http://www.opscode.com/blog/2012/09/28/chef-10-14-4-released/) | 2012-09-27 | Kendrick Martin |
-| [Chef 10.14.2](http://www.opscode.com/blog/2012/09/11/chef-10-14-2-released/) | 2012-09-10 | Phil Dibowitz, Tim Smith |
-| [Chef 10.14.0](http://www.opscode.com/blog/2012/09/07/chef-10-14-0-released/) | 2012-09-07 | Xabier de Zuazo |
-| [Chef 10.12.0](http://www.opscode.com/blog/2012/06/19/chef-10-12-0-released/) | 2012-06-18 | Chris Roberts |
-| [Chef 0.10.10](http://www.opscode.com/blog/2012/05/11/chef-0-10-10-released/) | 2012-05-11 | Juanje Ojeda, Igor Afonov |
-| [Chef 0.10.8](http://www.opscode.com/blog/2011/12/15/chef-0-10-8-released/) | 2011-12-15 | Bryan Berry |
-| [Chef 0.10.6](http://www.opscode.com/blog/2011/12/14/chef-0-10-6-released/) | 2011-12-13 | Andrea Campi |
-| [Chef 0.10.4](http://www.opscode.com/blog/2011/08/11/chef-0-10-4-released/) | 2011-08-11 | Matthew Kent |
-| [Chef 0.10.2](http://www.opscode.com/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Daniel Oliver |
-| [Chef 0.10.0](http://www.opscode.com/blog/2011/05/02/chef-0-10-0-released/) | 2011-05-02 | Grace Mollison, Darrin Eden |
-| [Chef 0.9.18](http://www.opscode.com/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Jesai Langenbach |
-| [Chef 0.9.16](http://www.opscode.com/blog/2011/04/15/chef-0-9-16-released/) | 2011-04-15 | Michael Leinartas |
-| [Chef 0.9.14](http://www.opscode.com/blog/2011/03/04/chef-0-9-14-released/) | 2011-03-04 | Gilles Devaux |
-| [Chef 0.9.12](http://www.opscode.com/blog/2010/10/22/chef-0-9-12-released/) | 2010-10-22 | Laurent Désarmes |
-| [Chef 0.9.10](http://www.opscode.com/blog/2010/10/19/chef-0-9-10-ohai-0-5-8-and-mixliblog-1-2-0-released/) | 2010-10-19 | Toomas Pelberg, Tommy Bishop |
-| [Chef 0.9.8](http://www.opscode.com/blog/2010/08/05/chef-0-9-8-and-mixlib-authentication-1-1-4-released/) | 2010-08-05 | Joe Williams |
-| [Chef 0.9.6](http://www.opscode.com/blog/2010/07/03/chef-0-9-6-released/) | 2010-07-03 | Caleb Tennis |
-| [Chef 0.9.4](http://www.opscode.com/blog/2010/06/30/chef-0-9-4-released/) | 2010-06-30 | Ian Meyer |
-| [Chef 0.9.0](http://www.opscode.com/blog/2010/06/21/chef-0-9-0-and-ohai-0-5-6-released/) | 2010-06-21 | Doug MacEachern |
-| [Chef 0.8.16](http://www.opscode.com/blog/2010/05/11/chef-0-8-16-and-ohai-0-5-4-release/) | 2010-05-11 | Akzhan Abdulin |
-| [Chef 0.8.14](http://www.opscode.com/blog/2010/05/07/chef-0-8-14-release/) | 2010-05-07 | Renaud Chaput |
-| [Chef 0.8.10](http://www.opscode.com/blog/2010/04/02/chef-0-8-10-release/) | 2010-04-02 | Thom May, Tollef Fog Heen |
-| [Chef 0.8.8](http://www.opscode.com/blog/2010/03/18/chef-0-8-8-release/) | 2010-03-18 | Eric Hankins |
-| [Chef 0.8.6](http://www.opscode.com/blog/2010/03/05/chef-0-8-6-release/) | 2010-03-05 | Ian Meyer |
-| [Chef 0.8.4](http://www.opscode.com/blog/2010/03/02/chef-0-8-4-release/) | 2010-03-02 | Tollef Fog Heen |
-| [Chef 0.8.2](http://www.opscode.com/blog/2010/03/01/chef-0-8-2-release/) | 2010-03-01 | Scott M. Likens |
-| [Chef 0.7.16](http://www.opscode.com/blog/2009/12/22/chef-0-7-16-release/) | 2009-12-22 | Bryan McLellan |
-| [Chef 0.7.14](http://www.opscode.com/blog/2009/10/26/chef-0-7-14-ohai-0-3-6-releases/) | 2009-10-16 | Thom May |
-| [Chef 0.7.12](http://www.opscode.com/blog/2009/10/06/chef-0-7-12rc0-ohai-0-3-4rc0-releases/) | 2009-10-06 | Diego Algorta |
-| [Chef 0.7.10](http://www.opscode.com/blog/2009/09/04/chef-0-7-10-release/) | 2009-09-04 | Dan DeLeo |
-| [Chef 0.7.8](http://www.opscode.com/blog/2009/08/13/chef-0-7-8-release/) | 2009-08-13 | Jeppe Nejsum Madsen |
-| [Chef 0.7.6](http://www.opscode.com/blog/2009/08/08/chef-0-7-6-release/) | 2009-08-08 | Grant Zanetti |
-| [Chef 0.7.4](http://www.opscode.com/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Hongli Lai |
-| [Chef 0.7.2](http://www.opscode.com/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Joshua Sierles |
-| [Chef 0.7.0](http://www.opscode.com/blog/2009/06/10/chef-0-7-0-release/) | 2009-06-10 | Matthew Kent |
-| [Chef 0.6.2](http://www.opscode.com/blog/2009/04/29/chef-0-6-2-release/) | 2009-04-29 | David Balatero |
-| [Chef 0.6.0](http://www.opscode.com/blog/2009/04/29/chef-0-6-0-release/) | 2009-04-29 | Matthew Kent |
-| [Chef 0.5.6](http://www.opscode.com/blog/2009/03/06/chef-0-5-6/) | 2009-03-06 | Sean Cribbs |
-| [Chef 0.5.4](http://www.opscode.com/blog/2009/02/13/chef-0-5-4/) | 2009-02-13 | Arjuna Christensen |
-| [Chef 0.5.2](http://www.opscode.com/blog/2009/02/01/chef-0-5-2-and-ohai-0-1-4/) | 2009-02-01 | Bryan McLellan |
+| [Client 11.16.0](https://www.chef.io/blog/2014/09/08/release-chef-client-11-16-0-ohai-7-4-0/) | 2014-09-08 | Jesse Hu |
+| [Client 11.14.2](https://www.chef.io/blog/2014/08/01/release-chef-client-11-14-2/) | 2014-08-01 | Nikhil Benesch |
+| [Client 11.12.0](https://www.chef.io/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/) | 2014-04-08 | Chris Bandy |
+| [Client 11.10.4](https://www.chef.io/blog/2014/02/20/chef-client-patch-release-11-10-4/) | 2014-02-20 | Jon Cowie |
+| [Client 11.10.2](https://www.chef.io/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Eric Tucker |
+| [Client 11.10.0](https://www.chef.io/blog/2014/02/06/chef-client-11-10-0-release/) | 2014-02-06 | Nikhil Benesch |
+| [Client 11.8.2](https://www.chef.io/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | James Ogden |
+| [Client 11.8.0](https://www.chef.io/blog/2013/10/31/release-chef-client-11-8-0-ohai-6-20-0/) | 2013-10-31 | Eric Saxby |
+| [Client 11.6.2](https://www.chef.io/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine |
+| [Client 11.6.0](https://www.chef.io/blog/2013/07/23/chef-client-11-6-0-ohai-6-18-0-and-more/) | 2013-07-23 | Jesse Campbell |
+| [Client 11.4.0](https://www.chef.io/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Vaidas Jablonskis |
+| [Client 11.2.0](https://www.chef.io/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Mike Javorski |
+| [Chef 11.0.0](https://www.chef.io/blog/2013/02/04/chef-11-released/) | 2013-02-04 | Andrea Campi, Bryan Berry |
+| [Chef 10.32.2](https://www.chef.io/blog/2014/04/08/release-chef-client-11-12-0-10-32-2/) | 2014-04-08 | Phil Dibowitz |
+| [Chef 10.30.4](https://www.chef.io/blog/2014/02/18/chef-client-release-11-10-2-10-30-4/) | 2014-02-18 | Christopher Laco |
+| [Chef 10.30.2](https://www.chef.io/blog/2013/12/06/release-chef-client-10-30-2-11-8-2-mixlib-shellout-1-3-0/) | 2013-12-06 | Phil Dibowitz |
+| [Chef 10.28.2](https://www.chef.io/blog/2013/10/08/release-chef-client-11-6-2-10-28-2/) | 2013-10-08 | Jeff Blaine |
+| [Chef 10.28.0](https://www.chef.io/blog/2013/09/03/chef-10-28-0-released/) | 2013-09-03 | Jeff Blaine |
+| [Chef 10.26.0](https://www.chef.io/blog/2013/05/08/chef-10-26-0-released/) | 2013-05-08 | Ranjib Dey |
+| [Chef 10.24.0](https://www.chef.io/blog/2013/02/15/chef-server-11-0-6-and-10-24-0-released/) | 2013-02-15 | Anthony Goddard |
+| [Chef 10.22.0](https://www.chef.io/blog/2013/02/13/chef-client-11-4-0-10-22-0-released/) | 2013-02-13 | Brian Bianco |
+| [Chef 10.20.0](https://www.chef.io/blog/2013/02/07/chef-client-11-2-0-10-20-0-released/) | 2013-02-06 | Chris Roberts |
+| [Chef 10.18.2](https://www.chef.io/blog/2013/01/18/chef-10-18-2-bugfix-release/) | 2013-01-18 | Fletcher Nichol |
+| [Chef 10.18.0](https://www.chef.io/blog/2013/01/16/chef-10-18-0-released/) | 2013-01-16 | Xabier de Zuazo |
+| [Chef 10.16.6](https://www.chef.io/blog/2013/01/11/chef-10-16-6-security-release/) | 2013-01-11 | Dan Kubb |
+| [Chef 10.16.4](https://www.chef.io/blog/2012/12/26/chef-10-16-4-released/) | 2012-12-26 | Avishai Ish-Shalom |
+| [Chef 10.16.2](https://www.chef.io/blog/2012/10/26/chef-10-16-2-released/) | 2012-10-26 | Jamie Winsor |
+| [Chef 10.16.0](https://www.chef.io/blog/2012/10/22/chef-10-16-0-released/) | 2012-10-22 | John Dewey |
+| [Chef 10.14.4](https://www.chef.io/blog/2012/09/28/chef-10-14-4-released/) | 2012-09-27 | Kendrick Martin |
+| [Chef 10.14.2](https://www.chef.io/blog/2012/09/11/chef-10-14-2-released/) | 2012-09-10 | Phil Dibowitz, Tim Smith |
+| [Chef 10.14.0](https://www.chef.io/blog/2012/09/07/chef-10-14-0-released/) | 2012-09-07 | Xabier de Zuazo |
+| [Chef 10.12.0](https://www.chef.io/blog/2012/06/19/chef-10-12-0-released/) | 2012-06-18 | Chris Roberts |
+| [Chef 0.10.10](https://www.chef.io/blog/2012/05/11/chef-0-10-10-released/) | 2012-05-11 | Juanje Ojeda, Igor Afonov |
+| [Chef 0.10.8](https://www.chef.io/blog/2011/12/15/chef-0-10-8-released/) | 2011-12-15 | Bryan Berry |
+| [Chef 0.10.6](https://www.chef.io/blog/2011/12/14/chef-0-10-6-released/) | 2011-12-13 | Andrea Campi |
+| [Chef 0.10.4](https://www.chef.io/blog/2011/08/11/chef-0-10-4-released/) | 2011-08-11 | Matthew Kent |
+| [Chef 0.10.2](https://www.chef.io/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Daniel Oliver |
+| [Chef 0.10.0](https://www.chef.io/blog/2011/05/02/chef-0-10-0-released/) | 2011-05-02 | Grace Mollison, Darrin Eden |
+| [Chef 0.9.18](https://www.chef.io/blog/2011/06/29/chef-0-10-2-and-0-9-18-released/) | 2011-06-29 | Jesai Langenbach |
+| [Chef 0.9.16](https://www.chef.io/blog/2011/04/15/chef-0-9-16-released/) | 2011-04-15 | Michael Leinartas |
+| [Chef 0.9.14](https://www.chef.io/blog/2011/03/04/chef-0-9-14-released/) | 2011-03-04 | Gilles Devaux |
+| [Chef 0.9.12](https://www.chef.io/blog/2010/10/22/chef-0-9-12-released/) | 2010-10-22 | Laurent Désarmes |
+| [Chef 0.9.10](https://www.chef.io/blog/2010/10/19/chef-0-9-10-ohai-0-5-8-and-mixliblog-1-2-0-released/) | 2010-10-19 | Toomas Pelberg, Tommy Bishop |
+| [Chef 0.9.8](https://www.chef.io/blog/2010/08/05/chef-0-9-8-and-mixlib-authentication-1-1-4-released/) | 2010-08-05 | Joe Williams |
+| [Chef 0.9.6](https://www.chef.io/blog/2010/07/03/chef-0-9-6-released/) | 2010-07-03 | Caleb Tennis |
+| [Chef 0.9.4](https://www.chef.io/blog/2010/06/30/chef-0-9-4-released/) | 2010-06-30 | Ian Meyer |
+| [Chef 0.9.0](https://www.chef.io/blog/2010/06/21/chef-0-9-0-and-ohai-0-5-6-released/) | 2010-06-21 | Doug MacEachern |
+| [Chef 0.8.16](https://www.chef.io/blog/2010/05/11/chef-0-8-16-and-ohai-0-5-4-release/) | 2010-05-11 | Akzhan Abdulin |
+| [Chef 0.8.14](https://www.chef.io/blog/2010/05/07/chef-0-8-14-release/) | 2010-05-07 | Renaud Chaput |
+| [Chef 0.8.10](https://www.chef.io/blog/2010/04/02/chef-0-8-10-release/) | 2010-04-02 | Thom May, Tollef Fog Heen |
+| [Chef 0.8.8](https://www.chef.io/blog/2010/03/18/chef-0-8-8-release/) | 2010-03-18 | Eric Hankins |
+| [Chef 0.8.6](https://www.chef.io/blog/2010/03/05/chef-0-8-6-release/) | 2010-03-05 | Ian Meyer |
+| [Chef 0.8.4](https://www.chef.io/blog/2010/03/02/chef-0-8-4-release/) | 2010-03-02 | Tollef Fog Heen |
+| [Chef 0.8.2](https://www.chef.io/blog/2010/03/01/chef-0-8-2-release/) | 2010-03-01 | Scott M. Likens |
+| [Chef 0.7.16](https://www.chef.io/blog/2009/12/22/chef-0-7-16-release/) | 2009-12-22 | Bryan McLellan |
+| [Chef 0.7.14](https://www.chef.io/blog/2009/10/26/chef-0-7-14-ohai-0-3-6-releases/) | 2009-10-16 | Thom May |
+| [Chef 0.7.12](https://www.chef.io/blog/2009/10/06/chef-0-7-12rc0-ohai-0-3-4rc0-releases/) | 2009-10-06 | Diego Algorta |
+| [Chef 0.7.10](https://www.chef.io/blog/2009/09/04/chef-0-7-10-release/) | 2009-09-04 | Dan DeLeo |
+| [Chef 0.7.8](https://www.chef.io/blog/2009/08/13/chef-0-7-8-release/) | 2009-08-13 | Jeppe Nejsum Madsen |
+| [Chef 0.7.6](https://www.chef.io/blog/2009/08/08/chef-0-7-6-release/) | 2009-08-08 | Grant Zanetti |
+| [Chef 0.7.4](https://www.chef.io/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Hongli Lai |
+| [Chef 0.7.2](https://www.chef.io/blog/2009/06/26/back-to-back-chef-0-7-2-and-chef-0-7-4-released/) | 2009-06-26 | Joshua Sierles |
+| [Chef 0.7.0](https://www.chef.io/blog/2009/06/10/chef-0-7-0-release/) | 2009-06-10 | Matthew Kent |
+| [Chef 0.6.2](https://www.chef.io/blog/2009/04/29/chef-0-6-2-release/) | 2009-04-29 | David Balatero |
+| [Chef 0.6.0](https://www.chef.io/blog/2009/04/29/chef-0-6-0-release/) | 2009-04-29 | Matthew Kent |
+| [Chef 0.5.6](https://www.chef.io/blog/2009/03/06/chef-0-5-6/) | 2009-03-06 | Sean Cribbs |
+| [Chef 0.5.4](https://www.chef.io/blog/2009/02/13/chef-0-5-4/) | 2009-02-13 | Arjuna Christensen |
+| [Chef 0.5.2](https://www.chef.io/blog/2009/02/01/chef-0-5-2-and-ohai-0-1-4/) | 2009-02-01 | Bryan McLellan |
+
+#### Awesome Community Chefs
+
+Each year at ChefConf, three individuals are awarded the Awesome Community Chef award. The Awesome Community Chef awards are a way for the community to recognize a few of the individuals who have made a dramatic impact and have helped further the cause.
+
+* 2013
+ * [Bryan Berry](https://github.com/bryanwb)
+ * [Fletcher Nichol](https://github.com/fnichol)
+ * [Jamie Winsor](https://github.com/reset)
+* 2014
+ * [Ranjib Dey](https://github.com/ranjib)
+ * [Miah Johnson](https://github.com/miah)
+ * [Seth Vargo](https://github.com/sethvargo)
+ * [Eric Wolfe](https://github.com/atomic-penguin)
+* 2015
+ * [Jon Cowie](https://github.com/jonlives)
+ * [Noah Kantrowitz](https://github.com/coderanger)
+ * [Matt Wrock](https://github.com/mwrock) \ No newline at end of file
diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md
index 41d70fac61..4c07dea872 100644
--- a/DOC_CHANGES.md
+++ b/DOC_CHANGES.md
@@ -6,8 +6,392 @@ Example Doc Change:
Description of the required change.
-->
+### PSCredential Support for `dsc_script`
-### knife ssh has --exit-on-error option
-`knife ssh` now has an --exit-on-error option that will cause it to
-fail immediately in the face of an SSH connection error. The default
-behavior is move on to the next node.
+`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
+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
+```
+
+Now you can run this recipe:
+
+```ruby
+single_page_website 'mysite' do
+ homepage '<h1>My own page</h1>'
+end
+```
+
+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
+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
+
+ 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
+```
+
+#### Adding another action: "stop"
+
+What if we want to stop the website? Just add another action into the bottom of
+`resources/single_page_website.rb`:
+
+```ruby
+action :stop do
+ service 'httpd' do
+ action :stop
+ end
+end
+```
+
+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
+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
+```
+
+Now, the above recipe knows what the current homepage is, and will not change it!
+
+This capability is also used for several other things, including reporting (to
+describe what changed) and pure Ruby actions.
+
+#### Pure Ruby Actions
+
+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
+# 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
+```
+
+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
+
+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:
+
+```ruby
+# resources/my_file.rb
+property :path, String, name_property: true
+property :content, String
+property :mode, String
+
+load_current_value do
+ if File.exist?(path)
+ content IO.read(path)
+ mode File.stat(path).mode
+ end
+end
+
+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 1418235ebc..af0bef493c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,13 +3,17 @@ gemspec :name => "chef"
gem "activesupport", "< 4.0.0", :group => :compat_testing, :platform => "ruby"
+gem 'chef-config', path: "chef-config"
+
group(:docgen) do
+ gem "tomlrb"
gem "yard"
end
group(:development, :test) do
gem "simplecov"
gem 'rack', "~> 1.5.1"
+ 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 6f53ab98fd..3c777366f8 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -1,3 +1,5 @@
+<!-- This is a generated file. Please do not edit directly -->
+
# Maintainers
This file lists how the Chef project is maintained. When making changes to the system, this
@@ -10,59 +12,57 @@ a maintainer, lieutenant, or the project lead.
# Project Lead
-[Adam Jacob](http://github.com/adamhjk)
+* [Adam Jacob](https://github.com/adamhjk)
-# Components
+## Components
## 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
+
* [Thom May](https://github.com/thommay)
### Maintainers
-* [Jon Cowie](http://github.com/jonlives)
-* [Phil Dibowitz](https://github.com/jaymzh)
-* [Lamont Granquist](http://github.com/lamont-granquist)
-* [Tyler Ball](https://github.com/tyler-ball)
+* [Bryan McLellan](https://github.com/btm)
+* [Noah Kantrowitz](https://github.com/coderanger)
* [Daniel DeLeo](https://github.com/danielsdeleo)
-* [Claire McQuin](https://github.com/mcquin)
-* [Jay Mundrawala](http://github.com/jdmundrawala)
-* [Bryan McLellan](http://github.com/btm)
-* [Ranjib Dey](http://github.com/ranjib)
* [AJ Christensen](https://github.com/fujin)
-* [Thom May](https://github.com/thommay)
+* [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)
## Dev Tools
Chef Zero, Knife, Chef Apply and Chef Shell.
-
-### Lieutenant
-
### Maintainers
-* [Steven Danna](https://github.com/stevendanna/)
-* [Joshua Timberman](https://github.com/jtimberman)
-* [Lamont Granquist](http://github.com/lamont-granquist)
* [Daniel DeLeo](https://github.com/danielsdeleo)
+* [Joshua Timberman](https://github.com/jtimberman)
+* [Lamont Granquist](https://github.com/lamont-granquist)
+* [Steven Danna](https://github.com/stevendanna)
## Test Tools
-### ChefSpec
-
-#### Lieutenant
+ChefSpec
+### Lieutenant
-[Seth Vargo](https://github.com/sethvargo)
+* [Seth Vargo](https://github.com/sethvargo)
-#### Maintainers
+### Maintainers
* [Joshua Timberman](https://github.com/jtimberman)
-* [Lamont Granquist](http://github.com/lamont-granquist)
-* [Ranjib Dey](http://github.com/ranjib)
+* [Lamont Granquist](https://github.com/lamont-granquist)
+* [Ranjib Dey](https://github.com/ranjib)
## Platform Specific Components
@@ -72,47 +72,52 @@ The specific components of Chef related to a given platform - including (but not
### Lieutenant
-* [Jon Cowie](http://github.com/jonlives)
+* [Jon Cowie](https://github.com/jonlives)
### Maintainers
-* [Lamont Granquist](http://github.com/lamont-granquist)
+* [Phil Dibowitz](https://github.com/jaymzh)
+* [Lamont Granquist](https://github.com/lamont-granquist)
## Ubuntu
### Lieutenant
+* [Ranjib Dey](https://github.com/ranjib)
+
### Maintainers
-* [Lamont Granquist](http://github.com/lamont-granquist)
-* [Ranjib Dey](http://github.com/ranjib)
+* [Lamont Granquist](https://github.com/lamont-granquist)
* [Thom May](https://github.com/thommay)
## Windows
### Lieutenant
-* [Bryan McLellan](http://github.com/btm)
+* [Bryan McLellan](https://github.com/btm)
### Maintainers
-* [Steven Murawski](http://github.com/smurawski)
-* [Jay Mundrawala](http://github.com/jdmundrawala)
+
+* [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](http://github.com/lamont-granquist)
+* [Lamont Granquist](https://github.com/lamont-granquist)
## AIX
### Lieutenant
-### Maintainers
-
-* [Lamont Granquist](http://github.com/lamont-granquist)
+* [Lamont Granquist](https://github.com/lamont-granquist)
## Mac OS X
@@ -124,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
@@ -132,3 +165,31 @@ The specific components of Chef related to a given platform - including (but not
### Maintainers
+* [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
new file mode 100644
index 0000000000..61c75c1a30
--- /dev/null
+++ b/MAINTAINERS.toml
@@ -0,0 +1,307 @@
+#
+# This file is structured to be consumed by both humans and computers.
+# It is a TOML document containing Markdown
+#
+[Preamble]
+ title = "Maintainers"
+ text = """
+This file lists how the Chef project is maintained. When making changes to the system, this
+file tells you who needs to review your patch - you need a simple majority of maintainers
+for the relevant subsystems to provide a :+1: on your pull request. Additionally, you need
+to not receive a veto from a Lieutenant or the Project Lead.
+
+Check out [How Chef is Maintained](https://github.com/opscode/chef-rfc/blob/master/rfc030-maintenance-policy.md#how-the-project-is-maintained) for details on the process, how to become
+a maintainer, lieutenant, or the project lead.
+"""
+
+[Org]
+ [Org.Lead]
+ title = "Project Lead"
+ person = "adamhjk"
+
+ [Org.Components]
+ title = "Components"
+
+ [Org.Components.Core]
+ title = "Chef Core"
+ text = """
+Handles the core parts of the Chef DSL, base resource and provider
+infrastructure, the Chef applications and [omnibus-chef](https://github.com/chef/omnibus-chef). Includes anything not covered by
+another component.
+"""
+
+ lieutenant = "thommay"
+
+ maintainers = [
+ "btm",
+ "coderanger",
+ "danielsdeleo",
+ "fujin",
+ "jaymzh",
+ "jdmundrawala",
+ "jonlives",
+ "lamont-granquist",
+ "mcquin",
+ "smurawski",
+ "tyler-ball",
+ "ranjib"
+ ]
+
+ [Org.Components.DevTools]
+ title = "Dev Tools"
+ text = "Chef Zero, Knife, Chef Apply and Chef Shell."
+
+ paths = [
+ "lib/chef/knife.rb",
+ "lib/chef/knife/",
+ "spec/functional/knife/",
+ "spec/integration/knife/",
+ "spec/unit/knife/"
+ ]
+
+ maintainers = [
+ "danielsdeleo",
+ "jtimberman",
+ "lamont-granquist",
+ "stevendanna"
+ ]
+
+ [Org.Components.TestTools]
+ title = "Test Tools"
+ text = "ChefSpec"
+
+ lieutenant = "sethvargo"
+
+ maintainers = [
+ "jtimberman",
+ "lamont-granquist",
+ "ranjib"
+ ]
+
+ [Org.Components.Subsystems]
+ title = "Platform Specific Components"
+ text = """
+The specific components of Chef related to a given platform - including (but not limited to) resources, providers, and the core DSL.
+"""
+
+ [Org.Components.Subsystems."Enterprise Linux"]
+ title = "Enterprise Linux"
+
+ lieutenant = "jonlives"
+
+ maintainers = [
+ "jaymzh",
+ "lamont-granquist"
+ ]
+
+ [Org.Components.Subsystems.Ubuntu]
+ title = "Ubuntu"
+
+ lieutenant = "ranjib"
+
+ maintainers = [
+ "lamont-granquist",
+ "thommay"
+ ]
+
+ [Org.Components.Subsystems.Windows]
+ title = "Windows"
+
+ lieutenant = "btm"
+ maintainers = [
+ "jdmundrawala",
+ "ksubrama",
+ "smurawski",
+ "chefsalim"
+ ]
+
+ [Org.Components.Subsystems.Solaris]
+ title = "Solaris"
+
+ lieutenant = "thommay"
+
+ maintainers = [
+ "lamont-granquist"
+ ]
+
+ [Org.Components.Subsystems.AIX]
+ title = "AIX"
+
+ lieutenant = "lamont-granquist"
+
+ [Org.Components.Subsystems."Mac OS X"]
+ title = "Mac OS X"
+
+ lieutenant = "jtimberman"
+
+ maintainers = [
+ "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"
+
+ lieutenant = "martinisoft"
+
+ maintainers = [
+ "Aevin1387",
+ "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]
+ Name = "Adam Jacob"
+ GitHub = "adamhjk"
+
+ [people.Aevin1387]
+ Name = "Cory Stephenson"
+ GitHub = "Aevin1387"
+
+ [people.btm]
+ Name = "Bryan McLellan"
+ GitHub = "btm"
+
+ [people.danielsdeleo]
+ Name = "Daniel DeLeo"
+ GitHub = "danielsdeleo"
+
+ [people.fujin]
+ Name = "AJ Christensen"
+ GitHub = "fujin"
+
+ [people.jaymzh]
+ Name = "Phil Dibowitz"
+ GitHub = "jaymzh"
+
+ [people.jdmundrawala]
+ Name = "Jay Mundrawala"
+ GitHub = "jdmundrawala"
+
+ [people.jonlives]
+ Name = "Jon Cowie"
+ GitHub = "jonlives"
+
+ [people.jtimberman]
+ Name = "Joshua Timberman"
+ GitHub = "jtimberman"
+
+ [people.lamont-granquist]
+ Name = "Lamont Granquist"
+ GitHub = "lamont-granquist"
+
+ [people.martinisoft]
+ Name = "Aaron Kalin"
+ GitHub = "martinisoft"
+
+ [people.mcquin]
+ Name = "Claire McQuin"
+ GitHub = "mcquin"
+
+ [people.ranjib]
+ Name = "Ranjib Dey"
+ GitHub = "ranjib"
+
+ [people.sethvargo]
+ Name = "Seth Vargo"
+ GitHub = "sethvargo"
+
+ [people.smurawski]
+ Name = "Steven Murawski"
+ GitHub = "smurawski"
+
+ [people.stevendanna]
+ Name = "Steven Danna"
+ GitHub = "stevendanna"
+
+ [people.tBunnyMan]
+ Name = "David Aronsohn"
+ GitHub = "tbunnyman"
+ IRC = "tBunnyMan"
+ Twitter = "OnlyHaveCans"
+
+ [people.thommay]
+ Name = "Thom May"
+ GitHub = "thommay"
+ IRC = "thom"
+ Twitter = "thommay"
+
+ [people.tyler-ball]
+ Name = "Tyler Ball"
+ GitHub = "tyler-ball"
+
+ [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 4d0a6cc7ef..cba5b9f415 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,153 +1,76 @@
-# Chef Client Release Notes 12.1.0:
+# Chef Client Release Notes 12.5.0:
+* OSX 10.11 support (support for SIP and service changes)
-# Internal API Changes in this Release
+## PSCredential support for the `dsc_script` resource
-## Experimental Audit Mode Feature
-
-This is a new feature intended to provide _infrastructure audits_. Chef already allows you to configure your infrastructure
-with code, but there are some use cases that are not covered by resource convergence. What if you want to check that
-the application Chef just installed is functioning correctly? If it provides a status page an audit can check this
-and validate that the application has database connectivity.
-
-Audits are performed by leveraging [Serverspec](http://serverspec.org/) and [RSpec](https://relishapp.com/rspec) on the
-node. As such the syntax is very similar to a normal RSpec spec.
-
-### Syntax
+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
-control_group "Database Audit" do
-
- control "postgres package" do
- it "should not be installed" do
- expect(package("postgresql")).to_not be_installed
- end
- end
-
- let(:p) { port(111) }
- control p do
- it "has nothing listening" do
- expect(p).to_not be_listening
- end
- end
-
+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
```
-Using the example above I will break down the components of an Audit:
-
-* `control_group` - This named block contains all the audits to be performed during the audit phase. During Chef convergence
- the audits will be collected and ran in a separate phase at the end of the Chef run. Any `control_group` block defined in
- a recipe that is ran on the node will be performed.
-* `control` - This keyword describes a section of audits to perform. The name here should either be a string describing
-the system under test, or a [Serverspec resource](http://serverspec.org/resource_types.html).
-* `it` - Inside this block you can use [RSpec expectations](https://relishapp.com/rspec/rspec-expectations/docs) to
-write the audits. You can use the Serverspec resources here or regular ruby code. Any raised errors will fail the
-audit.
-
-### Output and error handling
-
-Output from the audit run will appear in your `Chef::Config[:log_location]`. If an audit fails then Chef will raise
-an error and exit with a non-zero status.
-
-### Further reading
-
-More information about the audit mode can be found in its
-[RFC](https://github.com/opscode/chef-rfc/blob/master/rfc035-audit-mode.md)
-
-# End-User Changes
-
-## OpenBSD Package provider was added
-
-The package resource on OpenBSD is wired up to use the new OpenBSD package provider to install via pkg_add on OpenBSD systems.
-
-## Case Insensitive URI Handling
-
-Previously, when a URI scheme contained all uppercase letters, Chef
-would reject the URI as invalid. In compliance with RFC3986, Chef now
-treats URI schemes in a case insensitive manner.
-
-## File Content Verification (RFC 027)
-
-Per RFC 027, the file and file-like resources now accept a `verify`
-attribute. This attribute accepts a string(shell command) or a ruby
-block (similar to `only_if`) which can be used to verify the contents
-of a rendered template before deploying it to disk.
-
-## Drop SSL Warnings
-Now that the default for SSL checking is on, no more warning is emitted when SSL
-checking is off.
-
-## Multi-package Support
-The `package` provider has been extended to support multiple packages. This
-support is new and and not all subproviders yet support it. Full support for
-`apt` and `yum` has been implemented.
-
-## chef_gem deprecation of installation at compile time
-
-A `compile_time` flag has been added to the chef_gem resource to control if it is installed at compile_time or not. The prior behavior has been that this
-resource forces itself to install at compile_time which is problematic since if the gem is native it forces build_essentials and other dependent libraries
-to have to be installed at compile_time in an escalating war of forcing compile time execution. This default was engineered before it was understood that a better
-approach was to lazily require gems inside of provider code which only ran at converge time and that requiring gems in recipe code was bad practice.
-
-The default behavior has not changed, but every chef_gem resource will now emit out a warning:
-
-```
-[2015-02-06T13:13:48-08:00] WARN: chef_gem[aws-sdk] chef_gem compile_time installation is deprecated
-[2015-02-06T13:13:48-08:00] WARN: chef_gem[aws-sdk] Please set `compile_time false` on the resource to use the new behavior.
-[2015-02-06T13:13:48-08:00] WARN: chef_gem[aws-sdk] or set `compile_time true` on the resource if compile_time behavior is required.
-```
-
-The preferred way to fix this is to make every chef_gem resource explicit about compile_time installation (keeping in mind the best-practice to default to false
-unless there is a reason):
+This can now be replaced with
```ruby
-chef_gem 'aws-sdk' do
- compile_time false
+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
```
-There is also a Chef::Config[:chef_gem_compile_time] flag which has been added. If this is set to true (not recommended) then chef will only emit a single
-warning at the top of the chef-client run:
-
-```
-[2015-02-06T13:27:35-08:00] WARN: setting chef_gem_compile_time to true is deprecated
-```
+## New `knife rehash` for faster command loading
-It will behave like Chef 10 and Chef 11 and will default chef_gem to compile_time installations and will suppress
-subsequent warnings in the chef-client run.
+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.
-If this setting is changed to 'false' then it will adopt Chef-13 style behavior and will default all chef_gem installs to not run at compile_time by default. This
-may break existing cookbooks.
+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.
-* All existing cookbooks which require compile_time true MUST be updated to be explicit about this setting.
-* To be considered high quality, cookbooks which require compile_time true MUST be rewritten to avoid this setting.
-* All existing cookbooks which do not require compile_time true SHOULD be updated to be explicit about this setting.
+## Support for `/usr/bin/yum-deprecated` in the yum provider
-For cookbooks that need to maintain backwards compatibility a `respond_to?` check should be used:
+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:
-```
-chef_gem 'aws-sdk' do
- compile_time false if respond_to?(:compile_time)
+```ruby
+if File.exist?("/usr/bin/dnf")
+ execute "dnf install -y yum" do
+ not_if { File.exist?("/usr/bin/yum-deprecated") }
+ end
end
```
-## Knife Bootstrap Validatorless Bootstraps and Chef Vault integration
-
-The knife bootstrap command now supports validatorless bootstraps. This can be enabled via deleting the validation key.
-When the validation key is not present, knife bootstrap will use the user key in order to create a client for the node
-being bootstrapped. It will also then create a node object and set the environment, run_list, initial attributes, etc (avoiding
-the problem of the first chef-client failing and not saving the node's run_list correctly).
-
-Also knife vault integration has been added so that knife bootstrap can use the client key to add chef vault items to
-the node, reducing the number of steps necessary to bootstrap a node with chef vault.
-
-There is no support for validatorless bootstraps when the node object has been precreated by the user beforehand, as part
-of the process any old node or client will be deleted when doing validatorless bootstraps. The old process with the validation
-key still works for this use case. The setting of the run_list, environment and json attributes first via knife bootstrap
-should mitigate some of the need to precreate the node object by hand first.
-
-
-## Windows service now has a configurable timeout
+After which the yum-deprecated binary will exist, and the yum provider will find it and should operate
+normally and successfully.
-You can now set the amount of time a chef-client run is allowed when running the provided windows service. This can be configured by
-setting `windows_service.watchdog_timeout` in your `client.rb` to the number of seconds desired. The default value is 2 hours.
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000000..c726b4e7e5
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,16 @@
+# Roadmap
+
+This file provides direction for the project as set forth by [RFC030#Roadmap](https://github.com/chef/chef-rfc/blob/master/rfc030-maintenance-policy.md#roadmap).
+
+It is drafted by the Project Lead and the Lieutenants, with input from Maintainers and Contributors.
+
+## 2015 Q2
+* Core - URL support in package resources
+ - For package managment tools where it is appropriate, allow passing a URL as a package source
+* Windows - dsc_resource
+ - Adds the ability to use Microsoft Desired State Configuration (DSC) resources in Chef recipes.
+
+## 2015 Q3
+* Windows - Native 64bit support
+ - Runs under a 64bit Ruby
+
diff --git a/Rakefile b/Rakefile
index 70a45d94c0..6b9a52f68d 100644
--- a/Rakefile
+++ b/Rakefile
@@ -17,35 +17,18 @@
# limitations under the License.
#
-require File.dirname(__FILE__) + '/lib/chef/version'
+VERSION = IO.read(File.expand_path("../VERSION", __FILE__)).strip
require 'rubygems'
-require 'rubygems/package_task'
+require 'chef-config/package_task'
require 'rdoc/task'
-require './tasks/rspec.rb'
+require_relative 'tasks/rspec'
+require_relative 'tasks/external_tests'
+require_relative 'tasks/maintainers'
-GEM_NAME = "chef"
-
-Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
- gemspec = eval(IO.read(gemspec_path))
- Gem::PackageTask.new(gemspec).define
-end
-
-task :install => :package do
- sh %{gem install pkg/#{GEM_NAME}-#{Chef::VERSION}.gem --no-rdoc --no-ri}
-end
-
-task :uninstall do
- sh %{gem uninstall #{GEM_NAME} -x -v #{Chef::VERSION} }
-end
-
-desc "Build it, tag it and ship it"
-task :ship => :gem do
- sh("git tag #{Chef::VERSION}")
- sh("git push opscode --tags")
- Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
- sh("gem push #{built_gem}")
- end
+ChefConfig::PackageTask.new(File.expand_path('..', __FILE__), 'Chef') do |package|
+ package.component_paths = ['chef-config']
+ package.generate_version_class = true
end
task :pedant do
@@ -79,3 +62,4 @@ begin
rescue LoadError
puts "yard is not available. (sudo) gem install yard to generate yard documentation."
end
+
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000000..9a03cd310d
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+12.5.0.current.0
diff --git a/appveyor.yml b/appveyor.yml
index 5ba6d30b81..06448e2be2 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -14,15 +14,21 @@ skip_tags: true
branches:
only:
- master
- - 12-stable
+ - 12.4-stable
+
+cache:
+ - C:\Ruby200\lib\ruby\gems\2.0.0
+ - C:\Ruby200\bin
install:
- winrm quickconfig -q
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
- echo %PATH%
- ruby --version
+ - gem install bundler --quiet --no-ri --no-rdoc || gem install bundler --quiet --no-ri --no-rdoc || gem install bundler --quiet --no-ri --no-rdoc
+ - gem install rubygems-pkg/rubygems-update-2.4.6.gem
+ - update_rubygems
- gem --version
- - gem install bundler --quiet --no-ri --no-rdoc
- bundler --version
build_script:
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/.gitignore b/chef-config/.gitignore
new file mode 100644
index 0000000000..0cb6eeb067
--- /dev/null
+++ b/chef-config/.gitignore
@@ -0,0 +1,9 @@
+/.bundle/
+/.yardoc
+/Gemfile.lock
+/_yardoc/
+/coverage/
+/doc/
+/pkg/
+/spec/reports/
+/tmp/
diff --git a/chef-config/.rspec b/chef-config/.rspec
new file mode 100644
index 0000000000..eb3ef03653
--- /dev/null
+++ b/chef-config/.rspec
@@ -0,0 +1,2 @@
+--color
+-fd
diff --git a/chef-config/.travis.yml b/chef-config/.travis.yml
new file mode 100644
index 0000000000..927580f35f
--- /dev/null
+++ b/chef-config/.travis.yml
@@ -0,0 +1,31 @@
+language: ruby
+
+sudo: false
+# Early warning system to catch if Rubygems breaks something
+before_install:
+ gem update --system
+
+branches:
+ only:
+ - master
+
+matrix:
+ include:
+ - rvm: 2.0
+ - rvm: 2.1
+
+notifications:
+ on_change: true
+ on_failure: true
+ on_success: change
+ on_pull_requests: false
+ irc:
+ channels:
+ - chat.freenode.net#chef-hacking
+ hipchat:
+ rooms:
+ # Build Statuses
+ - secure: G8MNo94L8bmWWwkH2/ViB2QaZnZHZscYM/mEjDbOGd15sqrruwckeARyBoUcRI7P1C6AFmS4IKCNVXa6KzX4Pbh51gQWM92zRpRTZpplwtXz53/1l8ajLFLLMLvEMTlBFAANUKEUFAQPY4dMa14V3Qc5oijfIncN61k4nZNTKpY=
+ - rvm: 2.2
+ # Open Source
+ - secure: hmcex4PpG5dn8WvjndONO4xCUKOC5kPU/bUEGRrfVbe2YKJE7t0XXbNDC96W/xBgzgnJzvf1Er0zJKDrNf4qEDEWFoozdN00WLcqREgaLLS3Seto2FjR/BpBk5q+sCV0rwwEMms2P4Qk+VSnDCnm9EaeM55hOabqNuOrRzoZLBQ=
diff --git a/chef-config/Gemfile b/chef-config/Gemfile
new file mode 100644
index 0000000000..d39725ff87
--- /dev/null
+++ b/chef-config/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+# Specify your gem's dependencies in chef-config.gemspec
+gemspec
diff --git a/chef-config/LICENSE b/chef-config/LICENSE
new file mode 100644
index 0000000000..11069edd79
--- /dev/null
+++ b/chef-config/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/chef-config/README.md b/chef-config/README.md
new file mode 100644
index 0000000000..c36527282e
--- /dev/null
+++ b/chef-config/README.md
@@ -0,0 +1,4 @@
+# ChefConfig
+
+This repo is experimental. Use at your own risk.
+
diff --git a/chef-config/Rakefile b/chef-config/Rakefile
new file mode 100644
index 0000000000..36e7e2572d
--- /dev/null
+++ b/chef-config/Rakefile
@@ -0,0 +1,14 @@
+require 'rspec/core/rake_task'
+require 'chef-config/package_task'
+
+ChefConfig::PackageTask.new(File.expand_path('..', __FILE__), 'ChefConfig') do |package|
+ package.module_path = 'chef-config'
+end
+
+task :default => :spec
+
+desc "Run standard specs"
+RSpec::Core::RakeTask.new(:spec) do |t|
+ t.pattern = FileList['spec/**/*_spec.rb']
+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
new file mode 100644
index 0000000000..475bd0f2d2
--- /dev/null
+++ b/chef-config/chef-config.gemspec
@@ -0,0 +1,32 @@
+# coding: utf-8
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'chef-config/version'
+
+Gem::Specification.new do |spec|
+ spec.name = "chef-config"
+ spec.version = ChefConfig::VERSION
+ spec.authors = ["Adam Jacob"]
+ spec.email = ["adam@chef.io"]
+
+ spec.summary = %q{Chef's default configuration and config loading}
+ spec.homepage = "https://github.com/chef/chef"
+ spec.license = "Apache-2.0"
+
+ spec.require_paths = ["lib"]
+
+ spec.add_dependency "mixlib-shellout", "~> 2.0"
+ spec.add_dependency "mixlib-config", "~> 2.0"
+
+ spec.add_development_dependency "rake", "~> 10.0"
+
+ %w(rspec-core rspec-expectations rspec-mocks).each do |rspec|
+ spec.add_development_dependency(rspec, "~> 3.2")
+ end
+
+ spec.files = %w(Rakefile LICENSE README.md) +
+ Dir.glob("{lib,spec}/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
+
+ spec.bindir = "bin"
+ spec.executables = []
+end
diff --git a/chef-config/lib/chef-config.rb b/chef-config/lib/chef-config.rb
new file mode 100644
index 0000000000..1f593c868f
--- /dev/null
+++ b/chef-config/lib/chef-config.rb
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+module ChefConfig
+
+end
diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb
new file mode 100644
index 0000000000..a3f06e9b23
--- /dev/null
+++ b/chef-config/lib/chef-config/config.rb
@@ -0,0 +1,741 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Brown (<cb@opscode.com>)
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Author:: Mark Mzyk (<mmzyk@opscode.com>)
+# Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'mixlib/config'
+require 'pathname'
+
+require 'chef-config/logger'
+require 'chef-config/windows'
+require 'chef-config/path_helper'
+require 'mixlib/shellout'
+
+module ChefConfig
+
+ class Config
+
+ extend Mixlib::Config
+
+ # Evaluates the given string as config.
+ #
+ # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file.
+ def self.from_string(string, filename)
+ self.instance_eval(string, filename, 1)
+ end
+
+ def self.inspect
+ configuration.inspect
+ end
+
+ def self.platform_specific_path(path)
+ path = PathHelper.cleanpath(path)
+ if ChefConfig.windows?
+ # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb
+ if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef'
+ path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2])
+ end
+ end
+ path
+ end
+
+ def self.add_formatter(name, file_path=nil)
+ formatters << [name, file_path]
+ end
+
+ def self.add_event_logger(logger)
+ event_handlers << logger
+ end
+
+ # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.)
+ configurable(:config_file)
+
+ default(:config_dir) do
+ if config_file
+ PathHelper.dirname(config_file)
+ else
+ PathHelper.join(user_home, ".chef", "")
+ end
+ end
+
+ default :formatters, []
+
+ # Override the config dispatch to set the value of multiple server options simultaneously
+ #
+ # === Parameters
+ # url<String>:: String to be set for all of the chef-server-api URL's
+ #
+ configurable(:chef_server_url).writes_value { |url| url.to_s.strip }
+
+ # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel.
+ # So while this is basically identical to what method_missing would do, we pull
+ # it up here and get a real method written so that things get dispatched
+ # properly.
+ configurable(:daemonize).writes_value { |v| v }
+
+ # The root where all local chef object data is stored. cookbooks, data bags,
+ # environments are all assumed to be in separate directories under this.
+ # chef-solo uses these directories for input data. knife commands
+ # that upload or download files (such as knife upload, knife role from file,
+ # etc.) work.
+ default :chef_repo_path do
+ if self.configuration[:cookbook_path]
+ if self.configuration[:cookbook_path].kind_of?(String)
+ File.expand_path('..', self.configuration[:cookbook_path])
+ else
+ self.configuration[:cookbook_path].map do |path|
+ File.expand_path('..', path)
+ end
+ end
+ else
+ cache_path
+ end
+ end
+
+ def self.find_chef_repo_path(cwd)
+ # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it.
+ # This allows us to run config-free.
+ path = cwd
+ until File.directory?(PathHelper.join(path, "cookbooks"))
+ new_path = File.expand_path('..', path)
+ if new_path == path
+ ChefConfig.logger.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.")
+ return Dir.pwd
+ end
+ path = new_path
+ end
+ ChefConfig.logger.info("Auto-discovered chef repository at #{path}")
+ path
+ end
+
+ def self.derive_path_from_chef_repo_path(child_path)
+ if chef_repo_path.kind_of?(String)
+ PathHelper.join(chef_repo_path, child_path)
+ else
+ chef_repo_path.uniq.map { |path| PathHelper.join(path, child_path)}
+ end
+ end
+
+ # Location of acls on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/acls.
+ # Only applies to Enterprise Chef commands.
+ default(:acl_path) { derive_path_from_chef_repo_path('acls') }
+
+ # Location of clients on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/acls.
+ default(:client_path) { derive_path_from_chef_repo_path('clients') }
+
+ # Location of cookbooks on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/cookbooks. If chef_repo_path
+ # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]).
+ default(:cookbook_path) do
+ if self.configuration[:chef_repo_path]
+ derive_path_from_chef_repo_path('cookbooks')
+ else
+ Array(derive_path_from_chef_repo_path('cookbooks')).flatten +
+ Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten
+ end
+ end
+
+ # Location of containers on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/containers.
+ # Only applies to Enterprise Chef commands.
+ default(:container_path) { derive_path_from_chef_repo_path('containers') }
+
+ # Location of data bags on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/data_bags.
+ default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') }
+
+ # Location of environments on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/environments.
+ default(:environment_path) { derive_path_from_chef_repo_path('environments') }
+
+ # Location of groups on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/groups.
+ # Only applies to Enterprise Chef commands.
+ default(:group_path) { derive_path_from_chef_repo_path('groups') }
+
+ # Location of nodes on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/nodes.
+ default(:node_path) { derive_path_from_chef_repo_path('nodes') }
+
+ # Location of roles on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/roles.
+ default(:role_path) { derive_path_from_chef_repo_path('roles') }
+
+ # Location of users on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/users.
+ # Does not apply to Enterprise Chef commands.
+ default(:user_path) { derive_path_from_chef_repo_path('users') }
+
+ # Location of policies on disk. String or array of strings.
+ # Defaults to <chef_repo_path>/policies.
+ default(:policy_path) { derive_path_from_chef_repo_path('policies') }
+
+ # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity
+ default :enforce_path_sanity, true
+
+ # Formatted Chef Client output is a beta feature, disabled by default:
+ default :formatter, "null"
+
+ # The number of times the client should retry when registering with the server
+ default :client_registration_retries, 5
+
+ # An array of paths to search for knife exec scripts if they aren't in the current directory
+ default :script_path, []
+
+ # The root of all caches (checksums, cache and backup). If local mode is on,
+ # this is under the user's home directory.
+ default(:cache_path) do
+ if local_mode
+ PathHelper.join(config_dir, 'local-mode-cache')
+ else
+ primary_cache_root = platform_specific_path("/var")
+ primary_cache_path = platform_specific_path("/var/chef")
+ # Use /var/chef as the cache path only if that folder exists and we can read and write
+ # into it, or /var exists and we can read and write into it (we'll create /var/chef later).
+ # Otherwise, we'll create .chef under the user's home directory and use that as
+ # the cache path.
+ unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root)
+ secondary_cache_path = PathHelper.join(user_home, '.chef')
+ ChefConfig.logger.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}")
+ secondary_cache_path
+ else
+ primary_cache_path
+ end
+ end
+ end
+
+ # Returns true only if the path exists and is readable and writeable for the user.
+ def self.path_accessible?(path)
+ File.exists?(path) && File.readable?(path) && File.writable?(path)
+ end
+
+ # Where cookbook files are stored on the server (by content checksum)
+ default(:checksum_path) { PathHelper.join(cache_path, "checksums") }
+
+ # Where chef's cache files should be stored
+ default(:file_cache_path) { PathHelper.join(cache_path, "cache") }
+
+ # Where backups of chef-managed files should go
+ default(:file_backup_path) { PathHelper.join(cache_path, "backup") }
+
+ # The chef-client (or solo) lockfile.
+ #
+ # If your `file_cache_path` resides on a NFS (or non-flock()-supporting
+ # fs), it's recommended to set this to something like
+ # '/tmp/chef-client-running.pid'
+ default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") }
+
+ ## Daemonization Settings ##
+ # What user should Chef run as?
+ default :user, nil
+ default :group, nil
+ default :umask, 0022
+
+ # Valid log_levels are:
+ # * :debug
+ # * :info
+ # * :warn
+ # * :fatal
+ # These work as you'd expect. There is also a special `:auto` setting.
+ # When set to :auto, Chef will auto adjust the log verbosity based on
+ # context. When a tty is available (usually because the user is running chef
+ # in a console), the log level is set to :warn, and output formatters are
+ # used as the primary mode of output. When a tty is not available, the
+ # logger is the primary mode of output, and the log level is set to :info
+ default :log_level, :auto
+
+ # Logging location as either an IO stream or string representing log file path
+ default :log_location, STDOUT
+
+ # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty
+ default :force_formatter, false
+
+ # Using `force_logger` causes chef to default to logger output when STDOUT is a tty
+ default :force_logger, false
+
+ default :http_retry_count, 5
+ default :http_retry_delay, 5
+ default :interval, nil
+ default :once, nil
+ default :json_attribs, nil
+ # toggle info level log items that can create a lot of output
+ default :verbose_logging, true
+ default :node_name, nil
+ default :diff_disabled, false
+ default :diff_filesize_threshold, 10000000
+ default :diff_output_threshold, 1000000
+ default :local_mode, false
+
+ default :pid_file, nil
+
+ # Whether Chef Zero local mode should bind to a port. All internal requests
+ # will go through the socketless code path regardless, so the socket is
+ # only needed if other processes will connect to the local mode server.
+ #
+ # For compatibility this is set to true but it will be changed to false in
+ # the future.
+ default :listen, true
+
+ config_context :chef_zero do
+ config_strict_mode true
+ default(:enabled) { ChefConfig::Config.local_mode }
+ default :host, 'localhost'
+ default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works
+ end
+ default :chef_server_url, "https://localhost:443"
+
+ default(:chef_server_root) do
+ # if the chef_server_url is a path to an organization, aka
+ # 'some_url.../organizations/*' then remove the '/organization/*' by default
+ if self.configuration[:chef_server_url] =~ /\/organizations\/\S*$/
+ self.configuration[:chef_server_url].split('/')[0..-3].join('/')
+ elsif self.configuration[:chef_server_url] # default to whatever chef_server_url is
+ self.configuration[:chef_server_url]
+ else
+ "https://localhost:443"
+ end
+ end
+
+ default :rest_timeout, 300
+ default :yum_timeout, 900
+ default :yum_lock_timeout, 30
+ default :solo, false
+ default :splay, nil
+ default :why_run, false
+ default :color, false
+ default :client_fork, true
+ default :ez, false
+ default :enable_reporting, true
+ default :enable_reporting_url_fatals, false
+ # Possible values for :audit_mode
+ # :enabled, :disabled, :audit_only,
+ #
+ # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature
+ # and is disabled by default. When users choose to enable audit-mode,
+ # a warning is issued in application/client#reconfigure.
+ # This can be removed when audit-mode is enabled by default.
+ default :audit_mode, :disabled
+
+ # Chef only needs ohai to run the hostname plugin for the most basic
+ # functionality. If the rest of the ohai plugins are not needed (like in
+ # most of our testing scenarios)
+ default :minimal_ohai, false
+
+ # Policyfile is a feature where a node gets its run list and cookbook
+ # version set from a single document on the server instead of expanding the
+ # run list and having the server compute the cookbook version set based on
+ # environment constraints.
+ default :use_policyfile, false
+
+ # Policyfiles can be used in a native mode (default) or compatibility mode.
+ # Native mode requires Chef Server 12.1 (it can be enabled via feature flag
+ # on some prior versions). In native mode, policies and associated
+ # cookbooks are accessed via feature-specific APIs. In compat mode,
+ # policies are stored as data bags and cookbooks are stored at the
+ # cookbooks/ endpoint. Compatibility mode can be dangerous on existing Chef
+ # Servers; it's recommended to upgrade your Chef Server rather than use
+ # compatibility mode. Compatibility mode remains available so you can use
+ # policyfiles with servers that don't yet support the native endpoints.
+ default :policy_document_native_api, true
+
+ # Set these to enable SSL authentication / mutual-authentication
+ # with the server
+
+ # Client side SSL cert/key for mutual auth
+ default :ssl_client_cert, nil
+ default :ssl_client_key, nil
+
+ # Whether or not to verify the SSL cert for all HTTPS requests. When set to
+ # :verify_peer (default), all HTTPS requests will be validated regardless of other
+ # SSL verification settings. When set to :verify_none no HTTPS requests will
+ # be validated.
+ default :ssl_verify_mode, :verify_peer
+
+ # Whether or not to verify the SSL cert for HTTPS requests to the Chef
+ # server API. If set to `true`, the server's cert will be validated
+ # regardless of the :ssl_verify_mode setting. This is set to `true` when
+ # running in local-mode.
+ # NOTE: This is a workaround until verify_peer is enabled by default.
+ default(:verify_api_cert) { ChefConfig::Config.local_mode }
+
+ # Path to the default CA bundle files.
+ default :ssl_ca_path, nil
+ default(:ssl_ca_file) do
+ if ChefConfig.windows? and embedded_path = embedded_dir
+ cacert_path = File.join(embedded_path, "ssl/certs/cacert.pem")
+ cacert_path if File.exist?(cacert_path)
+ else
+ nil
+ end
+ end
+
+ # A directory that contains additional SSL certificates to trust. Any
+ # certificates in this directory will be added to whatever CA bundle ruby
+ # is using. Use this to add self-signed certs for your Chef Server or local
+ # HTTP file servers.
+ default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") }
+
+ # Where should chef-solo download recipes from?
+ default :recipe_url, nil
+
+ # Sets the version of the signed header authentication protocol to use (see
+ # the 'mixlib-authorization' project for more detail). Currently, versions
+ # 1.0 and 1.1 are available; however, the chef-server must first be
+ # upgraded to support version 1.1 before clients can begin using it.
+ #
+ # Version 1.1 of the protocol is required when using a `node_name` greater
+ # than ~90 bytes (~90 ascii characters), so chef-client will automatically
+ # switch to using version 1.1 when `node_name` is too large for the 1.0
+ # protocol. If you intend to use large node names, ensure that your server
+ # supports version 1.1. Automatic detection of large node names means that
+ # users will generally not need to manually configure this.
+ #
+ # In the future, this configuration option may be replaced with an
+ # automatic negotiation scheme.
+ default :authentication_protocol_version, "1.0"
+
+ # This key will be used to sign requests to the Chef server. This location
+ # must be writable by Chef during initial setup when generating a client
+ # identity on the server.
+ #
+ # The chef-server will look up the public key for the client using the
+ # `node_name` of the client.
+ #
+ # If chef-zero is enabled, this defaults to nil (no authentication).
+ default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") }
+
+ # When registering the client, should we allow the client key location to
+ # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem
+ # If the path of the key goes through a directory like /tmp this should
+ # never be set to true or its possibly an easily exploitable security hole.
+ default :follow_client_key_symlink, false
+
+ # This secret is used to decrypt encrypted data bag items.
+ default(:encrypted_data_bag_secret) do
+ if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret"))
+ platform_specific_path("/etc/chef/encrypted_data_bag_secret")
+ else
+ nil
+ end
+ end
+
+ # As of Chef 11.0, version "1" is the default encrypted data bag item
+ # format. Version "2" is available which adds encrypt-then-mac protection.
+ # To maintain compatibility, versions other than 1 must be opt-in.
+ #
+ # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure.
+ # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO)
+ default :data_bag_encrypt_version, 1
+
+ # When reading data bag items, any supported version is accepted. However,
+ # if all encrypted data bags have been generated with the version 2 format,
+ # it is recommended to disable support for earlier formats to improve
+ # security. For example, the version 2 format is identical to version 1
+ # except for the addition of an HMAC, so an attacker with MITM capability
+ # could downgrade an encrypted data bag to version 1 as part of an attack.
+ default :data_bag_decrypt_minimum_version, 0
+
+ # If there is no file in the location given by `client_key`, chef-client
+ # will temporarily use the "validator" identity to generate one. If the
+ # `client_key` is not present and the `validation_key` is also not present,
+ # chef-client will not be able to authenticate to the server.
+ #
+ # The `validation_key` is never used if the `client_key` exists.
+ #
+ # If chef-zero is enabled, this defaults to nil (no authentication).
+ default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") }
+ default :validation_client_name, "chef-validator"
+
+ # When creating a new client via the validation_client account, Chef 11
+ # servers allow the client to generate a key pair locally and send the
+ # public key to the server. This is more secure and helps offload work from
+ # the server, enhancing scalability. If enabled and the remote server
+ # implements only the Chef 10 API, client registration will not work
+ # properly.
+ #
+ # The default value is `true`. Set to `false` to disable client-side key
+ # generation (server generates client keys).
+ default(:local_key_generation) { true }
+
+ # Zypper package provider gpg checks. Set to true to enable package
+ # gpg signature checking. This will be default in the
+ # future. Setting to false disables the warnings.
+ # Leaving this set to nil or false is a security hazard!
+ default :zypper_check_gpg, nil
+
+ # Report Handlers
+ default :report_handlers, []
+
+ # Event Handlers
+ default :event_handlers, []
+
+ default :disable_event_loggers, false
+
+ # Exception Handlers
+ default :exception_handlers, []
+
+ # Start handlers
+ default :start_handlers, []
+
+ # Syntax Check Cache. Knife keeps track of files that is has already syntax
+ # checked by storing files in this directory. `syntax_check_cache_path` is
+ # the new (and preferred) configuration setting. If not set, knife will
+ # fall back to using cache_options[:path], which is deprecated but exists in
+ # many client configs generated by pre-Chef-11 bootstrappers.
+ default(:syntax_check_cache_path) { cache_options[:path] }
+
+ # Deprecated:
+ # Move this to the default value of syntax_cache_path when this is removed.
+ default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } }
+
+ # Whether errors should be raised for deprecation warnings. When set to
+ # `false` (the default setting), a warning is emitted but code using
+ # deprecated methods/features/etc. should work normally otherwise. When set
+ # to `true`, usage of deprecated methods/features will raise a
+ # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that
+ # deprecated functionality is not used internally by Chef. End users
+ # should generally leave this at the default setting (especially in
+ # production), but it may be useful when testing cookbooks or other code if
+ # the user wishes to aggressively address deprecations.
+ default(:treat_deprecation_warnings_as_errors) do
+ # Using an environment variable allows this setting to be inherited in
+ # tests that spawn new processes.
+ ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS")
+ end
+
+ # knife configuration data
+ config_context :knife do
+ default :ssh_port, nil
+ default :ssh_user, nil
+ default :ssh_attribute, nil
+ default :ssh_gateway, nil
+ default :bootstrap_version, nil
+ default :bootstrap_proxy, nil
+ default :bootstrap_template, nil
+ default :secret, nil
+ default :secret_file, nil
+ default :identity_file, nil
+ default :host_key_verify, nil
+ default :forward_agent, nil
+ default :sort_status_reverse, nil
+ default :hints, {}
+ end
+
+ def self.set_defaults_for_windows
+ # Those lists of regular expressions define what chef considers a
+ # valid user and group name
+ # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx
+ principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+'
+ default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
+ default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
+
+ default :fatal_windows_admin_check, false
+ end
+
+ def self.set_defaults_for_nix
+ # Those lists of regular expressions define what chef considers a
+ # valid user and group name
+ #
+ # user/group cannot start with '-', '+' or '~'
+ # user/group cannot contain ':', ',' or non-space-whitespace or null byte
+ # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not
+ # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup
+ default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
+ default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
+ end
+
+ # Those lists of regular expressions define what chef considers a
+ # valid user and group name
+ if ChefConfig.windows?
+ set_defaults_for_windows
+ else
+ set_defaults_for_nix
+ end
+
+ # This provides a hook which rspec can stub so that we can avoid twiddling
+ # global state in tests.
+ def self.env
+ ENV
+ end
+
+ def self.windows_home_path
+ ChefConfig.logger.deprecation("Chef::Config.windows_home_path is now deprecated. Consider using Chef::Util::PathHelper.home instead.")
+ PathHelper.home
+ end
+
+ # returns a platform specific path to the user home dir if set, otherwise default to current directory.
+ default( :user_home ) { PathHelper.home || Dir.pwd }
+
+ # Enable file permission fixup for selinux. Fixup will be done
+ # only if selinux is enabled in the system.
+ default :enable_selinux_file_permission_fixup, true
+
+ # Use atomic updates (i.e. move operation) while updating contents
+ # of the files resources. When set to false copy operation is
+ # used to update files.
+ default :file_atomic_update, true
+
+ # There are 3 possible values for this configuration setting.
+ # true => file staging is done in the destination directory
+ # false => file staging is done via tempfiles under ENV['TMP']
+ # :auto => file staging will try using destination directory if possible and
+ # will fall back to ENV['TMP'] if destination directory is not usable.
+ default :file_staging_uses_destdir, :auto
+
+ # Exit if another run is in progress and the chef-client is unable to
+ # get the lock before time expires. If nil, no timeout is enforced. (Exits
+ # immediately if 0.)
+ default :run_lock_timeout, nil
+
+ # Number of worker threads for syncing cookbooks in parallel. Increasing
+ # this number can result in gateway errors from the server (namely 503 and 504).
+ # If you are seeing this behavior while using the default setting, reducing
+ # the number of threads will help.
+ default :cookbook_sync_threads, 10
+
+ # At the beginning of the Chef Client run, the cookbook manifests are downloaded which
+ # contain URLs for every file in every relevant cookbook. Most of the files
+ # (recipes, resources, providers, libraries, etc) are immediately synchronized
+ # at the start of the run. The handling of "files" and "templates" directories,
+ # however, have two modes of operation. They can either all be downloaded immediately
+ # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as
+ # cookbook_file or template resources are converged which require them (no_lazy_load==false).
+ #
+ # The advantage of lazily loading these files is that unnecessary files are not
+ # synchronized. This may be useful to users with large files checked into cookbooks which
+ # are only selectively downloaded to a subset of clients which use the cookbook. However,
+ # better solutions are to either isolate large files into individual cookbooks and only
+ # include those cookbooks in the run lists of the servers that need them -- or move to
+ # using remote_file and a more appropriate backing store like S3 for large file
+ # distribution.
+ #
+ # The disadvantages of lazily loading files are that users some time find it
+ # confusing that their cookbooks are not fully synchronzied to the cache initially,
+ # and more importantly the time-sensitive URLs which are in the manifest may time
+ # out on long Chef runs before the resource that uses the file is converged
+ # (leading to many confusing 403 errors on template/cookbook_file resources).
+ #
+ default :no_lazy_load, true
+
+ # Default for the chef_gem compile_time attribute. Nil is the same as true but will emit
+ # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to
+ # true then the user will get backcompat behavior but with a single nag warning that cookbooks
+ # may break with this setting in the future. The false setting is the recommended setting and
+ # will become the default.
+ default :chef_gem_compile_time, nil
+
+ # A whitelisted array of attributes you want sent over the wire when node
+ # data is saved.
+ # The default setting is nil, which collects all data. Setting to [] will not
+ # collect any data for save.
+ default :automatic_attribute_whitelist, nil
+ default :default_attribute_whitelist, nil
+ default :normal_attribute_whitelist, nil
+ default :override_attribute_whitelist, nil
+
+ config_context :windows_service do
+ # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run
+ # to finish
+ default :watchdog_timeout, 2 * (60 * 60) # 2 hours
+ end
+
+ # Chef requires an English-language UTF-8 locale to function properly. We attempt
+ # to use the 'locale -a' command and search through a list of preferences until we
+ # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be
+ # able to use that even if there is no English locale on the server, but Mac, Solaris,
+ # AIX, etc do not have that locale. We then try to find an English locale and fall
+ # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try
+ # to do the work to return a non-US UTF-8 locale then we fail inside of providers when
+ # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then
+ # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding
+ # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by
+ # default rather than drop English.
+ #
+ # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly
+ # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'.
+ def self.guess_internal_locale
+ # https://github.com/opscode/chef/issues/2181
+ # Some systems have the `locale -a` command, but the result has
+ # invalid characters for the default encoding.
+ #
+ # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8",
+ # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding.
+ cmd = Mixlib::ShellOut.new("locale -a").run_command
+ cmd.error!
+ locales = cmd.stdout.split
+ case
+ when locales.include?('C.UTF-8')
+ 'C.UTF-8'
+ when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8')
+ 'en_US.UTF-8'
+ when locales.include?('en.UTF-8')
+ 'en.UTF-8'
+ else
+ # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8
+ guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i }
+ unless guesses.empty?
+ guessed_locale = guesses.first
+ # Transform into the form en_ZZ.UTF-8
+ guessed_locale.gsub(/UTF-?8$/i, "UTF-8")
+ else
+ ChefConfig.logger.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
+ 'C'
+ end
+ end
+ rescue
+ if ChefConfig.windows?
+ ChefConfig.logger.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else."
+ else
+ ChefConfig.logger.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
+ end
+ 'en_US.UTF-8'
+ end
+
+ default :internal_locale, guess_internal_locale
+
+ # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g.
+ # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's
+ # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been
+ # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be
+ # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with
+ # magic tags to make ruby correctly identify the encoding being used. Changing this default will
+ # break Chef community cookbooks and is very highly discouraged.
+ default :ruby_encoding, Encoding::UTF_8
+
+ # If installed via an omnibus installer, this gives the path to the
+ # "embedded" directory which contains all of the software packaged with
+ # omnibus. This is used to locate the cacert.pem file on windows.
+ def self.embedded_dir
+ Pathname.new(_this_file).ascend do |path|
+ if path.basename.to_s == "embedded"
+ return path.to_s
+ end
+ end
+
+ nil
+ end
+
+ # Path to this file in the current install.
+ def self._this_file
+ File.expand_path(__FILE__)
+ end
+ end
+end
diff --git a/chef-config/lib/chef-config/exceptions.rb b/chef-config/lib/chef-config/exceptions.rb
new file mode 100644
index 0000000000..1f80e505df
--- /dev/null
+++ b/chef-config/lib/chef-config/exceptions.rb
@@ -0,0 +1,26 @@
+#
+# 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-config/windows'
+require 'chef-config/logger'
+
+module ChefConfig
+
+ class ConfigurationError < ArgumentError; end
+ class InvalidPath < StandardError; end
+
+end
diff --git a/chef-config/lib/chef-config/logger.rb b/chef-config/lib/chef-config/logger.rb
new file mode 100644
index 0000000000..57f18809ee
--- /dev/null
+++ b/chef-config/lib/chef-config/logger.rb
@@ -0,0 +1,62 @@
+#
+# 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.
+#
+
+
+module ChefConfig
+
+ # Implements enough of Logger's API that we can use it in place of a real
+ # logger for `ChefConfig.logger`
+ class NullLogger
+
+ def <<(_msg)
+ end
+
+ def add(_severity, _message = nil, _progname = nil)
+ end
+
+ def debug(_progname = nil, &block)
+ end
+
+ def info(_progname = nil, &block)
+ end
+
+ def warn(_progname = nil, &block)
+ end
+
+ def deprecation(_progname = nil, &block)
+ end
+
+ def error(_progname = nil, &block)
+ end
+
+ def fatal(_progname = nil, &block)
+ end
+
+ end
+
+ @logger = NullLogger.new
+
+ def self.logger=(new_logger)
+ @logger = new_logger
+ end
+
+ def self.logger
+ @logger
+ 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
new file mode 100644
index 0000000000..45f451479a
--- /dev/null
+++ b/chef-config/lib/chef-config/path_helper.rb
@@ -0,0 +1,264 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef-config/windows'
+require 'chef-config/logger'
+require 'chef-config/exceptions'
+
+module ChefConfig
+ class PathHelper
+ # Maximum characters in a standard Windows path (260 including drive letter and NUL)
+ WIN_MAX_PATH = 259
+
+ def self.dirname(path)
+ if ChefConfig.windows?
+ # Find the first slash, not counting trailing slashes
+ end_slash = path.size
+ loop do
+ slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
+ if !slash
+ return end_slash == path.size ? '.' : path_separator
+ elsif slash == end_slash - 1
+ end_slash = slash
+ else
+ return path[0..slash-1]
+ end
+ end
+ else
+ ::File.dirname(path)
+ end
+ end
+
+ BACKSLASH = '\\'.freeze
+
+ def self.path_separator
+ if ChefConfig.windows?
+ File::ALT_SEPARATOR || BACKSLASH
+ else
+ File::SEPARATOR
+ end
+ end
+
+ def self.join(*args)
+ path_separator_regex = Regexp.escape(File::SEPARATOR)
+ unless path_separator == File::SEPARATOR
+ path_separator_regex << Regexp.escape(path_separator)
+ end
+
+ trailing_slashes = /[#{path_separator_regex}]+$/
+ leading_slashes = /^[#{path_separator_regex}]+/
+
+ args.flatten.inject() do |joined_path, component|
+ joined_path = joined_path.sub(trailing_slashes, '')
+ component = component.sub(leading_slashes, '')
+ joined_path += "#{path_separator}#{component}"
+ end
+ end
+
+ def self.validate_path(path)
+ if ChefConfig.windows?
+ unless printable?(path)
+ msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings."
+ ChefConfig.logger.error(msg)
+ raise ChefConfig::InvalidPath, msg
+ end
+
+ if windows_max_length_exceeded?(path)
+ ChefConfig.logger.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
+ path.insert(0, "\\\\?\\")
+ end
+ end
+
+ path
+ end
+
+ def self.windows_max_length_exceeded?(path)
+ # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
+ unless path =~ /^\\\\?\\/
+ if path.length > WIN_MAX_PATH
+ return true
+ end
+ end
+
+ false
+ end
+
+ def self.printable?(string)
+ # returns true if string is free of non-printable characters (escape sequences)
+ # this returns false for whitespace escape sequences as well, e.g. \n\t
+ if string =~ /[^[:print:]]/
+ false
+ else
+ true
+ end
+ end
+
+ # Produces a comparable path.
+ def self.canonical_path(path, add_prefix=true)
+ # First remove extra separators and resolve any relative paths
+ abs_path = File.absolute_path(path)
+
+ if ChefConfig.windows?
+ # Add the \\?\ API prefix on Windows unless add_prefix is false
+ # Downcase on Windows where paths are still case-insensitive
+ abs_path.gsub!(::File::SEPARATOR, path_separator)
+ if add_prefix && abs_path !~ /^\\\\?\\/
+ abs_path.insert(0, "\\\\?\\")
+ end
+
+ abs_path.downcase!
+ end
+
+ abs_path
+ end
+
+ def self.cleanpath(path)
+ path = Pathname.new(path).cleanpath.to_s
+ # ensure all forward slashes are backslashes
+ if ChefConfig.windows?
+ path = path.gsub(File::SEPARATOR, path_separator)
+ end
+ path
+ end
+
+ def self.paths_eql?(path1, path2)
+ canonical_path(path1) == canonical_path(path2)
+ end
+
+ # Paths which may contain glob-reserved characters need
+ # to be escaped before globbing can be done.
+ # http://stackoverflow.com/questions/14127343
+ def self.escape_glob(*parts)
+ path = cleanpath(join(*parts))
+ path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x }
+ end
+
+ def self.relative_path_from(from, to)
+ Pathname.new(cleanpath(to)).relative_path_from(Pathname.new(cleanpath(from)))
+ end
+
+ # Retrieves the "home directory" of the current user while trying to ascertain the existence
+ # of said directory. The path returned uses / for all separators (the ruby standard format).
+ # If the home directory doesn't exist or an error is otherwise encountered, nil is returned.
+ #
+ # If a set of path elements is provided, they are appended as-is to the home path if the
+ # homepath exists.
+ #
+ # If an optional block is provided, the joined path is passed to that block if the home path is
+ # valid and the result of the block is returned instead.
+ #
+ # Home-path discovery is performed once. If a path is discovered, that value is memoized so
+ # that subsequent calls to home_dir don't bounce around.
+ #
+ # See self.all_homes.
+ def self.home(*args)
+ @@home_dir ||= self.all_homes { |p| break p }
+ if @@home_dir
+ path = File.join(@@home_dir, *args)
+ block_given? ? (yield path) : path
+ end
+ end
+
+ # See self.home. This method performs a similar operation except that it yields all the different
+ # possible values of 'HOME' that one could have on this platform. Hence, on windows, if
+ # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice.
+ # This method goes out and checks the existence of each location at the time of the call.
+ #
+ # The return is a list of all the returned values from each block invocation or a list of paths
+ # if no block is provided.
+ def self.all_homes(*args)
+ paths = []
+ if ChefConfig.windows?
+ # By default, Ruby uses the the following environment variables to determine Dir.home:
+ # HOME
+ # HOMEDRIVE HOMEPATH
+ # USERPROFILE
+ # Ruby only checks to see if the variable is specified - not if the directory actually exists.
+ # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive)
+ # while USERPROFILE points to the location where the user application settings and profile are stored. HOME
+ # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is
+ # HOMESHARE instead of HOMEDRIVE.
+ #
+ # We instead walk down the following and only include paths that actually exist.
+ # HOME
+ # HOMEDRIVE HOMEPATH
+ # HOMESHARE HOMEPATH
+ # USERPROFILE
+
+ paths << ENV['HOME']
+ paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
+ paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH']
+ paths << ENV['USERPROFILE']
+ end
+ paths << Dir.home if ENV['HOME']
+
+ # Depending on what environment variables we're using, the slashes can go in any which way.
+ # Just change them all to / to keep things consistent.
+ # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on
+ # the particular brand of kool-aid you consume. This code assumes that \ and / are both
+ # path separators on any system being used.
+ paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path }
+
+ # Filter out duplicate paths and paths that don't exist.
+ valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) }
+ valid_paths = valid_paths.uniq
+
+ # Join all optional path elements at the end.
+ # If a block is provided, invoke it - otherwise just return what we've got.
+ joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) }
+ if block_given?
+ joined_paths.each { |p| yield p }
+ else
+ joined_paths
+ end
+ end
+
+ # 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
new file mode 100644
index 0000000000..9579f0638d
--- /dev/null
+++ b/chef-config/lib/chef-config/version.rb
@@ -0,0 +1,34 @@
+# 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
+ 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/windows.rb b/chef-config/lib/chef-config/windows.rb
new file mode 100644
index 0000000000..a2e90067df
--- /dev/null
+++ b/chef-config/lib/chef-config/windows.rb
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+module ChefConfig
+
+ def self.windows?
+ if RUBY_PLATFORM =~ /mswin|mingw|windows/
+ true
+ else
+ false
+ end
+ end
+
+end
+
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/chef-config/spec/spec_helper.rb b/chef-config/spec/spec_helper.rb
new file mode 100644
index 0000000000..df9461cde9
--- /dev/null
+++ b/chef-config/spec/spec_helper.rb
@@ -0,0 +1,75 @@
+require 'chef-config/windows'
+
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+ # rspec-expectations config goes here. You can use an alternate
+ # assertion/expectation library such as wrong or the stdlib/minitest
+ # assertions if you prefer.
+ config.expect_with :rspec do |expectations|
+ # This option will default to `true` in RSpec 4. It makes the `description`
+ # and `failure_message` of custom matchers include text for helper methods
+ # defined using `chain`, e.g.:
+ # be_bigger_than(2).and_smaller_than(4).description
+ # # => "be bigger than 2 and smaller than 4"
+ # ...rather than:
+ # # => "be bigger than 2"
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+ end
+
+ # rspec-mocks config goes here. You can use an alternate test double
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
+ config.mock_with :rspec do |mocks|
+ # Prevents you from mocking or stubbing a method that does not exist on
+ # a real object. This is generally recommended, and will default to
+ # `true` in RSpec 4.
+ mocks.verify_partial_doubles = true
+ end
+
+ # These two settings work together to allow you to limit a spec run
+ # to individual examples or groups you care about by tagging them with
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
+ # get run.
+ config.filter_run :focus
+ config.run_all_when_everything_filtered = true
+
+ config.filter_run_excluding :windows_only => true unless ChefConfig.windows?
+ config.filter_run_excluding :unix_only => true if ChefConfig.windows?
+
+ # Limits the available syntax to the non-monkey patched syntax that is
+ # recommended. For more details, see:
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
+ config.disable_monkey_patching!
+
+ # This setting enables warnings. It's recommended, but in some cases may
+ # be too noisy due to issues in dependencies.
+ config.warnings = true
+
+ # Many RSpec users commonly either run the entire suite or an individual
+ # file, and it's useful to allow more verbose output when running an
+ # individual spec file.
+ if config.files_to_run.one?
+ # Use the documentation formatter for detailed output,
+ # unless a formatter has already been configured
+ # (e.g. via a command-line flag).
+ config.default_formatter = 'doc'
+ end
+
+ # Print the 10 slowest examples and example groups at the
+ # end of the spec run, to help surface which specs are running
+ # particularly slow.
+ # config.profile_examples = 10
+
+ # Run specs in random order to surface order dependencies. If you find an
+ # order dependency and want to debug it, you can fix the order by providing
+ # the seed, which is printed after each run.
+ # --seed 1234
+ config.order = :random
+
+ # Seed global randomization in this process using the `--seed` CLI option.
+ # Setting this allows you to use `--seed` to deterministically reproduce
+ # test failures related to randomization by passing the same `--seed` value
+ # as the one that triggered the failure.
+ Kernel.srand config.seed
+end
diff --git a/chef-config/spec/unit/config_spec.rb b/chef-config/spec/unit/config_spec.rb
new file mode 100644
index 0000000000..395fa2618e
--- /dev/null
+++ b/chef-config/spec/unit/config_spec.rb
@@ -0,0 +1,581 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef-config/config'
+
+RSpec.describe ChefConfig::Config do
+ before(:each) do
+ ChefConfig::Config.reset
+
+ # By default, treat deprecation warnings as errors in tests.
+ ChefConfig::Config.treat_deprecation_warnings_as_errors(true)
+
+ # Set environment variable so the setting persists in child processes
+ ENV['CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS'] = "1"
+ end
+
+ describe "config attribute writer: chef_server_url" do
+ before do
+ ChefConfig::Config.chef_server_url = "https://junglist.gen.nz"
+ end
+
+ it "sets the server url" do
+ expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz")
+ end
+
+ context "when the url has a leading space" do
+ before do
+ ChefConfig::Config.chef_server_url = " https://junglist.gen.nz"
+ end
+
+ it "strips the space from the url when setting" do
+ expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz")
+ end
+
+ end
+
+ context "when the url is a frozen string" do
+ before do
+ ChefConfig::Config.chef_server_url = " https://junglist.gen.nz".freeze
+ end
+
+ it "strips the space from the url when setting without raising an error" do
+ expect(ChefConfig::Config.chef_server_url).to eq("https://junglist.gen.nz")
+ end
+ end
+ end
+
+ describe "when configuring formatters" do
+ # if TTY and not(force-logger)
+ # formatter = configured formatter or default formatter
+ # formatter goes to STDOUT/ERR
+ # if log file is writeable
+ # log level is configured level or info
+ # log location is file
+ # else
+ # log level is warn
+ # log location is STDERR
+ # end
+ # elsif not(TTY) and force formatter
+ # formatter = configured formatter or default formatter
+ # if log_location specified
+ # formatter goes to log_location
+ # else
+ # formatter goes to STDOUT/ERR
+ # end
+ # else
+ # formatter = "null"
+ # log_location = configured-value or defualt
+ # log_level = info or defualt
+ # end
+ #
+ it "has an empty list of formatters by default" do
+ expect(ChefConfig::Config.formatters).to eq([])
+ end
+
+ it "configures a formatter with a short name" do
+ ChefConfig::Config.add_formatter(:doc)
+ expect(ChefConfig::Config.formatters).to eq([[:doc, nil]])
+ end
+
+ it "configures a formatter with a file output" do
+ ChefConfig::Config.add_formatter(:doc, "/var/log/formatter.log")
+ expect(ChefConfig::Config.formatters).to eq([[:doc, "/var/log/formatter.log"]])
+ end
+
+ end
+
+ [ false, true ].each do |is_windows|
+
+ context "On #{is_windows ? 'Windows' : 'Unix'}" do
+ def to_platform(*args)
+ ChefConfig::Config.platform_specific_path(*args)
+ end
+
+ before :each do
+ allow(ChefConfig).to receive(:windows?).and_return(is_windows)
+ end
+
+ describe "class method: platform_specific_path" do
+ if is_windows
+ it "should return a windows path on windows systems" do
+ path = "/etc/chef/cookbooks"
+ allow(ChefConfig::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' })
+ # match on a regex that looks for the base path with an optional
+ # system drive at the beginning (c:)
+ # system drive is not hardcoded b/c it can change and b/c it is not present on linux systems
+ expect(ChefConfig::Config.platform_specific_path(path)).to eq("C:\\chef\\cookbooks")
+ end
+ else
+ it "should return given path on non-windows systems" do
+ path = "/etc/chef/cookbooks"
+ expect(ChefConfig::Config.platform_specific_path(path)).to eq("/etc/chef/cookbooks")
+ end
+ end
+ end
+
+ describe "default values" do
+ let :primary_cache_path do
+ if is_windows
+ "#{ChefConfig::Config.env['SYSTEMDRIVE']}\\chef"
+ else
+ "/var/chef"
+ end
+ end
+
+ let :secondary_cache_path do
+ if is_windows
+ "#{ChefConfig::Config[:user_home]}\\.chef"
+ else
+ "#{ChefConfig::Config[:user_home]}/.chef"
+ end
+ end
+
+ before do
+ if is_windows
+ allow(ChefConfig::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' })
+ ChefConfig::Config[:user_home] = 'C:\Users\charlie'
+ else
+ ChefConfig::Config[:user_home] = '/Users/charlie'
+ end
+
+ allow(ChefConfig::Config).to receive(:path_accessible?).and_return(false)
+ end
+
+ describe "ChefConfig::Config[:chef_server_root]" do
+ context "when chef_server_url isn't set manually" do
+ it "returns the default of 'https://localhost:443'" do
+ expect(ChefConfig::Config[:chef_server_root]).to eq("https://localhost:443")
+ end
+ end
+
+ context "when chef_server_url matches '../organizations/*' without a trailing slash" do
+ before do
+ ChefConfig::Config[:chef_server_url] = "https://example.com/organizations/myorg"
+ end
+ it "returns the full URL without /organizations/*" do
+ expect(ChefConfig::Config[:chef_server_root]).to eq("https://example.com")
+ end
+ end
+
+ context "when chef_server_url matches '../organizations/*' with a trailing slash" do
+ before do
+ ChefConfig::Config[:chef_server_url] = "https://example.com/organizations/myorg/"
+ end
+ it "returns the full URL without /organizations/*" do
+ expect(ChefConfig::Config[:chef_server_root]).to eq("https://example.com")
+ end
+ end
+
+ context "when chef_server_url matches '..organizations..' but not '../organizations/*'" do
+ before do
+ ChefConfig::Config[:chef_server_url] = "https://organizations.com/organizations"
+ end
+ it "returns the full URL without any modifications" do
+ expect(ChefConfig::Config[:chef_server_root]).to eq(ChefConfig::Config[:chef_server_url])
+ end
+ end
+
+ context "when chef_server_url is a standard URL without the string organization(s)" do
+ before do
+ ChefConfig::Config[:chef_server_url] = "https://example.com/some_other_string"
+ end
+ it "returns the full URL without any modifications" do
+ expect(ChefConfig::Config[:chef_server_root]).to eq(ChefConfig::Config[:chef_server_url])
+ end
+ end
+ end
+
+ describe "ChefConfig::Config[:cache_path]" do
+ context "when /var/chef exists and is accessible" do
+ it "defaults to /var/chef" do
+ allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var/chef")).and_return(true)
+ expect(ChefConfig::Config[:cache_path]).to eq(primary_cache_path)
+ end
+ end
+
+ context "when /var/chef does not exist and /var is accessible" do
+ it "defaults to /var/chef" do
+ allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false)
+ allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(true)
+ expect(ChefConfig::Config[:cache_path]).to eq(primary_cache_path)
+ end
+ end
+
+ context "when /var/chef does not exist and /var is not accessible" do
+ it "defaults to $HOME/.chef" do
+ allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false)
+ allow(ChefConfig::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(false)
+ expect(ChefConfig::Config[:cache_path]).to eq(secondary_cache_path)
+ end
+ end
+
+ context "when /var/chef exists and is not accessible" do
+ it "defaults to $HOME/.chef" do
+ allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(true)
+ allow(File).to receive(:readable?).with(to_platform("/var/chef")).and_return(true)
+ allow(File).to receive(:writable?).with(to_platform("/var/chef")).and_return(false)
+
+ expect(ChefConfig::Config[:cache_path]).to eq(secondary_cache_path)
+ end
+ end
+
+ context "when chef is running in local mode" do
+ before do
+ ChefConfig::Config.local_mode = true
+ end
+
+ context "and config_dir is /a/b/c" do
+ before do
+ ChefConfig::Config.config_dir to_platform('/a/b/c')
+ end
+
+ it "cache_path is /a/b/c/local-mode-cache" do
+ expect(ChefConfig::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache'))
+ end
+ end
+
+ context "and config_dir is /a/b/c/" do
+ before do
+ ChefConfig::Config.config_dir to_platform('/a/b/c/')
+ end
+
+ it "cache_path is /a/b/c/local-mode-cache" do
+ expect(ChefConfig::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache'))
+ end
+ end
+ end
+ end
+
+ it "ChefConfig::Config[:file_backup_path] defaults to /var/chef/backup" do
+ allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path)
+ backup_path = is_windows ? "#{primary_cache_path}\\backup" : "#{primary_cache_path}/backup"
+ expect(ChefConfig::Config[:file_backup_path]).to eq(backup_path)
+ end
+
+ it "ChefConfig::Config[:ssl_verify_mode] defaults to :verify_peer" do
+ expect(ChefConfig::Config[:ssl_verify_mode]).to eq(:verify_peer)
+ end
+
+ it "ChefConfig::Config[:ssl_ca_path] defaults to nil" do
+ expect(ChefConfig::Config[:ssl_ca_path]).to be_nil
+ end
+
+ # On Windows, we'll detect an omnibus build and set this to the
+ # cacert.pem included in the package, but it's nil if you're on Windows
+ # w/o omnibus (e.g., doing development on Windows, custom build, etc.)
+ if !is_windows
+ it "ChefConfig::Config[:ssl_ca_file] defaults to nil" do
+ expect(ChefConfig::Config[:ssl_ca_file]).to be_nil
+ end
+ end
+
+ it "ChefConfig::Config[:data_bag_path] defaults to /var/chef/data_bags" do
+ allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path)
+ data_bag_path = is_windows ? "#{primary_cache_path}\\data_bags" : "#{primary_cache_path}/data_bags"
+ expect(ChefConfig::Config[:data_bag_path]).to eq(data_bag_path)
+ end
+
+ it "ChefConfig::Config[:environment_path] defaults to /var/chef/environments" do
+ allow(ChefConfig::Config).to receive(:cache_path).and_return(primary_cache_path)
+ environment_path = is_windows ? "#{primary_cache_path}\\environments" : "#{primary_cache_path}/environments"
+ expect(ChefConfig::Config[:environment_path]).to eq(environment_path)
+ end
+
+ describe "setting the config dir" do
+
+ context "when the config file is /etc/chef/client.rb" do
+
+ before do
+ ChefConfig::Config.config_file = to_platform("/etc/chef/client.rb")
+ end
+
+ it "config_dir is /etc/chef" do
+ expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef"))
+ end
+
+ context "and chef is running in local mode" do
+ before do
+ ChefConfig::Config.local_mode = true
+ end
+
+ it "config_dir is /etc/chef" do
+ expect(ChefConfig::Config.config_dir).to eq(to_platform("/etc/chef"))
+ end
+ end
+
+ context "when config_dir is set to /other/config/dir/" do
+ before do
+ ChefConfig::Config.config_dir = to_platform("/other/config/dir/")
+ end
+
+ it "yields the explicit value" do
+ expect(ChefConfig::Config.config_dir).to eq(to_platform("/other/config/dir/"))
+ end
+ end
+
+ end
+
+ context "when the user's home dir is /home/charlie/" do
+ before do
+ ChefConfig::Config.user_home = to_platform("/home/charlie")
+ end
+
+ it "config_dir is /home/charlie/.chef/" do
+ expect(ChefConfig::Config.config_dir).to eq(ChefConfig::PathHelper.join(to_platform("/home/charlie/.chef"), ''))
+ end
+
+ context "and chef is running in local mode" do
+ before do
+ ChefConfig::Config.local_mode = true
+ end
+
+ it "config_dir is /home/charlie/.chef/" do
+ expect(ChefConfig::Config.config_dir).to eq(ChefConfig::PathHelper.join(to_platform("/home/charlie/.chef"), ''))
+ end
+ end
+ end
+
+ end
+
+ if is_windows
+ describe "finding the windows embedded dir" do
+ let(:default_config_location) { "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
+ let(:alternate_install_location) { "c:/my/alternate/install/place/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
+ let(:non_omnibus_location) { "c:/my/dev/stuff/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
+
+ let(:default_ca_file) { "c:/opscode/chef/embedded/ssl/certs/cacert.pem" }
+
+ it "finds the embedded dir in the default location" do
+ allow(ChefConfig::Config).to receive(:_this_file).and_return(default_config_location)
+ expect(ChefConfig::Config.embedded_dir).to eq("c:/opscode/chef/embedded")
+ end
+
+ it "finds the embedded dir in a custom install location" do
+ allow(ChefConfig::Config).to receive(:_this_file).and_return(alternate_install_location)
+ expect(ChefConfig::Config.embedded_dir).to eq("c:/my/alternate/install/place/chef/embedded")
+ end
+
+ it "doesn't error when not in an omnibus install" do
+ allow(ChefConfig::Config).to receive(:_this_file).and_return(non_omnibus_location)
+ expect(ChefConfig::Config.embedded_dir).to be_nil
+ end
+
+ it "sets the ssl_ca_cert path if the cert file is available" do
+ allow(ChefConfig::Config).to receive(:_this_file).and_return(default_config_location)
+ allow(File).to receive(:exist?).with(default_ca_file).and_return(true)
+ expect(ChefConfig::Config.ssl_ca_file).to eq(default_ca_file)
+ end
+ end
+ end
+ end
+
+ describe "ChefConfig::Config[:user_home]" do
+ it "should set when HOME is provided" do
+ expected = to_platform("/home/kitten")
+ allow(ChefConfig::PathHelper).to receive(:home).and_return(expected)
+ expect(ChefConfig::Config[:user_home]).to eq(expected)
+ end
+
+ it "falls back to the current working directory when HOME and USERPROFILE is not set" do
+ allow(ChefConfig::PathHelper).to receive(:home).and_return(nil)
+ expect(ChefConfig::Config[:user_home]).to eq(Dir.pwd)
+ end
+ end
+
+ describe "ChefConfig::Config[:encrypted_data_bag_secret]" do
+ let(:db_secret_default_path){ to_platform("/etc/chef/encrypted_data_bag_secret") }
+
+ before do
+ allow(File).to receive(:exist?).with(db_secret_default_path).and_return(secret_exists)
+ end
+
+ context "/etc/chef/encrypted_data_bag_secret exists" do
+ let(:secret_exists) { true }
+ it "sets the value to /etc/chef/encrypted_data_bag_secret" do
+ expect(ChefConfig::Config[:encrypted_data_bag_secret]).to eq db_secret_default_path
+ end
+ end
+
+ context "/etc/chef/encrypted_data_bag_secret does not exist" do
+ let(:secret_exists) { false }
+ it "sets the value to nil" do
+ expect(ChefConfig::Config[:encrypted_data_bag_secret]).to be_nil
+ end
+ end
+ end
+
+ describe "ChefConfig::Config[:event_handlers]" do
+ it "sets a event_handlers to an empty array by default" do
+ expect(ChefConfig::Config[:event_handlers]).to eq([])
+ end
+ it "should be able to add custom handlers" do
+ o = Object.new
+ ChefConfig::Config[:event_handlers] << o
+ expect(ChefConfig::Config[:event_handlers]).to be_include(o)
+ end
+ end
+
+ describe "ChefConfig::Config[:user_valid_regex]" do
+ context "on a platform that is not Windows" do
+ it "allows one letter usernames" do
+ any_match = ChefConfig::Config[:user_valid_regex].any? { |regex| regex.match('a') }
+ expect(any_match).to be_truthy
+ end
+ end
+ end
+
+ describe "ChefConfig::Config[:internal_locale]" do
+ let(:shell_out) do
+ cmd = instance_double("Mixlib::ShellOut", exitstatus: 0, stdout: locales, error!: nil)
+ allow(cmd).to receive(:run_command).and_return(cmd)
+ cmd
+ end
+
+ let(:locales) { locale_array.join("\n") }
+
+ before do
+ allow(Mixlib::ShellOut).to receive(:new).with("locale -a").and_return(shell_out)
+ end
+
+ shared_examples_for "a suitable locale" do
+ it "returns an English UTF-8 locale" do
+ expect(ChefConfig.logger).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/)
+ expect(ChefConfig.logger).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/)
+ expect(ChefConfig.logger).to_not receive(:debug).with(/No usable locale -a command found/)
+ expect(ChefConfig::Config.guess_internal_locale).to eq expected_locale
+ end
+ end
+
+ context "when the result includes 'C.UTF-8'" do
+ include_examples "a suitable locale" do
+ let(:locale_array) { [expected_locale, "en_US.UTF-8"] }
+ let(:expected_locale) { "C.UTF-8" }
+ end
+ end
+
+ context "when the result includes 'en_US.UTF-8'" do
+ include_examples "a suitable locale" do
+ let(:locale_array) { ["en_CA.UTF-8", expected_locale, "en_NZ.UTF-8"] }
+ let(:expected_locale) { "en_US.UTF-8" }
+ end
+ end
+
+ context "when the result includes 'en_US.utf8'" do
+ include_examples "a suitable locale" do
+ let(:locale_array) { ["en_CA.utf8", "en_US.utf8", "en_NZ.utf8"] }
+ let(:expected_locale) { "en_US.UTF-8" }
+ end
+ end
+
+ context "when the result includes 'en.UTF-8'" do
+ include_examples "a suitable locale" do
+ let(:locale_array) { ["en.ISO8859-1", expected_locale] }
+ let(:expected_locale) { "en.UTF-8" }
+ end
+ end
+
+ context "when the result includes 'en_*.UTF-8'" do
+ include_examples "a suitable locale" do
+ let(:locale_array) { [expected_locale, "en_CA.UTF-8", "en_GB.UTF-8"] }
+ let(:expected_locale) { "en_AU.UTF-8" }
+ end
+ end
+
+ context "when the result includes 'en_*.utf8'" do
+ include_examples "a suitable locale" do
+ let(:locale_array) { ["en_AU.utf8", "en_CA.utf8", "en_GB.utf8"] }
+ let(:expected_locale) { "en_AU.UTF-8" }
+ end
+ end
+
+ context "when the result does not include 'en_*.UTF-8'" do
+ let(:locale_array) { ["af_ZA", "af_ZA.ISO8859-1", "af_ZA.ISO8859-15", "af_ZA.UTF-8"] }
+
+ it "should fall back to C locale" do
+ expect(ChefConfig.logger).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.")
+ expect(ChefConfig::Config.guess_internal_locale).to eq 'C'
+ end
+ end
+
+ context "on error" do
+ let(:locale_array) { [] }
+
+ let(:shell_out_cmd) { instance_double("Mixlib::ShellOut") }
+
+ before do
+ allow(Mixlib::ShellOut).to receive(:new).and_return(shell_out_cmd)
+ allow(shell_out_cmd).to receive(:run_command)
+ allow(shell_out_cmd).to receive(:error!).and_raise(Mixlib::ShellOut::ShellCommandFailed, "this is an error")
+ end
+
+ it "should default to 'en_US.UTF-8'" do
+ if is_windows
+ expect(ChefConfig.logger).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.")
+ else
+ expect(ChefConfig.logger).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.")
+ end
+ expect(ChefConfig::Config.guess_internal_locale).to eq "en_US.UTF-8"
+ end
+ end
+ end
+ end
+ end
+
+ describe "Treating deprecation warnings as errors" do
+
+ context "when using our default RSpec configuration" do
+
+ it "defaults to treating deprecation warnings as errors" do
+ expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(true)
+ end
+
+ it "sets CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS environment variable" do
+ expect(ENV['CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS']).to eq("1")
+ end
+
+ it "treats deprecation warnings as errors in child processes when testing" do
+ # Doing a full integration test where we launch a child process is slow
+ # and liable to break for weird reasons (bundler env stuff, etc.), so
+ # we're just checking that the presence of the environment variable
+ # causes treat_deprecation_warnings_as_errors to be set to true after a
+ # config reset.
+ ChefConfig::Config.reset
+ expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(true)
+ end
+
+ end
+
+ context "outside of our test environment" do
+
+ before do
+ ENV.delete('CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS')
+ ChefConfig::Config.reset
+ end
+
+ it "defaults to NOT treating deprecation warnings as errors" do
+ expect(ChefConfig::Config[:treat_deprecation_warnings_as_errors]).to be(false)
+ end
+ end
+
+
+ end
+
+end
diff --git a/chef-config/spec/unit/path_helper_spec.rb b/chef-config/spec/unit/path_helper_spec.rb
new file mode 100644
index 0000000000..3e6213597a
--- /dev/null
+++ b/chef-config/spec/unit/path_helper_spec.rb
@@ -0,0 +1,291 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef-config/path_helper'
+require 'spec_helper'
+
+RSpec.describe ChefConfig::PathHelper do
+
+ let(:path_helper) { described_class }
+
+ shared_examples_for "common_functionality" do
+ describe "join" do
+
+ it "joins starting with '' resolve to absolute paths" do
+ expect(path_helper.join('', 'a', 'b')).to eq("#{path_helper.path_separator}a#{path_helper.path_separator}b")
+ end
+
+ it "joins ending with '' add a / to the end" do
+ expect(path_helper.join('a', 'b', '')).to eq("a#{path_helper.path_separator}b#{path_helper.path_separator}")
+ end
+
+ end
+
+ describe "dirname" do
+ it "dirname('abc') is '.'" do
+ expect(path_helper.dirname('abc')).to eq('.')
+ end
+ it "dirname('/') is '/'" do
+ expect(path_helper.dirname(path_helper.path_separator)).to eq(path_helper.path_separator)
+ end
+ it "dirname('a/b/c') is 'a/b'" do
+ expect(path_helper.dirname(path_helper.join('a', 'b', 'c'))).to eq(path_helper.join('a', 'b'))
+ end
+ it "dirname('a/b/c/') is 'a/b'" do
+ expect(path_helper.dirname(path_helper.join('a', 'b', 'c', ''))).to eq(path_helper.join('a', 'b'))
+ end
+ it "dirname('/a/b/c') is '/a/b'" do
+ expect(path_helper.dirname(path_helper.join('', 'a', 'b', 'c'))).to eq(path_helper.join('', 'a', 'b'))
+ end
+ end
+ end
+
+ context "on windows" do
+
+ before(:each) do
+ allow(ChefConfig).to receive(:windows?).and_return(true)
+ end
+
+ include_examples("common_functionality")
+
+ it "path_separator is \\" do
+ expect(path_helper.path_separator).to eq('\\')
+ end
+
+ describe "platform-specific #join behavior" do
+
+ it "joins components on Windows when some end with unix separators" do
+ expect(path_helper.join('C:\\foo/', "bar", "baz")).to eq('C:\\foo\\bar\\baz')
+ end
+
+ it "joins components when some end with separators" do
+ expected = path_helper.cleanpath("/foo/bar/baz")
+ expected = "C:#{expected}"
+ expect(path_helper.join('C:\\foo\\', "bar", "baz")).to eq(expected)
+ end
+
+ it "joins components when some end and start with separators" do
+ expected = path_helper.cleanpath("/foo/bar/baz")
+ expected = "C:#{expected}"
+ expect(path_helper.join('C:\\foo\\', "bar/", "/baz")).to eq(expected)
+ end
+
+ it "joins components that don't end in separators" do
+ expected = path_helper.cleanpath("/foo/bar/baz")
+ expected = "C:#{expected}"
+ expect(path_helper.join('C:\\foo', "bar", "baz")).to eq(expected)
+ end
+
+ end
+
+
+ it "cleanpath changes slashes into backslashes and leaves backslashes alone" do
+ expect(path_helper.cleanpath('/a/b\\c/d/')).to eq('\\a\\b\\c\\d')
+ end
+
+ it "cleanpath does not remove leading double backslash" do
+ expect(path_helper.cleanpath('\\\\a/b\\c/d/')).to eq('\\\\a\\b\\c\\d')
+ end
+
+ end
+
+ context "on unix" do
+
+ before(:each) do
+ allow(ChefConfig).to receive(:windows?).and_return(false)
+ end
+
+ include_examples("common_functionality")
+
+ it "path_separator is /" do
+ expect(path_helper.path_separator).to eq('/')
+ end
+
+ it "cleanpath removes extra slashes alone" do
+ expect(path_helper.cleanpath('/a///b/c/d/')).to eq('/a/b/c/d')
+ end
+
+ describe "platform-specific #join behavior" do
+
+ it "joins components when some end with separators" do
+ expected = path_helper.cleanpath("/foo/bar/baz")
+ expect(path_helper.join("/foo/", "bar", "baz")).to eq(expected)
+ end
+
+ it "joins components when some end and start with separators" do
+ expected = path_helper.cleanpath("/foo/bar/baz")
+ expect(path_helper.join("/foo/", "bar/", "/baz")).to eq(expected)
+ end
+
+ it "joins components that don't end in separators" do
+ expected = path_helper.cleanpath("/foo/bar/baz")
+ expect(path_helper.join("/foo", "bar", "baz")).to eq(expected)
+ end
+
+ end
+
+ end
+
+ describe "validate_path" do
+ context "on windows" do
+ before(:each) do
+ # pass by default
+ allow(ChefConfig).to receive(:windows?).and_return(true)
+ allow(path_helper).to receive(:printable?).and_return(true)
+ allow(path_helper).to receive(:windows_max_length_exceeded?).and_return(false)
+ end
+
+ it "returns the path if the path passes the tests" do
+ expect(path_helper.validate_path("C:\\ThisIsRigged")).to eql("C:\\ThisIsRigged")
+ end
+
+ it "does not raise an error if everything looks great" do
+ expect { path_helper.validate_path("C:\\cool path\\dude.exe") }.not_to raise_error
+ end
+
+ it "raises an error if the path has invalid characters" do
+ allow(path_helper).to receive(:printable?).and_return(false)
+ expect { path_helper.validate_path("Newline!\n") }.to raise_error(ChefConfig::InvalidPath)
+ end
+
+ it "Adds the \\\\?\\ prefix if the path exceeds MAX_LENGTH and does not have it" do
+ long_path = "C:\\" + "a" * 250 + "\\" + "b" * 250
+ prefixed_long_path = "\\\\?\\" + long_path
+ allow(path_helper).to receive(:windows_max_length_exceeded?).and_return(true)
+ expect(path_helper.validate_path(long_path)).to eql(prefixed_long_path)
+ end
+ end
+ end
+
+ describe "windows_max_length_exceeded?" do
+ it "returns true if the path is too long (259 + NUL) for the API" do
+ expect(path_helper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 6)).to be_truthy
+ end
+
+ it "returns false if the path is not too long (259 + NUL) for the standard API" do
+ expect(path_helper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 5)).to be_falsey
+ end
+
+ it "returns false if the path is over 259 characters but uses the \\\\?\\ prefix" do
+ expect(path_helper.windows_max_length_exceeded?("\\\\?\\C:\\" + "a" * 250 + "\\" + "b" * 250)).to be_falsey
+ end
+ end
+
+ describe "printable?" do
+ it "returns true if the string contains no non-printable characters" do
+ expect(path_helper.printable?("C:\\Program Files (x86)\\Microsoft Office\\Files.lst")).to be_truthy
+ end
+
+ it "returns true when given 'abc' in unicode" do
+ expect(path_helper.printable?("\u0061\u0062\u0063")).to be_truthy
+ end
+
+ it "returns true when given japanese unicode" do
+ expect(path_helper.printable?("\uff86\uff87\uff88")).to be_truthy
+ end
+
+ it "returns false if the string contains a non-printable character" do
+ expect(path_helper.printable?("\my files\work\notes.txt")).to be_falsey
+ end
+
+ # This isn't necessarily a requirement, but here to be explicit about functionality.
+ it "returns false if the string contains a newline or tab" do
+ expect(path_helper.printable?("\tThere's no way,\n\t *no* way,\n\t that you came from my loins.\n")).to be_falsey
+ end
+ end
+
+ describe "canonical_path" do
+ context "on windows", :windows_only do
+ it "returns an absolute path with backslashes instead of slashes" do
+ expect(path_helper.canonical_path("\\\\?\\C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini")
+ end
+
+ it "adds the \\\\?\\ prefix if it is missing" do
+ expect(path_helper.canonical_path("C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini")
+ end
+
+ it "returns a lowercase path" do
+ expect(path_helper.canonical_path("\\\\?\\C:\\CASE\\INSENSITIVE")).to eq("\\\\?\\c:\\case\\insensitive")
+ end
+ end
+
+ context "not on windows", :unix_only do
+ it "returns a canonical path" do
+ expect(path_helper.canonical_path("/etc//apache.d/sites-enabled/../sites-available/default")).to eq("/etc/apache.d/sites-available/default")
+ end
+ end
+ end
+
+ describe "paths_eql?" do
+ it "returns true if the paths are the same" do
+ allow(path_helper).to receive(:canonical_path).with("bandit").and_return("c:/bandit/bandit")
+ allow(path_helper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit")
+ expect(path_helper.paths_eql?("bandit", "../bandit/bandit")).to be_truthy
+ end
+
+ it "returns false if the paths are different" do
+ allow(path_helper).to receive(:canonical_path).with("bandit").and_return("c:/Bo/Bandit")
+ allow(path_helper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit")
+ expect(path_helper.paths_eql?("bandit", "../bandit/bandit")).to be_falsey
+ end
+ end
+
+ describe "escape_glob" do
+ it "escapes characters reserved by glob" do
+ path = "C:\\this\\*path\\[needs]\\escaping?"
+ escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
+ expect(path_helper.escape_glob(path)).to eq(escaped_path)
+ end
+
+ context "when given more than one argument" do
+ it "joins, cleanpaths, and escapes characters reserved by glob" do
+ args = ["this/*path", "[needs]", "escaping?"]
+ escaped_path = if ChefConfig.windows?
+ "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
+ else
+ "this/\\*path/\\[needs\\]/escaping\\?"
+ end
+ expect(path_helper).to receive(:join).with(*args).and_call_original
+ expect(path_helper).to receive(:cleanpath).and_call_original
+ expect(path_helper.escape_glob(*args)).to eq(escaped_path)
+ end
+ end
+ end
+
+ describe "all_homes" do
+ before do
+ stub_const('ENV', env)
+ allow(ChefConfig).to receive(:windows?).and_return(is_windows)
+ end
+
+ context "on windows" do
+ let (:is_windows) { true }
+ end
+
+ context "on unix" do
+ let (:is_windows) { false }
+
+ context "when HOME is not set" do
+ let (:env) { {} }
+ it "returns an empty array" do
+ expect(path_helper.all_homes).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/workstation_config_loader_spec.rb b/chef-config/spec/unit/workstation_config_loader_spec.rb
index a865103188..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
- env["HOME"] = home
+ 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
new file mode 100644
index 0000000000..428174889f
--- /dev/null
+++ b/chef-windows.gemspec
@@ -0,0 +1,22 @@
+gemspec = eval(IO.read(File.expand_path("../chef.gemspec", __FILE__)))
+
+gemspec.platform = Gem::Platform.new(["universal", "mingw32"])
+
+gemspec.add_dependency "ffi", "~> 1.9"
+gemspec.add_dependency "win32-api", "~> 1.5.3"
+gemspec.add_dependency "win32-dir", "~> 0.5.0"
+gemspec.add_dependency "win32-event", "~> 0.6.1"
+gemspec.add_dependency "win32-eventlog", "~> 0.6.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.7"
+gemspec.add_dependency "windows-api", "~> 0.4.4"
+gemspec.add_dependency "windows-pr", "~> 1.2.4"
+gemspec.add_dependency "wmi-lite", "~> 1.0"
+gemspec.extensions << "ext/win32-eventlog/Rakefile"
+gemspec.files += %w(ext/win32-eventlog/Rakefile ext/win32-eventlog/chef-log.man)
+
+gemspec.executables += %w( chef-service-manager chef-windows-service )
+
+gemspec
diff --git a/chef-x86-mingw32.gemspec b/chef-x86-mingw32.gemspec
deleted file mode 100644
index 18c31d8354..0000000000
--- a/chef-x86-mingw32.gemspec
+++ /dev/null
@@ -1,23 +0,0 @@
-# x86-mingw32 Gemspec #
-gemspec = eval(IO.read(File.expand_path("../chef.gemspec", __FILE__)))
-
-gemspec.platform = "x86-mingw32"
-
-gemspec.add_dependency "ffi", "~> 1.9"
-gemspec.add_dependency "windows-api", "~> 0.4.2"
-gemspec.add_dependency "windows-pr", "~> 1.2.2"
-gemspec.add_dependency "win32-api", "~> 1.5.1"
-gemspec.add_dependency "win32-dir", "0.4.5"
-gemspec.add_dependency "win32-event", "0.6.1"
-gemspec.add_dependency "win32-mutex", "0.4.1"
-gemspec.add_dependency "win32-process", "~> 0.7.3"
-gemspec.add_dependency "win32-service", "0.8.2"
-gemspec.add_dependency "win32-mmap", "0.4.0"
-gemspec.add_dependency "wmi-lite", "~> 1.0"
-gemspec.add_dependency "win32-eventlog", "0.6.1"
-gemspec.extensions << "ext/win32-eventlog/Rakefile"
-gemspec.files += %w(ext/win32-eventlog/Rakefile ext/win32-eventlog/chef-log.man)
-
-gemspec.executables += %w( chef-service-manager chef-windows-service )
-
-gemspec
diff --git a/chef.gemspec b/chef.gemspec
index d32e0005d6..f28cde21e7 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -10,27 +10,27 @@ Gem::Specification.new do |s|
s.description = s.summary
s.license = "Apache-2.0"
s.author = "Adam Jacob"
- s.email = "adam@getchef.com"
- s.homepage = "http://www.getchef.com"
+ s.email = "adam@chef.io"
+ s.homepage = "http://www.chef.io"
s.required_ruby_version = ">= 2.0.0"
- s.add_dependency "mixlib-config", "~> 2.0"
+ s.add_dependency "chef-config", "= #{Chef::VERSION}"
+
s.add_dependency "mixlib-cli", "~> 1.4"
s.add_dependency "mixlib-log", "~> 1.3"
s.add_dependency "mixlib-authentication", "~> 1.3"
- s.add_dependency "mixlib-shellout", ">= 2.0.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", "~> 1.2"
+ 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"
- s.add_dependency "chef-zero", "~> 4.0"
+ s.add_dependency "chef-zero", "~> 4.2", ">= 4.2.2"
s.add_dependency "pry", "~> 0.9"
s.add_dependency 'plist', '~> 3.1.0'
@@ -41,10 +41,12 @@ Gem::Specification.new do |s|
s.add_dependency "serverspec", "~> 2.7"
s.add_dependency "specinfra", "~> 2.10"
+ s.add_dependency "syslog-logger", "~> 1.6"
+
s.add_development_dependency "rack"
+ s.add_development_dependency "cheffish", "~> 1.1"
- # Rake 10.2 drops Ruby 1.8 support
- s.add_development_dependency "rake", "~> 10.1.0"
+ s.add_development_dependency "rake", "~> 10.1"
s.bindir = "bin"
s.executables = %w( chef-client chef-solo knife chef-shell chef-apply )
diff --git a/distro/common/markdown/man1/chef-shell.mkd b/distro/common/markdown/man1/chef-shell.mkd
index 5525ef8ea9..216dc73d41 100644
--- a/distro/common/markdown/man1/chef-shell.mkd
+++ b/distro/common/markdown/man1/chef-shell.mkd
@@ -131,8 +131,8 @@ Recipe mode implements Chef's recipe DSL. Exhaustively documenting this
DSL is outside the scope of this document. See the following pages in
the Chef documentation for more information:
- * <http://wiki.opscode.com/display/chef/Resources>
- * <http://wiki.opscode.com/display/chef/Recipes>
+ * <http://docs.chef.io/resources.html>
+ * <http://docs.chef.io/recipes.html>
Once you have defined resources in the recipe, you can trigger a
convergence run via `run_chef`
@@ -176,7 +176,7 @@ libraries.
## SEE ALSO
chef-client(8) knife(1)
- <http://wiki.opscode.com/display/chef/Chef+Shell>
+ <http://docs.chef.io/ctl_chef_shell.html>
## AUTHOR
@@ -192,4 +192,4 @@ libraries.
## CHEF
- chef-shell is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ chef-shell is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-bootstrap.mkd b/distro/common/markdown/man1/knife-bootstrap.mkd
index cb292de311..a1a2d3460c 100644
--- a/distro/common/markdown/man1/knife-bootstrap.mkd
+++ b/distro/common/markdown/man1/knife-bootstrap.mkd
@@ -138,4 +138,4 @@ to other users via the process list using tools such as ps(1).
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-client.mkd b/distro/common/markdown/man1/knife-client.mkd
index e7b732ef71..b95a578391 100644
--- a/distro/common/markdown/man1/knife-client.mkd
+++ b/distro/common/markdown/man1/knife-client.mkd
@@ -99,5 +99,5 @@ setting up a host for management with Chef.
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-configure.mkd b/distro/common/markdown/man1/knife-configure.mkd
index 507d30db4e..f3a4ef02bb 100644
--- a/distro/common/markdown/man1/knife-configure.mkd
+++ b/distro/common/markdown/man1/knife-configure.mkd
@@ -66,5 +66,5 @@ the specified _directory_.
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-cookbook-site.mkd b/distro/common/markdown/man1/knife-cookbook-site.mkd
index 9496cf1765..68bc8433df 100644
--- a/distro/common/markdown/man1/knife-cookbook-site.mkd
+++ b/distro/common/markdown/man1/knife-cookbook-site.mkd
@@ -119,5 +119,5 @@ Uploading cookbooks to the Opscode cookbooks site:
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-cookbook.mkd b/distro/common/markdown/man1/knife-cookbook.mkd
index deaf00447a..6a56059e80 100644
--- a/distro/common/markdown/man1/knife-cookbook.mkd
+++ b/distro/common/markdown/man1/knife-cookbook.mkd
@@ -236,7 +236,7 @@ to specify alternate files to be used on a specific OS platform or host.
The default specificity setting is _default_, that is files in
`COOKBOOK/files/default` will be used when a more specific copy is not
available. Further documentation for this feature is available on the
-Chef wiki: <http://wiki.opscode.com/display/chef/File+Distribution#FileDistribution-FileSpecificity>
+Chef wiki: <https://docs.chef.io/resource_cookbook_file.html#file-specificity>
Cookbooks also contain a metadata file that defines various properties
of the cookbook. The most important of these are the _version_ and the
@@ -248,8 +248,8 @@ cookbook.
## SEE ALSO
__knife-environment(1)__ __knife-cookbook-site(1)__
- <http://wiki.opscode.com/display/chef/Cookbooks>
- <http://wiki.opscode.com/display/chef/Metadata>
+ <http://docs.chef.io/cookbooks.html>
+ <http://docs.chef.io/cookbook_repo.html>
## AUTHOR
Chef was written by Adam Jacob <adam@opscode.com> with many contributions from the community.
@@ -260,4 +260,4 @@ cookbook.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-data-bag.mkd b/distro/common/markdown/man1/knife-data-bag.mkd
index 53abf95272..cab28a2f7f 100644
--- a/distro/common/markdown/man1/knife-data-bag.mkd
+++ b/distro/common/markdown/man1/knife-data-bag.mkd
@@ -117,5 +117,5 @@ encryption keys.
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. http://wiki.opscode.com/display/chef/Home
+ Knife is distributed with Chef. http://docs.chef.io/
diff --git a/distro/common/markdown/man1/knife-environment.mkd b/distro/common/markdown/man1/knife-environment.mkd
index 98ca4997bd..06bf423dc0 100644
--- a/distro/common/markdown/man1/knife-environment.mkd
+++ b/distro/common/markdown/man1/knife-environment.mkd
@@ -137,8 +137,8 @@ The Ruby format of an environment is as follows:
## SEE ALSO
__knife-node(1)__ __knife-cookbook(1)__ __knife-role(1)__
- <http://wiki.opscode.com/display/chef/Environments>
- <http://wiki.opscode.com/display/chef/Version+Constraints>
+ <http://docs.chef.io/environments.html>
+ <http://docs.chef.io/cookbook_versions.html>
## AUTHOR
Chef was written by Adam Jacob <adam@opscode.com> with many contributions from the community.
@@ -148,4 +148,4 @@ The Ruby format of an environment is as follows:
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-exec.mkd b/distro/common/markdown/man1/knife-exec.mkd
index d4aa87ca46..1b60177d16 100644
--- a/distro/common/markdown/man1/knife-exec.mkd
+++ b/distro/common/markdown/man1/knife-exec.mkd
@@ -39,4 +39,4 @@ description of the commands available.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-index.mkd b/distro/common/markdown/man1/knife-index.mkd
index 812f3fe7dd..f1425b8013 100644
--- a/distro/common/markdown/man1/knife-index.mkd
+++ b/distro/common/markdown/man1/knife-index.mkd
@@ -26,5 +26,5 @@ time for all objects to be indexed and available for search.
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-node.mkd b/distro/common/markdown/man1/knife-node.mkd
index 72b7d0072f..0262d64702 100644
--- a/distro/common/markdown/man1/knife-node.mkd
+++ b/distro/common/markdown/man1/knife-node.mkd
@@ -126,5 +126,5 @@ When adding a recipe to a run list, there are several valid formats:
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-role.mkd b/distro/common/markdown/man1/knife-role.mkd
index 7e0dac9bc5..e202c52d81 100644
--- a/distro/common/markdown/man1/knife-role.mkd
+++ b/distro/common/markdown/man1/knife-role.mkd
@@ -70,8 +70,8 @@ run\_list.
## SEE ALSO
__knife-node(1)__ __knife-environment(1)__
- <http://wiki.opscode.com/display/chef/Roles>
- <http://wiki.opscode.com/display/chef/Attributes>
+ <http://docs.chef.io/roles.html>
+ <http://docs.chef.io/attributes.html>
## AUTHOR
Chef was written by Adam Jacob <adam@opscode.com> with many contributions from the community.
@@ -81,5 +81,5 @@ run\_list.
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-search.mkd b/distro/common/markdown/man1/knife-search.mkd
index d6729be322..b289b2c83b 100644
--- a/distro/common/markdown/man1/knife-search.mkd
+++ b/distro/common/markdown/man1/knife-search.mkd
@@ -164,7 +164,7 @@ Find all nodes running CentOS in the production environment:
## SEE ALSO
__knife-ssh__(1)
- <http://wiki.opscode.com/display/chef/Attributes>
+ <http://docs.chef.io/attributes.html>
[Lucene Query Parser Syntax](http://lucene.apache.org/java/2_3_2/queryparsersyntax.html)
## AUTHOR
@@ -175,6 +175,6 @@ Find all nodes running CentOS in the production environment:
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-ssh.mkd b/distro/common/markdown/man1/knife-ssh.mkd
index 07fc5940ce..7d37075470 100644
--- a/distro/common/markdown/man1/knife-ssh.mkd
+++ b/distro/common/markdown/man1/knife-ssh.mkd
@@ -64,6 +64,6 @@ The available multiplexers are:
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-status.mkd b/distro/common/markdown/man1/knife-status.mkd
index 07f0ff305a..0a969e40dd 100644
--- a/distro/common/markdown/man1/knife-status.mkd
+++ b/distro/common/markdown/man1/knife-status.mkd
@@ -31,6 +31,6 @@ may not be publicly reachable.
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife-tag.mkd b/distro/common/markdown/man1/knife-tag.mkd
index 6a1a2c4b56..b5bbb8236f 100644
--- a/distro/common/markdown/man1/knife-tag.mkd
+++ b/distro/common/markdown/man1/knife-tag.mkd
@@ -35,5 +35,5 @@ Lists the tags applied to _node_
Permission is granted to copy, distribute and / or modify this document under the terms of the Apache 2.0 License.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io>
diff --git a/distro/common/markdown/man1/knife.mkd b/distro/common/markdown/man1/knife.mkd
index c3add163f9..3d7c095c10 100644
--- a/distro/common/markdown/man1/knife.mkd
+++ b/distro/common/markdown/man1/knife.mkd
@@ -186,7 +186,7 @@ recommended though, and git fits with a lot of the workflow paradigms.
__knife-node(1)__ __knife-recipe(1)__ __knife-role(1)__
__knife-search(1)__ __knife-ssh(1)__ __knife-tag(1)__
- Complete Chef documentation is available online: <http://wiki.opscode.com/display/chef/Home/>
+ Complete Chef documentation is available online: <http://docs.chef.io/>
JSON is JavaScript Object Notation <http://json.org/>
@@ -209,5 +209,5 @@ recommended though, and git fits with a lot of the workflow paradigms.
On some systems, the complete text of the Apache 2.0 License may be found in `/usr/share/common-licenses/Apache-2.0`.
## CHEF
- Knife is distributed with Chef. <http://wiki.opscode.com/display/chef/Home>
+ Knife is distributed with Chef. <http://docs.chef.io/>
diff --git a/distro/common/markdown/man8/chef-client.mkd b/distro/common/markdown/man8/chef-client.mkd
index e37d283133..ffe444ecf2 100644
--- a/distro/common/markdown/man8/chef-client.mkd
+++ b/distro/common/markdown/man8/chef-client.mkd
@@ -59,8 +59,7 @@ are largely services that exist only to provide the Client with information.
## SEE ALSO
-Full documentation for Chef and chef-client is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-client is located on docs site, http://docs.chef.io/.
## AUTHOR
diff --git a/distro/common/markdown/man8/chef-expander.mkd b/distro/common/markdown/man8/chef-expander.mkd
index 9190a9aebb..a2bb7d72b0 100644
--- a/distro/common/markdown/man8/chef-expander.mkd
+++ b/distro/common/markdown/man8/chef-expander.mkd
@@ -67,8 +67,7 @@ See __chef-expanderctl__(8) for details.
__chef-expanderctl__(8)
__chef-solr__(8)
-Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/.
## AUTHOR
diff --git a/distro/common/markdown/man8/chef-expanderctl.mkd b/distro/common/markdown/man8/chef-expanderctl.mkd
index 03ce6af8ac..db593cb47a 100644
--- a/distro/common/markdown/man8/chef-expanderctl.mkd
+++ b/distro/common/markdown/man8/chef-expanderctl.mkd
@@ -43,8 +43,7 @@ be restarted by the master process.
__chef-expander-cluster__(8)
__chef-solr__(8)
-Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/.
## AUTHOR
diff --git a/distro/common/markdown/man8/chef-server-webui.mkd b/distro/common/markdown/man8/chef-server-webui.mkd
index 977e1495e2..b176d12690 100644
--- a/distro/common/markdown/man8/chef-server-webui.mkd
+++ b/distro/common/markdown/man8/chef-server-webui.mkd
@@ -106,7 +106,7 @@ The default credentials are:
## SEE ALSO
Full documentation for Chef and chef-server-webui (Management Console)
-is located on the Chef wiki, http://wiki.opscode.com/display/chef/Home.
+is located on the Chef docs site, http://docs.chef.io/.
## AUTHOR
diff --git a/distro/common/markdown/man8/chef-server.mkd b/distro/common/markdown/man8/chef-server.mkd
index 1b0f35eb77..46a5ea4346 100644
--- a/distro/common/markdown/man8/chef-server.mkd
+++ b/distro/common/markdown/man8/chef-server.mkd
@@ -106,8 +106,7 @@ __chef-client__(8)
__chef-server-webui__(8)
__knife__(1)
-Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-server is located on docs site, http://docs.chef.io/.
## AUTHOR
diff --git a/distro/common/markdown/man8/chef-solo.mkd b/distro/common/markdown/man8/chef-solo.mkd
index 861a0faa2d..9d5d9a43b7 100644
--- a/distro/common/markdown/man8/chef-solo.mkd
+++ b/distro/common/markdown/man8/chef-solo.mkd
@@ -92,8 +92,8 @@ and use the run_list from ~/node.json.
## SEE ALSO
-Full documentation for Chef and chef-solo is located on the Chef wiki,
-http://wiki.opscode.com/display/chef/Home.
+Full documentation for Chef and chef-solo is located on the Chef docs site,
+http://docs.chef.io/.
## AUTHOR
diff --git a/distro/common/markdown/man8/chef-solr.mkd b/distro/common/markdown/man8/chef-solr.mkd
index 02e7d6285e..a210a90a07 100644
--- a/distro/common/markdown/man8/chef-solr.mkd
+++ b/distro/common/markdown/man8/chef-solr.mkd
@@ -75,7 +75,7 @@ when prompted for confirmation. The process should look like this:
__chef-expander-cluster__(8)
Full documentation for Chef and chef-server is located on the Chef
-wiki, http://wiki.opscode.com/display/chef/Home.
+Docs site, http://docs.chef.io/.
## AUTHOR
diff --git a/distro/powershell/chef/chef.psm1 b/distro/powershell/chef/chef.psm1
new file mode 100644
index 0000000000..6646226795
--- /dev/null
+++ b/distro/powershell/chef/chef.psm1
@@ -0,0 +1,327 @@
+
+function Load-Win32Bindings {
+ Add-Type -TypeDefinition @"
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace Chef
+{
+
+[StructLayout(LayoutKind.Sequential)]
+public struct PROCESS_INFORMATION
+{
+ public IntPtr hProcess;
+ public IntPtr hThread;
+ public uint dwProcessId;
+ public uint dwThreadId;
+}
+
+[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+public struct STARTUPINFO
+{
+ public uint cb;
+ public string lpReserved;
+ public string lpDesktop;
+ public string lpTitle;
+ public uint dwX;
+ public uint dwY;
+ public uint dwXSize;
+ public uint dwYSize;
+ public uint dwXCountChars;
+ public uint dwYCountChars;
+ public uint dwFillAttribute;
+ public STARTF dwFlags;
+ public ShowWindow wShowWindow;
+ public short cbReserved2;
+ public IntPtr lpReserved2;
+ public IntPtr hStdInput;
+ public IntPtr hStdOutput;
+ public IntPtr hStdError;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct SECURITY_ATTRIBUTES
+{
+ public int length;
+ public IntPtr lpSecurityDescriptor;
+ public bool bInheritHandle;
+}
+
+[Flags]
+public enum CreationFlags : int
+{
+ NONE = 0,
+ DEBUG_PROCESS = 0x00000001,
+ DEBUG_ONLY_THIS_PROCESS = 0x00000002,
+ CREATE_SUSPENDED = 0x00000004,
+ DETACHED_PROCESS = 0x00000008,
+ CREATE_NEW_CONSOLE = 0x00000010,
+ CREATE_NEW_PROCESS_GROUP = 0x00000200,
+ CREATE_UNICODE_ENVIRONMENT = 0x00000400,
+ CREATE_SEPARATE_WOW_VDM = 0x00000800,
+ CREATE_SHARED_WOW_VDM = 0x00001000,
+ CREATE_PROTECTED_PROCESS = 0x00040000,
+ EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
+ CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
+ CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
+ CREATE_DEFAULT_ERROR_MODE = 0x04000000,
+ CREATE_NO_WINDOW = 0x08000000,
+}
+
+[Flags]
+public enum STARTF : uint
+{
+ STARTF_USESHOWWINDOW = 0x00000001,
+ STARTF_USESIZE = 0x00000002,
+ STARTF_USEPOSITION = 0x00000004,
+ STARTF_USECOUNTCHARS = 0x00000008,
+ STARTF_USEFILLATTRIBUTE = 0x00000010,
+ STARTF_RUNFULLSCREEN = 0x00000020, // ignored for non-x86 platforms
+ STARTF_FORCEONFEEDBACK = 0x00000040,
+ STARTF_FORCEOFFFEEDBACK = 0x00000080,
+ STARTF_USESTDHANDLES = 0x00000100,
+}
+
+public enum ShowWindow : short
+{
+ SW_HIDE = 0,
+ SW_SHOWNORMAL = 1,
+ SW_NORMAL = 1,
+ SW_SHOWMINIMIZED = 2,
+ SW_SHOWMAXIMIZED = 3,
+ SW_MAXIMIZE = 3,
+ SW_SHOWNOACTIVATE = 4,
+ SW_SHOW = 5,
+ SW_MINIMIZE = 6,
+ SW_SHOWMINNOACTIVE = 7,
+ SW_SHOWNA = 8,
+ SW_RESTORE = 9,
+ SW_SHOWDEFAULT = 10,
+ SW_FORCEMINIMIZE = 11,
+ SW_MAX = 11
+}
+
+public enum StandardHandle : int
+{
+ Input = -10,
+ Output = -11,
+ Error = -12
+}
+
+public static class Kernel32
+{
+ [DllImport("kernel32.dll", SetLastError=true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool CreateProcess(
+ string lpApplicationName,
+ string lpCommandLine,
+ ref SECURITY_ATTRIBUTES lpProcessAttributes,
+ ref SECURITY_ATTRIBUTES lpThreadAttributes,
+ [MarshalAs(UnmanagedType.Bool)] bool bInheritHandles,
+ CreationFlags dwCreationFlags,
+ IntPtr lpEnvironment,
+ string lpCurrentDirectory,
+ ref STARTUPINFO lpStartupInfo,
+ out PROCESS_INFORMATION lpProcessInformation);
+
+ [DllImport("kernel32.dll", SetLastError=true)]
+ public static extern IntPtr GetStdHandle(
+ StandardHandle nStdHandle);
+
+ [DllImport("kernel32", SetLastError=true)]
+ public static extern int WaitForSingleObject(
+ IntPtr hHandle,
+ int dwMilliseconds);
+
+ [DllImport("kernel32", SetLastError=true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool CloseHandle(
+ IntPtr hObject);
+
+ [DllImport("kernel32", SetLastError=true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool GetExitCodeProcess(
+ IntPtr hProcess,
+ out int lpExitCode);
+}
+}
+"@
+}
+
+function Run-ExecutableAndWait($AppPath, $ArgumentString) {
+ # Use the Win32 API to create a new process and wait for it to terminate.
+ $null = Load-Win32Bindings
+
+ $si = New-Object Chef.STARTUPINFO
+ $pi = New-Object Chef.PROCESS_INFORMATION
+
+ $si.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($si)
+ $si.wShowWindow = [Chef.ShowWindow]::SW_SHOW
+ $si.dwFlags = [Chef.STARTF]::STARTF_USESTDHANDLES
+ $si.hStdError = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Error)
+ $si.hStdOutput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Output)
+ $si.hStdInput = [Chef.Kernel32]::GetStdHandle([Chef.StandardHandle]::Input)
+
+ $pSec = New-Object Chef.SECURITY_ATTRIBUTES
+ $pSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($pSec)
+ $pSec.bInheritHandle = $true
+ $tSec = New-Object Chef.SECURITY_ATTRIBUTES
+ $tSec.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($tSec)
+ $tSec.bInheritHandle = $true
+
+ $success = [Chef.Kernel32]::CreateProcess($AppPath, $ArgumentString, [ref] $pSec, [ref] $tSec, $true, [Chef.CreationFlags]::NONE, [IntPtr]::Zero, $pwd, [ref] $si, [ref] $pi)
+ if (-Not $success) {
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ throw "Unable to create process [$ArgumentString]. Error code $reason."
+ }
+ $waitReason = [Chef.Kernel32]::WaitForSingleObject($pi.hProcess, -1)
+ if ($waitReason -ne 0) {
+ if ($waitReason -eq -1) {
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ throw "Could not wait for process to terminate. Error code $reason."
+ } else {
+ throw "WaitForSingleObject failed with return code $waitReason - it's impossible!"
+ }
+ }
+ $success = [Chef.Kernel32]::GetExitCodeProcess($pi.hProcess, [ref] $global:LASTEXITCODE)
+ if (-Not $success) {
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ throw "Process exit code unavailable. Error code $reason."
+ }
+ $success = [Chef.Kernel32]::CloseHandle($pi.hProcess)
+ if (-Not $success) {
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ throw "Unable to release process handle. Error code $reason."
+ }
+ $success = [Chef.Kernel32]::CloseHandle($pi.hThread)
+ if (-Not $success) {
+ $reason = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
+ throw "Unable to release thread handle. Error code $reason."
+ }
+}
+
+function Get-ScriptDirectory {
+ if (!$PSScriptRoot) {
+ $Invocation = (Get-Variable MyInvocation -Scope 1).Value
+ $PSScriptRoot = Split-Path $Invocation.MyCommand.Path
+ }
+ $PSScriptRoot
+}
+
+function Run-RubyCommand($command, $argList) {
+ # This method exists to take the given list of arguments and get it past ruby's command-line
+ # interpreter unscathed and untampered. See https://github.com/ruby/ruby/blob/trunk/win32/win32.c#L1582
+ # for a list of transformations that ruby attempts to perform with your command-line arguments
+ # before passing it onto a script. The most important task is to defeat the globbing
+ # and wild-card expansion that ruby performs. Note that ruby does not use MSVCRT's argc/argv
+ # and deliberately reparses the raw command-line instead.
+ #
+ # To stop ruby from interpreting command-line arguments as globs, they need to be enclosed in '
+ # Ruby doesn't allow any escape characters inside '. This unfortunately prevents us from sending
+ # any strings which themselves contain '. Ruby does allow multi-fragment arguments though.
+ # "foo bar"'baz qux'123"foo" is interpreted as 1 argument because there are no un-escaped
+ # whitespace there. The argument would be interpreted as the string "foo barbaz qux123foo".
+ # This lets us escape ' characters by exiting the ' quoted string, injecting a "'" fragment and
+ # then resuming the ' quoted string again.
+ #
+ # In the process of defeating ruby, one must also defeat the helpfulness of powershell.
+ # When arguments come into this method, the standard PS rules for interpreting cmdlet arguments
+ # apply. When using & (call operator) and providing an array of arguments, powershell (verified
+ # on PS 4.0 on Windows Server 2012R2) will not evaluate them but (contrary to documentation),
+ # it will still marginally interpret them. The behaviour of PS 5.0 seems to be different but
+ # ignore that for now. If any of the provided arguments has a space in it, powershell checks
+ # the first and last character to ensure that they are " characters (and that's all it checks).
+ # If they are not, it will blindly surround that argument with " characters. It won't do this
+ # operation if no space is present, even if other special characters are present. If it notices
+ # leading and trailing " characters, it won't actually check to see if there are other "
+ # characters in the string. Since PS 5.0 changes this behavior, we could consider using the --%
+ # "stop screwing up my arguments" operator, which is available since PS 3.0. When encountered
+ # --% indicates that the rest of line is to be sent literally... except if the parser encounters
+ # %FOO% cmd style environment variables. Because reasons. And there is no way to escape the
+ # % character in *any* waym shape or form.
+ # https://connect.microsoft.com/PowerShell/feedback/details/376207/executing-commands-which-require-quotes-and-variables-is-practically-impossible
+ #
+ # In case you think that you're either reading this incorrectly or that I'm full of shit, here
+ # are some examples. These use EchoArgs.exe from the PowerShell Community Extensions package.
+ # I have not included the argument parsing output from EchoArgs.exe to prevent confusing you with
+ # more details about MSVCRT's parsing algorithm.
+ #
+ # $x = "foo '' bar `"baz`""
+ # & EchoArgs @($x, $x)
+ # Command line:
+ # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" "foo '' bar "baz"" "foo '' bar "baz""
+ #
+ # $x = "abc'123'nospace`"lulz`"!!!"
+ # & EchoArgs @($x, $x)
+ # Command line:
+ # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" abc'123'nospace"lulz"!!! abc'123'nospace"lulz"!!!
+ #
+ # $x = "`"`"Look ma! Tonnes of spaces! 'foo' 'bar'`"`""
+ # & EchoArgs @($x, $x)
+ # Command line:
+ # "C:\Program Files (x86)\PowerShell Community Extensions\Pscx3\Pscx\Apps\EchoArgs.exe" ""Look ma! Tonnes of spaces! 'foo' 'bar'"" ""Look ma! Tonnes of spaces! 'foo' 'bar'""
+ #
+ # Given all this, we can now device a strategy to work around all these immensely helpful, well
+ # documented and useful tools by looking at each incoming argument, escaping any ' characters
+ # with a '"'"' sequence, surrounding each argument with ' & joining them with a space separating
+ # them.
+ # There is another bug (https://bugs.ruby-lang.org/issues/11142) that causes ruby to mangle any
+ # "" two-character double quote sequence but since we always emit our strings inside ' except for
+ # ' characters, this should be ok. Just remember that an argument '' should get translated to
+ # ''"'"''"'"'' on the command line. If those intervening empty ''s are not present, the presence
+ # of "" will cause ruby to mangle that argument.
+ $transformedList = $argList | foreach { "'" + ( $_ -replace "'","'`"'`"'" ) + "'" }
+ $fortifiedArgString = $transformedList -join ' '
+
+ # Use the correct embedded ruby path. We'll be deployed at a path that looks like
+ # [C:\opscode or some other prefix]\chef\modules\chef
+ $ruby = Join-Path (Get-ScriptDirectory) "..\..\embedded\bin\ruby.exe"
+ $commandPath = Join-Path (Get-ScriptDirectory) "..\..\bin\$command"
+
+ Run-ExecutableAndWait $ruby """$ruby"" '$commandPath' $fortifiedArgString"
+}
+
+
+function chef-apply {
+ Run-RubyCommand 'chef-apply' $args
+}
+
+function chef-client {
+ Run-RubyCommand 'chef-client' $args
+}
+
+function chef-service-manager {
+ Run-RubyCommand 'chef-service-manager' $args
+}
+
+function chef-shell {
+ Run-RubyCommand 'chef-shell' $args
+}
+
+function chef-solo {
+ Run-RubyCommand 'chef-solo' $args
+}
+
+function chef-windows-service {
+ Run-RubyCommand 'chef-windows-service' $args
+}
+
+function knife {
+ Run-RubyCommand 'knife' $args
+}
+
+Export-ModuleMember -function chef-apply
+Export-ModuleMember -function chef-client
+Export-ModuleMember -function chef-service-manager
+Export-ModuleMember -function chef-shell
+Export-ModuleMember -function chef-solo
+Export-ModuleMember -function chef-windows-service
+Export-ModuleMember -function knife
+
+# To debug this module, uncomment the line below and then run the following.
+# Export-ModuleMember -function Run-RubyCommand
+# Remove-Module chef
+# Import-Module chef
+# "puts ARGV" | Out-File C:\opscode\chef\bin\puts_args
+# Run-RubyCommand puts_args 'Here' "are" some '"very interesting"' 'arguments[to]' "`"try out`""
diff --git a/ext/win32-eventlog/Rakefile b/ext/win32-eventlog/Rakefile
index 3112c27e8e..63fae1e73b 100644
--- a/ext/win32-eventlog/Rakefile
+++ b/ext/win32-eventlog/Rakefile
@@ -41,10 +41,14 @@ end
task :register => EVT_SHARED_OBJECT do
require 'win32/eventlog'
dll_file = File.expand_path(EVT_SHARED_OBJECT)
- Win32::EventLog.add_event_source(
- :source => "Application",
- :key_name => "Chef",
- :event_message_file => dll_file,
- :category_message_file => dll_file
- )
+ begin
+ Win32::EventLog.add_event_source(
+ :source => "Application",
+ :key_name => "Chef",
+ :event_message_file => dll_file,
+ :category_message_file => dll_file
+ )
+ rescue Errno::EIO => e
+ puts "Skipping event log registration due to missing privileges: #{e}"
+ end
end
diff --git a/ext/win32-eventlog/chef-log.man b/ext/win32-eventlog/chef-log.man
index 4b4a022d7f..10c28e739f 100644
--- a/ext/win32-eventlog/chef-log.man
+++ b/ext/win32-eventlog/chef-log.man
@@ -24,3 +24,33 @@ Exception type: %3%n
Exception message: %4%n
Exception backtrace: %5%n
.
+
+MessageId=10100
+SymbolicName=INFO
+Language=English
+[INFO] %1
+.
+
+MessageId=10101
+SymbolicName=WARN
+Language=English
+[WARN] %1
+.
+
+MessageId=10102
+SymbolicName=DEBUG
+Language=English
+[DEBUG] %1
+.
+
+MessageId=10103
+SymbolicName=ERROR
+Language=English
+[ERROR] %1
+.
+
+MessageId=10104
+SymbolicName=FATAL
+Language=English
+[FATAL] %1
+.
diff --git a/external_tests/chef-rewind.gemfile b/external_tests/chef-rewind.gemfile
new file mode 100644
index 0000000000..39f7d6e0e8
--- /dev/null
+++ b/external_tests/chef-rewind.gemfile
@@ -0,0 +1,5 @@
+source 'https://rubygems.org'
+
+gemspec(name: 'chef', path: "../")
+
+gem 'chef-rewind', github: 'thommay/chef-rewind'
diff --git a/external_tests/chef-sugar.gemfile b/external_tests/chef-sugar.gemfile
new file mode 100644
index 0000000000..31ef3bb5b8
--- /dev/null
+++ b/external_tests/chef-sugar.gemfile
@@ -0,0 +1,6 @@
+source 'https://rubygems.org'
+
+gemspec(name: 'chef', path: "../")
+
+gem 'chef-sugar', github: 'sethvargo/chef-sugar'
+gem 'chefspec'
diff --git a/external_tests/chefspec.gemfile b/external_tests/chefspec.gemfile
new file mode 100644
index 0000000000..fb7878afbd
--- /dev/null
+++ b/external_tests/chefspec.gemfile
@@ -0,0 +1,7 @@
+source 'https://rubygems.org'
+
+gemspec(name: 'chef', path: "../")
+
+gem 'chefspec', github: 'sethvargo/chefspec', group: :development
+gem 'aruba'
+gem 'yard'
diff --git a/external_tests/foodcritic.gemfile b/external_tests/foodcritic.gemfile
new file mode 100644
index 0000000000..a2b71a0d8c
--- /dev/null
+++ b/external_tests/foodcritic.gemfile
@@ -0,0 +1,9 @@
+source 'https://rubygems.org'
+
+gemspec(name: 'chef', path: "../")
+
+gem 'foodcritic', github: 'acrmp/foodcritic'
+gem 'cucumber'
+gem 'rubocop'
+gem 'simplecov'
+gem 'minitest'
diff --git a/external_tests/halite.gemfile b/external_tests/halite.gemfile
new file mode 100644
index 0000000000..cd8cd05668
--- /dev/null
+++ b/external_tests/halite.gemfile
@@ -0,0 +1,8 @@
+source 'https://rubygems.org'
+
+gemspec(name: 'chef', path: "../")
+
+gem 'poise', github: 'poise/poise'
+gem 'halite', github: 'poise/halite'
+gem 'poise-boiler', github: 'poise/poise-boiler'
+gem 'rspec-command', github: 'coderanger/rspec-command'
diff --git a/external_tests/poise.gemfile b/external_tests/poise.gemfile
new file mode 100644
index 0000000000..7d274b7a29
--- /dev/null
+++ b/external_tests/poise.gemfile
@@ -0,0 +1,7 @@
+source 'https://rubygems.org'
+
+gemspec(name: 'chef', path: "../")
+
+gem 'poise', github: 'poise/poise'
+gem 'halite', github: 'poise/halite'
+gem 'poise-boiler', github: 'poise/poise-boiler'
diff --git a/kitchen-tests/.kitchen.travis.yml b/kitchen-tests/.kitchen.travis.yml
index 15795e033a..2c3de60108 100644
--- a/kitchen-tests/.kitchen.travis.yml
+++ b/kitchen-tests/.kitchen.travis.yml
@@ -1,35 +1,38 @@
---
-driver_config:
+driver:
+ name: ec2
aws_access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
aws_secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
aws_ssh_key_id: <%= ENV['AWS_KEYPAIR_NAME'] %>
- iam_profile_name: <%= ENV['IAM_PROFILE_NAME'] %>
+ region: "us-west-2"
+ availability_zone: "us-west-2a"
+ security_group_ids: ["travis-ci"]
+ instance_type: "m3.medium"
provisioner:
- name: chef_zero
- github: <%= ENV['TRAVIS_REPO_SLUG'] %>
- branch: <%= ENV['TRAVIS_COMMIT'] %>
- require_chef_omnibus: 12.0.0-rc.2
+ name: chef_github
+ github_owner: "chef"
+ github_repo: "chef"
+ refname: <%= ENV['TRAVIS_COMMIT'] %>
+ github_access_token: <%= ENV['KITCHEN_GITHUB_TOKEN'] %>
data_path: test/fixtures
# disable file provider diffs so we don't overflow travis' line limit
client_rb:
diff_disabled: true
+transport:
+ ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %>
+
platforms:
- name: ubuntu-12.04
- driver_plugin: ec2
- driver_config:
- region: "us-west-2"
- availability_zone: "us-west-2a"
- ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %>
- security_group_ids: ["travis-ci"]
- - name: centos-6.4
- driver_plugin: ec2
- driver_config:
- region: "us-west-2"
- availability_zone: "us-west-2a"
- ssh_key: <%= ENV['EC2_SSH_KEY_PATH'] %>
- security_group_ids: ["travis-ci"]
+ driver:
+ # http://cloud-images.ubuntu.com/locator/ec2/
+ # 12.04 amd64 us-west-2 hvm:ssd
+ image_id: ami-f3635fc3
+ - name: rhel-6
+ driver:
+ # https://github.com/chef/releng-chef-repo/blob/master/script/ci#L93-L96
+ image_id: ami-7df0bd4d
suites:
- name: webapp
diff --git a/kitchen-tests/.kitchen.yml b/kitchen-tests/.kitchen.yml
index 775bb59378..c853f51b8d 100644
--- a/kitchen-tests/.kitchen.yml
+++ b/kitchen-tests/.kitchen.yml
@@ -6,10 +6,10 @@ driver:
memory: 2048
provisioner:
- name: chef_zero
- github: "opscode/chef"
- branch: <%= %x(git rev-parse HEAD) %>
- require_chef_omnibus: true
+ name: chef_github
+ github_owner: "chef"
+ github_repo: "chef"
+ refname: <%= %x(git rev-parse HEAD) %>
data_path: test/fixtures
client_rb:
diff_disabled: true
diff --git a/kitchen-tests/Gemfile b/kitchen-tests/Gemfile
index 60df7ef388..5e1907ba1f 100644
--- a/kitchen-tests/Gemfile
+++ b/kitchen-tests/Gemfile
@@ -2,13 +2,9 @@ source "https://rubygems.org"
group :end_to_end do
gem 'berkshelf'
- # Once merged into test-kitchen/test-kitchen:master we can remove
- # this and replace it with
- # gem 'test-kitchen', :github => 'test-kitchen/test-kitchen'
- # until the next test-kitchen gem release.
- gem 'test-kitchen', :github => 'mcquin/test-kitchen',
- :branch => 'mcquin/install_chef_from_github'
-
- gem 'kitchen-vagrant' # Used for local testing
- gem 'kitchen-ec2' # Used for remote (Travis) testing
+ gem 'test-kitchen', '~> 1.4.0'
+ 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 7f54b91f14..1a0b802adb 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -31,4 +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 ce9ceb312c..b7b9f7dc43 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -23,7 +23,13 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
+require 'chef/server_api'
+# DEPRECATION NOTE
+#
+# This code will be removed in Chef 13 in favor of the code in Chef::ApiClientV1,
+# which will be moved to this namespace. New development should occur in
+# Chef::ApiClientV1 until the time before Chef 13.
class Chef
class ApiClient
@@ -143,7 +149,7 @@ class Chef
end
def self.http_api
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
end
def self.reregister(name)
@@ -219,7 +225,7 @@ class Chef
end
def http_api
- @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
end
end
diff --git a/lib/chef/api_client_v1.rb b/lib/chef/api_client_v1.rb
new file mode 100644
index 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 297e46ef3c..970544c068 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -93,7 +93,6 @@ class Chef
if config[:config_file].nil?
Chef::Log.warn("No config file found or specified on command line, using command line options.")
elsif config_fetcher.config_missing?
- pp config_missing: true
Chef::Log.warn("*****************************************")
Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
Chef::Log.warn("*****************************************")
@@ -383,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 42805001d8..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",
@@ -85,6 +103,11 @@ class Chef::Application::Apply < Chef::Application
:default => !Chef::Platform.windows?,
:description => "Use colored output, defaults to enabled"
+ option :minimal_ohai,
+ :long => "--minimal-ohai",
+ :description => "Only run the bare minimum ohai plugins chef needs to function",
+ :boolean => true
+
attr_reader :json_attribs
def initialize
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index d5dc936f83..73eda81343 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -253,6 +253,16 @@ class Chef::Application::Client < Chef::Application
:description => "Enable audit-mode with `enabled`. Disable audit-mode with `disabled`. Skip converge and only perform audits with `audit-only`",
:proc => lambda { |mo| mo.gsub("-", "_").to_sym }
+ option :minimal_ohai,
+ :long => "--minimal-ohai",
+ :description => "Only run the bare minimum ohai plugins chef needs to function",
+ :boolean => true
+
+ option :listen,
+ :long => "--[no-]listen",
+ :description => "Whether a local mode (-z) server binds to a port",
+ :boolean => true
+
IMMEDIATE_RUN_SIGNAL = "1".freeze
attr_reader :chef_client_json
@@ -269,6 +279,12 @@ class Chef::Application::Client < Chef::Application
Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+
+ if Chef::Config.has_key?(:chef_repo_path) && Chef::Config.chef_repo_path.nil?
+ Chef::Config.delete(:chef_repo_path)
+ Chef::Log.warn "chef_repo_path was set in a config file but was empty. Assuming #{Chef::Config.chef_repo_path}"
+ end
+
if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
end
@@ -310,12 +326,6 @@ class Chef::Application::Client < Chef::Application
unless expected_modes.include?(mode)
Chef::Application.fatal!(unrecognized_audit_mode(mode))
end
-
- unless mode == :disabled
- # This should be removed when audit-mode is enabled by default/no longer
- # an experimental feature.
- Chef::Log.warn(audit_mode_experimental_message)
- end
end
end
@@ -438,26 +448,15 @@ class Chef::Application::Client < Chef::Application
"\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
end
- def audit_mode_settings_explaination
- "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `:audit_mode = :enabled` in your config file." +
- "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `:audit_mode = :disabled` in your config file." +
- "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `:audit_mode = :audit_only` in your config file." +
+ def audit_mode_settings_explanation
+ "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `audit_mode :enabled` in your config file." +
+ "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `audit_mode :disabled` in your config file." +
+ "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `audit_mode :audit_only` in your config file." +
"\nAudit mode is disabled by default."
end
def unrecognized_audit_mode(mode)
- "Unrecognized setting #{mode} for audit mode." + audit_mode_settings_explaination
- end
-
- def audit_mode_experimental_message
- msg = if Chef::Config[:audit_mode] == :audit_only
- "Chef-client has been configured to skip converge and run only audits."
- else
- "Chef-client has been configured to run audits after it converges."
- end
- msg += " Audit mode is an experimental feature currently under development. API changes may occur. Use at your own risk."
- msg += audit_mode_settings_explaination
- return msg
+ "Unrecognized setting #{mode} for audit mode." + audit_mode_settings_explanation
end
def fetch_recipe_tarball(url, path)
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
index 1a19a45598..af5216ae00 100644
--- a/lib/chef/application/knife.rb
+++ b/lib/chef/application/knife.rb
@@ -121,6 +121,11 @@ class Chef::Application::Knife < Chef::Application
:long => "--chef-zero-port PORT",
:description => "Port (or port range) to start chef-zero on. Port ranges like 1000,1010 or 8889-9999 will try all given ports until one works."
+ option :listen,
+ :long => "--[no-]listen",
+ :description => "Whether a local mode (-z) server binds to a port",
+ :boolean => true
+
option :version,
:short => "-v",
:long => "--version",
@@ -129,7 +134,6 @@ class Chef::Application::Knife < Chef::Application
:proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
:exit => 0
-
# Run knife
def run
Mixlib::Log::Formatter.show_time = false
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index 97a1952d0f..5bb2a1ceb0 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -180,6 +180,11 @@ class Chef::Application::Solo < Chef::Application
:description => "Set maximum duration to wait for another client run to finish, default is indefinitely.",
:proc => lambda { |s| s.to_i }
+ option :minimal_ohai,
+ :long => "--minimal-ohai",
+ :description => "Only run the bare minimum ohai plugins chef needs to function",
+ :boolean => true
+
attr_reader :chef_client_json
def initialize
@@ -209,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/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb
index a5dd9a6c48..d952d8a249 100644
--- a/lib/chef/audit/audit_reporter.rb
+++ b/lib/chef/audit/audit_reporter.rb
@@ -34,6 +34,7 @@ class Chef
@rest_client = rest_client
# Ruby 1.9.3 and above "enumerate their values in the order that the corresponding keys were inserted."
@ordered_control_groups = Hash.new
+ @audit_phase_error = nil
end
def run_context
@@ -46,7 +47,7 @@ class Chef
@run_status = run_status
end
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
Chef::Log.debug("Audit Reporter completed successfully without errors.")
ordered_control_groups.each do |name, control_group|
audit_data.add_control_group(control_group)
@@ -57,8 +58,9 @@ class Chef
# that runs tests - normal errors are interpreted as EXAMPLE failures and captured.
# We still want to send available audit information to the server so we process the
# known control groups.
- def audit_phase_failed(error)
+ def audit_phase_failed(error, audit_output)
# The stacktrace information has already been logged elsewhere
+ @audit_phase_error = error
Chef::Log.debug("Audit Reporter failed.")
ordered_control_groups.each do |name, control_group|
audit_data.add_control_group(control_group)
@@ -70,7 +72,9 @@ class Chef
end
def run_failed(error)
- post_auditing_data(error)
+ # Audit phase errors are captured when audit_phase_failed gets called.
+ # The error passed here isn't relevant to auditing, so we ignore it.
+ post_auditing_data
end
def control_group_started(name)
@@ -98,14 +102,14 @@ class Chef
private
- def post_auditing_data(error = nil)
+ def post_auditing_data
unless auditing_enabled?
Chef::Log.debug("Audit Reports are disabled. Skipping sending reports.")
return
end
unless run_status
- Chef::Log.debug("Run failed before audits were initialized, not sending audit report to server")
+ Chef::Log.debug("Run failed before audit mode was initialized, not sending audit report to server")
return
end
@@ -116,8 +120,10 @@ class Chef
Chef::Log.debug("Sending audit report (run-id: #{audit_data.run_id})")
run_data = audit_data.to_hash
- if error
- run_data[:error] = "#{error.class.to_s}: #{error.message}\n#{error.backtrace.join("\n")}"
+ if @audit_phase_error
+ error_info = "#{@audit_phase_error.class}: #{@audit_phase_error.message}"
+ error_info << "\n#{@audit_phase_error.backtrace.join("\n")}" if @audit_phase_error.backtrace
+ run_data[:error] = error_info
end
Chef::Log.debug "Audit Report:\n#{Chef::JSONCompat.to_json_pretty(run_data)}"
@@ -163,7 +169,6 @@ class Chef
def iso8601ify(time)
time.utc.iso8601.to_s
end
-
end
end
end
diff --git a/lib/chef/audit/logger.rb b/lib/chef/audit/logger.rb
new file mode 100644
index 0000000000..e46f54e582
--- /dev/null
+++ b/lib/chef/audit/logger.rb
@@ -0,0 +1,36 @@
+#
+# 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 'stringio'
+
+class Chef
+ class Audit
+ class Logger
+ def self.puts(message="")
+ @buffer ||= StringIO.new
+ @buffer.puts(message)
+
+ Chef::Log.info(message)
+ end
+
+ def self.read_buffer
+ return "" if @buffer.nil?
+ @buffer.string
+ end
+ end
+ end
+end
diff --git a/lib/chef/audit/runner.rb b/lib/chef/audit/runner.rb
index 13ba95428c..234d83ab8f 100644
--- a/lib/chef/audit/runner.rb
+++ b/lib/chef/audit/runner.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+require 'chef/audit/logger'
+
class Chef
class Audit
class Runner
@@ -79,11 +81,15 @@ class Chef
require 'rspec'
require 'rspec/its'
require 'specinfra'
+ require 'specinfra/helper'
+ require 'specinfra/helper/set'
require 'serverspec/helper'
require 'serverspec/matcher'
require 'serverspec/subject'
require 'chef/audit/audit_event_proxy'
require 'chef/audit/rspec_formatter'
+
+ Specinfra::Backend::Cmd.send(:include, Specinfra::Helper::Set)
end
# Configure RSpec just the way we like it:
@@ -111,8 +117,8 @@ class Chef
# the output stream to be changed for a formatter once the formatter has
# been added.
def set_streams
- RSpec.configuration.output_stream = Chef::Config[:log_location]
- RSpec.configuration.error_stream = Chef::Config[:log_location]
+ RSpec.configuration.output_stream = Chef::Audit::Logger
+ RSpec.configuration.error_stream = Chef::Audit::Logger
end
# Add formatters which we use to
@@ -136,9 +142,14 @@ class Chef
end
end
- # Set up the backend for Specinfra/Serverspec. :exec is the local system.
+ # Set up the backend for Specinfra/Serverspec. :exec is the local system; on Windows, it is :cmd
def configure_specinfra
- Specinfra.configuration.backend = :exec
+ if Chef::Platform.windows?
+ Specinfra.configuration.backend = :cmd
+ Specinfra.configuration.os = { :family => 'windows' }
+ else
+ Specinfra.configuration.backend = :exec
+ end
end
# Iterates through the control groups registered to this run_context, builds an
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
new file mode 100644
index 0000000000..c2cb9e2b24
--- /dev/null
+++ b/lib/chef/chef_class.rb
@@ -0,0 +1,219 @@
+#
+# Author:: Lamont Granquist (<lamont@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.
+
+# NOTE: This class is not intended for internal use by the chef-client code. Classes in
+# chef-client should still have objects like the node and run_context injected into them
+# via their initializers. This class is still global state and will complicate writing
+# unit tests for internal Chef objects. It is intended to be used only by recipe code.
+
+# NOTE: When adding require lines here you are creating tight coupling on a global that may be
+# included in many different situations and ultimately that ends in tears with circular requires.
+# Note the way that the run_context, provider_priority_map and resource_priority_map are "dependency
+# injected" into this class by other objects and do not reference the class symbols in those files
+# directly and we do not need to require those files here.
+
+require 'chef/platform/provider_priority_map'
+require 'chef/platform/resource_priority_map'
+require 'chef/platform/provider_handler_map'
+require 'chef/platform/resource_handler_map'
+
+class Chef
+ class << self
+
+ #
+ # Public API
+ #
+
+ #
+ # Get the node object
+ #
+ # @return [Chef::Node] node object of the chef-client run
+ #
+ attr_reader :node
+
+ #
+ # Get the run context
+ #
+ # @return [Chef::RunContext] run_context of the chef-client run
+ #
+ attr_reader :run_context
+
+ # Register an event handler with user specified block
+ #
+ # @return[Chef::EventDispatch::Base] handler object
+ def event_handler(&block)
+ dsl = Chef::EventDispatch::DSL.new('Chef client DSL')
+ dsl.instance_eval(&block)
+ end
+
+ # Get the array of providers associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ #
+ # @return [Array<Class>] Priority Array of Provider Classes to use for the resource_name on the node
+ #
+ def get_provider_priority_array(resource_name)
+ result = provider_priority_map.get_priority_array(node, resource_name.to_sym)
+ result = result.dup if result
+ result
+ end
+
+ #
+ # Get the array of resources associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ #
+ # @return [Array<Class>] Priority Array of Resource Classes to use for the resource_name on the node
+ #
+ def get_resource_priority_array(resource_name)
+ result = resource_priority_map.get_priority_array(node, resource_name.to_sym)
+ result = result.dup if result
+ result
+ end
+
+ #
+ # Set the array of providers associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node
+ # @param filter [Hash] Chef::Nodearray-style filter
+ #
+ # @return [Array<Class>] Modified Priority Array of Provider Classes to use for the resource_name on the node
+ #
+ def set_provider_priority_array(resource_name, priority_array, *filter, &block)
+ result = provider_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
+ result = result.dup if result
+ result
+ end
+
+ #
+ # Get the array of resources associated with a resource_name for the current node
+ #
+ # @param resource_name [Symbol] name of the resource as a symbol
+ # @param priority_array [Class, Array<Class>] Class or Array of Classes to set as the priority for resource_name on the node
+ # @param filter [Hash] Chef::Nodearray-style filter
+ #
+ # @return [Array<Class>] Modified Priority Array of Resource Classes to use for the resource_name on the node
+ #
+ def set_resource_priority_array(resource_name, priority_array, *filter, &block)
+ result = resource_priority_map.set_priority_array(resource_name.to_sym, priority_array, *filter, &block)
+ result = result.dup if result
+ result
+ end
+
+ #
+ # Dependency Injection API (Private not Public)
+ # [ in the ruby sense these have to be public methods, but they are
+ # *NOT* for public consumption ]
+ #
+
+ #
+ # Sets the resource_priority_map
+ #
+ # @param resource_priority_map [Chef::Platform::ResourcePriorityMap]
+ #
+ # @api private
+ def set_resource_priority_map(resource_priority_map)
+ @resource_priority_map = resource_priority_map
+ end
+
+ #
+ # Sets the provider_priority_map
+ #
+ # @param provider_priority_map [Chef::Platform::providerPriorityMap]
+ #
+ # @api private
+ def set_provider_priority_map(provider_priority_map)
+ @provider_priority_map = provider_priority_map
+ end
+
+ #
+ # Sets the node object
+ #
+ # @api private
+ # @param node [Chef::Node]
+ def set_node(node)
+ @node = node
+ end
+
+ #
+ # Sets the run_context object
+ #
+ # @param run_context [Chef::RunContext]
+ #
+ # @api private
+ def set_run_context(run_context)
+ @run_context = run_context
+ end
+
+ #
+ # Resets the internal state
+ #
+ # @api private
+ def reset!
+ @run_context = nil
+ @node = nil
+ @provider_priority_map = nil
+ @resource_priority_map = nil
+ @provider_handler_map = nil
+ @resource_handler_map = nil
+ end
+
+ # @api private
+ def provider_priority_map
+ # these slurp in the resource+provider world, so be exceedingly lazy about requiring them
+ @provider_priority_map ||= Chef::Platform::ProviderPriorityMap.instance
+ end
+ # @api private
+ def resource_priority_map
+ @resource_priority_map ||= Chef::Platform::ResourcePriorityMap.instance
+ end
+ # @api private
+ def provider_handler_map
+ @provider_handler_map ||= Chef::Platform::ProviderHandlerMap.instance
+ end
+ # @api private
+ def resource_handler_map
+ @resource_handler_map ||= Chef::Platform::ResourceHandlerMap.instance
+ end
+
+ #
+ # Emit a deprecation message.
+ #
+ # @param message The message to send.
+ # @param location The location. Defaults to the caller who called you (since
+ # generally the person who triggered the check is the one that needs to be
+ # fixed).
+ #
+ # @example
+ # Chef.deprecation("Deprecated!")
+ #
+ # @api private this will likely be removed in favor of an as-yet unwritten
+ # `Chef.log`
+ def log_deprecation(message, location=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
+
+ reset!
+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 3b4f8d4683..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");
@@ -50,6 +50,7 @@ require 'chef/run_lock'
require 'chef/policy_builder'
require 'chef/request_id'
require 'chef/platform/rebooter'
+require 'chef/mixin/deprecation'
require 'ohai'
require 'rbconfig'
@@ -60,114 +61,278 @@ class Chef
class Client
include Chef::Mixin::PathSanity
- # IO stream that will be used as 'STDOUT' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDOUT_FD = STDOUT
-
- # IO stream that will be used as 'STDERR' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDERR_FD = STDERR
+ extend Chef::Mixin::Deprecation
- # Clears all notifications for client run status events.
- # Primarily for testing purposes.
- def self.clear_notifications
- @run_start_notifications = nil
- @run_completed_successfully_notifications = nil
- @run_failed_notifications = nil
- end
-
- # The list of notifications to be run when the client run starts.
- def self.run_start_notifications
- @run_start_notifications ||= []
- end
-
- # The list of notifications to be run when the client run completes
- # successfully.
- def self.run_completed_successfully_notifications
- @run_completed_successfully_notifications ||= []
- end
-
- # The list of notifications to be run when the client run fails.
- def self.run_failed_notifications
- @run_failed_notifications ||= []
- end
-
- # Add a notification for the 'client run started' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_starts(&notification_block)
- run_start_notifications << notification_block
- end
-
- # Add a notification for the 'client run success' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_completes_successfully(&notification_block)
- run_completed_successfully_notifications << notification_block
- end
+ #
+ # The status of the Chef run.
+ #
+ # @return [Chef::RunStatus]
+ #
+ attr_reader :run_status
- # Add a notification for the 'client run failed' event. The notification
- # is provided as a block. The current Chef::RunStatus is passed to the
- # notification_block when the event is triggered.
- def self.when_run_fails(&notification_block)
- run_failed_notifications << notification_block
+ #
+ # The node represented by this client.
+ #
+ # @return [Chef::Node]
+ #
+ def node
+ run_status.node
end
-
- # Callback to fire notifications that the Chef run is starting
- def run_started
- self.class.run_start_notifications.each do |notification|
- notification.call(run_status)
- end
- @events.run_started(run_status)
+ def node=(value)
+ run_status.node = value
end
- # Callback to fire notifications that the run completed successfully
- def run_completed_successfully
- success_handlers = self.class.run_completed_successfully_notifications
- success_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The ohai system used by this client.
+ #
+ # @return [Ohai::System]
+ #
+ attr_reader :ohai
- # Callback to fire notifications that the Chef run failed
- def run_failed
- failure_handlers = self.class.run_failed_notifications
- failure_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The rest object used to communicate with the Chef server.
+ #
+ # @return [Chef::REST]
+ #
+ attr_reader :rest
- attr_accessor :node
- attr_accessor :ohai
- attr_accessor :rest
+ #
+ # The runner used to converge.
+ #
+ # @return [Chef::Runner]
+ #
attr_accessor :runner
+ #
+ # Extra node attributes that were applied to the node.
+ #
+ # @return [Hash]
+ #
attr_reader :json_attribs
- attr_reader :run_status
+
+ #
+ # The event dispatcher for the Chef run, including any configured output
+ # formatters and event loggers.
+ #
+ # @return [EventDispatch::Dispatcher]
+ #
+ # @see Chef::Formatters
+ # @see Chef::Config#formatters
+ # @see Chef::Config#stdout
+ # @see Chef::Config#stderr
+ # @see Chef::Config#force_logger
+ # @see Chef::Config#force_formatter
+ # TODO add stdout, stderr, and default formatters to Chef::Config so the
+ # defaults aren't calculated here. Remove force_logger and force_formatter
+ # from this code.
+ # @see Chef::EventLoggers
+ # @see Chef::Config#disable_event_logger
+ # @see Chef::Config#event_loggers
+ # @see Chef::Config#event_handlers
+ #
attr_reader :events
+ #
# Creates a new Chef::Client.
+ #
+ # @param json_attribs [Hash] Node attributes to layer into the node when it is
+ # fetched.
+ # @param args [Hash] Options:
+ # @option args [Array<RunList::RunListItem>] :override_runlist A runlist to
+ # use instead of the node's embedded run list.
+ # @option args [Array<String>] :specific_recipes A list of recipe file paths
+ # to load after the run list has been loaded.
+ #
def initialize(json_attribs=nil, args={})
@json_attribs = json_attribs || {}
- @node = nil
- @run_status = nil
- @runner = nil
@ohai = Ohai::System.new
event_handlers = configure_formatters + configure_event_loggers
event_handlers += Array(Chef::Config[:event_handlers])
@events = EventDispatch::Dispatcher.new(*event_handlers)
+ # TODO it seems like a bad idea to be deletin' other peoples' hashes.
@override_runlist = args.delete(:override_runlist)
@specific_recipes = args.delete(:specific_recipes)
+ @run_status = Chef::RunStatus.new(nil, events)
if new_runlist = args.delete(:runlist)
@json_attribs["run_list"] = new_runlist
end
end
+ #
+ # Do a full run for this Chef::Client.
+ #
+ # Locks the run while doing its job.
+ #
+ # Fires run_start before doing anything and fires run_completed or
+ # run_failed when finished. Also notifies client listeners of run_started
+ # at the beginning of Compile, and run_completed_successfully or run_failed
+ # when all is complete.
+ #
+ # Phase 1: Setup
+ # --------------
+ # Gets information about the system and the run we are doing.
+ #
+ # 1. Run ohai to collect system information.
+ # 2. Register / connect to the Chef server (unless in solo mode).
+ # 3. Retrieve the node (or create a new one).
+ # 4. Merge in json_attribs, Chef::Config.environment, and override_run_list.
+ #
+ # @see #run_ohai
+ # @see #load_node
+ # @see #build_node
+ # @see Chef::Config#lockfile
+ # @see Chef::RunLock#acquire
+ #
+ # Phase 2: Compile
+ # ----------------
+ # Decides *what* we plan to converge by compiling recipes.
+ #
+ # 1. Sync required cookbooks to the local cache.
+ # 2. Load libraries from all cookbooks.
+ # 3. Load attributes from all cookbooks.
+ # 4. Load LWRPs from all cookbooks.
+ # 5. Load resource definitions from all cookbooks.
+ # 6. Load recipes in the run list.
+ # 7. Load recipes from the command line.
+ #
+ # @see #setup_run_context Syncs and compiles cookbooks.
+ # @see Chef::CookbookCompiler#compile
+ #
+ # Phase 3: Converge
+ # -----------------
+ # Brings the system up to date.
+ #
+ # 1. Converge the resources built from recipes in Phase 2.
+ # 2. Save the node.
+ # 3. Reboot if we were asked to.
+ #
+ # @see #converge_and_save
+ # @see Chef::Runner
+ #
+ # Phase 4: Audit
+ # --------------
+ # Runs 'control_group' audits in recipes. This entire section can be enabled or disabled with config.
+ #
+ # 1. 'control_group' DSL collects audits during Phase 2
+ # 2. Audits are run using RSpec
+ # 3. Errors are collected and reported using the formatters
+ #
+ # @see #run_audits
+ # @see Chef::Audit::Runner#run
+ #
+ # @raise [Chef::Exceptions::RunFailedWrappingError] If converge or audit failed.
+ #
+ # @see Chef::Config#enforce_path_sanity
+ # @see Chef::Config#solo
+ # @see Chef::Config#audit_mode
+ #
+ # @return Always returns true.
+ #
+ def run
+ run_error = nil
+
+ runlock = RunLock.new(Chef::Config.lockfile)
+ # TODO feels like acquire should have its own block arg for this
+ runlock.acquire
+ # don't add code that may fail before entering this section to be sure to release lock
+ begin
+ runlock.save_pid
+
+ request_id = Chef::RequestID.instance.request_id
+ run_context = nil
+ events.run_start(Chef::VERSION)
+ Chef::Log.info("*** Chef #{Chef::VERSION} ***")
+ Chef::Log.info "Chef-client pid: #{Process.pid}"
+ Chef::Log.debug("Chef-client request_id: #{request_id}")
+ enforce_path_sanity
+ run_ohai
+
+ register unless Chef::Config[:solo]
+
+ load_node
+
+ build_node
+
+ run_status.run_id = request_id
+ run_status.start_clock
+ Chef::Log.info("Starting Chef Run for #{node.name}")
+ run_started
+
+ do_windows_admin_check
+
+ run_context = setup_run_context
+
+ if Chef::Config[:audit_mode] != :audit_only
+ converge_error = converge_and_save(run_context)
+ end
+
+ if Chef::Config[:why_run] == true
+ # why_run should probably be renamed to why_converge
+ Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes")
+ elsif Chef::Config[:audit_mode] != :disabled
+ audit_error = run_audits(run_context)
+ end
+
+ # Raise converge_error so run_failed reporters/events are processed.
+ raise converge_error if converge_error
+
+ run_status.stop_clock
+ Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
+ run_completed_successfully
+ events.run_completed(node)
+
+ # rebooting has to be the last thing we do, no exceptions.
+ Chef::Platform::Rebooter.reboot_if_needed!(node)
+ rescue Exception => run_error
+ # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
+ Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n ")}")
+ # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
+ if run_status
+ run_status.stop_clock
+ run_status.exception = run_error
+ run_failed
+ end
+ events.run_failed(run_error)
+ ensure
+ Chef::RequestID.instance.reset_request_id
+ request_id = nil
+ @run_status = nil
+ run_context = nil
+ runlock.release
+ 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 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
+
+ true
+ end
+
+ #
+ # Private API
+ # TODO make this stuff protected or private
+ #
+
+ # @api private
def configure_formatters
formatters_for_run.map do |formatter_name, output_path|
if output_path.nil?
@@ -180,6 +345,7 @@ class Chef
end
end
+ # @api private
def formatters_for_run
if Chef::Config.formatters.empty?
[default_formatter]
@@ -188,6 +354,7 @@ class Chef
end
end
+ # @api private
def default_formatter
if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
[:doc]
@@ -196,6 +363,7 @@ class Chef
end
end
+ # @api private
def configure_event_loggers
if Chef::Config.disable_event_logger
[]
@@ -212,8 +380,9 @@ class Chef
end
end
- # Resource repoters send event information back to the chef server for processing.
- # Can only be called after we have a @rest object
+ # Resource reporters send event information back to the chef server for
+ # processing. Can only be called after we have a @rest object
+ # @api private
def register_reporters
[
Chef::ResourceReporter.new(rest),
@@ -223,43 +392,123 @@ class Chef
end
end
+ #
+ # Callback to fire notifications that the Chef run is starting
+ #
+ # @api private
+ #
+ def run_started
+ self.class.run_start_notifications.each do |notification|
+ notification.call(run_status)
+ end
+ events.run_started(run_status)
+ end
+
+ #
+ # Callback to fire notifications that the run completed successfully
+ #
+ # @api private
+ #
+ def run_completed_successfully
+ success_handlers = self.class.run_completed_successfully_notifications
+ success_handlers.each do |notification|
+ notification.call(run_status)
+ end
+ end
+
+ #
+ # Callback to fire notifications that the Chef run failed
+ #
+ # @api private
+ #
+ def run_failed
+ failure_handlers = self.class.run_failed_notifications
+ failure_handlers.each do |notification|
+ notification.call(run_status)
+ end
+ end
+
+ #
# Instantiates a Chef::Node object, possibly loading the node's prior state
- # when using chef-client. Delegates to policy_builder
+ # when using chef-client. Sets Chef.node to the new node.
+ #
+ # @return [Chef::Node] The node object for this Chef run
#
+ # @see Chef::PolicyBuilder#load_node
+ #
+ # @api private
#
- # === Returns
- # Chef::Node:: The node object for this chef run
def load_node
policy_builder.load_node
- @node = policy_builder.node
+ run_status.node = policy_builder.node
+ Chef.set_node(policy_builder.node)
+ node
end
- # Mutates the `node` object to prepare it for the chef run. Delegates to
- # policy_builder
#
- # === Returns
- # Chef::Node:: The updated node object
+ # Mutates the `node` object to prepare it for the chef run.
+ #
+ # @return [Chef::Node] The updated node object
+ #
+ # @see Chef::PolicyBuilder#build_node
+ #
+ # @api private
+ #
def build_node
policy_builder.build_node
- @run_status = Chef::RunStatus.new(node, events)
+ run_status.node = node
node
end
+ #
+ # Sync cookbooks to local cache.
+ #
+ # TODO this appears to be unused.
+ #
+ # @see Chef::PolicyBuilder#sync_cookbooks
+ #
+ # @api private
+ #
+ def sync_cookbooks
+ policy_builder.sync_cookbooks
+ end
+
+ #
+ # Sets up the run context.
+ #
+ # @see Chef::PolicyBuilder#setup_run_context
+ #
+ # @return The newly set up run context
+ #
+ # @api private
def setup_run_context
- run_context = policy_builder.setup_run_context(@specific_recipes)
+ run_context = policy_builder.setup_run_context(specific_recipes)
assert_cookbook_path_not_empty(run_context)
run_status.run_context = run_context
run_context
end
- def sync_cookbooks
- policy_builder.sync_cookbooks
- end
-
+ #
+ # The PolicyBuilder strategy for figuring out run list and cookbooks.
+ #
+ # @return [Chef::PolicyBuilder::Policyfile, Chef::PolicyBuilder::ExpandNodeObject]
+ #
+ # @api private
+ #
def policy_builder
- @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, @override_runlist, events)
+ @policy_builder ||= Chef::PolicyBuilder.strategy.new(node_name, ohai.data, json_attribs, override_runlist, events)
end
+ #
+ # Save the updated node to Chef.
+ #
+ # Does not save if we are in solo mode or using override_runlist.
+ #
+ # @see Chef::Node#save
+ # @see Chef::Config#solo
+ #
+ # @api private
+ #
def save_updated_node
if Chef::Config[:solo]
# nothing to do
@@ -267,15 +516,46 @@ class Chef
Chef::Log.warn("Skipping final node save because override_runlist was given")
else
Chef::Log.debug("Saving the current state of node #{node_name}")
- @node.save
+ node.save
end
end
+ #
+ # Run ohai plugins. Runs all ohai plugins unless minimal_ohai is specified.
+ #
+ # Sends the ohai_completed event when finished.
+ #
+ # @see Chef::EventDispatcher#
+ # @see Chef::Config#minimal_ohai
+ #
+ # @api private
+ #
def run_ohai
- ohai.all_plugins
- @events.ohai_completed(node)
+ filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil
+ ohai.all_plugins(filter)
+ events.ohai_completed(node)
end
+ #
+ # Figure out the node name we are working with.
+ #
+ # It tries these, in order:
+ # - Chef::Config.node_name
+ # - ohai[:fqdn]
+ # - ohai[:machinename]
+ # - ohai[:hostname]
+ #
+ # If we are running against a server with authentication protocol < 1.0, we
+ # *require* authentication protocol version 1.1.
+ #
+ # @raise [Chef::Exceptions::CannotDetermineNodeName] If the node name is not
+ # set and cannot be determined via ohai.
+ #
+ # @see Chef::Config#node_name
+ # @see Chef::Config#authentication_protocol_version
+ #
+ # @api private
+ #
def node_name
name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname]
Chef::Config[:node_name] = name
@@ -284,6 +564,8 @@ class Chef
# node names > 90 bytes only work with authentication protocol >= 1.1
# see discussion in config.rb.
+ # TODO use a computed default in Chef::Config to determine this instead of
+ # setting it.
if name.bytesize > 90
Chef::Config[:authentication_protocol_version] = "1.1"
end
@@ -292,98 +574,189 @@ class Chef
end
#
- # === Returns
- # rest<Chef::REST>:: returns Chef::REST connection object
+ # Determine our private key and set up the connection to the Chef server.
+ #
+ # Skips registration and fires the `skipping_registration` event if
+ # Chef::Config.client_key is unspecified or already exists.
+ #
+ # If Chef::Config.client_key does not exist, we register the client with the
+ # Chef server and fire the registration_start and registration_completed events.
+ #
+ # @return [Chef::REST] The server connection object.
+ #
+ # @see Chef::Config#chef_server_url
+ # @see Chef::Config#client_key
+ # @see Chef::ApiClient::Registration#run
+ # @see Chef::EventDispatcher#skipping_registration
+ # @see Chef::EventDispatcher#registration_start
+ # @see Chef::EventDispatcher#registration_completed
+ # @see Chef::EventDispatcher#registration_failed
+ #
+ # @api private
+ #
def register(client_name=node_name, config=Chef::Config)
if !config[:client_key]
- @events.skipping_registration(client_name, config)
+ events.skipping_registration(client_name, config)
Chef::Log.debug("Client key is unspecified - skipping registration")
elsif File.exists?(config[:client_key])
- @events.skipping_registration(client_name, config)
+ events.skipping_registration(client_name, config)
Chef::Log.debug("Client key #{config[:client_key]} is present - skipping registration")
else
- @events.registration_start(node_name, config)
+ events.registration_start(node_name, config)
Chef::Log.info("Client key #{config[:client_key]} is not present - registering")
Chef::ApiClient::Registration.new(node_name, config[:client_key]).run
- @events.registration_completed
+ events.registration_completed
end
# We now have the client key, and should use it from now on.
@rest = Chef::REST.new(config[:chef_server_url], client_name, config[:client_key])
register_reporters
rescue Exception => e
+ # TODO this should probably only ever fire if we *started* registration.
+ # Move it to the block above.
# TODO: munge exception so a semantic failure message can be given to the
# user
- @events.registration_failed(client_name, e, config)
+ events.registration_failed(client_name, e, config)
raise
end
- # Converges the node.
#
- # === Returns
- # The thrown exception, if there was one. If this returns nil the converge was successful.
+ # Converges all compiled resources.
+ #
+ # Fires the converge_start, converge_complete and converge_failed events.
+ #
+ # If the exception `:end_client_run_early` is thrown during convergence, it
+ # does not mark the run complete *or* failed, and returns `nil`
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge exception, unless we are in audit mode, in which case
+ # we *return* the exception.
+ #
+ # @see Chef::Runner#converge
+ # @see Chef::Config#audit_mode
+ # @see Chef::EventDispatch#converge_start
+ # @see Chef::EventDispatch#converge_complete
+ # @see Chef::EventDispatch#converge_failed
+ #
+ # @api private
+ #
def converge(run_context)
converge_exception = nil
catch(:end_client_run_early) do
begin
- @events.converge_start(run_context)
+ events.converge_start(run_context)
Chef::Log.debug("Converging node #{node_name}")
@runner = Chef::Runner.new(run_context)
- runner.converge
- @events.converge_complete
+ @runner.converge
+ events.converge_complete
rescue Exception => e
- Chef::Log.error("Converge failed with error message #{e.message}")
- @events.converge_failed(e)
+ events.converge_failed(e)
+ raise e if Chef::Config[:audit_mode] == :disabled
converge_exception = e
end
end
converge_exception
end
+ #
+ # Converge the node via and then save it if successful.
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge or node save exception, unless we are in audit mode,
+ # in which case we *return* the exception.
+ #
+ # @see #converge
+ # @see #save_updated_mode
+ # @see Chef::Config#audit_mode
+ #
+ # @api private
+ #
# We don't want to change the old API on the `converge` method to have it perform
# saving. So we wrap it in this method.
+ # TODO given this seems to be pretty internal stuff, how badly do we need to
+ # split this stuff up?
+ #
def converge_and_save(run_context)
converge_exception = converge(run_context)
unless converge_exception
begin
save_updated_node
rescue Exception => e
+ raise e if Chef::Config[:audit_mode] == :disabled
converge_exception = e
end
end
converge_exception
end
+ #
+ # Run the audit phase.
+ #
+ # Triggers the audit_phase_start, audit_phase_complete and
+ # audit_phase_failed events.
+ #
+ # @param run_context The run context.
+ #
+ # @return Any thrown exceptions. `nil` if successful.
+ #
+ # @see Chef::Audit::Runner#run
+ # @see Chef::EventDispatch#audit_phase_start
+ # @see Chef::EventDispatch#audit_phase_complete
+ # @see Chef::EventDispatch#audit_phase_failed
+ #
+ # @api private
+ #
def run_audits(run_context)
- audit_exception = nil
begin
- @events.audit_phase_start(run_status)
+ events.audit_phase_start(run_status)
Chef::Log.info("Starting audit phase")
auditor = Chef::Audit::Runner.new(run_context)
auditor.run
if auditor.failed?
- raise Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ audit_exception = Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ @events.audit_phase_failed(audit_exception, Chef::Audit::Logger.read_buffer)
+ else
+ @events.audit_phase_complete(Chef::Audit::Logger.read_buffer)
end
- @events.audit_phase_complete
rescue Exception => e
Chef::Log.error("Audit phase failed with error message: #{e.message}")
- @events.audit_phase_failed(e)
+ @events.audit_phase_failed(e, Chef::Audit::Logger.read_buffer)
audit_exception = e
end
audit_exception
end
- # Expands the run list. Delegates to the policy_builder.
#
- # Normally this does not need to be called from here, it will be called by
- # build_node. This is provided so external users (like the chefspec
- # project) can inject custom behavior into the run process.
+ # Expands the run list.
+ #
+ # @return [Chef::RunListExpansion] The expanded run list.
+ #
+ # @see Chef::PolicyBuilder#expand_run_list
#
- # === Returns
- # RunListExpansion: A RunListExpansion or API compatible object.
def expanded_run_list
policy_builder.expand_run_list
end
+ #
+ # Check if the user has Administrator privileges on windows.
+ #
+ # Throws an error if the user is not an admin, and
+ # `Chef::Config.fatal_windows_admin_check` is true.
+ #
+ # @raise [Chef::Exceptions::WindowsNotAdmin] If the user is not an admin.
+ #
+ # @see Chef::platform#windows?
+ # @see Chef::Config#fatal_windows_admin_check
+ #
+ # @api private
+ #
def do_windows_admin_check
if Chef::Platform.windows?
Chef::Log.debug("Checking for administrator privileges....")
@@ -403,99 +776,121 @@ class Chef
end
end
- # Do a full run for this Chef::Client. Calls:
- #
- # * run_ohai - Collect information about the system
- # * build_node - Get the last known state, merge with local changes
- # * register - If not in solo mode, make sure the server knows about this client
- # * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks
- # * converge - Bring this system up to date
- #
- # === Returns
- # true:: Always returns true.
- def run
- runlock = RunLock.new(Chef::Config.lockfile)
- runlock.acquire
- # don't add code that may fail before entering this section to be sure to release lock
- begin
- runlock.save_pid
-
- request_id = Chef::RequestID.instance.request_id
- run_context = nil
- @events.run_start(Chef::VERSION)
- Chef::Log.info("*** Chef #{Chef::VERSION} ***")
- Chef::Log.info "Chef-client pid: #{Process.pid}"
- Chef::Log.debug("Chef-client request_id: #{request_id}")
- enforce_path_sanity
- run_ohai
-
- register unless Chef::Config[:solo]
-
- load_node
-
- build_node
+ # Notification registration
+ class<<self
+ #
+ # Add a listener for the 'client run started' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_starts(&notification_block)
+ run_start_notifications << notification_block
+ end
- run_status.run_id = request_id
- run_status.start_clock
- Chef::Log.info("Starting Chef Run for #{node.name}")
- run_started
+ #
+ # Add a listener for the 'client run success' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_completes_successfully(&notification_block)
+ run_completed_successfully_notifications << notification_block
+ end
- do_windows_admin_check
+ #
+ # Add a listener for the 'client run failed' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_fails(&notification_block)
+ run_failed_notifications << notification_block
+ end
- run_context = setup_run_context
+ #
+ # Clears all listeners for client run status events.
+ #
+ # Primarily for testing purposes.
+ #
+ # @api private
+ #
+ def clear_notifications
+ @run_start_notifications = nil
+ @run_completed_successfully_notifications = nil
+ @run_failed_notifications = nil
+ end
- if Chef::Config[:audit_mode] != :audit_only
- converge_error = converge_and_save(run_context)
- end
+ #
+ # TODO These seem protected to me.
+ #
+
+ #
+ # Listeners to be run when the client run starts.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_start_notifications
+ @run_start_notifications ||= []
+ end
- if Chef::Config[:why_run] == true
- # why_run should probably be renamed to why_converge
- Chef::Log.debug("Not running audits in 'why_run' mode - this mode is used to see potential converge changes")
- elsif Chef::Config[:audit_mode] != :disabled
- audit_error = run_audits(run_context)
- end
+ #
+ # Listeners to be run when the client run completes successfully.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_completed_successfully_notifications
+ @run_completed_successfully_notifications ||= []
+ end
- if converge_error || audit_error
- e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- e.fill_backtrace
- raise e
- end
+ #
+ # Listeners to be run when the client run fails.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_failed_notifications
+ @run_failed_notifications ||= []
+ end
+ end
- run_status.stop_clock
- Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
- run_completed_successfully
- @events.run_completed(node)
+ #
+ # IO stream that will be used as 'STDOUT' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDOUT_FD = STDOUT
- # rebooting has to be the last thing we do, no exceptions.
- Chef::Platform::Rebooter.reboot_if_needed!(node)
+ #
+ # IO stream that will be used as 'STDERR' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDERR_FD = STDERR
- true
+ #
+ # Deprecated writers
+ #
- rescue Exception => e
- # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
- Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}")
- # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
- if run_status
- run_status.stop_clock
- run_status.exception = e
- run_failed
- end
- Chef::Application.debug_stacktrace(e)
- @events.run_failed(e)
- raise
- ensure
- Chef::RequestID.instance.reset_request_id
- request_id = nil
- @run_status = nil
- run_context = nil
- runlock.release
- GC.start
- end
- true
- end
+ include Chef::Mixin::Deprecation
+ deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!"
+ deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!"
+ deprecated_attr :runner, "There is no alternative. Leave runner alone!"
private
+ attr_reader :override_runlist
+ attr_reader :specific_recipes
+
def empty_directory?(path)
!File.exists?(path) || (Dir.entries(path).size <= 2)
end
@@ -527,7 +922,6 @@ class Chef
Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
-
end
end
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index 2eb9870a64..6382af14c2 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -20,712 +20,65 @@
# limitations under the License.
require 'chef/log'
-require 'chef/exceptions'
-require 'mixlib/config'
-require 'chef/util/selinux'
-require 'chef/util/path_helper'
-require 'pathname'
-require 'chef/mixin/shell_out'
+require 'chef-config/logger'
-class Chef
- class Config
-
- extend Mixlib::Config
- extend Chef::Mixin::ShellOut
-
- PathHelper = Chef::Util::PathHelper
-
- # Evaluates the given string as config.
- #
- # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file.
- def self.from_string(string, filename)
- self.instance_eval(string, filename, 1)
- end
+# DI our logger into ChefConfig before we load the config. Some defaults are
+# auto-detected, and this emits log messages on some systems, all of which will
+# occur at require-time. So we need to set the logger first.
+ChefConfig.logger = Chef::Log
- # Manages the chef secret session key
- # === Returns
- # <newkey>:: A new or retrieved session key
- #
- def self.manage_secret_key
- newkey = nil
- if Chef::FileCache.has_key?("chef_server_cookie_id")
- newkey = Chef::FileCache.load("chef_server_cookie_id")
- else
- chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
- newkey = ""
- 40.times { |i| newkey << chars[rand(chars.size-1)] }
- Chef::FileCache.store("chef_server_cookie_id", newkey)
- end
- newkey
- end
-
- def self.inspect
- configuration.inspect
- end
-
- def self.platform_specific_path(path)
- path = PathHelper.cleanpath(path)
- if Chef::Platform.windows?
- # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb
- if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef'
- path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2])
- end
- end
- path
- end
-
- def self.add_formatter(name, file_path=nil)
- formatters << [name, file_path]
- end
-
- def self.add_event_logger(logger)
- event_handlers << logger
- end
-
- # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.)
- configurable(:config_file)
-
- default(:config_dir) do
- if config_file
- PathHelper.dirname(config_file)
- else
- PathHelper.join(user_home, ".chef", "")
- end
- end
-
- default :formatters, []
-
- # Override the config dispatch to set the value of multiple server options simultaneously
- #
- # === Parameters
- # url<String>:: String to be set for all of the chef-server-api URL's
- #
- configurable(:chef_server_url).writes_value { |url| url.to_s.strip }
-
- # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel.
- # So while this is basically identical to what method_missing would do, we pull
- # it up here and get a real method written so that things get dispatched
- # properly.
- configurable(:daemonize).writes_value { |v| v }
-
- # The root where all local chef object data is stored. cookbooks, data bags,
- # environments are all assumed to be in separate directories under this.
- # chef-solo uses these directories for input data. knife commands
- # that upload or download files (such as knife upload, knife role from file,
- # etc.) work.
- default :chef_repo_path do
- if self.configuration[:cookbook_path]
- if self.configuration[:cookbook_path].kind_of?(String)
- File.expand_path('..', self.configuration[:cookbook_path])
- else
- self.configuration[:cookbook_path].map do |path|
- File.expand_path('..', path)
- end
- end
- else
- cache_path
- end
- end
-
- def self.find_chef_repo_path(cwd)
- # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it.
- # This allows us to run config-free.
- path = cwd
- until File.directory?(PathHelper.join(path, "cookbooks"))
- new_path = File.expand_path('..', path)
- if new_path == path
- Chef::Log.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.")
- return Dir.pwd
- end
- path = new_path
- end
- Chef::Log.info("Auto-discovered chef repository at #{path}")
- path
- end
-
- def self.derive_path_from_chef_repo_path(child_path)
- if chef_repo_path.kind_of?(String)
- PathHelper.join(chef_repo_path, child_path)
- else
- chef_repo_path.map { |path| PathHelper.join(path, child_path)}
- end
- end
-
- # Location of acls on disk. String or array of strings.
- # Defaults to <chef_repo_path>/acls.
- # Only applies to Enterprise Chef commands.
- default(:acl_path) { derive_path_from_chef_repo_path('acls') }
-
- # Location of clients on disk. String or array of strings.
- # Defaults to <chef_repo_path>/acls.
- default(:client_path) { derive_path_from_chef_repo_path('clients') }
-
- # Location of cookbooks on disk. String or array of strings.
- # Defaults to <chef_repo_path>/cookbooks. If chef_repo_path
- # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]).
- default(:cookbook_path) do
- if self.configuration[:chef_repo_path]
- derive_path_from_chef_repo_path('cookbooks')
- else
- Array(derive_path_from_chef_repo_path('cookbooks')).flatten +
- Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten
- end
- end
-
- # Location of containers on disk. String or array of strings.
- # Defaults to <chef_repo_path>/containers.
- # Only applies to Enterprise Chef commands.
- default(:container_path) { derive_path_from_chef_repo_path('containers') }
-
- # Location of data bags on disk. String or array of strings.
- # Defaults to <chef_repo_path>/data_bags.
- default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') }
-
- # Location of environments on disk. String or array of strings.
- # Defaults to <chef_repo_path>/environments.
- default(:environment_path) { derive_path_from_chef_repo_path('environments') }
-
- # Location of groups on disk. String or array of strings.
- # Defaults to <chef_repo_path>/groups.
- # Only applies to Enterprise Chef commands.
- default(:group_path) { derive_path_from_chef_repo_path('groups') }
-
- # Location of nodes on disk. String or array of strings.
- # Defaults to <chef_repo_path>/nodes.
- default(:node_path) { derive_path_from_chef_repo_path('nodes') }
-
- # Location of roles on disk. String or array of strings.
- # Defaults to <chef_repo_path>/roles.
- default(:role_path) { derive_path_from_chef_repo_path('roles') }
-
- # Location of users on disk. String or array of strings.
- # Defaults to <chef_repo_path>/users.
- # Does not apply to Enterprise Chef commands.
- default(:user_path) { derive_path_from_chef_repo_path('users') }
-
- # Location of policies on disk. String or array of strings.
- # Defaults to <chef_repo_path>/policies.
- default(:policy_path) { derive_path_from_chef_repo_path('policies') }
-
- # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity
- default :enforce_path_sanity, true
-
- # Formatted Chef Client output is a beta feature, disabled by default:
- default :formatter, "null"
-
- # The number of times the client should retry when registering with the server
- default :client_registration_retries, 5
-
- # An array of paths to search for knife exec scripts if they aren't in the current directory
- default :script_path, []
-
- # The root of all caches (checksums, cache and backup). If local mode is on,
- # this is under the user's home directory.
- default(:cache_path) do
- if local_mode
- PathHelper.join(config_dir, 'local-mode-cache')
- else
- primary_cache_root = platform_specific_path("/var")
- primary_cache_path = platform_specific_path("/var/chef")
- # Use /var/chef as the cache path only if that folder exists and we can read and write
- # into it, or /var exists and we can read and write into it (we'll create /var/chef later).
- # Otherwise, we'll create .chef under the user's home directory and use that as
- # the cache path.
- unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root)
- secondary_cache_path = PathHelper.join(user_home, '.chef')
- Chef::Log.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}")
- secondary_cache_path
- else
- primary_cache_path
- end
- end
- end
-
- # Returns true only if the path exists and is readable and writeable for the user.
- def self.path_accessible?(path)
- File.exists?(path) && File.readable?(path) && File.writable?(path)
- end
-
- # Where cookbook files are stored on the server (by content checksum)
- default(:checksum_path) { PathHelper.join(cache_path, "checksums") }
-
- # Where chef's cache files should be stored
- default(:file_cache_path) { PathHelper.join(cache_path, "cache") }
-
- # Where backups of chef-managed files should go
- default(:file_backup_path) { PathHelper.join(cache_path, "backup") }
-
- # The chef-client (or solo) lockfile.
- #
- # If your `file_cache_path` resides on a NFS (or non-flock()-supporting
- # fs), it's recommended to set this to something like
- # '/tmp/chef-client-running.pid'
- default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") }
-
- ## Daemonization Settings ##
- # What user should Chef run as?
- default :user, nil
- default :group, nil
- default :umask, 0022
-
- # Valid log_levels are:
- # * :debug
- # * :info
- # * :warn
- # * :fatal
- # These work as you'd expect. There is also a special `:auto` setting.
- # When set to :auto, Chef will auto adjust the log verbosity based on
- # context. When a tty is available (usually because the user is running chef
- # in a console), the log level is set to :warn, and output formatters are
- # used as the primary mode of output. When a tty is not available, the
- # logger is the primary mode of output, and the log level is set to :info
- default :log_level, :auto
-
- # Logging location as either an IO stream or string representing log file path
- default :log_location, STDOUT
-
- # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty
- default :force_formatter, false
-
- # Using `force_logger` causes chef to default to logger output when STDOUT is a tty
- default :force_logger, false
-
- default :http_retry_count, 5
- default :http_retry_delay, 5
- default :interval, nil
- default :once, nil
- default :json_attribs, nil
- # toggle info level log items that can create a lot of output
- default :verbose_logging, true
- default :node_name, nil
- default :diff_disabled, false
- default :diff_filesize_threshold, 10000000
- default :diff_output_threshold, 1000000
- default :local_mode, false
-
- default :pid_file, nil
-
- config_context :chef_zero do
- config_strict_mode true
- default(:enabled) { Chef::Config.local_mode }
- default :host, 'localhost'
- default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works
- end
- default :chef_server_url, "https://localhost:443"
+require 'chef-config/config'
+require 'chef/platform/query_helpers'
- default :rest_timeout, 300
- default :yum_timeout, 900
- default :yum_lock_timeout, 30
- default :solo, false
- default :splay, nil
- default :why_run, false
- default :color, false
- default :client_fork, true
- default :ez, false
- default :enable_reporting, true
- default :enable_reporting_url_fatals, false
- # Possible values for :audit_mode
- # :enabled, :disabled, :audit_only,
- #
- # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature
- # and is disabled by default. When users choose to enable audit-mode,
- # a warning is issued in application/client#reconfigure.
- # This can be removed when audit-mode is enabled by default.
- default :audit_mode, :disabled
-
- # Policyfile is an experimental feature where a node gets its run list and
- # cookbook version set from a single document on the server instead of
- # expanding the run list and having the server compute the cookbook version
- # set based on environment constraints.
- #
- # Because this feature is experimental, it is not recommended for
- # production use. Developent/release of this feature may not adhere to
- # semver guidelines.
- default :use_policyfile, false
-
- # Set these to enable SSL authentication / mutual-authentication
- # with the server
-
- # Client side SSL cert/key for mutual auth
- default :ssl_client_cert, nil
- default :ssl_client_key, nil
-
- # Whether or not to verify the SSL cert for all HTTPS requests. When set to
- # :verify_peer (default), all HTTPS requests will be validated regardless of other
- # SSL verification settings. When set to :verify_none no HTTPS requests will
- # be validated.
- default :ssl_verify_mode, :verify_peer
-
- # Whether or not to verify the SSL cert for HTTPS requests to the Chef
- # server API. If set to `true`, the server's cert will be validated
- # regardless of the :ssl_verify_mode setting. This is set to `true` when
- # running in local-mode.
- # NOTE: This is a workaround until verify_peer is enabled by default.
- default(:verify_api_cert) { Chef::Config.local_mode }
-
- # Path to the default CA bundle files.
- default :ssl_ca_path, nil
- default(:ssl_ca_file) do
- if Chef::Platform.windows? and embedded_path = embedded_dir
- cacert_path = File.join(embedded_path, "ssl/certs/cacert.pem")
- cacert_path if File.exist?(cacert_path)
- else
- nil
- end
- end
-
- # A directory that contains additional SSL certificates to trust. Any
- # certificates in this directory will be added to whatever CA bundle ruby
- # is using. Use this to add self-signed certs for your Chef Server or local
- # HTTP file servers.
- default(:trusted_certs_dir) { PathHelper.join(config_dir, "trusted_certs") }
-
- # Where should chef-solo download recipes from?
- default :recipe_url, nil
-
- # Sets the version of the signed header authentication protocol to use (see
- # the 'mixlib-authorization' project for more detail). Currently, versions
- # 1.0 and 1.1 are available; however, the chef-server must first be
- # upgraded to support version 1.1 before clients can begin using it.
- #
- # Version 1.1 of the protocol is required when using a `node_name` greater
- # than ~90 bytes (~90 ascii characters), so chef-client will automatically
- # switch to using version 1.1 when `node_name` is too large for the 1.0
- # protocol. If you intend to use large node names, ensure that your server
- # supports version 1.1. Automatic detection of large node names means that
- # users will generally not need to manually configure this.
- #
- # In the future, this configuration option may be replaced with an
- # automatic negotiation scheme.
- default :authentication_protocol_version, "1.0"
-
- # This key will be used to sign requests to the Chef server. This location
- # must be writable by Chef during initial setup when generating a client
- # identity on the server.
- #
- # The chef-server will look up the public key for the client using the
- # `node_name` of the client.
- #
- # If chef-zero is enabled, this defaults to nil (no authentication).
- default(:client_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/client.pem") }
-
- # When registering the client, should we allow the client key location to
- # be a symlink? eg: /etc/chef/client.pem -> /etc/chef/prod-client.pem
- # If the path of the key goes through a directory like /tmp this should
- # never be set to true or its possibly an easily exploitable security hole.
- default :follow_client_key_symlink, false
-
- # This secret is used to decrypt encrypted data bag items.
- default(:encrypted_data_bag_secret) do
- if File.exist?(platform_specific_path("/etc/chef/encrypted_data_bag_secret"))
- platform_specific_path("/etc/chef/encrypted_data_bag_secret")
- else
- nil
- end
- end
-
- # As of Chef 11.0, version "1" is the default encrypted data bag item
- # format. Version "2" is available which adds encrypt-then-mac protection.
- # To maintain compatibility, versions other than 1 must be opt-in.
- #
- # Set this to `2` if you have chef-client 11.6.0+ in your infrastructure.
- # Set this to `3` if you have chef-client 11.?.0+, ruby 2 and OpenSSL >= 1.0.1 in your infrastructure. (TODO)
- default :data_bag_encrypt_version, 1
-
- # When reading data bag items, any supported version is accepted. However,
- # if all encrypted data bags have been generated with the version 2 format,
- # it is recommended to disable support for earlier formats to improve
- # security. For example, the version 2 format is identical to version 1
- # except for the addition of an HMAC, so an attacker with MITM capability
- # could downgrade an encrypted data bag to version 1 as part of an attack.
- default :data_bag_decrypt_minimum_version, 0
-
- # If there is no file in the location given by `client_key`, chef-client
- # will temporarily use the "validator" identity to generate one. If the
- # `client_key` is not present and the `validation_key` is also not present,
- # chef-client will not be able to authenticate to the server.
- #
- # The `validation_key` is never used if the `client_key` exists.
- #
- # If chef-zero is enabled, this defaults to nil (no authentication).
- default(:validation_key) { chef_zero.enabled ? nil : platform_specific_path("/etc/chef/validation.pem") }
- default :validation_client_name, "chef-validator"
-
- # When creating a new client via the validation_client account, Chef 11
- # servers allow the client to generate a key pair locally and send the
- # public key to the server. This is more secure and helps offload work from
- # the server, enhancing scalability. If enabled and the remote server
- # implements only the Chef 10 API, client registration will not work
- # properly.
- #
- # The default value is `true`. Set to `false` to disable client-side key
- # generation (server generates client keys).
- default(:local_key_generation) { true }
+# Ohai::Config defines its own log_level and log_location. When loaded, it will
+# override the default ChefConfig::Config values. We save them here before
+# loading ohai/config so that we can override them again inside Chef::Config.
+#
+# REMOVEME once these configurables are removed from the top level of Ohai.
+LOG_LEVEL = ChefConfig::Config[:log_level] unless defined? LOG_LEVEL
+LOG_LOCATION = ChefConfig::Config[:log_location] unless defined? LOG_LOCATION
- # Zypper package provider gpg checks. Set to true to enable package
- # gpg signature checking. This will be default in the
- # future. Setting to false disables the warnings.
- # Leaving this set to nil or false is a security hazard!
- default :zypper_check_gpg, nil
+# Load the ohai config into the chef config. We can't have an empty ohai
+# configuration context because `ohai.plugins_path << some_path` won't work,
+# and providing default ohai config values here isn't DRY.
+require 'ohai/config'
- # Report Handlers
- default :report_handlers, []
+class Chef
+ Config = ChefConfig::Config
- # Event Handlers
- default :event_handlers, []
+ # We re-open ChefConfig::Config to add additional settings. Generally,
+ # everything should go in chef-config so it's shared with whoever uses that.
+ # We make execeptions to that rule when:
+ # * The functionality isn't likely to be useful outside of Chef
+ # * The functionality makes use of a dependency we don't want to add to chef-config
+ class Config
- default :disable_event_loggers, false
default :event_loggers do
evt_loggers = []
- if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
+ if ChefConfig.windows? and not Chef::Platform.windows_server_2003?
evt_loggers << :win_evt
end
evt_loggers
end
- # Exception Handlers
- default :exception_handlers, []
-
- # Start handlers
- default :start_handlers, []
-
- # Syntax Check Cache. Knife keeps track of files that is has already syntax
- # checked by storing files in this directory. `syntax_check_cache_path` is
- # the new (and preferred) configuration setting. If not set, knife will
- # fall back to using cache_options[:path], which is deprecated but exists in
- # many client configs generated by pre-Chef-11 bootstrappers.
- default(:syntax_check_cache_path) { cache_options[:path] }
-
- # Deprecated:
- default(:cache_options) { { :path => PathHelper.join(file_cache_path, "checksums") } }
-
- # Whether errors should be raised for deprecation warnings. When set to
- # `false` (the default setting), a warning is emitted but code using
- # deprecated methods/features/etc. should work normally otherwise. When set
- # to `true`, usage of deprecated methods/features will raise a
- # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that
- # deprecated functionality is not used internally by Chef. End users
- # should generally leave this at the default setting (especially in
- # production), but it may be useful when testing cookbooks or other code if
- # the user wishes to aggressively address deprecations.
- default(:treat_deprecation_warnings_as_errors) do
- # Using an environment variable allows this setting to be inherited in
- # tests that spawn new processes.
- ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS")
- end
-
- # knife configuration data
- config_context :knife do
- default :ssh_port, nil
- default :ssh_user, nil
- default :ssh_attribute, nil
- default :ssh_gateway, nil
- default :bootstrap_version, nil
- default :bootstrap_proxy, nil
- default :bootstrap_template, nil
- default :secret, nil
- default :secret_file, nil
- default :identity_file, nil
- default :host_key_verify, nil
- default :forward_agent, nil
- default :sort_status_reverse, nil
- default :hints, {}
- end
-
- def self.set_defaults_for_windows
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx
- principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+'
- default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
- default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
-
- default :fatal_windows_admin_check, false
- end
-
- def self.set_defaults_for_nix
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- #
- # user/group cannot start with '-', '+' or '~'
- # user/group cannot contain ':', ',' or non-space-whitespace or null byte
- # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not
- # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup
- default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
- default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
- end
-
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- if Chef::Platform.windows?
- set_defaults_for_windows
- else
- set_defaults_for_nix
- end
-
- # This provides a hook which rspec can stub so that we can avoid twiddling
- # global state in tests.
- def self.env
- ENV
- end
-
- def self.windows_home_path
- env['SYSTEMDRIVE'] + env['HOMEPATH'] if env['SYSTEMDRIVE'] && env['HOMEPATH']
- end
-
- # returns a platform specific path to the user home dir if set, otherwise default to current directory.
- default( :user_home ) { env['HOME'] || windows_home_path || env['USERPROFILE'] || Dir.pwd }
-
- # Enable file permission fixup for selinux. Fixup will be done
- # only if selinux is enabled in the system.
- default :enable_selinux_file_permission_fixup, true
-
- # Use atomic updates (i.e. move operation) while updating contents
- # of the files resources. When set to false copy operation is
- # used to update files.
- default :file_atomic_update, true
-
- # There are 3 possible values for this configuration setting.
- # true => file staging is done in the destination directory
- # false => file staging is done via tempfiles under ENV['TMP']
- # :auto => file staging will try using destination directory if possible and
- # will fall back to ENV['TMP'] if destination directory is not usable.
- default :file_staging_uses_destdir, :auto
-
- # Exit if another run is in progress and the chef-client is unable to
- # get the lock before time expires. If nil, no timeout is enforced. (Exits
- # immediately if 0.)
- default :run_lock_timeout, nil
-
- # Number of worker threads for syncing cookbooks in parallel. Increasing
- # this number can result in gateway errors from the server (namely 503 and 504).
- # If you are seeing this behavior while using the default setting, reducing
- # the number of threads will help.
- default :cookbook_sync_threads, 10
-
- # At the beginning of the Chef Client run, the cookbook manifests are downloaded which
- # contain URLs for every file in every relevant cookbook. Most of the files
- # (recipes, resources, providers, libraries, etc) are immediately synchronized
- # at the start of the run. The handling of "files" and "templates" directories,
- # however, have two modes of operation. They can either all be downloaded immediately
- # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as
- # cookbook_file or template resources are converged which require them (no_lazy_load==false).
- #
- # The advantage of lazily loading these files is that unnecessary files are not
- # synchronized. This may be useful to users with large files checked into cookbooks which
- # are only selectively downloaded to a subset of clients which use the cookbook. However,
- # better solutions are to either isolate large files into individual cookbooks and only
- # include those cookbooks in the run lists of the servers that need them -- or move to
- # using remote_file and a more appropriate backing store like S3 for large file
- # distribution.
+ # Override the default values that were set by Ohai.
#
- # The disadvantages of lazily loading files are that users some time find it
- # confusing that their cookbooks are not fully synchronzied to the cache initially,
- # and more importantly the time-sensitive URLs which are in the manifest may time
- # out on long Chef runs before the resource that uses the file is converged
- # (leading to many confusing 403 errors on template/cookbook_file resources).
- #
- default :no_lazy_load, true
-
- # Default for the chef_gem compile_time attribute. Nil is the same as false but will emit
- # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to
- # true then the user will get backcompat behavior but with a single nag warning that cookbooks
- # may break with this setting in the future. The false setting is the recommended setting and
- # will become the default.
- default :chef_gem_compile_time, nil
-
- # A whitelisted array of attributes you want sent over the wire when node
- # data is saved.
- # The default setting is nil, which collects all data. Setting to [] will not
- # collect any data for save.
- default :automatic_attribute_whitelist, nil
- default :default_attribute_whitelist, nil
- default :normal_attribute_whitelist, nil
- default :override_attribute_whitelist, nil
-
- config_context :windows_service do
- # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run
- # to finish
- default :watchdog_timeout, 2 * (60 * 60) # 2 hours
- end
+ # REMOVEME once these configurables are removed from the top level of Ohai.
+ default :log_level, LOG_LEVEL
+ default :log_location, LOG_LOCATION
- # Chef requires an English-language UTF-8 locale to function properly. We attempt
- # to use the 'locale -a' command and search through a list of preferences until we
- # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be
- # able to use that even if there is no English locale on the server, but Mac, Solaris,
- # AIX, etc do not have that locale. We then try to find an English locale and fall
- # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try
- # to do the work to return a non-US UTF-8 locale then we fail inside of providers when
- # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then
- # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding
- # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by
- # default rather than drop English.
+ # Ohai::Config[:log_level] is deprecated and warns when set. Unfortunately,
+ # there is no way to distinguish between setting log_level and setting
+ # Ohai::Config[:log_level]. Since log_level and log_location are used by
+ # chef-client and other tools (e.g., knife), we will mute the warnings here
+ # by redefining the config_attr_writer to not warn for these options.
#
- # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly
- # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'.
- def self.guess_internal_locale
- # https://github.com/opscode/chef/issues/2181
- # Some systems have the `locale -a` command, but the result has
- # invalid characters for the default encoding.
- #
- # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8",
- # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding.
- locales = shell_out_with_systems_locale!("locale -a").stdout.split
- case
- when locales.include?('C.UTF-8')
- 'C.UTF-8'
- when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8')
- 'en_US.UTF-8'
- when locales.include?('en.UTF-8')
- 'en.UTF-8'
- else
- # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8
- guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i }
- unless guesses.empty?
- guessed_locale = guesses.first
- # Transform into the form en_ZZ.UTF-8
- guessed_locale.gsub(/UTF-?8$/i, "UTF-8")
- else
- Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
- 'C'
- end
- end
- rescue
- if Chef::Platform.windows?
- Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else."
- else
- Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
+ # REMOVEME once the warnings for these configurables are removed from Ohai.
+ [ :log_level, :log_location ].each do |option|
+ config_attr_writer option do |value|
+ value
end
- 'en_US.UTF-8'
end
- default :internal_locale, guess_internal_locale
-
- # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g.
- # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's
- # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been
- # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be
- # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with
- # magic tags to make ruby correctly identify the encoding being used. Changing this default will
- # break Chef community cookbooks and is very highly discouraged.
- default :ruby_encoding, Encoding::UTF_8
-
- # If installed via an omnibus installer, this gives the path to the
- # "embedded" directory which contains all of the software packaged with
- # omnibus. This is used to locate the cacert.pem file on windows.
- def self.embedded_dir
- Pathname.new(_this_file).ascend do |path|
- if path.basename.to_s == "embedded"
- return path.to_s
- end
- end
-
- nil
- end
-
- # Path to this file in the current install.
- def self._this_file
- File.expand_path(__FILE__)
- end
end
end
diff --git a/lib/chef/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 781d3b40b0..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
@@ -286,9 +288,13 @@ class Chef
# === Returns
# versions<Array>:: Returns the list of versions for the platform
def depends(cookbook, *version_args)
- version = new_args_format(:depends, cookbook, version_args)
- constraint = validate_version_constraint(:depends, cookbook, version)
- @dependencies[cookbook] = constraint.to_s
+ if cookbook == name
+ Chef::Log.warn "Ignoring self-dependency in cookbook #{name}, please remove it (in the future this will be fatal)."
+ else
+ version = new_args_format(:depends, cookbook, version_args)
+ constraint = validate_version_constraint(:depends, cookbook, version)
+ @dependencies[cookbook] = constraint.to_s
+ end
@dependencies[cookbook]
end
@@ -450,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?
@@ -494,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
@@ -528,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
@@ -586,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
@@ -603,7 +629,7 @@ class Chef
msg=<<-OBSOLETED
The dependency specification syntax you are using is no longer valid. You may not
specify more than one version constraint for a particular cookbook.
-Consult http://wiki.opscode.com/display/chef/Metadata for the updated syntax.
+Consult https://docs.chef.io/config_rb_metadata.html for the updated syntax.
Called by: #{caller_name} '#{dep_name}', #{version_constraints.map {|vc| vc.inspect}.join(", ")}
Called from:
@@ -622,7 +648,7 @@ OBSOLETED
The version constraint syntax you are using is not valid. If you recently
upgraded to Chef 0.10.0, be aware that you no may longer use "<<" and ">>" for
'less than' and 'greater than'; use '<' and '>' instead.
-Consult http://wiki.opscode.com/display/chef/Metadata for more information.
+Consult https://docs.chef.io/config_rb_metadata.html for more information.
Called by: #{caller_name} '#{dep_name}', '#{constraint_str}'
Called from:
diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb
index 2ddce31001..9d895b168e 100644
--- a/lib/chef/cookbook/remote_file_vendor.rb
+++ b/lib/chef/cookbook/remote_file_vendor.rb
@@ -30,7 +30,7 @@ class Chef
def initialize(manifest, rest)
@manifest = manifest
- @cookbook_name = @manifest[:cookbook_name]
+ @cookbook_name = @manifest[:cookbook_name] || @manifest[:name]
@rest = rest
end
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_loader.rb b/lib/chef/cookbook_loader.rb
index c05fedb141..79005b1569 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -106,7 +106,7 @@ class Chef
if @cookbooks_by_name.has_key?(cookbook.to_sym) or load_cookbook(cookbook.to_sym)
@cookbooks_by_name[cookbook.to_sym]
else
- raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (http://wiki.opscode.com/display/chef/Metadata)"
+ raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (https://docs.chef.io/config_rb_metadata.html)"
end
end
diff --git a/lib/chef/cookbook_manifest.rb b/lib/chef/cookbook_manifest.rb
index 0d21e9725c..10654a4945 100644
--- a/lib/chef/cookbook_manifest.rb
+++ b/lib/chef/cookbook_manifest.rb
@@ -36,6 +36,7 @@ class Chef
def_delegator :@cookbook_version, :root_paths
def_delegator :@cookbook_version, :segment_filenames
def_delegator :@cookbook_version, :name
+ def_delegator :@cookbook_version, :identifier
def_delegator :@cookbook_version, :metadata
def_delegator :@cookbook_version, :full_name
def_delegator :@cookbook_version, :version
@@ -141,9 +142,16 @@ class Chef
# REST api. If there is an existing document on the server and it
# is marked frozen, a PUT will result in a 409 Conflict.
def save_url
- "#{cookbook_url_path}/#{name}/#{version}"
+ if policy_mode?
+ "#{named_cookbook_url}/#{identifier}"
+ else
+ "#{named_cookbook_url}/#{version}"
+ end
end
+ def named_cookbook_url
+ "#{cookbook_url_path}/#{name}"
+ end
# Adds the `force=true` parameter to the upload URL. This allows
# the user to overwrite a frozen cookbook (a PUT against the
# normal #save_url raises a 409 Conflict in this case).
@@ -214,10 +222,16 @@ class Chef
end
end
- manifest[:cookbook_name] = name.to_s
manifest[:metadata] = metadata
manifest[:version] = metadata.version
- manifest[:name] = full_name
+
+ if policy_mode?
+ manifest[:name] = name.to_s
+ manifest[:identifier] = identifier
+ else
+ manifest[:name] = full_name
+ manifest[:cookbook_name] = name.to_s
+ end
@manifest_records_by_path = extract_manifest_records_by_path(manifest)
@manifest = manifest
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
index 9e7a55c772..0302a51165 100644
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ b/lib/chef/cookbook_site_streaming_uploader.rb
@@ -106,7 +106,7 @@ class Chef
url = URI.parse(to_url)
- Chef::Log.logger.debug("Signing: method: #{http_verb}, path: #{url.path}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
+ Chef::Log.logger.debug("Signing: method: #{http_verb}, url: #{url}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
# We use the body for signing the request if the file parameter
# wasn't a valid file or wasn't included. Extract the body (with
@@ -141,13 +141,8 @@ class Chef
req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty?
req.body_stream = body_stream
- http = Net::HTTP.new(url.host, url.port)
- if url.scheme == "https"
- http.use_ssl = true
- http.verify_mode = verify_mode
- end
+ http = Chef::HTTP::BasicClient.new(url).http_client
res = http.request(req)
- #res = http.start {|http_proc| http_proc.request(req) }
# alias status to code and to_s to body for test purposes
# TODO: stop the following madness!
@@ -166,17 +161,6 @@ class Chef
res
end
- private
-
- def verify_mode
- verify_mode = Chef::Config[:ssl_verify_mode]
- if verify_mode == :verify_none
- OpenSSL::SSL::VERIFY_NONE
- elsif verify_mode == :verify_peer
- OpenSSL::SSL::VERIFY_PEER
- end
- end
-
end
class StreamPart
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index b8f32a61bb..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
@@ -78,6 +78,16 @@ class Chef
attr_accessor :chef_server_rest
+ # The `identifier` field is used for cookbook_artifacts, which are
+ # organized on the chef server according to their content. If the
+ # policy_mode option to CookbookManifest is set to true it will include
+ # this field in the manifest Hash and in the upload URL.
+ #
+ # This field may be removed or have different behavior in the future, don't
+ # use it in 3rd party code.
+ # @api private
+ attr_accessor :identifier
+
# The first root path is the primary cookbook dir, from which metadata is loaded
def root_dir
root_paths[0]
@@ -458,10 +468,19 @@ class Chef
cookbook_version
end
+ def self.from_cb_artifact_data(o)
+ cookbook_version = new(o["name"])
+ # We want the Chef::Cookbook::Metadata object to always be inflated
+ cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
+ cookbook_version.manifest = o
+ cookbook_version.identifier = o["identifier"]
+ cookbook_version
+ end
+
# @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/definitions.rb b/lib/chef/dsl/definitions.rb
new file mode 100644
index 0000000000..1358f67720
--- /dev/null
+++ b/lib/chef/dsl/definitions.rb
@@ -0,0 +1,44 @@
+class Chef
+ module DSL
+ #
+ # Module containing a method for each declared definition
+ #
+ # Depends on declare_resource(name, created_at, &block)
+ #
+ # @api private
+ #
+ module Definitions
+ def self.add_definition(dsl_name)
+ module_eval <<-EOM, __FILE__, __LINE__+1
+ def #{dsl_name}(*args, &block)
+ evaluate_resource_definition(#{dsl_name.inspect}, *args, &block)
+ end
+ EOM
+ end
+
+ # @api private
+ def has_resource_definition?(name)
+ run_context.definitions.has_key?(name)
+ end
+
+ # Processes the arguments and block as a resource definition.
+ #
+ # @api private
+ def evaluate_resource_definition(definition_name, *args, &block)
+
+ # This dupes the high level object, but we still need to dup the params
+ new_def = run_context.definitions[definition_name].dup
+
+ new_def.params = new_def.params.dup
+ new_def.node = run_context.node
+ # This sets up the parameter overrides
+ new_def.instance_eval(&block) if block
+
+ new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
+ new_recipe.params = new_def.params
+ new_recipe.params[:name] = args[0]
+ new_recipe.instance_eval(&new_def.recipe)
+ end
+ end
+ end
+end
diff --git a/lib/chef/dsl/powershell.rb b/lib/chef/dsl/powershell.rb
new file mode 100644
index 0000000000..a17971c689
--- /dev/null
+++ b/lib/chef/dsl/powershell.rb
@@ -0,0 +1,29 @@
+#
+# Author:: Jay Mundrawala (<jdm@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/util/powershell/ps_credential'
+
+class Chef
+ module DSL
+ module Powershell
+ def ps_credential(username='placeholder', password)
+ Chef::Util::Powershell::PSCredential.new(username, password)
+ end
+ end
+ 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 c22f053292..26c0ec6768 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -19,8 +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'
class Chef
module DSL
@@ -31,48 +33,10 @@ class Chef
module Recipe
include Chef::Mixin::ShellOut
- include Chef::Mixin::ConvertToClassName
-
- def method_missing(method_symbol, *args, &block)
- # If we have a definition that matches, we want to use that instead. This should
- # let you do some really crazy over-riding of "native" types, if you really want
- # to.
- if has_resource_definition?(method_symbol)
- evaluate_resource_definition(method_symbol, *args, &block)
- elsif have_resource_class_for?(method_symbol)
- # Otherwise, we're rocking the regular resource call route.
- declare_resource(method_symbol, args[0], caller[0], &block)
- else
- begin
- super
- rescue NoMethodError
- raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
- rescue NameError
- raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
- end
- end
- end
+ include Chef::Mixin::PowershellOut
- def has_resource_definition?(name)
- run_context.definitions.has_key?(name)
- end
-
- # Processes the arguments and block as a resource definition.
- def evaluate_resource_definition(definition_name, *args, &block)
-
- # This dupes the high level object, but we still need to dup the params
- new_def = run_context.definitions[definition_name].dup
-
- new_def.params = new_def.params.dup
- new_def.node = run_context.node
- # This sets up the parameter overrides
- new_def.instance_eval(&block) if block
-
- new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
- new_recipe.params = new_def.params
- new_recipe.params[:name] = args[0]
- new_recipe.instance_eval(&new_def.recipe)
- end
+ include Chef::DSL::Resources
+ include Chef::DSL::Definitions
#
# Instantiates a resource (via #build_resource), then adds it to the
@@ -156,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
@@ -168,12 +132,71 @@ class Chef
raise Chef::Exceptions::ResourceNotFound, "exec was called, but you probably meant to use an execute resource. If not, please call Kernel#exec explicitly. The exec block called was \"#{args}\""
end
+ # DEPRECATED:
+ # method_missing must live for backcompat purposes until Chef 13.
+ def method_missing(method_symbol, *args, &block)
+ #
+ # If there is already DSL for this, someone must have called
+ # method_missing manually. Not a fan. Not. A. Fan.
+ #
+ if respond_to?(method_symbol)
+ Chef.log_deprecation("Calling method_missing(#{method_symbol.inspect}) directly is deprecated in Chef 12 and will be removed in Chef 13. Use public_send() or send() instead.")
+ return send(method_symbol, *args, &block)
+ end
+
+ #
+ # If a definition exists, then Chef::DSL::Definitions.add_definition was
+ # never called. DEPRECATED.
+ #
+ if run_context.definitions.has_key?(method_symbol.to_sym)
+ Chef.log_deprecation("Definition #{method_symbol} (#{run_context.definitions[method_symbol.to_sym]}) was added to the run_context without calling Chef::DSL::Definitions.add_definition(#{method_symbol.to_sym.inspect}). This will become required in Chef 13.")
+ Chef::DSL::Definitions.add_definition(method_symbol)
+ return send(method_symbol, *args, &block)
+ end
+
+ #
+ # See if the resource exists anyway. If the user had set
+ # Chef::Resource::Blah = <resource>, a deprecation warning will be
+ # emitted and the DSL method 'blah' will be added to the DSL.
+ #
+ resource_class = Chef::ResourceResolver.resolve(method_symbol, node: run_context ? run_context.node : nil)
+ if resource_class
+ Chef::DSL::Resources.add_resource_dsl(method_symbol)
+ return send(method_symbol, *args, &block)
+ end
+
+ begin
+ super
+ rescue NoMethodError
+ raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
+ rescue NameError
+ raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
+ end
+ end
+
+ module FullDSL
+ require 'chef/dsl/data_query'
+ require 'chef/dsl/platform_introspection'
+ require 'chef/dsl/include_recipe'
+ require 'chef/dsl/registry_helper'
+ require 'chef/dsl/reboot_pending'
+ require 'chef/dsl/audit'
+ require 'chef/dsl/powershell'
+ include Chef::DSL::DataQuery
+ include Chef::DSL::PlatformIntrospection
+ include Chef::DSL::IncludeRecipe
+ include Chef::DSL::Recipe
+ include Chef::DSL::RegistryHelper
+ include Chef::DSL::RebootPending
+ include Chef::DSL::Audit
+ include Chef::DSL::Powershell
+ end
end
end
end
-# We require this at the BOTTOM of this file to avoid circular requires (it is used
-# at runtime but not load time)
+# Avoid circular references for things that are only used in instance methods
+require 'chef/resource_builder'
require 'chef/resource'
# **DEPRECATED**
diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb
new file mode 100644
index 0000000000..49588ed516
--- /dev/null
+++ b/lib/chef/dsl/resources.rb
@@ -0,0 +1,31 @@
+class Chef
+ module DSL
+ #
+ # Module containing a method for each globally declared Resource
+ #
+ # Depends on declare_resource(name, created_at, &block)
+ #
+ # @api private
+ module Resources
+ def self.add_resource_dsl(dsl_name)
+ begin
+ module_eval(<<-EOM, __FILE__, __LINE__+1)
+ def #{dsl_name}(*args, &block)
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1
+ declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block)
+ end
+ EOM
+ rescue SyntaxError
+ # Handle the case where dsl_name has spaces, etc.
+ define_method(dsl_name.to_sym) do |*args, &block|
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1
+ declare_resource(dsl_name, args[0], caller[0], &block)
+ end
+ end
+ end
+ def self.remove_resource_dsl(dsl_name)
+ remove_method(dsl_name) if method_defined?(dsl_name)
+ end
+ end
+ end
+end
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 7274105802..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
@@ -82,6 +87,11 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ # Called after the Policyfile was loaded. This event only occurs when
+ # chef is in policyfile mode.
+ def policyfile_loaded(policy)
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
end
@@ -113,8 +123,8 @@ class Chef
def cookbook_sync_start(cookbook_count)
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
end
# Called when an individual file in a cookbook has been updated
@@ -239,13 +249,13 @@ class Chef
end
# Called when audit phase successfully finishes
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
end
# Called if there is an uncaught exception during the audit phase. The audit runner should
# be catching and handling errors from the examples, so this is only uncaught errors (like
# bugs in our handling code)
- def audit_phase_failed(exception)
+ def audit_phase_failed(exception, audit_output)
end
# Signifies the start of a `control_group` block with a defined name
@@ -264,26 +274,37 @@ class Chef
# def notifications_resolved
# end
+ #
+ # Resource events and ordering:
+ #
+ # 1. Start the action
+ # - resource_action_start
+ # 2. Check the guard
+ # - resource_skipped: (goto 7) if only_if/not_if say to skip
+ # 3. Load the current resource
+ # - resource_current_state_loaded
+ # - resource_current_state_load_bypassed (if not why-run safe)
+ # 4. Check if why-run safe
+ # - resource_bypassed: (goto 7) if not why-run safe
+ # 5. During processing:
+ # - resource_update_applied: For each actual change (many per action)
+ # 6. Processing complete status:
+ # - resource_failed if the resource threw an exception while running
+ # - resource_failed_retriable: (goto 3) if resource failed and will be retried
+ # - resource_updated if the resource was updated (resource_update_applied will have been called)
+ # - resource_up_to_date if the resource was up to date (no resource_update_applied)
+ # 7. Processing complete:
+ # - resource_completed
+ #
+
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type=nil, notifier=nil)
end
- # Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
-
- # Called when a resource fails and will not be retried.
- def resource_failed(resource, action, exception)
- end
-
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
end
- # Called when a resource action has been completed
- def resource_completed(resource)
- end
-
# Called after #load_current_resource has run.
def resource_current_state_loaded(resource, action, current_resource)
end
@@ -297,21 +318,33 @@ class Chef
def resource_bypassed(resource, action, current_resource)
end
- # Called when a resource has no converge actions, e.g., it was already correct.
- def resource_up_to_date(resource, action)
- end
-
# Called when a change has been made to a resource. May be called multiple
# times per resource, e.g., a file may have its content updated, and then
# its permissions updated.
def resource_update_applied(resource, action, update)
end
+ # Called when a resource fails, but will retry.
+ def resource_failed_retriable(resource, action, retry_count, exception)
+ end
+
+ # Called when a resource fails and will not be retried.
+ def resource_failed(resource, action, exception)
+ end
+
# Called after a resource has been completely converged, but only if
# modifications were made.
def resource_updated(resource, action)
end
+ # Called when a resource has no converge actions, e.g., it was already correct.
+ def resource_up_to_date(resource, action)
+ end
+
+ # Called when a resource action has been completed
+ def resource_completed(resource)
+ end
+
# A stream has opened.
def stream_opened(stream, options = {})
end
@@ -347,8 +380,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 9f43f14311..966a3f32ec 100644
--- a/lib/chef/event_dispatch/dispatcher.rb
+++ b/lib/chef/event_dispatch/dispatcher.rb
@@ -9,6 +9,8 @@ class Chef
# the registered subscribers.
class Dispatcher < Base
+ attr_reader :subscribers
+
def initialize(*subscribers)
@subscribers = subscribers
end
@@ -23,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/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb
index 6f5ef627fb..7a3a28b61f 100644
--- a/lib/chef/event_loggers/windows_eventlog.rb
+++ b/lib/chef/event_loggers/windows_eventlog.rb
@@ -18,17 +18,7 @@
require 'chef/event_loggers/base'
require 'chef/platform/query_helpers'
-
-if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
- if defined? Windows::Constants
- [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c|
- # These are redefined in 'win32/eventlog'
- Windows::Constants.send(:remove_const, c) if Windows::Constants.const_defined? c
- end
- end
-
- require 'win32/eventlog'
-end
+require 'chef/win32/eventlog'
class Chef
module EventLoggers
@@ -88,15 +78,21 @@ class Chef
#Exception message: %4
#Exception backtrace: %5
def run_failed(e)
+ data =
+ if @run_status
+ [@run_status.run_id,
+ @run_status.elapsed_time.to_s]
+ else
+ ["UNKNOWN", "UNKNOWN"]
+ end
+
@eventlog.report_event(
:event_type => ::Win32::EventLog::ERROR_TYPE,
:source => SOURCE,
:event_id => RUN_FAILED_EVENT_ID,
- :data => [@run_status.run_id,
- @run_status.elapsed_time.to_s,
- e.class.name,
- e.message,
- e.backtrace.join("\n")]
+ :data => data + [e.class.name,
+ e.message,
+ e.backtrace.join("\n")]
)
end
diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb
index ecd84c5ba5..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,11 +72,17 @@ 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
+ class InvalidKeyAttribute < ArgumentError; end
+ class InvalidUserAttribute < ArgumentError; end
+ class InvalidClientAttribute < ArgumentError; end
class RedirectLimitExceeded < RuntimeError; end
class AmbiguousRunlistSpecification < ArgumentError; end
class CookbookFrozen < ArgumentError; end
class CookbookNotFound < RuntimeError; end
+ class OnlyApiVersion0SupportedForAction < RuntimeError; end
# Cookbook loader used to raise an argument error when cookbook not found.
# for back compat, need to raise an error that inherits from ArgumentError
class CookbookNotFoundInRepo < ArgumentError; end
@@ -90,7 +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
@@ -112,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
@@ -211,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)")
@@ -399,18 +431,18 @@ class Chef
class AuditControlGroupDuplicate < RuntimeError
def initialize(name)
- super "Audit control group with name '#{name}' has already been defined"
+ super "Control group with name '#{name}' has already been defined"
end
end
class AuditNameMissing < RuntimeError; end
class NoAuditsProvided < RuntimeError
def initialize
- super "You must provide a block with audits"
+ super "You must provide a block with controls"
end
end
class AuditsFailed < RuntimeError
def initialize(num_failed, num_total)
- super "Audit phase found failures - #{num_failed}/#{num_total} audits failed"
+ super "Audit phase found failures - #{num_failed}/#{num_total} controls failed"
end
end
@@ -431,7 +463,7 @@ class Chef
wrapped_errors.each_with_index do |e,i|
backtrace << "#{i+1}) #{e.class} - #{e.message}"
backtrace += e.backtrace if e.backtrace
- backtrace << ""
+ backtrace << "" unless i == wrapped_errors.length - 1
end
set_backtrace(backtrace)
end
@@ -442,5 +474,20 @@ class Chef
super "PID file and lockfile are not permitted to match. Specify a different location with --pid or --lockfile"
end
end
+
+ class MultipleDscResourcesFound < RuntimeError
+ attr_reader :resources_found
+ def initialize(resources_found)
+ @resources_found = resources_found
+ matches_info = @resources_found.each do |r|
+ if r['Module'].nil?
+ "Resource #{r['Name']} was found in #{r['Module']['Name']}"
+ else
+ "Resource #{r['Name']} is a binary resource"
+ end
+ end
+ super "Found multiple matching resources. #{matches_info.join("\n")}"
+ end
+ end
end
end
diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb
index 472f30b752..c53d832414 100644
--- a/lib/chef/file_access_control/unix.rb
+++ b/lib/chef/file_access_control/unix.rb
@@ -197,6 +197,8 @@ class Chef
# the user has specified a permission, and it does not match the file, so fix the permission
Chef::Log.debug("found target_mode != current_mode, updating mode")
return true
+ elsif suid_bit_set? and (should_update_group? or should_update_owner?)
+ return true
else
Chef::Log.debug("found target_mode == current_mode, not updating mode")
# the user has specified a permission, but it matches the file, so behave idempotently
@@ -280,6 +282,9 @@ class Chef
return nil
end
+ def suid_bit_set?
+ return target_mode & 04000 > 0
+ end
end
end
end
diff --git a/lib/chef/file_content_management/deploy/mv_windows.rb b/lib/chef/file_content_management/deploy/mv_windows.rb
index 7504123012..0d16da9717 100644
--- a/lib/chef/file_content_management/deploy/mv_windows.rb
+++ b/lib/chef/file_content_management/deploy/mv_windows.rb
@@ -63,12 +63,22 @@ class Chef
raise Chef::Exceptions::WindowsNotAdmin, "can not get the security information for '#{dst}' due to missing Administrator privileges."
end
- if dst_sd.dacl_present?
- apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+ dacl_present = dst_sd.dacl_present?
+ if dacl_present
+ if dst_sd.dacl.nil?
+ apply_dacl = nil
+ else
+ apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+ end
end
- if dst_sd.sacl_present?
- apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+ sacl_present = dst_sd.sacl_present?
+ if sacl_present
+ if dst_sd.sacl.nil?
+ apply_sacl = nil
+ else
+ apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+ end
end
#
@@ -84,8 +94,8 @@ class Chef
dst_so = Security::SecurableObject.new(dst)
dst_so.group = dst_sd.group
dst_so.owner = dst_sd.owner
- dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dst_sd.dacl_present?
- dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if dst_sd.sacl_present?
+ dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dacl_present
+ dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if sacl_present
end
end
diff --git a/lib/chef/formatters/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 489888db8f..614cc44e6d 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -3,9 +3,9 @@ require 'chef/config'
class Chef
module Formatters
- #--
- # TODO: not sold on the name, but the output is similar to what rspec calls
- # "specdoc"
+
+ # Formatter similar to RSpec's documentation formatter. Uses indentation to
+ # show context.
class Doc < Formatters::Base
attr_reader :start_time, :end_time, :successful_audits, :failed_audits
@@ -22,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,12 +43,32 @@ class Chef
def run_completed(node)
@end_time = Time.now
+ # Print out deprecations.
+ if !deprecations.empty?
+ puts_line ""
+ puts_line "Deprecated features used!"
+ deprecations.each do |message, locations|
+ if locations.size == 1
+ puts_line " #{message} at #{locations.size} location:"
+ else
+ puts_line " #{message} at #{locations.size} locations:"
+ end
+ locations.each do |location|
+ prefix = " - "
+ Array(location).each do |line|
+ puts_line "#{prefix}#{line}"
+ prefix = " "
+ end
+ end
+ end
+ puts_line ""
+ end
if Chef::Config[:why_run]
puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated"
else
puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{elapsed_time} seconds"
if total_audits > 0
- puts_line " #{successful_audits}/#{total_audits} Audits succeeded"
+ puts_line " #{successful_audits}/#{total_audits} controls succeeded"
end
end
end
@@ -59,7 +80,7 @@ class Chef
else
puts_line "Chef Client failed. #{@updated_resources} resources updated in #{elapsed_time} seconds"
if total_audits > 0
- puts_line " #{successful_audits} Audits succeeded"
+ puts_line " #{successful_audits} controls succeeded"
end
end
end
@@ -93,6 +114,10 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ def policyfile_loaded(policy)
+ puts_line "Using policy '#{policy["name"]}' at revision '#{policy["revision_id"]}'"
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
puts_line "resolving cookbooks for run list: #{expanded_run_list.inspect}"
@@ -128,9 +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
@@ -175,17 +200,21 @@ class Chef
puts_line "Starting audit phase"
end
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
+ puts_line audit_output
puts_line "Auditing complete"
end
- def audit_phase_failed(error)
+ def audit_phase_failed(error, audit_output)
+ puts_line audit_output
puts_line ""
puts_line "Audit phase exception:"
indent
puts_line "#{error.message}"
- error.backtrace.each do |l|
- puts_line l
+ if error.backtrace
+ error.backtrace.each do |l|
+ puts_line l
+ end
end
end
@@ -228,6 +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
@@ -326,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
@@ -333,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/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
index 652d478b40..05ee3132a7 100644
--- a/lib/chef/formatters/error_inspectors/api_error_formatting.rb
+++ b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+require 'chef/http/authenticator'
+
class Chef
module Formatters
@@ -65,6 +67,24 @@ E
error_description.section("Server Response:",format_rest_error)
end
+ def describe_406_error(error_description, response)
+ if response["x-ops-server-api-version"]
+ version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+ client_api_version = version_header["request_version"]
+ min_server_version = version_header["min_version"]
+ max_server_version = version_header["max_version"]
+
+ error_description.section("Incompatible server API version:",<<-E)
+This version of the API that this Chef request specified is not supported by the Chef server you sent this request to.
+The server supports a min API version of #{min_server_version} and a max API version of #{max_server_version}.
+Chef just made a request with an API version of #{client_api_version}.
+Please either update your Chef client or server to be a compatible set.
+E
+ else
+ describe_http_error(error_description)
+ end
+ end
+
def describe_500_error(error_description)
error_description.section("Unknown Server Error:",<<-E)
The server had a fatal error attempting to load the node data.
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
index 93328adbe3..fe418ed485 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -30,19 +30,32 @@ class Chef
def initialize(path, exception)
@path, @exception = path, exception
+ @backtrace_lines_in_cookbooks = nil
+ @file_lines = nil
+ @culprit_backtrace_entry = nil
+ @culprit_line = nil
end
def add_explanation(error_description)
- case exception
- when Chef::Exceptions::RecipeNotFound
- error_description.section(exception.class.name, exception.message)
- else
- error_description.section(exception.class.name, exception.message)
+ error_description.section(exception.class.name, exception.message)
+ if found_error_in_cookbooks?
traceback = filtered_bt.map {|line| " #{line}"}.join("\n")
error_description.section("Cookbook Trace:", traceback)
error_description.section("Relevant File Content:", context)
end
+
+ if exception_message_modifying_frozen?
+ msg = <<-MESSAGE
+ 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
@@ -93,10 +106,25 @@ class Chef
end
def filtered_bt
- filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/ }
- r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
- Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}")
- return r.count > 0 ? r : exception.backtrace
+ backtrace_lines_in_cookbooks.count > 0 ? backtrace_lines_in_cookbooks : exception.backtrace
+ end
+
+ def found_error_in_cookbooks?
+ !backtrace_lines_in_cookbooks.empty?
+ end
+
+ def backtrace_lines_in_cookbooks
+ @backtrace_lines_in_cookbooks ||=
+ begin
+ filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/i }
+ r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
+ Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}")
+ r
+ end
+ end
+
+ def exception_message_modifying_frozen?
+ exception.message.include?("can't modify frozen")
end
end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
index aa5eb8485d..e011fa9d9b 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
@@ -72,6 +72,8 @@ E
describe_500_error(error_description)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
describe_503_error(error_description)
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
else
describe_http_error(error_description)
end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
index 0cb849a17f..971dbd664e 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
@@ -67,6 +67,8 @@ class Chef
describe_500_error(error_description)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable, Net::HTTPGatewayTimeOut
describe_503_error(error_description)
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
else
describe_http_error(error_description)
end
diff --git a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
index e257ee30c0..d81a9f7cc8 100644
--- a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
@@ -84,6 +84,8 @@ E
describe_500_error(error_description)
when Net::HTTPBadGateway, Net::HTTPServiceUnavailable
describe_503_error(error_description)
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
else
describe_http_error(error_description)
end
diff --git a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
index f31b348278..dbd23f4a52 100644
--- a/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/registration_error_inspector.rb
@@ -9,6 +9,8 @@ class Chef
# TODO: Lots of duplication with the node_load_error_inspector, just
# slightly tweaked to talk about validation keys instead of other keys.
class RegistrationErrorInspector
+ include APIErrorFormatting
+
attr_reader :exception
attr_reader :node_name
attr_reader :config
@@ -94,6 +96,8 @@ E
error_description.section("Relevant Config Settings:",<<-E)
chef_server_url "#{server_url}"
E
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
when Net::HTTPInternalServerError
error_description.section("Unknown Server Error:",<<-E)
The server had a fatal error attempting to load the node data.
diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
index 48572d909b..6e4d9322f9 100644
--- a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
@@ -63,7 +63,7 @@ class Chef
def recipe_snippet
return nil if dynamic_resource?
@snippet ||= begin
- if file = resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/,1] and line = resource.source_line[/^#{file}:([\d]+)/,1].to_i
+ if file = parse_source and line = parse_line(file)
return nil unless ::File.exists?(file)
lines = IO.readlines(file)
@@ -111,6 +111,16 @@ class Chef
line_nr_string + line
end
+ def parse_source
+ resource.source_line[/^(([\w]:)?[^:]+):([\d]+)/,1]
+ end
+
+ def parse_line(source)
+ resource.source_line[/^#{Regexp.escape(source)}:([\d]+)/,1].to_i
+ end
+
+
+
end
end
end
diff --git a/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
index ac19a983af..818228276e 100644
--- a/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb
@@ -98,6 +98,8 @@ E
error_description.section("Possible Causes:",<<-E)
* Your client (#{username}) may have misconfigured authorization permissions.
E
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
when Net::HTTPInternalServerError
error_description.section("Unknown Server Error:",<<-E)
The server had a fatal error attempting to load a role.
diff --git a/lib/chef/formatters/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/default_guard_interpreter.rb b/lib/chef/guard_interpreter/default_guard_interpreter.rb
index df91c2b1ad..fead9886b2 100644
--- a/lib/chef/guard_interpreter/default_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/default_guard_interpreter.rb
@@ -16,6 +16,8 @@
# limitations under the License.
#
+require 'chef/mixin/shell_out'
+
class Chef
class GuardInterpreter
class DefaultGuardInterpreter
diff --git a/lib/chef/guard_interpreter/resource_guard_interpreter.rb b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
index 1e2a534c18..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
@@ -92,8 +95,11 @@ class Chef
raise ArgumentError, "Specified guard interpreter class #{resource_class} must be a kind of Chef::Resource::Execute resource"
end
+ # Duplicate the node below because the new RunContext
+ # overwrites the state of Node instances passed to it.
+ # See https://github.com/chef/chef/issues/3485.
empty_events = Chef::EventDispatch::Dispatcher.new
- anonymous_run_context = Chef::RunContext.new(parent_resource.node, {}, empty_events)
+ anonymous_run_context = Chef::RunContext.new(parent_resource.node.dup, {}, empty_events)
interpreter_resource = resource_class.new('Guard resource', anonymous_run_context)
interpreter_resource.is_guard_interpreter = true
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 5e52337aff..16a826a3db 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -25,6 +25,7 @@ require 'tempfile'
require 'net/https'
require 'uri'
require 'chef/http/basic_client'
+require 'chef/http/socketless_chef_zero_client'
require 'chef/monkey_patches/net_http'
require 'chef/config'
require 'chef/platform/query_helpers'
@@ -196,14 +197,18 @@ class Chef
def http_client(base_url=nil)
base_url ||= url
- BasicClient.new(base_url)
+ if chef_zero_uri?(base_url)
+ SocketlessChefZeroClient.new(base_url)
+ else
+ BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
+ end
end
protected
def create_url(path)
return path if path.is_a?(URI)
- if path =~ /^(http|https):\/\//i
+ if path =~ /^(http|https|chefzero):\/\//i
URI.parse(path)
elsif path.nil? or path.empty?
URI.parse(@url)
@@ -292,7 +297,7 @@ class Chef
http_attempts += 1
response, request, return_value = yield
# handle HTTP 50X Error
- if response.kind_of?(Net::HTTPServerError)
+ if response.kind_of?(Net::HTTPServerError) && !Chef::Config.local_mode
if http_retry_count - http_attempts + 1 > 0
sleep_time = 1 + (2 ** http_attempts) + rand(2 ** http_attempts)
Chef::Log.error("Server returned error #{response.code} for #{url}, retrying #{http_attempts}/#{http_retry_count} in #{sleep_time}s")
@@ -351,6 +356,11 @@ class Chef
private
+ def chef_zero_uri?(uri)
+ uri = URI.parse(uri) unless uri.respond_to?(:scheme)
+ uri.scheme == "chefzero"
+ end
+
def redirected_to(response)
return nil unless response.kind_of?(Net::HTTPRedirection)
# Net::HTTPNotModified is undesired subclass of Net::HTTPRedirection so test for this
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index 4255f18cbd..bffa9c4b3a 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -24,6 +24,8 @@ class Chef
class HTTP
class Authenticator
+ DEFAULT_SERVER_API_VERSION = "1"
+
attr_reader :signing_key_filename
attr_reader :raw_key
attr_reader :attr_names
@@ -37,10 +39,16 @@ class Chef
@signing_key_filename = opts[:signing_key_filename]
@key = load_signing_key(opts[:signing_key_filename], opts[:raw_key])
@auth_credentials = AuthCredentials.new(opts[:client_name], @key)
+ if opts[:api_version]
+ @api_version = opts[:api_version]
+ else
+ @api_version = DEFAULT_SERVER_API_VERSION
+ end
end
def handle_request(method, url, headers={}, data=false)
headers.merge!(authentication_headers(method, url, data)) if sign_requests?
+ headers.merge!({'X-Ops-Server-API-Version' => @api_version})
[method, url, headers, data]
end
diff --git a/lib/chef/http/basic_client.rb b/lib/chef/http/basic_client.rb
index f0f5151dbd..de5e7c03a8 100644
--- a/lib/chef/http/basic_client.rb
+++ b/lib/chef/http/basic_client.rb
@@ -97,15 +97,22 @@ class Chef
#adapted from buildr/lib/buildr/core/transports.rb
def proxy_uri
- proxy = Chef::Config["#{url.scheme}_proxy"]
+ proxy = Chef::Config["#{url.scheme}_proxy"] ||
+ env["#{url.scheme.upcase}_PROXY"] || env["#{url.scheme}_proxy"]
+
# Check if the proxy string contains a scheme. If not, add the url's scheme to the
- # proxy before parsing. The regex /^.*:\/\// matches, for example, http://.
- proxy = if proxy.match(/^.*:\/\//)
- URI.parse(proxy)
- else
- URI.parse("#{url.scheme}://#{proxy}")
- end if String === proxy
- excludes = Chef::Config[:no_proxy].to_s.split(/\s*,\s*/).compact
+ # proxy before parsing. The regex /^.*:\/\// matches, for example, http://. Reusing proxy
+ # here since we are really just trying to get the string built correctly.
+ if String === proxy && !proxy.strip.empty?
+ if proxy.match(/^.*:\/\//)
+ proxy = URI.parse(proxy.strip)
+ else
+ proxy = URI.parse("#{url.scheme}://#{proxy.strip}")
+ end
+ end
+
+ no_proxy = Chef::Config[:no_proxy] || env['NO_PROXY'] || env['no_proxy']
+ excludes = no_proxy.to_s.split(/\s*,\s*/).compact
excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
end
@@ -126,18 +133,32 @@ class Chef
Chef::Config
end
+ def env
+ ENV
+ end
+
def http_client_builder
http_proxy = proxy_uri
if http_proxy.nil?
Net::HTTP
else
Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
- user = Chef::Config["#{url.scheme}_proxy_user"]
- pass = Chef::Config["#{url.scheme}_proxy_pass"]
+ user = http_proxy_user(http_proxy)
+ pass = http_proxy_pass(http_proxy)
Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass)
end
end
+ def http_proxy_user(http_proxy)
+ http_proxy.user || Chef::Config["#{url.scheme}_proxy_user"] ||
+ env["#{url.scheme.upcase}_PROXY_USER"] || env["#{url.scheme}_proxy_user"]
+ end
+
+ def http_proxy_pass(http_proxy)
+ http_proxy.password || Chef::Config["#{url.scheme}_proxy_pass"] ||
+ env["#{url.scheme.upcase}_PROXY_PASS"] || env["#{url.scheme}_proxy_pass"]
+ end
+
def configure_ssl(http_client)
http_client.use_ssl = true
ssl_policy.apply_to(http_client)
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/http/json_input.rb b/lib/chef/http/json_input.rb
index 23ccc3a8a7..3296d8821f 100644
--- a/lib/chef/http/json_input.rb
+++ b/lib/chef/http/json_input.rb
@@ -25,14 +25,19 @@ class Chef
# Middleware that takes json input and turns it into raw text
class JSONInput
+ attr_accessor :opts
+
def initialize(opts={})
+ @opts = opts
end
def handle_request(method, url, headers={}, data=false)
if data && should_encode_as_json?(headers)
headers.delete_if { |key, _value| key.downcase == 'content-type' }
headers["Content-Type"] = 'application/json'
- data = Chef::JSONCompat.to_json(data)
+ json_opts = {}
+ json_opts[:validate_utf8] = opts[:validate_utf8] if opts.has_key?(:validate_utf8)
+ data = Chef::JSONCompat.to_json(data, json_opts)
# Force encoding to binary to fix SSL related EOFErrors
# cf. http://tickets.opscode.com/browse/CHEF-2363
# http://redmine.ruby-lang.org/issues/5233
diff --git a/lib/chef/http/socketless_chef_zero_client.rb b/lib/chef/http/socketless_chef_zero_client.rb
new file mode 100644
index 0000000000..8f5543a16f
--- /dev/null
+++ b/lib/chef/http/socketless_chef_zero_client.rb
@@ -0,0 +1,207 @@
+#--
+# Author:: Daniel DeLeo (<dan@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.
+#
+# ---
+# Some portions of the code in this file are verbatim copies of code from the
+# fakeweb project: https://github.com/chrisk/fakeweb
+#
+# fakeweb is distributed under the MIT license, which is copied below:
+# ---
+#
+# Copyright 2006-2010 Blaine Cook, Chris Kampmeier, and other contributors
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+require 'chef_zero/server'
+
+class Chef
+ class HTTP
+
+ # HTTP Client class that talks directly to Zero via the Rack interface.
+ class SocketlessChefZeroClient
+
+ # This module is extended into Net::HTTP Response objects created from
+ # Socketless Chef Zero responses.
+ module ResponseExts
+
+ # Net::HTTP raises an error if #read_body is called with a block or
+ # file argument after the body has already been read from the network.
+ #
+ # Since we always set the body to the string response from Chef Zero
+ # and set the `@read` indicator variable, we have to patch this method
+ # or else streaming-style responses won't work.
+ def read_body(dest = nil, &block)
+ if dest
+ raise "responses from socketless chef zero can't be written to specific destination"
+ end
+
+ if block_given?
+ block.call(@body)
+ else
+ super
+ end
+ end
+
+ end
+
+ attr_reader :url
+
+ # copied verbatim from webrick (2-clause BSD License)
+ #
+ # HTTP status codes and descriptions
+ STATUS_MESSAGE = {
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Request Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 507 => 'Insufficient Storage',
+ 511 => 'Network Authentication Required',
+ }
+
+ STATUS_MESSAGE.values.each {|v| v.freeze }
+ STATUS_MESSAGE.freeze
+
+ def initialize(base_url)
+ @url = base_url
+ end
+
+ def host
+ @url.hostname
+ end
+
+ def port
+ @url.port
+ end
+
+ def request(method, url, body, headers, &handler_block)
+ request = req_to_rack(method, url, body, headers)
+ res = ChefZero::SocketlessServerMap.request(port, request)
+
+ net_http_response = to_net_http(res[0], res[1], res[2])
+
+ yield net_http_response if block_given?
+
+ [self, net_http_response]
+ end
+
+ def req_to_rack(method, url, body, headers)
+ body_str = body || ""
+ {
+ "SCRIPT_NAME" => "",
+ "SERVER_NAME" => "localhost",
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "PATH_INFO" => url.path,
+ "QUERY_STRING" => url.query,
+ "SERVER_PORT" => url.port,
+ "HTTP_HOST" => "localhost:#{url.port}",
+ "rack.url_scheme" => "chefzero",
+ "rack.input" => StringIO.new(body_str),
+ }
+ end
+
+ def to_net_http(code, headers, chunked_body)
+ body = chunked_body.join('')
+ msg = STATUS_MESSAGE[code] or raise "Cannot determine HTTP status message for code #{code}"
+ response = Net::HTTPResponse.send(:response_class, code.to_s).new("1.0", code.to_s, msg)
+ response.instance_variable_set(:@body, body)
+ headers.each do |name, value|
+ if value.respond_to?(:each)
+ value.each { |v| response.add_field(name, v) }
+ else
+ response[name] = value
+ end
+ end
+
+ response.instance_variable_set(:@read, true)
+ response.extend(ResponseExts)
+ response
+ end
+
+ private
+
+ def headers_extracted_from_options
+ options.reject {|name, _| KNOWN_OPTIONS.include?(name) }.map { |name, value|
+ [name.to_s.split("_").map { |segment| segment.capitalize }.join("-"), value]
+ }
+ end
+
+
+ end
+
+ end
+end
diff --git a/lib/chef/key.rb b/lib/chef/key.rb
new file mode 100644
index 0000000000..be4be7f230
--- /dev/null
+++ b/lib/chef/key.rb
@@ -0,0 +1,271 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/json_compat'
+require 'chef/mixin/params_validate'
+require 'chef/exceptions'
+
+class Chef
+ # Class for interacting with a chef key object. Can be used to create new keys,
+ # save to server, load keys from server, list keys, delete keys, etc.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr [String] actor the name of the client or user that this key is for
+ # @attr [String] name the name of the key
+ # @attr [String] public_key the RSA string of this key
+ # @attr [String] private_key the RSA string of the private key if returned via a POST or PUT
+ # @attr [String] expiration_date the ISO formatted string YYYY-MM-DDTHH:MM:SSZ, i.e. 2020-12-24T21:00:00Z
+ # @attr [String] rest Chef::REST object, initialized and cached via chef_rest method
+ # @attr [string] api_base either "users" or "clients", initialized and cached via api_base method
+ #
+ # @attr_reader [String] actor_field_name must be either 'client' or 'user'
+ class Key
+
+ include Chef::Mixin::ParamsValidate
+
+ attr_reader :actor_field_name
+
+ def initialize(actor, actor_field_name)
+ # Actor that the key is for, either a client or a user.
+ @actor = actor
+
+ unless actor_field_name == "user" || actor_field_name == "client"
+ raise Chef::Exceptions::InvalidKeyArgument, "the second argument to initialize must be either 'user' or 'client'"
+ end
+
+ @actor_field_name = actor_field_name
+
+ @name = nil
+ @public_key = nil
+ @private_key = nil
+ @expiration_date = nil
+ @create_key = nil
+ end
+
+ def chef_rest
+ @rest ||= if @actor_field_name == "user"
+ Chef::REST.new(Chef::Config[:chef_server_root])
+ else
+ Chef::REST.new(Chef::Config[:chef_server_url])
+ end
+ end
+
+ def api_base
+ @api_base ||= if @actor_field_name == "user"
+ "users"
+ else
+ "clients"
+ end
+ end
+
+ def actor(arg=nil)
+ set_or_return(:actor, arg,
+ :regex => /^[a-z0-9\-_]+$/)
+ end
+
+ def name(arg=nil)
+ set_or_return(:name, arg,
+ :kind_of => String)
+ end
+
+ def public_key(arg=nil)
+ raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set the public_key if create_key is true" if !arg.nil? && @create_key
+ set_or_return(:public_key, arg,
+ :kind_of => String)
+ end
+
+ def private_key(arg=nil)
+ set_or_return(:private_key, arg,
+ :kind_of => String)
+ end
+
+ def delete_public_key
+ @public_key = nil
+ end
+
+ def delete_create_key
+ @create_key = nil
+ end
+
+ def create_key(arg=nil)
+ raise Chef::Exceptions::InvalidKeyAttribute, "you cannot set create_key to true if the public_key field exists" if arg == true && !@public_key.nil?
+ set_or_return(:create_key, arg,
+ :kind_of => [TrueClass, FalseClass])
+ end
+
+ def expiration_date(arg=nil)
+ set_or_return(:expiration_date, arg,
+ :regex => /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z|infinity)$/)
+ end
+
+ def to_hash
+ result = {
+ @actor_field_name => @actor
+ }
+ result["name"] = @name if @name
+ result["public_key"] = @public_key if @public_key
+ result["private_key"] = @private_key if @private_key
+ result["expiration_date"] = @expiration_date if @expiration_date
+ result["create_key"] = @create_key if @create_key
+ result
+ end
+
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def create
+ # if public_key is undefined and create_key is false, we cannot create
+ if @public_key.nil? && !@create_key
+ raise Chef::Exceptions::MissingKeyAttribute, "either public_key must be defined or create_key must be true"
+ end
+
+ # defaults the key name to the fingerprint of the key
+ if @name.nil?
+ # if they didn't pass a public_key,
+ #then they must supply a name because we can't generate a fingerprint
+ unless @public_key.nil?
+ @name = fingerprint
+ else
+ raise Chef::Exceptions::MissingKeyAttribute, "a name cannot be auto-generated if no public key passed, either pass a public key or supply a name"
+ end
+ end
+
+ payload = {"name" => @name}
+ payload['public_key'] = @public_key unless @public_key.nil?
+ payload['create_key'] = @create_key if @create_key
+ payload['expiration_date'] = @expiration_date unless @expiration_date.nil?
+ result = chef_rest.post_rest("#{api_base}/#{@actor}/keys", payload)
+ # append the private key to the current key if the server returned one,
+ # since the POST endpoint just returns uri and private_key if needed.
+ new_key = self.to_hash
+ new_key["private_key"] = result["private_key"] if result["private_key"]
+ Chef::Key.from_hash(new_key)
+ end
+
+ def fingerprint
+ self.class.generate_fingerprint(@public_key)
+ end
+
+ # set @name and pass put_name if you wish to update the name of an existing key put_name to @name
+ def update(put_name=nil)
+ if @name.nil? && put_name.nil?
+ raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated or you must pass a name to update when update is called"
+ end
+
+ # If no name was passed, fall back to using @name in the PUT URL, otherwise
+ # use the put_name passed. This will update the a key by the name put_name
+ # to @name.
+ put_name = @name if put_name.nil?
+
+ new_key = chef_rest.put_rest("#{api_base}/#{@actor}/keys/#{put_name}", to_hash)
+ # if the server returned a public_key, remove the create_key field, as we now have a key
+ if new_key["public_key"]
+ self.delete_create_key
+ end
+ Chef::Key.from_hash(self.to_hash.merge(new_key))
+ end
+
+ def save
+ create
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ update
+ else
+ raise e
+ end
+ end
+
+ def destroy
+ if @name.nil?
+ raise Chef::Exceptions::MissingKeyAttribute, "the name field must be populated when delete is called"
+ end
+
+ chef_rest.delete_rest("#{api_base}/#{@actor}/keys/#{@name}")
+ end
+
+ # Class methods
+ def self.from_hash(key_hash)
+ if key_hash.has_key?("user")
+ key = Chef::Key.new(key_hash["user"], "user")
+ elsif key_hash.has_key?("client")
+ key = Chef::Key.new(key_hash["client"], "client")
+ else
+ raise Chef::Exceptions::MissingKeyAttribute, "The hash passed to from_hash does not contain the key 'user' or 'client'. Please pass a hash that defines one of those keys."
+ end
+ key.name key_hash['name'] if key_hash.key?('name')
+ key.public_key key_hash['public_key'] if key_hash.key?('public_key')
+ key.private_key key_hash['private_key'] if key_hash.key?('private_key')
+ key.create_key key_hash['create_key'] if key_hash.key?('create_key')
+ key.expiration_date key_hash['expiration_date'] if key_hash.key?('expiration_date')
+ key
+ end
+
+ def self.from_json(json)
+ Chef::Key.from_hash(Chef::JSONCompat.from_json(json))
+ end
+
+ class << self
+ alias_method :json_create, :from_json
+ end
+
+ def self.list_by_user(actor, inflate=false)
+ keys = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys")
+ self.list(keys, actor, :load_by_user, inflate)
+ end
+
+ def self.list_by_client(actor, inflate=false)
+ keys = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys")
+ self.list(keys, actor, :load_by_client, inflate)
+ end
+
+ def self.load_by_user(actor, key_name)
+ response = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("users/#{actor}/keys/#{key_name}")
+ Chef::Key.from_hash(response.merge({"user" => actor}))
+ end
+
+ def self.load_by_client(actor, key_name)
+ response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("clients/#{actor}/keys/#{key_name}")
+ Chef::Key.from_hash(response.merge({"client" => actor}))
+ end
+
+ def self.generate_fingerprint(public_key)
+ openssl_key_object = OpenSSL::PKey::RSA.new(public_key)
+ data_string = OpenSSL::ASN1::Sequence([
+ OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.n),
+ OpenSSL::ASN1::Integer.new(openssl_key_object.public_key.e)
+ ])
+ OpenSSL::Digest::SHA1.hexdigest(data_string.to_der).scan(/../).join(':')
+ end
+
+ private
+
+ def self.list(keys, actor, load_method_symbol, inflate)
+ if inflate
+ keys.inject({}) do |key_map, result|
+ name = result["name"]
+ key_map[name] = Chef::Key.send(load_method_symbol, actor, name)
+ key_map
+ end
+ else
+ keys
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb
index e13f80b5a8..46e968827e 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -27,6 +27,7 @@ require 'chef/knife/core/subcommand_loader'
require 'chef/knife/core/ui'
require 'chef/local_mode'
require 'chef/rest'
+require 'chef/http/authenticator'
require 'pp'
class Chef
@@ -86,6 +87,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
@@ -120,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] = [] }
@@ -141,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
@@ -205,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
@@ -214,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
@@ -264,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)
@@ -279,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
@@ -358,7 +339,7 @@ class Chef
case Chef::Config[:verbosity]
when 0, nil
- Chef::Config[:log_level] = :error
+ Chef::Config[:log_level] = :warn
when 1
Chef::Config[:log_level] = :info
else
@@ -373,6 +354,9 @@ class Chef
Chef::Config[:environment] = config[:environment] if config[:environment]
Chef::Config.local_mode = config[:local_mode] if config.has_key?(:local_mode)
+
+ Chef::Config.listen = config[:listen] if config.has_key?(:listen)
+
if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
end
@@ -397,6 +381,8 @@ class Chef
end
def configure_chef
+ # knife needs to send logger output to STDERR by default
+ Chef::Config[:log_location] = STDERR
config_loader = self.class.load_config(config[:config_file])
config[:config_file] = config_loader.config_location
@@ -480,6 +466,15 @@ class Chef
when Net::HTTPServiceUnavailable
ui.error "Service temporarily unavailable"
ui.info "Response: #{format_rest_error(response)}"
+ when Net::HTTPNotAcceptable
+ version_header = Chef::JSONCompat.from_json(response["x-ops-server-api-version"])
+ client_api_version = version_header["request_version"]
+ min_server_version = version_header["min_version"]
+ max_server_version = version_header["max_version"]
+ ui.error "The version of Chef that Knife is using is not supported by the Chef server you sent this request to"
+ ui.info "The request that Knife sent was using API version #{client_api_version}"
+ ui.info "The Chef server you sent the request to supports a min API verson of #{min_server_version} and a max API version of #{max_server_version}"
+ ui.info "Please either update your Chef client or server to be a compatible set"
else
ui.error response.message
ui.info "Response: #{format_rest_error(response)}"
@@ -536,6 +531,16 @@ class Chef
self.msg("Deleted #{obj_name}")
end
+ # helper method for testing if a field exists
+ # and returning the usage and proper error if not
+ def test_mandatory_field(field, fieldname)
+ if field.nil?
+ show_usage
+ ui.fatal("You must specify a #{fieldname}")
+ exit 1
+ end
+ end
+
def rest
@rest ||= begin
require 'chef/rest'
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index e168a6bd9b..5b29591fcc 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -21,6 +21,7 @@ require 'chef/knife/data_bag_secret_options'
require 'erubis'
require 'chef/knife/bootstrap/chef_vault_handler'
require 'chef/knife/bootstrap/client_builder'
+require 'chef/util/path_helper'
class Chef
class Knife
@@ -268,7 +269,7 @@ class Chef
bootstrap_files = []
bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb")
bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
- bootstrap_files << File.join(ENV['HOME'], '.chef', 'bootstrap', "#{template}.erb") if ENV['HOME']
+ Chef::Util::PathHelper.home('.chef', 'bootstrap', "#{template}.erb") {|p| bootstrap_files << p}
bootstrap_files << Gem.find_files(File.join("chef","knife","bootstrap","#{template}.erb"))
bootstrap_files.flatten!
@@ -315,6 +316,12 @@ class Chef
# new client-side hawtness, just delete your validation key.
if chef_vault_handler.doing_chef_vault? ||
(Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))
+
+ unless config[:chef_node_name]
+ ui.error("You must pass a node name with -N when bootstrapping with user credentials")
+ exit 1
+ end
+
client_builder.run
chef_vault_handler.run(node_name: config[:chef_node_name])
@@ -377,7 +384,7 @@ class Chef
command = render_template
if config[:use_sudo]
- command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S #{command}" : "sudo #{command}"
+ command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -SH #{command}" : "sudo -H #{command}"
end
command
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..304b06b8b7 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
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 17d7a9e3b5..575aec0f50 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -1,17 +1,18 @@
-bash -c '
+sh -c '
<%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-distro=`uname -s`
-
-if test "x$distro" = "xSunOS"; then
- if test -d "/usr/sfw/bin"; then
- PATH=/usr/sfw/bin:$PATH
- export PATH
- fi
+if test "x$TMPDIR" = "x"; then
+ tmp="/tmp"
+else
+ tmp=$TMPDIR
fi
+# secure-ish temp dir creation without having mktemp available (DDoS-able but not exploitable)
+tmp_dir="$tmp/install.sh.$$"
+(umask 077 && mkdir $tmp_dir) || exit 1
+
exists() {
- if command -v $1 &>/dev/null
+ if command -v $1 >/dev/null 2>&1
then
return 0
else
@@ -19,41 +20,183 @@ exists() {
fi
}
+http_404_error() {
+ echo "ERROR 404: Could not retrieve a valid install.sh!"
+ exit 1
+}
+
+capture_tmp_stderr() {
+ # spool up /tmp/stderr from all the commands we called
+ if test -f "$tmp_dir/stderr"; then
+ output=`cat $tmp_dir/stderr`
+ stderr_results="${stderr_results}\nSTDERR from $1:\n\n$output\n"
+ rm $tmp_dir/stderr
+ fi
+}
+
+# do_wget URL FILENAME
+do_wget() {
+ echo "trying wget..."
+ wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> -O "$2" "$1" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "ERROR 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "wget"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_curl URL FILENAME
+do_curl() {
+ echo "trying curl..."
+ curl -sL <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> -D $tmp_dir/stderr -o "$2" "$1" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "curl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_fetch URL FILENAME
+do_fetch() {
+ echo "trying fetch..."
+ fetch -o "$2" "$1" 2>$tmp_dir/stderr
+ # check for bad return status
+ test $? -ne 0 && return 1
+ return 0
+}
+
+# do_perl URL FILENAME
+do_perl() {
+ echo "trying perl..."
+ perl -e "use LWP::Simple; getprint(shift @ARGV);" "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "perl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_python URL FILENAME
+do_python() {
+ echo "trying python..."
+ python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "HTTP Error 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "python"
+ return 1
+ fi
+ return 0
+}
+
+# do_download URL FILENAME
+do_download() {
+ PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sfw/bin:/sbin:/bin:/usr/sbin:/usr/bin
+ export PATH
+
+ echo "downloading $1"
+ echo " to file $2"
+
+ # we try all of these until we get success.
+ # perl, in particular may be present but LWP::Simple may not be installed
+
+ if exists wget; then
+ do_wget $1 $2 && return 0
+ fi
+
+ if exists curl; then
+ do_curl $1 $2 && return 0
+ fi
+
+ if exists fetch; then
+ do_fetch $1 $2 && return 0
+ fi
+
+ if exists perl; then
+ do_perl $1 $2 && return 0
+ fi
+
+ if exists python; then
+ do_python $1 $2 && return 0
+ fi
+
+ echo ">>>>>> wget, curl, fetch, perl, or python not found on this instance."
+
+ if test "x$stderr_results" != "x"; then
+ echo "\nDEBUG OUTPUT FOLLOWS:\n$stderr_results"
+ fi
+
+ return 16
+}
+
<% if knife_config[:bootstrap_install_command] %>
<%= knife_config[:bootstrap_install_command] %>
<% else %>
- install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.chef.io/chef/install.sh" %>"
- if ! exists /usr/bin/chef-client; then
- echo "Installing Chef Client..."
- if exists wget; then
- bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> ${install_sh} -O -) <%= latest_current_chef_version_string %>
- elif exists curl; then
- bash <(curl -L <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> ${install_sh}) <%= latest_current_chef_version_string %>
- else
- echo "Neither wget nor curl found. Please install one and try again." >&2
- exit 1
- fi
+ install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>"
+ if test -f /usr/bin/chef-client; then
+ echo "-----> Existing Chef installation detected"
+ else
+ echo "-----> Installing Chef Omnibus (<%= latest_current_chef_version_string %>)"
+ do_download ${install_sh} $tmp_dir/install.sh
+ sh $tmp_dir/install.sh -P chef <%= latest_current_chef_version_string %>
fi
<% end %>
+if test "x$tmp_dir" != "x"; then
+ rm -r "$tmp_dir"
+fi
+
mkdir -p /etc/chef
<% if client_pem -%>
-cat > /etc/chef/client.pem <<'EOP'
+cat > /etc/chef/client.pem <<EOP
<%= ::File.read(::File.expand_path(client_pem)) %>
EOP
chmod 0600 /etc/chef/client.pem
<% end -%>
<% if validation_key -%>
-cat > /etc/chef/validation.pem <<'EOP'
+cat > /etc/chef/validation.pem <<EOP
<%= validation_key %>
EOP
chmod 0600 /etc/chef/validation.pem
<% end -%>
<% if encrypted_data_bag_secret -%>
-cat > /etc/chef/encrypted_data_bag_secret <<'EOP'
+cat > /etc/chef/encrypted_data_bag_secret <<EOP
<%= encrypted_data_bag_secret %>
EOP
chmod 0600 /etc/chef/encrypted_data_bag_secret
@@ -69,20 +212,20 @@ mkdir -p /etc/chef/trusted_certs
mkdir -p /etc/chef/ohai/hints
<% @chef_config[:knife][:hints].each do |name, hash| -%>
-cat > /etc/chef/ohai/hints/<%= name %>.json <<'EOP'
+cat > /etc/chef/ohai/hints/<%= name %>.json <<EOP
<%= Chef::JSONCompat.to_json(hash) %>
EOP
<% end -%>
<% end -%>
-cat > /etc/chef/client.rb <<'EOP'
+cat > /etc/chef/client.rb <<EOP
<%= config_content %>
EOP
-cat > /etc/chef/first-boot.json <<'EOP'
+cat > /etc/chef/first-boot.json <<EOP
<%= Chef::JSONCompat.to_json(first_boot) %>
EOP
-echo "Starting first Chef Client run..."
+echo "Starting the first Chef Client run..."
<%= start_chef %>'
diff --git a/lib/chef/knife/client_bulk_delete.rb b/lib/chef/knife/client_bulk_delete.rb
index 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 477a400e8a..fa9a1a7e32 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -23,63 +23,87 @@ class Chef
class ClientCreate < Knife
deps do
- require 'chef/api_client'
+ require 'chef/api_client_v1'
require 'chef/json_compat'
end
option :file,
- :short => "-f FILE",
- :long => "--file FILE",
- :description => "Write the key to a file"
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file if the server generated one."
option :admin,
- :short => "-a",
- :long => "--admin",
- :description => "Create the client as an admin",
- :boolean => true
+ :short => "-a",
+ :long => "--admin",
+ :description => "Open Source Chef 11 only. Create the client as an admin.",
+ :boolean => true
option :validator,
- :long => "--validator",
- :description => "Create the client as a validator",
- :boolean => true
+ :long => "--validator",
+ :description => "Create the client as a validator.",
+ :boolean => true
- banner "knife client create CLIENT (options)"
+ option :public_key,
+ :short => "-p FILE",
+ :long => "--public-key",
+ :description => "Set the initial default key for the client from a file on disk (cannot pass with --prevent-keygen)."
+
+ option :prevent_keygen,
+ :short => "-k",
+ :long => "--prevent-keygen",
+ :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --public-key.",
+ :boolean => true
+
+ banner "knife client create CLIENTNAME (options)"
+
+ def client
+ @client_field ||= Chef::ApiClientV1.new
+ end
+
+ def create_client(client)
+ # should not be using save :( bad behavior
+ Chef::ApiClientV1.from_hash(client).save
+ end
def run
- @client_name = @name_args[0]
+ test_mandatory_field(@name_args[0], "client name")
+ client.name @name_args[0]
- if @client_name.nil?
+ if config[:public_key] && config[:prevent_keygen]
show_usage
- ui.fatal("You must specify a client name")
+ ui.fatal("You cannot pass --public-key and --prevent-keygen")
exit 1
end
- client_hash = {
- "name" => @client_name,
- "admin" => !!config[:admin],
- "validator" => !!config[:validator]
- }
+ if !config[:prevent_keygen] && !config[:public_key]
+ client.create_key(true)
+ end
+
+ if config[:admin]
+ client.admin(true)
+ end
- output = Chef::ApiClient.from_hash(edit_hash(client_hash))
+ if config[:validator]
+ client.validator(true)
+ end
- # Chef::ApiClient.save will try to create a client and if it
- # exists will update it instead silently.
- client = output.save
+ if config[:public_key]
+ client.public_key File.read(File.expand_path(config[:public_key]))
+ end
- # We only get a private_key on client creation, not on client update.
- if client['private_key']
- ui.info("Created #{output}")
+ output = edit_data(client)
+ final_client = create_client(output)
+ ui.info("Created #{final_client}")
+ # output private_key if one
+ if final_client.private_key
if config[:file]
File.open(config[:file], "w") do |f|
- f.print(client['private_key'])
+ f.print(final_client.private_key)
end
else
- puts client['private_key']
+ puts final_client.private_key
end
- else
- ui.error "Client '#{client['name']}' already exists"
- exit 1
end
end
end
diff --git a/lib/chef/knife/client_delete.rb b/lib/chef/knife/client_delete.rb
index 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_key_create.rb b/lib/chef/knife/client_key_create.rb
new file mode 100644
index 0000000000..3b7e97eb24
--- /dev/null
+++ b/lib/chef/knife/client_key_create.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/key_create_base'
+
+class Chef
+ class Knife
+ # Implements knife user key create using Chef::Knife::KeyCreate
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyCreate < Knife
+ include Chef::Knife::KeyCreateBase
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'client'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyCreate.new(@actor, actor_field_name, ui, config)
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/client_key_delete.rb b/lib/chef/knife/client_key_delete.rb
new file mode 100644
index 0000000000..8ecdfe1ec8
--- /dev/null
+++ b/lib/chef/knife/client_key_delete.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+class Chef
+ class Knife
+ # Implements knife client key delete using Chef::Knife::KeyDelete
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyDelete < Knife
+ banner "knife client key delete CLIENT KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'client'
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/client_key_edit.rb b/lib/chef/knife/client_key_edit.rb
new file mode 100644
index 0000000000..1de45f4ca2
--- /dev/null
+++ b/lib/chef/knife/client_key_edit.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/key_edit_base'
+
+class Chef
+ class Knife
+ # Implements knife client key edit using Chef::Knife::KeyEdit
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyEdit < Knife
+ include Chef::Knife::KeyEditBase
+
+ banner 'knife client key edit CLIENT KEYNAME (options)'
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'client'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config)
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/knife/client_key_list.rb b/lib/chef/knife/client_key_list.rb
new file mode 100644
index 0000000000..f6f29ae03f
--- /dev/null
+++ b/lib/chef/knife/client_key_list.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/key_list_base'
+
+class Chef
+ class Knife
+ # Implements knife user key list using Chef::Knife::KeyList
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyList < Knife
+ include Chef::Knife::KeyListBase
+
+ banner "knife client key list CLIENT (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def list_method
+ :list_by_client
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/client_key_show.rb b/lib/chef/knife/client_key_show.rb
new file mode 100644
index 0000000000..c39a279000
--- /dev/null
+++ b/lib/chef/knife/client_key_show.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+class Chef
+ class Knife
+ # Implements knife client key show using Chef::Knife::KeyShow
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class ClientKeyShow < Knife
+ banner "knife client key show CLIENT KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def load_method
+ :load_by_client
+ end
+
+ def actor_missing_error
+ 'You must specify a client name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/client_list.rb b/lib/chef/knife/client_list.rb
index 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/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/generic_presenter.rb b/lib/chef/knife/core/generic_presenter.rb
index f3ea0f0d6c..2df9603faa 100644
--- a/lib/chef/knife/core/generic_presenter.rb
+++ b/lib/chef/knife/core/generic_presenter.rb
@@ -181,7 +181,7 @@ class Chef
# Must check :[] before attr because spec can include
# `keys` - want the key named `keys`, not a list of
# available keys.
- elsif data.respond_to?(:[])
+ elsif data.respond_to?(:[]) && data.has_key?(attr)
data = data[attr]
elsif data.respond_to?(attr.to_sym)
data = data.send(attr.to_sym)
diff --git a/lib/chef/knife/core/hashed_command_loader.rb b/lib/chef/knife/core/hashed_command_loader.rb
new file mode 100644
index 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/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb
index 3298d5e4ac..9cf839d3a6 100644
--- a/lib/chef/knife/core/status_presenter.rb
+++ b/lib/chef/knife/core/status_presenter.rb
@@ -66,16 +66,16 @@ class Chef
list.each do |node|
result = {}
- result["name"] = node.name
- result["chef_environment"] = node.chef_environment
- ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress]
- fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn]
+ result["name"] = node["name"] || node.name
+ result["chef_environment"] = node["chef_environment"]
+ ip = (node["ec2"] && node["ec2"]["public_ipv4"]) || node["ipaddress"]
+ fqdn = (node["ec2"] && node["ec2"]["public_hostname"]) || node["fqdn"]
result["ip"] = ip if ip
result["fqdn"] = fqdn if fqdn
- result["run_list"] = node.run_list if config[:run_list]
- result["ohai_time"] = node[:ohai_time]
- result["platform"] = node[:platform] if node[:platform]
- result["platform_version"] = node[:platform_version] if node[:platform_version]
+ result["run_list"] = node.run_list if config["run_list"]
+ result["ohai_time"] = node["ohai_time"]
+ result["platform"] = node["platform"] if node["platform"]
+ result["platform_version"] = node["platform_version"] if node["platform_version"]
if config[:long_output]
result["default"] = node.default_attrs
@@ -99,11 +99,12 @@ class Chef
# special case ec2 with their split horizon whatsis.
ip = (node[:ec2] && node[:ec2][:public_ipv4]) || node[:ipaddress]
fqdn = (node[:ec2] && node[:ec2][:public_hostname]) || node[:fqdn]
+ name = node['name'] || node.name
- hours, minutes, seconds = time_difference_in_hms(node["ohai_time"])
+ hours, minutes, _ = time_difference_in_hms(node["ohai_time"])
hours_text = "#{hours} hour#{hours == 1 ? ' ' : 's'}"
minutes_text = "#{minutes} minute#{minutes == 1 ? ' ' : 's'}"
- run_list = "#{node.run_list}" if config[:run_list]
+ run_list = "#{node['run_list']}" if config[:run_list]
if hours > 24
color = :red
text = hours_text
@@ -116,7 +117,7 @@ class Chef
end
line_parts = Array.new
- line_parts << @ui.color(text, color) + ' ago' << node.name
+ line_parts << @ui.color(text, color) + ' ago' << name
line_parts << fqdn if fqdn
line_parts << ip if ip
line_parts << run_list if run_list
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index f9b8f5008e..808e053c40 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -18,97 +18,120 @@
require 'chef/version'
require 'chef/util/path_helper'
+require 'chef/knife/core/gem_glob_loader'
+require 'chef/knife/core/hashed_command_loader'
+require 'chef/knife/core/custom_manifest_loader'
+
class Chef
class Knife
+ #
+ # Public Methods of a Subcommand Loader
+ #
+ # load_commands - loads all available subcommands
+ # load_command(args) - loads subcommands for the given args
+ # list_commands(args) - lists all available subcommands,
+ # optionally filtering by category
+ # subcommand_files - returns an array of all subcommand files
+ # that could be loaded
+ # commnad_class_from(args) - returns the subcommand class for the
+ # user-requested command
+ #
class SubcommandLoader
-
- MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
- MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}/}.freeze
-
attr_reader :chef_config_dir
attr_reader :env
- def initialize(chef_config_dir, env=ENV)
- @chef_config_dir, @env = chef_config_dir, env
- @forced_activate = {}
+ # 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
- # Load all the sub-commands
- def load_commands
- subcommand_files.each { |subcommand| Kernel.load subcommand }
- true
+ def self.plugin_manifest?
+ plugin_manifest_path && File.exist?(plugin_manifest_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 self.autogenerated_manifest?
+ plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY)
+ 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)))
+ 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
+
+ # 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.")
end
+ end
- # finally search ~/.chef/plugins/knife/*.rb
- user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(env['HOME'], '.chef', 'plugins', 'knife'), '*.rb')) if env['HOME']
+ # Load all the sub-commands
+ def load_commands
+ return true if @loaded
+ subcommand_files.each { |subcommand| Kernel.load subcommand }
+ @loaded = true
+ end
- user_specific_files
+ def force_load
+ @loaded=false
+ load_commands
+ end
+
+ 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')]
@@ -120,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?
- ENV["HOME"] && File.exist?(plugin_manifest_path)
- end
-
- def plugin_manifest
- Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
- end
-
- def plugin_manifest_path
- File.join(ENV['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/exec.rb b/lib/chef/knife/exec.rb
index 3e8196c616..ace4ee2300 100644
--- a/lib/chef/knife/exec.rb
+++ b/lib/chef/knife/exec.rb
@@ -17,6 +17,7 @@
#
require 'chef/knife'
+require 'chef/util/path_helper'
class Chef::Knife::Exec < Chef::Knife
@@ -42,7 +43,7 @@ class Chef::Knife::Exec < Chef::Knife
# Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts
config[:script_path] << File.join(Chef::Knife.chef_config_dir, 'scripts') if Chef::Knife.chef_config_dir
- config[:script_path] << File.join(ENV['HOME'], '.chef', 'scripts') if ENV['HOME']
+ Chef::Util::PathHelper.home('.chef', 'scripts') { |p| config[:script_path] << p }
scripts = Array(name_args)
context = Object.new
diff --git a/lib/chef/knife/key_create.rb b/lib/chef/knife/key_create.rb
new file mode 100644
index 0000000000..5ee36e9793
--- /dev/null
+++ b/lib/chef/knife/key_create.rb
@@ -0,0 +1,108 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+ class Knife
+ # Service class for UserKeyCreate and ClientKeyCreate,
+ # Implements common functionality of knife [user | org client] key create.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyCreate and ClientKeyCreate for what could populate it
+ class KeyCreate
+
+ attr_accessor :config
+
+ def initialize(actor, actor_field_name, ui, config)
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ @config = config
+ end
+
+ def public_key_or_key_name_error_msg
+<<EOS
+You must pass either --public-key or --key-name, or both.
+If you only pass --public-key, a key name will be generated from the fingerprint of your key.
+If you only pass --key-name, a key pair will be generated by the server.
+EOS
+ end
+
+ def edit_data(key)
+ @ui.edit_data(key)
+ end
+
+ def display_info(input)
+ @ui.info(input)
+ end
+
+ def display_private_key(private_key)
+ @ui.msg(private_key)
+ end
+
+ def output_private_key_to_file(private_key)
+ File.open(@config[:file], "w") do |f|
+ f.print(private_key)
+ end
+ end
+
+ def create_key_from_hash(output)
+ Chef::Key.from_hash(output).create
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ if !@config[:public_key] && !@config[:key_name]
+ raise Chef::Exceptions::KeyCommandInputError, public_key_or_key_name_error_msg
+ elsif !@config[:public_key]
+ key.create_key(true)
+ end
+
+ if @config[:public_key]
+ key.public_key(File.read(File.expand_path(@config[:public_key])))
+ end
+
+ if @config[:key_name]
+ key.name(@config[:key_name])
+ end
+
+ if @config[:expiration_date]
+ key.expiration_date(@config[:expiration_date])
+ else
+ key.expiration_date("infinity")
+ end
+
+ output = edit_data(key)
+ key = create_key_from_hash(output)
+
+ display_info("Created key: #{key.name}")
+ if key.private_key
+ if @config[:file]
+ output_private_key_to_file(key.private_key)
+ else
+ display_private_key(key.private_key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_create_base.rb b/lib/chef/knife/key_create_base.rb
new file mode 100644
index 0000000000..da31f70d1d
--- /dev/null
+++ b/lib/chef/knife/key_create_base.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Knife
+ # Extendable module that class_eval's common options into UserKeyCreate and ClientKeyCreate
+ #
+ # @author Tyler Cloke
+ module KeyCreateBase
+ def self.included(includer)
+ includer.class_eval do
+ option :public_key,
+ :short => "-p FILENAME",
+ :long => "--public-key FILENAME",
+ :description => "Public key for newly created key. If not passed, the server will create a key pair for you, but you must pass --key-name NAME in that case."
+
+ option :file,
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file, if you requested the server to create one."
+
+ option :key_name,
+ :short => "-k NAME",
+ :long => "--key-name NAME",
+ :description => "The name for your key. If you do not pass a name, you must pass --public-key, and the name will default to the fingerprint of the public key passed."
+
+ option :expiration_date,
+ :short => "-e DATE",
+ :long => "--expiration-date DATE",
+ :description => "Optionally pass the expiration date for the key in ISO 8601 fomatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z. Defaults to infinity if not passed. UTC timezone assumed."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_delete.rb b/lib/chef/knife/key_delete.rb
new file mode 100644
index 0000000000..fb996cff17
--- /dev/null
+++ b/lib/chef/knife/key_delete.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/key'
+
+class Chef
+ class Knife
+ # Service class for UserKeyDelete and ClientKeyDelete, used to delete keys.
+ # Implements common functionality of knife [user | org client] key delete.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyDelete and ClientKeyDelete for what could populate it
+ class KeyDelete
+ def initialize(name, actor, actor_field_name, ui)
+ @name = name
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ end
+
+ def confirm!
+ @ui.confirm("Do you really want to delete the key named #{@name} for the #{@actor_field_name} named #{@actor}")
+ end
+
+ def print_destroyed
+ @ui.info("Destroyed key named #{@name} for the #{@actor_field_name} named #{@actor}")
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ key.name(@name)
+ confirm!
+ key.destroy
+ print_destroyed
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/key_edit.rb b/lib/chef/knife/key_edit.rb
new file mode 100644
index 0000000000..48ae344e6e
--- /dev/null
+++ b/lib/chef/knife/key_edit.rb
@@ -0,0 +1,114 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+ class Knife
+ # Service class for UserKeyEdit and ClientKeyEdit,
+ # Implements common functionality of knife [user | org client] key edit.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyEdit and ClientKeyEdit for what could populate it
+ class KeyEdit
+
+ attr_accessor :config
+
+ def initialize(original_name, actor, actor_field_name, ui, config)
+ @original_name = original_name
+ @actor = actor
+ @actor_field_name = actor_field_name
+ @ui = ui
+ @config = config
+ end
+
+ def public_key_and_create_key_error_msg
+<<EOS
+You passed both --public-key and --create-key. Only pass one, or the other, or neither.
+Do not pass either if you do not want to change the public_key field of your key.
+Pass --public-key if you want to update the public_key field of your key from a specific public key.
+Pass --create-key if you want the server to generate a new key and use that to update the public_key field of your key.
+EOS
+ end
+
+ def edit_data(key)
+ @ui.edit_data(key)
+ end
+
+ def display_info(input)
+ @ui.info(input)
+ end
+
+ def display_private_key(private_key)
+ @ui.msg(private_key)
+ end
+
+ def output_private_key_to_file(private_key)
+ File.open(@config[:file], "w") do |f|
+ f.print(private_key)
+ end
+ end
+
+ def update_key_from_hash(output)
+ Chef::Key.from_hash(output).update(@original_name)
+ end
+
+ def run
+ key = Chef::Key.new(@actor, @actor_field_name)
+ if @config[:public_key] && @config[:create_key]
+ raise Chef::Exceptions::KeyCommandInputError, public_key_and_create_key_error_msg
+ end
+
+ if @config[:create_key]
+ key.create_key(true)
+ end
+
+ if @config[:public_key]
+ key.public_key(File.read(File.expand_path(@config[:public_key])))
+ end
+
+ if @config[:key_name]
+ key.name(@config[:key_name])
+ else
+ key.name(@original_name)
+ end
+
+ if @config[:expiration_date]
+ key.expiration_date(@config[:expiration_date])
+ end
+
+ output = edit_data(key)
+ key = update_key_from_hash(output)
+
+ to_display = "Updated key: #{key.name}"
+ to_display << " (formally #{@original_name})" if key.name != @original_name
+ display_info(to_display)
+ if key.private_key
+ if @config[:file]
+ output_private_key_to_file(key.private_key)
+ else
+ display_private_key(key.private_key)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_edit_base.rb b/lib/chef/knife/key_edit_base.rb
new file mode 100644
index 0000000000..bb5a951a5b
--- /dev/null
+++ b/lib/chef/knife/key_edit_base.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Knife
+ # Extendable module that class_eval's common options into UserKeyEdit and ClientKeyEdit
+ #
+ # @author Tyler Cloke
+ module KeyEditBase
+ def self.included(includer)
+ includer.class_eval do
+ option :public_key,
+ :short => "-p FILENAME",
+ :long => "--public-key FILENAME",
+ :description => "Replace the public_key field from a file on disk. If not passed, the public_key field will not change."
+
+ option :create_key,
+ :short => "-c",
+ :long => "--create-key",
+ :description => "Replace the public_key field with a key generated by the server. The private key will be returned."
+
+ option :file,
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file, if you requested the server to create one via --create-key."
+
+ option :key_name,
+ :short => "-k NAME",
+ :long => "--key-name NAME",
+ :description => "The new name for your key. Pass if you wish to update the name field of your key."
+
+ option :expiration_date,
+ :short => "-e DATE",
+ :long => "--expiration-date DATE",
+ :description => "Updates the expiration_date field of your key if passed. Pass in ISO 8601 fomatted string: YYYY-MM-DDTHH:MM:SSZ e.g. 2013-12-24T21:00:00Z or infinity. UTC timezone assumed."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_list.rb b/lib/chef/knife/key_list.rb
new file mode 100644
index 0000000000..e96a27161f
--- /dev/null
+++ b/lib/chef/knife/key_list.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+ class Knife
+ # Service class for UserKeyList and ClientKeyList, used to list keys.
+ # Implements common functionality of knife [user | org client] key list.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyList and ClientKeyList for what could populate it
+ class KeyList
+
+ attr_accessor :config
+
+ def initialize(actor, list_method, ui, config)
+ @actor = actor
+ @list_method = list_method
+ @ui = ui
+ @config = config
+ end
+
+ def expired_and_non_expired_msg
+<<EOS
+You cannot pass both --only-expired and --only-non-expired.
+Please pass one or none.
+EOS
+ end
+
+ def display_info(string)
+ @ui.output(string)
+ end
+
+ def colorize(string)
+ @ui.color(string, :cyan)
+ end
+
+ def run
+ if @config[:only_expired] && @config[:only_non_expired]
+ raise Chef::Exceptions::KeyCommandInputError, expired_and_non_expired_msg
+ end
+
+ # call proper list function
+ keys = Chef::Key.send(@list_method, @actor)
+ if @config[:with_details]
+ max_length = 0
+ keys.each do |key|
+ key['name'] = key['name'] + ":"
+ max_length = key['name'].length if key['name'].length > max_length
+ end
+ keys.each do |key|
+ next if !key['expired'] && @config[:only_expired]
+ next if key['expired'] && @config[:only_non_expired]
+ display = "#{colorize(key['name'].ljust(max_length))} #{key['uri']}"
+ display = "#{display} (expired)" if key["expired"]
+ display_info(display)
+ end
+ else
+ keys.each do |key|
+ next if !key['expired'] && @config[:only_expired]
+ next if key['expired'] && @config[:only_non_expired]
+ display_info(key['name'])
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/key_list_base.rb b/lib/chef/knife/key_list_base.rb
new file mode 100644
index 0000000000..861db0d75a
--- /dev/null
+++ b/lib/chef/knife/key_list_base.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ class Knife
+ # Extendable module that class_eval's common options into UserKeyList and ClientKeyList
+ #
+ # @author Tyler Cloke
+ module KeyListBase
+ def self.included(includer)
+ includer.class_eval do
+ option :with_details,
+ :short => "-w",
+ :long => "--with-details",
+ :description => "Show corresponding URIs and whether the key has expired or not."
+
+ option :only_expired,
+ :short => "-e",
+ :long => "--only-expired",
+ :description => "Only show expired keys."
+
+ option :only_non_expired,
+ :short => "-n",
+ :long => "--only-non-expired",
+ :description => "Only show non-expired keys."
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/key_show.rb b/lib/chef/knife/key_show.rb
new file mode 100644
index 0000000000..522f7a1a77
--- /dev/null
+++ b/lib/chef/knife/key_show.rb
@@ -0,0 +1,53 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/key'
+require 'chef/json_compat'
+require 'chef/exceptions'
+
+class Chef
+ class Knife
+ # Service class for UserKeyShow and ClientKeyShow, used to show keys.
+ # Implements common functionality of knife [user | org client] key show.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_accessor [Hash] cli input, see UserKeyShow and ClientKeyShow for what could populate it
+ class KeyShow
+
+ attr_accessor :config
+
+ def initialize(name, actor, load_method, ui)
+ @name = name
+ @actor = actor
+ @load_method = load_method
+ @ui = ui
+ end
+
+ def display_output(key)
+ @ui.output(@ui.format_for_display(key))
+ end
+
+ def run
+ key = Chef::Key.send(@load_method, @actor, @name)
+ key.public_key(key.public_key.strip)
+ display_output(key)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/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
new file mode 100644
index 0000000000..6c3415473f
--- /dev/null
+++ b/lib/chef/knife/osc_user_create.rb
@@ -0,0 +1,97 @@
+#
+# 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 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_create.rb.
+class Chef
+ class Knife
+ class OscUserCreate < Knife
+
+ deps do
+ require 'chef/user'
+ require 'chef/json_compat'
+ end
+
+ option :file,
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file"
+
+ option :admin,
+ :short => "-a",
+ :long => "--admin",
+ :description => "Create the user as an admin",
+ :boolean => true
+
+ option :user_password,
+ :short => "-p PASSWORD",
+ :long => "--password PASSWORD",
+ :description => "Password for newly created user",
+ :default => ""
+
+ option :user_key,
+ :long => "--user-key FILENAME",
+ :description => "Public key for newly created user. By default a key will be created for you."
+
+ banner "knife osc_user create USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ if config[:user_password].length == 0
+ show_usage
+ ui.fatal("You must specify a non-blank password")
+ exit 1
+ end
+
+ user = Chef::User.new
+ user.name(@user_name)
+ user.admin(config[:admin])
+ user.password config[:user_password]
+
+ if config[:user_key]
+ user.public_key File.read(File.expand_path(config[:user_key]))
+ end
+
+ output = edit_data(user)
+ user = Chef::User.from_hash(output).create
+
+ ui.info("Created #{user}")
+ if user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(user.private_key)
+ end
+ else
+ ui.msg user.private_key
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_delete.rb b/lib/chef/knife/osc_user_delete.rb
new file mode 100644
index 0000000000..5cd4f10413
--- /dev/null
+++ b/lib/chef/knife/osc_user_delete.rb
@@ -0,0 +1,51 @@
+#
+# 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 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in the user_delete.rb.
+
+class Chef
+ class Knife
+ class OscUserDelete < Knife
+
+ deps do
+ require 'chef/user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user delete USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ delete_object(Chef::User, @user_name)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_edit.rb b/lib/chef/knife/osc_user_edit.rb
new file mode 100644
index 0000000000..526475db05
--- /dev/null
+++ b/lib/chef/knife/osc_user_edit.rb
@@ -0,0 +1,58 @@
+#
+# 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 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_edit.rb.
+
+class Chef
+ class Knife
+ class OscUserEdit < Knife
+
+ deps do
+ require 'chef/user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user edit USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ original_user = Chef::User.load(@user_name).to_hash
+ edited_user = edit_data(original_user)
+ if original_user != edited_user
+ user = Chef::User.from_hash(edited_user)
+ user.update
+ ui.msg("Saved #{user}.")
+ else
+ ui.msg("User unchaged, not saving.")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_list.rb b/lib/chef/knife/osc_user_list.rb
new file mode 100644
index 0000000000..84fca31899
--- /dev/null
+++ b/lib/chef/knife/osc_user_list.rb
@@ -0,0 +1,47 @@
+#
+# 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 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_list.rb.
+
+class Chef
+ class Knife
+ class OscUserList < Knife
+
+ deps do
+ require 'chef/user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user list (options)"
+
+ option :with_uri,
+ :short => "-w",
+ :long => "--with-uri",
+ :description => "Show corresponding URIs"
+
+ def run
+ output(format_list_for_display(Chef::User.list))
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_reregister.rb b/lib/chef/knife/osc_user_reregister.rb
new file mode 100644
index 0000000000..163b286fe0
--- /dev/null
+++ b/lib/chef/knife/osc_user_reregister.rb
@@ -0,0 +1,64 @@
+#
+# 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 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_reregister.rb.
+
+class Chef
+ class Knife
+ class OscUserReregister < Knife
+
+ deps do
+ require 'chef/user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user reregister USER (options)"
+
+ option :file,
+ :short => "-f FILE",
+ :long => "--file FILE",
+ :description => "Write the private key to a file"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ user = Chef::User.load(@user_name).reregister
+ Chef::Log.debug("Updated user data: #{user.inspect}")
+ key = user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(key)
+ end
+ else
+ ui.msg key
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/osc_user_show.rb b/lib/chef/knife/osc_user_show.rb
new file mode 100644
index 0000000000..cb3a77585a
--- /dev/null
+++ b/lib/chef/knife/osc_user_show.rb
@@ -0,0 +1,54 @@
+#
+# Author:: Steven Danna (<steve@opscode.com>)
+# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_show.rb.
+
+class Chef
+ class Knife
+ class OscUserShow < Knife
+
+ include Knife::Core::MultiAttributeReturnOption
+
+ deps do
+ require 'chef/user'
+ require 'chef/json_compat'
+ end
+
+ banner "knife osc_user show USER (options)"
+
+ def run
+ @user_name = @name_args[0]
+
+ if @user_name.nil?
+ show_usage
+ ui.fatal("You must specify a user name")
+ exit 1
+ end
+
+ user = Chef::User.load(@user_name)
+ output(format_for_display(user))
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/knife/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/search.rb b/lib/chef/knife/search.rb
index a6cec761ce..9319d30e7d 100644
--- a/lib/chef/knife/search.rb
+++ b/lib/chef/knife/search.rb
@@ -106,8 +106,7 @@ class Chef
formatted_item = Hash.new
if item.is_a?(Hash)
# doing a little magic here to set the correct name
- formatted_item[item["data"]["__display_name"]] = item["data"]
- formatted_item[item["data"]["__display_name"]].delete("__display_name")
+ formatted_item[item["__display_name"]] = item.reject{|k| k == "__display_name"}
else
formatted_item = format_for_display(item)
end
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
index db0fb7dd41..68e01cf94f 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -31,6 +31,7 @@ class Chef
require 'chef/search/query'
require 'chef/mixin/shell_out'
require 'chef/mixin/command'
+ require 'chef/util/path_helper'
require 'mixlib/shellout'
end
@@ -159,6 +160,31 @@ class Chef
session_from_list(list)
end
+ def get_ssh_attribute(node)
+ # Order of precedence for ssh target
+ # 1) command line attribute
+ # 2) configuration file
+ # 3) cloud attribute
+ # 4) fqdn
+ if config[:attribute]
+ Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target")
+ attribute = config[:attribute]
+ elsif Chef::Config[:knife][:ssh_attribute]
+ Chef::Log.debug("Using node attribute #{Chef::Config[:knife][:ssh_attribute]}")
+ attribute = Chef::Config[:knife][:ssh_attribute]
+ elsif node[:cloud] &&
+ node[:cloud][:public_hostname] &&
+ !node[:cloud][:public_hostname].empty?
+ Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target")
+ attribute = 'cloud.public_hostname'
+ else
+ # falling back to default of fqdn
+ Chef::Log.debug("Using node attribute 'fqdn' as the ssh target")
+ attribute = "fqdn"
+ end
+ attribute
+ end
+
def search_nodes
list = Array.new
query = Chef::Search::Query.new
@@ -167,23 +193,9 @@ class Chef
# we should skip the loop to next iteration if the item
# returned by the search is nil
next if item.nil?
- # if a command line attribute was not passed, and we have a
- # cloud public_hostname, use that. see #configure_attribute
- # for the source of config[:attribute] and
- # config[:attribute_from_cli]
- if config[:attribute_from_cli]
- Chef::Log.debug("Using node attribute '#{config[:attribute_from_cli]}' from the command line as the ssh target")
- host = extract_nested_value(item, config[:attribute_from_cli])
- elsif item[:cloud] && item[:cloud][:public_hostname]
- Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target")
- host = item[:cloud][:public_hostname]
- else
- # ssh attribute from a configuration file or the default will land here
- Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target")
- host = extract_nested_value(item, config[:attribute])
- end
# next if we couldn't find the specified attribute in the
# returned node object
+ host = extract_nested_value(item,get_ssh_attribute(item))
next if host.nil?
ssh_port = item[:cloud].nil? ? nil : item[:cloud][:public_ssh_port]
srv = [host, ssh_port]
@@ -342,8 +354,10 @@ class Chef
def screen
tf = Tempfile.new("knife-ssh-screen")
- if File.exist? "#{ENV["HOME"]}/.screenrc"
- tf.puts("source #{ENV["HOME"]}/.screenrc")
+ Chef::Util::PathHelper.home('.screenrc') do |screenrc_path|
+ if File.exist? screenrc_path
+ tf.puts("source #{screenrc_path}")
+ end
end
tf.puts("caption always '%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%<'")
tf.puts("hardstatus alwayslastline 'knife ssh #{@name_args[0]}'")
@@ -411,16 +425,6 @@ class Chef
end
end
- def configure_attribute
- # Setting 'knife[:ssh_attribute] = "foo"' in knife.rb => Chef::Config[:knife][:ssh_attribute] == 'foo'
- # Running 'knife ssh -a foo' => both Chef::Config[:knife][:ssh_attribute] && config[:attribute] == foo
- # Thus we can differentiate between a config file value and a command line override at this point by checking config[:attribute]
- # We can tell here if fqdn was passed from the command line, rather than being the default, by checking config[:attribute]
- # However, after here, we cannot tell these things, so we must preserve config[:attribute]
- config[:attribute_from_cli] = config[:attribute]
- config[:attribute] = (config[:attribute_from_cli] || Chef::Config[:knife][:ssh_attribute] || "fqdn").strip
- end
-
def cssh
cssh_cmd = nil
%w[csshX cssh].each do |cmd|
@@ -494,7 +498,6 @@ class Chef
@longest = 0
- configure_attribute
configure_user
configure_password
configure_identity_file
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/status.rb b/lib/chef/knife/status.rb
index 93e81f8f03..95f2c724ff 100644
--- a/lib/chef/knife/status.rb
+++ b/lib/chef/knife/status.rb
@@ -18,10 +18,12 @@
require 'chef/knife'
require 'chef/knife/core/status_presenter'
+require 'chef/knife/core/node_presenter'
class Chef
class Knife
class Status < Knife
+ include Knife::Core::NodeFormattingOptions
deps do
require 'chef/search/query'
@@ -44,20 +46,43 @@ class Chef
:long => "--hide-healthy",
:description => "Hide nodes that have run chef in the last hour"
+ def append_to_query(term)
+ @query << " AND " unless @query.empty?
+ @query << term
+ end
+
def run
ui.use_presenter Knife::Core::StatusPresenter
- all_nodes = []
- q = Chef::Search::Query.new
- query = @name_args[0] ? @name_args[0].dup : '*:*'
+
+ if config[:long_output]
+ opts = {}
+ else
+ opts = {filter_result:
+ { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"],
+ ec2: ["ec2"], run_list: ["run_list"], platform: ["platform"],
+ platform_version: ["platform_version"], chef_environment: ["chef_environment"]}}
+ end
+
+ @query ||= ""
+ append_to_query(@name_args[0]) if @name_args[0]
+ append_to_query("chef_environment:#{config[:environment]}") if config[:environment]
+
if config[:hide_healthy]
time = Time.now.to_i
- query_unhealthy = "NOT ohai_time:[" << (time - 60*60).to_s << " TO " << time.to_s << "]"
- query << ' AND ' << query_unhealthy << @name_args[0] if @name_args[0]
- query = query_unhealthy unless @name_args[0]
+ # AND NOT is not valid lucene syntax, so don't use append_to_query
+ @query << " " unless @query.empty?
+ @query << "NOT ohai_time:[#{(time - 60*60).to_s} TO #{time.to_s}]"
end
- q.search(:node, query) do |node|
+
+ @query = @query.empty? ? "*:*" : @query
+
+ all_nodes = []
+ q = Chef::Search::Query.new
+ Chef::Log.info("Sending query: #{@query}")
+ q.search(:node, @query, opts) do |node|
all_nodes << node
end
+
output(all_nodes.sort { |n1, n2|
if (config[:sort_reverse] || Chef::Config[:knife][:sort_status_reverse])
(n2["ohai_time"] or 0) <=> (n1["ohai_time"] or 0)
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index 4130f06878..995573cd03 100644
--- a/lib/chef/knife/user_create.rb
+++ b/lib/chef/knife/user_create.rb
@@ -1,6 +1,7 @@
#
-# Author:: Steven Danna (<steve@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Author:: Steven Danna (<steve@chef.io>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2012, 2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,76 +18,134 @@
#
require 'chef/knife'
+require 'chef/knife/osc_user_create'
class Chef
class Knife
class UserCreate < Knife
+ attr_accessor :user_field
+
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
option :file,
:short => "-f FILE",
:long => "--file FILE",
- :description => "Write the private key to a file"
+ :description => "Write the private key to a file if the server generated one."
+
+ option :user_key,
+ :long => "--user-key FILENAME",
+ :description => "Set the initial default key for the user from a file on disk (cannot pass with --prevent-keygen)."
+
+ option :prevent_keygen,
+ :short => "-k",
+ :long => "--prevent-keygen",
+ :description => "API V1 only. Prevent server from generating a default key pair for you. Cannot be passed with --user-key.",
+ :boolean => true
option :admin,
:short => "-a",
:long => "--admin",
- :description => "Create the user as an admin",
+ :description => "DEPRECATED: Open Source Chef 11 only. Create the user as an admin.",
:boolean => true
option :user_password,
:short => "-p PASSWORD",
:long => "--password PASSWORD",
- :description => "Password for newly created user",
+ :description => "DEPRECATED: Open Source Chef 11 only. Password for newly created user.",
:default => ""
- option :user_key,
- :long => "--user-key FILENAME",
- :description => "Public key for newly created user. By default a key will be created for you."
+ banner "knife user create USERNAME DISPLAY_NAME FIRST_NAME LAST_NAME EMAIL PASSWORD (options)"
+
+ def user
+ @user_field ||= Chef::UserV1.new
+ end
+
+ def create_user_from_hash(hash)
+ Chef::UserV1.from_hash(hash).create
+ end
+
+ def osc_11_warning
+<<-EOF
+IF YOU ARE USING CHEF SERVER 12+, PLEASE FOLLOW THE INSTRUCTIONS UNDER knife user create --help.
+You only passed a single argument to knife user create.
+For backwards compatibility, when only a single argument is passed,
+knife user create assumes you want Open Source 11 Server user creation.
+knife user create for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user create.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
- banner "knife user create USER (options)"
+ def run_osc_11_user_create
+ # run osc_user_create with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
def run
- @user_name = @name_args[0]
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # If only 1 arg is passed, assume OSC 11 case.
+ if @name_args.length == 1
+ ui.warn(osc_11_warning)
+ run_osc_11_user_create
+ else # EC / CS 12 user create
- if @user_name.nil?
- show_usage
- ui.fatal("You must specify a user name")
- exit 1
- end
+ test_mandatory_field(@name_args[0], "username")
+ user.username @name_args[0]
- if config[:user_password].length == 0
- show_usage
- ui.fatal("You must specify a non-blank password")
- exit 1
- end
+ test_mandatory_field(@name_args[1], "display name")
+ user.display_name @name_args[1]
- user = Chef::User.new
- user.name(@user_name)
- user.admin(config[:admin])
- user.password config[:user_password]
+ test_mandatory_field(@name_args[2], "first name")
+ user.first_name @name_args[2]
- if config[:user_key]
- user.public_key File.read(File.expand_path(config[:user_key]))
- end
+ test_mandatory_field(@name_args[3], "last name")
+ user.last_name @name_args[3]
+
+ test_mandatory_field(@name_args[4], "email")
+ user.email @name_args[4]
+
+ test_mandatory_field(@name_args[5], "password")
+ user.password @name_args[5]
+
+ if config[:user_key] && config[:prevent_keygen]
+ show_usage
+ ui.fatal("You cannot pass --user-key and --prevent-keygen")
+ exit 1
+ end
+
+ if !config[:prevent_keygen] && !config[:user_key]
+ user.create_key(true)
+ end
- output = edit_data(user)
- user = Chef::User.from_hash(output).create
+ if config[:user_key]
+ user.public_key File.read(File.expand_path(config[:user_key]))
+ end
- ui.info("Created #{user}")
- if user.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(user.private_key)
+ output = edit_data(user)
+ final_user = create_user_from_hash(output)
+
+ ui.info("Created #{user}")
+ if final_user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(final_user.private_key)
+ end
+ else
+ ui.msg final_user.private_key
end
- else
- ui.msg user.private_key
end
end
+
+
end
end
end
diff --git a/lib/chef/knife/user_delete.rb b/lib/chef/knife/user_delete.rb
index b7af11bec8..828cd51588 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -23,12 +23,46 @@ class Chef
class UserDelete < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
banner "knife user delete USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user delete for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user delete.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_delete
+ # run osc_user_delete with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
+ # DEPRECATION NOTE
+ # Delete this override method after OSC 11 support is dropped
+ def delete_object(user_name)
+ confirm("Do you really want to delete #{user_name}")
+
+ if Kernel.block_given?
+ object = block.call
+ else
+ object = Chef::UserV1.load(user_name)
+ object.destroy
+ end
+
+ output(format_for_display(object)) if config[:print_after]
+ self.msg("Deleted #{user_name}")
+ end
+
def run
@user_name = @name_args[0]
@@ -38,9 +72,25 @@ class Chef
exit 1
end
- delete_object(Chef::User, @user_name)
- end
+ # DEPRECATION NOTE
+ #
+ # Below is modification of Chef::Knife.delete_object to detect OSC 11 server.
+ # When OSC 11 is deprecated, simply delete all this and go back to:
+ #
+ # delete_object(Chef::UserV1, @user_name)
+ #
+ # Also delete our override of delete_object above
+ object = Chef::UserV1.load(@user_name)
+ # OSC 11 case
+ if object.username.nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_delete
+ else # proceed with EC / CS delete
+ delete_object(@user_name)
+ end
+
+ end
end
end
end
diff --git a/lib/chef/knife/user_edit.rb b/lib/chef/knife/user_edit.rb
index ae319c8872..c3a4326ee8 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -23,12 +23,30 @@ class Chef
class UserEdit < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
banner "knife user edit USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user edit for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife oc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user edit.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_edit
+ # run osc_user_create with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
def run
@user_name = @name_args[0]
@@ -38,15 +56,26 @@ class Chef
exit 1
end
- original_user = Chef::User.load(@user_name).to_hash
- edited_user = edit_data(original_user)
- if original_user != edited_user
- user = Chef::User.from_hash(edited_user)
- user.update
- ui.msg("Saved #{user}.")
- else
- ui.msg("User unchaged, not saving.")
+ original_user = Chef::UserV1.load(@user_name).to_hash
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # if username is nil, we are in the OSC 11 case,
+ # forward to deprecated command
+ if original_user["username"].nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_edit
+ else # EC / CS 12 user create
+ edited_user = edit_data(original_user)
+ if original_user != edited_user
+ user = Chef::UserV1.from_hash(edited_user)
+ user.update
+ ui.msg("Saved #{user}.")
+ else
+ ui.msg("User unchanged, not saving.")
+ end
end
+
end
end
end
diff --git a/lib/chef/knife/user_key_create.rb b/lib/chef/knife/user_key_create.rb
new file mode 100644
index 0000000000..5ed699ff5b
--- /dev/null
+++ b/lib/chef/knife/user_key_create.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/key_create_base'
+
+class Chef
+ class Knife
+ # Implements knife user key create using Chef::Knife::KeyCreate
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the user that this key is for
+ class UserKeyCreate < Knife
+ include Chef::Knife::KeyCreateBase
+
+ banner 'knife user key create USER (options)'
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'user'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyCreate.new(@actor, actor_field_name, ui, config)
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_key_delete.rb b/lib/chef/knife/user_key_delete.rb
new file mode 100644
index 0000000000..6db1c9f552
--- /dev/null
+++ b/lib/chef/knife/user_key_delete.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+class Chef
+ class Knife
+ # Implements knife user key delete using Chef::Knife::KeyDelete
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class UserKeyDelete < Knife
+ banner "knife user key delete USER KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'user'
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyDelete.new(@name, @actor, actor_field_name, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_key_edit.rb b/lib/chef/knife/user_key_edit.rb
new file mode 100644
index 0000000000..0c35332523
--- /dev/null
+++ b/lib/chef/knife/user_key_edit.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/key_edit_base'
+
+class Chef
+ class Knife
+ # Implements knife user key edit using Chef::Knife::KeyEdit
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the user that this key is for
+ class UserKeyEdit < Knife
+ include Chef::Knife::KeyEditBase
+
+ banner 'knife user key edit USER KEYNAME (options)'
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def actor_field_name
+ 'user'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyEdit.new(@name, @actor, actor_field_name, ui, config)
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/knife/user_key_list.rb b/lib/chef/knife/user_key_list.rb
new file mode 100644
index 0000000000..a73f59c86f
--- /dev/null
+++ b/lib/chef/knife/user_key_list.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+require 'chef/knife/key_list_base'
+
+class Chef
+ class Knife
+ # Implements knife user key list using Chef::Knife::KeyList
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class UserKeyList < Knife
+ include Chef::Knife::KeyListBase
+
+ banner "knife user key list USER (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def list_method
+ :list_by_user
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_key_show.rb b/lib/chef/knife/user_key_show.rb
new file mode 100644
index 0000000000..b8453dd28e
--- /dev/null
+++ b/lib/chef/knife/user_key_show.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife'
+
+class Chef
+ class Knife
+ # Implements knife user key show using Chef::Knife::KeyShow
+ # as a service class.
+ #
+ # @author Tyler Cloke
+ #
+ # @attr_reader [String] actor the name of the client that this key is for
+ class UserKeyShow < Knife
+ banner "knife user key show USER KEYNAME (options)"
+
+ attr_reader :actor
+
+ def initialize(argv=[])
+ super(argv)
+ @service_object = nil
+ end
+
+ def run
+ apply_params!(@name_args)
+ service_object.run
+ end
+
+ def load_method
+ :load_by_user
+ end
+
+ def actor_missing_error
+ 'You must specify a user name'
+ end
+
+ def keyname_missing_error
+ 'You must specify a key name'
+ end
+
+ def service_object
+ @service_object ||= Chef::Knife::KeyShow.new(@name, @actor, load_method, ui)
+ end
+
+ def apply_params!(params)
+ @actor = params[0]
+ if @actor.nil?
+ show_usage
+ ui.fatal(actor_missing_error)
+ exit 1
+ end
+ @name = params[1]
+ if @name.nil?
+ show_usage
+ ui.fatal(keyname_missing_error)
+ exit 1
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/user_list.rb b/lib/chef/knife/user_list.rb
index 5d2e735a73..6a130392b9 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -18,12 +18,14 @@
require 'chef/knife'
+# NOTE: only knife user command that is backwards compatible with OSC 11,
+# so no deprecation warnings are necessary.
class Chef
class Knife
class UserList < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
@@ -35,8 +37,9 @@ class Chef
:description => "Show corresponding URIs"
def run
- output(format_list_for_display(Chef::User.list))
+ output(format_list_for_display(Chef::UserV1.list))
end
+
end
end
end
diff --git a/lib/chef/knife/user_reregister.rb b/lib/chef/knife/user_reregister.rb
index 946150e6e4..09fd1cd2d6 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/user_reregister.rb
@@ -23,12 +23,30 @@ class Chef
class UserReregister < Knife
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
banner "knife user reregister USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user reregister for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user reregister.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_reregister
+ # run osc_user_edit with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
option :file,
:short => "-f FILE",
:long => "--file FILE",
@@ -43,16 +61,29 @@ class Chef
exit 1
end
- user = Chef::User.load(@user_name).reregister
- Chef::Log.debug("Updated user data: #{user.inspect}")
- key = user.private_key
- if config[:file]
- File.open(config[:file], "w") do |f|
- f.print(key)
+ user = Chef::UserV1.load(@user_name)
+
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # if username is nil, we are in the OSC 11 case,
+ # forward to deprecated command
+ if user.username.nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_reregister
+ else # EC / CS 12 case
+ user.reregister
+ Chef::Log.debug("Updated user data: #{user.inspect}")
+ key = user.private_key
+ if config[:file]
+ File.open(config[:file], "w") do |f|
+ f.print(key)
+ end
+ else
+ ui.msg key
end
- else
- ui.msg key
end
+
end
end
end
diff --git a/lib/chef/knife/user_show.rb b/lib/chef/knife/user_show.rb
index 61ea101e4c..3a2443471a 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -25,12 +25,30 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/user'
+ require 'chef/user_v1'
require 'chef/json_compat'
end
banner "knife user show USER (options)"
+ def osc_11_warning
+<<-EOF
+The Chef Server you are using does not support the username field.
+This means it is an Open Source 11 Server.
+knife user show for Open Source 11 Server is being deprecated.
+Open Source 11 Server user commands now live under the knife osc_user namespace.
+For backwards compatibility, we will forward this request to knife osc_user show.
+If you are using an Open Source 11 Server, please use that command to avoid this warning.
+EOF
+ end
+
+ def run_osc_11_user_show
+ # run osc_user_edit with our input
+ ARGV.delete("user")
+ ARGV.unshift("osc_user")
+ Chef::Knife.run(ARGV, Chef::Application::Knife.options)
+ end
+
def run
@user_name = @name_args[0]
@@ -40,8 +58,19 @@ class Chef
exit 1
end
- user = Chef::User.load(@user_name)
- output(format_for_display(user))
+ user = Chef::UserV1.load(@user_name)
+
+ # DEPRECATION NOTE
+ # Remove this if statement and corrosponding code post OSC 11 support.
+ #
+ # if username is nil, we are in the OSC 11 case,
+ # forward to deprecated command
+ if user.username.nil?
+ ui.warn(osc_11_warning)
+ run_osc_11_user_show
+ else
+ output(format_for_display(user))
+ end
end
end
diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb
index e66acb6b66..79fb750dd8 100644
--- a/lib/chef/local_mode.rb
+++ b/lib/chef/local_mode.rb
@@ -18,6 +18,7 @@ require 'chef/config'
class Chef
module LocalMode
+
# Create a chef local server (if the configuration requires one) for the
# duration of the given block.
#
@@ -59,12 +60,21 @@ class Chef
server_options = {}
server_options[:data_store] = data_store
server_options[:log_level] = Chef::Log.level
+
server_options[:host] = Chef::Config.chef_zero.host
server_options[:port] = parse_port(Chef::Config.chef_zero.port)
@chef_zero_server = ChefZero::Server.new(server_options)
- @chef_zero_server.start_background
- Chef::Log.info("Started chef-zero at #{@chef_zero_server.url} with #{@chef_fs.fs_description}")
- Chef::Config.chef_server_url = @chef_zero_server.url
+
+ if Chef::Config[:listen]
+ @chef_zero_server.start_background
+ else
+ @chef_zero_server.start_socketless
+ end
+
+ local_mode_url = @chef_zero_server.local_mode_url
+
+ Chef::Log.info("Started chef-zero at #{local_mode_url} with #{@chef_fs.fs_description}")
+ Chef::Config.chef_server_url = local_mode_url
end
end
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index 682afcea4b..bf846c2072 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -21,6 +21,8 @@ require 'logger'
require 'chef/monologger'
require 'chef/exceptions'
require 'mixlib/log'
+require 'chef/log/syslog' unless (RUBY_PLATFORM =~ /mswin|mingw|windows/)
+require 'chef/log/winevt'
class Chef
class Log
@@ -35,7 +37,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/log/syslog.rb b/lib/chef/log/syslog.rb
new file mode 100644
index 0000000000..0c8190797f
--- /dev/null
+++ b/lib/chef/log/syslog.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Author:: SAWANOBORI Yukihiko (<sawanoboriyu@higanworks.com>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'logger'
+require 'syslog-logger'
+require 'chef/mixin/unformatter'
+
+class Chef
+ class Log
+ #
+ # Chef::Log::Syslog class.
+ # usage in client.rb:
+ # log_location Chef::Log::Syslog.new("chef-client", ::Syslog::LOG_DAEMON)
+ #
+ class Syslog < Logger::Syslog
+ include Chef::Mixin::Unformatter
+
+ attr_accessor :sync, :formatter
+
+ def initialize(program_name = 'chef-client', facility = ::Syslog::LOG_DAEMON, logopts=nil)
+ super
+ return if defined? ::Logger::Syslog::SYSLOG
+ ::Logger::Syslog.const_set :SYSLOG, SYSLOG
+ end
+
+ def close
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/log/winevt.rb b/lib/chef/log/winevt.rb
new file mode 100644
index 0000000000..c5b7c3485a
--- /dev/null
+++ b/lib/chef/log/winevt.rb
@@ -0,0 +1,99 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+#
+# Copyright:: 2015, Chef Software, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/event_loggers/base'
+require 'chef/platform/query_helpers'
+require 'chef/mixin/unformatter'
+
+class Chef
+ class Log
+ #
+ # Chef::Log::WinEvt class.
+ # usage in client.rb:
+ # log_location Chef::Log::WinEvt.new
+ #
+ class WinEvt
+ # These must match those that are defined in the manifest file
+ INFO_EVENT_ID = 10100
+ WARN_EVENT_ID = 10101
+ DEBUG_EVENT_ID = 10102
+ ERROR_EVENT_ID = 10103
+ FATAL_EVENT_ID = 10104
+
+ # Since we must install the event logger, this is not really configurable
+ SOURCE = 'Chef'
+
+ include Chef::Mixin::Unformatter
+
+ attr_accessor :sync, :formatter, :level
+
+ def initialize(eventlog=nil)
+ @eventlog = eventlog || ::Win32::EventLog::open('Application')
+ end
+
+ def close
+ end
+
+ def info(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::INFO_TYPE,
+ :source => SOURCE,
+ :event_id => INFO_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def warn(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::WARN_TYPE,
+ :source => SOURCE,
+ :event_id => WARN_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def debug(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::INFO_TYPE,
+ :source => SOURCE,
+ :event_id => DEBUG_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def error(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::ERROR_TYPE,
+ :source => SOURCE,
+ :event_id => ERROR_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ def fatal(msg)
+ @eventlog.report_event(
+ :event_type => ::Win32::EventLog::ERROR_TYPE,
+ :source => SOURCE,
+ :event_id => FATAL_EVENT_ID,
+ :data => [msg]
+ )
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/api_version_request_handling.rb b/lib/chef/mixin/api_version_request_handling.rb
new file mode 100644
index 0000000000..20ab3bf452
--- /dev/null
+++ b/lib/chef/mixin/api_version_request_handling.rb
@@ -0,0 +1,66 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module ApiVersionRequestHandling
+ # Input:
+ # exeception:
+ # Net::HTTPServerException that may or may not contain the x-ops-server-api-version header
+ # supported_client_versions:
+ # An array of Integers that represent the API versions the client supports.
+ #
+ # Output:
+ # nil:
+ # If the execption was not a 406 or the server does not support versioning
+ # Array of length zero:
+ # If there was no intersection between supported client versions and supported server versions
+ # Arrary of Integers:
+ # If there was an intersection of supported versions, the array returns will contain that intersection
+ def server_client_api_version_intersection(exception, supported_client_versions)
+ # return empty array unless 406 Unacceptable with proper header
+ return nil if exception.response.code != "406" || exception.response["x-ops-server-api-version"].nil?
+
+ # intersection of versions the server and client support, will be of length zero if no intersection
+ server_supported_client_versions = Array.new
+
+ header = Chef::JSONCompat.from_json(exception.response["x-ops-server-api-version"])
+ min_server_version = Integer(header["min_version"])
+ max_server_version = Integer(header["max_version"])
+
+ supported_client_versions.each do |version|
+ if version >= min_server_version && version <= max_server_version
+ server_supported_client_versions.push(version)
+ end
+ end
+ server_supported_client_versions
+ end
+
+ def reregister_only_v0_supported_error_msg(max_version, min_version)
+ <<-EOH
+The reregister command only supports server API version 0.
+The server that received the request supports a min version of #{min_version} and a max version of #{max_version}.
+User keys are now managed via the key rotation commmands.
+Please refer to the documentation on how to manage your keys via the key rotation commands:
+https://docs.chef.io/server_security.html#key-rotation
+EOH
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb
index 19f229fdd3..14676e5ed4 100644
--- a/lib/chef/mixin/convert_to_class_name.rb
+++ b/lib/chef/mixin/convert_to_class_name.rb
@@ -23,9 +23,7 @@ class Chef
extend self
def convert_to_class_name(str)
- str = str.dup
- str.gsub!(/[^A-Za-z0-9_]/,'_')
- str.gsub!(/^(_+)?/,'')
+ str = normalize_snake_case_name(str)
rname = nil
regexp = %r{^(.+?)(_(.+))?$}
@@ -51,6 +49,13 @@ class Chef
str
end
+ def normalize_snake_case_name(str)
+ str = str.dup
+ str.gsub!(/[^A-Za-z0-9_]/,'_')
+ str.gsub!(/^(_+)?/,'')
+ str
+ end
+
def snake_case_basename(str)
with_namespace = convert_to_snake_case(str)
with_namespace.split("::").last.sub(/^_/, '')
@@ -58,7 +63,8 @@ class Chef
def filename_to_qualified_string(base, filename)
file_base = File.basename(filename, ".rb")
- base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+ str = base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+ normalize_snake_case_name(str)
end
# Copied from rails activesupport. In ruby >= 2.0 const_get will just do this, so this can
diff --git a/lib/chef/mixin/deprecation.rb b/lib/chef/mixin/deprecation.rb
index 489f27c339..562af541bd 100644
--- a/lib/chef/mixin/deprecation.rb
+++ b/lib/chef/mixin/deprecation.rb
@@ -95,6 +95,30 @@ class Chef
DeprecatedInstanceVariable.new(obj, name, level)
end
+ def deprecated_attr(name, alternative)
+ deprecated_attr_reader(name, alternative)
+ deprecated_attr_writer(name, alternative)
+ end
+
+ def deprecated_attr_reader(name, alternative, level=:warn)
+ define_method(name) do
+ Chef.log_deprecation("#{self.class}.#{name} is deprecated. Support will be removed in a future release.")
+ Chef.log_deprecation(alternative)
+ Chef.log_deprecation("Called from:")
+ caller[0..3].each {|c| Chef.log_deprecation(c)}
+ instance_variable_get("@#{name}")
+ end
+ end
+
+ def deprecated_attr_writer(name, alternative, level=:warn)
+ define_method("#{name}=") do |value|
+ Chef.log_deprecation("Writing to #{self.class}.#{name} with #{name}= is deprecated. Support will be removed in a future release.")
+ Chef.log_deprecation(alternative)
+ Chef.log_deprecation("Called from:")
+ caller[0..3].each {|c| Chef.log_deprecation(c)}
+ instance_variable_set("@#{name}", value)
+ end
+ end
end
end
end
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index baf210bfc5..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,185 +118,352 @@ class Chef
DelayedEvaluator.new(&block)
end
- NULL_ARG = Object.new
+ def set_or_return(symbol, value, validation)
+ property = SetOrReturnProperty.new(name: symbol, **validation)
+ property.call(self, value)
+ end
- def nillable_set_or_return(symbol, arg, validation)
- iv_symbol = "@#{symbol.to_s}".to_sym
- if NULL_ARG.equal?(arg)
- if self.instance_variable_defined?(iv_symbol) == true
- get_ivar(iv_symbol, symbol, validation)
- else
- # on access we create the iv and set it to nil for back-compat
- set_ivar(iv_symbol, symbol, nil, validation)
- end
- else
- set_ivar(iv_symbol, symbol, arg, validation)
- end
+ private
+
+ def explicitly_allows_nil?(key, validation)
+ validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
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
- get_ivar(iv_symbol, symbol, validation)
+ # 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
- set_ivar(iv_symbol, symbol, arg, validation)
+ nil
end
end
- private
-
- def get_ivar(iv_symbol, symbol, validation)
- ivar = self.instance_variable_get(iv_symbol)
- if(ivar.is_a?(DelayedEvaluator))
- validate({ symbol => ivar.call }, { symbol => validation })[symbol]
- else
- ivar
+ # 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
- def set_ivar(iv_symbol, symbol, arg, validation)
- if(arg.is_a?(DelayedEvaluator))
- val = arg
- else
- val = validate({ symbol => arg }, { symbol => validation })[symbol]
-
- # 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)
+ #
+ # 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
- self.instance_variable_set(iv_symbol, val)
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 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
- # 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!"
+ #
+ # 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
- 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 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 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}."
- 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
- # 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
+ #
+ # List of regexes values that must match.
+ #
+ # Uses regex.match() to evaluate. At least one must match for the value to
+ # be valid.
+ #
+ # `nil` passes regex validation automatically.
+ #
+ # @example
+ # ```ruby
+ # property :x, regex: [ /abc/, /xyz/ ]
+ # ```
+ #
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(regex).flatten.each do |r|
+ return true if r.match(value.to_s)
end
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
end
+ end
- # Assert that parameter returns false when passed a predicate method.
- # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
- # error value.blank? returns a 'truthy' (not nil or false) value.
- #
- # Note, this will *PASS* if the object doesn't respond to the method.
- # So, to make sure a value is not nil and not blank, you need to do
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
- def _pv_cannot_be(opts, key, predicate_method_base_name)
- value = _pv_opts_lookup(opts, key)
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
-
- if value.respond_to?(predicate_method)
- if value.send(predicate_method)
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ #
+ # List of procs we pass the value to.
+ #
+ # All procs must return true for the value to be valid. If any procs do
+ # not return true, the key will be used for the message: `"Property x's
+ # value :y <message>"`.
+ #
+ # @example
+ # ```ruby
+ # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
+ # ```
+ #
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ callbacks.each do |message, zeproc|
+ unless zeproc.call(value)
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
end
end
end
+ end
- # Assign a default value to a parameter.
- def _pv_default(opts, key, default_value)
- value = _pv_opts_lookup(opts, key)
- if value == nil
- opts[key] = default_value
+ #
+ # Allows a parameter to default to the value of the resource name.
+ #
+ # @example
+ # ```ruby
+ # property :x, name_property: true
+ # ```
+ #
+ def _pv_name_property(opts, key, is_name_property=true)
+ if is_name_property
+ if opts[key].nil?
+ opts[key] = self.instance_variable_get(:"@name")
end
end
+ end
+ alias :_pv_name_attribute :_pv_name_property
- # Check a parameter against a regular expression.
- def _pv_regex(opts, key, regex)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- passes = false
- [ regex ].flatten.each do |r|
- if value != nil
- if r.match(value.to_s)
- passes = true
- end
- end
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
- end
+ #
+ # List of valid things values can be.
+ #
+ # Uses Ruby's `===` to evaluate (is === value). At least one must match
+ # for the value to be valid.
+ #
+ # If a proc is passed, it is instance_eval'd in the resource, passed the
+ # value, and must return a truthy or falsey value.
+ #
+ # @example Class
+ # ```ruby
+ # property :x, String
+ # x 'valid' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ #
+ # @example Value
+ # ```ruby
+ # property :x, [ :a, :b, :c, nil ]
+ # x :a #=> valid
+ # x nil #=> valid
+ # ```
+ #
+ # @example Regex
+ # ```ruby
+ # property :x, /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example Proc
+ # ```ruby
+ # property :x, proc { |x| x > y }
+ # property :y, default: 2
+ # x 3 #=> valid
+ # x 1 #=> invalid
+ # ```
+ #
+ # @example Property
+ # ```ruby
+ # type = Property.new(is: String)
+ # property :x, type
+ # x 'foo' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example RSpec Matcher
+ # ```ruby
+ # include RSpec::Matchers
+ # property :x, a_string_matching /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_is(opts, key, to_be, raise_error: true)
+ return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
+ value = _pv_opts_lookup(opts, key)
+ to_be = [ to_be ].flatten(1)
+ to_be.each do |tb|
+ case tb
+ when Proc
+ return true if instance_exec(value, &tb)
+ when Property
+ validate(opts, { key => tb.validation_options })
+ return true
+ else
+ return true if tb === value
end
end
- # Check a parameter against a hash of proc's.
- def _pv_callbacks(opts, key, callbacks)
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- callbacks.each do |message, zeproc|
- if zeproc.call(value) != true
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
- end
- end
+ if raise_error
+ raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ else
+ false
+ end
+ end
+
+ #
+ # Method to mess with a value before it is validated and stored.
+ #
+ # Allows you to transform values into a canonical form that is easy to
+ # work with.
+ #
+ # This is passed the value to transform, and is run in the context of the
+ # instance (so it has access to other resource properties). It must return
+ # the value that will be stored in the instance.
+ #
+ # @example
+ # ```ruby
+ # property :x, Integer, coerce: { |v| v.to_i }
+ # ```
+ #
+ def _pv_coerce(opts, key, coercer)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s] = instance_exec(opts[key], &coercer)
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym] = instance_exec(opts[key], &coercer)
+ end
+ end
+
+ # Used by #set_or_return to avoid emitting a deprecation warning for
+ # "value nil" and to keep default stickiness working exactly the same
+ # @api private
+ class SetOrReturnProperty < Chef::Property
+ def get(resource)
+ value = super
+ # All values are sticky, frozen or not
+ if !is_set?(resource)
+ set_value(resource, value)
end
+ value
end
- # Allow a parameter to default to @name
- def _pv_name_attribute(opts, key, is_name_attribute=true)
- if is_name_attribute
- if opts[key] == nil
- opts[key] = self.instance_variable_get("@name")
- end
+ def call(resource, value=NOT_PASSED)
+ # setting to nil does a get
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ get(resource)
+ else
+ super
end
end
+ end
end
end
end
diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb
new file mode 100644
index 0000000000..e4f29c07c4
--- /dev/null
+++ b/lib/chef/mixin/powershell_out.rb
@@ -0,0 +1,98 @@
+#--
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+require 'chef/mixin/shell_out'
+require 'chef/mixin/windows_architecture_helper'
+
+class Chef
+ module Mixin
+ module PowershellOut
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::WindowsArchitectureHelper
+
+ # Run a command under powershell with the same API as shell_out. The
+ # options hash is extended to take an "architecture" flag which
+ # can be set to :i386 or :x86_64 to force the windows architecture.
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def powershell_out(*command_args)
+ script = command_args.first
+ options = command_args.last.is_a?(Hash) ? command_args.last : nil
+
+ run_command_with_os_architecture(script, options)
+ end
+
+ # Run a command under powershell with the same API as shell_out!
+ # (raises exceptions on errors)
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def powershell_out!(*command_args)
+ cmd = powershell_out(*command_args)
+ cmd.error!
+ cmd
+ end
+
+ private
+
+ # Helper function to run shell_out and wrap it with the correct
+ # flags to possibly disable WOW64 redirection (which we often need
+ # because chef-client runs as a 32-bit app on 64-bit windows).
+ #
+ # @param script [String] script to run
+ # @param options [Hash] options hash
+ # @return [Mixlib::Shellout] mixlib-shellout object
+ def run_command_with_os_architecture(script, options)
+ options ||= {}
+ options = options.dup
+ arch = options.delete(:architecture)
+
+ with_os_architecture(nil, architecture: arch) do
+ shell_out(
+ build_powershell_command(script),
+ options
+ )
+ end
+ end
+
+ # Helper to build a powershell command around the script to run.
+ #
+ # @param script [String] script to run
+ # @retrurn [String] powershell command to execute
+ def build_powershell_command(script)
+ flags = [
+ # Hides the copyright banner at startup.
+ "-NoLogo",
+ # Does not present an interactive prompt to the user.
+ "-NonInteractive",
+ # Does not load the Windows PowerShell profile.
+ "-NoProfile",
+ # always set the ExecutionPolicy flag
+ # see http://technet.microsoft.com/en-us/library/ee176961.aspx
+ "-ExecutionPolicy Unrestricted",
+ # Powershell will hang if STDIN is redirected
+ # http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
+ "-InputFormat None"
+ ]
+
+ "powershell.exe #{flags.join(' ')} -Command \"#{script}\""
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/powershell_type_coercions.rb b/lib/chef/mixin/powershell_type_coercions.rb
new file mode 100644
index 0000000000..75b3276c84
--- /dev/null
+++ b/lib/chef/mixin/powershell_type_coercions.rb
@@ -0,0 +1,82 @@
+#
+# Author:: Adam Edwards (<adamed@opscode.com>)
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module PowershellTypeCoercions
+
+ def type_coercions
+ @type_coercions ||= {
+ Fixnum => { :type => lambda { |x| x.to_s }},
+ Float => { :type => lambda { |x| x.to_s }},
+ FalseClass => { :type => lambda { |x| '$false' }},
+ TrueClass => { :type => lambda { |x| '$true' }},
+ Hash => {:type => Proc.new { |x| translate_hash(x)}},
+ Array => {:type => Proc.new { |x| translate_array(x)}}
+ }
+ end
+
+ def translate_type(value)
+ translation = type_coercions[value.class]
+
+ if translation
+ translation[:type].call(value)
+ elsif value.respond_to? :to_psobject
+ "(#{value.to_psobject})"
+ else
+ safe_string(value.to_s)
+ end
+ end
+
+ private
+
+ def translate_hash(x)
+ translated = x.inject([]) do |memo, (k,v)|
+ memo << "#{k}=#{translate_type(v)}"
+ end
+ "@{#{translated.join(';')}}"
+ end
+
+ def translate_array(x)
+ translated = x.map do |v|
+ translate_type(v)
+ end
+ "@(#{translated.join(',')})"
+ end
+
+ def unsafe?(s)
+ ["'", '#', '`', '"'].any? do |x|
+ s.include? x
+ end
+ end
+
+ def safe_string(s)
+ # do we need to worry about binary data?
+ if unsafe?(s)
+ encoded_str = Base64.strict_encode64(s.encode("UTF-8"))
+ "([System.Text.Encoding]::UTF8.GetString("\
+ "[System.Convert]::FromBase64String('#{encoded_str}')"\
+ "))"
+ else
+ "'#{s}'"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb
new file mode 100644
index 0000000000..095e273dab
--- /dev/null
+++ b/lib/chef/mixin/provides.rb
@@ -0,0 +1,27 @@
+
+require 'chef/mixin/descendants_tracker'
+
+class Chef
+ module Mixin
+ module Provides
+ # TODO no longer needed, remove or deprecate?
+ include Chef::Mixin::DescendantsTracker
+
+ def provides(short_name, opts={}, &block)
+ raise NotImplementedError, :provides
+ end
+
+ # Check whether this resource provides the resource_name DSL for the given
+ # node. TODO remove this when we stop checking unregistered things.
+ def provides?(node, resource)
+ raise NotImplementedError, :provides?
+ end
+
+ # Get the list of recipe DSL this resource is responsible for on the given
+ # node.
+ def provided_as(node)
+ node_map.list(node)
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
index d705a9e7be..be83edc697 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/unformatter.rb b/lib/chef/mixin/unformatter.rb
new file mode 100644
index 0000000000..aa5977edd7
--- /dev/null
+++ b/lib/chef/mixin/unformatter.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Mixin
+ module Unformatter
+
+ def write(message)
+ data = message.match(/(\[.+?\] )?([\w]+):(.*)$/)
+ self.send(data[2].downcase.chomp.to_sym, data[3].strip)
+ rescue NoMethodError
+ self.send(:info, message)
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/uris.rb b/lib/chef/mixin/uris.rb
new file mode 100644
index 0000000000..0136b55f6a
--- /dev/null
+++ b/lib/chef/mixin/uris.rb
@@ -0,0 +1,44 @@
+#
+# Author:: Jay Mundrawala (<jdm@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 'uri'
+
+class Chef
+ module Mixin
+ module Uris
+ # uri_scheme? returns true if the string starts with
+ # scheme://
+ # For example, it will match http://foo.bar.com
+ def uri_scheme?(source)
+ # From open-uri
+ !!(%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ source)
+ end
+
+
+ def as_uri(source)
+ begin
+ URI.parse(source)
+ rescue URI::InvalidURIError
+ Chef::Log.warn("#{source} was an invalid URI. Trying to escape invalid characters")
+ URI.parse(URI.escape(source))
+ end
+ end
+
+ end
+ end
+end
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 65ad042910..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,10 +36,31 @@ class Chef
is_i386_process_on_x86_64_windows?
end
- def with_os_architecture(node)
+ def forced_32bit_override_required?(node, desired_architecture)
+ desired_architecture == :i386 &&
+ node_windows_architecture(node) == :x86_64 &&
+ !is_i386_process_on_x86_64_windows?
+ end
+
+ def wow64_directory
+ Chef::ReservedNames::Win32::System.get_system_wow64_directory
+ end
+
+ def with_os_architecture(node, architecture: nil)
+ node ||= begin
+ os_arch = ENV['PROCESSOR_ARCHITEW6432'] ||
+ ENV['PROCESSOR_ARCHITECTURE']
+ Hash.new.tap do |n|
+ n[:kernel] = Hash.new
+ n[:kernel][:machine] = os_arch == 'AMD64' ? :x86_64 : :i386
+ end
+ end
+
+ architecture ||= node_windows_architecture(node)
+
wow64_redirection_state = nil
- if wow64_architecture_override_required?(node, node_windows_architecture(node))
+ if wow64_architecture_override_required?(node, architecture)
wow64_redirection_state = disable_wow64_file_redirection(node)
end
@@ -77,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 490b235065..cd12b4254a 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -18,13 +18,16 @@
require 'chef/exceptions'
+require 'chef/mixin/wide_string'
require 'chef/platform/query_helpers'
require 'chef/win32/error' if Chef::Platform.windows?
require 'chef/win32/api/system' if Chef::Platform.windows?
+require 'chef/win32/api/unicode' if Chef::Platform.windows?
class Chef
module Mixin
module WindowsEnvHelper
+ include Chef::Mixin::WideString
if Chef::Platform.windows?
include Chef::ReservedNames::Win32::API::System
@@ -39,7 +42,16 @@ class Chef
def broadcast_env_change
flags = SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG
- SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil)
+ # for why two calls, see:
+ # http://stackoverflow.com/questions/4968373/why-doesnt-sendmessagetimeout-update-the-environment-variables
+ if ( SendMessageTimeoutA(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string('Environment').address, flags, 5000, nil) == 0 )
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ if ( SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, FFI::MemoryPointer.from_string(
+ utf8_to_wide('Environment')
+ ).address, flags, 5000, nil) == 0 )
+ Chef::ReservedNames::Win32::Error.raise!
+ end
end
def expand_path(path)
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index f9f6416f14..22c7d5bd8e 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -77,13 +77,30 @@ class Chef
@run_state = {}
end
+ # after the run_context has been set on the node, go through the cookbook_collection
+ # and setup the node[:cookbooks] attribute so that it is published in the node object
+ def set_cookbook_attribute
+ return unless run_context.cookbook_collection
+ run_context.cookbook_collection.each do |cookbook_name, cookbook|
+ automatic_attrs[:cookbooks][cookbook_name][:version] = cookbook.version
+ end
+ end
+
# Used by DSL
def node
self
end
def chef_server_rest
- @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ # for saving node data we use validate_utf8: false which will not
+ # raise an exception on bad utf8 data, but will replace the bad
+ # characters and render valid JSON.
+ @chef_server_rest ||= Chef::REST.new(
+ Chef::Config[:chef_server_url],
+ Chef::Config[:node_name],
+ Chef::Config[:client_key],
+ validate_utf8: false,
+ )
end
# Set the name of this Node, or return the current name.
@@ -244,6 +261,7 @@ class Chef
# saved back to the node and be searchable
def loaded_recipe(cookbook, recipe)
fully_qualified_recipe = "#{cookbook}::#{recipe}"
+
automatic_attrs[:recipes] << fully_qualified_recipe unless Array(self[:recipes]).include?(fully_qualified_recipe)
end
@@ -297,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
@@ -323,12 +342,30 @@ class Chef
if attrs.key?("recipes") || attrs.key?("run_list")
raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only."
end
- Chef::Log.info("Setting the run_list to #{new_run_list.inspect} from CLI options")
+ Chef::Log.info("Setting the run_list to #{new_run_list.to_s} from CLI options")
run_list(new_run_list)
end
attrs
end
+ # chef_environment when set in -j JSON will take precedence over
+ # -E ENVIRONMENT. Ideally, IMO, the order of precedence should be (lowest to
+ # highest):
+ # config_file
+ # -j JSON
+ # -E ENVIRONMENT
+ # so that users could reuse their JSON and override the chef_environment
+ # configured within it with -E ENVIRONMENT. Because command line options are
+ # merged with Chef::Config there is currently no way to distinguish between
+ # an environment set via config from an environment set via command line.
+ def consume_chef_environment(attrs)
+ attrs = attrs ? attrs.dup : {}
+ if env = attrs.delete("chef_environment")
+ chef_environment(env)
+ end
+ attrs
+ end
+
# Clear defaults and overrides, so that any deleted attributes
# between runs are still gone.
def reset_defaults_and_overrides
@@ -354,7 +391,8 @@ class Chef
self.tags # make sure they're defined
- automatic_attrs[:recipes] = expansion.recipes
+ automatic_attrs[:recipes] = expansion.recipes.with_fully_qualified_names_and_version_constraints
+ automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints
automatic_attrs[:roles] = expansion.roles
apply_expansion_attributes(expansion)
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index 2ca6d9ba17..751f9576f6 100644
--- a/lib/chef/node_map.rb
+++ b/lib/chef/node_map.rb
@@ -19,128 +19,204 @@
class Chef
class NodeMap
- VALID_OPTS = [
- :on_platform,
- :on_platforms,
- :platform,
- :os,
- :platform_family,
- ]
-
- DEPRECATED_OPTS = [
- :on_platform,
- :on_platforms,
- ]
-
- # Create a new NodeMap
#
- def initialize
- @map = {}
- end
-
# Set a key/value pair on the map with a filter. The filter must be true
# when applied to the node in order to retrieve the value.
#
# @param key [Object] Key to store
# @param value [Object] Value associated with the key
# @param filters [Hash] Node filter options to apply to key retrieval
+ #
# @yield [node] Arbitrary node filter as a block which takes a node argument
+ #
# @return [NodeMap] Returns self for possible chaining
#
- def set(key, value, filters = {}, &block)
- validate_filter!(filters)
- deprecate_filter!(filters)
- @map[key] ||= []
- # we match on the first value we find, so we want to unshift so that the
- # last setter wins
- # FIXME: need a test for this behavior
- @map[key].unshift({ filters: filters, block: block, value: value })
- self
+ def set(key, value, platform: nil, platform_version: nil, platform_family: nil, os: nil, on_platform: nil, on_platforms: nil, canonical: nil, override: nil, &block)
+ Chef.log_deprecation("The on_platform option to node_map has been deprecated") if on_platform
+ Chef.log_deprecation("The on_platforms option to node_map has been deprecated") if on_platforms
+ platform ||= on_platform || on_platforms
+ filters = {}
+ filters[:platform] = platform if platform
+ filters[:platform_version] = platform_version if platform_version
+ filters[:platform_family] = platform_family if platform_family
+ filters[:os] = os if os
+ new_matcher = { value: value, filters: filters }
+ new_matcher[:block] = block if block
+ new_matcher[:canonical] = canonical if canonical
+ new_matcher[:override] = override if override
+
+ # The map is sorted in order of preference already; we just need to find
+ # our place in it (just before the first value with the same preference level).
+ insert_at = nil
+ map[key] ||= []
+ map[key].each_with_index do |matcher,index|
+ cmp = compare_matchers(key, new_matcher, matcher)
+ insert_at ||= index if cmp && cmp <= 0
+ end
+ if insert_at
+ map[key].insert(insert_at, new_matcher)
+ else
+ map[key] << new_matcher
+ end
+ map
end
+ #
# Get a value from the NodeMap via applying the node to the filters that
# were set on the key.
#
- # @param node [Chef::Node] The Chef::Node object for the run
+ # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to
+ # ignore all filters.
+ # @param key [Object] Key to look up
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality. Default: `nil`
+ #
+ # @return [Object] Value
+ #
+ def get(node, key, canonical: nil)
+ raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
+ list(node, key, canonical: canonical).first
+ end
+
+ #
+ # List all matches for the given node and key from the NodeMap, from
+ # most-recently added to oldest.
+ #
+ # @param node [Chef::Node] The Chef::Node object for the run, or `nil` to
+ # ignore all filters.
# @param key [Object] Key to look up
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality. Default: `nil`
+ #
# @return [Object] Value
#
- def get(node, key)
- # FIXME: real exception
- raise "first argument must be a Chef::Node" unless node.is_a?(Chef::Node)
- return nil unless @map.has_key?(key)
- @map[key].each do |matcher|
- if filters_match?(node, matcher[:filters]) &&
- block_matches?(node, matcher[:block])
- return matcher[:value]
+ def list(node, key, canonical: nil)
+ raise ArgumentError, "first argument must be a Chef::Node" unless node.is_a?(Chef::Node) || node.nil?
+ return [] unless map.has_key?(key)
+ map[key].select do |matcher|
+ node_matches?(node, matcher) && canonical_matches?(canonical, matcher)
+ end.map { |matcher| matcher[:value] }
+ end
+
+ # Seriously, don't use this, it's nearly certain to change on you
+ # @return remaining
+ # @api private
+ def delete_canonical(key, value)
+ remaining = map[key]
+ if remaining
+ remaining.delete_if { |matcher| matcher[:canonical] && Array(matcher[:value]) == Array(value) }
+ if remaining.empty?
+ map.delete(key)
+ remaining = nil
end
end
- nil
+ remaining
end
- private
+ protected
- # only allow valid filter options
- def validate_filter!(filters)
- filters.each_key do |key|
- # FIXME: real exception
- raise "Bad key #{key} in Chef::NodeMap filter expression" unless VALID_OPTS.include?(key)
- end
+ #
+ # Succeeds if:
+ # - no negative matches (!value)
+ # - at least one positive match (value or :all), or no positive filters
+ #
+ def matches_black_white_list?(node, filters, attribute)
+ # It's super common for the filter to be nil. Catch that so we don't
+ # spend any time here.
+ return true if !filters[attribute]
+ filter_values = Array(filters[attribute])
+ value = node[attribute]
+
+ # Split the blacklist and whitelist
+ blacklist, whitelist = filter_values.partition { |v| v.is_a?(String) && v.start_with?('!') }
+
+ # If any blacklist value matches, we don't match
+ return false if blacklist.any? { |v| v[1..-1] == value }
+
+ # If the whitelist is empty, or anything matches, we match.
+ whitelist.empty? || whitelist.any? { |v| v == :all || v == value }
end
- # warn on deprecated filter options
- def deprecate_filter!(filters)
- filters.each_key do |key|
- Chef::Log.warn "The #{key} option to node_map has been deprecated" if DEPRECATED_OPTS.include?(key)
+ def matches_version_list?(node, filters, attribute)
+ # It's super common for the filter to be nil. Catch that so we don't
+ # spend any time here.
+ return true if !filters[attribute]
+ filter_values = Array(filters[attribute])
+ value = node[attribute]
+
+ filter_values.empty? ||
+ Array(filter_values).any? do |v|
+ Chef::VersionConstraint::Platform.new(v).include?(value)
end
end
- # @todo: this works fine, but is probably hard to understand
- def negative_match(filter, param)
- # We support strings prefaced by '!' to mean 'not'. In particular, this is most useful
- # for os matching on '!windows'.
- negative_matches = filter.select { |f| f[0] == '!' }
- return true if !negative_matches.empty? && negative_matches.include?('!' + param)
-
- # We support the symbol :all to match everything, for backcompat, but this can and should
- # simply be ommitted.
- positive_matches = filter.reject { |f| f[0] == '!' || f == :all }
- return true if !positive_matches.empty? && !positive_matches.include?(param)
+ def filters_match?(node, filters)
+ matches_black_white_list?(node, filters, :os) &&
+ matches_black_white_list?(node, filters, :platform_family) &&
+ matches_black_white_list?(node, filters, :platform) &&
+ matches_version_list?(node, filters, :platform_version)
+ end
- # sorry double-negative: this means we pass this filter.
- false
+ def block_matches?(node, block)
+ return true if block.nil?
+ block.call node
end
- def filters_match?(node, filters)
- return true if filters.empty?
+ def node_matches?(node, matcher)
+ return true if !node
+ filters_match?(node, matcher[:filters]) && block_matches?(node, matcher[:block])
+ end
- # each filter is applied in turn. if any fail, then it shortcuts and returns false.
- # if it passes or does not exist it succeeds and continues on. so multiple filters are
- # effectively joined by 'and'. all filters can be single strings, or arrays which are
- # effectively joined by 'or'.
+ def canonical_matches?(canonical, matcher)
+ return true if canonical.nil?
+ !!canonical == !!matcher[:canonical]
+ end
- os_filter = [ filters[:os] ].flatten.compact
- unless os_filter.empty?
- return false if negative_match(os_filter, node[:os])
- end
+ def compare_matchers(key, new_matcher, matcher)
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:block] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_version] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:platform_family] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:filters][:os] }
+ return cmp if cmp != 0
+ cmp = compare_matcher_properties(new_matcher, matcher) { |m| m[:override] }
+ return cmp if cmp != 0
+ # If all things are identical, return 0
+ 0
+ end
- platform_family_filter = [ filters[:platform_family] ].flatten.compact
- unless platform_family_filter.empty?
- return false if negative_match(platform_family_filter, node[:platform_family])
+ def compare_matcher_properties(new_matcher, matcher)
+ a = yield(new_matcher)
+ b = yield(matcher)
+
+ # Check for blcacklists ('!windows'). Those always come *after* positive
+ # whitelists.
+ a_negated = Array(a).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ b_negated = Array(b).any? { |f| f.is_a?(String) && f.start_with?('!') }
+ if a_negated != b_negated
+ return 1 if a_negated
+ return -1 if b_negated
end
- # :on_platform and :on_platforms here are synonyms which are deprecated
- platform_filter = [ filters[:platform] || filters[:on_platform] || filters[:on_platforms] ].flatten.compact
- unless platform_filter.empty?
- return false if negative_match(platform_filter, node[:platform])
+ # We treat false / true and nil / not-nil with the same comparison
+ a = nil if a == false
+ b = nil if b == false
+ cmp = a <=> b
+ # This is the case where one is non-nil, and one is nil. The one that is
+ # nil is "greater" (i.e. it should come last).
+ if cmp.nil?
+ return 1 if a.nil?
+ return -1 if b.nil?
end
-
- return true
+ cmp
end
- def block_matches?(node, block)
- return true if block.nil?
- block.call node
+ def map
+ @map ||= {}
end
end
end
diff --git a/lib/chef/platform/handler_map.rb b/lib/chef/platform/handler_map.rb
new file mode 100644
index 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/platform/provider_handler_map.rb b/lib/chef/platform/provider_handler_map.rb
new file mode 100644
index 0000000000..4549d7994e
--- /dev/null
+++ b/lib/chef/platform/provider_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 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 3d8212e24f..9b511f0237 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -20,13 +20,8 @@ require 'chef/log'
require 'chef/exceptions'
require 'chef/mixin/params_validate'
require 'chef/version_constraint/platform'
-
-# This file depends on nearly every provider in chef, but requiring them
-# directly causes circular requires resulting in uninitialized constant errors.
-# Therefore, we do the includes inline rather than up top.
require 'chef/provider'
-
class Chef
class Platform
@@ -34,374 +29,7 @@ class Chef
attr_writer :platforms
def platforms
- @platforms ||= begin
- require 'chef/providers'
-
- {
- :mac_os_x => {
- :default => {
- :package => Chef::Provider::Package::Homebrew,
- :user => Chef::Provider::User::Dscl,
- :group => Chef::Provider::Group::Dscl
- }
- },
- :mac_os_x_server => {
- :default => {
- :package => Chef::Provider::Package::Homebrew,
- :user => Chef::Provider::User::Dscl,
- :group => Chef::Provider::Group::Dscl
- }
- },
- :freebsd => {
- :default => {
- :group => Chef::Provider::Group::Pw,
- :user => Chef::Provider::User::Pw,
- :cron => Chef::Provider::Cron
- }
- },
- :ubuntu => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- },
- ">= 11.10" => {
- :ifconfig => Chef::Provider::Ifconfig::Debian
- }
- # Chef::Provider::Service::Upstart is a candidate to be used in
- # ubuntu versions >= 13.10 but it currently requires all the
- # services to have an entry under /etc/init. We need to update it
- # to use the service ctl apis in order to migrate to using it on
- # ubuntu >= 13.10.
- },
- :gcel => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :linaro => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :raspbian => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :linuxmint => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Upstart,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :debian => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- },
- ">= 6.0" => {
- :service => Chef::Provider::Service::Insserv
- },
- ">= 7.0" => {
- :ifconfig => Chef::Provider::Ifconfig::Debian
- }
- },
- :xenserver => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :xcp => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :centos => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :amazon => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :scientific => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :fedora => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 15" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :opensuse => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Zypper,
- :group => Chef::Provider::Group::Suse
- },
- # Only OpenSuSE 12.3+ should use the Usermod group provider:
- ">= 12.3" => {
- :group => Chef::Provider::Group::Usermod
- }
- },
- :suse => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Zypper,
- :group => Chef::Provider::Group::Gpasswd
- },
- "< 12.0" => {
- :group => Chef::Provider::Group::Suse,
- :service => Chef::Provider::Service::Redhat
- }
- },
- :oracle => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :redhat => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :ibm_powerkvm => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :cloudlinux => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :parallels => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :cron => Chef::Provider::Cron,
- :package => Chef::Provider::Package::Yum,
- :mdadm => Chef::Provider::Mdadm,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :gentoo => {
- :default => {
- :package => Chef::Provider::Package::Portage,
- :service => Chef::Provider::Service::Gentoo,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :arch => {
- :default => {
- :package => Chef::Provider::Package::Pacman,
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :mswin => {
- :default => {
- :env => Chef::Provider::Env::Windows,
- :user => Chef::Provider::User::Windows,
- :group => Chef::Provider::Group::Windows,
- :mount => Chef::Provider::Mount::Windows,
- :batch => Chef::Provider::Batch,
- :powershell_script => Chef::Provider::PowershellScript
- }
- },
- :mingw32 => {
- :default => {
- :env => Chef::Provider::Env::Windows,
- :user => Chef::Provider::User::Windows,
- :group => Chef::Provider::Group::Windows,
- :mount => Chef::Provider::Mount::Windows,
- :batch => Chef::Provider::Batch,
- :powershell_script => Chef::Provider::PowershellScript
- }
- },
- :windows => {
- :default => {
- :env => Chef::Provider::Env::Windows,
- :user => Chef::Provider::User::Windows,
- :group => Chef::Provider::Group::Windows,
- :mount => Chef::Provider::Mount::Windows,
- :batch => Chef::Provider::Batch,
- :powershell_script => Chef::Provider::PowershellScript
- }
- },
- :solaris => {},
- :openindiana => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :cron => Chef::Provider::Cron::Solaris,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :opensolaris => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :cron => Chef::Provider::Cron::Solaris,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :nexentacore => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Solaris,
- :cron => Chef::Provider::Cron::Solaris,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :omnios => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :cron => Chef::Provider::Cron::Solaris,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- }
- },
- :solaris2 => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :cron => Chef::Provider::Cron::Solaris,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- },
- "< 5.11" => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Solaris,
- :cron => Chef::Provider::Cron::Solaris,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- }
- },
- :smartos => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::SmartOS,
- :cron => Chef::Provider::Cron::Solaris,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :netbsd => {
- :default => {
- :group => Chef::Provider::Group::Groupmod
- }
- },
- :openbsd => {
- :default => {
- :group => Chef::Provider::Group::Usermod,
- :package => Chef::Provider::Package::Openbsd,
- :service => Chef::Provider::Service::Openbsd
- }
- },
- :hpux => {
- :default => {
- :group => Chef::Provider::Group::Usermod
- }
- },
- :aix => {
- :default => {
- :group => Chef::Provider::Group::Aix,
- :mount => Chef::Provider::Mount::Aix,
- :ifconfig => Chef::Provider::Ifconfig::Aix,
- :cron => Chef::Provider::Cron::Aix,
- :package => Chef::Provider::Package::Aix,
- :user => Chef::Provider::User::Aix,
- :service => Chef::Provider::Service::Aix
- }
- },
- :exherbo => {
- :default => {
- :package => Chef::Provider::Package::Paludis,
- :service => Chef::Provider::Service::Systemd,
- :cron => Chef::Provider::Cron,
- :mdadm => Chef::Provider::Mdadm
- }
- },
- :default => {
- :mount => Chef::Provider::Mount::Mount,
- :user => Chef::Provider::User::Useradd,
- :group => Chef::Provider::Group::Gpasswd,
- :ifconfig => Chef::Provider::Ifconfig,
- }
- }
- end
+ @platforms ||= { default: {} }
end
include Chef::Mixin::ParamsValidate
@@ -411,7 +39,7 @@ class Chef
name_sym = name
if name.kind_of?(String)
- name.downcase!
+ name = name.downcase
name.gsub!(/\s/, "_")
name_sym = name.to_sym
end
@@ -432,8 +60,6 @@ class Chef
Chef::Log.debug("Chef::Version::Comparable does not know how to parse the platform version: #{version}")
end
end
- else
- Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)")
end
provider_map
end
@@ -550,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
@@ -567,16 +193,20 @@ class Chef
pmap.has_key?(rtkey) ? pmap[rtkey] : nil
end
+ include Chef::Mixin::ConvertToClassName
+
def resource_matching_provider(platform, version, resource_type)
if resource_type.kind_of?(Chef::Resource)
- begin
- Chef::Provider.const_get(resource_type.class.to_s.split('::').last)
- rescue NameError
- nil
+ class_name = resource_type.class.name ? resource_type.class.name.split('::').last :
+ convert_to_class_name(resource_type.resource_name.to_s)
+
+ if Chef::Provider.const_defined?(class_name)
+ Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.")
+ Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to use the resource's DSL.")
+ return Chef::Provider.const_get(class_name)
end
- else
- nil
end
+ nil
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 2517f46dfd..5599c74c2d 100644
--- a/lib/chef/platform/provider_priority_map.rb
+++ b/lib/chef/platform/provider_priority_map.rb
@@ -1,83 +1,11 @@
-
-require 'chef/providers'
+require 'singleton'
+require 'chef/platform/priority_map'
class Chef
class Platform
- class ProviderPriorityMap
+ # @api private
+ class ProviderPriorityMap < Chef::Platform::PriorityMap
include Singleton
-
- def initialize
- load_default_map
- end
-
- def load_default_map
-
- #
- # Linux
- #
-
- # default block for linux O/Sen must come before platform_family exceptions
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Insserv,
- Chef::Provider::Service::Redhat,
- ], os: "linux"
-
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Arch,
- ], platform_family: "arch"
-
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Gentoo,
- ], platform_family: "gentoo"
-
- priority :service, [
- # we can determine what systemd supports accurately
- Chef::Provider::Service::Systemd,
- # on debian-ish system if an upstart script exists that must win over sysv types
- Chef::Provider::Service::Upstart,
- Chef::Provider::Service::Insserv,
- Chef::Provider::Service::Debian,
- Chef::Provider::Service::Invokercd,
- ], platform_family: "debian"
-
- priority :service, [
- Chef::Provider::Service::Systemd,
- Chef::Provider::Service::Insserv,
- Chef::Provider::Service::Redhat,
- ], platform_family: [ "rhel", "fedora", "suse" ]
-
- #
- # BSDen
- #
-
- priority :service, Chef::Provider::Service::Freebsd, os: [ "freebsd", "netbsd" ]
- priority :service, Chef::Provider::Service::Openbsd, os: [ "openbsd" ]
-
- #
- # Solaris-en
- #
-
- priority :service, Chef::Provider::Service::Solaris, os: "solaris2"
-
- #
- # Mac
- #
-
- priority :service, Chef::Provider::Service::Macosx, os: "darwin"
- priority :package, Chef::Provider::Package::Homebrew, os: "darwin"
- end
-
- def priority_map
- @priority_map ||= Chef::NodeMap.new
- end
-
- def priority(*args)
- priority_map.set(*args)
- end
-
end
end
end
diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb
index ff83c871fa..e64189fbd6 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -21,21 +21,14 @@ class Chef
class << self
def windows?
- if RUBY_PLATFORM =~ /mswin|mingw|windows/
- true
- else
- false
- end
+ ChefConfig.windows?
end
def windows_server_2003?
+ # WMI startup shouldn't be performed unless we're on Windows.
return false unless windows?
require 'wmi-lite/wmi'
- # CHEF-4888: Work around ruby #2618, expected to be fixed in Ruby 2.1.0
- # https://github.com/ruby/ruby/commit/588504b20f5cc880ad51827b93e571e32446e5db
- # https://github.com/ruby/ruby/commit/27ed294c7134c0de582007af3c915a635a6506cd
-
wmi = WmiLite::Wmi.new
host = wmi.first_of('Win32_OperatingSystem')
is_server_2003 = (host['version'] && host['version'].start_with?("5.2"))
@@ -43,10 +36,22 @@ class Chef
is_server_2003
end
+ def supports_powershell_execution_bypass?(node)
+ node[:languages] && node[:languages][:powershell] &&
+ node[:languages][:powershell][:version].to_i >= 3
+ end
+
def supports_dsc?(node)
node[:languages] && node[:languages][:powershell] &&
node[:languages][:powershell][:version].to_i >= 4
end
+
+ def supports_dsc_invoke_resource?(node)
+ require 'rubygems'
+ supports_dsc?(node) &&
+ Gem::Version.new(node[:languages][:powershell][:version]) >=
+ Gem::Version.new("5.0.10018.0")
+ end
end
end
end
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
new file mode 100644
index 0000000000..5cc86fd2e7
--- /dev/null
+++ b/lib/chef/platform/resource_priority_map.rb
@@ -0,0 +1,11 @@
+require 'singleton'
+require 'chef/platform/priority_map'
+
+class Chef
+ class Platform
+ # @api private
+ class ResourcePriorityMap < Chef::Platform::PriorityMap
+ include Singleton
+ end
+ end
+end
diff --git a/lib/chef/platform/service_helpers.rb b/lib/chef/platform/service_helpers.rb
index dc0a808c06..d50812e687 100644
--- a/lib/chef/platform/service_helpers.rb
+++ b/lib/chef/platform/service_helpers.rb
@@ -42,34 +42,34 @@ class Chef
# different services is NOT a design concern of this module.
#
def service_resource_providers
- service_resource_providers = []
+ @service_resource_providers ||= [].tap do |service_resource_providers|
- if ::File.exist?("/usr/sbin/update-rc.d")
- service_resource_providers << :debian
- end
+ if ::File.exist?("/usr/sbin/update-rc.d")
+ service_resource_providers << :debian
+ end
- if ::File.exist?("/usr/sbin/invoke-rc.d")
- service_resource_providers << :invokercd
- end
+ if ::File.exist?("/usr/sbin/invoke-rc.d")
+ service_resource_providers << :invokercd
+ end
- if ::File.exist?("/sbin/insserv")
- service_resource_providers << :insserv
- end
+ if ::File.exist?("/sbin/insserv")
+ service_resource_providers << :insserv
+ end
- # debian >= 6.0 has /etc/init but does not have upstart
- if ::File.exist?("/etc/init") && ::File.exist?("/sbin/start")
- service_resource_providers << :upstart
- end
+ # debian >= 6.0 has /etc/init but does not have upstart
+ if ::File.exist?("/etc/init") && ::File.exist?("/sbin/start")
+ service_resource_providers << :upstart
+ end
- if ::File.exist?("/sbin/chkconfig")
- service_resource_providers << :redhat
- end
+ if ::File.exist?("/sbin/chkconfig")
+ service_resource_providers << :redhat
+ end
- if systemd_sanity_check?
- service_resource_providers << :systemd
- end
+ if systemd_sanity_check?
+ service_resource_providers << :systemd
+ end
- service_resource_providers
+ end
end
def config_for_service(service_name)
diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb
index 2ee8a23258..524bdd95b1 100644
--- a/lib/chef/policy_builder/expand_node_object.rb
+++ b/lib/chef/policy_builder/expand_node_object.rb
@@ -24,6 +24,7 @@ require 'chef/rest'
require 'chef/run_context'
require 'chef/config'
require 'chef/node'
+require 'chef/chef_class'
class Chef
module PolicyBuilder
@@ -54,6 +55,15 @@ class Chef
@run_list_expansion = nil
end
+ # This method injects the run_context and provider and resource priority
+ # maps into the Chef class. The run_context has to be injected here, the provider and
+ # resource maps could be moved if a better place can be found to do this work.
+ #
+ # @param run_context [Chef::RunContext] the run_context to inject
+ def setup_chef_class(run_context)
+ Chef.set_run_context(run_context)
+ end
+
def setup_run_context(specific_recipes=nil)
if Chef::Config[:solo]
Chef::Cookbook::FileVendor.fetch_from_disk(Chef::Config[:cookbook_path])
@@ -68,6 +78,10 @@ class Chef
run_context = Chef::RunContext.new(node, cookbook_collection, @events)
end
+ # TODO: this is really obviously not the place for this
+ # FIXME: need same edits
+ setup_chef_class(run_context)
+
# TODO: this is not the place for this. It should be in Runner or
# CookbookCompiler or something.
run_context.load(@run_list_expansion)
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index d368b055f7..5991e3ce10 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -119,6 +119,7 @@ class Chef
@node = Chef::Node.find_or_create(node_name)
validate_policyfile
+ events.policyfile_loaded(policy)
node
rescue Exception => e
events.node_load_failed(node_name, e, Chef::Config)
@@ -239,7 +240,7 @@ class Chef
def policyfile_location
if Chef::Config[:policy_document_native_api]
validate_policy_config!
- "policies/#{policy_group}/#{policy_name}"
+ "policy_groups/#{policy_group}/policies/#{policy_name}"
else
"data/policyfiles/#{deployment_group}"
end
@@ -368,17 +369,20 @@ class Chef
end
def artifact_manifest_for(cookbook_name, lock_data)
- xyz_version = lock_data["dotted_decimal_identifier"]
- rel_url = "cookbook_artifacts/#{cookbook_name}/#{xyz_version}"
- http_api.get(rel_url)
+ identifier = lock_data["identifier"]
+ rel_url = "cookbook_artifacts/#{cookbook_name}/#{identifier}"
+ inflate_cbv_object(http_api.get(rel_url))
rescue Exception => e
- message = "Error loading cookbook #{cookbook_name} at version #{xyz_version} from #{rel_url}: #{e.class} - #{e.message}"
+ message = "Error loading cookbook #{cookbook_name} with identifier #{identifier} from #{rel_url}: #{e.class} - #{e.message}"
err = Chef::Exceptions::CookbookNotFound.new(message)
err.set_backtrace(e.backtrace)
raise err
end
+ def inflate_cbv_object(raw_manifest)
+ Chef::CookbookVersion.from_cb_artifact_data(raw_manifest)
+ 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 680fe9782f..3138704a55 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -22,34 +22,25 @@ require 'chef/mixin/convert_to_class_name'
require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/mixin/why_run'
require 'chef/mixin/shell_out'
-require 'chef/mixin/descendants_tracker'
+require 'chef/mixin/powershell_out'
+require 'chef/mixin/provides'
require 'chef/platform/service_helpers'
require 'chef/node_map'
+require 'forwardable'
class Chef
class Provider
+ require 'chef/mixin/why_run'
+ require 'chef/mixin/shell_out'
+ require 'chef/mixin/provides'
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
- extend Chef::Mixin::DescendantsTracker
+ include Chef::Mixin::PowershellOut
+ extend Chef::Mixin::Provides
- class << self
- def node_map
- @node_map ||= Chef::NodeMap.new
- end
-
- def provides(resource_name, opts={}, &block)
- node_map.set(resource_name.to_sym, true, opts, &block)
- end
-
- # provides a node on the resource (early binding)
- def provides?(node, resource)
- node_map.get(node, resource.resource_name)
- end
-
- # supports the given resource and action (late binding)
- def supports?(resource, action)
- true
- end
+ # supports the given resource and action (late binding)
+ def self.supports?(resource, action)
+ true
end
attr_accessor :new_resource
@@ -75,6 +66,7 @@ class Chef
@recipe_name = nil
@cookbook_name = nil
+ self.class.include_resource_dsl_module(new_resource)
end
def whyrun_mode?
@@ -98,6 +90,9 @@ class Chef
new_resource.cookbook_name
end
+ def check_resource_semantics!
+ end
+
def load_current_resource
raise Chef::Exceptions::Override, "You must override load_current_resource in #{self.to_s}"
end
@@ -123,12 +118,14 @@ class Chef
# TODO: it would be preferable to get the action to be executed in the
# constructor...
+ check_resource_semantics!
+
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
- if !whyrun_mode? || whyrun_supported?
+ if whyrun_mode? && !whyrun_supported?
+ events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
+ else
load_current_resource
events.resource_current_state_loaded(@new_resource, @action, @current_resource)
- elsif whyrun_mode? && !whyrun_supported?
- events.resource_current_state_load_bypassed(@new_resource, @action, @current_resource)
end
define_resource_requirements
@@ -141,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}")
@@ -180,6 +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.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
@@ -197,14 +407,50 @@ class Chef
# manipulating notifies.
converge_by ("evaluate block and run any associated actions") do
- saved_run_context = @run_context
- @run_context = @run_context.dup
- @run_context.resource_collection = Chef::ResourceCollection.new
- instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- @run_context = saved_run_context
+ saved_run_context = run_context
+ begin
+ @run_context = run_context.create_child
+ instance_eval(&block)
+ Chef::Runner.new(run_context).converge
+ ensure
+ @run_context = saved_run_context
+ end
end
end
+ module DeprecatedLWRPClass
+ def const_missing(class_name)
+ if deprecated_constants[class_name.to_sym]
+ Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.")
+ deprecated_constants[class_name.to_sym]
+ else
+ raise NameError, "uninitialized constant Chef::Provider::#{class_name}"
+ end
+ end
+
+ # @api private
+ def register_deprecated_lwrp_class(provider_class, class_name)
+ # Register Chef::Provider::MyProvider with deprecation warnings if you
+ # try to access it
+ if Chef::Provider.const_defined?(class_name, false)
+ Chef::Log.warn "Chef::Provider::#{class_name} already exists! Cannot create deprecation class for #{provider_class}"
+ else
+ deprecated_constants[class_name.to_sym] = provider_class
+ end
+ end
+
+ private
+
+ def deprecated_constants
+ @deprecated_constants ||= {}
+ end
+ end
+ extend DeprecatedLWRPClass
end
end
+
+# Requiring things at the bottom breaks cycles
+require 'chef/chef_class'
+require 'chef/mixin/why_run'
+require 'chef/resource_collection'
+require 'chef/runner'
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
index 354a640e59..5f0134443d 100644
--- a/lib/chef/provider/batch.rb
+++ b/lib/chef/provider/batch.rb
@@ -22,10 +22,20 @@ class Chef
class Provider
class Batch < Chef::Provider::WindowsScript
+ provides :batch, os: "windows"
+
def initialize (new_resource, run_context)
super(new_resource, run_context, '.bat')
end
+ def command
+ basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+ interpreter_path = Chef::Util::PathHelper.join(basepath, interpreter)
+
+ "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\""
+ end
+
def flags
@new_resource.flags.nil? ? '/c' : new_resource.flags + ' /c'
end
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 1590c624f6..6d86e336ec 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -25,6 +25,8 @@ class Chef
class Cron < Chef::Provider
include Chef::Mixin::Command
+ provides :cron, os: ["!aix", "!solaris2"]
+
SPECIAL_TIME_VALUES = [:reboot, :yearly, :annually, :monthly, :weekly, :daily, :midnight, :hourly]
CRON_ATTRIBUTES = [:minute, :hour, :day, :month, :weekday, :time, :command, :mailto, :path, :shell, :home, :environment]
WEEKDAY_SYMBOLS = [:sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday]
diff --git a/lib/chef/provider/cron/aix.rb b/lib/chef/provider/cron/aix.rb
index 473700bf2f..9cacbc6ec9 100644
--- a/lib/chef/provider/cron/aix.rb
+++ b/lib/chef/provider/cron/aix.rb
@@ -23,6 +23,8 @@ class Chef
class Cron
class Aix < Chef::Provider::Cron::Unix
+ provides :cron, os: "aix"
+
private
# For AIX we ignore env vars/[ :mailto, :path, :shell, :home ]
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
index 350f8bda18..01c61e4253 100644
--- a/lib/chef/provider/cron/unix.rb
+++ b/lib/chef/provider/cron/unix.rb
@@ -20,6 +20,7 @@
require 'chef/log'
require 'chef/provider'
+require 'chef/provider/cron'
class Chef
class Provider
@@ -27,6 +28,8 @@ class Chef
class Unix < Chef::Provider::Cron
include Chef::Mixin::ShellOut
+ provides :cron, os: 'solaris2'
+
private
def read_crontab
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 19e7c01ab1..6d9b7f4397 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -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 416393ac60..8892d3a73d 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -43,6 +43,9 @@ class Chef
end
def define_resource_requirements
+ # deep inside FAC we have to assert requirements, so call FACs hook to set that up
+ access_controls.define_resource_requirements
+
requirements.assert(:create) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
@@ -61,7 +64,13 @@ class Chef
is_parent_writable = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
if ::File.exists?(base_dir)
- Chef::FileAccessControl.writable?(base_dir)
+ if Chef::FileAccessControl.writable?(base_dir)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(base_dir, node)
+ Chef::Util::PathHelper.writable_sip_path?(base_dir)
+ else
+ false
+ end
else
is_parent_writable.call(base_dir)
end
@@ -71,7 +80,13 @@ class Chef
# in why run mode & parent directory does not exist no permissions check is required
# If not in why run, permissions must be valid and we rely on prior assertion that dir exists
if !whyrun_mode? || ::File.exists?(parent_directory)
- Chef::FileAccessControl.writable?(parent_directory)
+ if Chef::FileAccessControl.writable?(parent_directory)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node)
+ Chef::Util::PathHelper.writable_sip_path?(@new_resource.path)
+ else
+ false
+ end
else
true
end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
new file mode 100644
index 0000000000..379369ba6e
--- /dev/null
+++ b/lib/chef/provider/dsc_resource.rb
@@ -0,0 +1,164 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+#
+# Copyright:: 2014, Chef Software, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/util/powershell/cmdlet'
+require 'chef/util/dsc/local_configuration_manager'
+require 'chef/mixin/powershell_type_coercions'
+require 'chef/util/dsc/resource_store'
+
+class Chef
+ class Provider
+ class DscResource < Chef::Provider
+ include Chef::Mixin::PowershellTypeCoercions
+
+ provides :dsc_resource, os: "windows"
+
+ def initialize(new_resource, run_context)
+ super
+ @new_resource = new_resource
+ @module_name = new_resource.module_name
+ end
+
+ def action_run
+ if ! test_resource
+ converge_by(generate_description) do
+ result = set_resource
+ end
+ end
+ end
+
+ def load_current_resource
+ end
+
+ def whyrun_supported?
+ true
+ end
+
+ def define_resource_requirements
+ 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::ProviderNotFound,
+ err
+ a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."]
+ a.block_action!
+ end
+ requirements.assert(:run) do |a|
+ a.assertion {
+ meta_configuration['RefreshMode'] == 'Disabled'
+ }
+ err = ["The LCM must have its RefreshMode set to Disabled. "]
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(' ')
+ a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
+ a.block_action!
+ end
+ end
+
+ protected
+
+ def local_configuration_manager
+ @local_configuration_manager ||= Chef::Util::DSC::LocalConfigurationManager.new(
+ node,
+ nil
+ )
+ end
+
+ def resource_store
+ Chef::Util::DSC::ResourceStore.instance
+ end
+
+ def supports_dsc_invoke_resource?
+ run_context && Chef::Platform.supports_dsc_invoke_resource?(node)
+ end
+
+ def generate_description
+ @converge_description
+ end
+
+ def dsc_resource_name
+ new_resource.resource.to_s
+ end
+
+ def module_name
+ @module_name ||= begin
+ found = resource_store.find(dsc_resource_name)
+
+ r = case found.length
+ when 0
+ raise Chef::Exceptions::ResourceNotFound,
+ "Could not find #{dsc_resource_name}. Check to make "\
+ "sure that it shows up when running Get-DscResource"
+ when 1
+ if found[0]['Module'].nil?
+ :none
+ else
+ found[0]['Module']['Name']
+ end
+ else
+ raise Chef::Exceptions::MultipleDscResourcesFound, found
+ end
+ end
+ end
+
+ def test_resource
+ result = invoke_resource(:test)
+ # We really want this information from the verbose stream,
+ # however Invoke-DscResource is not correctly writing to that
+ # stream and instead just dumping to stdout
+ @converge_description = result.stdout
+
+ if result.return_value.is_a?(Array)
+ # WMF Feb 2015 Preview
+ result.return_value[0]["InDesiredState"]
+ else
+ # WMF April 2015 Preview
+ result.return_value["InDesiredState"]
+ end
+ end
+
+ def set_resource
+ result = invoke_resource(:set)
+ result.return_value
+ end
+
+ def invoke_resource(method, output_format=:object)
+ properties = translate_type(@new_resource.properties)
+ switches = "-Method #{method.to_s} -Name #{@new_resource.resource}"\
+ " -Property #{properties} -Verbose"
+
+ if module_name != :none
+ switches += " -Module #{module_name}"
+ end
+
+ cmdlet = Chef::Util::Powershell::Cmdlet.new(
+ node,
+ "Invoke-DscResource #{switches}",
+ output_format
+ )
+ cmdlet.run!
+ end
+
+ def meta_configuration
+ cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object)
+ result = cmdlet.run!
+ result.return_value
+ end
+
+ end
+ end
+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/env.rb b/lib/chef/provider/env.rb
index 815a19bc0c..cf75ff7d85 100644
--- a/lib/chef/provider/env.rb
+++ b/lib/chef/provider/env.rb
@@ -26,6 +26,8 @@ class Chef
include Chef::Mixin::Command
attr_accessor :key_exists
+ provides :env, os: "!windows"
+
def initialize(new_resource, run_context)
super
@key_exists = true
diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb
index dd7cb1bc46..56cebdb888 100644
--- a/lib/chef/provider/env/windows.rb
+++ b/lib/chef/provider/env/windows.rb
@@ -24,6 +24,8 @@ class Chef
class Windows < Chef::Provider::Env
include Chef::Mixin::WindowsEnvHelper
+ provides :env, os: "windows"
+
def create_env
obj = env_obj(@new_resource.key_name)
unless obj
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index c070d29458..5ed7c6ac5b 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -26,8 +26,10 @@ require 'fileutils'
require 'chef/scan_access_control'
require 'chef/mixin/checksum'
require 'chef/mixin/file_class'
+require 'chef/mixin/enforce_ownership_and_permissions'
require 'chef/util/backup'
require 'chef/util/diff'
+require 'chef/util/selinux'
require 'chef/deprecation/provider/file'
require 'chef/deprecation/warnings'
require 'chef/file_content_management/deploy'
@@ -386,10 +388,11 @@ class Chef
def update_file_contents
do_backup unless needs_creating?
- deployment_strategy.deploy(tempfile.path, ::File.realpath(@new_resource.path))
- Chef::Log.info("#{@new_resource} updated file contents #{@new_resource.path}")
+ deployment_strategy.deploy(tempfile.path, ::File.realpath(new_resource.path))
+ Chef::Log.info("#{new_resource} updated file contents #{new_resource.path}")
if managing_content?
- @new_resource.checksum(checksum(@new_resource.path)) # for reporting
+ # save final checksum for reporting.
+ new_resource.final_checksum = checksum(new_resource.path)
end
end
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
index 29738cc046..a802758dce 100644
--- a/lib/chef/provider/group.rb
+++ b/lib/chef/provider/group.rb
@@ -125,13 +125,13 @@ class Chef
def action_create
case @group_exists
when false
- converge_by("create #{@new_resource}") do
+ converge_by("create #{@new_resource.group_name}") do
create_group
Chef::Log.info("#{@new_resource} created")
end
else
if compare_group
- converge_by(["alter group #{@new_resource}"] + change_desc) do
+ converge_by(["alter group #{@new_resource.group_name}"] + change_desc) do
manage_group
Chef::Log.info("#{@new_resource} altered")
end
@@ -141,7 +141,7 @@ class Chef
def action_remove
if @group_exists
- converge_by("remove group #{@new_resource}") do
+ converge_by("remove group #{@new_resource.group_name}") do
remove_group
Chef::Log.info("#{@new_resource} removed")
end
@@ -150,7 +150,7 @@ class Chef
def action_manage
if @group_exists && compare_group
- converge_by(["manage group #{@new_resource}"] + change_desc) do
+ converge_by(["manage group #{@new_resource.group_name}"] + change_desc) do
manage_group
Chef::Log.info("#{@new_resource} managed")
end
@@ -159,7 +159,7 @@ class Chef
def action_modify
if compare_group
- converge_by(["modify group #{@new_resource}"] + change_desc) do
+ converge_by(["modify group #{@new_resource.group_name}"] + change_desc) do
manage_group
Chef::Log.info("#{@new_resource} modified")
end
diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb
index 6ac9d03357..92bb8cb225 100644
--- a/lib/chef/provider/group/aix.rb
+++ b/lib/chef/provider/group/aix.rb
@@ -23,6 +23,7 @@ class Chef
class Provider
class Group
class Aix < Chef::Provider::Group::Groupadd
+ provides :group, platform: 'aix'
def required_binaries
[ "/usr/bin/mkgroup",
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
index a59a94aa98..9775ac8270 100644
--- a/lib/chef/provider/group/dscl.rb
+++ b/lib/chef/provider/group/dscl.rb
@@ -21,6 +21,8 @@ class Chef
class Group
class Dscl < Chef::Provider::Group
+ provides :group, os: 'darwin'
+
def dscl(*args)
host = "."
stdout_result = ""; stderr_result = ""; cmd = "dscl #{host} -#{args.join(' ')}"
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
index 521affac11..432c524acd 100644
--- a/lib/chef/provider/group/gpasswd.rb
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Group
class Gpasswd < Chef::Provider::Group::Groupadd
+ provides :group
def load_current_resource
super
diff --git a/lib/chef/provider/group/groupmod.rb b/lib/chef/provider/group/groupmod.rb
index 7ad762af8d..82b68b8672 100644
--- a/lib/chef/provider/group/groupmod.rb
+++ b/lib/chef/provider/group/groupmod.rb
@@ -21,6 +21,8 @@ class Chef
class Group
class Groupmod < Chef::Provider::Group
+ provides :group, os: 'netbsd'
+
def load_current_resource
super
[ "group", "user" ].each do |binary|
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 7a66ab4d69..5b5c8136f1 100644
--- a/lib/chef/provider/group/pw.rb
+++ b/lib/chef/provider/group/pw.rb
@@ -20,6 +20,7 @@ class Chef
class Provider
class Group
class Pw < Chef::Provider::Group
+ provides :group, platform: 'freebsd'
def load_current_resource
super
@@ -108,7 +109,7 @@ class Chef
else
# Append is not set so we're resetting the membership of
# the group to the given members.
- members_to_be_added = @new_resource.members
+ members_to_be_added = @new_resource.members.dup
@current_resource.members.each do |member|
# No need to re-add a member if it's present in the new
# list of members
diff --git a/lib/chef/provider/group/suse.rb b/lib/chef/provider/group/suse.rb
index 7ac2831d02..b47ea33e80 100644
--- a/lib/chef/provider/group/suse.rb
+++ b/lib/chef/provider/group/suse.rb
@@ -22,6 +22,8 @@ class Chef
class Provider
class Group
class Suse < Chef::Provider::Group::Groupadd
+ provides :group, platform: 'opensuse', platform_version: '< 12.3'
+ provides :group, platform: 'suse', platform_version: '< 12.0'
def load_current_resource
super
diff --git a/lib/chef/provider/group/usermod.rb b/lib/chef/provider/group/usermod.rb
index e9dcc38b43..d78d42d6e1 100644
--- a/lib/chef/provider/group/usermod.rb
+++ b/lib/chef/provider/group/usermod.rb
@@ -23,6 +23,9 @@ class Chef
class Group
class Usermod < Chef::Provider::Group::Groupadd
+ provides :group, os: %w(openbsd solaris2 hpux)
+ provides :group, platform: "opensuse"
+
def load_current_resource
super
end
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
index c9c3da29e0..46d8afc7f6 100644
--- a/lib/chef/provider/group/windows.rb
+++ b/lib/chef/provider/group/windows.rb
@@ -26,6 +26,8 @@ class Chef
class Group
class Windows < Chef::Provider::Group
+ provides :group, os: 'windows'
+
def initialize(new_resource,run_context)
super
@net_group = Chef::Util::Windows::NetGroup.new(@new_resource.group_name)
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 06080c90c3..7869917307 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -39,6 +39,8 @@ require 'erb'
class Chef
class Provider
class Ifconfig < Chef::Provider
+ provides :ifconfig
+
include Chef::Mixin::ShellOut
include Chef::Mixin::Command
@@ -192,7 +194,7 @@ class Chef
private
def add_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
command << " netmask #{@new_resource.mask}" if @new_resource.mask
command << " metric #{@new_resource.metric}" if @new_resource.metric
command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
@@ -200,7 +202,7 @@ class Chef
end
def enable_command
- command = "ifconfig #{@new_resource.device} #{@new_resource.name}"
+ command = "ifconfig #{@new_resource.device} #{@new_resource.target}"
command << " netmask #{@new_resource.mask}" if @new_resource.mask
command << " metric #{@new_resource.metric}" if @new_resource.metric
command << " mtu #{@new_resource.mtu}" if @new_resource.mtu
diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb
index 8fead44bc6..25c3de3040 100644
--- a/lib/chef/provider/ifconfig/aix.rb
+++ b/lib/chef/provider/ifconfig/aix.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Ifconfig
class Aix < Chef::Provider::Ifconfig
+ provides :ifconfig, platform: %w(aix)
def load_current_resource
@current_resource = Chef::Resource::Ifconfig.new(@new_resource.name)
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb
index 7589971143..1e6863c8b5 100644
--- a/lib/chef/provider/ifconfig/debian.rb
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -23,6 +23,8 @@ class Chef
class Provider
class Ifconfig
class Debian < Chef::Provider::Ifconfig
+ provides :ifconfig, platform: %w(ubuntu), platform_version: '>= 11.10'
+ provides :ifconfig, platform: %w(debian), platform_version: '>= 7.0'
INTERFACES_FILE = "/etc/network/interfaces"
INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d"
diff --git a/lib/chef/provider/ifconfig/redhat.rb b/lib/chef/provider/ifconfig/redhat.rb
index ef35b0e012..ee053d1e52 100644
--- a/lib/chef/provider/ifconfig/redhat.rb
+++ b/lib/chef/provider/ifconfig/redhat.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Ifconfig
class Redhat < Chef::Provider::Ifconfig
+ provides :ifconfig, platform_family: %w(fedora rhel)
def initialize(new_resource, run_context)
super(new_resource, run_context)
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index 492ddda6da..a96c382a01 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -19,6 +19,7 @@
#
require 'chef/provider'
+require 'chef/dsl/include_recipe'
class Chef
class Provider
@@ -27,124 +28,71 @@ class Chef
# Base class from which LWRP providers inherit.
class LWRPBase < Provider
- # Chef::Provider::LWRPBase::InlineResources
- # Implementation of inline resource convergence for LWRP providers. See
- # Provider::LWRPBase.use_inline_resources for a longer explanation.
- #
- # This code is restricted to a module so that it can be selectively
- # applied to providers on an opt-in basis.
- module InlineResources
-
- # Class methods for InlineResources. Overrides the `action` DSL method
- # with one that enables inline resource convergence.
- module ClassMethods
- # Defines an action method on the provider, using
- # recipe_eval_with_update_check to execute the given block.
- def action(name, &block)
- define_method("action_#{name}") do
- recipe_eval_with_update_check(&block)
- end
- end
- end
-
- # Executes the given block in a temporary run_context with its own
- # resource collection. After the block is executed, any resources
- # declared inside are converged, and if any are updated, the
- # new_resource will be marked updated.
- def recipe_eval_with_update_check(&block)
- saved_run_context = @run_context
- temp_run_context = @run_context.dup
- @run_context = temp_run_context
- @run_context.resource_collection = Chef::ResourceCollection.new
-
- return_value = instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- return_value
- ensure
- @run_context = saved_run_context
- if temp_run_context.resource_collection.any? {|r| r.updated? }
- new_resource.updated_by_last_action(true)
- end
- end
-
- end
-
- extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::FromFile
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
- # They are not included by its replacment, Chef::DSL::Recipe, but
+ # They are not included by its replacement, Chef::DSL::Recipe, but
# they may be used in existing LWRPs.
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
- def self.build_from_file(cookbook_name, filename, run_context)
- provider_class = nil
- provider_name = filename_to_qualified_string(cookbook_name, filename)
+ # Allow include_recipe from within LWRP provider code
+ include Chef::DSL::IncludeRecipe
+
+ # no-op `load_current_resource`. Allows simple LWRP providers to work
+ # without defining this method explicitly (silences
+ # Chef::Exceptions::Override exception)
+ def load_current_resource
+ end
+
+ # class methods
+ class <<self
+ include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::FromFile
+
+ def build_from_file(cookbook_name, filename, run_context)
+ if LWRPBase.loaded_lwrps[filename]
+ Chef::Log.info("LWRP provider #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
+ return loaded_lwrps[filename]
+ end
- class_name = convert_to_class_name(provider_name)
+ resource_name = filename_to_qualified_string(cookbook_name, filename)
- if Chef::Provider.const_defined?(class_name, false)
- Chef::Log.info("#{class_name} light-weight provider is already initialized -- Skipping loading #{filename}!")
- Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.")
- provider_class = Chef::Provider.const_get(class_name)
- else
+ # We load the class first to give it a chance to set its own name
provider_class = Class.new(self)
- Chef::Provider.const_set(class_name, provider_class)
+ provider_class.provides resource_name.to_sym
provider_class.class_from_file(filename)
- Chef::Log.debug("Loaded contents of #{filename} into a provider named #{provider_name} defined in Chef::Provider::#{class_name}")
- end
- provider_class
- end
+ # Respect resource_name set inside the LWRP
+ provider_class.instance_eval do
+ define_singleton_method(:to_s) do
+ "LWRP provider #{resource_name} from cookbook #{cookbook_name}"
+ end
+ define_singleton_method(:inspect) { to_s }
+ end
- # Enables inline evaluation of resources in provider actions.
- #
- # Without this option, any resources declared inside the LWRP are added
- # to the resource collection after the current position at the time the
- # action is executed. Because they are added to the primary resource
- # collection for the chef run, they can notify other resources outside
- # the LWRP, and potentially be notified by resources outside the LWRP
- # (but this is complicated by the fact that they don't exist until the
- # provider executes). In this mode, it is impossible to correctly set the
- # updated_by_last_action flag on the parent LWRP resource, since it
- # executes and returns before its component resources are run.
- #
- # With this option enabled, each action creates a temporary run_context
- # with its own resource collection, evaluates the action's code in that
- # context, and then converges the resources created. If any resources
- # were updated, then this provider's new_resource will be marked updated.
- #
- # In this mode, resources created within the LWRP cannot interact with
- # external resources via notifies, though notifications to other
- # resources within the LWRP will work. Delayed notifications are executed
- # at the conclusion of the provider's action, *not* at the end of the
- # main chef run.
- #
- # This mode of evaluation is experimental, but is believed to be a better
- # set of tradeoffs than the append-after mode, so it will likely become
- # the default in a future major release of Chef.
- #
- def self.use_inline_resources
- extend InlineResources::ClassMethods
- include InlineResources
- end
+ Chef::Log.debug("Loaded contents of #{filename} into provider #{resource_name} (#{provider_class})")
- # DSL for defining a provider's actions.
- def self.action(name, &block)
- define_method("action_#{name}") do
- instance_eval(&block)
+ LWRPBase.loaded_lwrps[filename] = true
+
+ Chef::Provider.register_deprecated_lwrp_class(provider_class, convert_to_class_name(resource_name))
+
+ provider_class
end
- end
- # no-op `load_current_resource`. Allows simple LWRP providers to work
- # without defining this method explicitly (silences
- # Chef::Exceptions::Override exception)
- def load_current_resource
- end
+ # DSL for defining a provider's actions.
+ def action(name, &block)
+ define_method("action_#{name}") do
+ instance_eval(&block)
+ end
+ end
+
+ protected
+ def loaded_lwrps
+ @loaded_lwrps ||= {}
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/mdadm.rb b/lib/chef/provider/mdadm.rb
index d156e49d48..325f1b5977 100644
--- a/lib/chef/provider/mdadm.rb
+++ b/lib/chef/provider/mdadm.rb
@@ -23,6 +23,8 @@ class Chef
class Provider
class Mdadm < Chef::Provider
+ provides :mdadm
+
def popen4
raise Exception, "deprecated"
end
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 1631d87033..6bdfd5b867 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -24,7 +24,6 @@ require 'chef/provider'
class Chef
class Provider
class Mount < Chef::Provider
-
include Chef::Mixin::ShellOut
attr_accessor :unmount_retries
@@ -43,13 +42,17 @@ class Chef
end
def action_mount
- unless current_resource.mounted
+ if current_resource.mounted
+ if mount_options_unchanged?
+ Chef::Log.debug("#{new_resource} is already mounted")
+ else
+ action_remount
+ end
+ else
converge_by("mount #{current_resource.device} to #{current_resource.mount_point}") do
mount_fs
Chef::Log.info("#{new_resource} mounted")
end
- else
- Chef::Log.debug("#{new_resource} is already mounted")
end
end
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 0d7e11a1b8..510dfde46d 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class Mount
class Aix < Chef::Provider::Mount::Mount
+ provides :mount, platform: %w(aix)
# Override for aix specific handling
def initialize(new_resource, run_context)
@@ -31,7 +32,7 @@ class Chef
@new_resource.options.clear
end
if @new_resource.fstype == "auto"
- @new_resource.fstype = nil
+ @new_resource.send(:clear_fstype)
end
end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 0a6e269d2d..ef074166a9 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -24,6 +24,8 @@ class Chef
class Mount
class Mount < Chef::Provider::Mount
+ provides :mount
+
def initialize(new_resource, run_context)
super
@real_device = nil
diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb
index d8cec24138..deb04d4d7b 100644
--- a/lib/chef/provider/mount/solaris.rb
+++ b/lib/chef/provider/mount/solaris.rb
@@ -27,6 +27,8 @@ class Chef
class Mount
# Mount Solaris File systems
class Solaris < Chef::Provider::Mount
+ provides :mount, platform: %w(openindiana opensolaris nexentacore omnios solaris2 smartos)
+
extend Forwardable
VFSTAB = '/etc/vfstab'.freeze
diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb
index 02aa78919a..87873474b3 100644
--- a/lib/chef/provider/mount/windows.rb
+++ b/lib/chef/provider/mount/windows.rb
@@ -27,6 +27,8 @@ class Chef
class Mount
class Windows < Chef::Provider::Mount
+ provides :mount, os: "windows"
+
def is_volume(name)
name =~ /^\\\\\?\\Volume\{[\w-]+\}\\$/ ? true : false
end
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
index a6b5ab5daa..b7f4aa704b 100644
--- a/lib/chef/provider/ohai.rb
+++ b/lib/chef/provider/ohai.rb
@@ -21,6 +21,7 @@ require 'ohai'
class Chef
class Provider
class Ohai < Chef::Provider
+ provides :ohai
def whyrun_supported?
true
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 2e8e29981b..880104bff7 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.rb
@@ -43,6 +43,12 @@ class Chef
true
end
+ def check_resource_semantics!
+ if new_resource.package_name.is_a?(Array) && new_resource.source != nil
+ raise Chef::Exceptions::InvalidResourceSpecification, "You may not specify both multipackage and source"
+ end
+ end
+
def load_current_resource
end
@@ -136,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
@@ -464,10 +470,7 @@ class Chef
# @return [Array] new_version(s) as an array
def new_version_array
- @new_version_array ||=
- [ new_resource.version ].flatten.map do |v|
- ( v.nil? || v.empty? ) ? nil : v
- end
+ [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
end
# @todo: extract apt/dpkg specific preseeding to a helper class
@@ -487,6 +490,30 @@ class Chef
false
end
end
+
+ private
+
+ def shell_out_with_timeout(*command_args)
+ shell_out(*add_timeout_option(command_args))
+ end
+
+ def shell_out_with_timeout!(*command_args)
+ shell_out!(*add_timeout_option(command_args))
+ end
+
+ def add_timeout_option(command_args)
+ args = command_args.dup
+ if args.last.is_a?(Hash)
+ options = args.pop.dup
+ options[:timeout] = new_resource.timeout if new_resource.timeout
+ options[:timeout] = 900 unless options.has_key?(:timeout)
+ args << options
+ else
+ args << { :timeout => new_resource.timeout ? new_resource.timeout : 900 }
+ end
+ args
+ end
+
end
end
end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
index 107f914c05..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
@@ -52,7 +53,7 @@ class Chef
@package_source_found = ::File.exists?(@new_resource.source)
if @package_source_found
Chef::Log.debug("#{@new_resource} checking pkg status")
- ret = shell_out("installp -L -d #{@new_resource.source}")
+ ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
ret.stdout.each_line do | line |
case line
when /#{@new_resource.package_name}:/
@@ -60,11 +61,12 @@ class Chef
@new_resource.version(fields[2])
end
end
+ raise Chef::Exceptions::Package, "package source #{@new_resource.source} does not provide package #{@new_resource.package_name}" unless @new_resource.version
end
end
Chef::Log.debug("#{@new_resource} checking install state")
- ret = shell_out("lslpp -lcq #{@current_resource.package_name}")
+ ret = shell_out_with_timeout("lslpp -lcq #{@current_resource.package_name}")
ret.stdout.each_line do | line |
case line
when /#{@current_resource.package_name}/
@@ -83,7 +85,7 @@ class Chef
def candidate_version
return @candidate_version if @candidate_version
- ret = shell_out("installp -L -d #{@new_resource.source}")
+ ret = shell_out_with_timeout("installp -L -d #{@new_resource.source}")
ret.stdout.each_line do | line |
case line
when /\w:#{Regexp.escape(@new_resource.package_name)}:(.*)/
@@ -109,10 +111,10 @@ class Chef
def install_package(name, version)
Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
if @new_resource.options.nil?
- shell_out!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
+ shell_out_with_timeout!( "installp -aYF -d #{@new_resource.source} #{@new_resource.package_name}" )
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
else
- shell_out!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
+ shell_out_with_timeout!( "installp -aYF #{expand_options(@new_resource.options)} -d #{@new_resource.source} #{@new_resource.package_name}" )
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
end
end
@@ -121,10 +123,10 @@ class Chef
def remove_package(name, version)
if @new_resource.options.nil?
- shell_out!( "installp -u #{name}" )
+ shell_out_with_timeout!( "installp -u #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
else
- shell_out!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "installp -u #{expand_options(@new_resource.options)} #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
index e426b51992..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
@@ -62,7 +63,7 @@ class Chef
installed_version = nil
candidate_version = nil
- shell_out!("apt-cache#{expand_options(default_release_options)} policy #{pkg}", {:timeout=>900}).stdout.each_line do |line|
+ shell_out_with_timeout!("apt-cache#{expand_options(default_release_options)} policy #{pkg}").stdout.each_line do |line|
case line
when /^\s{2}Installed: (.+)$/
installed_version = $1
@@ -78,7 +79,7 @@ class Chef
if candidate_version == '(none)'
# This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
is_virtual_package = true
- showpkg = shell_out!("apt-cache showpkg #{pkg}", {:timeout => 900}).stdout
+ showpkg = shell_out_with_timeout!("apt-cache showpkg #{pkg}").stdout
providers = Hash.new
showpkg.rpartition(/Reverse Provides: ?#{$/}/)[2].each_line do |line|
provider, version = line.split
@@ -175,7 +176,7 @@ class Chef
# interactive prompts. Command is run with default localization rather
# than forcing locale to "C", so command output may not be stable.
def run_noninteractive(command)
- shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil }, :timeout => @new_resource.timeout)
+ shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
end
end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index 11691a2479..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,32 +52,28 @@ 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("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
package_installed = nil
Chef::Log.debug("#{@new_resource} checking install state")
- status = shell_out("dpkg -s #{@current_resource.package_name}")
+ status = shell_out_with_timeout("dpkg -s #{@current_resource.package_name}")
status.stdout.each_line do |line|
case line
when DPKG_INSTALLED
@@ -134,13 +128,13 @@ class Chef
run_noninteractive("dpkg-reconfigure #{name}")
end
- # Runs command via shell_out with magic environment to disable
+ # Runs command via shell_out_with_timeout with magic environment to disable
# interactive prompts. Command is run with default localization rather
# than forcing locale to "C", so command output may not be stable.
#
# FIXME: This should be "LC_ALL" => "en_US.UTF-8" in order to stabilize the output and get UTF-8
def run_noninteractive(command)
- shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+ shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
end
end
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
index 90727b738d..2f7880bf08 100644
--- a/lib/chef/provider/package/easy_install.rb
+++ b/lib/chef/provider/package/easy_install.rb
@@ -32,10 +32,10 @@ class Chef
begin
# first check to see if we can import it
- output = shell_out!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
if output.include? "ImportError"
# then check to see if its on the path
- output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
if output.downcase.include? "#{name.downcase}"
check = true
end
@@ -73,10 +73,10 @@ class Chef
package_version = nil
if install_check(module_name)
begin
- output = shell_out!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
package_version = output.strip
rescue
- output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+ output = shell_out_with_timeout!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
output_array = output.gsub(/[\[\]]/,'').split(/\s*,\s*/)
package_path = ""
@@ -107,7 +107,7 @@ class Chef
return @candidate_version if @candidate_version
# do a dry run to get the latest version
- result = shell_out!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
+ result = shell_out_with_timeout!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
@candidate_version = result.stdout[/(.*)Best match: (.*) (.*)$/, 3]
@candidate_version
end
diff --git a/lib/chef/provider/package/freebsd/base.rb b/lib/chef/provider/package/freebsd/base.rb
index 6a3b97a4fd..7c032b3787 100644
--- a/lib/chef/provider/package/freebsd/base.rb
+++ b/lib/chef/provider/package/freebsd/base.rb
@@ -47,7 +47,7 @@ class Chef
# Otherwise look up the path to the ports directory using 'whereis'
else
- whereis = shell_out!("whereis -s #{port}", :env => nil)
+ whereis = shell_out_with_timeout!("whereis -s #{port}", :env => nil)
unless path = whereis.stdout[/^#{Regexp.escape(port)}:\s+(.+)$/, 1]
raise Chef::Exceptions::Package, "Could not find port with the name #{port}"
end
@@ -57,7 +57,7 @@ class Chef
def makefile_variable_value(variable, dir = nil)
options = dir ? { :cwd => dir } : {}
- make_v = shell_out!("make -V #{variable}", options.merge!(:env => nil, :returns => [0,1]))
+ make_v = shell_out_with_timeout!("make -V #{variable}", options.merge!(:env => nil, :returns => [0,1]))
make_v.exitstatus.zero? ? make_v.stdout.strip.split($\).first : nil # $\ is the line separator, i.e. newline.
end
end
diff --git a/lib/chef/provider/package/freebsd/pkg.rb b/lib/chef/provider/package/freebsd/pkg.rb
index ebbfbb19b4..33a8c2c108 100644
--- a/lib/chef/provider/package/freebsd/pkg.rb
+++ b/lib/chef/provider/package/freebsd/pkg.rb
@@ -34,24 +34,24 @@ class Chef
case @new_resource.source
when /^http/, /^ftp/
if @new_resource.source =~ /\/$/
- shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
else
- shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
end
Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
when /^\//
- shell_out!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
+ shell_out_with_timeout!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
else
- shell_out!("pkg_add -r #{latest_link_name}", :env => nil).status
+ shell_out_with_timeout!("pkg_add -r #{latest_link_name}", :env => nil).status
end
end
end
def remove_package(name, version)
- shell_out!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status
+ shell_out_with_timeout!("pkg_delete #{package_name}-#{version || @current_resource.version}", :env => nil).status
end
# The name of the package (without the version number) as understood by pkg_add and pkg_info.
@@ -72,7 +72,7 @@ class Chef
end
def current_installed_version
- pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
+ pkg_info = shell_out_with_timeout!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
pkg_info.stdout[/^#{Regexp.escape(package_name)}-(.+)/, 1]
end
diff --git a/lib/chef/provider/package/freebsd/pkgng.rb b/lib/chef/provider/package/freebsd/pkgng.rb
index bfe6dca617..2fdc9dda71 100644
--- a/lib/chef/provider/package/freebsd/pkgng.rb
+++ b/lib/chef/provider/package/freebsd/pkgng.rb
@@ -28,11 +28,11 @@ class Chef
unless @current_resource.version
case @new_resource.source
when /^(http|ftp|\/)/
- shell_out!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg add#{expand_options(@new_resource.options)} #{@new_resource.source}", :env => { 'LC_ALL' => nil }).status
Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
else
- shell_out!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { 'LC_ALL' => nil }).status
+ shell_out_with_timeout!("pkg install -y#{expand_options(@new_resource.options)} #{name}", :env => { 'LC_ALL' => nil }).status
end
end
end
@@ -40,11 +40,11 @@ class Chef
def remove_package(name, version)
options = @new_resource.options && @new_resource.options.sub(repo_regex, '')
options && !options.empty? || options = nil
- shell_out!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
+ shell_out_with_timeout!("pkg delete -y#{expand_options(options)} #{name}#{version ? '-' + version : ''}", :env => nil).status
end
def current_installed_version
- pkg_info = shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
+ pkg_info = shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
pkg_info.stdout[/^Version +: (.+)$/, 1]
end
@@ -63,7 +63,7 @@ class Chef
options = $1
end
- pkg_query = shell_out!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil)
+ pkg_query = shell_out_with_timeout!("pkg rquery#{expand_options(options)} '%v' #{@new_resource.package_name}", :env => nil)
pkg_query.exitstatus.zero? ? pkg_query.stdout.strip.split(/\n/).last : nil
end
diff --git a/lib/chef/provider/package/freebsd/port.rb b/lib/chef/provider/package/freebsd/port.rb
index 8b191179f0..3fbd002214 100644
--- a/lib/chef/provider/package/freebsd/port.rb
+++ b/lib/chef/provider/package/freebsd/port.rb
@@ -26,18 +26,18 @@ class Chef
include PortsHelper
def install_package(name, version)
- shell_out!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
+ shell_out_with_timeout!("make -DBATCH install clean", :timeout => 1800, :env => nil, :cwd => port_dir).status
end
def remove_package(name, version)
- shell_out!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
+ shell_out_with_timeout!("make deinstall", :timeout => 300, :env => nil, :cwd => port_dir).status
end
def current_installed_version
pkg_info = if @new_resource.supports_pkgng?
- shell_out!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
+ shell_out_with_timeout!("pkg info \"#{@new_resource.package_name}\"", :env => nil, :returns => [0,70])
else
- shell_out!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1])
+ shell_out_with_timeout!("pkg_info -E \"#{@new_resource.package_name}*\"", :env => nil, :returns => [0,1])
end
pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+)/, 1]
end
diff --git a/lib/chef/provider/package/homebrew.rb b/lib/chef/provider/package/homebrew.rb
index e043c01f56..e5c45f0a62 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -26,8 +26,8 @@ class Chef
class Package
class Homebrew < Chef::Provider::Package
+ provides :package, os: "darwin", override: true
provides :homebrew_package
- provides :package, os: ["mac_os_x", "darwin"]
include Chef::Mixin::HomebrewUser
@@ -126,7 +126,8 @@ class Chef
homebrew_user = Etc.getpwuid(homebrew_uid)
Chef::Log.debug "Executing '#{command}' as user '#{homebrew_user.name}'"
- output = shell_out!(command, :timeout => 1800, :user => homebrew_uid, :environment => { 'HOME' => homebrew_user.dir, 'RUBYOPT' => nil })
+ # FIXME: this 1800 second default timeout should be deprecated
+ output = shell_out_with_timeout!(command, :timeout => 1800, :user => homebrew_uid, :environment => { 'HOME' => homebrew_user.dir, 'RUBYOPT' => nil })
output.stdout.chomp
end
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
index 87022d770a..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
@@ -42,14 +43,14 @@ class Chef
end
def get_current_version
- shell_out("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
+ shell_out_with_timeout("pkg info #{@new_resource.package_name}").stdout.each_line do |line|
return $1.split[0] if line =~ /^\s+Version: (.*)/
end
return nil
end
def get_candidate_version
- shell_out!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
+ shell_out_with_timeout!("pkg info -r #{new_resource.package_name}").stdout.each_line do |line|
return $1.split[0] if line =~ /Version: (.*)/
end
return nil
@@ -73,7 +74,7 @@ class Chef
else
normal_command
end
- shell_out(command)
+ shell_out_with_timeout(command)
end
def upgrade_package(name, version)
@@ -82,7 +83,7 @@ class Chef
def remove_package(name, version)
package_name = "#{name}@#{version}"
- shell_out!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
+ shell_out_with_timeout!( "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}" )
end
end
end
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
index b252344c99..c7ea71ac8c 100644
--- a/lib/chef/provider/package/macports.rb
+++ b/lib/chef/provider/package/macports.rb
@@ -3,8 +3,8 @@ class Chef
class Package
class Macports < Chef::Provider::Package
- provides :macports_package
provides :package, os: "darwin"
+ provides :macports_package
def load_current_resource
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@@ -49,21 +49,21 @@ class Chef
unless @current_resource.version == version
command = "port#{expand_options(@new_resource.options)} install #{name}"
command << " @#{version}" if version and !version.empty?
- shell_out!(command)
+ shell_out_with_timeout!(command)
end
end
def purge_package(name, version)
command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
command << " @#{version}" if version and !version.empty?
- shell_out!(command)
+ shell_out_with_timeout!(command)
end
def remove_package(name, version)
command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
command << " @#{version}" if version and !version.empty?
- shell_out!(command)
+ shell_out_with_timeout!(command)
end
def upgrade_package(name, version)
@@ -76,14 +76,14 @@ class Chef
# that hasn't been installed.
install_package(name, version)
elsif current_version != version
- shell_out!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
+ shell_out_with_timeout!( "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}" )
end
end
private
def get_response_from_command(command)
output = nil
- status = shell_out(command)
+ status = shell_out_with_timeout(command)
begin
output = status.stdout
rescue Exception
diff --git a/lib/chef/provider/package/openbsd.rb b/lib/chef/provider/package/openbsd.rb
index f0931d7555..83fc09c8ae 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -22,8 +22,8 @@
require 'chef/resource/package'
require 'chef/provider/package'
-require 'chef/mixin/shell_out'
require 'chef/mixin/get_source_from_package'
+require 'chef/exceptions'
class Chef
class Provider
@@ -31,31 +31,49 @@ class Chef
class Openbsd < Chef::Provider::Package
provides :package, os: "openbsd"
+ provides :openbsd_package
include Chef::Mixin::ShellOut
include Chef::Mixin::GetSourceFromPackage
def initialize(*args)
super
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @new_resource.source(pkg_path) if !@new_resource.source
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
end
def load_current_resource
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource.package_name(new_resource.package_name)
@current_resource.version(installed_version)
@current_resource
end
+ def define_resource_requirements
+ super
+
+ # Below are incomplete/missing features for this package provider
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !new_resource.source }
+ a.failure_message(Chef::Exceptions::Package, 'The openbsd package provider does not support the source attribute')
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion do
+ if new_resource.package_name =~ /^(.+?)--(.+)/
+ !new_resource.version
+ else
+ true
+ end
+ end
+ a.failure_message(Chef::Exceptions::Package, 'The openbsd package provider does not support providing a version and flavor')
+ end
+ end
+
def install_package(name, version)
unless @current_resource.version
- version_string = ''
- version_string += "-#{version}" if version
if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add
name = parts[1]
end
- shell_out!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => @new_resource.source}).status
- Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
+ shell_out_with_timeout!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => pkg_path}).status
+ Chef::Log.debug("#{new_resource.package_name} installed")
end
end
@@ -65,38 +83,51 @@ class Chef
if parts = name.match(/^(.+?)--(.+)/)
name = parts[1]
end
- shell_out!("pkg_delete #{name}#{version_string}", :env => nil).status
+ shell_out_with_timeout!("pkg_delete #{name}#{version_string}", :env => nil).status
end
private
def installed_version
- if parts = @new_resource.package_name.match(/^(.+?)--(.+)/)
+ if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
name = parts[1]
else
- name = @new_resource.package_name
+ name = new_resource.package_name
end
- pkg_info = shell_out!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
+ pkg_info = shell_out_with_timeout!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
result = pkg_info.stdout[/^inst:#{Regexp.escape(name)}-(.+?)\s/, 1]
- Chef::Log.debug("installed_version of '#{@new_resource.package_name}' is '#{result}'")
+ Chef::Log.debug("installed_version of '#{new_resource.package_name}' is '#{result}'")
result
end
def candidate_version
@candidate_version ||= begin
- version_string = ''
- version_string += "-#{version}" if @new_resource.version
- pkg_info = shell_out!("pkg_info -I \"#{@new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1])
- if parts = @new_resource.package_name.match(/^(.+?)--(.+)/)
- result = pkg_info.stdout[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
+ results = []
+ shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
+ if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
+ results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
+ else
+ results << line[/^#{Regexp.escape(new_resource.package_name)}-(.+?)\s/, 1]
+ end
+ end
+ results = results.reject(&:nil?)
+ Chef::Log.debug("candidate versions of '#{new_resource.package_name}' are '#{results}'")
+ case results.length
+ when 0
+ []
+ when 1
+ results[0]
else
- result = pkg_info.stdout[/^#{Regexp.escape(@new_resource.package_name)}-(.+?)\s/, 1]
+ raise Chef::Exceptions::Package, "#{new_resource.name} has multiple matching candidates. Please use a more specific name" if results.length > 1
end
- Chef::Log.debug("candidate_version of '#{@new_resource.package_name}' is '#{result}'")
- result
end
end
+ def version_string
+ ver = ''
+ ver += "-#{new_resource.version}" if new_resource.version
+ end
+
def pkg_path
ENV['PKG_PATH'] || "http://ftp.OpenBSD.org/pub/#{node.kernel.name}/#{node.kernel.release}/packages/#{node.kernel.machine}/"
end
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index f16fc811f5..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
@@ -34,7 +35,7 @@ class Chef
@current_resource.version(nil)
Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}")
- status = shell_out("pacman -Qi #{@new_resource.package_name}")
+ status = shell_out_with_timeout("pacman -Qi #{@new_resource.package_name}")
status.stdout.each_line do |line|
case line
when /^Version(\s?)*: (.+)$/
@@ -62,7 +63,7 @@ class Chef
package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
- status = shell_out("pacman -Sl")
+ status = shell_out_with_timeout("pacman -Sl")
status.stdout.each_line do |line|
case line
when /^(#{package_repos}) #{Regexp.escape(@new_resource.package_name)} (.+)$/
@@ -85,7 +86,7 @@ class Chef
end
def install_package(name, version)
- shell_out!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
end
def upgrade_package(name, version)
@@ -93,7 +94,7 @@ class Chef
end
def remove_package(name, version)
- shell_out!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}" )
end
def purge_package(name, version)
diff --git a/lib/chef/provider/package/paludis.rb b/lib/chef/provider/package/paludis.rb
index 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 bb047ad2fa..95782a6774 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -25,6 +25,10 @@ class Chef
class Provider
class Package
class Portage < Chef::Provider::Package
+
+ provides :package, platform: "gentoo"
+ provides :portage_package
+
PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
def load_current_resource
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index f10fe23c71..c5d52a8384 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -17,7 +17,6 @@
#
require 'chef/provider/package'
require 'chef/mixin/command'
-require 'chef/mixin/shell_out'
require 'chef/resource/package'
require 'chef/mixin/get_source_from_package'
@@ -60,9 +59,9 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
@current_resource.package_name($1)
@new_resource.version($2)
@candidate_version = $2
@@ -76,10 +75,10 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking install state")
- @rpm_status = shell_out("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
+ @rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
@rpm_status.stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^([\w\d+_.-]+)\s([\w\d~_.-]+)$/
Chef::Log.debug("#{@new_resource} current version is #{$2}")
@current_resource.version($2)
end
@@ -90,12 +89,12 @@ class Chef
def install_package(name, version)
unless @current_resource.version
- shell_out!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -i #{@new_resource.source}" )
else
if allow_downgrade
- shell_out!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -U --oldpackage #{@new_resource.source}" )
else
- shell_out!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -U #{@new_resource.source}" )
end
end
end
@@ -104,9 +103,9 @@ class Chef
def remove_package(name, version)
if version
- shell_out!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}-#{version}" )
else
- shell_out!( "rpm #{@new_resource.options} -e #{name}" )
+ shell_out_with_timeout!( "rpm #{@new_resource.options} -e #{name}" )
end
end
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index ff1e346cd1..729f755b2a 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -32,14 +32,7 @@ require 'rubygems/version'
require 'rubygems/dependency'
require 'rubygems/spec_fetcher'
require 'rubygems/platform'
-
-# Compatibility note: Rubygems 2.0 removes rubygems/format in favor of
-# rubygems/package.
-begin
- require 'rubygems/format'
-rescue LoadError
- require 'rubygems/package'
-end
+require 'rubygems/package'
require 'rubygems/dependency_installer'
require 'rubygems/uninstaller'
require 'rubygems/specification'
@@ -380,8 +373,13 @@ class Chef
# Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef
# Default to installing somewhere more functional
if new_resource.options && new_resource.options.kind_of?(Hash)
- msg = "options should be a string instead of a hash\n"
- msg << "in #{new_resource} from #{new_resource.source_line}"
+ msg = [
+ "Gem options must be passed to gem_package as a string instead of a hash when",
+ "using this installation of Chef because it runs with its own packaged Ruby. A hash",
+ "may only be used when installing a gem to the same Ruby installation that Chef is",
+ "running under. See https://docs.chef.io/resource_gem_package.html for more information.",
+ "Error raised at #{new_resource} from #{new_resource.source_line}",
+ ].join("\n")
raise ArgumentError, msg
end
gem_location = find_gem_by_path
@@ -396,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
@@ -533,13 +531,16 @@ class Chef
def install_via_gem_command(name, version)
if @new_resource.source =~ /\.gem$/i
name = @new_resource.source
+ elsif @new_resource.clear_sources
+ src = ' --clear-sources'
+ src << (@new_resource.source && " --source=#{@new_resource.source}" || '')
else
- src = @new_resource.source && " --source=#{@new_resource.source} --source=https://rubygems.org"
+ src = @new_resource.source && " --source=#{@new_resource.source} --source=https://rubygems.org"
end
if !version.nil? && version.length > 0
- shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
else
- shell_out!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
end
end
@@ -563,9 +564,9 @@ class Chef
def uninstall_via_gem_command(name, version)
if version
- shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
else
- shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
end
end
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 7cef91953a..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
@@ -43,7 +44,7 @@ class Chef
def check_package_state(name)
Chef::Log.debug("#{@new_resource} checking package #{name}")
version = nil
- info = shell_out!("/opt/local/sbin/pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1])
+ info = shell_out_with_timeout!("/opt/local/sbin/pkg_info", "-E", "#{name}*", :env => nil, :returns => [0,1])
if info.stdout
version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
@@ -60,11 +61,11 @@ class Chef
return @candidate_version if @candidate_version
name = nil
version = nil
- pkg = shell_out!("/opt/local/bin/pkgin se #{new_resource.package_name}", :env => nil, :returns => [0,1])
+ pkg = shell_out_with_timeout!("/opt/local/bin/pkgin", "se", new_resource.package_name, :env => nil, :returns => [0,1])
pkg.stdout.each_line do |line|
case line
when /^#{new_resource.package_name}/
- name, version = line.split[0].split(/-([^-]+)$/)
+ name, version = line.split(/[; ]/)[0].split(/-([^-]+)$/)
end
end
@candidate_version = version
@@ -74,7 +75,7 @@ class Chef
def install_package(name, version)
Chef::Log.debug("#{@new_resource} installing package #{name} version #{version}")
package = "#{name}-#{version}"
- out = shell_out!("/opt/local/bin/pkgin -y install #{package}", :env => nil)
+ out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "install", package, :env => nil)
end
def upgrade_package(name, version)
@@ -85,7 +86,7 @@ class Chef
def remove_package(name, version)
Chef::Log.debug("#{@new_resource} removing package #{name} version #{version}")
package = "#{name}"
- out = shell_out!("/opt/local/bin/pkgin -y remove #{package}", :env => nil)
+ out = shell_out_with_timeout!("/opt/local/bin/pkgin", "-y", "remove", package, :env => nil)
end
end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
index a2cfd93ef6..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)
@@ -55,7 +57,7 @@ class Chef
@package_source_found = ::File.exists?(@new_resource.source)
if @package_source_found
Chef::Log.debug("#{@new_resource} checking pkg status")
- shell_out("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
+ shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}").stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@new_resource.version($1)
@@ -65,7 +67,7 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking install state")
- status = shell_out("pkginfo -l #{@current_resource.package_name}")
+ status = shell_out_with_timeout("pkginfo -l #{@current_resource.package_name}")
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@@ -87,7 +89,7 @@ class Chef
def candidate_version
return @candidate_version if @candidate_version
- status = shell_out("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
+ status = shell_out_with_timeout("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}")
status.stdout.each_line do |line|
case line
when /VERSION:\s+(.+)/
@@ -110,7 +112,7 @@ class Chef
else
command = "pkgadd -n -d #{@new_resource.source} all"
end
- shell_out!(command)
+ shell_out_with_timeout!(command)
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
else
if ::File.directory?(@new_resource.source) # CHEF-4469
@@ -118,17 +120,17 @@ class Chef
else
command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
end
- shell_out!(command)
+ shell_out_with_timeout!(command)
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
end
end
def remove_package(name, version)
if @new_resource.options.nil?
- shell_out!( "pkgrm -n #{name}" )
+ shell_out_with_timeout!( "pkgrm -n #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
else
- shell_out!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
index 143d82f111..7ff0b71807 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -16,14 +16,18 @@
# limitations under the License.
#
+require 'chef/mixin/uris'
require 'chef/resource/windows_package'
require 'chef/provider/package'
require 'chef/util/path_helper'
+require 'chef/mixin/checksum'
class Chef
class Provider
class Package
class Windows < Chef::Provider::Package
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
provides :package, os: "windows"
provides :windows_package, os: "windows"
@@ -36,19 +40,23 @@ class Chef
# load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode?
def load_current_resource
- @new_resource.source(Chef::Util::PathHelper.validate_path(@new_resource.source))
-
@current_resource = Chef::Resource::WindowsPackage.new(@new_resource.name)
- @current_resource.version(package_provider.installed_version)
- @new_resource.version(package_provider.package_version)
- @current_resource
+ if downloadable_file_missing?
+ Chef::Log.debug("We do not know the version of #{new_resource.source} because the file is not downloaded")
+ current_resource.version(:unknown.to_s)
+ else
+ current_resource.version(package_provider.installed_version)
+ new_resource.version(package_provider.package_version)
+ end
+
+ current_resource
end
def package_provider
@package_provider ||= begin
case installer_type
when :msi
- Chef::Provider::Package::Windows::MSI.new(@new_resource)
+ Chef::Provider::Package::Windows::MSI.new(resource_for_provider)
else
raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
end
@@ -71,6 +79,17 @@ class Chef
end
end
+ def action_install
+ if uri_scheme?(new_resource.source)
+ download_source_file
+ load_current_resource
+ else
+ validate_content!
+ end
+
+ super
+ end
+
# Chef::Provider::Package action_install + action_remove call install_package + remove_package
# Pass those calls to the correct sub-provider
def install_package(name, version)
@@ -80,6 +99,71 @@ class Chef
def remove_package(name, version)
package_provider.remove_package(name, version)
end
+
+ # @return [Array] new_version(s) as an array
+ def new_version_array
+ # Because the one in the parent caches things
+ [new_resource.version]
+ end
+
+ private
+
+ def downloadable_file_missing?
+ uri_scheme?(new_resource.source) && !::File.exists?(source_location)
+ end
+
+ def resource_for_provider
+ @resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r|
+ r.source(Chef::Util::PathHelper.validate_path(source_location))
+ r.timeout(new_resource.timeout)
+ r.returns(new_resource.returns)
+ r.options(new_resource.options)
+ end
+ end
+
+ def download_source_file
+ source_resource.run_action(:create)
+ Chef::Log.debug("#{@new_resource} fetched source file to #{source_resource.path}")
+ end
+
+ def source_resource
+ @source_resource ||= Chef::Resource::RemoteFile.new(default_download_cache_path, run_context).tap do |r|
+ r.source(new_resource.source)
+ r.checksum(new_resource.checksum)
+ r.backup(false)
+
+ if new_resource.remote_file_attributes
+ new_resource.remote_file_attributes.each do |(k,v)|
+ r.send(k.to_sym, v)
+ end
+ end
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::URI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def source_location
+ if uri_scheme?(new_resource.source)
+ source_resource.path
+ else
+ Chef::Util::PathHelper.cleanpath(new_resource.source)
+ end
+ end
+
+ def validate_content!
+ if new_resource.checksum
+ source_checksum = checksum(source_location)
+ if new_resource.checksum != source_checksum
+ raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(source_checksum))
+ end
+ end
+ end
+
end
end
end
diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb
index 938452945e..31faa78215 100644
--- a/lib/chef/provider/package/windows/msi.rb
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -56,7 +56,7 @@ class Chef
Chef::Log.debug("#{@new_resource} installing MSI package '#{@new_resource.source}'")
shell_out!("msiexec /qn /i \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
end
-
+
def remove_package(name, version)
# We could use MsiConfigureProduct here, but we'll start off with msiexec
Chef::Log.debug("#{@new_resource} removing MSI package '#{@new_resource.source}'")
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 2dbda60750..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");
@@ -18,7 +18,7 @@
require 'chef/config'
require 'chef/provider/package'
-require 'chef/mixin/shell_out'
+require 'chef/mixin/which'
require 'chef/resource/package'
require 'singleton'
require 'chef/mixin/get_source_from_package'
@@ -28,6 +28,7 @@ class Chef
class Package
class Yum < Chef::Provider::Package
+ provides :package, platform_family: %w(rhel fedora)
provides :yum_package, os: "linux"
class RPMUtils
@@ -646,10 +647,12 @@ class Chef
# Cache for our installed and available packages, pulled in from yum-dump.py
class YumCache
- include Chef::Mixin::Command
+ include Chef::Mixin::Which
include Chef::Mixin::ShellOut
include Singleton
+ attr_accessor :yum_binary
+
def initialize
@rpmdb = RPMDb.new
@@ -713,12 +716,11 @@ class Chef
status = nil
begin
- status = shell_out!("/usr/bin/python #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
+ status = shell_out!("#{python_bin} #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
status.stdout.each_line do |line|
one_line = true
line.chomp!
-
if line =~ %r{\[option (.*)\] (.*)}
if $1 == "installonlypkgs"
@allow_multi_install = $2.split
@@ -780,6 +782,32 @@ class Chef
@next_refresh = :none
end
+ def python_bin
+ yum_executable = which(yum_binary)
+ if yum_executable && shabang?(yum_executable)
+ extract_interpreter(yum_executable)
+ else
+ Chef::Log.warn("Yum executable not found or doesn't start with #!. Using default python.")
+ "/usr/bin/python"
+ end
+ rescue StandardError => e
+ Chef::Log.warn("An error occured attempting to determine correct python executable. Using default.")
+ Chef::Log.debug(e)
+ "/usr/bin/python"
+ end
+
+ def extract_interpreter(file)
+ ::File.open(file, 'r', &:readline)[2..-1].chomp
+ end
+
+ def shabang?(file)
+ ::File.open(file, 'r') do |f|
+ f.read(2) == '#!'
+ end
+ rescue Errno::ENOENT
+ false
+ end
+
def reload
@next_refresh = :all
end
@@ -954,11 +982,31 @@ class Chef
super
@yum = YumCache.instance
+ @yum.yum_binary = yum_binary
+ end
+
+ def yum_binary
+ @yum_binary ||=
+ begin
+ yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
+ yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
+ end
end
# Extra attributes
#
+ def arch_for_name(n)
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch
+ elsif @arch
+ idx = package_name_array.index(n)
+ as_array(@arch)[idx]
+ else
+ nil
+ end
+ end
+
def arch
if @new_resource.respond_to?("arch")
@new_resource.arch
@@ -967,6 +1015,12 @@ class Chef
end
end
+ def set_arch(arch)
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch(arch)
+ end
+ end
+
def flush_cache
if @new_resource.respond_to?("flush_cache")
@new_resource.flush_cache
@@ -978,12 +1032,14 @@ class Chef
# Helpers
#
- def yum_arch
+ def yum_arch(arch)
arch ? ".#{arch}" : nil
end
def yum_command(command)
- status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+ command = "#{yum_binary} #{command}"
+ Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
+ status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
# This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
# considered fatal - meaning the rpm is still successfully installed. These issue
@@ -1000,7 +1056,7 @@ class Chef
if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
"so running install again to verify.")
- status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+ status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
break
end
end
@@ -1060,46 +1116,66 @@ class Chef
end
end
- # Don't overwrite an existing arch
- unless arch
- parse_arch
- end
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
+ installed_version = []
+ @candidate_version = []
+ @arch = []
if @new_resource.source
unless ::File.exists?(@new_resource.source)
raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
end
Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
case line
when /([\w\d_.-]+)\s([\w\d_.-]+)/
@current_resource.package_name($1)
@new_resource.version($2)
end
end
- end
-
- if @new_resource.version
- new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch}"
+ @candidate_version << @new_resource.version
+ installed_version << @yum.installed_version(@current_resource.package_name, arch)
else
- new_resource = "#{@new_resource.package_name}#{yum_arch}"
- end
- Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+ package_name_array.each_with_index do |pkg, idx|
+ # Don't overwrite an existing arch
+ if arch
+ name, parch = pkg, arch
+ else
+ name, parch = parse_arch(pkg)
+ # if we parsed an arch from the name, update the name
+ # to be just the package name.
+ if parch
+ if @new_resource.package_name.is_a?(Array)
+ @new_resource.package_name[idx] = name
+ else
+ @new_resource.package_name(name)
+ # only set the arch if it's a single package
+ set_arch(parch)
+ end
+ end
+ end
- installed_version = []
- @candidate_version = []
- package_name_array.each do |pkg|
- installed_version << @yum.installed_version(pkg, arch)
- @candidate_version << @yum.candidate_version(pkg, arch)
+ if @new_resource.version
+ new_resource =
+ "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
+ else
+ new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
+ end
+ Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+ installed_version << @yum.installed_version(name, parch)
+ @candidate_version << @yum.candidate_version(name, parch)
+ @arch << parch
+ end
end
+
if installed_version.size == 1
@current_resource.version(installed_version[0])
@candidate_version = @candidate_version[0]
+ @arch = @arch[0]
else
@current_resource.version(installed_version)
end
@@ -1114,7 +1190,7 @@ class Chef
# Work around yum not exiting with an error if a package doesn't exist
# for CHEF-2062
all_avail = as_array(name).zip(as_array(version)).any? do |n, v|
- @yum.version_available?(n, v, arch)
+ @yum.version_available?(n, v, arch_for_name(n))
end
method = log_method = nil
methods = []
@@ -1156,20 +1232,20 @@ class Chef
repos = []
pkg_string_bits = []
- index = 0
as_array(name).zip(as_array(version)).each do |n, v|
+ idx = package_name_array.index(n)
+ a = arch_for_name(n)
s = ''
- unless v == current_version_array[index]
- s = "#{n}-#{v}#{yum_arch}"
- repo = @yum.package_repository(n, v, arch)
+ unless v == current_version_array[idx]
+ s = "#{n}-#{v}#{yum_arch(a)}"
+ repo = @yum.package_repository(n, v, a)
repos << "#{s} from #{repo} repository"
pkg_string_bits << s
end
- index += 1
end
pkg_string = pkg_string_bits.join(' ')
Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
else
raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
"and release? (version-release, e.g. 1.84-10.fc6)"
@@ -1178,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
@@ -1216,13 +1292,17 @@ class Chef
def remove_package(name, version)
if version
- remove_str = as_array(name).zip(as_array(version)).map do |x|
- "#{x.join('-')}#{yum_arch}"
+ remove_str = as_array(name).zip(as_array(version)).map do |n, v|
+ a = arch_for_name(n)
+ "#{[n, v].join('-')}#{yum_arch(a)}"
end.join(' ')
else
- remove_str = as_array(name).map { |n| "#{n}#{yum_arch}" }.join(' ')
+ remove_str = as_array(name).map do |n|
+ a = arch_for_name(n)
+ "#{n}#{yum_arch(a)}"
+ end.join(' ')
end
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
if flush_cache[:after]
@yum.reload
@@ -1237,22 +1317,26 @@ class Chef
private
- def parse_arch
+ def parse_arch(package_name)
# Allow for foo.x86_64 style package_name like yum uses in it's output
#
- if @new_resource.package_name =~ %r{^(.*)\.(.*)$}
+ if package_name =~ %r{^(.*)\.(.*)$}
new_package_name = $1
new_arch = $2
# foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
# Ensure we don't have an existing package matching package_name, then ensure we at
# least have a match for the new_package+new_arch before we overwrite. If neither
# then fall through to standard package handling.
- if (@yum.installed_version(@new_resource.package_name).nil? and @yum.candidate_version(@new_resource.package_name).nil?) and
- (@yum.installed_version(new_package_name, new_arch) or @yum.candidate_version(new_package_name, new_arch))
- @new_resource.package_name(new_package_name)
- @new_resource.arch(new_arch)
+ old_installed = @yum.installed_version(package_name)
+ old_candidate = @yum.candidate_version(package_name)
+ new_installed = @yum.installed_version(new_package_name, new_arch)
+ new_candidate = @yum.candidate_version(new_package_name, new_arch)
+ if (old_installed.nil? and old_candidate.nil?) and (new_installed or new_candidate)
+ Chef::Log.debug("Parsed out arch #{new_arch}, new package name is #{new_package_name}")
+ return new_package_name, new_arch
end
end
+ return package_name, nil
end
# If we don't have the package we could have been passed a 'whatprovides' feature
@@ -1297,7 +1381,7 @@ class Chef
new_package_name = packages.first.name
new_package_version = packages.first.version.to_s
debug_msg = "#{name}: Unable to match package '#{name}' but matched #{packages.size} "
- debug_msg << packages.size == 1 ? "package" : "packages"
+ debug_msg << (packages.size == 1 ? "package" : "packages")
debug_msg << ", selected '#{new_package_name}' version '#{new_package_version}'"
Chef::Log.debug(debug_msg)
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index 2cd321660b..ac42304ffb 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -29,46 +29,49 @@ class Chef
class Package
class Zypper < Chef::Provider::Package
+ provides :package, platform_family: "suse"
+ provides :zypper_package, os: "linux"
+
def load_current_resource
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
+ @current_resource = Chef::Resource::ZypperPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
is_installed=false
is_out_of_date=false
version=''
oud_version=''
- Chef::Log.debug("#{@new_resource} checking zypper")
- status = shell_out("zypper --non-interactive info #{@new_resource.package_name}")
+ Chef::Log.debug("#{new_resource} checking zypper")
+ status = shell_out_with_timeout("zypper --non-interactive info #{new_resource.package_name}")
status.stdout.each_line do |line|
case line
when /^Version: (.+)$/
version = $1
- Chef::Log.debug("#{@new_resource} version #{$1}")
+ Chef::Log.debug("#{new_resource} version #{$1}")
when /^Installed: Yes$/
is_installed=true
- Chef::Log.debug("#{@new_resource} is installed")
+ Chef::Log.debug("#{new_resource} is installed")
when /^Installed: No$/
is_installed=false
- Chef::Log.debug("#{@new_resource} is not installed")
+ Chef::Log.debug("#{new_resource} is not installed")
when /^Status: out-of-date \(version (.+) installed\)$/
is_out_of_date=true
oud_version=$1
- Chef::Log.debug("#{@new_resource} out of date version #{$1}")
+ Chef::Log.debug("#{new_resource} out of date version #{$1}")
end
end
if is_installed==false
@candidate_version=version
- @current_resource.version(nil)
+ current_resource.version(nil)
end
if is_installed==true
if is_out_of_date==true
- @current_resource.version(oud_version)
+ current_resource.version(oud_version)
@candidate_version=version
else
- @current_resource.version(version)
+ current_resource.version(version)
@candidate_version=version
end
end
@@ -77,7 +80,7 @@ class Chef
raise Chef::Exceptions::Package, "zypper failed - #{status.inspect}!"
end
- @current_resource
+ current_resource
end
def zypper_version()
@@ -104,9 +107,9 @@ class Chef
def zypper_package(command, pkgname, version)
version = "=#{version}" unless version.nil? || version.empty?
if zypper_version < 1.0
- shell_out!("zypper#{gpg_checks} #{command} -y #{pkgname}")
+ shell_out_with_timeout!("zypper#{gpg_checks} #{command} -y #{pkgname}")
else
- shell_out!("zypper --non-interactive#{gpg_checks} "+
+ shell_out_with_timeout!("zypper --non-interactive#{gpg_checks} "+
"#{command} #{pkgname}#{version}")
end
end
diff --git a/lib/chef/provider/powershell_script.rb b/lib/chef/provider/powershell_script.rb
index 8c79b384e9..b876b6d8ee 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -22,71 +22,180 @@ class Chef
class Provider
class PowershellScript < Chef::Provider::WindowsScript
+ provides :powershell_script, os: "windows"
+
+ def initialize (new_resource, run_context)
+ super(new_resource, run_context, '.ps1')
+ add_exit_status_wrapper
+ end
+
+ def action_run
+ validate_script_syntax!
+ super
+ end
+
+ def command
+ basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+ # Powershell.exe is always in "v1.0" folder (for backwards compatibility)
+ interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter)
+
+ "\"#{interpreter_path}\" #{flags} \"#{script_file.path}\""
+ end
+
+ def flags
+ # Must use -File rather than -Command to launch the script
+ # file created by the base class that contains the script
+ # code -- otherwise, powershell.exe does not propagate the
+ # error status of a failed Windows process that ran at the
+ # end of the script, it gets changed to '1'.
+ interpreter_flags = [default_interpreter_flags, '-File'].join(' ')
+
+ if ! (@new_resource.flags.nil?)
+ interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
+ end
+
+ interpreter_flags
+ end
+
protected
- EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze
- EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE ) {exit $LASTEXITCODE} else { exit 1 }}".freeze
- EXIT_STATUS_RESET_SCRIPT = "\n$global:LASTEXITCODE=$null".freeze
- # Process exit codes are strange with PowerShell. Unless you
- # explicitly call exit in Powershell, the powershell.exe
- # interpreter returns only 0 for success or 1 for failure. Since
- # we'd like to get specific exit codes from executable tools run
- # with Powershell, we do some work using the automatic variables
- # $? and $LASTEXITCODE to return the process exit code of the
- # last process run in the script if it is the last command
- # executed, otherwise 0 or 1 based on whether $? is set to true
- # (success, where we return 0) or false (where we return 1).
- def normalize_script_exit_status( code )
- target_code = ( EXIT_STATUS_EXCEPTION_HANDLER +
- EXIT_STATUS_RESET_SCRIPT +
- "\n" +
- code.to_s +
- EXIT_STATUS_NORMALIZATION_SCRIPT )
- convert_boolean_return = @new_resource.convert_boolean_return
- self.code = <<EOH
-new-variable -name interpolatedexitcode -visibility private -value $#{convert_boolean_return}
-new-variable -name chefscriptresult -visibility private
-$chefscriptresult = {
-#{target_code}
-}.invokereturnasis()
-if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 }
-EOH
- Chef::Log.debug("powershell_script provider called with script code:\n\n#{code}\n")
+ # Process exit codes are strange with PowerShell and require
+ # special handling to cover common use cases.
+ def add_exit_status_wrapper
+ self.code = wrapper_script
+ Chef::Log.debug("powershell_script provider called with script code:\n\n#{@new_resource.code}\n")
Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{self.code}\n")
end
- public
+ def validate_script_syntax!
+ interpreter_arguments = default_interpreter_flags.join(' ')
+ Tempfile.open(['chef_powershell_script-user-code', '.ps1']) do | user_script_file |
+ # 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}"
- def initialize (new_resource, run_context)
- super(new_resource, run_context, '.ps1')
- normalize_script_exit_status(new_resource.code)
+ # Note that other script providers like bash allow syntax errors
+ # to be suppressed by setting 'returns' to a value that the
+ # interpreter would return as a status code in the syntax
+ # error case. We explicitly don't do this here -- syntax
+ # errors will not be suppressed, since doing so could make
+ # it harder for users to detect / debug invalid scripts.
+
+ # Therefore, the only return value for a syntactically valid
+ # script is 0. If an exception is raised by shellout, this
+ # means a non-zero return and thus a syntactically invalid script.
+
+ with_os_architecture(node, architecture: new_resource.architecture) do
+ shell_out!(validation_command, {returns: [0]})
+ end
+ end
end
- def flags
- default_flags = [
+ def default_interpreter_flags
+ # Execution policy 'Bypass' is preferable since it doesn't require
+ # user input confirmation for files such as PowerShell modules
+ # downloaded from the Internet. However, 'Bypass' is not supported
+ # prior to PowerShell 3.0, so the fallback is 'Unrestricted'
+ execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? 'Bypass' : 'Unrestricted'
+
+ [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
- "-ExecutionPolicy Unrestricted",
+ "-ExecutionPolicy #{execution_policy}",
# Powershell will hang if STDIN is redirected
# http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
- "-InputFormat None",
- # Must use -File rather than -Command to launch the script
- # file created by the base class that contains the script
- # code -- otherwise, powershell.exe does not propagate the
- # error status of a failed Windows process that ran at the
- # end of the script, it gets changed to '1'.
- "-File"
+ "-InputFormat None"
]
+ end
+
+ # A wrapper script is used to launch user-supplied script while
+ # still obtaining useful process exit codes. Unless you
+ # explicitly call exit in Powershell, the powershell.exe
+ # interpreter returns only 0 for success or 1 for failure. Since
+ # we'd like to get specific exit codes from executable tools run
+ # with Powershell, we do some work using the automatic variables
+ # $? and $LASTEXITCODE to return the process exit code of the
+ # last process run in the script if it is the last command
+ # executed, otherwise 0 or 1 based on whether $? is set to true
+ # (success, where we return 0) or false (where we return 1).
+ def wrapper_script
+<<-EOH
+# Chef Client wrapper for powershell_script resources
- interpreter_flags = default_flags.join(' ')
+# LASTEXITCODE can be uninitialized -- make it explictly 0
+# to avoid incorrect detection of failure (non-zero) codes
+$global:LASTEXITCODE = 0
- if ! (@new_resource.flags.nil?)
- interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
- end
+# Catch any exceptions -- without this, exceptions will result
+# In a zero return code instead of the desired non-zero code
+# that indicates a failure
+trap [Exception] {write-error ($_.Exception.Message);exit 1}
- interpreter_flags
+# Variable state that should not be accessible to the user code
+new-variable -name interpolatedexitcode -visibility private -value $#{@new_resource.convert_boolean_return}
+new-variable -name chefscriptresult -visibility private
+
+# Initialize a variable we use to capture $? inside a block
+$global:lastcmdlet = $null
+
+# Execute the user's code in a script block --
+$chefscriptresult =
+{
+ #{@new_resource.code}
+
+ # This assignment doesn't affect the block's return value
+ $global:lastcmdlet = $?
+}.invokereturnasis()
+
+# Assume failure status of 1 -- success cases
+# will have to override this
+$exitstatus = 1
+
+# If convert_boolean_return is enabled, the block's return value
+# gets precedence in determining our exit status
+if ($interpolatedexitcode -and $chefscriptresult -ne $null -and $chefscriptresult.gettype().name -eq 'boolean')
+{
+ $exitstatus = [int32](!$chefscriptresult)
+}
+elseif ($lastcmdlet)
+{
+ # Otherwise, a successful cmdlet execution defines the status
+ $exitstatus = 0
+}
+elseif ( $LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0 )
+{
+ # If the cmdlet status is failed, allow the Win32 status
+ # in $LASTEXITCODE to define exit status. This handles the case
+ # where no cmdlets, only Win32 processes have run since $?
+ # will be set to $false whenever a Win32 process returns a non-zero
+ # status.
+ $exitstatus = $LASTEXITCODE
+}
+
+# If this script is launched with -File, the process exit
+# status of PowerShell.exe will be $exitstatus. If it was
+# launched with -Command, it will be 0 if $exitstatus was 0,
+# 1 (i.e. failed) otherwise.
+exit $exitstatus
+EOH
end
+
end
end
end
diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb
index 8dde4653ec..22e77dcc13 100644
--- a/lib/chef/provider/reboot.rb
+++ b/lib/chef/provider/reboot.rb
@@ -22,6 +22,7 @@ require 'chef/provider'
class Chef
class Provider
class Reboot < Chef::Provider
+ provides :reboot
def whyrun_supported?
true
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index 94f4e2655b..948fa6c63f 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -31,6 +31,8 @@ class Chef
class Provider
class RegistryKey < Chef::Provider
+ provides :registry_key
+
include Chef::Mixin::Checksum
def whyrun_supported?
@@ -62,7 +64,7 @@ class Chef
def values_to_hash(values)
if values
- @name_hash = Hash[values.map { |val| [val[:name], val] }]
+ @name_hash = Hash[values.map { |val| [val[:name].downcase, val] }]
else
@name_hash = {}
end
@@ -98,8 +100,8 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
- current_value = @name_hash[value[:name]]
+ if @name_hash.has_key?(value[:name].downcase)
+ current_value = @name_hash[value[:name].downcase]
unless current_value[:type] == value[:type] && current_value[:data] == value[:data]
converge_by("set value #{value}") do
registry.set_value(@new_resource.key, value)
@@ -120,7 +122,7 @@ class Chef
end
end
@new_resource.unscrubbed_values.each do |value|
- unless @name_hash.has_key?(value[:name])
+ unless @name_hash.has_key?(value[:name].downcase)
converge_by("create value #{value}") do
registry.set_value(@new_resource.key, value)
end
@@ -131,7 +133,7 @@ class Chef
def action_delete
if registry.key_exists?(@new_resource.key)
@new_resource.unscrubbed_values.each do |value|
- if @name_hash.has_key?(value[:name])
+ if @name_hash.has_key?(value[:name].downcase)
converge_by("delete value #{value}") do
registry.delete_value(@new_resource.key, value)
end
diff --git a/lib/chef/provider/remote_directory.rb b/lib/chef/provider/remote_directory.rb
index 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/remote_file.rb b/lib/chef/provider/remote_file.rb
index da2573dacb..c4643edc0b 100644
--- a/lib/chef/provider/remote_file.rb
+++ b/lib/chef/provider/remote_file.rb
@@ -24,6 +24,7 @@ require 'chef/deprecation/warnings'
class Chef
class Provider
class RemoteFile < Chef::Provider::File
+ provides :remote_file
extend Chef::Deprecation::Warnings
include Chef::Deprecation::Provider::RemoteFile
diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb
index ef55dd77cd..4f450ce333 100644
--- a/lib/chef/provider/remote_file/content.rb
+++ b/lib/chef/provider/remote_file/content.rb
@@ -20,6 +20,7 @@
require 'uri'
require 'tempfile'
require 'chef/file_content_management/content_base'
+require 'chef/mixin/uris'
class Chef
class Provider
@@ -28,6 +29,8 @@ class Chef
private
+ include Chef::Mixin::Uris
+
def file_for_provider
Chef::Log.debug("#{@new_resource} checking for changes")
@@ -45,7 +48,11 @@ class Chef
sources = sources.dup
source = sources.shift
begin
- uri = URI.parse(source)
+ uri = if Chef::Provider::RemoteFile::Fetcher.network_share?(source)
+ source
+ else
+ as_uri(source)
+ end
raw_file = grab_file_from_uri(uri)
rescue SocketError, Errno::ECONNREFUSED, Errno::ENOENT, Errno::EACCES, Timeout::Error, Net::HTTPServerException, Net::HTTPFatalError, Net::FTPError => e
Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e.to_s}")
diff --git a/lib/chef/provider/remote_file/fetcher.rb b/lib/chef/provider/remote_file/fetcher.rb
index 249b29186f..53bfe9935c 100644
--- a/lib/chef/provider/remote_file/fetcher.rb
+++ b/lib/chef/provider/remote_file/fetcher.rb
@@ -23,15 +23,29 @@ class Chef
class Fetcher
def self.for_resource(uri, new_resource, current_resource)
- case uri.scheme
- when "http", "https"
- Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
- when "ftp"
- Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
- when "file"
- Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource)
+ if network_share?(uri)
+ Chef::Provider::RemoteFile::NetworkFile.new(uri, new_resource, current_resource)
else
- raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported"
+ case uri.scheme
+ when "http", "https"
+ Chef::Provider::RemoteFile::HTTP.new(uri, new_resource, current_resource)
+ when "ftp"
+ Chef::Provider::RemoteFile::FTP.new(uri, new_resource, current_resource)
+ when "file"
+ Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource)
+ else
+ raise ArgumentError, "Invalid uri, Only http(s), ftp, and file are currently supported"
+ end
+ end
+ end
+
+ # Windows network share: \\computername\share\file
+ def self.network_share?(source)
+ case source
+ when String
+ !!(%r{\A\\\\[A-Za-z0-9+\-\.]+} =~ source)
+ else
+ false
end
end
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb
index e78311f2c3..026206b64e 100644
--- a/lib/chef/provider/remote_file/local_file.rb
+++ b/lib/chef/provider/remote_file/local_file.rb
@@ -32,15 +32,21 @@ class Chef
@new_resource = new_resource
@uri = uri
end
-
+
# CHEF-4472: Remove the leading slash from windows paths that we receive from a file:// URI
- def fix_windows_path(path)
- path.gsub(/^\/([a-zA-Z]:)/,'\1')
+ def fix_windows_path(path)
+ path.gsub(/^\/([a-zA-Z]:)/,'\1')
+ end
+
+ def source_path
+ @source_path ||= begin
+ path = URI.unescape(uri.path)
+ Chef::Platform.windows? ? fix_windows_path(path) : path
+ end
end
# Fetches the file at uri, returning a Tempfile-like File handle
def fetch
- source_path = Chef::Platform.windows? ? fix_windows_path(uri.path) : uri.path
tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
Chef::Log.debug("#{new_resource} staging #{source_path} to #{tempfile.path}")
FileUtils.cp(source_path, tempfile.path)
diff --git a/lib/chef/provider/remote_file/network_file.rb b/lib/chef/provider/remote_file/network_file.rb
new file mode 100644
index 0000000000..093a388d2a
--- /dev/null
+++ b/lib/chef/provider/remote_file/network_file.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Jesse Campbell (<hikeit@gmail.com>)
+# Copyright:: Copyright (c) 2013 Jesse Campbell
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'uri'
+require 'tempfile'
+require 'chef/provider/remote_file'
+
+class Chef
+ class Provider
+ class RemoteFile
+ class NetworkFile
+
+ attr_reader :new_resource
+
+ def initialize(source, new_resource, current_resource)
+ @new_resource = new_resource
+ @source = source
+ end
+
+ # Fetches the file on a network share, returning a Tempfile-like File handle
+ # windows only
+ def fetch
+ tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
+ Chef::Log.debug("#{new_resource} staging #{@source} to #{tempfile.path}")
+ FileUtils.cp(@source, tempfile.path)
+ tempfile.close if tempfile
+ tempfile
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 75da2ddb31..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
@@ -168,6 +178,32 @@ class Chef
@new_resource.respond_to?(method_name) &&
!!@new_resource.send(method_name)
end
+
+ module ServicePriorityInit
+
+ #
+ # Platform-specific versions
+ #
+
+ #
+ # Linux
+ #
+
+ require 'chef/chef_class'
+ require 'chef/provider/service/systemd'
+ require 'chef/provider/service/insserv'
+ require 'chef/provider/service/redhat'
+ require 'chef/provider/service/arch'
+ require 'chef/provider/service/gentoo'
+ require 'chef/provider/service/upstart'
+ require 'chef/provider/service/debian'
+ require 'chef/provider/service/invokercd'
+
+ Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: 'arch'
+ Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: 'gentoo'
+ Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: 'debian'
+ Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w(rhel fedora suse)
+ end
end
end
end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 0aef62c62e..0c95ce2c8e 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -91,15 +91,18 @@ class Chef
protected
def determine_current_status!
- Chef::Log.debug "#{@new_resource} using lssrc to check the status "
+ Chef::Log.debug "#{@new_resource} using lssrc to check the status"
begin
- services = shell_out!("lssrc -a | grep -w #{@new_resource.service_name}").stdout.split("\n")
- is_resource_group?(services)
-
- if services.length == 1 && services[0].split(' ').last == "active"
- @current_resource.running true
- else
+ if is_resource_group?
+ # Groups as a whole have no notion of whether they're running
@current_resource.running false
+ else
+ service = shell_out!("lssrc -s #{@new_resource.service_name}").stdout
+ if service.split(' ').last == 'active'
+ @current_resource.running true
+ else
+ @current_resource.running false
+ end
end
Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
# ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
@@ -112,11 +115,9 @@ class Chef
end
end
- def is_resource_group? (services)
- if services.length > 1
- Chef::Log.debug("#{@new_resource.service_name} is a group")
- @is_resource_group = true
- elsif services[0].split(' ')[1] == @new_resource.service_name
+ def is_resource_group?
+ so = shell_out("lssrc -g #{@new_resource.service_name}")
+ if so.exitstatus == 0
Chef::Log.debug("#{@new_resource.service_name} is a group")
@is_resource_group = true
end
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 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 9204e3ef92..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
@@ -147,7 +147,7 @@ class Chef
# some scripts support multiple instances through symlinks such as openvpn.
# We should get the service name from rcvar.
Chef::Log.debug("name=\"service\" not found at #{init_command}. falling back to rcvar")
- sn = shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
+ shell_out!("#{init_command} rcvar").stdout[/(\w+_enable)=/, 1]
else
# for why-run mode when the rcd_script is not there yet
new_resource.service_name
diff --git a/lib/chef/provider/service/gentoo.rb b/lib/chef/provider/service/gentoo.rb
index 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 0a219a69e1..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");
@@ -18,6 +18,7 @@
require 'chef/provider/service/simple'
require 'chef/mixin/command'
+require 'chef/platform/service_helpers'
class Chef
class Provider
@@ -71,7 +72,7 @@ class Chef
def restart_service
if @new_resource.restart_command
super
- elsif @new_resource.supports[:restart]
+ elsif supports[:restart]
shell_out_with_systems_locale!("#{default_init_command} restart")
else
stop_service
@@ -83,7 +84,7 @@ class Chef
def reload_service
if @new_resource.reload_command
super
- elsif @new_resource.supports[:reload]
+ elsif supports[:reload]
shell_out_with_systems_locale!("#{default_init_command} reload")
end
end
diff --git a/lib/chef/provider/service/insserv.rb b/lib/chef/provider/service/insserv.rb
index 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 10ad1aa29d..0a8fca4262 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -16,8 +16,10 @@
# limitations under the License.
#
+require 'etc'
require 'rexml/document'
require 'chef/resource/service'
+require 'chef/resource/macosx_service'
require 'chef/provider/service/simple'
require 'chef/util/path_helper'
@@ -26,6 +28,7 @@ class Chef
class Service
class Macosx < Chef::Provider::Service::Simple
+ provides :macosx_service, os: "darwin"
provides :service, os: "darwin"
def self.gather_plist_dirs
@@ -33,26 +36,43 @@ class Chef
/Library/LaunchDaemons
/System/Library/LaunchAgents
/System/Library/LaunchDaemons }
-
- locations << "#{ENV['HOME']}/Library/LaunchAgents" if ENV['HOME']
+ Chef::Util::PathHelper.home('Library', 'LaunchAgents') { |p| locations << p }
locations
end
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::Service.new(@new_resource.name)
+ @current_resource = Chef::Resource::MacosxService.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
@plist_size = 0
- @plist = find_service_plist
+ @plist = @new_resource.plist ? @new_resource.plist : find_service_plist
@service_label = find_service_label
+ # LauchAgents should be loaded as the console user.
+ @console_user = @plist ? @plist.include?('LaunchAgents') : false
+ @session_type = @new_resource.session_type
+
+ if @console_user
+ @console_user = Etc.getlogin
+ Chef::Log.debug("#{new_resource} console_user: '#{@console_user}'")
+ cmd = "su "
+ 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?
+ end
+
+ Chef::Log.debug("#{new_resource} Plist: '#{@plist}' service_label: '#{@service_label}'")
set_service_status
@current_resource
end
def define_resource_requirements
- #super
requirements.assert(:reload) do |a|
a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
end
@@ -62,6 +82,12 @@ class Chef
a.failure_message Chef::Exceptions::Service, "Several plist files match service name. Please use full service name."
end
+ requirements.assert(:all_actions) do |a|
+ a.assertion {::File.exists?(@plist.to_s) }
+ a.failure_message Chef::Exceptions::Service,
+ "Could not find plist for #{@new_resource}"
+ end
+
requirements.assert(:enable, :disable) do |a|
a.assertion { !@service_label.to_s.empty? }
a.failure_message Chef::Exceptions::Service,
@@ -70,7 +96,7 @@ class Chef
requirements.assert(:all_actions) do |a|
a.assertion { @plist_size > 0 }
- # No failrue here in original code - so we also will not
+ # No failure here in original code - so we also will not
# fail. Instead warn that the service is potentially missing
a.whyrun "Assuming that the service would have been previously installed and is currently disabled." do
@current_resource.enabled(false)
@@ -86,7 +112,7 @@ class Chef
if @new_resource.start_command
super
else
- shell_out_with_systems_locale!("launchctl load -w '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
+ load_service
end
end
end
@@ -98,7 +124,7 @@ class Chef
if @new_resource.stop_command
super
else
- shell_out_with_systems_locale!("launchctl unload '#{@plist}'", :user => @owner_uid, :group => @owner_gid)
+ unload_service
end
end
end
@@ -107,9 +133,9 @@ class Chef
if @new_resource.restart_command
super
else
- stop_service
+ unload_service
sleep 1
- start_service
+ load_service
end
end
@@ -122,10 +148,7 @@ class Chef
if @current_resource.enabled
Chef::Log.debug("#{@new_resource} already enabled, not enabling")
else
- shell_out!(
- "launchctl load -w '#{@plist}'",
- :user => @owner_uid, :group => @owner_gid
- )
+ load_service
end
end
@@ -133,38 +156,49 @@ class Chef
unless @current_resource.enabled
Chef::Log.debug("#{@new_resource} not enabled, not disabling")
else
- shell_out!(
- "launchctl unload -w '#{@plist}'",
- :user => @owner_uid, :group => @owner_gid
- )
+ unload_service
+ end
+ end
+
+ def load_service
+ session = @session_type ? "-S #{@session_type} " : ''
+ cmd = 'launchctl load -w ' + session + @plist
+ shell_out_as_user(cmd)
+ end
+
+ def unload_service
+ cmd = 'launchctl unload -w ' + @plist
+ shell_out_as_user(cmd)
+ end
+
+ def shell_out_as_user(cmd)
+ if @console_user
+ shell_out_with_systems_locale("#{@base_user_cmd} '#{cmd}'")
+ else
+ shell_out_with_systems_locale(cmd)
+
end
end
def set_service_status
return if @plist == nil or @service_label.to_s.empty?
- cmd = shell_out(
- "launchctl list #{@service_label}",
- :user => @owner_uid, :group => @owner_gid
- )
+ cmd = "launchctl list #{@service_label}"
+ res = shell_out_as_user(cmd)
- if cmd.exitstatus == 0
+ if res.exitstatus == 0
@current_resource.enabled(true)
else
@current_resource.enabled(false)
end
if @current_resource.enabled
- @owner_uid = ::File.stat(@plist).uid
- @owner_gid = ::File.stat(@plist).gid
-
- shell_out!(
- "launchctl list", :user => @owner_uid, :group => @owner_gid
- ).stdout.each_line do |line|
- case line
- when /(\d+|-)\s+(?:\d+|-)\s+(.*\.?)#{@service_label}/
+ res.stdout.each_line do |line|
+ case line.downcase
+ when /\s+\"pid\"\s+=\s+(\d+).*/
pid = $1
@current_resource.running(!pid.to_i.zero?)
+ Chef::Log.debug("Current PID for #{@service_label} is #{pid}")
end
end
else
@@ -179,6 +213,9 @@ class Chef
# onto the node yet."
return nil if @plist.nil?
+ # Plist must exist by this point
+ raise Chef::Exceptions::FileNotFound, "Cannot find #{@plist}!" unless ::File.exists?(@plist)
+
# Most services have the same internal label as the name of the
# plist file. However, there is no rule saying that *has* to be
# the case, and some core services (notably, ssh) do not follow
@@ -186,7 +223,9 @@ class Chef
# plist files can come in XML or Binary formats. this command
# will make sure we get XML every time.
- plist_xml = shell_out!("plutil -convert xml1 -o - #{@plist}").stdout
+ plist_xml = shell_out_with_systems_locale!(
+ "plutil -convert xml1 -o - #{@plist}"
+ ).stdout
plist_doc = REXML::Document.new(plist_xml)
plist_doc.elements[
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/service/windows.rb b/lib/chef/provider/service/windows.rb
index ba53f0a3c3..355ffafc2a 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -25,7 +25,6 @@ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
end
class Chef::Provider::Service::Windows < Chef::Provider::Service
-
provides :service, os: "windows"
provides :windows_service, os: "windows"
diff --git a/lib/chef/provider/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 f6ac72448e..244b11db98 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -23,7 +23,6 @@ require 'etc'
class Chef
class Provider
class User < Chef::Provider
-
include Chef::Mixin::Command
attr_accessor :user_exists, :locked
@@ -208,7 +207,6 @@ class Chef
def unlock_user
raise NotImplementedError
end
-
end
end
end
diff --git a/lib/chef/provider/user/aix.rb b/lib/chef/provider/user/aix.rb
index af08ab4364..a575a41e54 100644
--- a/lib/chef/provider/user/aix.rb
+++ b/lib/chef/provider/user/aix.rb
@@ -18,9 +18,10 @@ class Chef
class Provider
class User
class Aix < Chef::Provider::User::Useradd
+ provides :user, platform: %w(aix)
UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
-
+
def create_user
super
add_password
@@ -88,7 +89,7 @@ class Chef
end
end
end
-
+
end
end
end
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
index debf36f771..0c0c85e18b 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -44,6 +44,8 @@ class Chef
# This provider only supports Mac OSX versions 10.7 and above
class Dscl < Chef::Provider::User
+ provides :user, os: "darwin"
+
def define_resource_requirements
super
@@ -650,7 +652,11 @@ user password using shadow hash.")
def run_plutil(*args)
result = shell_out("plutil -#{args.join(' ')}")
raise(Chef::Exceptions::PlistUtilCommandFailed,"plutil error: #{result.inspect}") unless result.exitstatus == 0
- result.stdout
+ if result.stdout.encoding == Encoding::ASCII_8BIT
+ result.stdout.encode("utf-8", "binary", :undef => :replace, :invalid => :replace, :replace => '?')
+ else
+ result.stdout
+ end
end
def convert_binary_plist_to_xml(binary_plist_string)
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index fe71e93561..810ffb9a8d 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -22,6 +22,7 @@ class Chef
class Provider
class User
class Pw < Chef::Provider::User
+ provides :user, platform: %w(freebsd)
def load_current_resource
super
diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb
index d480acaced..b242095f0c 100644
--- a/lib/chef/provider/user/solaris.rb
+++ b/lib/chef/provider/user/solaris.rb
@@ -22,6 +22,8 @@ class Chef
class Provider
class User
class Solaris < Chef::Provider::User::Useradd
+ provides :user, platform: %w(omnios solaris2)
+
UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
attr_writer :password_file
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
index cc770c0be2..a1b5b3459c 100644
--- a/lib/chef/provider/user/useradd.rb
+++ b/lib/chef/provider/user/useradd.rb
@@ -23,6 +23,7 @@ class Chef
class Provider
class User
class Useradd < Chef::Provider::User
+ provides :user
UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:password, "-p"], [:shell, "-s"], [:uid, "-u"]]
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index 66ef30c349..e282a11d45 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -27,6 +27,8 @@ class Chef
class User
class Windows < Chef::Provider::User
+ provides :user, os: "windows"
+
def initialize(new_resource,run_context)
super
@net_user = Chef::Util::Windows::NetUser.new(@new_resource.username)
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 d83a3e0468..82a24fc078 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -17,9 +17,33 @@
#
require 'chef/exceptions'
-require 'chef/platform/provider_priority_map'
+require 'chef/platform/priority_map'
class Chef
+ #
+ # Provider Resolution
+ # ===================
+ #
+ # Provider resolution is the process of taking a Resource object and an
+ # action, and determining the Provider class that should be instantiated to
+ # handle the action.
+ #
+ # If the resource has its `provider` set, that is used.
+ #
+ # Otherwise, we take the lists of Providers that have registered as
+ # providing the DSL through `provides :dsl_name, <filters>` or
+ # `Chef.set_resource_priority_array :dsl_name, <filters>`. We filter each
+ # list of Providers through:
+ #
+ # 1. The filters it was registered with (such as `os: 'linux'` or
+ # `platform_family: 'debian'`)
+ # 2. `provides?(node, resource)`
+ # 3. `supports?(resource, action)`
+ #
+ # Anything that passes the filter and returns `true` to provides and supports,
+ # is considered a match. The first matching Provider in the *most recently
+ # registered list* is selected and returned.
+ #
class ProviderResolver
attr_reader :node
@@ -32,35 +56,53 @@ class Chef
@action = action
end
- # return a deterministically sorted list of Chef::Provider subclasses
- def providers
- @providers ||= Chef::Provider.descendants
- end
-
def resolve
maybe_explicit_provider(resource) ||
maybe_dynamic_provider_resolution(resource, action) ||
maybe_chef_platform_lookup(resource)
end
- # this cut looks at if the provider can handle the resource type on the node
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
+ def provided_by?(provider_class)
+ potential_handlers.include?(provider_class)
+ end
+
def enabled_handlers
- @enabled_handlers ||=
- providers.select do |klass|
- klass.provides?(node, resource)
- end.sort {|a,b| a.to_s <=> b.to_s }
+ @enabled_handlers ||= potential_handlers.select { |handler| !overrode_provides?(handler) || handler.provides?(node, resource) }
end
- # this cut looks at if the provider can handle the specific resource and action
+ # TODO deprecate this and allow actions to be passed as a filter to
+ # `provides` so we don't have to have two separate things.
+ # @api private
def supported_handlers
- @supported_handlers ||=
- enabled_handlers.select do |klass|
- klass.supports?(resource, action)
- end
+ enabled_handlers.select { |handler| handler.supports?(resource, action) }
end
private
+ def potential_handlers
+ handler_map.list(node, resource.resource_name).uniq
+ end
+
+ # The list of handlers, with any in the priority_map moved to the front
+ def prioritized_handlers
+ @prioritized_handlers ||= begin
+ supported_handlers = self.supported_handlers
+ if supported_handlers.empty?
+ # if none of the providers specifically support the resource, we still need to pick one of the providers that are
+ # enabled on the node to handle the why-run use case. FIXME we should only do this in why-run mode then.
+ Chef::Log.debug "No providers responded true to `supports?` for action #{action} on resource #{resource}, falling back to enabled handlers so we can return something anyway."
+ supported_handlers = enabled_handlers
+ end
+
+ prioritized = priority_map.list(node, resource.resource_name).flatten(1)
+ prioritized &= supported_handlers # Filter the priority map by the actual enabled handlers
+ prioritized |= supported_handlers # Bring back any handlers that aren't in the priority map, at the *end* (ordered set)
+ prioritized
+ end
+ end
+
# if resource.provider is set, just return one of those objects
def maybe_explicit_provider(resource)
return nil unless resource.provider
@@ -69,35 +111,17 @@ class Chef
# try dynamically finding a provider based on querying the providers to see what they support
def maybe_dynamic_provider_resolution(resource, action)
- # log this so we know what providers will work for the generic resource on the node (early cut)
- Chef::Log.debug "providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
-
- # what providers were excluded by machine state (late cut)
- Chef::Log.debug "providers that refused resource #{resource} were: #{enabled_handlers - supported_handlers}"
- Chef::Log.debug "providers that support resource #{resource} include: #{supported_handlers}"
-
- # if none of the providers specifically support the resource, we still need to pick one of the providers that are
- # enabled on the node to handle the why-run use case.
- handlers = supported_handlers.empty? ? enabled_handlers : supported_handlers
- Chef::Log.debug "no providers supported the resource, falling back to enabled handlers" if supported_handlers.empty?
-
- if handlers.count >= 2
- # this magic stack ranks the providers by where they appear in the provider_priority_map, it is mostly used
- # to pick amongst N different ways to start init scripts on different debian/ubuntu systems.
- priority_list = [ get_provider_priority_map(resource.resource_name, node) ].flatten.compact
- handlers = handlers.sort_by { |x| i = priority_list.index x; i.nil? ? Float::INFINITY : i }
- handlers = [ handlers.first ]
- end
-
- Chef::Log.debug "providers that survived replacement include: #{handlers}"
-
- raise Chef::Exceptions::AmbiguousProviderResolution.new(resource, handlers) if handlers.count >= 2
+ Chef::Log.debug "Providers for generic #{resource.resource_name} resource enabled on node include: #{enabled_handlers}"
- Chef::Log.debug "dynamic provider resolver FAILED to resolve a provider" if handlers.empty?
+ handler = prioritized_handlers.first
- return nil if handlers.empty?
+ if handler
+ Chef::Log.debug "Provider for action #{action} on resource #{resource} is #{handler}"
+ else
+ Chef::Log.debug "Dynamic provider resolver FAILED to resolve a provider for action #{action} on resource #{resource}"
+ end
- handlers[0]
+ handler
end
# try the old static lookup of providers by platform
@@ -105,13 +129,42 @@ class Chef
Chef::Platform.find_provider_for_node(node, resource)
end
- # dep injection hooks
- def get_provider_priority_map(resource_name, node)
- provider_priority_map.get(node, resource_name)
+ def priority_map
+ Chef.provider_priority_map
+ end
+
+ def handler_map
+ Chef.provider_handler_map
end
- def provider_priority_map
- Chef::Platform::ProviderPriorityMap.instance.priority_map
+ def overrode_provides?(handler)
+ handler.method(:provides?).owner != Chef::Provider.method(:provides?).owner
+ end
+
+ module Deprecated
+ # return a deterministically sorted list of Chef::Provider subclasses
+ def providers
+ @providers ||= Chef::Provider.descendants
+ end
+
+ def enabled_handlers
+ @enabled_handlers ||= begin
+ handlers = super
+ if handlers.empty?
+ # Look through all providers, and find ones that return true to provides.
+ # Don't bother with ones that don't override provides?, since they
+ # would have been in enabled_handlers already if that were so. (It's a
+ # perf concern otherwise.)
+ handlers = providers.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource) }
+ handlers.each do |handler|
+ Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource.resource_name}, but provides #{resource.resource_name.inspect} was never called!")
+ Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ end
+ end
+ handlers
+ end
+ end
end
+ prepend Deprecated
end
end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index 796a0f8fa6..18500d4669 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -25,6 +25,7 @@ require 'chef/provider/cron/aix'
require 'chef/provider/deploy'
require 'chef/provider/directory'
require 'chef/provider/dsc_script'
+require 'chef/provider/dsc_resource'
require 'chef/provider/env'
require 'chef/provider/erl_call'
require 'chef/provider/execute'
@@ -121,6 +122,7 @@ require 'chef/provider/deploy/timestamped'
require 'chef/provider/remote_file/ftp'
require 'chef/provider/remote_file/http'
require 'chef/provider/remote_file/local_file'
+require 'chef/provider/remote_file/network_file'
require 'chef/provider/remote_file/fetcher'
require "chef/provider/lwrp_base"
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index 91f7f30aa9..262560f754 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -25,6 +25,7 @@ require 'chef/dsl/include_recipe'
require 'chef/dsl/registry_helper'
require 'chef/dsl/reboot_pending'
require 'chef/dsl/audit'
+require 'chef/dsl/powershell'
require 'chef/mixin/from_file'
@@ -35,13 +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::Recipe::FullDSL
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index 2df73a52e8..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,23 +18,31 @@
# limitations under the License.
#
+require 'chef/exceptions'
require 'chef/mixin/params_validate'
require 'chef/dsl/platform_introspection'
require 'chef/dsl/data_query'
require 'chef/dsl/registry_helper'
require 'chef/dsl/reboot_pending'
+require 'chef/dsl/resources'
require 'chef/mixin/convert_to_class_name'
require 'chef/guard_interpreter/resource_guard_interpreter'
require 'chef/resource/conditional'
require 'chef/resource/conditional_action_not_nothing'
+require 'chef/resource/action_provider'
require 'chef/resource_collection'
require 'chef/node_map'
require 'chef/node'
require 'chef/platform'
require 'chef/resource/resource_notification'
+require 'chef/provider_resolver'
+require 'chef/resource_resolver'
+require 'set'
require 'chef/mixin/deprecation'
-require 'chef/mixin/descendants_tracker'
+require 'chef/mixin/provides'
+require 'chef/mixin/shell_out'
+require 'chef/mixin/powershell_out'
class Chef
class Resource
@@ -46,6 +55,11 @@ class Chef
include Chef::DSL::PlatformIntrospection
include Chef::DSL::RegistryHelper
include Chef::DSL::RebootPending
+ extend Chef::Mixin::Provides
+
+ # This lets user code do things like `not_if { shell_out!("command") }`
+ include Chef::Mixin::ShellOut
+ include Chef::Mixin::PowershellOut
#
# The node the current Chef run is using.
@@ -78,7 +92,6 @@ class Chef
run_context.resource_collection.find(*args)
end
-
#
# Resource User Interface (for users)
#
@@ -91,14 +104,14 @@ class Chef
# @param run_context The context of the Chef run. Corresponds to #run_context.
#
def initialize(name, run_context=nil)
- name(name)
+ name(name) unless name.nil?
@run_context = run_context
@noop = nil
@before = nil
@params = Hash.new
@provider = nil
- @allowed_actions = [ :nothing ]
- @action = :nothing
+ @allowed_actions = self.class.allowed_actions.to_a
+ @action = self.class.default_action
@updated = false
@updated_by_last_action = false
@supports = {}
@@ -120,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.
- #
- # execute 'Vitruvius' do
- # command 'ls'
- # end
- #
- # Will set the name to "Vitruvius".
+ # The list of properties defined on this resource.
#
- # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
+ # Everything defined with `property` is in this list.
#
- # This is also used for resource notifications and subscribes in the same manner.
+ # @param include_superclass [Boolean] `true` to include properties defined
+ # on superclasses; `false` or `nil` to return the list of properties
+ # directly on this class.
#
- # 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.
+ # @return [Hash<Symbol,Property>] The list of property names and types.
#
- # @param name [Object] The name to set, typically a String or Array
- # @return [String] The name of this Resource.
- #
- def name(name=nil)
- if !name.nil?
- if name.is_a?(Array)
- @name = name.join(', ')
+ 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
#
@@ -161,26 +164,27 @@ class Chef
#
def action(arg=nil)
if arg
- action_list = arg.kind_of?(Array) ? arg : [ arg ]
- action_list = action_list.collect { |a| a.to_sym }
- action_list.each do |action|
+ arg = Array(arg).map(&:to_sym)
+ arg.each do |action|
validate(
{ action: action },
- { action: { kind_of: Symbol, equal_to: @allowed_actions } }
+ { action: { kind_of: Symbol, equal_to: allowed_actions } }
)
end
- @action = action_list
+ @action = arg
else
@action
end
end
+ # Alias for normal assigment syntax.
+ alias_method :action=, :action
+
#
# Sets up a notification that will run a particular action on another resource
# if and when *this* resource is updated by an action.
#
- # If the action does nothing--does not update this resource, the
- # notification never triggers.)
+ # If the action does not update this resource, the notification never triggers.
#
# Only one resource may be specified per notification.
#
@@ -464,27 +468,49 @@ class Chef
#
# Get the value of the state attributes in this resource as a hash.
#
+ # Does not include properties that are not set (unless they are identity
+ # properties).
+ #
# @return [Hash{Symbol => Object}] A Hash of attribute => value for the
# Resource class's `state_attrs`.
- def state
- self.class.state_attrs.inject({}) do |state_attrs, attr_name|
- state_attrs[attr_name] = send(attr_name)
- state_attrs
+ #
+ def state_for_resource_reporter
+ state = {}
+ state_properties = self.class.state_properties
+ state_properties.each do |property|
+ if property.identity? || property.is_set?(self)
+ state[property.name] = send(property.name)
+ end
end
+ state
end
#
- # The value of the identity attribute, if declared. Falls back to #name if
- # no identity attribute is declared.
+ # Since there are collisions with LWRP parameters named 'state' this
+ # method is not used by the resource_reporter and is most likely unused.
+ # It certainly cannot be relied upon and cannot be fixed.
+ #
+ # @deprecated
+ #
+ alias_method :state, :state_for_resource_reporter
+
+ #
+ # The value of the identity of this resource.
#
- # @return The value of the identity attribute.
+ # - If there are no identity properties on the resource, `name` is returned.
+ # - If there is exactly one identity property on the resource, it is returned.
+ # - If there are more than one, they are returned in a hash.
+ #
+ # @return [Object,Hash<Symbol,Object>] The identity of this resource.
#
def identity
- if identity_attr = self.class.identity_attr
- send(identity_attr)
- else
- name
+ result = {}
+ identity_properties = self.class.identity_properties
+ identity_properties.each do |property|
+ result[property.name] = send(property.name)
end
+ return result.values.first if identity_properties.size == 1
+ result
end
#
@@ -506,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.
@@ -587,14 +611,14 @@ class Chef
#
def to_s
- "#{@resource_name}[#{@name}]"
+ "#{resource_name}[#{name}]"
end
def to_text
return "suppressed sensitive resource output" if sensitive
ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS
text = "# Declared in #{@source_line}\n\n"
- text << self.class.dsl_name + "(\"#{name}\") do\n"
+ text << "#{resource_name}(\"#{name}\") do\n"
ivars.each do |ivar|
if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect
@@ -663,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
+
+ 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
- # 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
+ # 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 "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 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
- # If this class doesn't have an identity attr, we'll defer to the superclass:
- if @identity_attr || !superclass.respond_to?(:identity_attr)
- @identity_attr
- else
- superclass.identity_attr
+ #
+ # Set the identity of this resource to a particular 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 }
+
+ # 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
#
@@ -748,6 +1099,12 @@ class Chef
# have.
#
attr_accessor :allowed_actions
+ def allowed_actions(value=NOT_PASSED)
+ if value != NOT_PASSED
+ self.allowed_actions = value
+ end
+ @allowed_actions
+ end
#
# Whether or not this resource was updated during an action. If multiple
@@ -806,19 +1163,15 @@ class Chef
end
#
- # The DSL name of this resource (e.g. `package` or `yum_package`)
+ # The display name of this resource type, for printing purposes.
#
- # @return [String] The DSL name of this resource.
- def self.dsl_name
- convert_to_snake_case(name, 'Chef::Resource')
- end
-
+ # Will be used to print out the resource in messages, e.g. resource_name[name]
#
- # The name of this resource (e.g. `file`)
+ # @return [Symbol] The name of this resource type (e.g. `:execute`).
#
- # @return [String] The name of this resource.
- #
- attr_reader :resource_name
+ def resource_name
+ @resource_name || self.class.resource_name
+ end
#
# Sets a list of capabilities of the real resource. For example, `:remount`
@@ -851,6 +1204,73 @@ class Chef
end
#
+ # The DSL name of this resource (e.g. `package` or `yum_package`)
+ #
+ # @return [String] The DSL name of this resource.
+ #
+ # @deprecated Use resource_name instead.
+ #
+ def self.dsl_name
+ Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead."
+ if name
+ name = self.name.split('::')[-1]
+ convert_to_snake_case(name)
+ end
+ end
+
+ #
+ # The display name of this resource type, for printing purposes.
+ #
+ # This also automatically calls "provides" to provide DSL with the given
+ # name.
+ #
+ # resource_name defaults to your class name.
+ #
+ # Call `resource_name nil` to remove the resource name (and any
+ # corresponding DSL).
+ #
+ # @param value [Symbol] The desired name of this resource type (e.g.
+ # `execute`), or `nil` if this class is abstract and has no resource_name.
+ #
+ # @return [Symbol] The name of this resource type (e.g. `:execute`).
+ #
+ def self.resource_name(name=NOT_PASSED)
+ # Setter
+ if name != NOT_PASSED
+ remove_canonical_dsl
+
+ # Set the resource_name and call provides
+ if name
+ name = name.to_sym
+ # If our class is not already providing this name, provide it.
+ if !Chef::ResourceResolver.includes_handler?(name, self)
+ provides name, canonical: true
+ end
+ @resource_name = name
+ else
+ @resource_name = nil
+ end
+ end
+ @resource_name
+ end
+ def self.resource_name=(name)
+ resource_name(name)
+ end
+
+ #
+ # Use the class name as the resource name.
+ #
+ # Munges the last part of the class name from camel case to snake case,
+ # and sets the resource_name to that:
+ #
+ # A::B::BlahDBlah -> blah_d_blah
+ #
+ def self.use_automatic_resource_name
+ automatic_name = convert_to_snake_case(self.name.split('::')[-1])
+ resource_name automatic_name
+ end
+
+ #
# The module where Chef should look for providers for this resource.
# The provider for `MyResource` will be looked up using
# `provider_base::MyResource`. Defaults to `Chef::Provider`.
@@ -864,11 +1284,186 @@ class Chef
# # ...other stuff
# end
#
+ # @deprecated Use `provides` on the provider, or `provider` on the resource, instead.
+ #
def self.provider_base(arg=nil)
- @provider_base ||= arg
- @provider_base ||= Chef::Provider
+ if arg
+ Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
+ end
+ @provider_base ||= arg || Chef::Provider
+ end
+
+ #
+ # The list of allowed actions for the resource.
+ #
+ # @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
+ #
+ # @return [Array<Symbol>] The list of actions, as symbols.
+ #
+ def self.allowed_actions(*actions)
+ @allowed_actions ||=
+ if superclass.respond_to?(:allowed_actions)
+ superclass.allowed_actions.dup
+ else
+ [ :nothing ]
+ end
+ @allowed_actions |= actions.flatten
+ end
+ def self.allowed_actions=(value)
+ @allowed_actions = value.uniq
end
+ #
+ # The action that will be run if no other action is specified.
+ #
+ # Setting default_action will automatially add the action to
+ # allowed_actions, if it isn't already there.
+ #
+ # Defaults to [:nothing].
+ #
+ # @param action_name [Symbol,Array<Symbol>] The default action (or series
+ # of actions) to use.
+ #
+ # @return [Array<Symbol>] The default actions for the resource.
+ #
+ def self.default_action(action_name=NOT_PASSED)
+ unless action_name.equal?(NOT_PASSED)
+ @default_action = Array(action_name).map(&:to_sym)
+ self.allowed_actions |= @default_action
+ end
+
+ if @default_action
+ @default_action
+ elsif superclass.respond_to?(:default_action)
+ superclass.default_action
+ else
+ [:nothing]
+ end
+ end
+ def self.default_action=(action_name)
+ default_action action_name
+ end
+
+ #
+ # Define an action on this resource.
+ #
+ # The action is defined as a *recipe* block that will be compiled and then
+ # converged when the action is taken (when Resource is converged). The recipe
+ # has access to the resource's attributes and methods, as well as the Chef
+ # recipe DSL.
+ #
+ # Resources in the action recipe may notify and subscribe to other resources
+ # within the action recipe, but cannot notify or subscribe to resources
+ # in the main Chef run.
+ #
+ # Resource actions are *inheritable*: if resource A defines `action :create`
+ # and B is a subclass of A, B gets all of A's actions. Additionally,
+ # resource B can define `action :create` and call `super()` to invoke A's
+ # action code.
+ #
+ # The first action defined (besides `:nothing`) will become the default
+ # action for the resource.
+ #
+ # @param name [Symbol] The action name to define.
+ # @param recipe_block The recipe to run when the action is taken. This block
+ # takes no parameters, and will be evaluated in a new context containing:
+ #
+ # - The resource's public and protected methods (including attributes)
+ # - The Chef Recipe DSL (file, etc.)
+ # - super() referring to the parent version of the action (if any)
+ #
+ # @return The Action class implementing the action
+ #
+ def self.action(action, &recipe_block)
+ action = action.to_sym
+ 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
#
# Internal Resource Interface (for Chef)
@@ -879,7 +1474,6 @@ class Chef
include Chef::Mixin::ConvertToClassName
extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::DescendantsTracker
# XXX: this is required for definition params inside of the scope of a
# subresource to work correctly.
@@ -942,13 +1536,39 @@ class Chef
class << self
# back-compat
- # NOTE: that we do not support unregistering classes as descendents like
+ # NOTE: that we do not support unregistering classes as descendants like
# we used to for LWRP unloading because that was horrible and removed in
# Chef-12.
+ # @deprecated
+ # @api private
alias :resource_classes :descendants
+ # @deprecated
+ # @api private
alias :find_subclass_by_name :find_descendants_by_name
end
+ # @deprecated
+ # @api private
+ # We memoize a sorted version of descendants so that resource lookups don't
+ # have to sort all the things, all the time.
+ # This was causing performance issues in test runs, and probably in real
+ # life as well.
+ @@sorted_descendants = nil
+ def self.sorted_descendants
+ @@sorted_descendants ||= descendants.sort_by { |x| x.to_s }
+ end
+ def self.inherited(child)
+ super
+ @@sorted_descendants = nil
+ # set resource_name automatically if it's not set
+ if child.name && !child.resource_name
+ if child.name =~ /^Chef::Resource::(\w+)$/
+ child.resource_name(convert_to_snake_case($1))
+ end
+ end
+ end
+
+
# If an unknown method is invoked, determine whether the enclosing Provider's
# lexical scope can fulfill the request. E.g. This happens when the Resource's
# block invokes new_resource.
@@ -960,6 +1580,32 @@ class Chef
end
end
+ #
+ # Mark this resource as providing particular DSL.
+ #
+ # Resources have an automatic DSL based on their resource_name, equivalent to
+ # `provides :resource_name` (providing the resource on all OS's). If you
+ # declare a `provides` with the given resource_name, it *replaces* that
+ # provides (so that you can provide your resource DSL only on certain OS's).
+ #
+ def self.provides(name, **options, &block)
+ name = name.to_sym
+
+ # `provides :resource_name, os: 'linux'`) needs to remove the old
+ # canonical DSL before adding the new one.
+ if @resource_name && name == @resource_name
+ remove_canonical_dsl
+ end
+
+ result = Chef.resource_handler_map.set(name, self, options, &block)
+ Chef::DSL::Resources.add_resource_dsl(name)
+ result
+ end
+
+ def self.provides?(node, resource_name)
+ Chef::ResourceResolver.new(node, resource_name).provided_by?(self)
+ end
+
# Helper for #notifies
def validate_resource_spec!(resource_spec)
run_context.resource_collection.validate_lookup_spec!(resource_spec)
@@ -981,31 +1627,37 @@ 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
end
#
- # DSL method used to define attribute on a resource wrapping params_validate
- #
- def self.attribute(attr_name, validation_opts={})
- define_method(attr_name) do |arg=NULL_ARG|
- nillable_set_or_return(attr_name.to_sym, arg, validation_opts)
- end
- end
-
- #
# The cookbook in which this Resource was defined (if any).
#
# @return Chef::CookbookVersion The cookbook in which this Resource was defined.
@@ -1025,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
@@ -1089,33 +1742,6 @@ class Chef
end
end
- # Maps a short_name (and optionally a platform and version) to a
- # Chef::Resource. This allows finer grained per platform resource
- # attributes and the end of overloaded resource definitions
- # (I'm looking at you Chef::Resource::Package)
- # Ex:
- # class WindowsFile < Chef::Resource
- # provides :file, os: "linux", platform_family: "rhel", platform: "redhat"
- # provides :file, os: "!windows
- # provides :file, os: [ "linux", "aix" ]
- # provides :file, os: "solaris2" do |node|
- # node['platform_version'].to_f <= 5.11
- # end
- # # ...other stuff
- # end
- #
- def self.provides(short_name, opts={}, &block)
- short_name_sym = short_name
- if short_name.kind_of?(String)
- # YAGNI: this is probably completely unnecessary and can be removed?
- Chef::Log.warn "[DEPRECATION] Passing a String to Chef::Resource#provides will be removed"
- short_name.downcase!
- short_name.gsub!(/\s/, "_")
- short_name_sym = short_name.to_sym
- end
- node_map.set(short_name_sym, constantize(self.name), opts, &block)
- end
-
# Returns a resource based on a short_name and node
#
# ==== Parameters
@@ -1125,34 +1751,44 @@ class Chef
# === Returns
# <Chef::Resource>:: returns the proper Chef::Resource class
def self.resource_for_node(short_name, node)
- klass = node_map.get(node, short_name) ||
- resource_matching_short_name(short_name)
+ klass = Chef::ResourceResolver.resolve(short_name, node: node)
raise Chef::Exceptions::NoSuchResourceType.new(short_name, node) if klass.nil?
klass
end
- def self.node_map
- @@node_map ||= NodeMap.new
- end
-
- # Returns the class of a Chef::Resource based on the short name
+ #
+ # Returns the class with the given resource_name.
+ #
# ==== Parameters
# short_name<Symbol>:: short_name of the resource (ie :directory)
#
# === Returns
# <Chef::Resource>:: returns the proper Chef::Resource class
+ #
def self.resource_matching_short_name(short_name)
- begin
- rname = convert_to_class_name(short_name.to_s)
- Chef::Resource.const_get(rname)
- rescue NameError
- nil
+ Chef::ResourceResolver.resolve(short_name, canonical: true)
+ end
+
+ # @api private
+ def self.register_deprecated_lwrp_class(resource_class, class_name)
+ if Chef::Resource.const_defined?(class_name, false)
+ Chef::Log.warn "#{class_name} already exists! Deprecation class overwrites #{resource_class}"
+ Chef::Resource.send(:remove_const, class_name)
end
+
+ if !Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Resource.const_set(class_name, resource_class)
+ deprecated_constants[class_name.to_sym] = resource_class
+ end
+
end
- private
+ def self.deprecated_constants
+ @deprecated_constants ||= {}
+ end
- def lookup_provider_constant(name)
+ # @api private
+ def lookup_provider_constant(name, action=:nothing)
begin
self.class.provider_base.const_get(convert_to_class_name(name.to_s))
rescue NameError => e
@@ -1163,9 +1799,19 @@ class Chef
end
end
end
+
+ private
+
+ def self.remove_canonical_dsl
+ if @resource_name
+ remaining = Chef.resource_handler_map.delete_canonical(@resource_name, self)
+ if !remaining
+ Chef::DSL::Resources.remove_resource_dsl(@resource_name)
+ end
+ end
+ end
end
end
-# We require this at the BOTTOM of this file to avoid circular requires (it is used
-# at runtime but not load time)
-require 'chef/provider_resolver'
+# Requiring things at the bottom breaks cycles
+require 'chef/chef_class'
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/apt_package.rb b/lib/chef/resource/apt_package.rb
index f944825ac3..ca119b50c4 100644
--- a/lib/chef/resource/apt_package.rb
+++ b/lib/chef/resource/apt_package.rb
@@ -23,12 +23,10 @@ class Chef
class Resource
class AptPackage < Chef::Resource::Package
- provides :apt_package
provides :package, os: "linux", platform_family: [ "debian" ]
def initialize(name, run_context=nil)
super
- @resource_name = :apt_package
@default_release = nil
end
diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb
index 0add0ce501..025687e879 100644
--- a/lib/chef/resource/bash.rb
+++ b/lib/chef/resource/bash.rb
@@ -25,7 +25,6 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :bash
@interpreter = "bash"
end
diff --git a/lib/chef/resource/batch.rb b/lib/chef/resource/batch.rb
index 576e6b4c6b..efe3f2205f 100644
--- a/lib/chef/resource/batch.rb
+++ b/lib/chef/resource/batch.rb
@@ -22,8 +22,10 @@ class Chef
class Resource
class Batch < Chef::Resource::WindowsScript
+ provides :batch, os: "windows"
+
def initialize(name, run_context=nil)
- super(name, run_context, :batch, "cmd.exe")
+ super(name, run_context, nil, "cmd.exe")
end
end
diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb
index 917f0d1d50..7c1496a46b 100644
--- a/lib/chef/resource/bff_package.rb
+++ b/lib/chef/resource/bff_package.rb
@@ -22,14 +22,6 @@ require 'chef/provider/package/aix'
class Chef
class Resource
class BffPackage < Chef::Resource::Package
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :bff_package
- end
-
end
end
end
-
-
diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb
index b2210262d2..69dbc48050 100644
--- a/lib/chef/resource/breakpoint.rb
+++ b/lib/chef/resource/breakpoint.rb
@@ -22,14 +22,12 @@ require 'chef/resource'
class Chef
class Resource
class Breakpoint < Chef::Resource
+ default_action :break
def initialize(action="break", *args)
- @name = caller.first
- super(@name, *args)
- @action = "break"
- @allowed_actions << :break
- @resource_name = :breakpoint
+ super(caller.first, *args)
end
+
end
end
end
diff --git a/lib/chef/resource/chef_gem.rb b/lib/chef/resource/chef_gem.rb
index 59f575a524..7e9d21ebd2 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -23,11 +23,8 @@ class Chef
class Resource
class ChefGem < Chef::Resource::Package::GemPackage
- provides :chef_gem
-
def initialize(name, run_context=nil)
super
- @resource_name = :chef_gem
@compile_time = Chef::Config[:chef_gem_compile_time]
@gem_binary = RbConfig::CONFIG['bindir'] + "/gem"
end
@@ -53,9 +50,9 @@ class Chef
# Chef::Resource.run_action: Caveat: this skips Chef::Runner.run_action, where notifications are handled
# Action could be an array of symbols, but probably won't (think install + enable for a package)
if compile_time.nil?
- Chef::Log.deprecation "#{self} chef_gem compile_time installation is deprecated"
- Chef::Log.deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
- Chef::Log.deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
+ Chef.log_deprecation "#{self} chef_gem compile_time installation is deprecated"
+ Chef.log_deprecation "#{self} Please set `compile_time false` on the resource to use the new behavior."
+ Chef.log_deprecation "#{self} or set `compile_time true` on the resource if compile_time behavior is required."
end
if compile_time || compile_time.nil?
diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb
index 7be353b648..42f16e6db6 100644
--- a/lib/chef/resource/cookbook_file.rb
+++ b/lib/chef/resource/cookbook_file.rb
@@ -27,13 +27,11 @@ class Chef
class CookbookFile < Chef::Resource::File
include Chef::Mixin::Securable
- provides :cookbook_file
+ default_action :create
def initialize(name, run_context=nil)
super
@provider = Chef::Provider::CookbookFile
- @resource_name = :cookbook_file
- @action = "create"
@source = ::File.basename(name)
@cookbook = nil
end
diff --git a/lib/chef/resource/cron.rb b/lib/chef/resource/cron.rb
index 9c04658bf3..93cf41bc37 100644
--- a/lib/chef/resource/cron.rb
+++ b/lib/chef/resource/cron.rb
@@ -27,11 +27,11 @@ class Chef
state_attrs :minute, :hour, :day, :month, :weekday, :user
+ default_action :create
+ allowed_actions :create, :delete
+
def initialize(name, run_context=nil)
super
- @resource_name = :cron
- @action = :create
- @allowed_actions.push(:create, :delete)
@minute = "*"
@hour = "*"
@day = "*"
@@ -138,7 +138,7 @@ class Chef
:kind_of => [String, Symbol]
)
end
-
+
def time(arg=nil)
set_or_return(
:time,
@@ -214,5 +214,3 @@ class Chef
end
end
end
-
-
diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb
index 36659c349b..d5e9c910b1 100644
--- a/lib/chef/resource/csh.rb
+++ b/lib/chef/resource/csh.rb
@@ -25,7 +25,6 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :csh
@interpreter = "csh"
end
diff --git a/lib/chef/resource/deploy.rb b/lib/chef/resource/deploy.rb
index f886f856df..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"
@@ -51,15 +52,15 @@ class Chef
#
class Deploy < Chef::Resource
- provider_base Chef::Provider::Deploy
-
identity_attr :repository
state_attrs :deploy_to, :revision
+ default_action :deploy
+ allowed_actions :force_deploy, :deploy, :rollback
+
def initialize(name, run_context=nil)
super
- @resource_name = :deploy
@deploy_to = name
@environment = nil
@repository_cache = 'cached-copy'
@@ -69,20 +70,18 @@ class Chef
@symlink_before_migrate = {"config/database.yml" => "config/database.yml"}
@symlinks = {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}
@revision = 'HEAD'
- @action = :deploy
@migrate = false
@rollback_on_error = false
@remote = "origin"
@enable_submodules = false
@shallow_clone = false
+ @depth = nil
@scm_provider = Chef::Provider::Git
@svn_force_export = false
- @allowed_actions.push(:force_deploy, :deploy, :rollback)
@additional_remotes = Hash[]
@keep_releases = 5
@enable_checkout = true
@checkout_branch = "deploy"
- @timeout = nil
end
# where the checked out/cloned code goes
@@ -100,23 +99,51 @@ 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."
- attribute :deploy_to, :kind_of => [ String ]
+ def deploy_to(arg=nil)
+ set_or_return(
+ :deploy_to,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :repo, :kind_of => [ String ]
+ def repo(arg=nil)
+ set_or_return(
+ :repo,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
alias :repository :repo
- attribute :remote, :kind_of => [ String ]
+ def remote(arg=nil)
+ set_or_return(
+ :remote,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :role, :kind_of => [ String ]
+ def role(arg=nil)
+ set_or_return(
+ :role,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- def restart_command(arg=NULL_ARG, &block)
- arg = block if block_given?
- nillable_set_or_return(
+ def restart_command(arg=nil, &block)
+ arg ||= block
+ set_or_return(
:restart_command,
arg,
:kind_of => [ String, Proc ]
@@ -124,60 +151,161 @@ class Chef
end
alias :restart :restart_command
- attribute :migrate, :kind_of => [ TrueClass, FalseClass ]
+ def migrate(arg=nil)
+ set_or_return(
+ :migrate,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
- attribute :migration_command, kind_of: String
+ def migration_command(arg=nil)
+ set_or_return(
+ :migration_command,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :rollback_on_error, :kind_of => [ TrueClass, FalseClass ]
+ def rollback_on_error(arg=nil)
+ set_or_return(
+ :rollback_on_error,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
- attribute :user, kind_of: String
+ def user(arg=nil)
+ set_or_return(
+ :user,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :group, kind_of: [ String ]
+ def group(arg=nil)
+ set_or_return(
+ :group,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :enable_submodules, kind_of: [ TrueClass, FalseClass ]
+ def enable_submodules(arg=nil)
+ set_or_return(
+ :enable_submodules,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
- attribute :shallow_clone, kind_of: [ TrueClass, FalseClass ]
+ def shallow_clone(arg=nil)
+ set_or_return(
+ :shallow_clone,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
- attribute :repository_cache, kind_of: String
+ def repository_cache(arg=nil)
+ set_or_return(
+ :repository_cache,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :copy_exclude, kind_of: String
+ def copy_exclude(arg=nil)
+ set_or_return(
+ :copy_exclude,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :revision, kind_of: String
+ def revision(arg=nil)
+ set_or_return(
+ :revision,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
alias :branch :revision
- attribute :git_ssh_wrapper, kind_of: String
+ def git_ssh_wrapper(arg=nil)
+ set_or_return(
+ :git_ssh_wrapper,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
alias :ssh_wrapper :git_ssh_wrapper
- attribute :svn_username, kind_of: String
+ def svn_username(arg=nil)
+ set_or_return(
+ :svn_username,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :svn_password, kind_of: String
+ def svn_password(arg=nil)
+ set_or_return(
+ :svn_password,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :svn_arguments, kind_of: String
+ def svn_arguments(arg=nil)
+ set_or_return(
+ :svn_arguments,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
- attribute :svn_info_args, kind_of: String
+ def svn_info_args(arg=nil)
+ set_or_return(
+ :svn_arguments,
+ arg,
+ :kind_of => [ String ])
+ end
- def scm_provider(arg=NULL_ARG)
+ def scm_provider(arg=nil)
klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
lookup_provider_constant(arg)
else
arg
end
- nillable_set_or_return(
+ set_or_return(
:scm_provider,
klass,
:kind_of => [ Class ]
)
end
- attribute :svn_force_export, kind_of: [ TrueClass, FalseClass ]
+ # This is to support "provider :revision" without deprecation warnings.
+ # Do NOT copy this.
+ def self.provider_base
+ Chef::Provider::Deploy
+ end
- def environment(arg=NULL_ARG)
+ def svn_force_export(arg=nil)
+ set_or_return(
+ :svn_force_export,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ def environment(arg=nil)
if arg.is_a?(String)
Chef::Log.debug "Setting RAILS_ENV, RACK_ENV, and MERB_ENV to `#{arg}'"
Chef::Log.warn "[DEPRECATED] please modify your deploy recipe or attributes to set the environment using a hash"
arg = {"RAILS_ENV"=>arg,"MERB_ENV"=>arg,"RACK_ENV"=>arg}
end
- nillable_set_or_return(
+ set_or_return(
:environment,
arg,
:kind_of => [ Hash ]
@@ -185,8 +313,8 @@ class Chef
end
# The number of old release directories to keep around after cleanup
- def keep_releases(arg=NULL_ARG)
- [nillable_set_or_return(
+ def keep_releases(arg=nil)
+ [set_or_return(
:keep_releases,
arg,
:kind_of => [ Integer ]), 1].max
@@ -196,7 +324,13 @@ class Chef
# SCM clone/checkout before symlinking. Use this to get rid of files and
# directories you want to be shared between releases.
# Default: ["log", "tmp/pids", "public/system"]
- attribute :purge_before_symlink, kind_of: Array
+ def purge_before_symlink(arg=nil)
+ set_or_return(
+ :purge_before_symlink,
+ arg,
+ :kind_of => Array
+ )
+ end
# An array of paths, relative to your app's root, where you expect dirs to
# exist before symlinking. This runs after #purge_before_symlink, so you
@@ -206,7 +340,13 @@ class Chef
# then specify tmp here so that the tmp directory will exist when you
# symlink the pids directory in to the current release.
# Default: ["tmp", "public", "config"]
- attribute :create_dirs_before_symlink, kind_of: Array
+ def create_dirs_before_symlink(arg=nil)
+ set_or_return(
+ :create_dirs_before_symlink,
+ arg,
+ :kind_of => Array
+ )
+ end
# A Hash of shared/dir/path => release/dir/path. This attribute determines
# which files and dirs in the shared directory get symlinked to the current
@@ -214,7 +354,13 @@ class Chef
# $shared/pids that you would like to symlink as $current_release/tmp/pids
# you specify it as "pids" => "tmp/pids"
# Default {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}
- attribute :symlinks, kind_of: Hash
+ def symlinks(arg=nil)
+ set_or_return(
+ :symlinks,
+ arg,
+ :kind_of => Hash
+ )
+ end
# A Hash of shared/dir/path => release/dir/path. This attribute determines
# which files in the shared directory get symlinked to the current release
@@ -223,44 +369,74 @@ class Chef
# For a rails/merb app, this is used to link in a known good database.yml
# (with the production db password) before running migrate.
# Default {"config/database.yml" => "config/database.yml"}
- attribute :symlink_before_migrate, kind_of: Hash
+ def symlink_before_migrate(arg=nil)
+ set_or_return(
+ :symlink_before_migrate,
+ arg,
+ :kind_of => Hash
+ )
+ end
# Callback fires before migration is run.
- def before_migrate(arg=NULL_ARG, &block)
- arg = block if block_given?
- nillable_set_or_return(:before_migrate, arg, kind_of: [Proc, String])
+ def before_migrate(arg=nil, &block)
+ arg ||= block
+ set_or_return(:before_migrate, arg, :kind_of => [Proc, String])
end
# Callback fires before symlinking
- def before_symlink(arg=NULL_ARG, &block)
- arg = block if block_given?
- nillable_set_or_return(:before_symlink, arg, kind_of: [Proc, String])
+ def before_symlink(arg=nil, &block)
+ arg ||= block
+ set_or_return(:before_symlink, arg, :kind_of => [Proc, String])
end
# Callback fires before restart
- def before_restart(arg=NULL_ARG, &block)
- arg = block if block_given?
- nillable_set_or_return(:before_restart, arg, kind_of: [Proc, String])
+ def before_restart(arg=nil, &block)
+ arg ||= block
+ set_or_return(:before_restart, arg, :kind_of => [Proc, String])
end
# Callback fires after restart
- def after_restart(arg=NULL_ARG, &block)
- arg = block if block_given?
- nillable_set_or_return(:after_restart, arg, kind_of: [Proc, String])
+ def after_restart(arg=nil, &block)
+ arg ||= block
+ set_or_return(:after_restart, arg, :kind_of => [Proc, String])
end
- attribute :additional_remotes, kind_of: Hash
+ def additional_remotes(arg=nil)
+ set_or_return(
+ :additional_remotes,
+ arg,
+ :kind_of => Hash
+ )
+ end
- attribute :enable_checkout, kind_of: [ TrueClass, FalseClass ]
+ def enable_checkout(arg=nil)
+ set_or_return(
+ :enable_checkout,
+ arg,
+ :kind_of => [TrueClass, FalseClass]
+ )
+ end
- attribute :checkout_branch, kind_of: String
+ def checkout_branch(arg=nil)
+ set_or_return(
+ :checkout_branch,
+ arg,
+ :kind_of => String
+ )
+ end
# FIXME The Deploy resource may be passed to an SCM provider as its
# resource. The SCM provider knows that SCM resources can specify a
# timeout for SCM operations. The deploy resource must therefore support
# a timeout method, but the timeout it describes is for SCM operations,
# not the overall deployment. This is potentially confusing.
- attribute :timeout, kind_of: Integer
+ def timeout(arg=nil)
+ set_or_return(
+ :timeout,
+ arg,
+ :kind_of => Integer
+ )
+ end
end
end
diff --git a/lib/chef/resource/deploy_revision.rb b/lib/chef/resource/deploy_revision.rb
index e144ce2162..1397359ac8 100644
--- a/lib/chef/resource/deploy_revision.rb
+++ b/lib/chef/resource/deploy_revision.rb
@@ -22,23 +22,9 @@ class Chef
# Convenience class for using the deploy resource with the revision
# deployment strategy (provider)
class DeployRevision < Chef::Resource::Deploy
-
- provides :deploy_revision
-
- def initialize(*args, &block)
- super
- @resource_name = :deploy_revision
- end
end
class DeployBranch < Chef::Resource::DeployRevision
-
- provides :deploy_branch
-
- def initialize(*args, &block)
- super
- @resource_name = :deploy_branch
- end
end
end
diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb
index 1ab7f0d16d..9cac2ce243 100644
--- a/lib/chef/resource/directory.rb
+++ b/lib/chef/resource/directory.rb
@@ -32,15 +32,13 @@ class Chef
include Chef::Mixin::Securable
- provides :directory
+ default_action :create
+ allowed_actions :create, :delete
def initialize(name, run_context=nil)
super
- @resource_name = :directory
@path = name
- @action = :create
@recursive = false
- @allowed_actions.push(:create, :delete)
end
def recursive(arg=nil)
diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb
index 35a47e8a82..38adf24cf6 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.rb
@@ -25,11 +25,6 @@ class Chef
provides :dpkg_package, os: "linux"
- def initialize(name, run_context=nil)
- super
- @resource_name = :dpkg_package
- end
-
end
end
end
diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb
new file mode 100644
index 0000000000..5db00f49ca
--- /dev/null
+++ b/lib/chef/resource/dsc_resource.rb
@@ -0,0 +1,82 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+#
+# Copyright:: 2014, Opscode, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+require 'chef/dsl/powershell'
+
+class Chef
+ class Resource
+ class DscResource < Chef::Resource
+
+ provides :dsc_resource, os: "windows"
+
+ include Chef::DSL::Powershell
+
+ default_action :run
+
+ def initialize(name, run_context)
+ super
+ @properties = {}
+ @resource = nil
+ end
+
+ def resource(value=nil)
+ if value
+ @resource = value
+ else
+ @resource
+ end
+ end
+
+ def module_name(value=nil)
+ if value
+ @module_name = value
+ else
+ @module_name
+ end
+ end
+
+ def property(property_name, value=nil)
+ if not property_name.is_a?(Symbol)
+ raise TypeError, "A property name of type Symbol must be specified, '#{property_name.to_s}' of type #{property_name.class.to_s} was given"
+ end
+
+ if value.nil?
+ value_of(@properties[property_name])
+ else
+ @properties[property_name] = value
+ end
+ end
+
+ def properties
+ @properties.reduce({}) do |memo, (k, v)|
+ memo[k] = value_of(v)
+ memo
+ end
+ end
+
+ private
+
+ def value_of(value)
+ if value.is_a?(DelayedEvaluator)
+ value.call
+ else
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/dsc_script.rb b/lib/chef/resource/dsc_script.rb
index cf96ef6b7f..c3602fa60e 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -17,18 +17,19 @@
#
require 'chef/exceptions'
+require 'chef/dsl/powershell'
class Chef
class Resource
class DscScript < Chef::Resource
+ include Chef::DSL::Powershell
- provides :dsc_script, platform: "windows"
+ provides :dsc_script, os: "windows"
+
+ default_action :run
def initialize(name, run_context=nil)
super
- @allowed_actions.push(:run)
- @action = :run
- @resource_name = :dsc_script
@imports = {}
end
diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/easy_install_package.rb
index 5286e9a289..df4cee1ab3 100644
--- a/lib/chef/resource/easy_install_package.rb
+++ b/lib/chef/resource/easy_install_package.rb
@@ -22,13 +22,6 @@ class Chef
class Resource
class EasyInstallPackage < Chef::Resource::Package
- provides :easy_install_package
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :easy_install_package
- end
-
def easy_install_binary(arg=nil)
set_or_return(
:easy_install_binary,
diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/env.rb
index 4b5fe6cc09..025bfc72b7 100644
--- a/lib/chef/resource/env.rb
+++ b/lib/chef/resource/env.rb
@@ -25,14 +25,16 @@ class Chef
state_attrs :value
+ provides :env, os: "windows"
+
+ default_action :create
+ allowed_actions :create, :delete, :modify
+
def initialize(name, run_context=nil)
super
- @resource_name = :env
@key_name = name
@value = nil
- @action = :create
@delim = nil
- @allowed_actions.push(:create, :delete, :modify)
end
def key_name(arg=nil)
diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb
index 24009d51c7..1976c54c45 100644
--- a/lib/chef/resource/erl_call.rb
+++ b/lib/chef/resource/erl_call.rb
@@ -28,18 +28,16 @@ class Chef
identity_attr :code
+ default_action :run
+
def initialize(name, run_context=nil)
super
- @resource_name = :erl_call
@code = "q()." # your erlang code goes here
@cookie = nil # cookie of the erlang node
@distributed = false # if you want to have a distributed erlang node
@name_type = "sname" # type of erlang hostname name or sname
@node_name = "chef@localhost" # the erlang node hostname
-
- @action = "run"
- @allowed_actions.push(:run)
end
def code(arg=nil)
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 9f8b629fb8..ec669a75d3 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -32,12 +32,12 @@ class Chef
# Only execute resources (and subclasses) can be guard interpreters.
attr_accessor :is_guard_interpreter
+ default_action :run
+
def initialize(name, run_context=nil)
super
- @resource_name = :execute
@command = name
@backup = 5
- @action = "run"
@creates = nil
@cwd = nil
@environment = nil
@@ -46,7 +46,6 @@ class Chef
@returns = 0
@timeout = nil
@user = nil
- @allowed_actions.push(:run)
@umask = nil
@default_guard_interpreter = :execute
@is_guard_interpreter = false
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index 53a6a160af..d278652cc3 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -38,15 +38,22 @@ class Chef
attr_writer :checksum
- provides :file
+ #
+ # The checksum of the rendered file. This has to be saved on the
+ # new_resource for the 'after' state for reporting but we cannot
+ # mutate the new_resource.checksum which would change the
+ # user intent in the new_resource if the resource is reused.
+ #
+ # @returns [String] Checksum of the file we actually rendered
+ attr_accessor :final_checksum
+
+ default_action :create
+ allowed_actions :create, :delete, :touch, :create_if_missing
def initialize(name, run_context=nil)
super
- @resource_name = :file
@path = name
@backup = 5
- @action = "create"
- @allowed_actions.push(:create, :delete, :touch, :create_if_missing)
@atomic_update = Chef::Config[:file_atomic_update]
@force_unlink = false
@manage_symlink_source = nil
@@ -129,6 +136,15 @@ class Chef
@verifications
end
end
+
+ def state_for_resource_reporter
+ state_attrs = super()
+ # fix up checksum state with final_checksum saved by the provider
+ if checksum.nil? && final_checksum
+ state_attrs[:checksum] = final_checksum
+ end
+ state_attrs
+ end
end
end
end
diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb
index 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/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index 9c8db506f8..c7c43450ba 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -31,11 +31,6 @@ class Chef
provides :package, platform: "freebsd"
- def initialize(name, run_context=nil)
- super
- @resource_name = :freebsd_package
- end
-
def after_created
assign_provider
end
diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb
index 631aa13f56..b981797876 100644
--- a/lib/chef/resource/gem_package.rb
+++ b/lib/chef/resource/gem_package.rb
@@ -22,17 +22,19 @@ class Chef
class Resource
class GemPackage < Chef::Resource::Package
- provides :gem_package
-
def initialize(name, run_context=nil)
super
- @resource_name = :gem_package
+ @clear_sources = false
end
def source(arg=nil)
set_or_return(:source, arg, :kind_of => [ String, Array ])
end
+ def clear_sources(arg=nil)
+ set_or_return(:clear_sources, arg, :kind_of => [ TrueClass, FalseClass ])
+ end
+
# Sets a custom gem_binary to run for gem commands.
def gem_binary(gem_cmd=nil)
set_or_return(:gem_binary,gem_cmd,:kind_of => [ String ])
diff --git a/lib/chef/resource/git.rb b/lib/chef/resource/git.rb
index 7156873315..393a0689fe 100644
--- a/lib/chef/resource/git.rb
+++ b/lib/chef/resource/git.rb
@@ -22,11 +22,8 @@ class Chef
class Resource
class Git < Chef::Resource::Scm
- provides :git
-
def initialize(name, run_context=nil)
super
- @resource_name = :git
@additional_remotes = Hash[]
end
diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb
index daf851fac6..2e80f32fea 100644
--- a/lib/chef/resource/group.rb
+++ b/lib/chef/resource/group.rb
@@ -25,17 +25,17 @@ class Chef
state_attrs :members
+ allowed_actions :create, :remove, :modify, :manage
+ default_action :create
+
def initialize(name, run_context=nil)
super
- @resource_name = :group
@group_name = name
@gid = nil
@members = []
@excluded_members = []
- @action = :create
@append = false
@non_unique = false
- @allowed_actions.push(:create, :remove, :modify, :manage)
end
def group_name(arg=nil)
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index 3bd5dc16dc..048ba6b3aa 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -25,12 +25,10 @@ class Chef
class Resource
class HomebrewPackage < Chef::Resource::Package
- provides :homebrew_package
- provides :package, os: ["mac_os_x", "darwin"]
+ provides :package, os: "darwin"
def initialize(name, run_context=nil)
super
- @resource_name = :homebrew_package
@homebrew_user = nil
end
diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb
index ccb0a26629..f9f056325a 100644
--- a/lib/chef/resource/http_request.rb
+++ b/lib/chef/resource/http_request.rb
@@ -26,14 +26,14 @@ class Chef
identity_attr :url
+ default_action :get
+ allowed_actions :get, :put, :post, :delete, :head, :options
+
def initialize(name, run_context=nil)
super
- @resource_name = :http_request
@message = name
@url = nil
- @action = :get
@headers = {}
- @allowed_actions.push(:get, :put, :post, :delete, :head, :options)
end
def url(args=nil)
diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb
index c289ddadbe..527eb0e515 100644
--- a/lib/chef/resource/ifconfig.rb
+++ b/lib/chef/resource/ifconfig.rb
@@ -27,12 +27,12 @@ class Chef
state_attrs :inet_addr, :mask
+ default_action :add
+ allowed_actions :add, :delete, :enable, :disable
+
def initialize(name, run_context=nil)
super
- @resource_name = :ifconfig
@target = name
- @action = :add
- @allowed_actions.push(:add, :delete, :enable, :disable)
@hwaddr = nil
@mask = nil
@inet_addr = nil
@@ -145,5 +145,3 @@ class Chef
end
end
-
-
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index c0e699e31a..2bf8e1dba8 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -23,12 +23,13 @@ class Chef
class Resource
class IpsPackage < ::Chef::Resource::Package
+ provides :package, os: "solaris2"
provides :ips_package, os: "solaris2"
+ allowed_actions :install, :remove, :upgrade
+
def initialize(name, run_context = nil)
super(name, run_context)
- @resource_name = :ips_package
- @allowed_actions.push(:install, :remove, :upgrade)
@accept_license = false
end
diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb
index 8726eded1d..f932383cc1 100644
--- a/lib/chef/resource/link.rb
+++ b/lib/chef/resource/link.rb
@@ -25,21 +25,19 @@ class Chef
class Link < Chef::Resource
include Chef::Mixin::Securable
- provides :link
-
identity_attr :target_file
state_attrs :to, :owner, :group
+ default_action :create
+ allowed_actions :create, :delete
+
def initialize(name, run_context=nil)
verify_links_supported!
super
- @resource_name = :link
@to = nil
- @action = :create
@link_type = :symbolic
@target_file = name
- @allowed_actions.push(:create, :delete)
end
def to(arg=nil)
@@ -85,7 +83,7 @@ class Chef
# make link quack like a file (XXX: not for public consumption)
def path
- @target_file
+ target_file
end
private
diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb
index 7f970a87a4..9adffb26bb 100644
--- a/lib/chef/resource/log.rb
+++ b/lib/chef/resource/log.rb
@@ -26,6 +26,8 @@ class Chef
identity_attr :message
+ default_action :write
+
# Sends a string from a recipe to a log provider
#
# log "some string to log" do
@@ -48,10 +50,7 @@ class Chef
# node<Chef::Node>:: Node where resource will be used
def initialize(name, run_context=nil)
super
- @resource_name = :log
@level = :info
- @action = :write
- @allowed_actions.push(:write)
@message = name
end
@@ -75,5 +74,3 @@ class Chef
end
end
end
-
-
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index a777c511f0..443e0ed819 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -19,6 +19,13 @@
#
require 'chef/resource'
+require 'chef/resource_resolver'
+require 'chef/node'
+require 'chef/log'
+require 'chef/exceptions'
+require 'chef/mixin/convert_to_class_name'
+require 'chef/mixin/from_file'
+require 'chef/mixin/params_validate' # for DelayedEvaluator
class Chef
class Resource
@@ -28,130 +35,94 @@ class Chef
# so attributes, default action, etc. can be defined with pleasing syntax.
class LWRPBase < Resource
- NULL_ARG = Object.new
+ # Class methods
+ class <<self
- extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::FromFile
+ include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::FromFile
- # Evaluates the LWRP resource file and instantiates a new Resource class.
- def self.build_from_file(cookbook_name, filename, run_context)
- resource_class = nil
- rname = filename_to_qualified_string(cookbook_name, filename)
+ attr_accessor :loaded_lwrps
- class_name = convert_to_class_name(rname)
- if Resource.const_defined?(class_name, false)
- Chef::Log.info("#{class_name} light-weight resource is already initialized -- Skipping loading #{filename}!")
- Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.")
- resource_class = Resource.const_get(class_name)
- else
- resource_class = Class.new(self)
+ def build_from_file(cookbook_name, filename, run_context)
+ if LWRPBase.loaded_lwrps[filename]
+ Chef::Log.info("LWRP resource #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
+ return loaded_lwrps[filename]
+ end
- Chef::Resource.const_set(class_name, resource_class)
- resource_class.resource_name = rname
+ resource_name = filename_to_qualified_string(cookbook_name, filename)
+
+ # We load the class first to give it a chance to set its own name
+ resource_class = Class.new(self)
+ resource_class.resource_name resource_name.to_sym
resource_class.run_context = run_context
resource_class.class_from_file(filename)
- Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
- end
-
- resource_class
- end
+ # Make a useful string for the class (rather than <Class:312894723894>)
+ resource_class.instance_eval do
+ define_singleton_method(:to_s) do
+ "LWRP resource #{resource_name} from cookbook #{cookbook_name}"
+ end
+ define_singleton_method(:inspect) { to_s }
+ end
- # Set the resource name for this LWRP
- def self.resource_name(arg = NULL_ARG)
- if arg.equal?(NULL_ARG)
- @resource_name
- else
- @resource_name = arg
- end
- end
+ Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})")
- class << self
- alias_method :resource_name=, :resource_name
- end
+ LWRPBase.loaded_lwrps[filename] = true
- # Sets the default action
- def self.default_action(action_name=NULL_ARG)
- unless action_name.equal?(NULL_ARG)
- @actions ||= []
- if action_name.is_a?(Array)
- action = action_name.map { |arg| arg.to_sym }
- @actions = actions | action
- @default_action = action
- else
- action = action_name.to_sym
- @actions.push(action) unless @actions.include?(action)
- @default_action = action
- end
+ # Create the deprecated Chef::Resource::LwrpFoo class
+ Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name))
+ resource_class
end
- @default_action ||= from_superclass(:default_action)
- end
+ alias :attribute :property
- # Adds +action_names+ to the list of valid actions for this resource.
- def self.actions(*action_names)
- if action_names.empty?
- defined?(@actions) ? @actions : from_superclass(:actions, []).dup
- else
- # BC-compat way for checking if actions have already been defined
- if defined?(@actions)
- @actions.push(*action_names)
+ # Adds +action_names+ to the list of valid actions for this resource.
+ # Does not include superclass's action list when appending.
+ def actions(*action_names)
+ action_names = action_names.flatten
+ if !action_names.empty? && !@allowed_actions
+ self.allowed_actions = ([ :nothing ] + action_names).uniq
else
- @actions = action_names
+ allowed_actions(*action_names)
end
end
- end
-
- # @deprecated
- def self.valid_actions(*args)
- Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!")
- actions(*args)
- end
+ alias :actions= :allowed_actions=
- # Set the run context on the class. Used to provide access to the node
- # during class definition.
- def self.run_context=(run_context)
- @run_context = run_context
- end
+ # @deprecated
+ def valid_actions(*args)
+ Chef::Log.warn("`valid_actions' is deprecated, please use allowed_actions `instead'!")
+ allowed_actions(*args)
+ end
- def self.run_context
- @run_context
- end
+ # Set the run context on the class. Used to provide access to the node
+ # during class definition.
+ attr_accessor :run_context
- def self.node
- run_context.node
- end
+ def node
+ run_context ? run_context.node : nil
+ end
- def self.lazy(&block)
- DelayedEvaluator.new(&block)
- end
+ def lazy(&block)
+ DelayedEvaluator.new(&block)
+ end
- private
+ protected
- # Get the value from the superclass, if it responds, otherwise return
- # +nil+. Since class instance variables are **not** inherited upon
- # subclassing, this is a required check to ensure Chef pulls the
- # +default_action+ and other DSL-y methods when extending LWRP::Base.
- def self.from_superclass(m, default = nil)
- return default if superclass == Chef::Resource::LWRPBase
- superclass.respond_to?(m) ? superclass.send(m) : default
- end
+ def loaded_lwrps
+ @loaded_lwrps ||= {}
+ end
- # Default initializer. Sets the default action and allowed actions.
- def initialize(name, run_context=nil)
- super(name, run_context)
+ private
- # Raise an exception if the resource_name was not defined
- if self.class.resource_name.nil?
- raise Chef::Exceptions::InvalidResourceSpecification,
- "You must specify `resource_name'!"
+ # Get the value from the superclass, if it responds, otherwise return
+ # +nil+. Since class instance variables are **not** inherited upon
+ # subclassing, this is a required check to ensure Chef pulls the
+ # +default_action+ and other DSL-y methods when extending LWRP::Base.
+ def from_superclass(m, default = nil)
+ return default if superclass == Chef::Resource::LWRPBase
+ superclass.respond_to?(m) ? superclass.send(m) : default
end
-
- @resource_name = self.class.resource_name.to_sym
- @action = self.class.default_action
- allowed_actions.push(self.class.actions).flatten!
end
-
end
end
end
diff --git a/lib/chef/resource/macosx_service.rb b/lib/chef/resource/macosx_service.rb
new file mode 100644
index 0000000000..f1ed4051cb
--- /dev/null
+++ b/lib/chef/resource/macosx_service.rb
@@ -0,0 +1,58 @@
+#
+# Author:: Mike Dodge (<mikedodge04@gmail.com>)
+# Copyright:: Copyright (c) 2015 Facebook, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource/service'
+
+class Chef
+ class Resource
+ class MacosxService < Chef::Resource::Service
+
+ provides :macosx_service, os: "darwin"
+ provides :service, os: "darwin"
+
+ identity_attr :service_name
+
+ state_attrs :enabled, :running
+
+ def initialize(name, run_context=nil)
+ super
+ @plist = nil
+ @session_type = nil
+ end
+
+ # This will enable user to pass a plist in the case
+ # that the filename and label for the service dont match
+ def plist(arg=nil)
+ set_or_return(
+ :plist,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ def session_type(arg=nil)
+ set_or_return(
+ :session_type,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/resource/macports_package.rb b/lib/chef/resource/macports_package.rb
index 0d4e5dec65..5843016897 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -16,17 +16,11 @@
# limitations under the License.
#
+require 'chef/resource/package'
+
class Chef
class Resource
class MacportsPackage < Chef::Resource::Package
-
- provides :macports_package
- provides :package, os: "darwin"
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :macports_package
- end
end
end
end
diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb
index 46a85b2475..b789fab155 100644
--- a/lib/chef/resource/mdadm.rb
+++ b/lib/chef/resource/mdadm.rb
@@ -27,9 +27,11 @@ class Chef
state_attrs :devices, :level, :chunk
+ default_action :create
+ allowed_actions :create, :assemble, :stop
+
def initialize(name, run_context=nil)
super
- @resource_name = :mdadm
@chunk = 16
@devices = []
@@ -38,9 +40,6 @@ class Chef
@metadata = "0.90"
@bitmap = nil
@raid_device = name
-
- @action = :create
- @allowed_actions.push(:create, :assemble, :stop)
end
def chunk(arg=nil)
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 275c069f61..a5da0ba329 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -27,9 +27,11 @@ class Chef
state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain
+ default_action :mount
+ allowed_actions :mount, :umount, :remount, :enable, :disable
+
def initialize(name, run_context=nil)
super
- @resource_name = :mount
@mount_point = name
@device = nil
@device_type = :device
@@ -40,9 +42,7 @@ class Chef
@pass = 2
@mounted = false
@enabled = false
- @action = :mount
@supports = { :remount => false }
- @allowed_actions.push(:mount, :umount, :remount, :enable, :disable)
@username = nil
@password = nil
@domain = nil
@@ -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/ohai.rb b/lib/chef/resource/ohai.rb
index b567db40f9..9425e55c0c 100644
--- a/lib/chef/resource/ohai.rb
+++ b/lib/chef/resource/ohai.rb
@@ -25,12 +25,11 @@ class Chef
state_attrs :plugin
+ default_action :reload
+
def initialize(name, run_context=nil)
super
- @resource_name = :ohai
@name = name
- @allowed_actions.push(:reload)
- @action = :reload
@plugin = nil
end
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index 20a2523e3a..9ae8813d69 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -29,23 +29,6 @@ class Chef
include Chef::Mixin::ShellOut
provides :package, os: "openbsd"
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :openbsd_package
- end
-
- def after_created
- assign_provider
- end
-
- private
-
- def assign_provider
- @provider = Chef::Provider::Package::Openbsd
- end
-
end
end
end
-
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index f4f49b543b..5be1c34b89 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -22,24 +22,23 @@ require 'chef/resource'
class Chef
class Resource
class Package < Chef::Resource
-
identity_attr :package_name
state_attrs :version, :options
+ default_action :install
+ allowed_actions :install, :upgrade, :remove, :purge, :reconfig
+
def initialize(name, run_context=nil)
super
- @action = :install
- @allowed_actions.push(:install, :upgrade, :remove, :purge, :reconfig)
@candidate_version = nil
@options = nil
@package_name = name
- @resource_name = :package
@response_file = nil
@response_file_variables = Hash.new
@source = nil
@version = nil
- @timeout = 900
+ @timeout = nil
end
def package_name(arg=nil)
diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/resource/pacman_package.rb
index 4c45dd004f..54b8efc4c2 100644
--- a/lib/chef/resource/pacman_package.rb
+++ b/lib/chef/resource/pacman_package.rb
@@ -21,14 +21,7 @@ require 'chef/resource/package'
class Chef
class Resource
class PacmanPackage < Chef::Resource::Package
-
provides :pacman_package, os: "linux"
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :pacman_package
- end
-
end
end
end
diff --git a/lib/chef/resource/paludis_package.rb b/lib/chef/resource/paludis_package.rb
index 552c96857a..56c47bc141 100644
--- a/lib/chef/resource/paludis_package.rb
+++ b/lib/chef/resource/paludis_package.rb
@@ -22,13 +22,12 @@ require 'chef/provider/package/paludis'
class Chef
class Resource
class PaludisPackage < Chef::Resource::Package
-
provides :paludis_package, os: "linux"
+ allowed_actions :install, :remove, :upgrade
+
def initialize(name, run_context=nil)
super(name, run_context)
- @resource_name = :paludis_package
- @allowed_actions.push(:install, :remove, :upgrade)
@timeout = 3600
end
end
diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb
index c4bdb6e130..773eba6571 100644
--- a/lib/chef/resource/perl.rb
+++ b/lib/chef/resource/perl.rb
@@ -22,10 +22,8 @@ require 'chef/provider/script'
class Chef
class Resource
class Perl < Chef::Resource::Script
-
def initialize(name, run_context=nil)
super
- @resource_name = :perl
@interpreter = "perl"
end
diff --git a/lib/chef/resource/portage_package.rb b/lib/chef/resource/portage_package.rb
index 42c03560b6..1af48702fa 100644
--- a/lib/chef/resource/portage_package.rb
+++ b/lib/chef/resource/portage_package.rb
@@ -21,10 +21,8 @@ require 'chef/resource/package'
class Chef
class Resource
class PortagePackage < Chef::Resource::Package
-
def initialize(name, run_context=nil)
super
- @resource_name = :portage_package
@provider = Chef::Provider::Package::Portage
end
diff --git a/lib/chef/resource/powershell_script.rb b/lib/chef/resource/powershell_script.rb
index 1b8aef94a2..7d432883e4 100644
--- a/lib/chef/resource/powershell_script.rb
+++ b/lib/chef/resource/powershell_script.rb
@@ -20,9 +20,10 @@ require 'chef/resource/windows_script'
class Chef
class Resource
class PowershellScript < Chef::Resource::WindowsScript
+ provides :powershell_script, os: "windows"
def initialize(name, run_context=nil)
- super(name, run_context, :powershell_script, "powershell.exe")
+ super(name, run_context, nil, "powershell.exe")
@convert_boolean_return = false
end
diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb
index b1f23d13ce..432ee46b85 100644
--- a/lib/chef/resource/python.rb
+++ b/lib/chef/resource/python.rb
@@ -21,10 +21,8 @@ require 'chef/provider/script'
class Chef
class Resource
class Python < Chef::Resource::Script
-
def initialize(name, run_context=nil)
super
- @resource_name = :python
@interpreter = "python"
end
diff --git a/lib/chef/resource/reboot.rb b/lib/chef/resource/reboot.rb
index c111b23d2e..401f2f338f 100644
--- a/lib/chef/resource/reboot.rb
+++ b/lib/chef/resource/reboot.rb
@@ -24,11 +24,11 @@ require 'chef/resource'
class Chef
class Resource
class Reboot < Chef::Resource
+ allowed_actions :request_reboot, :reboot_now, :cancel
+
def initialize(name, run_context=nil)
super
- @resource_name = :reboot
@provider = Chef::Provider::Reboot
- @allowed_actions.push(:request_reboot, :reboot_now, :cancel)
@reason = "Reboot by Chef"
@delay_mins = 0
diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb
index 8126ccf126..d2e5c4b94c 100644
--- a/lib/chef/resource/registry_key.rb
+++ b/lib/chef/resource/registry_key.rb
@@ -22,10 +22,12 @@ require 'chef/digester'
class Chef
class Resource
class RegistryKey < Chef::Resource
-
identity_attr :key
state_attrs :values
+ default_action :create
+ allowed_actions :create, :create_if_missing, :delete, :delete_key
+
# Some registry key data types may not be safely reported as json.
# Example (CHEF-5323):
#
@@ -59,13 +61,10 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :registry_key
- @action = :create
@architecture = :machine
@recursive = false
@key = name
@values, @unscrubbed_values = [], []
- @allowed_actions.push(:create, :create_if_missing, :delete, :delete_key)
end
def key(arg=nil)
@@ -94,7 +93,7 @@ class Chef
raise ArgumentError, "Bad key #{key} in RegistryKey values hash" unless [:name,:type,:data].include?(key)
end
raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String)
- raise Argument Error "Type of type => #{v[:name]} should be symbol" unless v[:type].is_a?(Symbol)
+ raise ArgumentError, "Type of type => #{v[:type]} should be symbol" unless v[:type].is_a?(Symbol)
end
@unscrubbed_values = @values
elsif self.instance_variable_defined?(:@values)
diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb
index d4108da47a..b731f7b201 100644
--- a/lib/chef/resource/remote_directory.rb
+++ b/lib/chef/resource/remote_directory.rb
@@ -26,19 +26,18 @@ class Chef
class RemoteDirectory < Chef::Resource::Directory
include Chef::Mixin::Securable
- provides :remote_directory
-
identity_attr :path
state_attrs :files_owner, :files_group, :files_mode
+ default_action :create
+ allowed_actions :create, :create_if_missing, :delete
+
def initialize(name, run_context=nil)
super
- @resource_name = :remote_directory
@path = name
@source = ::File.basename(name)
@delete = false
- @action = :create
@recursive = true
@purge = false
@files_backup = 5
@@ -46,7 +45,6 @@ class Chef
@files_group = nil
@files_mode = 0644 unless Chef::Platform.windows?
@overwrite = true
- @allowed_actions.push(:create, :create_if_missing, :delete)
@cookbook = nil
end
diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb
index 7ba98b9d3b..b7a553cbe8 100644
--- a/lib/chef/resource/remote_file.rb
+++ b/lib/chef/resource/remote_file.rb
@@ -21,18 +21,15 @@ require 'uri'
require 'chef/resource/file'
require 'chef/provider/remote_file'
require 'chef/mixin/securable'
+require 'chef/mixin/uris'
class Chef
class Resource
class RemoteFile < Chef::Resource::File
include Chef::Mixin::Securable
- provides :remote_file
-
def initialize(name, run_context=nil)
super
- @resource_name = :remote_file
- @action = "create"
@source = []
@use_etag = true
@use_last_modified = true
@@ -125,12 +122,10 @@ class Chef
)
end
- def after_created
- validate_source(@source)
- end
-
private
+ include Chef::Mixin::Uris
+
def validate_source(source)
source = Array(source).flatten
raise ArgumentError, "#{resource_name} has an empty source" if source.empty?
@@ -144,7 +139,7 @@ class Chef
end
def absolute_uri?(source)
- source.kind_of?(String) and URI.parse(source).absolute?
+ Chef::Provider::RemoteFile::Fetcher.network_share?(source) or (source.kind_of?(String) and as_uri(source).absolute?)
rescue URI::InvalidURIError
false
end
diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb
index 942905d138..3ba8f6215b 100644
--- a/lib/chef/resource/route.rb
+++ b/lib/chef/resource/route.rb
@@ -22,17 +22,16 @@ require 'chef/resource'
class Chef
class Resource
class Route < Chef::Resource
-
identity_attr :target
state_attrs :netmask, :gateway
+ default_action :add
+ allowed_actions :add, :delete
+
def initialize(name, run_context=nil)
super
- @resource_name = :route
@target = name
- @action = [:add]
- @allowed_actions.push(:add, :delete)
@netmask = nil
@gateway = nil
@metric = nil
@@ -136,5 +135,3 @@ class Chef
end
end
end
-
-
diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb
index f00121dd69..b8b5144a42 100644
--- a/lib/chef/resource/rpm_package.rb
+++ b/lib/chef/resource/rpm_package.rb
@@ -22,12 +22,10 @@ require 'chef/provider/package/rpm'
class Chef
class Resource
class RpmPackage < Chef::Resource::Package
-
provides :rpm_package, os: [ "linux", "aix" ]
def initialize(name, run_context=nil)
super
- @resource_name = :rpm_package
@allow_downgrade = false
end
diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb
index 2b2aa0249d..3c3909043d 100644
--- a/lib/chef/resource/ruby.rb
+++ b/lib/chef/resource/ruby.rb
@@ -22,13 +22,10 @@ require 'chef/provider/script'
class Chef
class Resource
class Ruby < Chef::Resource::Script
-
def initialize(name, run_context=nil)
super
- @resource_name = :ruby
@interpreter = "ruby"
end
-
end
end
end
diff --git a/lib/chef/resource/ruby_block.rb b/lib/chef/resource/ruby_block.rb
index a9cbf234cf..ae8e4cb7cd 100644
--- a/lib/chef/resource/ruby_block.rb
+++ b/lib/chef/resource/ruby_block.rb
@@ -23,14 +23,13 @@ require 'chef/provider/ruby_block'
class Chef
class Resource
class RubyBlock < Chef::Resource
+ default_action :run
+ allowed_actions :create, :run
identity_attr :block_name
def initialize(name, run_context=nil)
super
- @resource_name = :ruby_block
- @action = "run"
- @allowed_actions << :create << :run
@block_name = name
end
diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb
index 87c217b4cc..85028c266b 100644
--- a/lib/chef/resource/scm.rb
+++ b/lib/chef/resource/scm.rb
@@ -22,23 +22,22 @@ require 'chef/resource'
class Chef
class Resource
class Scm < Chef::Resource
-
identity_attr :destination
state_attrs :revision
+ default_action :sync
+ allowed_actions :checkout, :export, :sync, :diff, :log
+
def initialize(name, run_context=nil)
super
@destination = name
- @resource_name = :scm
@enable_submodules = false
@enable_checkout = true
@revision = "HEAD"
@remote = "origin"
@ssh_wrapper = nil
@depth = nil
- @allowed_actions.push(:checkout, :export, :sync, :diff, :log)
- @action = [:sync]
@checkout_branch = "deploy"
@environment = nil
end
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index fd0fd5a7fd..30bed367cb 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -23,13 +23,11 @@ require 'chef/provider/script'
class Chef
class Resource
class Script < Chef::Resource::Execute
-
# Chef-13: go back to using :name as the identity attr
identity_attr :command
def initialize(name, run_context=nil)
super
- @resource_name = :script
# Chef-13: the command variable should be initialized to nil
@command = name
@code = nil
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index 36df7c859a..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");
@@ -22,14 +22,15 @@ require 'chef/resource'
class Chef
class Resource
class Service < Chef::Resource
-
identity_attr :service_name
state_attrs :enabled, :running
+ default_action :nothing
+ allowed_actions :enable, :disable, :start, :stop, :restart, :reload
+
def initialize(name, run_context=nil)
super
- @resource_name = :service
@service_name = name
@enabled = nil
@running = nil
@@ -43,9 +44,8 @@ class Chef
@init_command = nil
@priority = nil
@timeout = nil
- @action = "nothing"
- @supports = { :restart => false, :reload => false, :status => false }
- @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload)
+ @run_levels = nil
+ @supports = { :restart => nil, :reload => nil, :status => nil }
end
def service_name(arg=nil)
@@ -175,6 +175,13 @@ class Chef
)
end
+ def run_levels(arg=nil)
+ set_or_return(
+ :run_levels,
+ arg,
+ :kind_of => [ Array ] )
+ end
+
def supports(args={})
if args.is_a? Array
args.each { |arg| @supports[arg] = true }
diff --git a/lib/chef/resource/smartos_package.rb b/lib/chef/resource/smartos_package.rb
index 99b3b953b0..b8bd940c24 100644
--- a/lib/chef/resource/smartos_package.rb
+++ b/lib/chef/resource/smartos_package.rb
@@ -22,16 +22,7 @@ require 'chef/provider/package/smartos'
class Chef
class Resource
class SmartosPackage < Chef::Resource::Package
-
- provides :smartos_package
provides :package, os: "solaris2", platform_family: "smartos"
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :smartos_package
- end
-
end
end
end
-
diff --git a/lib/chef/resource/solaris_package.rb b/lib/chef/resource/solaris_package.rb
index 94be4693b6..a98fb8b4fa 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -23,21 +23,8 @@ require 'chef/provider/package/solaris'
class Chef
class Resource
class SolarisPackage < Chef::Resource::Package
-
- provides :solaris_package
provides :package, os: "solaris2", platform_family: "nexentacore"
- provides :package, os: "solaris2", platform_family: "solaris2" do |node|
- # on >= Solaris 11 we default to IPS packages instead
- node[:platform_version].to_f <= 5.10
- end
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :solaris_package
- end
-
+ provides :package, os: "solaris2", platform_family: "solaris2", platform_version: "<= 5.10"
end
end
end
-
-
diff --git a/lib/chef/resource/subversion.rb b/lib/chef/resource/subversion.rb
index 3afbe0baaf..ae6a37caa2 100644
--- a/lib/chef/resource/subversion.rb
+++ b/lib/chef/resource/subversion.rb
@@ -22,13 +22,12 @@ require "chef/resource/scm"
class Chef
class Resource
class Subversion < Chef::Resource::Scm
+ allowed_actions :force_export
def initialize(name, run_context=nil)
super
@svn_arguments = '--no-auth-cache'
@svn_info_args = '--no-auth-cache'
- @resource_name = :subversion
- allowed_actions << :force_export
end
# Override exception to strip password if any, so it won't appear in logs and different Chef notifications
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 67a9e6a418..5a7f7efd6f 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -27,15 +27,11 @@ class Chef
class Template < Chef::Resource::File
include Chef::Mixin::Securable
- provides :template
-
attr_reader :inline_helper_blocks
attr_reader :inline_helper_modules
def initialize(name, run_context=nil)
super
- @resource_name = :template
- @action = "create"
@source = "#{::File.basename(name)}.erb"
@cookbook = nil
@local = false
diff --git a/lib/chef/resource/timestamped_deploy.rb b/lib/chef/resource/timestamped_deploy.rb
index b2109db85c..344f8b0a5e 100644
--- a/lib/chef/resource/timestamped_deploy.rb
+++ b/lib/chef/resource/timestamped_deploy.rb
@@ -21,10 +21,6 @@ class Chef
# Convenience class for using the deploy resource with the timestamped
# deployment strategy (provider)
class TimestampedDeploy < Chef::Resource::Deploy
- provides :timestamped_deploy
- def initialize(*args, &block)
- super(*args, &block)
- end
end
end
end
diff --git a/lib/chef/resource/user.rb b/lib/chef/resource/user.rb
index 9d6e857de7..b85b648c92 100644
--- a/lib/chef/resource/user.rb
+++ b/lib/chef/resource/user.rb
@@ -21,14 +21,15 @@ require 'chef/resource'
class Chef
class Resource
class User < Chef::Resource
-
identity_attr :username
state_attrs :uid, :gid, :home
+ default_action :create
+ allowed_actions :create, :remove, :modify, :manage, :lock, :unlock
+
def initialize(name, run_context=nil)
super
- @resource_name = :user
@username = name
@comment = nil
@uid = nil
@@ -40,14 +41,12 @@ class Chef
@manage_home = false
@force = false
@non_unique = false
- @action = :create
@supports = {
:manage_home => false,
:non_unique => false
}
@iterations = 27855
@salt = nil
- @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
end
def username(arg=nil)
diff --git a/lib/chef/resource/whyrun_safe_ruby_block.rb b/lib/chef/resource/whyrun_safe_ruby_block.rb
index 6fa5383f5d..f289f15001 100644
--- a/lib/chef/resource/whyrun_safe_ruby_block.rb
+++ b/lib/chef/resource/whyrun_safe_ruby_block.rb
@@ -19,12 +19,6 @@
class Chef
class Resource
class WhyrunSafeRubyBlock < Chef::Resource::RubyBlock
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :whyrun_safe_ruby_block
- end
-
end
end
end
diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb
index 16cfcf865e..a76765cc36 100644
--- a/lib/chef/resource/windows_package.rb
+++ b/lib/chef/resource/windows_package.rb
@@ -16,6 +16,7 @@
# limitations under the License.
#
+require 'chef/mixin/uris'
require 'chef/resource/package'
require 'chef/provider/package/windows'
require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
@@ -23,14 +24,15 @@ require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
class Chef
class Resource
class WindowsPackage < Chef::Resource::Package
+ include Chef::Mixin::Uris
- provides :package, os: "windows"
provides :windows_package, os: "windows"
+ provides :package, os: "windows"
+
+ allowed_actions :install, :remove
def initialize(name, run_context=nil)
super
- @allowed_actions.push(:install, :remove)
- @resource_name = :windows_package
@source ||= source(@package_name)
# Unique to this resource
@@ -69,10 +71,30 @@ class Chef
@source
else
raise ArgumentError, "Bad type for WindowsPackage resource, use a String" unless arg.is_a?(String)
- Chef::Log.debug("#{package_name}: sanitizing source path '#{arg}'")
- @source = ::File.absolute_path(arg).gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)
+ if uri_scheme?(arg)
+ @source = arg
+ else
+ @source = Chef::Util::PathHelper.canonical_path(arg, false)
+ end
end
end
+
+ def checksum(arg=nil)
+ set_or_return(
+ :checksum,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def remote_file_attributes(arg=nil)
+ set_or_return(
+ :remote_file_attributes,
+ arg,
+ :kind_of => [ Hash ]
+ )
+ end
+
end
end
end
diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb
index 6b0827b77c..48e2b535a8 100644
--- a/lib/chef/resource/windows_script.rb
+++ b/lib/chef/resource/windows_script.rb
@@ -22,6 +22,7 @@ require 'chef/mixin/windows_architecture_helper'
class Chef
class Resource
class WindowsScript < Chef::Resource::Script
+ # This is an abstract resource meant to be subclasses; thus no 'provides'
set_guard_inherited_attributes(:architecture)
@@ -30,8 +31,8 @@ class Chef
def initialize(name, run_context, resource_name, interpreter_command)
super(name, run_context)
@interpreter = interpreter_command
- @resource_name = resource_name
- @default_guard_interpreter = resource_name
+ @resource_name = resource_name if resource_name
+ @default_guard_interpreter = self.resource_name
end
include Chef::Mixin::WindowsArchitectureHelper
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
index 8090adceb0..a77690652e 100644
--- a/lib/chef/resource/windows_service.rb
+++ b/lib/chef/resource/windows_service.rb
@@ -25,8 +25,10 @@ class Chef
# Until #1773 is resolved, you need to manually specify the windows_service resource
# to use action :configure_startup and attribute startup_type
- provides :service, os: "windows"
provides :windows_service, os: "windows"
+ provides :service, os: "windows"
+
+ allowed_actions :configure_startup
identity_attr :service_name
@@ -34,8 +36,6 @@ class Chef
def initialize(name, run_context=nil)
super
- @resource_name = :windows_service
- @allowed_actions.push(:configure_startup)
@startup_type = :automatic
@run_as_user = ""
@run_as_password = ""
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index 8fbca9b097..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");
@@ -22,15 +22,13 @@ require 'chef/provider/package/yum'
class Chef
class Resource
class YumPackage < Chef::Resource::Package
-
- provides :yum_package
provides :package, os: "linux", platform_family: [ "rhel", "fedora" ]
def initialize(name, run_context=nil)
super
- @resource_name = :yum_package
@flush_cache = { :before => false, :after => false }
@allow_downgrade = false
+ @yum_binary = nil
end
# Install a specific arch
@@ -38,7 +36,7 @@ class Chef
set_or_return(
:arch,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String, Array ]
)
end
@@ -60,6 +58,14 @@ class Chef
)
end
+ def yum_binary(arg=nil)
+ set_or_return(
+ :yum_binary,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
end
end
end
diff --git a/lib/chef/resource/zypper_package.rb b/lib/chef/resource/zypper_package.rb
new file mode 100644
index 0000000000..f09a20e2c6
--- /dev/null
+++ b/lib/chef/resource/zypper_package.rb
@@ -0,0 +1,27 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/resource/package'
+
+class Chef
+ class Resource
+ class ZypperPackage < Chef::Resource::Package
+ provides :package, platform_family: "suse"
+ end
+ end
+end
diff --git a/lib/chef/resource_builder.rb b/lib/chef/resource_builder.rb
index bb0962d128..9e9f7047a4 100644
--- a/lib/chef/resource_builder.rb
+++ b/lib/chef/resource_builder.rb
@@ -18,6 +18,10 @@
# NOTE: this was extracted from the Recipe DSL mixin, relevant specs are in spec/unit/recipe_spec.rb
+require 'chef/exceptions'
+require 'chef/resource'
+require 'chef/log'
+
class Chef
class ResourceBuilder
attr_reader :type
@@ -46,6 +50,9 @@ class Chef
raise ArgumentError, "You must supply a name when declaring a #{type} resource" if name.nil?
@resource = resource_class.new(name, run_context)
+ if resource.resource_name.nil?
+ raise Chef::Exceptions::InvalidResourceSpecification, "#{resource}.resource_name is `nil`! Did you forget to put `provides :blah` or `resource_name :blah` in your resource class?"
+ end
resource.source_line = created_at
resource.declared_type = type
diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb
index 9d6844129c..cffabb6786 100644
--- a/lib/chef/resource_definition.rb
+++ b/lib/chef/resource_definition.rb
@@ -50,6 +50,7 @@ class Chef
else
raise ArgumentError, "You must pass a block to a definition."
end
+ Chef::DSL::Definitions.add_definition(name)
true
end
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 1816fc857d..7d13a5a5ce 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -59,11 +59,11 @@ class Chef
# attrs.
def for_json
as_hash = {}
- as_hash["type"] = new_resource.class.dsl_name
+ as_hash["type"] = new_resource.resource_name.to_sym
as_hash["name"] = new_resource.name.to_s
as_hash["id"] = new_resource.identity.to_s
- as_hash["after"] = state(new_resource)
- as_hash["before"] = current_resource ? state(current_resource) : {}
+ as_hash["after"] = new_resource.state_for_resource_reporter
+ as_hash["before"] = current_resource ? current_resource.state_for_resource_reporter : {}
as_hash["duration"] = (elapsed_time * 1000).to_i.to_s
as_hash["delta"] = new_resource.diff if new_resource.respond_to?("diff")
as_hash["delta"] = "" if as_hash["delta"].nil?
@@ -89,13 +89,6 @@ class Chef
def success?
!self.exception
end
-
- def state(r)
- r.class.state_attrs.inject({}) do |state_attrs, attr_name|
- state_attrs[attr_name] = r.send(attr_name)
- state_attrs
- end
- end
end # End class ResouceReport
attr_reader :updated_resources
@@ -220,7 +213,7 @@ class Chef
# If we failed before we received the run_started callback, there's not much we can do
# in terms of reporting
if @run_status
- post_reporting_data
+ post_reporting_data
end
end
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
new file mode 100644
index 0000000000..67cf134c62
--- /dev/null
+++ b/lib/chef/resource_resolver.rb
@@ -0,0 +1,186 @@
+#
+# Author:: Lamont Granquist (<lamont@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/exceptions'
+require 'chef/platform/resource_priority_map'
+require 'chef/mixin/convert_to_class_name'
+
+class Chef
+ class ResourceResolver
+ #
+ # Resolve a resource by name.
+ #
+ # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+ # @param node [Chef::Node] The node against which to resolve. `nil` causes
+ # platform filters to be ignored.
+ #
+ def self.resolve(resource_name, node: nil, canonical: nil)
+ new(node, resource_name, canonical: canonical).resolve
+ end
+
+ #
+ # Resolve a list of all resources that implement the given DSL (in order of
+ # preference).
+ #
+ # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+ # @param node [Chef::Node] The node against which to resolve. `nil` causes
+ # platform filters to be ignored.
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality.
+ #
+ def self.list(resource_name, node: nil, canonical: nil)
+ new(node, resource_name, canonical: canonical).list
+ end
+
+
+ include Chef::Mixin::ConvertToClassName
+
+ # @api private
+ attr_reader :node
+ # @api private
+ attr_reader :resource_name
+ # @api private
+ def resource
+ Chef.log_deprecation("Chef::ResourceResolver.resource deprecated. Use resource_name instead.")
+ resource_name
+ end
+ # @api private
+ attr_reader :action
+ # @api private
+ attr_reader :canonical
+
+ #
+ # Create a resolver.
+ #
+ # @param node [Chef::Node] The node against which to resolve. `nil` causes
+ # platform filters to be ignored.
+ # @param resource_name [Symbol] The resource DSL name (e.g. `:file`).
+ # @param canonical [Boolean] `true` or `false` to match canonical or
+ # non-canonical values only. `nil` to ignore canonicality. Default: `nil`
+ #
+ # @api private use Chef::ResourceResolver.resolve or .list instead.
+ def initialize(node, resource_name, canonical: nil)
+ @node = node
+ @resource_name = resource_name.to_sym
+ @canonical = canonical
+ end
+
+ # @api private use Chef::ResourceResolver.resolve instead.
+ def resolve
+ # log this so we know what resources will work for the generic resource on the node (early cut)
+ Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
+
+ handler = prioritized_handlers.first
+
+ if handler
+ Chef::Log.debug "Resource for #{resource_name} is #{handler}"
+ else
+ Chef::Log.debug "Dynamic resource resolver FAILED to resolve a resource for #{resource_name}"
+ end
+
+ handler
+ end
+
+ # @api private
+ def list
+ Chef::Log.debug "Resources for generic #{resource_name} resource enabled on node include: #{prioritized_handlers}"
+ prioritized_handlers
+ end
+
+ #
+ # Whether this DSL is provided by the given resource_class.
+ #
+ # Does NOT call provides? on the resource (it is assumed this is being
+ # called *from* provides?).
+ #
+ # @api private
+ def provided_by?(resource_class)
+ potential_handlers.include?(resource_class)
+ end
+
+ #
+ # Whether the given handler attempts to provide the resource class at all.
+ #
+ # @api private
+ def self.includes_handler?(resource_name, resource_class)
+ handler_map.list(nil, resource_name).include?(resource_class)
+ end
+
+ protected
+
+ def self.priority_map
+ Chef.resource_priority_map
+ end
+
+ def self.handler_map
+ Chef.resource_handler_map
+ end
+
+ def priority_map
+ 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 ||= 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
+ def resources
+ Chef::Resource.sorted_descendants
+ end
+
+ def enabled_handlers
+ handlers = super
+ if handlers.empty?
+ handlers = resources.select { |handler| overrode_provides?(handler) && handler.provides?(node, resource_name) }
+ handlers.each do |handler|
+ Chef.log_deprecation("#{handler}.provides? returned true when asked if it provides DSL #{resource_name}, but provides #{resource_name.inspect} was never called!")
+ Chef.log_deprecation("In Chef 13, this will break: you must call provides to mark the names you provide, even if you also override provides? yourself.")
+ end
+ end
+ handlers
+ end
+ end
+ prepend Deprecated
+ end
+end
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 680b393741..af4dd8a642 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -29,6 +29,7 @@ require 'chef/resource/deploy_revision'
require 'chef/resource/directory'
require 'chef/resource/dpkg_package'
require 'chef/resource/dsc_script'
+require 'chef/resource/dsc_resource'
require 'chef/resource/easy_install_package'
require 'chef/resource/env'
require 'chef/resource/erl_call'
@@ -79,6 +80,7 @@ require 'chef/resource/windows_package'
require 'chef/resource/yum_package'
require 'chef/resource/lwrp_base'
require 'chef/resource/bff_package'
+require 'chef/resource/zypper_package'
begin
# Optional resources chef_node, chef_client, machine, machine_image, etc.
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index f0de443058..f87cec9b76 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -39,6 +39,7 @@ require 'chef/platform/query_helpers'
require 'chef/http/remote_request_id'
class Chef
+
# == Chef::REST
# Chef's custom REST client with built-in JSON support and RSA signed header
# authentication.
@@ -57,9 +58,13 @@ class Chef
# http://localhost:4000, a call to +get_rest+ with 'nodes' will make an
# HTTP GET request to http://localhost:4000/nodes
def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={})
+
+ signing_key_filename = nil if chef_zero_uri?(url)
+
options = options.dup
options[:client_name] = client_name
options[:signing_key_filename] = signing_key_filename
+
super(url, options)
@decompressor = Decompressor.new(options)
@@ -188,11 +193,6 @@ class Chef
public :create_url
- def http_client(base_url=nil)
- base_url ||= url
- BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
- end
-
############################################################################
# DEPRECATED
############################################################################
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index fc54506407..0c8d3d1a48 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -25,117 +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 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 Chef::ResourceCollection for this run. Populated by evaluating
- # recipes, which is triggered by #load. (See also: CookbookCompiler)
- attr_accessor :resource_collection
+ #
+ # The list of control groups to execute during the audit phase
+ #
+ attr_reader :audits
- # The list of audits (control groups) to execute during the audit phase
- attr_accessor :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.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|
@@ -146,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")
@@ -174,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}"
@@ -185,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
@@ -197,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
@@ -279,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 0eefded964..2824f08f31 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -63,6 +63,25 @@ class Chef
end
end
end
+
+ # Get an array of strings of the fully-qualified recipe names (with ::default appended) and
+ # with the versions in "NAME@VERSION" format.
+ #
+ # @return [Array] Array of strings with fully-qualified recipe names
+ def with_fully_qualified_names_and_version_constraints
+ self.map do |recipe_name|
+ qualified_recipe = if recipe_name.include?('::')
+ recipe_name
+ else
+ "#{recipe_name}::default"
+ end
+
+ version = @versions[recipe_name]
+ qualified_recipe = "#{qualified_recipe}@#{version}" if version
+
+ qualified_recipe
+ end
+ end
end
end
end
diff --git a/lib/chef/run_status.rb b/lib/chef/run_status.rb
index 0f181426b0..ce8ff296f4 100644
--- a/lib/chef/run_status.rb
+++ b/lib/chef/run_status.rb
@@ -39,15 +39,13 @@ class Chef::RunStatus
attr_accessor :run_id
+ attr_accessor :node
+
def initialize(node, events)
@node = node
@events = events
end
- def node
- @node
- end
-
# sets +start_time+ to the current time.
def start_clock
@start_time = Time.now
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index 8656e810db..6469a18c49 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -89,7 +89,7 @@ WARNDEP
if block
response["rows"].each { |row| block.call(row) if row }
unless (response["start"] + response["rows"].length) >= response["total"]
- args_h[:start] = response["start"] + (args_h[:rows] || 0)
+ args_h[:start] = response["start"] + response["rows"].length
search(type, query, args_h, &block)
end
true
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
index 8cdcd7a09d..764296f8c8 100644
--- a/lib/chef/server_api.rb
+++ b/lib/chef/server_api.rb
@@ -30,6 +30,7 @@ class Chef
def initialize(url = Chef::Config[:chef_server_url], options = {})
options[:client_name] ||= Chef::Config[:node_name]
options[:signing_key_filename] ||= Chef::Config[:client_key]
+ options[:signing_key_filename] = nil if chef_zero_uri?(url)
super(url, options)
end
@@ -41,3 +42,5 @@ class Chef
use Chef::HTTP::RemoteRequestID
end
end
+
+require 'chef/config'
diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb
index fed32b3795..3a68785ce9 100644
--- a/lib/chef/shell.rb
+++ b/lib/chef/shell.rb
@@ -29,6 +29,7 @@ require 'chef/config_fetcher'
require 'chef/shell/shell_session'
require 'chef/shell/ext'
require 'chef/json_compat'
+require 'chef/util/path_helper'
# = Shell
# Shell is Chef in an IRB session. Shell can interact with a Chef server via the
@@ -101,7 +102,7 @@ module Shell
end
def self.configure_irb
- irb_conf[:HISTORY_FILE] = "~/.chef/chef_shell_history"
+ irb_conf[:HISTORY_FILE] = Chef::Util::PathHelper.home(".chef", "chef_shell_history")
irb_conf[:SAVE_HISTORY] = 1000
irb_conf[:IRB_RC] = lambda do |conf|
@@ -109,7 +110,7 @@ module Shell
conf.prompt_c = "chef#{leader(m)} > "
conf.return_format = " => %s \n"
- conf.prompt_i = "chef#{leader(m)} > "
+ conf.prompt_i = "chef#{leader(m)} (#{Chef::VERSION})> "
conf.prompt_n = "chef#{leader(m)} ?> "
conf.prompt_s = "chef#{leader(m)}%l> "
conf.use_tracer = false
@@ -295,18 +296,19 @@ FOOTER
private
def config_file_for_shell_mode(environment)
+ dot_chef_dir = Chef::Util::PathHelper.home('.chef')
if config[:config_file]
config[:config_file]
- elsif environment && ENV['HOME']
+ elsif environment
Shell.env = environment
- config_file_to_try = ::File.join(ENV['HOME'], '.chef', environment, 'chef_shell.rb')
+ config_file_to_try = ::File.join(dot_chef_dir, environment, 'chef_shell.rb')
unless ::File.exist?(config_file_to_try)
puts "could not find chef-shell config for environment #{environment} at #{config_file_to_try}"
exit 1
end
config_file_to_try
- elsif ENV['HOME'] && ::File.exist?(File.join(ENV['HOME'], '.chef', 'chef_shell.rb'))
- File.join(ENV['HOME'], '.chef', 'chef_shell.rb')
+ elsif dot_chef_dir && ::File.exist?(File.join(dot_chef_dir, 'chef_shell.rb'))
+ File.join(dot_chef_dir, 'chef_shell.rb')
elsif config[:solo]
Chef::Config.platform_specific_path("/etc/chef/solo.rb")
elsif config[:client]
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 42fa6b5fa1..31ebeda86f 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -21,7 +21,19 @@ require 'chef/mixin/from_file'
require 'chef/mash'
require 'chef/json_compat'
require 'chef/search/query'
+require 'chef/server_api'
+# TODO
+# DEPRECATION NOTE
+# This class will be replaced by Chef::UserV1 in Chef 13. It is the code to support the User object
+# corrosponding to the Open Source Chef Server 11 and only still exists to support
+# users still on OSC 11.
+#
+# Chef::UserV1 now supports Chef Server 12 and will be moved to this namespace in Chef 13.
+#
+# New development should occur in Chef::UserV1.
+# This file and corrosponding osc_user knife files
+# should be removed once client support for Open Source Chef Server 11 expires.
class Chef
class User
@@ -36,6 +48,10 @@ class Chef
@admin = false
end
+ def chef_rest_v0
+ @chef_rest_v0 ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
+ end
+
def name(arg=nil)
set_or_return(:name, arg,
:regex => /^[a-z0-9\-_]+$/)
@@ -77,13 +93,13 @@ class Chef
end
def destroy
- Chef::REST.new(Chef::Config[:chef_server_url]).delete_rest("users/#{@name}")
+ chef_rest_v0.delete("users/#{@name}")
end
def create
payload = {:name => self.name, :admin => self.admin, :password => self.password }
payload[:public_key] = public_key if public_key
- new_user =Chef::REST.new(Chef::Config[:chef_server_url]).post_rest("users", payload)
+ new_user = chef_rest_v0.post("users", payload)
Chef::User.from_hash(self.to_hash.merge(new_user))
end
@@ -91,7 +107,7 @@ class Chef
payload = {:name => name, :admin => admin}
payload[:private_key] = new_key if new_key
payload[:password] = password if password
- updated_user = Chef::REST.new(Chef::Config[:chef_server_url]).put_rest("users/#{name}", payload)
+ updated_user = chef_rest_v0.put("users/#{name}", payload)
Chef::User.from_hash(self.to_hash.merge(updated_user))
end
@@ -108,8 +124,7 @@ class Chef
end
def reregister
- r = Chef::REST.new(Chef::Config[:chef_server_url])
- reregistered_self = r.put_rest("users/#{name}", { :name => name, :admin => admin, :private_key => true })
+ reregistered_self = chef_rest_v0.put("users/#{name}", { :name => name, :admin => admin, :private_key => true })
private_key(reregistered_self["private_key"])
self
end
@@ -144,7 +159,7 @@ class Chef
end
def self.list(inflate=false)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest('users')
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get('users')
users = if response.is_a?(Array)
transform_ohc_list_response(response) # OHC/OPC
else
@@ -161,7 +176,7 @@ class Chef
end
def self.load(name)
- response = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("users/#{name}")
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"}).get("users/#{name}")
Chef::User.from_hash(response)
end
diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb
new file mode 100644
index 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/backup.rb b/lib/chef/util/backup.rb
index 0cc009ca1f..6c95cedad7 100644
--- a/lib/chef/util/backup.rb
+++ b/lib/chef/util/backup.rb
@@ -78,8 +78,16 @@ class Chef
Chef::Log.info("#{@new_resource} removed backup at #{backup_file}")
end
+ def unsorted_backup_files
+ # If you replace this with Dir[], you will probably break Windows.
+ fn = Regexp.escape(::File.basename(path))
+ Dir.entries(::File.dirname(backup_path)).select do |f|
+ !!(f =~ /\A#{fn}.chef-[0-9.]*\B/)
+ end.map {|f| ::File.join(::File.dirname(backup_path), f)}
+ end
+
def sorted_backup_files
- Dir[Chef::Util::PathHelper.escape_glob(prefix, ".#{path}") + ".chef-*"].sort { |a,b| b <=> a }
+ unsorted_backup_files.sort { |a,b| b <=> a }
end
end
end
diff --git a/lib/chef/util/dsc/resource_store.rb b/lib/chef/util/dsc/resource_store.rb
new file mode 100644
index 0000000000..fdcecc2b3c
--- /dev/null
+++ b/lib/chef/util/dsc/resource_store.rb
@@ -0,0 +1,110 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/util/powershell/cmdlet'
+require 'chef/util/powershell/cmdlet_result'
+require 'chef/exceptions'
+
+class Chef
+class Util
+class DSC
+ class ResourceStore
+
+ def self.instance
+ @@instance ||= ResourceStore.new.tap do |store|
+ store.send(:populate_cache)
+ end
+ end
+
+ def resources
+ @resources ||= []
+ end
+
+ def find(name, module_name=nil)
+ found = find_resources(name, module_name, resources)
+
+ # We don't have it, query for the resource...it might
+ # have been added since we last queried
+ if found.length == 0
+ rs = query_resource(name)
+ add_resources(rs)
+ found = find_resources(name, module_name, rs)
+ end
+
+ found
+ end
+
+ private
+
+ def add_resource(new_r)
+ count = resources.count do |r|
+ r['ResourceType'].casecmp(new_r['ResourceType']) == 0
+ end
+ if count == 0
+ resources << new_r
+ end
+ end
+
+ def add_resources(rs)
+ rs.each do |r|
+ add_resource(r)
+ end
+ end
+
+ def populate_cache
+ @resources = query_resources
+ end
+
+ def find_resources(name, module_name, rs)
+ found = rs.find_all do |r|
+ name_matches = r['Name'].casecmp(name) == 0
+ if name_matches
+ module_name == nil || (r['Module'] and r['Module']['Name'].casecmp(module_name) == 0)
+ else
+ false
+ end
+ end
+ end
+
+
+ # Returns a list of dsc resources
+ def query_resources
+ cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, 'get-dscresource',
+ :object)
+ result = cmdlet.run
+ result.return_value
+ end
+
+ # Returns a list of dsc resources matching the provided name
+ def query_resource(resource_name)
+ cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource #{resource_name}",
+ :object)
+ result = cmdlet.run
+ ret_val = result.return_value
+ if ret_val.nil?
+ []
+ elsif ret_val.is_a? Array
+ ret_val
+ else
+ [ret_val]
+ end
+ end
+ end
+end
+end
+end
diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb
index 1ae489598d..9ebc9319b8 100644
--- a/lib/chef/util/path_helper.rb
+++ b/lib/chef/util/path_helper.rb
@@ -16,136 +16,10 @@
# limitations under the License.
#
+require 'chef-config/path_helper'
+
class Chef
class Util
- class PathHelper
- # Maximum characters in a standard Windows path (260 including drive letter and NUL)
- WIN_MAX_PATH = 259
-
- def self.dirname(path)
- if Chef::Platform.windows?
- # Find the first slash, not counting trailing slashes
- end_slash = path.size
- loop do
- slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
- if !slash
- return end_slash == path.size ? '.' : path_separator
- elsif slash == end_slash - 1
- end_slash = slash
- else
- return path[0..slash-1]
- end
- end
- else
- ::File.dirname(path)
- end
- end
-
- BACKSLASH = '\\'.freeze
-
- def self.path_separator
- if Chef::Platform.windows?
- File::ALT_SEPARATOR || BACKSLASH
- else
- File::SEPARATOR
- end
- end
-
- def self.join(*args)
- args.flatten.inject do |joined_path, component|
- # Joined path ends with /
- joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '')
- component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '')
- joined_path += "#{path_separator}#{component}"
- end
- end
-
- def self.validate_path(path)
- if Chef::Platform.windows?
- unless printable?(path)
- msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings."
- Chef::Log.error(msg)
- raise Chef::Exceptions::ValidationFailed, msg
- end
-
- if windows_max_length_exceeded?(path)
- Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
- path.insert(0, "\\\\?\\")
- end
- end
-
- path
- end
-
- def self.windows_max_length_exceeded?(path)
- # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
- unless path =~ /^\\\\?\\/
- if path.length > WIN_MAX_PATH
- return true
- end
- end
-
- false
- end
-
- def self.printable?(string)
- # returns true if string is free of non-printable characters (escape sequences)
- # this returns false for whitespace escape sequences as well, e.g. \n\t
- if string =~ /[^[:print:]]/
- false
- else
- true
- end
- end
-
- # Produces a comparable path.
- def self.canonical_path(path, add_prefix=true)
- # First remove extra separators and resolve any relative paths
- abs_path = File.absolute_path(path)
-
- if Chef::Platform.windows?
- # Add the \\?\ API prefix on Windows unless add_prefix is false
- # Downcase on Windows where paths are still case-insensitive
- abs_path.gsub!(::File::SEPARATOR, path_separator)
- if add_prefix && abs_path !~ /^\\\\?\\/
- abs_path.insert(0, "\\\\?\\")
- end
-
- abs_path.downcase!
- end
-
- abs_path
- end
-
- def self.cleanpath(path)
- path = Pathname.new(path).cleanpath.to_s
- # ensure all forward slashes are backslashes
- if Chef::Platform.windows?
- path = path.gsub(File::SEPARATOR, path_separator)
- end
- path
- end
-
- def self.paths_eql?(path1, path2)
- canonical_path(path1) == canonical_path(path2)
- end
-
- # Paths which may contain glob-reserved characters need
- # to be escaped before globbing can be done.
- # http://stackoverflow.com/questions/14127343
- def self.escape_glob(*parts)
- path = cleanpath(join(*parts))
- path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x }
- end
-
- def self.relative_path_from(from, to)
- pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from)))
- end
- end
+ PathHelper = ChefConfig::PathHelper
end
end
-
-# Break a require loop when require chef/util/path_helper
-require 'chef/platform'
-require 'chef/exceptions'
diff --git a/lib/chef/util/powershell/cmdlet.rb b/lib/chef/util/powershell/cmdlet.rb
index 40edbb13c6..47d63a2b85 100644
--- a/lib/chef/util/powershell/cmdlet.rb
+++ b/lib/chef/util/powershell/cmdlet.rb
@@ -20,7 +20,9 @@ require 'mixlib/shellout'
require 'chef/mixin/windows_architecture_helper'
require 'chef/util/powershell/cmdlet_result'
-class Chef::Util::Powershell
+class Chef
+class Util
+class Powershell
class Cmdlet
def initialize(node, cmdlet, output_format=nil, output_format_options={})
@output_format = output_format
@@ -46,6 +48,10 @@ class Chef::Util::Powershell
attr_reader :output_format
def run(switches={}, execution_options={}, *arguments)
+ streams = { :json => CmdletStream.new('json'),
+ :verbose => CmdletStream.new('verbose'),
+ }
+
arguments_string = arguments.join(' ')
switches_string = command_switches_string(switches)
@@ -56,21 +62,25 @@ class Chef::Util::Powershell
json_depth = @output_format_options[:depth]
end
- json_command = @json_format ? " | convertto-json -compress -depth #{json_depth}" : ""
- command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive -command \"trap [Exception] {write-error -exception ($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} #{arguments_string}#{json_command}\";if ( ! $? ) { exit 1 }"
+ json_command = @json_format ? " | convertto-json -compress -depth #{json_depth} "\
+ "> #{streams[:json].path}" : ""
+ redirections = "4> '#{streams[:verbose].path}'"
+ command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive "\
+ "-command \"trap [Exception] {write-error -exception "\
+ "($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} "\
+ "#{arguments_string} #{redirections}"\
+ "#{json_command}\";if ( ! $? ) { exit 1 }"
augmented_options = {:returns => [0], :live_stream => false}.merge(execution_options)
command = Mixlib::ShellOut.new(command_string, augmented_options)
- os_architecture = "#{ENV['PROCESSOR_ARCHITEW6432']}" == 'AMD64' ? :x86_64 : :i386
-
status = nil
with_os_architecture(@node) do
status = command.run_command
end
- CmdletResult.new(status, @output_format)
+ CmdletResult.new(status, streams, @output_format)
end
def run!(switches={}, execution_options={}, *arguments)
@@ -131,6 +141,30 @@ class Chef::Util::Powershell
command_switches.join(' ')
end
+
+ class CmdletStream
+ def initialize(name)
+ @filename = Dir::Tmpname.create(name) {}
+ ObjectSpace.define_finalizer(self, self.class.destroy(@filename))
+ end
+
+ def path
+ @filename
+ end
+
+ def read
+ if File.exist? @filename
+ File.open(@filename, 'rb:bom|UTF-16LE') do |f|
+ f.read.encode('UTF-8')
+ end
+ end
+ end
+
+ def self.destroy(name)
+ proc { File.delete(name) if File.exists? name }
+ end
+ end
end
end
-
+end
+end
diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb
index 246701a7bc..f1fdd968b1 100644
--- a/lib/chef/util/powershell/cmdlet_result.rb
+++ b/lib/chef/util/powershell/cmdlet_result.rb
@@ -18,22 +18,35 @@
require 'chef/json_compat'
-class Chef::Util::Powershell
+class Chef
+class Util
+class Powershell
class CmdletResult
attr_reader :output_format
- def initialize(status, output_format)
+ def initialize(status, streams, output_format)
@status = status
@output_format = output_format
+ @streams = streams
end
+ def stdout
+ @status.stdout
+ end
+
def stderr
@status.stderr
end
+ def stream(name)
+ @streams[name].read
+ end
+
def return_value
if output_format == :object
- Chef::JSONCompat.parse(@status.stdout)
+ Chef::JSONCompat.parse(stream(:json))
+ elsif output_format == :json
+ stream(:json)
else
@status.stdout
end
@@ -44,3 +57,5 @@ class Chef::Util::Powershell
end
end
end
+end
+end
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/util/powershell/ps_credential.rb
new file mode 100644
index 0000000000..3f4558a77c
--- /dev/null
+++ b/lib/chef/util/powershell/ps_credential.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/win32/crypto' if Chef::Platform.windows?
+
+class Chef::Util::Powershell
+ class PSCredential
+ def initialize(username, password)
+ @username = username
+ @password = password
+ end
+
+ def to_psobject
+ "New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
+ end
+
+ def to_s
+ to_psobject
+ end
+
+ private
+
+ def encrypt(str)
+ Chef::ReservedNames::Win32::Crypto.encrypt(str)
+ end
+ end
+end
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/net_user.rb b/lib/chef/util/windows/net_user.rb
index 5df1a8aaa4..26fbe53db6 100644
--- a/lib/chef/util/windows/net_user.rb
+++ b/lib/chef/util/windows/net_user.rb
@@ -18,98 +18,69 @@
require 'chef/util/windows'
require 'chef/exceptions'
+require 'chef/win32/net'
+require 'chef/win32/security'
#wrapper around a subset of the NetUser* APIs.
#nothing Chef specific, but not complete enough to be its own gem, so util for now.
class Chef::Util::Windows::NetUser < Chef::Util::Windows
private
-
- LogonUser = Windows::API.new('LogonUser', 'SSSLLP', 'I', 'advapi32')
-
- DOMAIN_GROUP_RID_USERS = 0x00000201
-
- UF_SCRIPT = 0x000001
- UF_ACCOUNTDISABLE = 0x000002
- UF_PASSWD_CANT_CHANGE = 0x000040
- UF_NORMAL_ACCOUNT = 0x000200
- UF_DONT_EXPIRE_PASSWD = 0x010000
-
- #[:symbol_name, default_val]
- #default_val duals as field type
- #array index duals as structure offset
-
- #OC-8391
- #Changing [:password, nil], to [:password, ""],
- #if :password is set to nil, windows user creation api ignores the password policy applied
- #thus initializing it with empty string value.
- USER_INFO_3 = [
- [:name, nil],
- [:password, ""],
- [:password_age, 0],
- [:priv, 0], #"The NetUserAdd and NetUserSetInfo functions ignore this member"
- [:home_dir, nil],
- [:comment, nil],
- [:flags, UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT],
- [:script_path, nil],
- [:auth_flags, 0],
- [:full_name, nil],
- [:user_comment, nil],
- [:parms, nil],
- [:workstations, nil],
- [:last_logon, 0],
- [:last_logoff, 0],
- [:acct_expires, -1],
- [:max_storage, -1],
- [:units_per_week, 0],
- [:logon_hours, nil],
- [:bad_pw_count, 0],
- [:num_logons, 0],
- [:logon_server, nil],
- [:country_code, 0],
- [:code_page, 0],
- [:user_id, 0],
- [:primary_group_id, DOMAIN_GROUP_RID_USERS],
- [:profile, nil],
- [:home_dir_drive, nil],
- [:password_expired, 0]
- ]
-
- USER_INFO_3_TEMPLATE =
- USER_INFO_3.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join
-
- SIZEOF_USER_INFO_3 = #sizeof(USER_INFO_3)
- USER_INFO_3.inject(0){|sum,item|
- sum + (item[1].class == Fixnum ? 4 : PTR_SIZE)
- }
-
- def user_info_3(args)
- USER_INFO_3.collect { |field|
- args.include?(field[0]) ? args[field[0]] : field[1]
- }
- end
-
- def user_info_3_pack(user)
- user.collect { |v|
- v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v))
- }.pack(USER_INFO_3_TEMPLATE)
+ NetUser = Chef::ReservedNames::Win32::NetUser
+ Security = Chef::ReservedNames::Win32::Security
+
+ USER_INFO_3_TRANSFORM = {
+ name: :usri3_name,
+ password: :usri3_password,
+ password_age: :usri3_password_age,
+ priv: :usri3_priv,
+ home_dir: :usri3_home_dir,
+ comment: :usri3_comment,
+ flags: :usri3_flags,
+ script_path: :usri3_script_path,
+ auth_flags: :usri3_auth_flags,
+ full_name: :usri3_full_name,
+ user_comment: :usri3_usr_comment,
+ parms: :usri3_parms,
+ workstations: :usri3_workstations,
+ last_logon: :usri3_last_logon,
+ last_logoff: :usri3_last_logoff,
+ acct_expires: :usri3_acct_expires,
+ max_storage: :usri3_max_storage,
+ units_per_week: :usri3_units_per_week,
+ logon_hours: :usri3_logon_hours,
+ bad_pw_count: :usri3_bad_pw_count,
+ num_logons: :usri3_num_logons,
+ logon_server: :usri3_logon_server,
+ country_code: :usri3_country_code,
+ code_page: :usri3_code_page,
+ user_id: :usri3_user_id,
+ primary_group_id: :usri3_primary_group_id,
+ profile: :usri3_profile,
+ home_dir_drive: :usri3_home_dir_drive,
+ password_expired: :usri3_password_expired,
+ }
+
+ def transform_usri3(args)
+ args.inject({}) do |memo, (k,v)|
+ memo[USER_INFO_3_TRANSFORM[k]] = v
+ memo
+ end
end
- def user_info_3_unpack(buffer)
- user = Hash.new
- USER_INFO_3.each_with_index do |field,offset|
- user[field[0]] = field[1].class == Fixnum ?
- dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset)
+ def usri3_to_hash(usri3)
+ t = USER_INFO_3_TRANSFORM.invert
+ usri3.inject({}) do |memo, (k,v)|
+ memo[t[k]] = v
+ memo
end
- user
end
def set_info(args)
- user = user_info_3(args)
- buffer = user_info_3_pack(user)
- rc = NetUserSetInfo.call(nil, @name, 3, buffer, nil)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ rc = NetUser::net_user_set_info_l3(nil, @username, transform_usri3(args))
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
@@ -120,49 +91,32 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
@name = multi_to_wide(username)
end
- LOGON32_PROVIDER_DEFAULT = 0
- LOGON32_LOGON_NETWORK = 3
+ LOGON32_PROVIDER_DEFAULT = Security::LOGON32_PROVIDER_DEFAULT
+ LOGON32_LOGON_NETWORK = Security::LOGON32_LOGON_NETWORK
#XXX for an extra painful alternative, see: http://support.microsoft.com/kb/180548
def validate_credentials(passwd)
- token = 0.chr * PTR_SIZE
- res = LogonUser.call(@username, nil, passwd,
- LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token)
- if res == 0
+ begin
+ token = Security::logon_user(@username, nil, passwd,
+ LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT)
+ return true
+ rescue Chef::Exceptions::Win32APIError
return false
end
- ::Windows::Handle::CloseHandle.call(token.unpack('L')[0])
- return true
end
def get_info
- ptr = 0.chr * PTR_SIZE
- rc = NetUserGetInfo.call(nil, @name, 3, ptr)
-
- if rc == NERR_UserNotFound
- raise Chef::Exceptions::UserIDNotFound, get_last_error(rc)
- elsif rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ ui3 = NetUser::net_user_get_info_l3(nil, @username)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
-
- ptr = ptr.unpack('L')[0]
- buffer = 0.chr * SIZEOF_USER_INFO_3
- memcpy(buffer, ptr, buffer.size)
- NetApiBufferFree(ptr)
- user_info_3_unpack(buffer)
+ usri3_to_hash(ui3)
end
def add(args)
- user = user_info_3(args)
- buffer = user_info_3_pack(user)
-
- rc = NetUserAdd.call(nil, 3, buffer, rc)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
-
- #usri3_primary_group_id:
- #"When you call the NetUserAdd function, this member must be DOMAIN_GROUP_RID_USERS"
- NetLocalGroupAddMembers(nil, multi_to_wide("Users"), 3, buffer[0,PTR_SIZE], 1)
+ transformed_args = transform_usri3(args)
+ NetUser::net_user_add_l3(nil, transformed_args)
+ NetUser::net_local_group_add_member(nil, "Users", args[:name])
end
def user_modify(&proc)
@@ -182,15 +136,16 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def delete
- rc = NetUserDel.call(nil, @name)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ NetUser::net_user_del(nil, @username)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
def disable_account
user_modify do |user|
- user[:flags] |= UF_ACCOUNTDISABLE
+ user[:flags] |= NetUser::UF_ACCOUNTDISABLE
#This does not set the password to nil. It (for some reason) means to ignore updating the field.
#See similar behavior for the logon_hours field documented at
#http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -200,7 +155,7 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
def enable_account
user_modify do |user|
- user[:flags] &= ~UF_ACCOUNTDISABLE
+ user[:flags] &= ~NetUser::UF_ACCOUNTDISABLE
#This does not set the password to nil. It (for some reason) means to ignore updating the field.
#See similar behavior for the logon_hours field documented at
#http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -209,6 +164,6 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def check_enabled
- (get_info()[:flags] & UF_ACCOUNTDISABLE) != 0
+ (get_info()[:flags] & NetUser::UF_ACCOUNTDISABLE) != 0
end
end
diff --git a/lib/chef/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 27ba372f61..faa61aee54 100644
--- a/lib/chef/version.rb
+++ b/lib/chef/version.rb
@@ -1,6 +1,4 @@
-
-# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,9 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+# NOTE: This file is generated by running `rake version` in the top level of
+# this repo. Do not edit this manually. Edit the VERSION file and run the rake
+# task instead.
+#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
class Chef
CHEF_ROOT = File.dirname(File.expand_path(File.dirname(__FILE__)))
- VERSION = '12.1.0.rc.0'
+ VERSION = '12.5.0.current.0'
end
#
@@ -25,6 +29,6 @@ end
#
# NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs. The
# Chef::Version class is for _cookbooks_ only, and cannot handle
-# pre-release chef-client versions like "10.14.0.rc.2". Please
-# use Rubygem's Gem::Version class instead.
+# pre-release versions like "10.14.0.rc.2". Please use Rubygem's
+# Gem::Version class instead.
#
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index 8b81947c9b..4786222bd4 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -67,7 +67,7 @@ class Chef
# BaseTsd.h: #ifdef (_WIN64) host.typedef int HALF_PTR; #else host.typedef short HALF_PTR;
host.typedef :ulong, :HACCEL # (L) Handle to an accelerator table. WinDef.h: #host.typedef HANDLE HACCEL;
# See http://msdn.microsoft.com/en-us/library/ms645526%28VS.85%29.aspx
- host.typedef :ulong, :HANDLE # (L) Handle to an object. WinNT.h: #host.typedef PVOID HANDLE;
+ host.typedef :size_t, :HANDLE # (L) Handle to an object. WinNT.h: #host.typedef PVOID HANDLE;
# todo: Platform-dependent! Need to change to :uint64 for Win64
host.typedef :ulong, :HBITMAP # (L) Handle to a bitmap: http://msdn.microsoft.com/en-us/library/dd183377%28VS.85%29.aspx
host.typedef :ulong, :HBRUSH # (L) Handle to a brush. http://msdn.microsoft.com/en-us/library/dd183394%28VS.85%29.aspx
@@ -117,6 +117,7 @@ class Chef
host.typedef :uint32, :LCID # Locale identifier. For more information, see Locales.
host.typedef :uint32, :LCTYPE # Locale information type. For a list, see Locale Information Constants.
host.typedef :uint32, :LGRPID # Language group identifier. For a list, see EnumLanguageGroupLocales.
+ host.typedef :pointer, :LMSTR # Pointer to null termiated string of unicode characters
host.typedef :long, :LONG # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
host.typedef :int32, :LONG32 # 32-bit signed integer. The range is -2,147,483,648 through +...647 decimal.
host.typedef :int64, :LONG64 # 64-bit signed integer. The range is –9,223,372,036,854,775,808 through +...807
@@ -184,7 +185,10 @@ class Chef
host.typedef :pointer, :PSTR # Pointer to a null-terminated string of 8-bit Windows (ANSI) characters. For more information, see Character Sets Used By Fonts.
host.typedef :pointer, :PTBYTE # Pointer to a TBYTE.
host.typedef :pointer, :PTCHAR # Pointer to a TCHAR.
+ 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/crypto.rb b/lib/chef/win32/api/crypto.rb
new file mode 100644
index 0000000000..1837a57557
--- /dev/null
+++ b/lib/chef/win32/api/crypto.rb
@@ -0,0 +1,63 @@
+#
+# Author:: Jay Mundrawala (<jdm@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 Crypto
+ extend Chef::ReservedNames::Win32::API
+
+ ###############################################
+ # Win32 API Bindings
+ ###############################################
+
+ ffi_lib 'Crypt32'
+
+ CRYPTPROTECT_UI_FORBIDDEN = 0x1
+ CRYPTPROTECT_LOCAL_MACHINE = 0x4
+ CRYPTPROTECT_AUDIT = 0x10
+
+ class CRYPT_INTEGER_BLOB < FFI::Struct
+ layout :cbData, :DWORD, # Count, in bytes, of data
+ :pbData, :pointer # Pointer to data buffer
+ def initialize(str=nil)
+ super(nil)
+ if str
+ self[:pbData] = FFI::MemoryPointer.from_string(str)
+ self[:cbData] = str.bytesize
+ end
+ end
+
+ end
+
+ safe_attach_function :CryptProtectData, [
+ :PDATA_BLOB,
+ :LPCWSTR,
+ :PDATA_BLOB,
+ :pointer,
+ :PCRYPTPROTECT_PROMPTSTRUCT,
+ :DWORD,
+ :PDATA_BLOB
+ ], :BOOL
+
+ end
+ end
+ end
+end
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/installer.rb b/lib/chef/win32/api/installer.rb
index 745802d260..b4851eccf1 100644
--- a/lib/chef/win32/api/installer.rb
+++ b/lib/chef/win32/api/installer.rb
@@ -108,7 +108,7 @@ UINT MsiCloseHandle(
end
msi_close_handle(pkg_ptr.read_pointer)
- return buffer
+ return buffer.chomp(0.chr)
end
# Opens a Microsoft Installer (MSI) file from an absolute path and returns a pointer to a handle
@@ -158,7 +158,7 @@ UINT MsiCloseHandle(
raise Chef::Exceptions::Package, msg
end
- version
+ version.chomp(0.chr)
end
end
end
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index eeb2b078a4..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
@@ -32,12 +33,76 @@ class Chef
MAX_PREFERRED_LENGTH = 0xFFFF
- NERR_Success = 0
- NERR_UserNotFound = 2221
+ DOMAIN_GROUP_RID_USERS = 0x00000201
+
+ UF_SCRIPT = 0x000001
+ UF_ACCOUNTDISABLE = 0x000002
+ UF_PASSWD_CANT_CHANGE = 0x000040
+ UF_NORMAL_ACCOUNT = 0x000200
+ UF_DONT_EXPIRE_PASSWD = 0x010000
+
+ USE_NOFORCE = 0
+ USE_FORCE = 1
+ USE_LOTS_OF_FORCE = 2 #every windows API should support this flag
+
+ NERR_Success = 0
+ NERR_InvalidComputer = 2351
+ NERR_NotPrimary = 2226
+ NERR_SpeGroupOp = 2234
+ NERR_LastAdmin = 2452
+ NERR_BadUsername = 2202
+ NERR_BadPassword = 2203
+ NERR_PasswordTooShort = 2245
+ NERR_UserNotFound = 2221
+ NERR_GroupNotFound = 2220
+ ERROR_ACCESS_DENIED = 5
+ ERROR_MORE_DATA = 234
ffi_lib "netapi32"
+ module StructHelpers
+ def set(key, val)
+ val = if val.is_a? String
+ encoded = if val.encoding == Encoding::UTF_16LE
+ val
+ else
+ val.to_wstring
+ end
+ FFI::MemoryPointer.from_string(encoded)
+ else
+ val
+ end
+ self[key] = val
+ end
+
+ def get(key)
+ if respond_to? key
+ send(key)
+ else
+ val = self[key]
+ if val.is_a? FFI::Pointer
+ if val.null?
+ nil
+ else
+ val.read_wstring
+ end
+ else
+ val
+ end
+ end
+ end
+
+ def as_ruby
+ members.inject({}) do |memo, key|
+ memo[key] = get(key)
+ memo
+ end
+ end
+ end
+
+
class USER_INFO_3 < FFI::Struct
+ include StructHelpers
layout :usri3_name, :LPWSTR,
:usri3_password, :LPWSTR,
:usri3_password_age, :DWORD,
@@ -67,8 +132,75 @@ class Chef
:usri3_profile, :LPWSTR,
:usri3_home_dir_drive, :LPWSTR,
:usri3_password_expired, :DWORD
+
+ def usri3_logon_hours
+ val = self[:usri3_logon_hours]
+ if !val.nil? && !val.null?
+ val.read_bytes(21)
+ else
+ nil
+ end
+ end
end
+ class LOCALGROUP_MEMBERS_INFO_0 < FFI::Struct
+ layout :lgrmi0_sid, :PSID
+ end
+
+ class LOCALGROUP_MEMBERS_INFO_3 < FFI::Struct
+ layout :lgrmi3_domainandname, :LPWSTR
+ end
+
+ class LOCALGROUP_INFO_0 < FFI::Struct
+ layout :lgrpi0_name, :LPWSTR
+ end
+
+ class USE_INFO_2 < FFI::Struct
+ include StructHelpers
+
+ layout :ui2_local, :LMSTR,
+ :ui2_remote, :LMSTR,
+ :ui2_password, :LMSTR,
+ :ui2_status, :DWORD,
+ :ui2_asg_type, :DWORD,
+ :ui2_refcount, :DWORD,
+ :ui2_usecount, :DWORD,
+ :ui2_username, :LPWSTR,
+ :ui2_domainname, :LMSTR
+ end
+
+
+#NET_API_STATUS NetLocalGroupAdd(
+ #_In_ LPCWSTR servername,
+ #_In_ DWORD level,
+ #_In_ LPBYTE buf,
+ #_Out_ LPDWORD parm_err
+#);
+ safe_attach_function :NetLocalGroupAdd, [
+ :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDel(
+ #_In_ LPCWSTR servername,
+ #_In_ LPCWSTR groupname
+#);
+ safe_attach_function :NetLocalGroupDel, [:LPCWSTR, :LPCWSTR], :DWORD
+
+#NET_API_STATUS NetLocalGroupGetMembers(
+ #_In_ LPCWSTR servername,
+ #_In_ LPCWSTR localgroupname,
+ #_In_ DWORD level,
+ #_Out_ LPBYTE *bufptr,
+ #_In_ DWORD prefmaxlen,
+ #_Out_ LPDWORD entriesread,
+ #_Out_ LPDWORD totalentries,
+ #_Inout_ PDWORD_PTR resumehandle
+#);
+ safe_attach_function :NetLocalGroupGetMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD,
+ :LPDWORD, :LPDWORD, :PDWORD_PTR
+ ], :DWORD
+
# NET_API_STATUS NetUserEnum(
# _In_ LPCWSTR servername,
# _In_ DWORD level,
@@ -79,12 +211,113 @@ class Chef
# _Out_ LPDWORD totalentries,
# _Inout_ LPDWORD resume_handle
# );
- safe_attach_function :NetUserEnum, [ :LPCWSTR, :DWORD, :DWORD, :LPBYTE, :DWORD, :LPDWORD, :LPDWORD, :LPDWORD ], :DWORD
+ safe_attach_function :NetUserEnum, [
+ :LPCWSTR, :DWORD, :DWORD, :LPBYTE,
+ :DWORD, :LPDWORD, :LPDWORD, :LPDWORD
+ ], :DWORD
# NET_API_STATUS NetApiBufferFree(
# _In_ LPVOID Buffer
# );
- safe_attach_function :NetApiBufferFree, [ :LPVOID ], :DWORD
+ safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
+
+#NET_API_STATUS NetUserAdd(
+ #_In_ LMSTR servername,
+ #_In_ DWORD level,
+ #_In_ LPBYTE buf,
+ #_Out_ LPDWORD parm_err
+#);
+ safe_attach_function :NetUserAdd, [
+ :LMSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupAddMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupAddMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupSetMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupSetMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
+
+#NET_API_STATUS NetLocalGroupDelMembers(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR groupname,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _In_ DWORD totalentries
+#);
+ safe_attach_function :NetLocalGroupDelMembers, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :DWORD
+ ], :DWORD
+
+#NET_API_STATUS NetUserGetInfo(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR username,
+# _In_ DWORD level,
+# _Out_ LPBYTE *bufptr
+#);
+ safe_attach_function :NetUserGetInfo, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE
+ ], :DWORD
+
+#NET_API_STATUS NetApiBufferFree(
+# _In_ LPVOID Buffer
+#);
+ safe_attach_function :NetApiBufferFree, [:LPVOID], :DWORD
+
+#NET_API_STATUS NetUserSetInfo(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR username,
+# _In_ DWORD level,
+# _In_ LPBYTE buf,
+# _Out_ LPDWORD parm_err
+#);
+ safe_attach_function :NetUserSetInfo, [
+ :LPCWSTR, :LPCWSTR, :DWORD, :LPBYTE, :LPDWORD
+ ], :DWORD
+
+#NET_API_STATUS NetUserDel(
+# _In_ LPCWSTR servername,
+# _In_ LPCWSTR username
+#);
+ safe_attach_function :NetUserDel, [:LPCWSTR, :LPCWSTR], :DWORD
+
+#NET_API_STATUS NetUseDel(
+ #_In_ LMSTR UncServerName,
+ #_In_ LMSTR UseName,
+ #_In_ DWORD ForceCond
+#);
+ safe_attach_function :NetUseDel, [:LMSTR, :LMSTR, :DWORD], :DWORD
+
+#NET_API_STATUS NetUseGetInfo(
+ #_In_ LMSTR UncServerName,
+ #_In_ LMSTR UseName,
+ #_In_ DWORD Level,
+ #_Out_ LPBYTE *BufPtr
+#);
+ safe_attach_function :NetUseGetInfo, [:LMSTR, :LMSTR, :DWORD, :pointer], :DWORD
+
+#NET_API_STATUS NetUseAdd(
+ #_In_ LMSTR UncServerName,
+ #_In_ DWORD Level,
+ #_In_ LPBYTE Buf,
+ #_Out_ LPDWORD ParmError
+#);
+ safe_attach_function :NetUseAdd, [:LMSTR, :DWORD, :LPBYTE, :LPDWORD], :DWORD
end
end
end
diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb
new file mode 100644
index 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/security.rb b/lib/chef/win32/api/security.rb
index 229f2ace10..4c352a3554 100644
--- a/lib/chef/win32/api/security.rb
+++ b/lib/chef/win32/api/security.rb
@@ -193,6 +193,20 @@ class Chef
MAXDWORD = 0xffffffff
+ # LOGON32 constants for LogonUser
+ LOGON32_LOGON_INTERACTIVE = 2;
+ LOGON32_LOGON_NETWORK = 3;
+ LOGON32_LOGON_BATCH = 4;
+ LOGON32_LOGON_SERVICE = 5;
+ LOGON32_LOGON_UNLOCK = 7;
+ LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
+ LOGON32_LOGON_NEW_CREDENTIALS = 9;
+
+ LOGON32_PROVIDER_DEFAULT = 0;
+ LOGON32_PROVIDER_WINNT35 = 1;
+ LOGON32_PROVIDER_WINNT40 = 2;
+ LOGON32_PROVIDER_WINNT50 = 3;
+
###############################################
# Win32 API Bindings
###############################################
@@ -270,6 +284,14 @@ class Chef
:MaxTokenInfoClass
]
+ class TOKEN_OWNER < FFI::Struct
+ layout :Owner, :pointer
+ end
+
+ class TOKEN_PRIMARY_GROUP < FFI::Struct
+ layout :PrimaryGroup, :pointer
+ end
+
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572%28v=vs.85%29.aspx
SECURITY_IMPERSONATION_LEVEL = enum :SECURITY_IMPERSONATION_LEVEL, [
:SecurityAnonymous,
@@ -405,6 +427,8 @@ class Chef
safe_attach_function :SetSecurityDescriptorOwner, [ :pointer, :pointer, :BOOL ], :BOOL
safe_attach_function :SetSecurityDescriptorSacl, [ :pointer, :BOOL, :pointer, :BOOL ], :BOOL
safe_attach_function :GetTokenInformation, [ :HANDLE, :TOKEN_INFORMATION_CLASS, :pointer, :DWORD, :PDWORD ], :BOOL
+ safe_attach_function :LogonUserW, [:LPTSTR, :LPTSTR, :LPTSTR, :DWORD, :DWORD, :PHANDLE], :BOOL
+
end
end
end
diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb
index 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 0b2cb09a6b..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[-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
new file mode 100644
index 0000000000..aa20c2dfd4
--- /dev/null
+++ b/lib/chef/win32/crypto.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Jay Mundrawala (<jdm@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/error'
+require 'chef/win32/api/memory'
+require 'chef/win32/api/crypto'
+require 'chef/win32/unicode'
+require 'digest'
+
+class Chef
+ module ReservedNames::Win32
+ class Crypto
+ include Chef::ReservedNames::Win32::API::Crypto
+ extend Chef::ReservedNames::Win32::API::Crypto
+
+ def self.encrypt(str, &block)
+ data_blob = CRYPT_INTEGER_BLOB.new
+ unless CryptProtectData(CRYPT_INTEGER_BLOB.new(str.to_wstring), nil, nil, nil, nil, 0, data_blob)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ bytes = data_blob[:pbData].get_bytes(0, data_blob[:cbData])
+ if block
+ block.call(bytes)
+ else
+ Digest.hexencode(bytes)
+ end
+ ensure
+ unless data_blob[:pbData].null?
+ Chef::ReservedNames::Win32::Memory.local_free(data_blob[:pbData])
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/win32/eventlog.rb b/lib/chef/win32/eventlog.rb
new file mode 100644
index 0000000000..24af2da0d6
--- /dev/null
+++ b/lib/chef/win32/eventlog.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+#
+# Copyright:: 2015, Chef Software, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+if Chef::Platform::windows? and not Chef::Platform::windows_server_2003?
+ if !defined? Chef::Win32EventLogLoaded
+ if defined? Windows::Constants
+ [:INFINITE, :WAIT_FAILED, :FORMAT_MESSAGE_IGNORE_INSERTS, :ERROR_INSUFFICIENT_BUFFER].each do |c|
+ # These are redefined in 'win32/eventlog'
+ Windows::Constants.send(:remove_const, c) if Windows::Constants.const_defined? c
+ end
+ end
+
+ require 'win32/eventlog'
+ Chef::Win32EventLogLoaded = true
+ end
+end
diff --git a/lib/chef/win32/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
new file mode 100644
index 0000000000..59f29c4d1b
--- /dev/null
+++ b/lib/chef/win32/net.rb
@@ -0,0 +1,344 @@
+#
+# 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.
+#
+
+require 'chef/win32/api/net'
+require 'chef/win32/error'
+require 'chef/mixin/wide_string'
+
+class Chef
+ module ReservedNames::Win32
+ class Net
+ include Chef::ReservedNames::Win32::API::Error
+ extend Chef::ReservedNames::Win32::API::Error
+
+ include Chef::ReservedNames::Win32::API::Net
+ extend Chef::ReservedNames::Win32::API::Net
+
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
+ def self.default_user_info_3
+ ui3 = USER_INFO_3.new.tap do |s|
+ { usri3_name: nil,
+ usri3_password: nil,
+ usri3_password_age: 0,
+ usri3_priv: 0,
+ usri3_home_dir: nil,
+ usri3_comment: nil,
+ usri3_flags: UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT,
+ usri3_script_path: nil,
+ usri3_auth_flags: 0,
+ usri3_full_name: nil,
+ usri3_usr_comment: nil,
+ usri3_parms: nil,
+ usri3_workstations: nil,
+ usri3_last_logon: 0,
+ usri3_last_logoff: 0,
+ usri3_acct_expires: -1,
+ usri3_max_storage: -1,
+ usri3_units_per_week: 0,
+ usri3_logon_hours: nil,
+ usri3_bad_pw_count: 0,
+ usri3_num_logons: 0,
+ usri3_logon_server: nil,
+ usri3_country_code: 0,
+ usri3_code_page: 0,
+ usri3_user_id: 0,
+ usri3_primary_group_id: DOMAIN_GROUP_RID_USERS,
+ usri3_profile: nil,
+ usri3_home_dir_drive: nil,
+ usri3_password_expired: 0
+ }.each do |(k,v)|
+ s.set(k, v)
+ end
+ end
+ end
+
+ def self.net_api_error!(code)
+ msg = case code
+ when NERR_InvalidComputer
+ "The user does not have access to the requested information."
+ when NERR_NotPrimary
+ "The operation is allowed only on the primary domain controller of the domain."
+ when NERR_SpeGroupOp
+ "This operation is not allowed on this special group."
+ when NERR_LastAdmin
+ "This operation is not allowed on the last administrative account."
+ when NERR_BadUsername
+ "The user name or group name parameter is invalid."
+ when NERR_BadPassword
+ "The password parameter is invalid."
+ when NERR_UserNotFound
+ raise Chef::Exceptions::UserIDNotFound, code
+ when NERR_PasswordTooShort
+ <<END
+The password is shorter than required. (The password could also be too
+long, be too recent in its change history, not have enough unique characters,
+or not meet another password policy requirement.)
+END
+ when NERR_GroupNotFound
+ "The group name could not be found."
+ when ERROR_ACCESS_DENIED
+ "The user does not have access to the requested information."
+ else
+ "Received unknown error code (#{code})"
+ end
+
+ raise Chef::Exceptions::Win32NetAPIError.new(msg, code)
+ end
+
+ def self.net_local_group_add(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = LOCALGROUP_INFO_0.new
+ buf[:lgrpi0_name] = FFI::MemoryPointer.from_string(group_name)
+
+ rc = NetLocalGroupAdd(server_name, 0, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_del(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ rc = NetLocalGroupDel(server_name, group_name)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_get_members(server_name, group_name)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = FFI::MemoryPointer.new(:pointer)
+ entries_read_ptr = FFI::MemoryPointer.new(:long)
+ total_read_ptr = FFI::MemoryPointer.new(:long)
+ resume_handle_ptr = FFI::MemoryPointer.new(:pointer)
+
+ rc = ERROR_MORE_DATA
+ group_members = []
+ while rc == ERROR_MORE_DATA
+ rc = NetLocalGroupGetMembers(
+ server_name, group_name, 0, buf, -1,
+ entries_read_ptr, total_read_ptr, resume_handle_ptr
+ )
+
+ nread = entries_read_ptr.read_long
+ nread.times do |i|
+ member = LOCALGROUP_MEMBERS_INFO_0.new(buf.read_pointer +
+ (i * LOCALGROUP_MEMBERS_INFO_0.size))
+ member_sid = Chef::ReservedNames::Win32::Security::SID.new(member[:lgrmi0_sid])
+ group_members << member_sid.to_s
+ end
+ NetApiBufferFree(buf.read_pointer)
+ end
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ group_members
+ end
+
+ def self.net_user_add_l3(server_name, args)
+ buf = default_user_info_3
+
+ args.each do |k, v|
+ buf.set(k, v)
+ end
+
+ server_name = wstring(server_name)
+
+ rc = NetUserAdd(server_name, 3, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_user_get_info_l3(server_name, user_name)
+ server_name = wstring(server_name)
+ user_name = wstring(user_name)
+
+ ui3_p = FFI::MemoryPointer.new(:pointer)
+
+ rc = NetUserGetInfo(server_name, user_name, 3, ui3_p)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ ui3 = USER_INFO_3.new(ui3_p.read_pointer).as_ruby
+
+ rc = NetApiBufferFree(ui3_p.read_pointer)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ ui3
+ end
+
+ def self.net_user_set_info_l3(server_name, user_name, info)
+ buf = default_user_info_3
+
+ info.each do |k, v|
+ buf.set(k, v)
+ end
+
+ server_name = wstring(server_name)
+ user_name = wstring(user_name)
+
+ rc = NetUserSetInfo(server_name, user_name, 3, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_user_del(server_name, user_name)
+ server_name = wstring(server_name)
+ user_name = wstring(user_name)
+
+ rc = NetUserDel(server_name, user_name)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_add_member(server_name, group_name, domain_user)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+ domain_user = wstring(domain_user)
+
+ buf = LOCALGROUP_MEMBERS_INFO_3.new
+ buf[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(domain_user)
+
+ rc = NetLocalGroupAddMembers(server_name, group_name, 3, buf, 1)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.members_to_lgrmi3(members)
+ buf = FFI::MemoryPointer.new(LOCALGROUP_MEMBERS_INFO_3, members.size)
+ members.size.times.collect do |i|
+ member_info = LOCALGROUP_MEMBERS_INFO_3.new(
+ buf + i * LOCALGROUP_MEMBERS_INFO_3.size)
+ member_info[:lgrmi3_domainandname] = FFI::MemoryPointer.from_string(wstring(members[i]))
+ member_info
+ end
+ end
+
+ def self.net_local_group_add_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupAddMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_set_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupSetMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_local_group_del_members(server_name, group_name, members)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ lgrmi3s = members_to_lgrmi3(members)
+ rc = NetLocalGroupDelMembers(
+ server_name, group_name, 3, lgrmi3s[0], members.size)
+
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_use_del(server_name, use_name, force=:use_noforce)
+ server_name = wstring(server_name)
+ use_name = wstring(use_name)
+ force_const = case force
+ when :use_noforce
+ USE_NOFORCE
+ when :use_force
+ USE_FORCE
+ when :use_lots_of_force
+ USE_LOTS_OF_FORCE
+ else
+ raise ArgumentError, "force must be one of [:use_noforce, :use_force, or :use_lots_of_force]"
+ end
+
+ rc = NetUseDel(server_name, use_name, force_const)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+
+ def self.net_use_get_info_l2(server_name, use_name)
+ server_name = wstring(server_name)
+ use_name = wstring(use_name)
+ ui2_p = FFI::MemoryPointer.new(:pointer)
+
+ rc = NetUseGetInfo(server_name, use_name, 2, ui2_p)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+
+ ui2 = USE_INFO_2.new(ui2_p.read_pointer).as_ruby
+ NetApiBufferFree(ui2_p.read_pointer)
+
+ ui2
+ end
+
+ def self.net_use_add_l2(server_name, ui2_hash)
+ server_name = wstring(server_name)
+ group_name = wstring(group_name)
+
+ buf = USE_INFO_2.new
+
+ ui2_hash.each do |(k,v)|
+ buf.set(k,v)
+ end
+
+ rc = NetUseAdd(server_name, 2, buf, nil)
+ if rc != NERR_Success
+ net_api_error!(rc)
+ end
+ end
+ end
+ NetUser = Net # For backwards compatibility
+ end
+end
diff --git a/lib/chef/win32/process.rb b/lib/chef/win32/process.rb
index 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 3902d8caaf..bc80517d80 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -22,6 +22,7 @@ require 'chef/win32/memory'
require 'chef/win32/process'
require 'chef/win32/unicode'
require 'chef/win32/security/token'
+require 'chef/mixin/wide_string'
class Chef
module ReservedNames::Win32
@@ -31,6 +32,8 @@ class Chef
include Chef::ReservedNames::Win32::API::Security
extend Chef::ReservedNames::Win32::API::Security
extend Chef::ReservedNames::Win32::API::Macros
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
def self.access_check(security_descriptor, token, desired_access, generic_mapping)
token_handle = token.handle.handle
@@ -270,6 +273,36 @@ class Chef
[ present.read_char != 0, acl.null? ? nil : ACL.new(acl, security_descriptor), defaulted.read_char != 0 ]
end
+ def self.get_token_information_owner(token)
+ owner_result_size = FFI::MemoryPointer.new(:ulong)
+ if GetTokenInformation(token.handle.handle, :TokenOwner, nil, 0, owner_result_size)
+ raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!"
+ elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ owner_result_storage = FFI::MemoryPointer.new owner_result_size.read_ulong
+ unless GetTokenInformation(token.handle.handle, :TokenOwner, owner_result_storage, owner_result_size.read_ulong, owner_result_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ owner_result = TOKEN_OWNER.new owner_result_storage
+ SID.new(owner_result[:Owner], owner_result_storage)
+ end
+
+ def self.get_token_information_primary_group(token)
+ group_result_size = FFI::MemoryPointer.new(:ulong)
+ if GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, nil, 0, group_result_size)
+ raise "Expected ERROR_INSUFFICIENT_BUFFER from GetTokenInformation, and got no error!"
+ elsif FFI::LastError.error != ERROR_INSUFFICIENT_BUFFER
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ group_result_storage = FFI::MemoryPointer.new group_result_size.read_ulong
+ unless GetTokenInformation(token.handle.handle, :TokenPrimaryGroup, group_result_storage, group_result_size.read_ulong, group_result_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ group_result = TOKEN_PRIMARY_GROUP.new group_result_storage
+ SID.new(group_result[:PrimaryGroup], group_result_storage)
+ end
+
def self.initialize_acl(acl_size)
acl = FFI::MemoryPointer.new acl_size
unless InitializeAcl(acl, acl_size, ACL_REVISION)
@@ -415,6 +448,10 @@ class Chef
[ SecurityDescriptor.new(absolute_sd), SID.new(owner), SID.new(group), ACL.new(dacl), ACL.new(sacl) ]
end
+ def self.open_current_process_token(desired_access = TOKEN_READ)
+ open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, desired_access)
+ end
+
def self.open_process_token(process, desired_access)
process = process.handle if process.respond_to?(:handle)
process = process.handle if process.respond_to?(:handle)
@@ -513,7 +550,7 @@ class Chef
def self.with_privileges(*privilege_names)
# Set privileges
- token = open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, TOKEN_READ | TOKEN_ADJUST_PRIVILEGES)
+ token = open_current_process_token(TOKEN_READ | TOKEN_ADJUST_PRIVILEGES)
old_privileges = token.enable_privileges(*privilege_names)
# Let the caller do their privileged stuff
@@ -533,7 +570,7 @@ class Chef
true
else
- process_token = open_process_token(Chef::ReservedNames::Win32::Process.get_current_process, TOKEN_READ)
+ process_token = open_current_process_token(TOKEN_READ)
elevation_result = FFI::Buffer.new(:ulong)
elevation_result_size = FFI::MemoryPointer.new(:uint32)
success = GetTokenInformation(process_token.handle.handle, :TokenElevation, elevation_result, 4, elevation_result_size)
@@ -543,6 +580,18 @@ class Chef
success && (elevation_result.read_ulong != 0)
end
end
+
+ def self.logon_user(username, domain, password, logon_type, logon_provider)
+ username = wstring(username)
+ domain = wstring(domain)
+ password = wstring(password)
+
+ token = FFI::Buffer.new(:pointer)
+ unless LogonUserW(username, domain, password, logon_type, logon_provider, token)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ Token.new(Handle.new(token.read_pointer))
+ end
end
end
end
diff --git a/lib/chef/win32/security/sid.rb b/lib/chef/win32/security/sid.rb
index 8e9407dc80..f8bd934876 100644
--- a/lib/chef/win32/security/sid.rb
+++ b/lib/chef/win32/security/sid.rb
@@ -203,6 +203,23 @@ class Chef
SID.from_account("#{::ENV['USERDOMAIN']}\\#{::ENV['USERNAME']}")
end
+ # See https://technet.microsoft.com/en-us/library/cc961992.aspx
+ # In practice, this is SID.Administrators if the current_user is an admin (even if not
+ # running elevated), and is current_user otherwise. On win2k3, it technically can be
+ # current_user in all cases if a certain group policy is set.
+ def self.default_security_object_owner
+ token = Chef::ReservedNames::Win32::Security.open_current_process_token
+ Chef::ReservedNames::Win32::Security.get_token_information_owner(token)
+ end
+
+ # See https://technet.microsoft.com/en-us/library/cc961996.aspx
+ # In practice, this seems to be SID.current_user for Microsoft Accounts, the current
+ # user's Domain Users group for domain accounts, and SID.None otherwise.
+ def self.default_security_object_group
+ token = Chef::ReservedNames::Win32::Security.open_current_process_token
+ Chef::ReservedNames::Win32::Security.get_token_information_primary_group(token)
+ end
+
def self.admin_account_name
@admin_account_name ||= begin
admin_account_name = nil
diff --git a/lib/chef/win32/security/token.rb b/lib/chef/win32/security/token.rb
index 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 dd02ad9a66..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,162 +16,8 @@
# limitations under the License.
#
-require 'chef/config_fetcher'
-require 'chef/config'
-require 'chef/null_logger'
+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
- if env['HOME']
- candidate_configs << File.join(env['HOME'], '.chef', 'config.rb')
- candidate_configs << File.join(env['HOME'], '.chef', '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/pedant.gemfile b/pedant.gemfile
index baa3e9aece..8e64fc039c 100644
--- a/pedant.gemfile
+++ b/pedant.gemfile
@@ -10,6 +10,7 @@ gem "mixlib-shellout", github: "opscode/mixlib-shellout", branch: "master"
gem "ohai", github: "opscode/ohai", branch: "master"
group(:docgen) do
+ gem "tomlrb"
gem "yard"
end
diff --git a/rubygems-pkg/rubygems-update-2.4.6.gem b/rubygems-pkg/rubygems-update-2.4.6.gem
new file mode 100644
index 0000000000..97ebec693a
--- /dev/null
+++ b/rubygems-pkg/rubygems-update-2.4.6.gem
Binary files differ
diff --git a/spec/data/big_json_plus_one.json b/spec/data/big_json_plus_one.json
deleted file mode 100644
index 8ea4b74644..0000000000
--- a/spec/data/big_json_plus_one.json
+++ /dev/null
@@ -1,2 +0,0 @@
-{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":"test"
-}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
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/lwrp/providers/buck_passer.rb b/spec/data/lwrp/providers/buck_passer.rb
index c56ab94f85..2bbca07bf7 100644
--- a/spec/data/lwrp/providers/buck_passer.rb
+++ b/spec/data/lwrp/providers/buck_passer.rb
@@ -1,11 +1,28 @@
-provides 'buck_passer'
+provides :buck_passer
+
+def without_deprecation_warnings(&block)
+ old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ begin
+ block.call
+ ensure
+ Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+ end
+end
+
action :pass_buck do
lwrp_foo :prepared_thumbs do
action :prepare_thumbs
- provider :lwrp_thumb_twiddler
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_thumb_twiddler
+ end
end
lwrp_foo :twiddled_thumbs do
action :twiddle_thumbs
- provider :lwrp_thumb_twiddler
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_thumb_twiddler
+ end
end
end
diff --git a/spec/data/lwrp/providers/buck_passer_2.rb b/spec/data/lwrp/providers/buck_passer_2.rb
index d34da3c378..c3bab7266f 100644
--- a/spec/data/lwrp/providers/buck_passer_2.rb
+++ b/spec/data/lwrp/providers/buck_passer_2.rb
@@ -1,10 +1,26 @@
+def without_deprecation_warnings(&block)
+ old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ begin
+ block.call
+ ensure
+ Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+ end
+end
+
action :pass_buck do
lwrp_bar :prepared_eyes do
action :prepare_eyes
- provider :lwrp_paint_drying_watcher
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_paint_drying_watcher
+ end
end
lwrp_bar :dried_paint_watched do
action :watch_paint_dry
- provider :lwrp_paint_drying_watcher
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_paint_drying_watcher
+ end
end
end
diff --git a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
index f5841fb01c..77c1111ff5 100644
--- a/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
+++ b/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb
@@ -3,11 +3,23 @@
# are passed properly (as demonstrated by the call to generate_new_name).
attr_reader :enclosed_resource
+def without_deprecation_warnings(&block)
+ old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ begin
+ block.call
+ ensure
+ Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+ end
+end
+
action :twiddle_thumbs do
@enclosed_resource = lwrp_foo :foo do
monkey generate_new_name(new_resource.monkey){ 'the monkey' }
- action :twiddle_thumbs
- provider :lwrp_monkey_name_printer
+ # We know there will be a deprecation error here; head it off
+ without_deprecation_warnings do
+ provider :lwrp_monkey_name_printer
+ end
end
end
diff --git a/spec/data/lwrp/resources/bar.rb b/spec/data/lwrp/resources/bar.rb
index b6359648db..2ff35efd08 100644
--- a/spec/data/lwrp/resources/bar.rb
+++ b/spec/data/lwrp/resources/bar.rb
@@ -1,2 +1,2 @@
-provides "lwrp_bar" # This makes sure that we cover the case of lwrps using provides
+provides :lwrp_bar # This makes sure that we cover the case of lwrps using provides
actions :pass_buck, :prepare_eyes, :watch_paint_dry
diff --git a/spec/data/lwrp_override/resources/foo.rb b/spec/data/lwrp_override/resources/foo.rb
index 14decb9634..2fc13d32fd 100644
--- a/spec/data/lwrp_override/resources/foo.rb
+++ b/spec/data/lwrp_override/resources/foo.rb
@@ -3,3 +3,8 @@
actions :never_execute
attribute :ever, :kind_of => String
+
+class ::Chef
+ def method_created_by_override_lwrp_foo
+ end
+end
diff --git a/spec/data/big_json.json b/spec/data/nested.json
index 96c2818894..775bb21981 100644
--- a/spec/data/big_json.json
+++ b/spec/data/nested.json
@@ -1,2 +1,2 @@
-{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":"test"
-}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} \ No newline at end of file
+{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":{"key":"test"
+}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
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/audit/runner_spec.rb b/spec/functional/audit/runner_spec.rb
index 494942889a..aae8fcf582 100644
--- a/spec/functional/audit/runner_spec.rb
+++ b/spec/functional/audit/runner_spec.rb
@@ -46,22 +46,12 @@ describe Chef::Audit::Runner do
RSpec::Core::Sandbox.sandboxed { ex.run }
end
- before do
- Chef::Config[:log_location] = stdout
- end
-
describe "#run" do
let(:audits) { {} }
let(:run_context) { instance_double(Chef::RunContext, :events => events, :audits => audits) }
let(:control_group_name) { "control_group_name" }
- it "Correctly runs an empty controls block" do
- in_sub_process do
- runner.run
- end
- end
-
shared_context "passing audit" do
let(:audits) do
should_pass = lambda do
@@ -84,50 +74,40 @@ describe Chef::Audit::Runner do
end
end
- context "there is a single successful control" do
- include_context "passing audit"
- it "correctly runs" do
- in_sub_process do
- runner.run
-
- expect(stdout.string).to match(/1 example, 0 failures/)
+ describe "log location is stdout" do
+ before do
+ allow(Chef::Log).to receive(:info) do |msg|
+ stdout.puts(msg)
end
end
- end
- context "there is a single failing control" do
- include_context "failing audit"
- it "correctly runs" do
+ it "Correctly runs an empty controls block" do
in_sub_process do
runner.run
-
- expect(stdout.string).to match(/Failure\/Error: expect\(2 - 1\)\.to eq\(0\)/)
- expect(stdout.string).to match(/1 example, 1 failure/)
- expect(stdout.string).to match(/# control_group_name should fail/)
end
end
- end
- describe "log location is a file" do
- let(:tmpfile) { Tempfile.new("audit") }
- before do
- Chef::Config[:log_location] = tmpfile.path
- end
+ context "there is a single successful control" do
+ include_context "passing audit"
+ it "correctly runs" do
+ in_sub_process do
+ runner.run
- after do
- tmpfile.close
- tmpfile.unlink
+ expect(stdout.string).to match(/1 example, 0 failures/)
+ end
+ end
end
- include_context "failing audit"
- it "correctly runs" do
- in_sub_process do
- runner.run
+ context "there is a single failing control" do
+ include_context "failing audit"
+ it "correctly runs" do
+ in_sub_process do
+ runner.run
- contents = tmpfile.read
- expect(contents).to match(/Failure\/Error: expect\(2 - 1\)\.to eq\(0\)/)
- expect(contents).to match(/1 example, 1 failure/)
- expect(contents).to match(/# control_group_name should fail/)
+ expect(stdout.string).to match(/Failure\/Error: expect\(2 - 1\)\.to eq\(0\)/)
+ expect(stdout.string).to match(/1 example, 1 failure/)
+ expect(stdout.string).to match(/# control_group_name should fail/)
+ end
end
end
end
diff --git a/spec/functional/event_loggers/windows_eventlog_spec.rb b/spec/functional/event_loggers/windows_eventlog_spec.rb
index 4e383dd429..0723e7b984 100644
--- a/spec/functional/event_loggers/windows_eventlog_spec.rb
+++ b/spec/functional/event_loggers/windows_eventlog_spec.rb
@@ -79,4 +79,18 @@ describe Chef::EventLoggers::WindowsEventLogger, :windows_only, :not_supported_o
end).to be_truthy
end
+ it 'writes run_failed event with event_id 10003 even when run_status is not set' do
+ logger.run_failed(mock_exception)
+
+ expect(event_log.read(flags, offset).any? do |e|
+ e.source == 'Chef' && e.event_id == 10003 &&
+ e.string_inserts[0].include?("UNKNOWN") &&
+ e.string_inserts[1].include?("UNKNOWN") &&
+ e.string_inserts[2].include?(mock_exception.class.name) &&
+ e.string_inserts[3].include?(mock_exception.message) &&
+ e.string_inserts[4].include?(mock_exception.backtrace[0]) &&
+ e.string_inserts[4].include?(mock_exception.backtrace[1])
+ end).to be_truthy
+ end
+
end
diff --git a/spec/functional/file_content_management/deploy_strategies_spec.rb b/spec/functional/file_content_management/deploy_strategies_spec.rb
index 03a6c504c1..8a995d0e41 100644
--- a/spec/functional/file_content_management/deploy_strategies_spec.rb
+++ b/spec/functional/file_content_management/deploy_strategies_spec.rb
@@ -43,7 +43,7 @@ shared_examples_for "a content deploy strategy" do
##
# UNIX Context
- let(:default_mode) { normalize_mode(0100666 - File.umask) }
+ let(:default_mode) { normalize_mode(0666 & ~File.umask) }
it "touches the file to create it (UNIX)", :unix_only do
content_deployer.create(target_file_path)
diff --git a/spec/functional/knife/ssh_spec.rb b/spec/functional/knife/ssh_spec.rb
index 5b8ad6f368..6608d05771 100644
--- a/spec/functional/knife/ssh_spec.rb
+++ b/spec/functional/knife/ssh_spec.rb
@@ -165,7 +165,7 @@ describe Chef::Knife::Ssh do
it "uses the ssh_attribute" do
@knife.run
- expect(@knife.config[:attribute]).to eq("ec2.public_hostname")
+ expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("ec2.public_hostname")
end
end
@@ -177,7 +177,7 @@ describe Chef::Knife::Ssh do
it "uses the default" do
@knife.run
- expect(@knife.config[:attribute]).to eq("fqdn")
+ expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("fqdn")
end
end
diff --git a/spec/functional/mixin/powershell_out_spec.rb b/spec/functional/mixin/powershell_out_spec.rb
new file mode 100644
index 0000000000..9cc8aeed7e
--- /dev/null
+++ b/spec/functional/mixin/powershell_out_spec.rb
@@ -0,0 +1,43 @@
+#
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/powershell_out'
+
+describe Chef::Mixin::PowershellOut, windows_only: true do
+ include Chef::Mixin::PowershellOut
+
+ describe "#powershell_out" do
+ it "runs a powershell command and collects stdout" do
+ expect(powershell_out("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/
+ end
+
+ it "does not raise exceptions when the command is invalid" do
+ powershell_out("this-is-not-a-valid-command").run_command
+ end
+ end
+
+ describe "#powershell_out!" do
+ it "runs a powershell command and collects stdout" do
+ expect(powershell_out!("get-process").run_command.stdout).to match /Handles\s+NPM\(K\)\s+PM\(K\)\s+WS\(K\)\s+VM\(M\)\s+CPU\(s\)\s+Id\s+ProcessName/
+ end
+
+ it "raises exceptions when the command is invalid" do
+ expect { powershell_out!("this-is-not-a-valid-command").run_command }.to raise_exception(Mixlib::ShellOut::ShellCommandFailed)
+ end
+ end
+end
diff --git a/spec/functional/provider/whyrun_safe_ruby_block_spec.rb b/spec/functional/provider/whyrun_safe_ruby_block_spec.rb
index 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 763021607b..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
@@ -70,7 +70,7 @@ describe Chef::Platform::Rebooter do
shared_context 'test a reboot method' do
def test_rebooter_method(method_sym, is_windows, expected_reboot_str)
- allow(Chef::Platform).to receive(:windows?).and_return(is_windows)
+ allow(ChefConfig).to receive(:windows?).and_return(is_windows)
expect(rebooter).to receive(:shell_out!).once.with(expected_reboot_str)
expect(rebooter).to receive(method_sym).once.and_call_original
rebooter.send(method_sym, run_context.node)
diff --git a/spec/functional/resource/aixinit_service_spec.rb b/spec/functional/resource/aixinit_service_spec.rb
index 19b65ca2a0..3d9216158e 100755
--- a/spec/functional/resource/aixinit_service_spec.rb
+++ b/spec/functional/resource/aixinit_service_spec.rb
@@ -208,4 +208,4 @@ describe Chef::Resource::Service, :requires_root, :aix_only do
end
end
end
-end \ No newline at end of file
+end
diff --git a/spec/functional/resource/cookbook_file_spec.rb b/spec/functional/resource/cookbook_file_spec.rb
index 7797ed0041..6d4c5b4a8f 100644
--- a/spec/functional/resource/cookbook_file_spec.rb
+++ b/spec/functional/resource/cookbook_file_spec.rb
@@ -32,7 +32,7 @@ describe Chef::Resource::CookbookFile do
content
end
- let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+ let(:default_mode) { (0666 & ~File.umask).to_s(8) }
it_behaves_like "a securable resource with reporting"
diff --git a/spec/functional/resource/directory_spec.rb b/spec/functional/resource/directory_spec.rb
index 2c4025f83e..88a810964f 100644
--- a/spec/functional/resource/directory_spec.rb
+++ b/spec/functional/resource/directory_spec.rb
@@ -23,7 +23,7 @@ describe Chef::Resource::Directory do
let(:directory_base) { "directory_spec" }
- let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) }
+ let(:default_mode) { (0777 & ~File.umask).to_s(8) }
def create_resource
events = Chef::EventDispatch::Dispatcher.new
diff --git a/spec/functional/resource/dsc_resource_spec.rb b/spec/functional/resource/dsc_resource_spec.rb
new file mode 100644
index 0000000000..6f453eeb9f
--- /dev/null
+++ b/spec/functional/resource/dsc_resource_spec.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::DscResource, :windows_powershell_dsc_only do
+ before(:all) do
+ @ohai = Ohai::System.new
+ @ohai.all_plugins(['platform', 'os', 'languages/powershell'])
+ end
+
+ let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new }
+
+ let(:node) {
+ Chef::Node.new.tap do |n|
+ n.consume_external_attrs(@ohai.data, {})
+ end
+ }
+
+ let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) }
+
+ let(:new_resource) {
+ Chef::Resource::DscResource.new("dsc_resource_test", run_context)
+ }
+
+ context 'when Powershell does not support Invoke-DscResource'
+ context 'when Powershell supports Invoke-DscResource' do
+ before do
+ if !Chef::Platform.supports_dsc_invoke_resource?(node)
+ skip 'Requires Powershell >= 5.0.10018.0'
+ end
+ end
+ context 'with an invalid dsc resource' do
+ it 'raises an exception if the resource is not found' do
+ new_resource.resource 'thisdoesnotexist'
+ expect { new_resource.run_action(:run) }.to raise_error(
+ Chef::Exceptions::ResourceNotFound)
+ end
+ end
+
+ context 'with a valid dsc resource' do
+ let(:tmp_file_name) { Dir::Tmpname.create('tmpfile') {} }
+ let(:test_text) { "'\"!@#$%^&*)(}{][\u2713~n"}
+
+ before do
+ new_resource.resource :File
+ new_resource.property :Contents, test_text
+ new_resource.property :DestinationPath, tmp_file_name
+ end
+
+ after do
+ File.delete(tmp_file_name) if File.exists? tmp_file_name
+ end
+
+ it 'converges the resource if it is not converged' do
+ new_resource.run_action(:run)
+ contents = File.open(tmp_file_name, 'rb:bom|UTF-16LE') do |f|
+ f.read.encode('UTF-8')
+ end
+ expect(contents).to eq(test_text)
+ expect(new_resource).to be_updated
+ end
+
+ it 'does not converge the resource if it is already converged' do
+ new_resource.run_action(:run)
+ expect(new_resource).to be_updated
+ reresource =
+ Chef::Resource::DscResource.new("dsc_resource_retest", run_context)
+ reresource.resource :File
+ reresource.property :Contents, test_text
+ reresource.property :DestinationPath, tmp_file_name
+ reresource.run_action(:run)
+ expect(reresource).not_to be_updated
+ end
+ end
+
+ end
+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/env_spec.rb b/spec/functional/resource/env_spec.rb
index 16caec14bf..b9dcd7b33a 100755
--- a/spec/functional/resource/env_spec.rb
+++ b/spec/functional/resource/env_spec.rb
@@ -29,14 +29,15 @@ describe Chef::Resource::Env, :windows_only do
let(:env_value_expandable) { '%SystemRoot%' }
let(:test_run_context) {
node = Chef::Node.new
+ node.default['os'] = 'windows'
node.default['platform'] = 'windows'
node.default['platform_version'] = '6.1'
empty_events = Chef::EventDispatch::Dispatcher.new
Chef::RunContext.new(node, {}, empty_events)
}
- let(:test_resource) {
- Chef::Resource::Env.new('unknown', test_run_context)
- }
+ let(:test_resource) {
+ Chef::Resource::Env.new('unknown', test_run_context)
+ }
before(:each) do
resource_lower = Chef::Resource::Env.new(chef_env_test_lower_case, test_run_context)
diff --git a/spec/functional/resource/execute_spec.rb b/spec/functional/resource/execute_spec.rb
index aaa1c772b7..692ccfb796 100644
--- a/spec/functional/resource/execute_spec.rb
+++ b/spec/functional/resource/execute_spec.rb
@@ -62,7 +62,7 @@ describe Chef::Resource::Execute do
end
describe "when parent resource sets :cwd" do
- let(:guard) { %{ruby -e 'exit 1 unless File.exists?("./big_json_plus_one.json")'} }
+ let(:guard) { %{ruby -e 'exit 1 unless File.exists?("./nested.json")'} }
it "guard inherits :cwd from resource and runs" do
resource.cwd CHEF_SPEC_DATA
@@ -137,9 +137,16 @@ describe Chef::Resource::Execute do
end
end
+ # Ensure that CommandTimeout is raised, and is caused by resource.timeout really expiring.
+ # https://github.com/chef/chef/issues/2985
+ #
+ # resource.timeout should be short, this is what we're testing
+ # resource.command ruby sleep timer should be longer than resource.timeout to give us something to timeout
+ # Timeout::timeout should be longer than resource.timeout, but less than the resource.command ruby sleep timer,
+ # so we fail if we finish on resource.command instead of resource.timeout, but raise CommandTimeout anyway (#2175).
it "times out when a timeout is set on the resource" do
- Timeout::timeout(5) do
- resource.command %{ruby -e 'sleep 600'}
+ Timeout::timeout(30) do
+ resource.command %{ruby -e 'sleep 300'}
resource.timeout 0.1
expect { resource.run_action(:run) }.to raise_error(Mixlib::ShellOut::CommandTimeout)
end
diff --git a/spec/functional/resource/file_spec.rb b/spec/functional/resource/file_spec.rb
index cf70c349fb..9e30e62111 100644
--- a/spec/functional/resource/file_spec.rb
+++ b/spec/functional/resource/file_spec.rb
@@ -64,7 +64,7 @@ describe Chef::Resource::File do
provider.current_resource
end
- let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+ let(:default_mode) { (0666 & ~File.umask).to_s(8) }
it_behaves_like "a file resource"
@@ -86,6 +86,31 @@ describe Chef::Resource::File do
end
end
+
+ describe "when using backup" do
+ before do
+ Chef::Config[:file_backup_path] = CHEF_SPEC_BACKUP_PATH
+ resource_without_content.backup(1)
+ resource_without_content.run_action(:create)
+ end
+
+ let(:backup_glob) { File.join(CHEF_SPEC_BACKUP_PATH, test_file_dir.sub(/^([A-Za-z]:)/, ""), "#{file_base}*") }
+
+ let(:path) do
+ # Use native system path
+ ChefConfig::PathHelper.canonical_path(File.join(test_file_dir, make_tmpname(file_base)), false)
+ end
+
+ it "only stores the number of requested backups" do
+ resource_without_content.content('foo')
+ resource_without_content.run_action(:create)
+ resource_without_content.content('bar')
+ resource_without_content.run_action(:create)
+ expect(Dir.glob(backup_glob).length).to eq(1)
+ end
+
+ end
+
# github issue 1842.
describe "when running action :create on a relative path" do
before do
diff --git a/spec/functional/resource/group_spec.rb b/spec/functional/resource/group_spec.rb
index 6676aa32e9..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,45 +135,76 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
group_should_not_exist(group_name)
end
- describe "when append is not set" do
- let(:included_members) { [spec_members[1]] }
+ # dscl doesn't perform any error checking and will let you add users that don't exist.
+ describe "when no users exist", :not_supported_on_mac_osx do
+ describe "when append is not set" do
+ # excluded_members can only be used when append is set. It is ignored otherwise.
+ let(:excluded_members) { [] }
- before do
- create_user(spec_members[1])
- create_user(spec_members[0])
- add_members_to_group([spec_members[0]])
- end
-
- after do
- remove_user(spec_members[1])
- remove_user(spec_members[0])
+ it "should raise an error" do
+ expect { group_resource.run_action(tested_action) }.to raise_error()
+ end
end
- it "should remove the existing users and add the new users to the group" do
- group_resource.run_action(tested_action)
+ describe "when append is set" do
+ before do
+ group_resource.append(true)
+ end
- expect(user_exist_in_group?(spec_members[1])).to eq(true)
- expect(user_exist_in_group?(spec_members[0])).to eq(false)
+ it "should raise an error" do
+ expect { group_resource.run_action(tested_action) }.to raise_error()
+ end
end
end
- describe "when append is set" do
- before(:each) do
- group_resource.append(true)
+ describe "when the users exist" do
+ before do
+ (spec_members).each do |member|
+ create_user(member)
+ end
end
- describe "when the users exist" do
- before do
- (included_members + excluded_members).each do |member|
- create_user(member)
+ after do
+ (spec_members).each do |member|
+ remove_user(member)
+ end
+ end
+
+ describe "when append is not set" do
+ it "should set the group to to contain given members" do
+ group_resource.run_action(tested_action)
+
+ included_members.each do |member|
+ expect(user_exist_in_group?(member)).to eq(true)
+ end
+ (spec_members - included_members).each do |member|
+ expect(user_exist_in_group?(member)).to eq(false)
end
end
- after do
- (included_members + excluded_members).each do |member|
- remove_user(member)
+ describe "when group already contains some users" do
+ before do
+ add_members_to_group([included_members[0]])
+ add_members_to_group(spec_members - included_members)
+ end
+
+ it "should remove all existing users and only add the new users to the group" do
+ group_resource.run_action(tested_action)
+
+ included_members.each do |member|
+ expect(user_exist_in_group?(member)).to eq(true)
+ end
+ (spec_members - included_members).each do |member|
+ expect(user_exist_in_group?(member)).to eq(false)
+ end
end
end
+ end
+
+ describe "when append is set" do
+ before(:each) do
+ group_resource.append(true)
+ end
it "should add included members to the group" do
group_resource.run_action(tested_action)
@@ -186,9 +217,9 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
end
end
- describe "when group contains some users" do
+ describe "when group already contains some users" do
before(:each) do
- add_members_to_group([ spec_members[0], spec_members[2] ])
+ add_members_to_group([included_members[0], excluded_members[0]])
end
it "should add the included users and remove excluded users" do
@@ -203,20 +234,6 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
end
end
end
-
- describe "when the users doesn't exist" do
- describe "when append is not set" do
- it "should raise an error" do
- expect { @grp_resource.run_action(tested_action) }.to raise_error
- end
- end
-
- describe "when append is set" do
- it "should raise an error" do
- expect { @grp_resource.run_action(tested_action) }.to raise_error
- end
- end
- end
end
end
@@ -231,6 +248,12 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
group_should_exist(group_name)
end
+ after(:each) do
+ group_resource.run_action(:remove)
+ end
+
+ # TODO: The ones below might actually return ArgumentError now - but I don't have
+ # a way to verify that. Change it and delete this comment if that's the case.
describe "when updating membership" do
it "raises an error for a non well-formed domain name" do
group_resource.members [invalid_domain_user_name]
@@ -256,7 +279,7 @@ describe Chef::Resource::Group, :requires_root_or_running_windows, :not_supporte
end
end
- let(:group_name) { "t-#{SecureRandom.random_number(9999)}" }
+ let(:group_name) { "group#{SecureRandom.random_number(9999)}" }
let(:included_members) { nil }
let(:excluded_members) { nil }
let(:group_resource) {
@@ -300,7 +323,7 @@ theoldmanwalkingdownthestreetalwayshadagoodsmileonhisfacetheoldmanwalking\
downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" }
it "should not create a group" do
- expect { group_resource.run_action(:create) }.to raise_error
+ expect { group_resource.run_action(:create) }.to raise_error(ArgumentError)
group_should_not_exist(group_name)
end
end
@@ -372,6 +395,11 @@ downthestreetalwayshadagoodsmileonhisfacetheoldmanwalkingdownthestreeQQQQQQ" }
let(:tested_action) { :manage }
describe "when there is no group" do
+ before(:each) do
+ group_resource.run_action(:remove)
+ group_should_not_exist(group_name)
+ end
+
it "raises an error on modify" do
expect { group_resource.run_action(:modify) }.to raise_error
end
diff --git a/spec/functional/resource/link_spec.rb b/spec/functional/resource/link_spec.rb
index d39a0c2ef6..7e903b30b4 100644
--- a/spec/functional/resource/link_spec.rb
+++ b/spec/functional/resource/link_spec.rb
@@ -348,8 +348,7 @@ describe Chef::Resource::Link do
end
it_behaves_like 'delete errors out'
end
- context 'and the link already exists and is not writeable to this user', :skip => true do
- end
+
it_behaves_like 'a securable resource without existing target' do
let(:path) { target_file }
def allowed_acl(sid, expected_perms)
@@ -360,7 +359,7 @@ describe Chef::Resource::Link do
end
def parent_inheritable_acls
dummy_file_path = File.join(test_file_dir, "dummy_file")
- dummy_file = FileUtils.touch(dummy_file_path)
+ FileUtils.touch(dummy_file_path)
dummy_desc = get_security_descriptor(dummy_file_path)
FileUtils.rm_rf(dummy_file_path)
dummy_desc
@@ -416,8 +415,6 @@ describe Chef::Resource::Link do
end
end
end
- context "when the link destination is not readable to this user", :skip => true do
- end
context "when the link destination does not exist" do
include_context 'create symbolic link succeeds'
include_context 'delete is noop'
@@ -518,8 +515,6 @@ describe Chef::Resource::Link do
end
it_behaves_like 'delete errors out'
end
- context "and the link already exists and is not writeable to this user", :skip => true do
- end
context "and specifies security attributes" do
before(:each) do
resource.owner(windows? ? 'Guest' : 'nobody')
@@ -559,10 +554,10 @@ describe Chef::Resource::Link do
end
context 'and the link does not yet exist' do
it 'links to the target file' do
+ skip('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks') if (os_x? or freebsd? or aix?)
resource.run_action(:create)
expect(File.exists?(target_file)).to be_truthy
# OS X gets angry about this sort of link. Bug in OS X, IMO.
- pending('OS X/FreeBSD/AIX symlink? and readlink working on hard links to symlinks') if (os_x? or freebsd? or aix?)
expect(symlink?(target_file)).to be_truthy
expect(readlink(target_file)).to eq(canonicalize(@other_target))
end
@@ -578,7 +573,7 @@ describe Chef::Resource::Link do
end
context 'and the link does not yet exist' do
it 'links to the target file' do
- pending('OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks') if (os_x? or freebsd? or aix?)
+ skip('OS X/FreeBSD/AIX fails to create hardlinks to broken symlinks') if (os_x? or freebsd? or aix?)
resource.run_action(:create)
# Windows and Unix have different definitions of exists? here, and that's OK.
if windows?
@@ -593,8 +588,7 @@ describe Chef::Resource::Link do
end
end
end
- context "when the link destination is not readable to this user", :skip => true do
- end
+
context "when the link destination does not exist" do
context 'and the link does not yet exist' do
it 'create errors out' do
diff --git a/spec/functional/resource/package_spec.rb b/spec/functional/resource/package_spec.rb
index 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 1b3ac844e0..be744e748b 100644
--- a/spec/functional/resource/powershell_spec.rb
+++ b/spec/functional/resource/powershell_script_spec.rb
@@ -56,13 +56,13 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
resource.run_action(:run)
end
- it "returns the -27 for a powershell script that exits with -27" do
+ it "returns the exit status 27 for a powershell script that exits with 27" do
file = Tempfile.new(['foo', '.ps1'])
begin
- file.write "exit -27"
+ file.write "exit 27"
file.close
resource.code(". \"#{file.path}\"")
- resource.returns(-27)
+ resource.returns(27)
resource.run_action(:run)
ensure
file.close
@@ -70,6 +70,30 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
end
end
+ let (:negative_exit_status) { -27 }
+ let (:unsigned_exit_status) { (-negative_exit_status ^ 65535) + 1 }
+ it "returns the exit status -27 as a signed integer or an unsigned 16-bit 2's complement value of 65509 for a powershell script that exits with -27" do
+ # Versions of PowerShell prior to 4.0 return a 16-bit unsigned value --
+ # PowerShell 4.0 and later versions return a 32-bit signed value.
+ file = Tempfile.new(['foo', '.ps1'])
+ begin
+ file.write "exit #{negative_exit_status.to_s}"
+ file.close
+ resource.code(". \"#{file.path}\"")
+
+ # PowerShell earlier than 4.0 takes negative exit codes
+ # and returns them as the underlying unsigned 16-bit
+ # 2's complement representation. We cover multiple versions
+ # of PowerShell in this example by including both the signed
+ # exit code and its converted counterpart as permitted return values.
+ # See http://support.microsoft.com/en-us/kb/2646183/zh-cn
+ resource.returns([negative_exit_status, unsigned_exit_status])
+ expect { resource.run_action(:run) }.not_to raise_error
+ ensure
+ file.close
+ file.unlink
+ end
+ end
it "returns the process exit code" do
resource.code(arbitrary_nonzero_process_exit_code_content)
@@ -98,7 +122,19 @@ describe Chef::Resource::WindowsScript::PowershellScript, :windows_only do
it "returns 1 if the last command was a cmdlet that failed and was preceded by a successfully executed non-cmdlet Windows binary" do
resource.code([windows_process_exit_code_success_content, cmdlet_exit_code_not_found_content].join(';'))
resource.returns(1)
- resource.run_action(:run)
+ expect { resource.run_action(:run) }.not_to raise_error
+ end
+
+ it "raises 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 "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) }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
end
# This somewhat ambiguous case, two failures of different types,
@@ -191,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)
@@ -204,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/resource/remote_directory_spec.rb b/spec/functional/resource/remote_directory_spec.rb
index bcafca7399..37ffbbc971 100644
--- a/spec/functional/resource/remote_directory_spec.rb
+++ b/spec/functional/resource/remote_directory_spec.rb
@@ -22,7 +22,7 @@ describe Chef::Resource::RemoteDirectory do
include_context Chef::Resource::Directory
let(:directory_base) { "directory_spec" }
- let(:default_mode) { ((0100777 - File.umask) & 07777).to_s(8) }
+ let(:default_mode) { (0777 & ~File.umask).to_s(8) }
def create_resource
cookbook_repo = File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks"))
diff --git a/spec/functional/resource/remote_file_spec.rb b/spec/functional/resource/remote_file_spec.rb
index 29091fd785..4fbcd2d24b 100644
--- a/spec/functional/resource/remote_file_spec.rb
+++ b/spec/functional/resource/remote_file_spec.rb
@@ -52,7 +52,7 @@ describe Chef::Resource::RemoteFile do
create_resource
end
- let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+ let(:default_mode) { (0666 & ~File.umask).to_s(8) }
context "when fetching files over HTTP" do
before(:all) do
diff --git a/spec/functional/resource/template_spec.rb b/spec/functional/resource/template_spec.rb
index d7b35e7450..35c5166e31 100644
--- a/spec/functional/resource/template_spec.rb
+++ b/spec/functional/resource/template_spec.rb
@@ -58,7 +58,7 @@ describe Chef::Resource::Template do
create_resource
end
- let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+ let(:default_mode) { (0666 & ~File.umask).to_s(8) }
it_behaves_like "a file resource"
diff --git a/spec/functional/resource/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb
index 45b6754453..5d960daf11 100644
--- a/spec/functional/resource/user/dscl_spec.rb
+++ b/spec/functional/resource/user/dscl_spec.rb
@@ -19,9 +19,8 @@ require 'spec_helper'
require 'chef/mixin/shell_out'
metadata = {
- :unix_only => true,
+ :mac_osx_only => true,
:requires_root => true,
- :provider => {:user => Chef::Provider::User::Dscl},
:not_supported_on_mac_osx_106 => true,
}
diff --git a/spec/functional/resource/user/useradd_spec.rb b/spec/functional/resource/user/useradd_spec.rb
index 9ac88d7b60..474f6a4ecf 100644
--- a/spec/functional/resource/user/useradd_spec.rb
+++ b/spec/functional/resource/user/useradd_spec.rb
@@ -32,6 +32,7 @@ end
metadata = { :unix_only => true,
:requires_root => true,
+ :not_supported_on_mac_osx => true,
:provider => {:user => user_provider_for_platform}
}
@@ -64,8 +65,12 @@ describe Chef::Provider::User::Useradd, metadata do
end
end
- def supports_quote_in_username?
- OHAI_SYSTEM["platform_family"] == "debian"
+ def self.quote_in_username_unsupported?
+ if OHAI_SYSTEM["platform_family"] == "debian"
+ false
+ else
+ "Only debian family systems support quotes in username"
+ end
end
def password_should_be_set
@@ -76,9 +81,22 @@ describe Chef::Provider::User::Useradd, metadata do
end
end
+ def try_cleanup
+ ['/home/cheftestfoo', '/home/cheftestbar'].each do |f|
+ FileUtils.rm_rf(f) if File.exists? f
+ end
+
+ ['cf-test'].each do |u|
+ r = Chef::Resource::User.new("DELETE USER", run_context)
+ r.username('cf-test')
+ r.run_action(:remove)
+ end
+ end
+
before do
# Silence shell_out live stream
Chef::Log.level = :warn
+ try_cleanup
end
after do
@@ -94,7 +112,7 @@ describe Chef::Provider::User::Useradd, metadata do
break if status.exitstatus != 8
sleep 1
- max_retries = max_retries -1
+ max_retries = max_retries - 1
rescue UserNotFound
break
end
@@ -148,15 +166,10 @@ describe Chef::Provider::User::Useradd, metadata do
end
end
- let(:skip) { false }
-
describe "action :create" do
context "when the user does not exist beforehand" do
before do
- if reason = skip
- pending(reason)
- end
user_resource.run_action(:create)
expect(user_resource).to be_updated_by_last_action
end
@@ -172,14 +185,7 @@ describe Chef::Provider::User::Useradd, metadata do
# tabulation: '\t', etc.). Note that using a slash ('/') may break the
# default algorithm for the definition of the user's home directory.
- context "and the username contains a single quote" do
- let(:skip) do
- if supports_quote_in_username?
- false
- else
- "Platform #{OHAI_SYSTEM["platform"]} not expected to support username w/ quote"
- end
- end
+ context "and the username contains a single quote", skip: quote_in_username_unsupported? do
let(:username) { "t'bilisi" }
@@ -328,7 +334,7 @@ describe Chef::Provider::User::Useradd, metadata do
before do
if reason = skip
- pending(reason)
+ skip(reason)
end
existing_user.run_action(:create)
expect(existing_user).to be_updated_by_last_action
@@ -386,18 +392,18 @@ describe Chef::Provider::User::Useradd, metadata do
end
context "and home directory is updated" do
- let(:existing_home) { "/home/foo" }
- let(:home) { "/home/bar" }
+ let(:existing_home) { "/home/cheftestfoo" }
+ let(:home) { "/home/cheftestbar" }
it "ensures the home directory is set to the desired value" do
- expect(pw_entry.home).to eq("/home/bar")
+ expect(pw_entry.home).to eq("/home/cheftestbar")
end
context "and manage_home is enabled" do
let(:existing_manage_home) { true }
let(:manage_home) { true }
it "moves the home directory to the new location" do
- expect(File).not_to exist("/home/foo")
- expect(File).to exist("/home/bar")
+ expect(File).not_to exist("/home/cheftestfoo")
+ expect(File).to exist("/home/cheftestbar")
end
end
@@ -409,19 +415,19 @@ describe Chef::Provider::User::Useradd, metadata do
# Inconsistent behavior. See: CHEF-2205
it "created the home dir b/c of CHEF-2205 so it still exists" do
# This behavior seems contrary to expectation and non-convergent.
- expect(File).not_to exist("/home/foo")
- expect(File).to exist("/home/bar")
+ expect(File).not_to exist("/home/cheftestfoo")
+ expect(File).to exist("/home/cheftestbar")
end
elsif ohai[:platform] == "aix"
it "creates the home dir in the desired location" do
- expect(File).not_to exist("/home/foo")
- expect(File).to exist("/home/bar")
+ expect(File).not_to exist("/home/cheftestfoo")
+ expect(File).to exist("/home/cheftestbar")
end
else
it "does not create the home dir in the desired location (XXX)" do
# This behavior seems contrary to expectation and non-convergent.
- expect(File).not_to exist("/home/foo")
- expect(File).not_to exist("/home/bar")
+ expect(File).not_to exist("/home/cheftestfoo")
+ expect(File).not_to exist("/home/cheftestbar")
end
end
end
@@ -432,8 +438,8 @@ describe Chef::Provider::User::Useradd, metadata do
it "leaves the old home directory around (XXX)" do
# Would it be better to remove the old home?
- expect(File).to exist("/home/foo")
- expect(File).not_to exist("/home/bar")
+ expect(File).to exist("/home/cheftestfoo")
+ expect(File).not_to exist("/home/cheftestbar")
end
end
end
@@ -521,7 +527,7 @@ describe Chef::Provider::User::Useradd, metadata do
def aix_user_lock_status
lock_info = shell_out!("lsuser -a account_locked #{username}")
- status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1]
+ /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)[1]
end
def user_account_should_be_locked
diff --git a/spec/functional/resource/user/windows_spec.rb b/spec/functional/resource/user/windows_spec.rb
new file mode 100644
index 0000000000..5e68478b34
--- /dev/null
+++ b/spec/functional/resource/user/windows_spec.rb
@@ -0,0 +1,125 @@
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/shell_out'
+
+describe Chef::Provider::User::Windows, :windows_only do
+ include Chef::Mixin::ShellOut
+
+ let(:username) { 'ChefFunctionalTest' }
+ let(:password) { SecureRandom.uuid }
+
+ let(:node) do
+ n = Chef::Node.new
+ n.consume_external_attrs(OHAI_SYSTEM.data.dup, {})
+ n
+ end
+
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:new_resource) do
+ Chef::Resource::User.new(username, run_context).tap do |r|
+ r.provider(Chef::Provider::User::Windows)
+ r.password(password)
+ end
+ end
+
+ def delete_user(u)
+ shell_out("net user #{u} /delete")
+ end
+
+ before do
+ delete_user(username)
+ end
+
+ describe 'action :create' do
+ it 'creates a user when a username and password are given' do
+ new_resource.run_action(:create)
+ expect(new_resource).to be_updated_by_last_action
+ expect(shell_out("net user #{username}").exitstatus).to eq(0)
+ end
+
+ it 'reports no changes if there are no changes needed' do
+ new_resource.run_action(:create)
+ new_resource.run_action(:create)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+
+ it 'allows chaning the password' do
+ new_resource.run_action(:create)
+ new_resource.password(SecureRandom.uuid)
+ new_resource.run_action(:create)
+ expect(new_resource).to be_updated_by_last_action
+ end
+ end
+
+ describe 'action :remove' do
+ before do
+ new_resource.run_action(:create)
+ end
+
+ it 'deletes the user' do
+ new_resource.run_action(:remove)
+ expect(new_resource).to be_updated_by_last_action
+ expect(shell_out("net user #{username}").exitstatus).to eq(2)
+ end
+
+ it 'is idempotent' do
+ new_resource.run_action(:remove)
+ new_resource.run_action(:remove)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+ end
+
+ describe 'action :lock' do
+ before do
+ new_resource.run_action(:create)
+ end
+
+ it 'locks the user account' do
+ new_resource.run_action(:lock)
+ expect(new_resource).to be_updated_by_last_action
+ expect(shell_out("net user #{username}").stdout).to match(/Account active\s*No/)
+ end
+
+ it 'is idempotent' do
+ new_resource.run_action(:lock)
+ new_resource.run_action(:lock)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+ end
+
+ describe 'action :unlock' do
+ before do
+ new_resource.run_action(:create)
+ new_resource.run_action(:lock)
+ end
+
+ it 'unlocks the user account' do
+ new_resource.run_action(:unlock)
+ expect(new_resource).to be_updated_by_last_action
+ expect(shell_out("net user #{username}").stdout).to match(/Account active\s*Yes/)
+ end
+
+ it 'is idempotent' do
+ new_resource.run_action(:unlock)
+ new_resource.run_action(:unlock)
+ expect(new_resource).not_to be_updated_by_last_action
+ end
+ end
+end
diff --git a/spec/functional/shell_spec.rb b/spec/functional/shell_spec.rb
index fa9de77b0e..a753948c7f 100644
--- a/spec/functional/shell_spec.rb
+++ b/spec/functional/shell_spec.rb
@@ -29,6 +29,8 @@ describe Shell do
describe "smoke tests", :unix_only => true do
include Chef::Mixin::Command::Unix
+ TIMEOUT=300
+
def read_until(io, expected_value)
start = Time.new
buffer = ""
@@ -38,15 +40,30 @@ describe Shell do
rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EIO, EOFError
sleep 0.01
end
- if Time.new - start > 30
- STDERR.puts "did not read expected value `#{expected_value}' within 15s"
- STDERR.puts "Buffer so far: `#{buffer}'"
- break
+ if Time.new - start > TIMEOUT
+ raise "did not read expected value `#{expected_value}' within #{TIMEOUT}s\n" +
+ "Buffer so far: `#{buffer}'"
end
end
buffer
end
+ def flush_output(io)
+ start = Time.new
+ loop do
+ begin
+ io.read_nonblock(1)
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
+ sleep 0.01
+ rescue EOFError, Errno::EIO
+ break
+ end
+ if Time.new - start > TIMEOUT
+ raise "timed out after #{TIMEOUT}s waiting for output to end"
+ end
+ end
+ end
+
def wait_or_die(pid)
start = Time.new
@@ -67,12 +84,12 @@ describe Shell do
path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__)
output = ''
status = popen4("#{path_to_chef_shell} -c #{config} #{options}", :waitlast => true) do |pid, stdin, stdout, stderr|
- read_until(stdout, "chef >")
+ read_until(stdout, "chef (#{Chef::VERSION})>")
yield stdout, stdin if block_given?
stdin.write("'done'\n")
output = read_until(stdout, '=> "done"')
stdin.print("exit\n")
- read_until(stdout, "\n")
+ flush_output(stdout)
end
[output, status.exitstatus]
@@ -84,14 +101,12 @@ describe Shell do
config = File.expand_path("shef-config.rb", CHEF_SPEC_DATA)
path_to_chef_shell = File.expand_path("../../../bin/chef-shell", __FILE__)
reader, writer, pid = PTY.spawn("#{path_to_chef_shell} -c #{config} #{options}")
- read_until(reader, "chef >")
+ read_until(reader, "chef (#{Chef::VERSION})>")
yield reader, writer if block_given?
writer.puts('"done"')
output = read_until(reader, '=> "done"')
writer.print("exit\n")
- read_until(reader, "exit")
- read_until(reader, "\n")
- read_until(reader, "\n")
+ flush_output(reader)
writer.close
exitstatus = wait_or_die(pid)
diff --git a/spec/functional/util/powershell/cmdlet_spec.rb b/spec/functional/util/powershell/cmdlet_spec.rb
index b240a5ec12..201fb95af8 100644
--- a/spec/functional/util/powershell/cmdlet_spec.rb
+++ b/spec/functional/util/powershell/cmdlet_spec.rb
@@ -19,7 +19,7 @@
require 'chef/json_compat'
require File.expand_path('../../../../spec_helper', __FILE__)
-describe Chef::Util::Powershell::Cmdlet, :windows_only do
+describe Chef::Util::Powershell::Cmdlet, :windows_powershell_dsc_only do
before(:all) do
ohai = Ohai::System.new
ohai.load_plugins
@@ -88,7 +88,7 @@ describe Chef::Util::Powershell::Cmdlet, :windows_only do
context "when returning json" do
let(:cmd_output_format) { :json }
- it "returns json format data", :windows_powershell_dsc_only do
+ it "returns json format data" do
result = cmdlet_alias_requires_switch_or_argument.run({},{},'ls')
expect(result.succeeded?).to eq(true)
expect(lambda{Chef::JSONCompat.parse(result.return_value)}).not_to raise_error
@@ -97,7 +97,7 @@ describe Chef::Util::Powershell::Cmdlet, :windows_only do
context "when returning Ruby objects" do
let(:cmd_output_format) { :object }
- it "returns object format data", :windows_powershell_dsc_only do
+ it "returns object format data" do
result = simple_cmdlet.run({},{:cwd => etc_directory}, 'hosts')
expect(result.succeeded?).to eq(true)
data = result.return_value
diff --git a/spec/functional/win32/crypto_spec.rb b/spec/functional/win32/crypto_spec.rb
new file mode 100644
index 0000000000..1492995886
--- /dev/null
+++ b/spec/functional/win32/crypto_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Jay Mundrawala(<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+if Chef::Platform.windows?
+ require 'chef/win32/crypto'
+end
+
+describe 'Chef::ReservedNames::Win32::Crypto', :windows_only do
+ describe '#encrypt' do
+ before(:all) do
+ ohai_reader = Ohai::System.new
+ ohai_reader.all_plugins("platform")
+
+ new_node = Chef::Node.new
+ new_node.consume_external_attrs(ohai_reader.data,{})
+
+ events = Chef::EventDispatch::Dispatcher.new
+
+ @run_context = Chef::RunContext.new(new_node, {}, events)
+ end
+
+ let (:plaintext) { 'p@assword' }
+
+ it 'can be decrypted by powershell' do
+ encrypted = Chef::ReservedNames::Win32::Crypto.encrypt(plaintext)
+ resource = Chef::Resource::WindowsScript::PowershellScript.new("Powershell resource functional test", @run_context)
+ resource.code <<-EOF
+$encrypted = '#{encrypted}' | ConvertTo-SecureString
+$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($encrypted)
+$plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
+if ($plaintext -ne '#{plaintext}') {
+ Write-Error 'Got: ' $plaintext
+ exit 1
+}
+exit 0
+ EOF
+ resource.returns(0)
+ resource.run_action(:run)
+ end
+ end
+end
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/functional/win32/sid_spec.rb b/spec/functional/win32/sid_spec.rb
new file mode 100644
index 0000000000..1f5f66178a
--- /dev/null
+++ b/spec/functional/win32/sid_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Dan Bjorge (<dbjorge@gmail.com>)
+# Copyright:: Copyright (c) 2015 Dan Bjorge
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+if Chef::Platform.windows?
+ require 'chef/win32/security'
+end
+
+describe 'Chef::ReservedNames::Win32::SID', :windows_only do
+ if Chef::Platform.windows?
+ SID ||= Chef::ReservedNames::Win32::Security::SID
+ end
+
+ it 'should resolve default_security_object_group as a sane user group', :windows_not_domain_joined_only do
+ # Domain accounts: domain-specific Domain Users SID
+ # Microsoft Accounts: SID.current_user
+ # Else: SID.None
+ expect(SID.default_security_object_group).to eq(SID.None).or eq(SID.current_user)
+ end
+
+ context 'running as an elevated administrator user' do
+ it 'should resolve default_security_object_owner as the Administrators group' do
+ expect(SID.default_security_object_owner).to eq(SID.Administrators)
+ end
+ end
+
+ context 'running as a non-elevated administrator user' do
+ it 'should resolve default_security_object_owner as the current user' do
+ skip 'requires user support in mixlib-shellout, see security_spec.rb'
+ expect(SID.default_security_object_owner).to eq(SID.Administrators)
+ end
+ end
+
+ context 'running as a non-elevated, non-administrator user' do
+ it 'should resolve default_security_object_owner as the current user' do
+ skip 'requires user support in mixlib-shellout, see security_spec.rb'
+ expect(SID.default_security_object_owner).to eq(SID.current_user)
+ end
+ end
+end
diff --git a/spec/integration/client/client_spec.rb b/spec/integration/client/client_spec.rb
index 8afb52e29a..1a030c130b 100644
--- a/spec/integration/client/client_spec.rb
+++ b/spec/integration/client/client_spec.rb
@@ -3,34 +3,35 @@ require 'chef/mixin/shell_out'
require 'tiny_server'
require 'tmpdir'
-def recipes_filename
- File.join(CHEF_SPEC_DATA, 'recipes.tgz')
-end
-def start_tiny_server(server_opts={})
- recipes_size = File::Stat.new(recipes_filename).size
- @server = TinyServer::Manager.new(server_opts)
- @server.start
- @api = TinyServer::API.instance
- @api.clear
- #
- # trivial endpoints
- #
- # just a normal file
- # (expected_content should be uncompressed)
- @api.get("/recipes.tgz", 200) {
- File.open(recipes_filename, "rb") do |f|
- f.read
- end
- }
-end
+describe "chef-client" do
-def stop_tiny_server
- @server.stop
- @server = @api = nil
-end
+ def recipes_filename
+ File.join(CHEF_SPEC_DATA, 'recipes.tgz')
+ end
+
+ def start_tiny_server(server_opts={})
+ @server = TinyServer::Manager.new(server_opts)
+ @server.start
+ @api = TinyServer::API.instance
+ @api.clear
+ #
+ # trivial endpoints
+ #
+ # just a normal file
+ # (expected_content should be uncompressed)
+ @api.get("/recipes.tgz", 200) {
+ File.open(recipes_filename, "rb") do |f|
+ f.read
+ end
+ }
+ end
+
+ def stop_tiny_server
+ @server.stop
+ @server = @api = nil
+ end
-describe "chef-client" do
include IntegrationSupport
include Chef::Mixin::ShellOut
@@ -45,7 +46,9 @@ describe "chef-client" do
# machine that has omnibus chef installed. In that case we need to ensure
# we're running `chef-client` from the source tree and not the external one.
# cf. CHEF-4914
- let(:chef_client) { "ruby '#{chef_dir}/chef-client'" }
+ let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" }
+
+ let(:critical_env_vars) { %w(PATH RUBYOPT BUNDLE_GEMFILE GEM_PATH).map {|o| "#{o}=#{ENV[o]}"} .join(' ') }
when_the_repository "has a cookbook with a no-op recipe" do
before { file 'cookbooks/x/recipes/default.rb', '' }
@@ -56,7 +59,38 @@ local_mode true
cookbook_path "#{path_to('cookbooks')}"
EOM
- result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+ shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+ end
+
+ it "should complete successfully with no other environment variables", :skip => (Chef::Platform.windows?) do
+ file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+ begin
+ result = shell_out("env -i #{critical_env_vars} #{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+ result.error!
+ rescue
+ Chef::Log.info "Bare invocation will have the following load-path."
+ Chef::Log.info shell_out!("env -i #{critical_env_vars} ruby -e 'puts $:'").stdout
+ raise
+ end
+ end
+
+ it "should complete successfully with --no-listen" do
+ file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+
+ result = shell_out("#{chef_client} --no-listen -c \"#{path_to('config/client.rb')}\" -o 'x::default'", :cwd => chef_dir)
+ result.error!
+ end
+
+ it "should be able to node.save with bad utf8 characters in the node data" do
+ file "cookbooks/x/attributes/default.rb", 'default["badutf8"] = "Elan Ruusam\xE4e"'
+ result = shell_out("#{chef_client} -z -r 'x::default' --disable-config", :cwd => path_to(''))
result.error!
end
@@ -269,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
@@ -308,7 +395,8 @@ end
end
end
- context "when using recipe-url" do
+ # Fails on appveyor, but works locally on windows and on windows hosts in Ci.
+ context "when using recipe-url", :skip_appveyor do
before(:all) do
start_tiny_server
end
@@ -329,7 +417,7 @@ EOM
it 'should fail when passed --recipe-url and not passed -z' do
result = shell_out("#{chef_client} --recipe-url=http://localhost:9000/recipes.tgz", :cwd => tmp_dir)
- expect(result.exitstatus).to eq(1)
+ expect(result.exitstatus).not_to eq(0)
end
end
end
diff --git a/spec/integration/client/ipv6_spec.rb b/spec/integration/client/ipv6_spec.rb
index 76dd1938f7..8be873edf4 100644
--- a/spec/integration/client/ipv6_spec.rb
+++ b/spec/integration/client/ipv6_spec.rb
@@ -76,7 +76,7 @@ END_CLIENT_RB
let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
- let(:chef_client_cmd) { %Q[ruby '#{chef_dir}/chef-client' -c "#{path_to('config/client.rb')}" -lwarn] }
+ let(:chef_client_cmd) { %Q[ruby '#{chef_dir}/chef-client' --minimal-ohai -c "#{path_to('config/client.rb')}" -lwarn] }
after do
FileUtils.rm_rf(cache_path)
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/knife/common_options_spec.rb b/spec/integration/knife/common_options_spec.rb
index ec76738b6f..b2e2e3fc2a 100644
--- a/spec/integration/knife/common_options_spec.rb
+++ b/spec/integration/knife/common_options_spec.rb
@@ -39,7 +39,7 @@ describe 'knife common options', :workstation do
it 'knife raw /nodes/x should retrieve the node' do
knife('raw /nodes/x').should_succeed( /"name": "x"/ )
- expect(Chef::Config.chef_server_url).to eq('http://localhost:9999')
+ expect(Chef::Config.chef_server_url).to eq('chefzero://localhost:9999')
end
end
@@ -101,7 +101,7 @@ EOM
it 'knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node' do
knife('raw -z --chef-zero-port=9999 /nodes/x').should_succeed( /"name": "x"/ )
- expect(Chef::Config.chef_server_url).to eq('http://localhost:9999')
+ expect(Chef::Config.chef_server_url).to eq('chefzero://localhost:9999')
end
context 'when the default port (8889) is already bound' do
@@ -149,7 +149,7 @@ EOM
it 'knife raw -z --chef-zero-port=9999 /nodes/x retrieves the node' do
knife('raw -z --chef-zero-port=9999 /nodes/x').should_succeed( /"name": "x"/ )
- expect(Chef::Config.chef_server_url).to eq('http://localhost:9999')
+ expect(Chef::Config.chef_server_url).to eq('chefzero://localhost:9999')
end
end
end
diff --git a/spec/integration/knife/deps_spec.rb b/spec/integration/knife/deps_spec.rb
index 3120db4940..b7333cefda 100644
--- a/spec/integration/knife/deps_spec.rb
+++ b/spec/integration/knife/deps_spec.rb
@@ -216,22 +216,16 @@ depends "self"'
end
it 'knife deps prints each once' do
- knife('deps /cookbooks/foo /cookbooks/self').should_succeed <<EOM
-/cookbooks/baz
-/cookbooks/bar
-/cookbooks/foo
-/cookbooks/self
-EOM
+ knife('deps /cookbooks/foo /cookbooks/self').should_succeed(
+ stdout: "/cookbooks/baz\n/cookbooks/bar\n/cookbooks/foo\n/cookbooks/self\n",
+ stderr: "WARN: Ignoring self-dependency in cookbook self, please remove it (in the future this will be fatal).\n"
+ )
end
it 'knife deps --tree prints each once' do
- knife('deps --tree /cookbooks/foo /cookbooks/self').should_succeed <<EOM
-/cookbooks/foo
- /cookbooks/bar
- /cookbooks/baz
- /cookbooks/foo
-/cookbooks/self
- /cookbooks/self
-EOM
+ knife('deps --tree /cookbooks/foo /cookbooks/self').should_succeed(
+ stdout: "/cookbooks/foo\n /cookbooks/bar\n /cookbooks/baz\n /cookbooks/foo\n/cookbooks/self\n",
+ stderr: "WARN: Ignoring self-dependency in cookbook self, please remove it (in the future this will be fatal).\n"
+ )
end
end
when_the_repository 'has roles with circular dependencies' do
diff --git a/spec/integration/knife/upload_spec.rb b/spec/integration/knife/upload_spec.rb
index cef4f54e97..826ecec364 100644
--- a/spec/integration/knife/upload_spec.rb
+++ b/spec/integration/knife/upload_spec.rb
@@ -154,6 +154,24 @@ EOM
end
end
+ context 'when cookbook metadata has a self-dependency' do
+ before do
+ file 'cookbooks/x/metadata.rb', "name 'x'; version '1.0.0'; depends 'x'"
+ end
+
+ it "should warn", :chef_lt_13_only do
+ knife('upload /cookbooks').should_succeed(
+ stdout: "Updated /cookbooks/x\n",
+ stderr: "WARN: Ignoring self-dependency in cookbook x, please remove it (in the future this will be fatal).\n"
+ )
+ knife('diff --name-status /').should_succeed ''
+ end
+ it "should fail in Chef 13", :chef_gte_13_only do
+ knife('upload /cookbooks').should_fail ''
+ # FIXME: include the error message here
+ end
+ end
+
context 'as well as one extra copy of each thing' do
before do
file 'clients/y.json', { 'public_key' => ChefZero::PUBLIC_KEY }
diff --git a/spec/integration/recipes/lwrp_inline_resources_spec.rb b/spec/integration/recipes/lwrp_inline_resources_spec.rb
index a3baba8b0f..e70605d3d3 100644
--- a/spec/integration/recipes/lwrp_inline_resources_spec.rb
+++ b/spec/integration/recipes/lwrp_inline_resources_spec.rb
@@ -5,7 +5,7 @@ describe "LWRPs with inline resources" do
include IntegrationSupport
include Chef::Mixin::ShellOut
- let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
+ let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) }
# Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
# following constraints are satisfied:
@@ -16,7 +16,7 @@ describe "LWRPs with inline resources" do
# machine that has omnibus chef installed. In that case we need to ensure
# we're running `chef-client` from the source tree and not the external one.
# cf. CHEF-4914
- let(:chef_client) { "ruby '#{chef_dir}/chef-client'" }
+ let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" }
when_the_repository "has a cookbook with a nested LWRP" do
before do
diff --git a/spec/integration/recipes/lwrp_spec.rb b/spec/integration/recipes/lwrp_spec.rb
new file mode 100644
index 0000000000..7ecdfc7c3a
--- /dev/null
+++ b/spec/integration/recipes/lwrp_spec.rb
@@ -0,0 +1,53 @@
+require 'support/shared/integration/integration_helper'
+require 'chef/mixin/shell_out'
+
+describe "LWRPs" do
+ include IntegrationSupport
+ include Chef::Mixin::ShellOut
+
+ let(:chef_dir) { File.expand_path("../../../../bin", __FILE__) }
+
+ # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
+ # following constraints are satisfied:
+ # * Windows: windows can only run batch scripts as bare executables. Rubygems
+ # creates batch wrappers for installed gems, but we don't have batch wrappers
+ # in the source tree.
+ # * Other `chef-client` in PATH: A common case is running the tests on a
+ # machine that has omnibus chef installed. In that case we need to ensure
+ # we're running `chef-client` from the source tree and not the external one.
+ # cf. CHEF-4914
+ let(:chef_client) { "ruby '#{chef_dir}/chef-client' --minimal-ohai" }
+
+ when_the_repository "has a cookbook named l-w-r-p" do
+ before do
+ directory 'cookbooks/l-w-r-p' do
+
+ file 'resources/foo.rb', <<EOM
+default_action :create
+EOM
+ file 'providers/foo.rb', <<EOM
+action :create do
+end
+EOM
+
+ file 'recipes/default.rb', <<EOM
+l_w_r_p_foo "me"
+EOM
+
+ end # directory 'cookbooks/x'
+ end
+
+ it "should complete with success" do
+ file 'config/client.rb', <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+log_level :warn
+EOM
+
+ result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" --no-color -F doc -o 'l-w-r-p::default'", :cwd => chef_dir)
+ expect(result.stdout).to match(/\* l_w_r_p_foo\[me\] action create \(up to date\)/)
+ expect(result.stdout).not_to match(/WARN: You are overriding l_w_r_p_foo/)
+ result.error!
+ end
+ end
+end
diff --git a/spec/integration/recipes/provider_choice.rb b/spec/integration/recipes/provider_choice.rb
new file mode 100644
index 0000000000..01537b2c05
--- /dev/null
+++ b/spec/integration/recipes/provider_choice.rb
@@ -0,0 +1,36 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Recipe DSL methods" do
+ include IntegrationSupport
+
+ context "With resource class providing 'provider_thingy'" do
+ before :context do
+ class Chef::Resource::ProviderThingy < Chef::Resource
+ resource_name :provider_thingy
+ default_action :create
+ def to_s
+ "provider_thingy resource class"
+ end
+ end
+ end
+ context "And class Chef::Provider::ProviderThingy with no provides" do
+ before :context do
+ class Chef::Provider::ProviderThingy < Chef::Provider
+ def load_current_resource
+ end
+ def action_create
+ Chef::Log.warn("hello from #{self.class.name}")
+ end
+ end
+ end
+
+ it "provider_thingy 'blah' runs the provider and warns" do
+ recipe = converge {
+ provider_thingy 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to match /hello from Chef::Provider::ProviderThingy/
+ expect(recipe.logged_warnings).to match /you must use 'provides' to provide DSL/i
+ end
+ end
+ end
+end
diff --git a/spec/integration/recipes/recipe_dsl_spec.rb b/spec/integration/recipes/recipe_dsl_spec.rb
new file mode 100644
index 0000000000..52bca87c99
--- /dev/null
+++ b/spec/integration/recipes/recipe_dsl_spec.rb
@@ -0,0 +1,1492 @@
+require 'support/shared/integration/integration_helper'
+
+describe "Recipe DSL methods" do
+ include IntegrationSupport
+
+ module Namer
+ extend self
+ attr_accessor :current_index
+ end
+
+ before(:all) { Namer.current_index = 1 }
+ before { Namer.current_index += 1 }
+
+ context "with resource 'base_thingy' declared as BaseThingy" do
+ before(:context) {
+
+ class BaseThingy < Chef::Resource
+ resource_name 'base_thingy'
+ default_action :create
+
+ class<<self
+ attr_accessor :created_name
+ attr_accessor :created_resource
+ attr_accessor :created_provider
+ end
+
+ def provider
+ Provider
+ end
+ class Provider < Chef::Provider
+ def load_current_resource
+ end
+ def action_create
+ BaseThingy.created_name = new_resource.name
+ BaseThingy.created_resource = new_resource.class
+ BaseThingy.created_provider = self.class
+ end
+ end
+ end
+
+ # Modules to put stuff in
+ module RecipeDSLSpecNamespace; end
+ module RecipeDSLSpecNamespace::Bar; end
+
+ }
+
+ before :each do
+ BaseThingy.created_resource = nil
+ BaseThingy.created_provider = nil
+ end
+
+ it "creates base_thingy when you call base_thingy in a recipe" do
+ recipe = converge {
+ base_thingy 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_name).to eq 'blah'
+ expect(BaseThingy.created_resource).to eq BaseThingy
+ end
+
+ it "errors out when you call base_thingy do ... end in a recipe" do
+ expect_converge {
+ base_thingy do; end
+ }.to raise_error(ArgumentError, 'You must supply a name when declaring a base_thingy resource')
+ end
+
+ it "emits a warning when you call base_thingy 'foo', 'bar' do ... end in a recipe" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ recipe = converge {
+ base_thingy 'foo', 'bar' do
+ end
+ }
+ expect(recipe.logged_warnings).to match(/Cannot create resource base_thingy with more than one argument. All arguments except the name \("foo"\) will be ignored. This will cause an error in Chef 13. Arguments: \["foo", "bar"\]/)
+ expect(BaseThingy.created_name).to eq 'foo'
+ expect(BaseThingy.created_resource).to eq BaseThingy
+ end
+
+ context "Deprecated automatic resource DSL" do
+ before do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ context "with a resource 'backcompat_thingy' declared in Chef::Resource and Chef::Provider" do
+ before(:context) {
+
+ class Chef::Resource::BackcompatThingy < Chef::Resource
+ default_action :create
+ end
+ class Chef::Provider::BackcompatThingy < Chef::Provider
+ def load_current_resource
+ end
+ def action_create
+ BaseThingy.created_resource = new_resource.class
+ BaseThingy.created_provider = self.class
+ end
+ end
+
+ }
+
+ it "backcompat_thingy creates a Chef::Resource::BackcompatThingy" do
+ recipe = converge {
+ backcompat_thingy 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq Chef::Resource::BackcompatThingy
+ expect(BaseThingy.created_provider).to eq Chef::Provider::BackcompatThingy
+ end
+
+ context "and another resource 'backcompat_thingy' in BackcompatThingy with 'provides'" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::BackcompatThingy < BaseThingy
+ provides :backcompat_thingy
+ resource_name :backcompat_thingy
+ end
+
+ }
+
+ it "backcompat_thingy creates a BackcompatThingy" do
+ recipe = converge {
+ backcompat_thingy 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to match(/Class Chef::Provider::BackcompatThingy does not declare 'provides :backcompat_thingy'./)
+ expect(BaseThingy.created_resource).not_to be_nil
+ end
+ end
+ end
+
+ context "with a resource named RecipeDSLSpecNamespace::Bar::BarThingy" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Bar::BarThingy < BaseThingy
+ end
+
+ }
+
+ it "bar_thingy does not work" do
+ expect_converge {
+ bar_thingy 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+ end
+
+ context "with a resource named Chef::Resource::NoNameThingy with resource_name nil" do
+ before(:context) {
+
+ class Chef::Resource::NoNameThingy < BaseThingy
+ resource_name nil
+ end
+
+ }
+
+ it "no_name_thingy does not work" do
+ expect_converge {
+ no_name_thingy 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+ end
+
+ context "with a resource named AnotherNoNameThingy with resource_name :another_thingy_name" do
+ before(:context) {
+
+ class AnotherNoNameThingy < BaseThingy
+ resource_name :another_thingy_name
+ end
+
+ }
+
+ it "another_no_name_thingy does not work" do
+ expect_converge {
+ another_no_name_thingy 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "another_thingy_name works" do
+ recipe = converge {
+ another_thingy_name 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy)
+ end
+ end
+
+ context "with a resource named AnotherNoNameThingy2 with resource_name :another_thingy_name2; resource_name :another_thingy_name3" do
+ before(:context) {
+
+ class AnotherNoNameThingy2 < BaseThingy
+ resource_name :another_thingy_name2
+ resource_name :another_thingy_name3
+ end
+
+ }
+
+ it "another_no_name_thingy does not work" do
+ expect_converge {
+ another_no_name_thingy2 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "another_thingy_name2 does not work" do
+ expect_converge {
+ another_thingy_name2 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "yet_another_thingy_name3 works" do
+ recipe = converge {
+ another_thingy_name3 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq(AnotherNoNameThingy2)
+ end
+ end
+
+ context "provides overriding resource_name" do
+ context "with a resource named AnotherNoNameThingy3 with provides :another_no_name_thingy3, os: 'blarghle'" do
+ before(:context) {
+
+ class AnotherNoNameThingy3 < BaseThingy
+ resource_name :another_no_name_thingy_3
+ provides :another_no_name_thingy3, os: 'blarghle'
+ end
+
+ }
+
+ it "and os = linux, another_no_name_thingy3 does not work" do
+ expect_converge {
+ # TODO this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_no_name_thingy3 'blah' do; end
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ end
+
+ it "and os = blarghle, another_no_name_thingy3 works" do
+ recipe = converge {
+ # TODO this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ another_no_name_thingy3 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy3)
+ end
+ end
+
+ context "with a resource named AnotherNoNameThingy4 with two provides" do
+ before(:context) {
+
+ class AnotherNoNameThingy4 < BaseThingy
+ resource_name :another_no_name_thingy_4
+ provides :another_no_name_thingy4, os: 'blarghle'
+ provides :another_no_name_thingy4, platform_family: 'foo'
+ end
+
+ }
+
+ it "and os = linux, another_no_name_thingy4 does not work" do
+ expect_converge {
+ # TODO this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_no_name_thingy4 'blah' do; end
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ end
+
+ it "and os = blarghle, another_no_name_thingy4 works" do
+ recipe = converge {
+ # TODO this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ another_no_name_thingy4 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy4)
+ end
+
+ it "and platform_family = foo, another_no_name_thingy4 works" do
+ recipe = converge {
+ # TODO this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:platform_family] = 'foo'
+ another_no_name_thingy4 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy4)
+ end
+ end
+
+ context "with a resource named AnotherNoNameThingy5, a different resource_name, and a provides with the original resource_name" do
+ before(:context) {
+
+ class AnotherNoNameThingy5 < BaseThingy
+ resource_name :another_thingy_name_for_another_no_name_thingy5
+ provides :another_no_name_thingy5, os: 'blarghle'
+ end
+
+ }
+
+ it "and os = linux, another_no_name_thingy5 does not work" do
+ expect_converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_no_name_thingy5 'blah' do; end
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ end
+
+ it "and os = blarghle, another_no_name_thingy5 works" do
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ another_no_name_thingy5 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy5)
+ end
+
+ it "the new resource name can be used in a recipe" do
+ recipe = converge {
+ another_thingy_name_for_another_no_name_thingy5 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy5)
+ end
+ end
+
+ context "with a resource named AnotherNoNameThingy6, a provides with the original resource name, and a different resource_name" do
+ before(:context) {
+
+ class AnotherNoNameThingy6 < BaseThingy
+ provides :another_no_name_thingy6, os: 'blarghle'
+ resource_name :another_thingy_name_for_another_no_name_thingy6
+ end
+
+ }
+
+ it "and os = linux, another_no_name_thingy6 does not work" do
+ expect_converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_no_name_thingy6 'blah' do; end
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ end
+
+ it "and os = blarghle, another_no_name_thingy6 works" do
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ another_no_name_thingy6 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy6)
+ end
+
+ it "the new resource name can be used in a recipe" do
+ recipe = converge {
+ another_thingy_name_for_another_no_name_thingy6 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy6)
+ end
+ end
+
+ context "with a resource named AnotherNoNameThingy7, a new resource_name, and provides with that new resource name" do
+ before(:context) {
+
+ class AnotherNoNameThingy7 < BaseThingy
+ resource_name :another_thingy_name_for_another_no_name_thingy7
+ provides :another_thingy_name_for_another_no_name_thingy7, os: 'blarghle'
+ end
+
+ }
+
+ it "and os = linux, another_thingy_name_for_another_no_name_thingy7 does not work" do
+ expect_converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_thingy_name_for_another_no_name_thingy7 'blah' do; end
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ end
+
+ it "and os = blarghle, another_thingy_name_for_another_no_name_thingy7 works" do
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ another_thingy_name_for_another_no_name_thingy7 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy7)
+ end
+
+ it "the old resource name does not work" do
+ expect_converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_no_name_thingy_7 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+ end
+
+ # opposite order from the previous test (provides, then resource_name)
+ context "with a resource named AnotherNoNameThingy8, a provides with a new resource name, and resource_name with that new resource name" do
+ before(:context) {
+
+ class AnotherNoNameThingy8 < BaseThingy
+ provides :another_thingy_name_for_another_no_name_thingy8, os: 'blarghle'
+ resource_name :another_thingy_name_for_another_no_name_thingy8
+ end
+
+ }
+
+ it "and os = linux, another_thingy_name_for_another_no_name_thingy8 does not work" do
+ expect_converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_thingy_name_for_another_no_name_thingy8 'blah' do; end
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ end
+
+ it "and os = blarghle, another_thingy_name_for_another_no_name_thingy8 works" do
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ another_thingy_name_for_another_no_name_thingy8 'blah' do; end
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq (AnotherNoNameThingy8)
+ end
+
+ it "the old resource name does not work" do
+ expect_converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ another_thingy_name8 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+ end
+ end
+ end
+
+ context "provides" do
+ context "when MySupplier provides :hemlock" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::MySupplier < BaseThingy
+ resource_name :hemlock
+ end
+
+ }
+
+ it "my_supplier does not work in a recipe" do
+ expect_converge {
+ my_supplier 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "hemlock works in a recipe" do
+ expect_recipe {
+ hemlock 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::MySupplier
+ end
+ end
+
+ context "when Thingy3 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy3 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe" do
+ expect_recipe {
+ thingy3 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ context "and Thingy4 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+ recipe = converge {
+ thingy3 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ it "thingy4 does not work in a recipe" do
+ expect_converge {
+ thingy4 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "resource_matching_short_name returns Thingy4" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+ end
+ end
+
+ context "when Thingy5 has resource_name :thingy5 and provides :thingy5reverse, :thingy5_2 and :thingy5_2reverse" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy5 < BaseThingy
+ resource_name :thingy5
+ provides :thingy5reverse
+ provides :thingy5_2
+ provides :thingy5_2reverse
+ end
+
+ }
+
+ it "thingy5 works in a recipe" do
+ expect_recipe {
+ thingy5 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ end
+
+ context "and Thingy6 provides :thingy5" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy6 < BaseThingy
+ resource_name :thingy6
+ provides :thingy5
+ end
+
+ }
+
+ it "thingy6 works in a recipe and yields Thingy6" do
+ recipe = converge {
+ thingy6 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy6
+ end
+
+ it "thingy5 works in a recipe and yields Foo::Thingy5 (the alphabetical one)" do
+ recipe = converge {
+ thingy5 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ end
+
+ it "resource_matching_short_name returns Thingy5" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy5)).to eq RecipeDSLSpecNamespace::Thingy5
+ end
+
+ context "and AThingy5 provides :thingy5reverse" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::AThingy5 < BaseThingy
+ resource_name :thingy5reverse
+ end
+
+ }
+
+ it "thingy5reverse works in a recipe and yields AThingy5 (the alphabetical one)" do
+ recipe = converge {
+ thingy5reverse 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::AThingy5
+ end
+ end
+
+ context "and ZRecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+ before(:context) {
+
+ module ZRecipeDSLSpecNamespace
+ class Thingy5 < BaseThingy
+ resource_name :thingy5_2
+ end
+ end
+
+ }
+
+ it "thingy5_2 works in a recipe and yields the RecipeDSLSpaceNamespace one (the alphabetical one)" do
+ recipe = converge {
+ thingy5_2 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy5
+ end
+ end
+
+ context "and ARecipeDSLSpecNamespace::Thingy5 provides :thingy5_2" do
+ before(:context) {
+
+ module ARecipeDSLSpecNamespace
+ class Thingy5 < BaseThingy
+ resource_name :thingy5_2reverse
+ end
+ end
+
+ }
+
+ it "thingy5_2reverse works in a recipe and yields the ARecipeDSLSpaceNamespace one (the alphabetical one)" do
+ recipe = converge {
+ thingy5_2reverse 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq ARecipeDSLSpecNamespace::Thingy5
+ end
+ end
+ end
+
+ context "when Thingy3 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy3 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe" do
+ expect_recipe {
+ thingy3 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ context "and Thingy4 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+ recipe = converge {
+ thingy3 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ it "thingy4 does not work in a recipe" do
+ expect_converge {
+ thingy4 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "resource_matching_short_name returns Thingy4" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+ end
+
+ context "and Thingy4 has resource_name :thingy3" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy4 < BaseThingy
+ resource_name :thingy3
+ end
+
+ }
+
+ it "thingy3 works in a recipe and yields Thingy3 (the alphabetical one)" do
+ recipe = converge {
+ thingy3 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+
+ it "thingy4 does not work in a recipe" do
+ expect_converge {
+ thingy4 'blah' do; end
+ }.to raise_error(NoMethodError)
+ end
+
+ it "resource_matching_short_name returns Thingy4" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy3)).to eq RecipeDSLSpecNamespace::Thingy3
+ end
+ end
+ end
+
+ end
+
+ context "when Thingy7 provides :thingy8" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy7 < BaseThingy
+ resource_name :thingy7
+ provides :thingy8
+ end
+
+ }
+
+ context "and Thingy8 has resource_name :thingy8" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy8 < BaseThingy
+ resource_name :thingy8
+ end
+
+ }
+
+ it "thingy7 works in a recipe and yields Thingy7" do
+ recipe = converge {
+ thingy7 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
+ end
+
+ it "thingy8 works in a recipe and yields Thingy7 (alphabetical)" do
+ recipe = converge {
+ thingy8 'blah' do; end
+ }
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy7
+ end
+
+ it "resource_matching_short_name returns Thingy8" do
+ expect(Chef::Resource.resource_matching_short_name(:thingy8)).to eq RecipeDSLSpecNamespace::Thingy8
+ end
+ end
+ end
+
+ context "when Thingy12 provides :thingy12, :twizzle and :twizzle2" do
+ before(:context) {
+
+ class RecipeDSLSpecNamespace::Thingy12 < BaseThingy
+ resource_name :thingy12
+ provides :twizzle
+ provides :twizzle2
+ end
+
+ }
+
+ it "thingy12 works in a recipe and yields Thingy12" do
+ expect_recipe {
+ thingy12 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
+ end
+
+ it "twizzle works in a recipe and yields Thingy12" do
+ expect_recipe {
+ twizzle 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
+ end
+
+ it "twizzle2 works in a recipe and yields Thingy12" do
+ expect_recipe {
+ twizzle2 'blah' do; end
+ }.to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq RecipeDSLSpecNamespace::Thingy12
+ end
+ end
+
+ context "with platform-specific resources 'my_super_thingy_foo' and 'my_super_thingy_bar'" do
+ before(:context) {
+ class MySuperThingyFoo < BaseThingy
+ resource_name :my_super_thingy_foo
+ provides :my_super_thingy, platform: 'foo'
+ end
+
+ class MySuperThingyBar < BaseThingy
+ resource_name :my_super_thingy_bar
+ provides :my_super_thingy, platform: 'bar'
+ end
+ }
+
+ it "A run with platform 'foo' uses MySuperThingyFoo" do
+ r = Cheffish::ChefRun.new(chef_config)
+ r.client.run_context.node.automatic['platform'] = 'foo'
+ r.compile_recipe {
+ my_super_thingy 'blah' do; end
+ }
+ r.converge
+ expect(r).to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq MySuperThingyFoo
+ end
+
+ it "A run with platform 'bar' uses MySuperThingyBar" do
+ r = Cheffish::ChefRun.new(chef_config)
+ r.client.run_context.node.automatic['platform'] = 'bar'
+ r.compile_recipe {
+ my_super_thingy 'blah' do; end
+ }
+ r.converge
+ expect(r).to emit_no_warnings_or_errors
+ expect(BaseThingy.created_resource).to eq MySuperThingyBar
+ end
+
+ it "A run with platform 'x' reports that my_super_thingy is not supported" do
+ r = Cheffish::ChefRun.new(chef_config)
+ r.client.run_context.node.automatic['platform'] = 'x'
+ expect {
+ r.compile_recipe {
+ my_super_thingy 'blah' do; end
+ }
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ end
+ end
+
+ context "when Thingy10 provides :thingy10" do
+ before(:context) {
+ class RecipeDSLSpecNamespace::Thingy10 < BaseThingy
+ resource_name :thingy10
+ end
+ }
+
+ it "declaring a resource providing the same :thingy10 with override: true does not produce a warning" do
+ expect(Chef::Log).not_to receive(:warn)
+ class RecipeDSLSpecNamespace::Thingy10AlternateProvider < BaseThingy
+ provides :thingy10, override: true
+ end
+ end
+ end
+
+ context "when Thingy11 provides :thingy11" do
+ before(:context) {
+ class RecipeDSLSpecNamespace::Thingy11 < BaseThingy
+ resource_name :thingy10
+ end
+ }
+
+ it "declaring a resource providing the same :thingy11 with os: 'linux' does not produce a warning" do
+ expect(Chef::Log).not_to receive(:warn)
+ class RecipeDSLSpecNamespace::Thingy11AlternateProvider < BaseThingy
+ provides :thingy11, os: 'linux'
+ end
+ end
+ end
+ end
+
+ context "with a resource named 'B' with resource name :two_classes_one_dsl" do
+ let(:two_classes_one_dsl) { :"two_classes_one_dsl#{Namer.current_index}" }
+ let(:resource_class) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "B"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result
+ }
+ before { resource_class } # pull on it so it gets defined before the recipe runs
+
+ context "and another resource named 'A' with resource_name :two_classes_one_dsl" do
+ let(:resource_class_a) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "A"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result
+ }
+ before { resource_class_a } # pull on it so it gets defined before the recipe runs
+
+ it "two_classes_one_dsl resolves to A (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_a
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class_a
+ end
+ end
+
+ context "and another resource named 'Z' with resource_name :two_classes_one_dsl" do
+ let(:resource_class_z) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "Z"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result
+ }
+ before { resource_class_z } # pull on it so it gets defined before the recipe runs
+
+ it "two_classes_one_dsl resolves to B (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+
+ context "and a priority array [ Z, B ]" do
+ before do
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z, resource_class ])
+ end
+
+ it "two_classes_one_dsl resolves to Z (respects the priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_z
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+
+ context "when Z provides(:two_classes_one_dsl) { false }" do
+ before do
+ resource_class_z.provides(two_classes_one_dsl) { false }
+ end
+
+ it "two_classes_one_dsl resolves to B (picks the next thing in the priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+ end
+ end
+
+ context "and priority arrays [ B ] and [ Z ]" do
+ before do
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class ])
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ])
+ end
+
+ it "two_classes_one_dsl resolves to Z (respects the most recent priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_z
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+
+ context "when Z provides(:two_classes_one_dsl) { false }" do
+ before do
+ resource_class_z.provides(two_classes_one_dsl) { false }
+ end
+
+ it "two_classes_one_dsl resolves to B (picks the first match from the other priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+ end
+ end
+
+ context "and a priority array [ Z ]" do
+ before do
+ Chef.set_resource_priority_array(two_classes_one_dsl, [ resource_class_z ])
+ end
+
+ context "when Z provides(:two_classes_one_dsl) { false }" do
+ before do
+ resource_class_z.provides(two_classes_one_dsl) { false }
+ end
+
+ it "two_classes_one_dsl resolves to B (picks the first match outside the priority array)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+
+ it "resource_matching_short_name returns B" do
+ expect(Chef::Resource.resource_matching_short_name(two_classes_one_dsl)).to eq resource_class
+ end
+ end
+ end
+
+ end
+
+ context "and a provider named 'B' which provides :two_classes_one_dsl" do
+ before do
+ resource_class.send(:define_method, :provider) { nil }
+ end
+
+ let(:provider_class) {
+ result = Class.new(BaseThingy::Provider) do
+ def self.name
+ "B"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.provides two_classes_one_dsl
+ result
+ }
+ before { provider_class } # pull on it so it gets defined before the recipe runs
+
+ context "and another provider named 'A'" do
+ let(:provider_class_a) {
+ result = Class.new(BaseThingy::Provider) do
+ def self.name
+ "A"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result
+ }
+ context "which provides :two_classes_one_dsl" do
+ before { provider_class_a.provides two_classes_one_dsl }
+
+ it "two_classes_one_dsl resolves to A (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class_a
+ end
+ end
+ context "which provides(:two_classes_one_dsl) { false }" do
+ before { provider_class_a.provides(two_classes_one_dsl) { false } }
+
+ it "two_classes_one_dsl resolves to B (since A declined)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+ end
+
+ context "and another provider named 'Z'" do
+ let(:provider_class_z) {
+ result = Class.new(BaseThingy::Provider) do
+ def self.name
+ "Z"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result
+ }
+ before { provider_class_z } # pull on it so it gets defined before the recipe runs
+
+ context "which provides :two_classes_one_dsl" do
+ before { provider_class_z.provides two_classes_one_dsl }
+
+ it "two_classes_one_dsl resolves to B (alphabetically earliest)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+
+ context "with a priority array [ Z, B ]" do
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] }
+
+ it "two_classes_one_dsl resolves to Z (respects the priority map)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class_z
+ end
+ end
+ end
+
+ context "which provides(:two_classes_one_dsl) { false }" do
+ before { provider_class_z.provides(two_classes_one_dsl) { false } }
+
+ context "with a priority array [ Z, B ]" do
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z, provider_class ] }
+
+ it "two_classes_one_dsl resolves to B (the next one in the priority map)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+
+ context "with priority arrays [ B ] and [ Z ]" do
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class_z ] }
+ before { Chef.set_provider_priority_array two_classes_one_dsl, [ provider_class ] }
+
+ it "two_classes_one_dsl resolves to B (the one in the next priority map)" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ instance_eval("#{two_classes_one_dsl} 'blah'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+ end
+ end
+ end
+
+ context "and another resource Blarghle with provides :two_classes_one_dsl, os: 'blarghle'" do
+ let(:resource_class_blarghle) {
+ result = Class.new(BaseThingy) do
+ def self.name
+ "Blarghle"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ end
+ result.resource_name two_classes_one_dsl
+ result.provides two_classes_one_dsl, os: 'blarghle'
+ result
+ }
+ before { resource_class_blarghle } # pull on it so it gets defined before the recipe runs
+
+ it "on os = blarghle, two_classes_one_dsl resolves to Blarghle" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'blarghle'
+ instance_eval("#{two_classes_one_dsl} 'blah' do; end")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class_blarghle
+ end
+
+ it "on os = linux, two_classes_one_dsl resolves to B" do
+ two_classes_one_dsl = self.two_classes_one_dsl
+ recipe = converge {
+ # this is an ugly way to test, make Cheffish expose node attrs
+ run_context.node.automatic[:os] = 'linux'
+ instance_eval("#{two_classes_one_dsl} 'blah' do; end")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ end
+ end
+ end
+
+ context "with a resource MyResource" do
+ let(:resource_class) { Class.new(BaseThingy) do
+ def self.called_provides
+ @called_provides
+ end
+ def to_s
+ "MyResource"
+ end
+ end }
+ let(:my_resource) { :"my_resource#{Namer.current_index}" }
+ let(:blarghle_blarghle_little_star) { :"blarghle_blarghle_little_star#{Namer.current_index}" }
+
+ context "with resource_name :my_resource" do
+ before {
+ resource_class.resource_name my_resource
+ }
+
+ context "with provides? returning true to my_resource" do
+ before {
+ my_resource = self.my_resource
+ resource_class.define_singleton_method(:provides?) do |node, resource_name|
+ @called_provides = true
+ resource_name == my_resource
+ end
+ }
+
+ it "my_resource returns the resource and calls provides?, but does not emit a warning" do
+ dsl_name = self.my_resource
+ recipe = converge {
+ instance_eval("#{dsl_name} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_resource).to eq resource_class
+ expect(resource_class.called_provides).to be_truthy
+ end
+ end
+
+ context "with provides? returning true to blarghle_blarghle_little_star and not resource_name" do
+ before do
+ blarghle_blarghle_little_star = self.blarghle_blarghle_little_star
+ resource_class.define_singleton_method(:provides?) do |node, resource_name|
+ @called_provides = true
+ resource_name == blarghle_blarghle_little_star
+ end
+ end
+
+ it "my_resource does not return the resource" do
+ dsl_name = self.my_resource
+ expect_converge {
+ instance_eval("#{dsl_name} 'foo'")
+ }.to raise_error(Chef::Exceptions::NoSuchResourceType)
+ expect(resource_class.called_provides).to be_truthy
+ end
+
+ it "blarghle_blarghle_little_star 'foo' returns the resource and emits a warning" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ dsl_name = self.blarghle_blarghle_little_star
+ recipe = converge {
+ instance_eval("#{dsl_name} 'foo'")
+ }
+ expect(recipe.logged_warnings).to include "WARN: #{resource_class}.provides? returned true when asked if it provides DSL #{dsl_name}, but provides :#{dsl_name} was never called!"
+ expect(BaseThingy.created_resource).to eq resource_class
+ expect(resource_class.called_provides).to be_truthy
+ end
+ end
+
+ context "and a provider" do
+ let(:provider_class) do
+ Class.new(BaseThingy::Provider) do
+ def self.name
+ "MyProvider"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ def self.called_provides
+ @called_provides
+ end
+ end
+ end
+
+ before do
+ resource_class.send(:define_method, :provider) { nil }
+ end
+
+ context "that provides :my_resource" do
+ before do
+ provider_class.provides my_resource
+ end
+
+ context "with supports? returning true" do
+ before do
+ provider_class.define_singleton_method(:supports?) { |resource,action| true }
+ end
+
+ it "my_resource runs the provider and does not emit a warning" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+
+ context "and another provider supporting :my_resource with supports? false" do
+ let(:provider_class2) do
+ Class.new(BaseThingy::Provider) do
+ def self.name
+ "MyProvider2"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ def self.called_provides
+ @called_provides
+ end
+ provides my_resource
+ def self.supports?(resource, action)
+ false
+ end
+ end
+ end
+
+ it "my_resource runs the first provider" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+ end
+ end
+
+ context "with supports? returning false" do
+ before do
+ provider_class.define_singleton_method(:supports?) { |resource,action| false }
+ end
+
+ # TODO no warning? ick
+ it "my_resource runs the provider anyway" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ end
+
+ context "and another provider supporting :my_resource with supports? true" do
+ let(:provider_class2) do
+ my_resource = self.my_resource
+ Class.new(BaseThingy::Provider) do
+ def self.name
+ "MyProvider2"
+ end
+ def self.to_s; name; end
+ def self.inspect; name.inspect; end
+ def self.called_provides
+ @called_provides
+ end
+ provides my_resource
+ def self.supports?(resource, action)
+ true
+ end
+ end
+ end
+ before { provider_class2 } # make sure the provider class shows up
+
+ it "my_resource runs the other provider" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class2
+ end
+ end
+ end
+ end
+
+ context "with provides? returning true" do
+ before {
+ my_resource = self.my_resource
+ provider_class.define_singleton_method(:provides?) do |node, resource|
+ @called_provides = true
+ resource.declared_type == my_resource
+ end
+ }
+
+ context "that provides :my_resource" do
+ before {
+ provider_class.provides my_resource
+ }
+
+ it "my_resource calls the provider (and calls provides?), but does not emit a warning" do
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to eq ''
+ expect(BaseThingy.created_provider).to eq provider_class
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+
+ context "that does not call provides :my_resource" do
+ it "my_resource calls the provider (and calls provides?), and emits a warning" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ my_resource = self.my_resource
+ recipe = converge {
+ instance_eval("#{my_resource} 'foo'")
+ }
+ expect(recipe.logged_warnings).to include("WARN: #{provider_class}.provides? returned true when asked if it provides DSL #{my_resource}, but provides :#{my_resource} was never called!")
+ expect(BaseThingy.created_provider).to eq provider_class
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+ end
+
+ context "with provides? returning false to my_resource" do
+ before {
+ my_resource = self.my_resource
+ provider_class.define_singleton_method(:provides?) do |node, resource|
+ @called_provides = true
+ false
+ end
+ }
+
+ context "that provides :my_resource" do
+ before {
+ provider_class.provides my_resource
+ }
+
+ it "my_resource fails to find a provider (and calls provides)" do
+ my_resource = self.my_resource
+ expect_converge {
+ instance_eval("#{my_resource} 'foo'")
+ }.to raise_error(Chef::Exceptions::ProviderNotFound)
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+
+ context "that does not provide :my_resource" do
+ it "my_resource fails to find a provider (and calls provides)" do
+ my_resource = self.my_resource
+ expect_converge {
+ instance_eval("#{my_resource} 'foo'")
+ }.to raise_error(Chef::Exceptions::ProviderNotFound)
+ expect(provider_class.called_provides).to be_truthy
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ before(:all) { Namer.current_index = 0 }
+ before { Namer.current_index += 1 }
+
+ context "with an LWRP that declares actions" do
+ let(:resource_class) {
+ Class.new(Chef::Resource::LWRPBase) do
+ provides :"recipe_dsl_spec#{Namer.current_index}"
+ actions :create
+ end
+ }
+ let(:resource) {
+ resource_class.new("blah", run_context)
+ }
+ it "The actions are part of actions along with :nothing" do
+ expect(resource_class.actions).to eq [ :nothing, :create ]
+ end
+ it "The actions are part of allowed_actions along with :nothing" do
+ expect(resource.allowed_actions).to eq [ :nothing, :create ]
+ end
+
+ context "and a subclass that declares more actions" do
+ let(:subresource_class) {
+ Class.new(Chef::Resource::LWRPBase) do
+ provides :"recipe_dsl_spec_sub#{Namer.current_index}"
+ actions :delete
+ end
+ }
+ let(:subresource) {
+ subresource_class.new("subblah", run_context)
+ }
+
+ it "The parent class actions are not part of actions" do
+ expect(subresource_class.actions).to eq [ :nothing, :delete ]
+ end
+ it "The parent class actions are not part of allowed_actions" do
+ expect(subresource.allowed_actions).to eq [ :nothing, :delete ]
+ end
+ it "The parent class actions do not change" do
+ expect(resource_class.actions).to eq [ :nothing, :create ]
+ expect(resource.allowed_actions).to eq [ :nothing, :create ]
+ end
+ end
+ end
+
+ context "with a dynamically defined resource and regular provider" do
+ before(:context) do
+ Class.new(Chef::Resource) do
+ resource_name :lw_resource_with_hw_provider_test_case
+ default_action :create
+ attr_accessor :created_provider
+ end
+ class Chef::Provider::LwResourceWithHwProviderTestCase < Chef::Provider
+ def load_current_resource
+ end
+ def action_create
+ new_resource.created_provider = self.class
+ end
+ end
+ end
+
+ it "looks up the provider in Chef::Provider converting the resource name from snake case to camel case" do
+ resource = nil
+ recipe = converge {
+ resource = lw_resource_with_hw_provider_test_case 'blah' do; end
+ }
+ expect(resource.created_provider).to eq(Chef::Provider::LwResourceWithHwProviderTestCase)
+ end
+ end
+end
diff --git a/spec/integration/recipes/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/integration/solo/solo_spec.rb b/spec/integration/solo/solo_spec.rb
index 41f5f5506f..f45933c799 100644
--- a/spec/integration/solo/solo_spec.rb
+++ b/spec/integration/solo/solo_spec.rb
@@ -15,6 +15,8 @@ describe "chef-solo" do
let(:cookbook_ancient_100_metadata_rb) { cb_metadata("ancient", "1.0.0") }
+ let(:chef_solo) { "ruby bin/chef-solo --minimal-ohai" }
+
when_the_repository "has a cookbook with a basic recipe" do
before do
file 'cookbooks/x/metadata.rb', cookbook_x_100_metadata_rb
@@ -26,7 +28,7 @@ describe "chef-solo" do
cookbook_path "#{path_to('cookbooks')}"
file_cache_path "#{path_to('config/cache')}"
EOM
- result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
+ result = shell_out("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
result.error!
expect(result.stdout).to include("ITWORKS")
end
@@ -41,7 +43,7 @@ EOM
{"run_list":["x::default"]}
E
- result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -j '#{path_to('config/node.json')}' -l debug", :cwd => chef_dir)
+ result = shell_out("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -j '#{path_to('config/node.json')}' -l debug", :cwd => chef_dir)
result.error!
expect(result.stdout).to include("ITWORKS")
end
@@ -62,7 +64,7 @@ E
cookbook_path "#{path_to('cookbooks')}"
file_cache_path "#{path_to('config/cache')}"
EOM
- result = shell_out("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
+ result = shell_out("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -o 'x::default' -l debug", :cwd => chef_dir)
expect(result.exitstatus).to eq(0) # For CHEF-5120 this becomes 1
expect(result.stdout).to include("WARN: MissingCookbookDependency")
end
@@ -95,14 +97,14 @@ EOM
chef_dir = File.join(File.dirname(__FILE__), "..", "..", "..")
# Instantiate the first chef-solo run
- s1 = Process.spawn("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' \
+ s1 = Process.spawn("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -o 'x::default' \
-l debug -L #{path_to('logs/runs.log')}", :chdir => chef_dir)
# Give it some time to progress
sleep 1
# Instantiate the second chef-solo run
- s2 = Process.spawn("ruby bin/chef-solo -c \"#{path_to('config/solo.rb')}\" -o 'x::default' \
+ s2 = Process.spawn("#{chef_solo} -c \"#{path_to('config/solo.rb')}\" -o 'x::default' \
-l debug -L #{path_to('logs/runs.log')}", :chdir => chef_dir)
Process.waitpid(s1)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8888efc424..aadf55f64b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -54,6 +54,9 @@ Dir['lib/chef/knife/**/*.rb'].
map {|f| f.gsub(%r[\.rb$], '') }.
each {|f| require f }
+require 'chef/resource_resolver'
+require 'chef/provider_resolver'
+
require 'chef/mixins'
require 'chef/dsl'
require 'chef/application'
@@ -84,12 +87,14 @@ Dir["spec/support/**/*.rb"].
OHAI_SYSTEM = Ohai::System.new
OHAI_SYSTEM.all_plugins("platform")
-TEST_PLATFORM =
- (OHAI_SYSTEM['platform'] ||
- 'unknown_test_platform').dup.freeze
-TEST_PLATFORM_VERSION =
- (OHAI_SYSTEM['platform_version'] ||
- 'unknown_platform_version').dup.freeze
+test_node = Chef::Node.new
+test_node.automatic['os'] = (OHAI_SYSTEM['os'] || 'unknown_os').dup.freeze
+test_node.automatic['platform_family'] = (OHAI_SYSTEM['platform_family'] || 'unknown_platform_family').dup.freeze
+test_node.automatic['platform'] = (OHAI_SYSTEM['platform'] || 'unknown_platform').dup.freeze
+test_node.automatic['platform_version'] = (OHAI_SYSTEM['platform_version'] || 'unknown_platform_version').dup.freeze
+TEST_NODE = test_node.freeze
+TEST_PLATFORM = TEST_NODE['platform']
+TEST_PLATFORM_VERSION = TEST_NODE['platform_version']
RSpec.configure do |config|
config.include(Matchers)
@@ -110,19 +115,27 @@ RSpec.configure do |config|
# Tests that randomly fail, but may have value.
config.filter_run_excluding :volatile => true
config.filter_run_excluding :volatile_on_solaris => true if solaris?
+ 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"]
- # Add jruby filters here
config.filter_run_excluding :windows_only => true unless windows?
config.filter_run_excluding :not_supported_on_mac_osx_106 => true if mac_osx_106?
+ config.filter_run_excluding :not_supported_on_mac_osx=> true if mac_osx?
+ config.filter_run_excluding :mac_osx_only=> true if !mac_osx?
config.filter_run_excluding :not_supported_on_win2k3 => true if windows_win2k3?
config.filter_run_excluding :not_supported_on_solaris => true if solaris?
config.filter_run_excluding :win2k3_only => true unless windows_win2k3?
config.filter_run_excluding :windows_2008r2_or_later => true unless windows_2008r2_or_later?
config.filter_run_excluding :windows64_only => true unless windows64?
config.filter_run_excluding :windows32_only => true unless windows32?
+ config.filter_run_excluding :ruby64_only => true unless ruby_64bit?
+ config.filter_run_excluding :ruby32_only => true unless ruby_32bit?
config.filter_run_excluding :windows_powershell_dsc_only => true unless windows_powershell_dsc?
config.filter_run_excluding :windows_powershell_no_dsc_only => true unless ! windows_powershell_dsc?
config.filter_run_excluding :windows_domain_joined_only => true unless windows_domain_joined?
+ config.filter_run_excluding :windows_not_domain_joined_only => true if windows_domain_joined?
config.filter_run_excluding :solaris_only => true unless solaris?
config.filter_run_excluding :system_windows_service_gem_only => true unless system_windows_service_gem?
config.filter_run_excluding :unix_only => true unless unix?
@@ -143,7 +156,7 @@ RSpec.configure do |config|
config.filter_run_excluding :aes_256_gcm_only => true unless aes_256_gcm?
config.filter_run_excluding :broken => true
- running_platform_arch = `uname -m`.strip
+ running_platform_arch = `uname -m`.strip unless windows?
config.filter_run_excluding :arch => lambda {|target_arch|
running_platform_arch != target_arch
@@ -154,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
}
@@ -168,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/key_helpers.rb b/spec/support/key_helpers.rb
new file mode 100644
index 0000000000..076f709380
--- /dev/null
+++ b/spec/support/key_helpers.rb
@@ -0,0 +1,104 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+shared_examples_for "a knife key command" do
+ let(:stderr) { StringIO.new }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "before apply_params! is called" do
+ context "when apply_params! is called with invalid args (missing actor)" do
+ let(:params) { [] }
+ it "shows the usage" do
+ expect(command).to receive(:show_usage)
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+
+ it "outputs the proper error" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ expect(stderr.string).to include(command.actor_missing_error)
+ end
+
+ it "exits 1" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+ end
+ end # before apply_params! is called
+
+ context "after apply_params! is called with valid args" do
+ before do
+ command.apply_params!(params)
+ end
+
+ it "properly defines the actor" do
+ expect(command.actor).to eq("charmander")
+ end
+ end # after apply_params! is called with valid args
+
+ context "when the command is run" do
+ before do
+ allow(command).to receive(:service_object).and_return(service_object)
+ allow(command).to receive(:name_args).and_return(["charmander"])
+ end
+
+ context "when the command is successful" do
+ before do
+ expect(service_object).to receive(:run)
+ end
+ end
+ end
+end # a knife key command
+
+shared_examples_for "a knife key command with a keyname as the second arg" do
+ let(:stderr) { StringIO.new }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "before apply_params! is called" do
+ context "when apply_params! is called with invalid args (missing keyname)" do
+ let(:params) { ["charmander"] }
+ it "shows the usage" do
+ expect(command).to receive(:show_usage)
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+
+ it "outputs the proper error" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ expect(stderr.string).to include(command.keyname_missing_error)
+ end
+
+ it "exits 1" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+ end
+ end # before apply_params! is called
+end
diff --git a/spec/support/lib/chef/provider/openldap_includer.rb b/spec/support/lib/chef/provider/openldap_includer.rb
new file mode 100644
index 0000000000..afb0c7cf01
--- /dev/null
+++ b/spec/support/lib/chef/provider/openldap_includer.rb
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+class Chef
+ class Provider
+ class OpenldapIncluder < Chef::Provider::LWRPBase
+ provides :openldap_includer
+
+ def action_run
+ include_recipe "openldap::default"
+ end
+ end
+ end
+end
diff --git a/spec/support/lib/chef/resource/cat.rb b/spec/support/lib/chef/resource/cat.rb
index ecca50cb53..efc78aa59c 100644
--- a/spec/support/lib/chef/resource/cat.rb
+++ b/spec/support/lib/chef/resource/cat.rb
@@ -23,7 +23,6 @@ class Chef
attr_accessor :action
def initialize(name, run_context=nil)
- @resource_name = :cat
super
@action = "sell"
end
diff --git a/spec/support/lib/chef/resource/one_two_three_four.rb b/spec/support/lib/chef/resource/one_two_three_four.rb
index 296d2cd970..8f273a0cda 100644
--- a/spec/support/lib/chef/resource/one_two_three_four.rb
+++ b/spec/support/lib/chef/resource/one_two_three_four.rb
@@ -19,12 +19,8 @@
class Chef
class Resource
class OneTwoThreeFour < Chef::Resource
- attr_reader :i_can_count
- def initialize(name, run_context)
- @resource_name = :one_two_three_four
- super
- end
+ attr_reader :i_can_count
def i_can_count(tf)
@i_can_count = tf
diff --git a/spec/support/lib/chef/resource/openldap_includer.rb b/spec/support/lib/chef/resource/openldap_includer.rb
new file mode 100644
index 0000000000..6f443b4c7c
--- /dev/null
+++ b/spec/support/lib/chef/resource/openldap_includer.rb
@@ -0,0 +1,27 @@
+#
+# Author:: Adam Jacob (<adam@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.
+#
+
+
+class Chef
+ class Resource
+ class OpenldapIncluder < Chef::Resource::LWRPBase
+ allowed_actions :run
+ default_action :run
+ end
+ end
+end
diff --git a/spec/support/lib/chef/resource/with_state.rb b/spec/support/lib/chef/resource/with_state.rb
index 226de0a6d2..773ae7ddb8 100644
--- a/spec/support/lib/chef/resource/with_state.rb
+++ b/spec/support/lib/chef/resource/with_state.rb
@@ -23,15 +23,6 @@ class Chef
class Resource
class WithState < Chef::Resource
attr_accessor :state
-
- def initialize(name, run_context=nil)
- @resource_name = :with_state
- super
- end
-
- def state
- @state
- end
end
end
end
diff --git a/spec/support/lib/chef/resource/zen_follower.rb b/spec/support/lib/chef/resource/zen_follower.rb
index ddc289e48d..155e6ae729 100644
--- a/spec/support/lib/chef/resource/zen_follower.rb
+++ b/spec/support/lib/chef/resource/zen_follower.rb
@@ -24,11 +24,6 @@ class Chef
provides :follower, platform: "zen"
- def initialize(name, run_context=nil)
- @resource_name = :zen_follower
- super
- end
-
def master(arg=nil)
if !arg.nil?
@master = arg
diff --git a/spec/support/lib/chef/resource/zen_master.rb b/spec/support/lib/chef/resource/zen_master.rb
index d47d174e28..4106549d79 100644
--- a/spec/support/lib/chef/resource/zen_master.rb
+++ b/spec/support/lib/chef/resource/zen_master.rb
@@ -22,13 +22,10 @@ require 'chef/json_compat'
class Chef
class Resource
class ZenMaster < Chef::Resource
+ allowed_actions :win, :score
+
attr_reader :peace
- def initialize(name, run_context=nil)
- @resource_name = :zen_master
- super
- allowed_actions << :win << :score
- end
def peace(tf)
@peace = tf
diff --git a/spec/support/mock/platform.rb b/spec/support/mock/platform.rb
index ab2c19baff..7eae82fe7d 100644
--- a/spec/support/mock/platform.rb
+++ b/spec/support/mock/platform.rb
@@ -6,7 +6,7 @@
# testing code that mixes in platform specific modules like +Chef::Mixin::Securable+
# or +Chef::FileAccessControl+
def platform_mock(platform = :unix, &block)
- allow(Chef::Platform).to receive(:windows?).and_return(platform == :windows ? true : false)
+ allow(ChefConfig).to receive(:windows?).and_return(platform == :windows ? true : false)
ENV['SYSTEMDRIVE'] = (platform == :windows ? 'C:' : nil)
if platform == :windows
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
index a412fe38e1..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
@@ -88,6 +96,20 @@ def mac_osx_106?
false
end
+def mac_osx?
+ if File.exists? "/usr/bin/sw_vers"
+ result = ShellHelpers.shell_out("/usr/bin/sw_vers")
+ result.stdout.each_line do |line|
+ if line =~ /^ProductName:\sMac OS X.*$/
+ return true
+ end
+ end
+ end
+
+ false
+end
+
+
# detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system)
def windows64?
windows? && ( ENV['PROCESSOR_ARCHITECTURE'] == 'AMD64' || ENV['PROCESSOR_ARCHITEW6432'] == 'AMD64' )
diff --git a/spec/support/shared/context/client.rb b/spec/support/shared/context/client.rb
new file mode 100644
index 0000000000..eb537e9889
--- /dev/null
+++ b/spec/support/shared/context/client.rb
@@ -0,0 +1,277 @@
+
+require 'spec_helper'
+
+# Stubs a basic client object
+shared_context "client" do
+ let(:fqdn) { "hostname.example.org" }
+ let(:hostname) { "hostname" }
+ let(:machinename) { "machinename.example.org" }
+ let(:platform) { "example-platform" }
+ let(:platform_version) { "example-platform-1.0" }
+
+ let(:ohai_data) do
+ {
+ :fqdn => fqdn,
+ :hostname => hostname,
+ :machinename => machinename,
+ :platform => platform,
+ :platform_version => platform_version
+ }
+ end
+
+ let(:ohai_system) do
+ ohai = instance_double("Ohai::System", :all_plugins => true, :data => ohai_data)
+ allow(ohai).to receive(:[]) do |k|
+ ohai_data[k]
+ end
+ ohai
+ end
+
+ let(:node) do
+ Chef::Node.new.tap do |n|
+ n.name(fqdn)
+ n.chef_environment("_default")
+ end
+ end
+
+ let(:json_attribs) { nil }
+ let(:client_opts) { {} }
+
+ let(:client) do
+ Chef::Config[:event_loggers] = []
+ Chef::Client.new(json_attribs, client_opts).tap do |c|
+ c.node = node
+ end
+ end
+
+ before do
+ Chef::Log.logger = Logger.new(StringIO.new)
+
+ # Node/Ohai data
+ #Chef::Config[:node_name] = fqdn
+ allow(Ohai::System).to receive(:new).and_return(ohai_system)
+ end
+end
+
+# Stubs a client for a client run.
+# Requires a client object be defined in the scope of this included context.
+# e.g.:
+# describe "some functionality" do
+# include_context "client"
+# include_context "a client run"
+# ...
+# end
+shared_context "a client run" do
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+
+ let(:api_client_exists?) { false }
+ let(:enable_fork) { false }
+
+ let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
+ let(:http_node_load) { double("Chef::REST (node)") }
+ let(:http_node_save) { double("Chef::REST (node save)") }
+
+ let(:runner) { instance_double("Chef::Runner") }
+ let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) }
+
+ def stub_for_register
+ # --Client.register
+ # Make sure Client#register thinks the client key doesn't
+ # exist, so it tries to register and create one.
+ allow(File).to receive(:exists?).and_call_original
+ expect(File).to receive(:exists?).
+ with(Chef::Config[:client_key]).
+ exactly(:once).
+ and_return(api_client_exists?)
+
+ unless api_client_exists?
+ # Client.register will register with the validation client name.
+ expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run)
+ end
+ end
+
+ def stub_for_node_load
+ # Client.register will then turn around create another
+ # Chef::REST object, this time with the client key it got from the
+ # previous step.
+ expect(Chef::REST).to receive(:new).
+ with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
+ exactly(:once).
+ and_return(http_node_load)
+
+ # --Client#build_node
+ # looks up the node, which we will return, then later saves it.
+ expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node)
+
+ # --ResourceReporter#node_load_completed
+ # gets a run id from the server for storing resource history
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed)
+ end
+
+ def stub_for_sync_cookbooks
+ # --Client#setup_run_context
+ # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
+ #
+ expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks)
+ expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
+ expect(http_cookbook_sync).to receive(:post).
+ with("environments/_default/cookbook_versions", {:run_list => []}).
+ and_return({})
+ end
+
+ def stub_for_converge
+ # define me
+ end
+
+ def stub_for_audit
+ # define me
+ end
+
+ def stub_for_node_save
+ # define me
+ end
+
+ def stub_for_run
+ # define me
+ end
+
+ before do
+ Chef::Config[:client_fork] = enable_fork
+ Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef'
+ Chef::Config[:why_run] = false
+ Chef::Config[:audit_mode] = :enabled
+
+ stub_const("Chef::Client::STDOUT_FD", stdout)
+ stub_const("Chef::Client::STDERR_FD", stderr)
+
+ stub_for_register
+ stub_for_node_load
+ stub_for_sync_cookbooks
+ stub_for_converge
+ stub_for_audit
+ stub_for_node_save
+
+ expect_any_instance_of(Chef::RunLock).to receive(:acquire)
+ expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
+ expect_any_instance_of(Chef::RunLock).to receive(:release)
+
+ # Post conditions: check that node has been filled in correctly
+ expect(client).to receive(:run_started)
+
+ stub_for_run
+ end
+end
+
+shared_context "converge completed" do
+ def stub_for_converge
+ # --Client#converge
+ expect(Chef::Runner).to receive(:new).and_return(runner)
+ expect(runner).to receive(:converge).and_return(true)
+ end
+
+ def stub_for_node_save
+ allow(node).to receive(:data_for_save).and_return(node.for_json)
+
+ # --Client#save_updated_node
+ expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key], validate_utf8: false).and_return(http_node_save)
+ expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true)
+ end
+end
+
+shared_context "converge failed" do
+ let(:converge_error) do
+ err = Chef::Exceptions::UnsupportedAction.new("Action unsupported")
+ err.set_backtrace([ "/path/recipe.rb:15", "/path/recipe.rb:12" ])
+ err
+ end
+
+ def stub_for_converge
+ expect(Chef::Runner).to receive(:new).and_return(runner)
+ expect(runner).to receive(:converge).and_raise(converge_error)
+ end
+
+ def stub_for_node_save
+ expect(client).to_not receive(:save_updated_node)
+ end
+end
+
+shared_context "audit phase completed" do
+ def stub_for_audit
+ # -- Client#run_audits
+ expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+ expect(audit_runner).to receive(:run).and_return(true)
+ expect(client.events).to receive(:audit_phase_complete)
+ end
+end
+
+shared_context "audit phase failed with error" do
+ let(:audit_error) do
+ err = RuntimeError.new("Unexpected audit error")
+ err.set_backtrace([ "/path/recipe.rb:57", "/path/recipe.rb:55" ])
+ err
+ end
+
+ def stub_for_audit
+ expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+ expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!")
+ expect(audit_runner).to receive(:run).and_raise(audit_error)
+ expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!")
+ end
+end
+
+shared_context "audit phase completed with failed controls" do
+ let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => true,
+ :num_failed => 1, :num_total => 3) }
+
+ let(:audit_error) do
+ err = Chef::Exceptions::AuditsFailed.new(audit_runner.num_failed, audit_runner.num_total)
+ err.set_backtrace([ "/path/recipe.rb:108", "/path/recipe.rb:103" ])
+ err
+ end
+
+ def stub_for_audit
+ expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
+ expect(Chef::Audit::Logger).to receive(:read_buffer).and_return("Audit mode output!")
+ expect(audit_runner).to receive(:run)
+ expect(Chef::Exceptions::AuditsFailed).to receive(:new).with(
+ audit_runner.num_failed, audit_runner.num_total
+ ).and_return(audit_error)
+ expect(client.events).to receive(:audit_phase_failed).with(audit_error, "Audit mode output!")
+ end
+end
+
+shared_context "run completed" do
+ def stub_for_run
+ expect(client).to receive(:run_completed_successfully)
+
+ # --ResourceReporter#run_completed
+ # updates the server with the resource history
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed)
+ # --AuditReporter#run_completed
+ # posts the audit data to server.
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed)
+ end
+end
+
+shared_context "run failed" do
+ def stub_for_run
+ expect(client).to receive(:run_failed)
+
+ # --ResourceReporter#run_completed
+ # updates the server with the resource history
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
+ # --AuditReporter#run_completed
+ # posts the audit data to server.
+ # (has its own tests, so stubbing it here.)
+ expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
+ end
+
+ before do
+ expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
+ end
+end
diff --git a/spec/support/shared/examples/client.rb b/spec/support/shared/examples/client.rb
new file mode 100644
index 0000000000..330cb40ac6
--- /dev/null
+++ b/spec/support/shared/examples/client.rb
@@ -0,0 +1,53 @@
+
+require 'spec_helper'
+require 'spec/support/shared/context/client'
+
+# requires platform and platform_version be defined
+shared_examples "a completed run" do
+ include_context "run completed"
+
+ it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do
+ # This is what we're testing.
+ expect(client.run).to be true
+
+ # fork is stubbed, so we can see the outcome of the run
+ expect(node.automatic_attrs[:platform]).to eq(platform)
+ expect(node.automatic_attrs[:platform_version]).to eq(platform_version)
+ end
+end
+
+shared_examples "a completed run with audit failure" do
+ include_context "run completed"
+
+ before do
+ expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
+ end
+
+ it "converges, runs audits, saves the node and raises the error in a wrapping error" do
+ expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq(run_errors.size)
+ run_errors.each do |run_error|
+ expect(error.wrapped_errors).to include(run_error)
+ expect(error.backtrace).to include(*run_error.backtrace)
+ end
+ end
+
+ # fork is stubbed, so we can see the outcome of the run
+ expect(node.automatic_attrs[:platform]).to eq(platform)
+ expect(node.automatic_attrs[:platform_version]).to eq(platform_version)
+ end
+end
+
+shared_examples "a failed run" do
+ include_context "run failed"
+
+ it "skips node save and raises the error in a wrapping error" do
+ expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq(run_errors.size)
+ run_errors.each do |run_error|
+ expect(error.wrapped_errors).to include(run_error)
+ expect(error.backtrace).to include(*run_error.backtrace)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared/functional/file_resource.rb b/spec/support/shared/functional/file_resource.rb
index 4f8e2f5b71..3ce3c9c94e 100644
--- a/spec/support/shared/functional/file_resource.rb
+++ b/spec/support/shared/functional/file_resource.rb
@@ -592,10 +592,6 @@ shared_examples_for "a configured file resource" do
File.open(path, "wb") { |f| f.write(wrong_content) }
end
- it "updates the source file content" do
- skip
- end
-
it "marks the resource as updated" do
resource.run_action(:create)
expect(resource).to be_updated_by_last_action
diff --git a/spec/support/shared/functional/securable_resource.rb b/spec/support/shared/functional/securable_resource.rb
index e016bb685d..b3c32356aa 100644
--- a/spec/support/shared/functional/securable_resource.rb
+++ b/spec/support/shared/functional/securable_resource.rb
@@ -163,9 +163,6 @@ shared_examples_for "a securable resource with existing target" do
let(:desired_gid) { 1337 }
let(:expected_gid) { 1337 }
- skip "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true
- skip "should set a group (Rerun specs under root)", :requires_unprivileged_user => true
-
describe "when setting the owner", :requires_root do
before do
resource.owner expected_user_name
@@ -205,11 +202,6 @@ shared_examples_for "a securable resource with existing target" do
resource.run_action(:create)
end
- it "should set permissions as specified" do
- pending("Linux does not support lchmod")
- expect{ File.lstat(path).mode & 007777 }.to eq(@mode_string.oct & 007777)
- end
-
it "is marked as updated only if changes are made" do
expect(resource.updated_by_last_action?).to eq(expect_updated?)
end
@@ -222,15 +214,28 @@ shared_examples_for "a securable resource with existing target" do
resource.run_action(:create)
end
- it "should set permissions in numeric form as a ruby-interpreted octal" do
- pending('Linux does not support lchmod')
- expect{ File.lstat(path).mode & 007777 }.to eq(@mode_integer & 007777)
- end
-
it "is marked as updated only if changes are made" do
expect(resource.updated_by_last_action?).to eq(expect_updated?)
end
end
+
+ describe "when setting the suid bit", :requires_root do
+ before do
+ @suid_mode = 04776
+ resource.mode @suid_mode
+ resource.run_action(:create)
+ end
+
+ it "should set the suid bit" do
+ expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777)
+ end
+
+ it "should retain the suid bit when updating the user" do
+ resource.user 1338
+ resource.run_action(:create)
+ expect(File.lstat(path).mode & 007777).to eq(@suid_mode & 007777)
+ end
+ end
end
context "on Windows", :windows_only do
@@ -288,17 +293,13 @@ shared_examples_for "a securable resource without existing target" do
include_context "diff disabled"
- context "on Unix", :unix_only do
- skip "if we need any securable resource tests on Unix without existing target resource."
- end
-
context "on Windows", :windows_only do
include_context "use Windows permissions"
- it "sets owner to Administrators on create if owner is not specified" do
+ it "leaves owner as system default on create if owner is not specified" do
expect(File.exist?(path)).to eq(false)
resource.run_action(:create)
- expect(descriptor.owner).to eq(SID.Administrators)
+ expect(descriptor.owner).to eq(SID.default_security_object_owner)
end
it "sets owner when owner is specified" do
@@ -318,22 +319,24 @@ shared_examples_for "a securable resource without existing target" do
end
it "leaves owner alone if owner is not specified and resource already exists" do
- # Set owner to Guest so it's not the same as the current user (which is the default on create)
- resource.owner 'Guest'
+ arbitrary_non_default_owner = SID.Guest
+ expect(arbitrary_non_default_owner).not_to eq(SID.default_security_object_owner)
+
+ resource.owner 'Guest' # Change to arbitrary_non_default_owner once issue #1508 is fixed
resource.run_action(:create)
- expect(descriptor.owner).to eq(SID.Guest)
+ expect(descriptor.owner).to eq(arbitrary_non_default_owner)
new_resource = create_resource
expect(new_resource.owner).to eq(nil)
new_resource.run_action(:create)
- expect(descriptor.owner).to eq(SID.Guest)
+ expect(descriptor.owner).to eq(arbitrary_non_default_owner)
end
- it "sets group to None on create if group is not specified" do
+ it "leaves group as system default on create if group is not specified" do
expect(resource.group).to eq(nil)
expect(File.exist?(path)).to eq(false)
resource.run_action(:create)
- expect(descriptor.group).to eq(SID.None)
+ expect(descriptor.group).to eq(SID.default_security_object_group)
end
it "sets group when group is specified" do
@@ -346,23 +349,18 @@ shared_examples_for "a securable resource without existing target" do
expect { resource.group 'Lance "The Nose" Glindenberry III' }.to raise_error(Chef::Exceptions::ValidationFailed)
end
- it "sets group when group is specified with a \\" do
- pending("Need to find a group containing a backslash that is on most peoples' machines")
- resource.group "#{ENV['COMPUTERNAME']}\\Administrators"
- resource.run_action(:create)
- expect{ descriptor.group }.to eq(SID.Everyone)
- end
-
it "leaves group alone if group is not specified and resource already exists" do
- # Set group to Everyone so it's not the default (None)
- resource.group 'Everyone'
+ arbitrary_non_default_group = SID.Everyone
+ expect(arbitrary_non_default_group).not_to eq(SID.default_security_object_group)
+
+ resource.group 'Everyone' # Change to arbitrary_non_default_group once issue #1508 is fixed
resource.run_action(:create)
- expect(descriptor.group).to eq(SID.Everyone)
+ expect(descriptor.group).to eq(arbitrary_non_default_group)
new_resource = create_resource
expect(new_resource.group).to eq(nil)
new_resource.run_action(:create)
- expect(descriptor.group).to eq(SID.Everyone)
+ expect(descriptor.group).to eq(arbitrary_non_default_group)
end
describe "with rights and deny_rights attributes" do
diff --git a/spec/support/shared/functional/securable_resource_with_reporting.rb b/spec/support/shared/functional/securable_resource_with_reporting.rb
index 8a2ceed837..3176ebba0d 100644
--- a/spec/support/shared/functional/securable_resource_with_reporting.rb
+++ b/spec/support/shared/functional/securable_resource_with_reporting.rb
@@ -35,7 +35,7 @@ shared_examples_for "a securable resource with reporting" do
# Default mode varies based on implementation. Providers that use a tempfile
# will default to 0600. Providers that use File.open will default to 0666 -
# umask
- # let(:default_mode) { ((0100666 - File.umask) & 07777).to_s(8) }
+ # let(:default_mode) { (0666 & ~File.umask).to_s(8) }
describe "reading file security metadata for reporting on unix", :unix_only => true do
# According to POSIX standard created files get either the
@@ -185,7 +185,7 @@ shared_examples_for "a securable resource with reporting" do
# TODO: most stable way to specify?
expect(current_resource.owner).to eq(Etc.getpwuid(Process.uid).name)
expect(current_resource.group).to eq(@expected_group_name)
- expect(current_resource.mode).to eq("0#{((0100666 - File.umask) & 07777).to_s(8)}")
+ expect(current_resource.mode).to eq("0#{(0666 & ~File.umask).to_s(8)}")
end
end
@@ -239,8 +239,8 @@ shared_examples_for "a securable resource with reporting" do
end
context "and mode is specified as a String" do
- let(:default_create_mode) { (0100666 - File.umask) }
- let(:expected_mode) { "0#{(default_create_mode & 07777).to_s(8)}" }
+ let(:default_create_mode) { 0666 & ~File.umask }
+ let(:expected_mode) { "0#{default_create_mode.to_s(8)}" }
before do
resource.mode(expected_mode)
@@ -252,7 +252,7 @@ shared_examples_for "a securable resource with reporting" do
end
context "and mode is specified as an Integer" do
- let(:set_mode) { (0100666 - File.umask) & 07777 }
+ let(:set_mode) { 0666 & ~File.umask }
let(:expected_mode) { "0#{set_mode.to_s(8)}" }
before do
@@ -279,14 +279,14 @@ shared_examples_for "a securable resource with reporting" do
end
it "has empty values for file metadata in 'current_resource'" do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
expect(current_resource.owner).to be_nil
expect(current_resource.expanded_rights).to be_nil
end
context "and no security metadata is specified in new_resource" do
before do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
end
it "sets the metadata values on the new_resource as strings after creating" do
@@ -322,7 +322,7 @@ shared_examples_for "a securable resource with reporting" do
let(:expected_user_name) { 'domain\user' }
before do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
resource.owner(expected_user_name)
resource.run_action(:create)
end
@@ -336,7 +336,7 @@ shared_examples_for "a securable resource with reporting" do
context "when the target file exists" do
before do
- pending "windows reporting not yet fully supported"
+ skip "windows reporting not yet fully supported"
FileUtils.touch(resource.path)
resource.action(:create)
end
diff --git a/spec/support/shared/functional/win32_service.rb b/spec/support/shared/functional/win32_service.rb
index 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/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb
index 35b86dc4e8..3499cc98ec 100644
--- a/spec/support/shared/functional/windows_script.rb
+++ b/spec/support/shared/functional/windows_script.rb
@@ -114,7 +114,7 @@ shared_context Chef::Resource::WindowsScript do
describe "when the run action is invoked on Windows" do
it "executes the script code" do
- resource.code("@whoami > #{script_output_path}")
+ resource.code("whoami > #{script_output_path}")
resource.returns(0)
resource.run_action(:run)
end
diff --git a/spec/support/shared/integration/integration_helper.rb b/spec/support/shared/integration/integration_helper.rb
index e6942c62af..927ff2f42b 100644
--- a/spec/support/shared/integration/integration_helper.rb
+++ b/spec/support/shared/integration/integration_helper.rb
@@ -22,14 +22,19 @@ require 'fileutils'
require 'chef/config'
require 'chef/json_compat'
require 'chef/server_api'
-require 'chef_zero/rspec'
require 'support/shared/integration/knife_support'
require 'support/shared/integration/app_server_support'
+require 'cheffish/rspec/chef_run_support'
require 'spec_helper'
module IntegrationSupport
include ChefZero::RSpec
+ def self.included(includer_class)
+ includer_class.extend(Cheffish::RSpec::ChefRunSupport)
+ includer_class.extend(ClassMethods)
+ end
+
module ClassMethods
include ChefZero::RSpec
@@ -49,10 +54,6 @@ module IntegrationSupport
end
end
- def self.included(includer_class)
- includer_class.extend(ClassMethods)
- end
-
def api
Chef::ServerAPI.new
end
diff --git a/spec/support/shared/shared_examples.rb b/spec/support/shared/shared_examples.rb
index 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
new file mode 100644
index 0000000000..05a4117f8e
--- /dev/null
+++ b/spec/support/shared/unit/api_versioning.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/exceptions"
+
+shared_examples_for "version handling" do
+ let(:response_406) { OpenStruct.new(:code => '406') }
+ let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+
+ before do
+ allow(rest_v1).to receive(http_verb).and_raise(exception_406)
+ end
+
+ context "when the server does not support the min or max server API version that Chef::UserV1 supports" do
+ before do
+ allow(object).to receive(:server_client_api_version_intersection).and_return([])
+ end
+
+ it "raises the original exception" do
+ expect{ object.send(method) }.to raise_error(exception_406)
+ end
+ end # when the server does not support the min or max server API version that Chef::UserV1 supports
+end # version handling
+
+shared_examples_for "user and client reregister" do
+ let(:response_406) { OpenStruct.new(:code => '406') }
+ let(:exception_406) { Net::HTTPServerException.new("406 Not Acceptable", response_406) }
+ let(:generic_exception) { Exception.new }
+ let(:min_version) { "2" }
+ let(:max_version) { "5" }
+ let(:return_hash_406) {
+ {
+ "min_version" => min_version,
+ "max_version" => max_version,
+ "request_version" => "30"
+ }
+ }
+
+ context "when V0 is not supported by the server" do
+ context "when the exception is 406 and returns x-ops-server-api-version header" do
+ before do
+ allow(rest_v0).to receive(:put).and_raise(exception_406)
+ allow(response_406).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash_406))
+ end
+
+ it "raises an error about only V0 being supported" do
+ expect(object).to receive(:reregister_only_v0_supported_error_msg).with(max_version, min_version)
+ expect{ object.reregister }.to raise_error(Chef::Exceptions::OnlyApiVersion0SupportedForAction)
+ end
+
+ end
+ context "when the exception is not versioning related" do
+ before do
+ allow(rest_v0).to receive(:put).and_raise(generic_exception)
+ end
+
+ it "raises the original error" do
+ expect{ object.reregister }.to raise_error(generic_exception)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared/unit/knife_shared.rb b/spec/support/shared/unit/knife_shared.rb
new file mode 100644
index 0000000000..8c9010f3cf
--- /dev/null
+++ b/spec/support/shared/unit/knife_shared.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+shared_examples_for "mandatory field missing" do
+ context "when field is nil" do
+ before do
+ knife.name_args = name_args
+ end
+
+ it "exits 1" do
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match /You must specify a #{fieldname}/
+ end
+ end
+end
diff --git a/spec/support/shared/unit/provider/file.rb b/spec/support/shared/unit/provider/file.rb
index 86f32c9e89..7de9698451 100644
--- a/spec/support/shared/unit/provider/file.rb
+++ b/spec/support/shared/unit/provider/file.rb
@@ -255,7 +255,7 @@ shared_examples_for Chef::Provider::File do
context "examining file security metadata on Unix with a file that exists" do
before do
# fake that we're on unix even if we're on windows
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
# mock up the filesystem to behave like unix
setup_normal_file
stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
@@ -331,7 +331,7 @@ shared_examples_for Chef::Provider::File do
context "examining file security metadata on Unix with a file that does not exist" do
before do
# fake that we're on unix even if we're on windows
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
setup_missing_file
end
@@ -380,7 +380,7 @@ shared_examples_for Chef::Provider::File do
before do
# fake that we're on unix even if we're on windows
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
# mock up the filesystem to behave like unix
setup_normal_file
stat_struct = double("::File.stat", :mode => 0600, :uid => 0, :gid => 0, :mtime => 10000)
@@ -529,26 +529,49 @@ shared_examples_for Chef::Provider::File do
:for_reporting => diff_for_reporting )
allow(diff).to receive(:diff).with(resource_path, tempfile_path).and_return(true)
expect(provider).to receive(:diff).at_least(:once).and_return(diff)
- expect(provider).to receive(:managing_content?).at_least(:once).and_return(true)
expect(provider).to receive(:checksum).with(tempfile_path).and_return(tempfile_sha256)
- expect(provider).to receive(:checksum).with(resource_path).and_return(tempfile_sha256)
+ allow(provider).to receive(:managing_content?).and_return(true)
+ allow(provider).to receive(:checksum).with(resource_path).and_return(tempfile_sha256)
+ expect(resource).not_to receive(:checksum).with(tempfile_sha256) # do not mutate the new resource
expect(provider.deployment_strategy).to receive(:deploy).with(tempfile_path, normalized_path)
end
context "when the file was created" do
before { expect(provider).to receive(:needs_creating?).at_least(:once).and_return(true) }
- it "does not backup the file and does not produce a diff for reporting" do
+ it "does not backup the file" do
expect(provider).not_to receive(:do_backup)
provider.send(:do_contents_changes)
+ end
+
+ it "does not produce a diff for reporting" do
+ provider.send(:do_contents_changes)
expect(resource.diff).to be_nil
end
+
+ it "renders the final checksum correctly for reporting" do
+ provider.send(:do_contents_changes)
+ expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256)
+ end
end
context "when the file was not created" do
- before { expect(provider).to receive(:needs_creating?).at_least(:once).and_return(false) }
- it "backs up the file and produces a diff for reporting" do
+ before do
+ allow(provider).to receive(:do_backup) # stub do_backup
+ expect(provider).to receive(:needs_creating?).at_least(:once).and_return(false)
+ end
+
+ it "backs up the file" do
expect(provider).to receive(:do_backup)
provider.send(:do_contents_changes)
+ end
+
+ it "produces a diff for reporting" do
+ provider.send(:do_contents_changes)
expect(resource.diff).to eq(diff_for_reporting)
end
+
+ it "renders the final checksum correctly for reporting" do
+ provider.send(:do_contents_changes)
+ expect(resource.state_for_resource_reporter[:checksum]).to eql(tempfile_sha256)
+ end
end
end
diff --git a/spec/support/shared/unit/user_and_client_shared.rb b/spec/support/shared/unit/user_and_client_shared.rb
new file mode 100644
index 0000000000..bc5ffa07c2
--- /dev/null
+++ b/spec/support/shared/unit/user_and_client_shared.rb
@@ -0,0 +1,115 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+shared_examples_for "user or client create" do
+
+ context "when server API V1 is valid on the Chef Server receiving the request" do
+
+ it "creates a new object via the API" do
+ expect(rest_v1).to receive(:post).with(url, payload).and_return({})
+ object.create
+ end
+
+ it "creates a new object via the API with a public_key when it exists" do
+ object.public_key "some_public_key"
+ expect(rest_v1).to receive(:post).with(url, payload.merge({:public_key => "some_public_key"})).and_return({})
+ object.create
+ end
+
+ context "raise error when create_key and public_key are both set" do
+
+ before do
+ object.public_key "key"
+ object.create_key true
+ end
+
+ it "rasies the proper error" do
+ expect { object.create }.to raise_error(error)
+ end
+ end
+
+ context "when create_key == true" do
+ before do
+ object.create_key true
+ end
+
+ it "creates a new object via the API with create_key" do
+ expect(rest_v1).to receive(:post).with(url, payload.merge({:create_key => true})).and_return({})
+ object.create
+ end
+ end
+
+ context "when chef_key is returned by the server" do
+ let(:chef_key) {
+ {
+ "chef_key" => {
+ "public_key" => "some_public_key"
+ }
+ }
+ }
+
+ it "puts the public key into the objectr returned by create" do
+ expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key))
+ new_object = object.create
+ expect(new_object.public_key).to eq("some_public_key")
+ end
+
+ context "when private_key is returned in chef_key" do
+ let(:chef_key) {
+ {
+ "chef_key" => {
+ "public_key" => "some_public_key",
+ "private_key" => "some_private_key"
+ }
+ }
+ }
+
+ it "puts the private key into the object returned by create" do
+ expect(rest_v1).to receive(:post).with(url, payload).and_return(payload.merge(chef_key))
+ new_object = object.create
+ expect(new_object.private_key).to eq("some_private_key")
+ end
+ end
+ end # when chef_key is returned by the server
+
+ end # when server API V1 is valid on the Chef Server receiving the request
+
+ context "when server API V1 is not valid on the Chef Server receiving the request" do
+
+ context "when the server supports API V0" do
+ before do
+ allow(object).to receive(:server_client_api_version_intersection).and_return([0])
+ allow(rest_v1).to receive(:post).and_raise(exception_406)
+ end
+
+ it "creates a new object via the API" do
+ expect(rest_v0).to receive(:post).with(url, payload).and_return({})
+ object.create
+ end
+
+ it "creates a new object via the API with a public_key when it exists" do
+ object.public_key "some_public_key"
+ expect(rest_v0).to receive(:post).with(url, payload.merge({:public_key => "some_public_key"})).and_return({})
+ object.create
+ end
+
+ end # when the server supports API V0
+ end # when server API V1 is not valid on the Chef Server receiving the request
+
+end # user or client create
+
diff --git a/spec/unit/api_client_spec.rb b/spec/unit/api_client_spec.rb
index 7668e31f5a..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
@@ -123,10 +128,6 @@ describe Chef::ApiClient do
it "does not include the private key if not present" do
expect(@json).not_to include("private_key")
end
-
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
- let(:jsonable) { @client }
- end
end
describe "when deserializing from JSON (string) using ApiClient#from_json" do
@@ -222,8 +223,8 @@ describe Chef::ApiClient do
"validator" => true,
"json_class" => "Chef::ApiClient"
}
- @http_client = double("Chef::REST mock")
- allow(Chef::REST).to receive(:new).and_return(@http_client)
+ @http_client = double("Chef::ServerAPI mock")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
expect(@http_client).to receive(:get).with("clients/black").and_return(client)
@client = Chef::ApiClient.load(client['name'])
end
@@ -269,18 +270,13 @@ describe Chef::ApiClient do
File.open(Chef::Config[:client_key], "r") {|f| f.read.chomp }
end
- it "has an HTTP client configured with default credentials" do
- expect(@client.http_api).to be_a_kind_of(Chef::REST)
- expect(@client.http_api.client_name).to eq("silent-bob")
- expect(@client.http_api.signing_key.to_s).to eq(private_key_data)
- end
end
describe "when requesting a new key" do
before do
@http_client = double("Chef::REST mock")
- allow(Chef::REST).to receive(:new).and_return(@http_client)
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
end
context "and the client does not exist on the server" do
diff --git a/spec/unit/api_client_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/client_spec.rb b/spec/unit/application/client_spec.rb
index ea2ad473e5..64a6bcc9d2 100644
--- a/spec/unit/application/client_spec.rb
+++ b/spec/unit/application/client_spec.rb
@@ -60,7 +60,7 @@ describe Chef::Application::Client, "reconfigure" do
context "when interval is given" do
before do
Chef::Config[:interval] = 600
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
end
it "should terminate with message" do
@@ -77,7 +77,7 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
context "when interval is given on windows" do
before do
Chef::Config[:interval] = 600
- allow(Chef::Platform).to receive(:windows?).and_return(true)
+ allow(ChefConfig).to receive(:windows?).and_return(true)
end
it "should not terminate" do
@@ -131,6 +131,16 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
end
+ describe "when --no-listen is set" do
+
+ it "configures listen = false" do
+ app.config[:listen] = false
+ app.reconfigure
+ expect(Chef::Config[:listen]).to eq(false)
+ end
+
+ end
+
describe "when the json_attribs configuration option is specified" do
let(:json_attribs) { {"a" => "b"} }
@@ -155,11 +165,6 @@ Enable chef-client interval runs by setting `:client_fork = true` in your config
before do
allow(Chef::Log).to receive(:warn)
end
-
- it "emits a warning that audit mode is an experimental feature" do
- expect(Chef::Log).to receive(:warn).with(/Audit mode is an experimental feature/)
- app.reconfigure
- end
end
shared_examples "unrecognized setting" do
@@ -305,7 +310,7 @@ describe Chef::Application::Client, "run_application", :unix_only do
allow(Chef::Daemon).to receive(:daemonize).and_return(true)
end
- it "should exit hard with exitstatus 3" do
+ it "should exit hard with exitstatus 3", :volatile do
pid = fork do
@app.run_application
end
@@ -320,9 +325,9 @@ describe Chef::Application::Client, "run_application", :unix_only do
end
expect(@pipe[0].gets).to eq("started\n")
Process.kill("TERM", pid)
- Process.wait
- sleep 1 # Make sure we give the converging child process enough time to finish
- expect(IO.select([@pipe[0]], nil, nil, 0)).not_to be_nil
+ Process.wait(pid)
+ # The timeout value needs to be large enough for the child process to finish
+ expect(IO.select([@pipe[0]], nil, nil, 15)).not_to be_nil
expect(@pipe[0].gets).to eq("finished\n")
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/audit/audit_reporter_spec.rb b/spec/unit/audit/audit_reporter_spec.rb
index 84d7ea82f0..46c2a96b4c 100644
--- a/spec/unit/audit/audit_reporter_spec.rb
+++ b/spec/unit/audit/audit_reporter_spec.rb
@@ -88,6 +88,29 @@ describe Chef::Audit::AuditReporter do
reporter.run_completed(node)
end
+ context "when audit phase failed" do
+
+ let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError",
+ :message => "Audit phase failed with error message: derpderpderp",
+ :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) }
+
+ before do
+ reporter.instance_variable_set(:@audit_phase_error, audit_error)
+ end
+
+ it "reports an error" do
+ reporter.run_completed(node)
+ expect(run_data).to have_key(:error)
+ expect(run_data).to have_key(:error)
+ expect(run_data[:error]).to eq <<-EOM.strip!
+Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp
+/path/recipe.rb:57
+/path/library.rb:106
+EOM
+ end
+
+ end
+
context "when unable to post to server" do
let(:error) do
@@ -203,7 +226,7 @@ describe Chef::Audit::AuditReporter do
it "doesn't send reports" do
expect(reporter).to receive(:auditing_enabled?).and_return(true)
expect(reporter).to receive(:run_status).and_return(nil)
- expect(Chef::Log).to receive(:debug).with("Run failed before audits were initialized, not sending audit report to server")
+ expect(Chef::Log).to receive(:debug).with("Run failed before audit mode was initialized, not sending audit report to server")
reporter.run_completed(node)
end
@@ -215,9 +238,13 @@ describe Chef::Audit::AuditReporter do
let(:audit_data) { Chef::Audit::AuditData.new(node.name, run_id) }
let(:run_data) { audit_data.to_hash }
- let(:error) { double("AuditError", :class => "Chef::Exception::AuditError",
- :message => "Well that certainly didn't work",
- :backtrace => ["line 0", "line 1", "line 2"]) }
+ let(:audit_error) { double("AuditError", :class => "Chef::Exceptions::AuditError",
+ :message => "Audit phase failed with error message: derpderpderp",
+ :backtrace => ["/path/recipe.rb:57", "/path/library.rb:106"]) }
+
+ let(:run_error) { double("RunError", :class => "Chef::Exceptions::RunError",
+ :message => "This error shouldn't be reported.",
+ :backtrace => ["fix it", "fix it", "fix it"]) }
before do
allow(reporter).to receive(:auditing_enabled?).and_return(true)
@@ -226,15 +253,32 @@ describe Chef::Audit::AuditReporter do
allow(audit_data).to receive(:to_hash).and_return(run_data)
end
- it "adds the error information to the reported data" do
- expect(rest).to receive(:create_url)
- expect(rest).to receive(:post)
- reporter.run_failed(error)
- expect(run_data).to have_key(:error)
- expect(run_data[:error]).to eq "Chef::Exception::AuditError: Well that certainly didn't work\n" +
- "line 0\nline 1\nline 2"
+ context "when no prior exception is stored" do
+ it "reports no error" do
+ expect(rest).to receive(:create_url)
+ expect(rest).to receive(:post)
+ reporter.run_failed(run_error)
+ expect(run_data).to_not have_key(:error)
+ end
end
+ context "when some prior exception is stored" do
+ before do
+ reporter.instance_variable_set(:@audit_phase_error, audit_error)
+ end
+
+ it "reports the prior error" do
+ expect(rest).to receive(:create_url)
+ expect(rest).to receive(:post)
+ reporter.run_failed(run_error)
+ expect(run_data).to have_key(:error)
+ expect(run_data[:error]).to eq <<-EOM.strip!
+Chef::Exceptions::AuditError: Audit phase failed with error message: derpderpderp
+/path/recipe.rb:57
+/path/library.rb:106
+EOM
+ end
+ end
end
shared_context "audit data" do
@@ -270,14 +314,14 @@ describe Chef::Audit::AuditReporter do
it "notifies audit phase finished to debug log" do
expect(Chef::Log).to receive(:debug).with(/Audit Reporter completed/)
- reporter.audit_phase_complete
+ reporter.audit_phase_complete("Output from audit mode")
end
it "collects audit data" do
ordered_control_groups.each do |_name, group|
expect(audit_data).to receive(:add_control_group).with(group)
end
- reporter.audit_phase_complete
+ reporter.audit_phase_complete("Output from audit mode")
end
end
@@ -288,14 +332,14 @@ describe Chef::Audit::AuditReporter do
it "notifies audit phase failed to debug log" do
expect(Chef::Log).to receive(:debug).with(/Audit Reporter failed/)
- reporter.audit_phase_failed(error)
+ reporter.audit_phase_failed(error, "Output from audit mode")
end
it "collects audit data" do
ordered_control_groups.each do |_name, group|
expect(audit_data).to receive(:add_control_group).with(group)
end
- reporter.audit_phase_failed(error)
+ reporter.audit_phase_failed(error, "Output from audit mode")
end
end
diff --git a/spec/unit/audit/logger_spec.rb b/spec/unit/audit/logger_spec.rb
new file mode 100644
index 0000000000..9dd9ce2cd9
--- /dev/null
+++ b/spec/unit/audit/logger_spec.rb
@@ -0,0 +1,42 @@
+#
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Audit::Logger do
+
+ before(:each) do
+ Chef::Audit::Logger.instance_variable_set(:@buffer, nil)
+ end
+
+ it 'calling puts creates @buffer and adds the message' do
+ Chef::Audit::Logger.puts("Output message")
+ expect(Chef::Audit::Logger.read_buffer).to eq("Output message\n")
+ end
+
+ it 'calling puts multiple times adds to the message' do
+ Chef::Audit::Logger.puts("Output message")
+ Chef::Audit::Logger.puts("Output message")
+ Chef::Audit::Logger.puts("Output message")
+ expect(Chef::Audit::Logger.read_buffer).to eq("Output message\nOutput message\nOutput message\n")
+ end
+
+ it 'calling it before @buffer is set returns an empty string' do
+ expect(Chef::Audit::Logger.read_buffer).to eq("")
+ end
+
+end
diff --git a/spec/unit/audit/runner_spec.rb b/spec/unit/audit/runner_spec.rb
index 0bd4c18388..1de024260f 100644
--- a/spec/unit/audit/runner_spec.rb
+++ b/spec/unit/audit/runner_spec.rb
@@ -68,8 +68,8 @@ describe Chef::Audit::Runner do
in_sub_process do
runner.send(:setup)
- expect(RSpec.configuration.output_stream).to eq(log_location)
- expect(RSpec.configuration.error_stream).to eq(log_location)
+ expect(RSpec.configuration.output_stream).to eq(Chef::Audit::Logger)
+ expect(RSpec.configuration.error_stream).to eq(Chef::Audit::Logger)
expect(RSpec.configuration.formatters.size).to eq(2)
expect(RSpec.configuration.formatters).to include(instance_of(Chef::Audit::AuditEventProxy))
diff --git a/spec/unit/chef_class_spec.rb b/spec/unit/chef_class_spec.rb
new file mode 100644
index 0000000000..f1b877520c
--- /dev/null
+++ b/spec/unit/chef_class_spec.rb
@@ -0,0 +1,110 @@
+#
+# Author:: Lamont Granquist (<lamont@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'
+
+describe "Chef class" do
+ let(:platform) { "debian" }
+
+ let(:node) do
+ node = Chef::Node.new
+ node.automatic['platform'] = platform
+ node
+ end
+
+ let(:run_context) do
+ Chef::RunContext.new(node, nil, nil)
+ end
+
+ let(:resource_priority_map) do
+ double("Chef::Platform::ResourcePriorityMap")
+ end
+
+ let(:provider_priority_map) do
+ double("Chef::Platform::ProviderPriorityMap")
+ end
+
+ before do
+ Chef.set_run_context(run_context)
+ Chef.set_node(node)
+ Chef.set_resource_priority_map(resource_priority_map)
+ Chef.set_provider_priority_map(provider_priority_map)
+ end
+
+ context "priority maps" do
+ context "#get_provider_priority_array" do
+ it "should use the current node to get the right priority_map" do
+ expect(provider_priority_map).to receive(:get_priority_array).with(node, :http_request).and_return("stuff")
+ expect(Chef.get_provider_priority_array(:http_request)).to eql("stuff")
+ end
+ end
+ context "#get_resource_priority_array" do
+ it "should use the current node to get the right priority_map" do
+ expect(resource_priority_map).to receive(:get_priority_array).with(node, :http_request).and_return("stuff")
+ expect(Chef.get_resource_priority_array(:http_request)).to eql("stuff")
+ end
+ end
+ context "#set_provider_priority_array" do
+ it "should delegate to the provider_priority_map" do
+ expect(provider_priority_map).to receive(:set_priority_array).with(:http_request, ["a", "b"], platform: "debian").and_return("stuff")
+ expect(Chef.set_provider_priority_array(:http_request, ["a", "b"], platform: "debian")).to eql("stuff")
+ end
+ end
+ context "#set_priority_map_for_resource" do
+ it "should delegate to the resource_priority_map" do
+ expect(resource_priority_map).to receive(:set_priority_array).with(:http_request, ["a", "b"], platform: "debian").and_return("stuff")
+ expect(Chef.set_resource_priority_array(:http_request, ["a", "b"], platform: "debian")).to eql("stuff")
+ end
+ end
+ end
+
+ context "#run_context" do
+ it "should return the injected RunContext" do
+ expect(Chef.run_context).to eql(run_context)
+ end
+ end
+
+ context "#node" do
+ it "should return the injected Node" do
+ expect(Chef.node).to eql(node)
+ end
+ end
+
+ context '#event_handler' do
+ it 'adds a new handler' do
+ x = 1
+ Chef.event_handler do
+ on :converge_start do
+ x = 2
+ end
+ end
+ expect(Chef::Config[:event_handlers]).to_not be_empty
+ Chef::Config[:event_handlers].first.send(:converge_start)
+ expect(x).to eq(2)
+ end
+
+ it 'raise error if unknown event type is passed' do
+ expect do
+ Chef.event_handler do
+ on :yolo do
+ end
+ end
+ end.to raise_error(Chef::Exceptions::InvalidEventType)
+ end
+ end
+end
diff --git a/spec/unit/chef_fs/file_pattern_spec.rb b/spec/unit/chef_fs/file_pattern_spec.rb
index a9f06e8424..ed5f314605 100644
--- a/spec/unit/chef_fs/file_pattern_spec.rb
+++ b/spec/unit/chef_fs/file_pattern_spec.rb
@@ -157,7 +157,7 @@ describe Chef::ChefFS::FilePattern do
end
end
- context 'with simple pattern "a\*\b"', :pending => (Chef::Platform.windows?) do
+ context 'with simple pattern "a\*\b"', :skip => (Chef::Platform.windows?) do
let(:pattern) { Chef::ChefFS::FilePattern.new('a\*\b') }
it 'match?' do
expect(pattern.match?('a*b')).to be_truthy
@@ -264,7 +264,7 @@ describe Chef::ChefFS::FilePattern do
end
end
- context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :pending => (Chef::Platform.windows?) do
+ context 'with star pattern "/abc/d[a-z][0-9]f/ghi"', :skip => (Chef::Platform.windows?) do
let(:pattern) { Chef::ChefFS::FilePattern.new('/abc/d[a-z][0-9]f/ghi') }
it 'match?' do
expect(pattern.match?('/abc/de1f/ghi')).to be_truthy
@@ -352,11 +352,7 @@ describe Chef::ChefFS::FilePattern do
expect(pattern.could_match_children?('/abc/def/ghi')).to be_truthy
expect(pattern.could_match_children?('abc')).to be_falsey
end
- it 'could_match_children? /abc** returns false for /xyz' do
- pending 'Make could_match_children? more rigorous'
- # At the moment, we return false for this, but in the end it would be nice to return true:
- expect(pattern.could_match_children?('/xyz')).to be_falsey
- end
+
it 'exact_child_name_under' do
expect(pattern.exact_child_name_under('/')).to eq(nil)
expect(pattern.exact_child_name_under('/abc')).to eq(nil)
@@ -440,14 +436,6 @@ describe Chef::ChefFS::FilePattern do
expect(p('/.').exact_path).to eq('/')
expect(p('/.').match?('/')).to be_truthy
end
- it 'handles dot by itself', :pending => "decide what to do with dot by itself" do
- expect(p('.').normalized_pattern).to eq('.')
- expect(p('.').exact_path).to eq('.')
- expect(p('.').match?('.')).to be_truthy
- expect(p('./').normalized_pattern).to eq('.')
- expect(p('./').exact_path).to eq('.')
- expect(p('./').match?('.')).to be_truthy
- end
it 'handles dotdot' do
expect(p('abc/../def').normalized_pattern).to eq('def')
expect(p('abc/../def').exact_path).to eq('def')
diff --git a/spec/unit/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 2ec32b32ac..8146774764 100644
--- a/spec/unit/client_spec.rb
+++ b/spec/unit/client_spec.rb
@@ -19,62 +19,31 @@
#
require 'spec_helper'
+require 'spec/support/shared/context/client'
+require 'spec/support/shared/examples/client'
require 'chef/run_context'
require 'chef/rest'
require 'rbconfig'
-describe Chef::Client do
+class FooError < RuntimeError
+end
- let(:hostname) { "hostname" }
- let(:machinename) { "machinename.example.org" }
- let(:fqdn) { "hostname.example.org" }
-
- let(:ohai_data) do
- { :fqdn => fqdn,
- :hostname => hostname,
- :machinename => machinename,
- :platform => 'example-platform',
- :platform_version => 'example-platform-1.0',
- :data => {}
- }
- end
+describe Chef::Client do
+ include_context "client"
- let(:ohai_system) do
- ohai_system = double( "Ohai::System",
- :all_plugins => true,
- :data => ohai_data)
- allow(ohai_system).to receive(:[]) do |key|
- ohai_data[key]
+ context "when minimal ohai is configured" do
+ before do
+ Chef::Config[:minimal_ohai] = true
end
- ohai_system
- end
- let(:node) do
- Chef::Node.new.tap do |n|
- n.name(fqdn)
- n.chef_environment("_default")
+ it "runs ohai with only the minimum required plugins" do
+ expected_filter = %w[fqdn machinename hostname platform platform_version os os_version]
+ expect(ohai_system).to receive(:all_plugins).with(expected_filter)
+ client.run_ohai
end
end
- let(:json_attribs) { nil }
- let(:client_opts) { {} }
-
- let(:client) do
- Chef::Config[:event_loggers] = []
- Chef::Client.new(json_attribs, client_opts).tap do |c|
- c.node = node
- end
- end
-
- before do
- Chef::Log.logger = Logger.new(StringIO.new)
-
- # Node/Ohai data
- #Chef::Config[:node_name] = fqdn
- allow(Ohai::System).to receive(:new).and_return(ohai_system)
- end
-
describe "authentication protocol selection" do
after do
Chef::Config[:authentication_protocol_version] = "1.0"
@@ -101,7 +70,6 @@ describe Chef::Client do
describe "configuring output formatters" do
context "when no formatter has been configured" do
-
context "and STDOUT is a TTY" do
before do
allow(STDOUT).to receive(:tty?).and_return(true)
@@ -187,135 +155,12 @@ describe Chef::Client do
end
describe "a full client run" do
- shared_context "a client run" do
- let(:http_node_load) { double("Chef::REST (node)") }
- let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
- let(:http_node_save) { double("Chef::REST (node save)") }
- let(:runner) { double("Chef::Runner") }
- let(:audit_runner) { instance_double("Chef::Audit::Runner", :failed? => false) }
-
- let(:api_client_exists?) { false }
-
- let(:stdout) { StringIO.new }
- let(:stderr) { StringIO.new }
-
- let(:enable_fork) { false }
-
- def stub_for_register
- # --Client.register
- # Make sure Client#register thinks the client key doesn't
- # exist, so it tries to register and create one.
- allow(File).to receive(:exists?).and_call_original
- expect(File).to receive(:exists?).
- with(Chef::Config[:client_key]).
- exactly(:once).
- and_return(api_client_exists?)
-
- unless api_client_exists?
- # Client.register will register with the validation client name.
- expect_any_instance_of(Chef::ApiClient::Registration).to receive(:run)
- end
- end
-
- def stub_for_node_load
- # Client.register will then turn around create another
- # Chef::REST object, this time with the client key it got from the
- # previous step.
- expect(Chef::REST).to receive(:new).
- with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
- exactly(:once).
- and_return(http_node_load)
-
- # --Client#build_node
- # looks up the node, which we will return, then later saves it.
- expect(Chef::Node).to receive(:find_or_create).with(fqdn).and_return(node)
-
- # --ResourceReporter#node_load_completed
- # gets a run id from the server for storing resource history
- # (has its own tests, so stubbing it here.)
- expect_any_instance_of(Chef::ResourceReporter).to receive(:node_load_completed)
- end
-
- def stub_for_sync_cookbooks
- # --Client#setup_run_context
- # ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
- #
- expect_any_instance_of(Chef::CookbookSynchronizer).to receive(:sync_cookbooks)
- expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
- expect(http_cookbook_sync).to receive(:post).
- with("environments/_default/cookbook_versions", {:run_list => []}).
- and_return({})
- end
-
- def stub_for_converge
- # --Client#converge
- expect(Chef::Runner).to receive(:new).and_return(runner)
- expect(runner).to receive(:converge).and_return(true)
- end
-
- def stub_for_audit
- # -- Client#run_audits
- expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
- expect(audit_runner).to receive(:run).and_return(true)
- end
-
- def stub_for_node_save
- allow(node).to receive(:data_for_save).and_return(node.for_json)
-
- # --Client#save_updated_node
- expect(Chef::REST).to receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_node_save)
- expect(http_node_save).to receive(:put_rest).with("nodes/#{fqdn}", node.for_json).and_return(true)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_completed_successfully)
-
- # --ResourceReporter#run_completed
- # updates the server with the resource history
- # (has its own tests, so stubbing it here.)
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_completed)
- # --AuditReporter#run_completed
- # posts the audit data to server.
- # (has its own tests, so stubbing it here.)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_completed)
- end
-
- before do
- Chef::Config[:client_fork] = enable_fork
- Chef::Config[:cache_path] = windows? ? 'C:\chef' : '/var/chef'
- Chef::Config[:why_run] = false
- Chef::Config[:audit_mode] = :enabled
-
- stub_const("Chef::Client::STDOUT_FD", stdout)
- stub_const("Chef::Client::STDERR_FD", stderr)
-
- stub_for_register
- stub_for_node_load
- stub_for_sync_cookbooks
- stub_for_converge
- stub_for_audit
- stub_for_node_save
- stub_for_run
- end
- end
-
shared_examples_for "a successful client run" do
include_context "a client run"
+ include_context "converge completed"
+ include_context "audit phase completed"
- it "runs ohai, sets up authentication, loads node state, synchronizes policy, converges, and runs audits" do
- # This is what we're testing.
- client.run
-
- # fork is stubbed, so we can see the outcome of the run
- expect(node.automatic_attrs[:platform]).to eq("example-platform")
- expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
- end
+ include_examples "a completed run"
end
describe "when running chef-client without fork" do
@@ -323,24 +168,19 @@ describe Chef::Client do
end
describe "when the client key already exists" do
- let(:api_client_exists?) { true }
- include_examples "a successful client run"
+ include_examples "a successful client run" do
+ let(:api_client_exists?) { true }
+ end
end
- describe "when an override run list is given" do
- let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
-
- it "should permit spaces in overriding run list" do
+ context "when an override run list is given" do
+ it "permits spaces in overriding run list" do
Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
end
- describe "when running the client" do
+ describe "calling run" do
include_examples "a successful client run" do
-
- before do
- # Client will try to compile and run override_recipe
- expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
- end
+ let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
def stub_for_sync_cookbooks
# --Client#setup_run_context
@@ -357,13 +197,22 @@ describe Chef::Client do
# Expect NO node save
expect(node).not_to receive(:save)
end
+
+ before do
+ # Client will try to compile and run override_recipe
+ expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
+ end
end
end
end
describe "when a permanent run list is passed as an option" do
- include_examples "a successful client run" do
+ it "sets the new run list on the node" do
+ client.run
+ expect(node.run_list).to eq(Chef::RunList.new(new_runlist))
+ end
+ include_examples "a successful client run" do
let(:new_runlist) { "recipe[new_run_list_recipe]" }
let(:client_opts) { {:runlist => new_runlist} }
@@ -383,168 +232,62 @@ describe Chef::Client do
# do not create a fixture for this.
expect_any_instance_of(Chef::RunContext::CookbookCompiler).to receive(:compile)
end
-
- it "sets the new run list on the node" do
- client.run
- expect(node.run_list).to eq(Chef::RunList.new(new_runlist))
- end
end
end
- describe "when converge fails" do
- include_context "a client run" do
- let(:e) { Exception.new }
- def stub_for_converge
- expect(Chef::Runner).to receive(:new).and_return(runner)
- expect(runner).to receive(:converge).and_raise(e)
- expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
- end
-
- def stub_for_node_save
- expect(client).to_not receive(:save_updated_node)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_failed)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
- end
- end
-
- it "runs the audits and raises the error" do
- expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq(1)
- expect(error.wrapped_errors[0]).to eq(e)
- end
- end
- end
-
- describe "when the audit phase fails" do
- context "with an exception" do
- include_context "a client run" do
- let(:e) { Exception.new }
- def stub_for_audit
- expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
- expect(audit_runner).to receive(:run).and_raise(e)
- expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
-
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_failed)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
- end
- end
-
- it "should save the node after converge and raise exception" do
- expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq(1)
- expect(error.wrapped_errors[0]).to eq(e)
+ describe "when converge completes successfully" do
+ include_context "a client run"
+ include_context "converge completed"
+ context 'when audit mode is enabled' do
+ describe "when audit phase errors" do
+ include_context "audit phase failed with error"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
end
end
- end
-
- context "with failed audits" do
- include_context "a client run" do
- let(:audit_runner) do
- instance_double("Chef::Audit::Runner", :run => true, :failed? => true, :num_failed => 1, :num_total => 1)
- end
-
- def stub_for_audit
- expect(Chef::Audit::Runner).to receive(:new).and_return(audit_runner)
- expect(Chef::Application).to receive(:debug_stacktrace).with an_instance_of(Chef::Exceptions::RunFailedWrappingError)
- end
-
- def stub_for_run
- expect_any_instance_of(Chef::RunLock).to receive(:acquire)
- expect_any_instance_of(Chef::RunLock).to receive(:save_pid)
- expect_any_instance_of(Chef::RunLock).to receive(:release)
- # Post conditions: check that node has been filled in correctly
- expect(client).to receive(:run_started)
- expect(client).to receive(:run_failed)
-
- expect_any_instance_of(Chef::ResourceReporter).to receive(:run_failed)
- expect_any_instance_of(Chef::Audit::AuditReporter).to receive(:run_failed)
- end
+ describe "when audit phase completed" do
+ include_context "audit phase completed"
+ include_examples "a completed run"
end
- it "should save the node after converge and raise exception" do
- expect{ client.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
- expect(error.wrapped_errors.size).to eq(1)
- expect(error.wrapped_errors[0]).to be_instance_of(Chef::Exceptions::AuditsFailed)
+ describe "when audit phase completed with failed controls" do
+ include_context "audit phase completed with failed controls"
+ include_examples "a completed run with audit failure" do
+ let(:run_errors) { [audit_error] }
end
end
end
end
- describe "when why_run mode is enabled" do
- include_context "a client run" do
-
- before do
- Chef::Config[:why_run] = true
- end
-
- def stub_for_audit
- expect(Chef::Audit::Runner).to_not receive(:new)
- end
-
- def stub_for_node_save
- # This is how we should be mocking external calls - not letting it fall all the way through to the
- # REST call
- expect(node).to receive(:save)
- end
-
- it "runs successfully without enabling the audit runner" do
- client.run
+ describe "when converge errors" do
+ include_context "a client run"
+ include_context "converge failed"
- # fork is stubbed, so we can see the outcome of the run
- expect(node.automatic_attrs[:platform]).to eq("example-platform")
- expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
+ describe "when audit phase errors" do
+ include_context "audit phase failed with error"
+ include_examples "a failed run" do
+ let(:run_errors) { [converge_error, audit_error] }
end
end
- end
-
- describe "when audits are disabled" do
- include_context "a client run" do
-
- before do
- Chef::Config[:audit_mode] = :disabled
- end
- def stub_for_audit
- expect(Chef::Audit::Runner).to_not receive(:new)
+ describe "when audit phase completed" do
+ include_context "audit phase completed"
+ include_examples "a failed run" do
+ let(:run_errors) { [converge_error] }
end
+ end
- it "runs successfully without enabling the audit runner" do
- client.run
-
- # fork is stubbed, so we can see the outcome of the run
- expect(node.automatic_attrs[:platform]).to eq("example-platform")
- expect(node.automatic_attrs[:platform_version]).to eq("example-platform-1.0")
+ describe "when audit phase completed with failed controls" do
+ include_context "audit phase completed with failed controls"
+ include_examples "a failed run" do
+ let(:run_errors) { [converge_error, audit_error] }
end
end
end
-
end
-
describe "when handling run failures" do
-
it "should remove the run_lock on failure of #load_node" do
@run_lock = double("Chef::RunLock", :acquire => true)
allow(Chef::RunLock).to receive(:new).and_return(@run_lock)
@@ -618,6 +361,7 @@ describe Chef::Client do
# check pre-conditions.
expect(node[:roles]).to be_nil
expect(node[:recipes]).to be_nil
+ expect(node[:expanded_run_list]).to be_nil
allow(client.policy_builder).to receive(:node).and_return(node)
@@ -630,7 +374,10 @@ describe Chef::Client do
expect(node[:roles]).to include("role_containing_cookbook1")
expect(node[:recipes]).not_to be_nil
expect(node[:recipes].length).to eq(1)
- expect(node[:recipes]).to include("cookbook1")
+ expect(node[:recipes]).to include("cookbook1::default")
+ expect(node[:expanded_run_list]).not_to be_nil
+ expect(node[:expanded_run_list].length).to eq(1)
+ expect(node[:expanded_run_list]).to include("cookbook1::default")
end
it "should set the environment from the specified configuration value" do
@@ -653,7 +400,7 @@ describe Chef::Client do
describe "windows_admin_check" do
context "platform is not windows" do
before do
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
end
it "shouldn't be called" do
@@ -664,7 +411,7 @@ describe Chef::Client do
context "platform is windows" do
before do
- allow(Chef::Platform).to receive(:windows?).and_return(true)
+ allow(ChefConfig).to receive(:windows?).and_return(true)
end
it "should be called" do
@@ -713,6 +460,7 @@ describe Chef::Client do
Chef::Config[:solo] = true
Chef::Config[:cookbook_path] = ["/path/to/invalid/cookbook_path"]
end
+
context "when any directory of cookbook_path contains no cookbook" do
it "raises CookbookNotFound error" do
expect do
@@ -757,4 +505,35 @@ describe Chef::Client do
end
end
+
+ describe "always attempt to run handlers" do
+ subject { client }
+ before do
+ # fail on the first thing in begin block
+ allow_any_instance_of(Chef::RunLock).to receive(:save_pid).and_raise(NoMethodError)
+ end
+
+ context 'when audit mode is enabled' do
+ before do
+ Chef::Config[:audit_mode] = :enabled
+ end
+ it "should run exception handlers on early fail" do
+ expect(subject).to receive(:run_failed)
+ expect { subject.run }.to raise_error(Chef::Exceptions::RunFailedWrappingError) do |error|
+ expect(error.wrapped_errors.size).to eq 1
+ expect(error.wrapped_errors).to include(NoMethodError)
+ end
+ end
+ end
+
+ context 'when audit mode is disabled' do
+ before do
+ Chef::Config[:audit_mode] = :disabled
+ end
+ it "should run exception handlers on early fail" do
+ expect(subject).to receive(:run_failed)
+ expect { subject.run }.to raise_error(NoMethodError)
+ end
+ end
+ end
end
diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb
index 06178f7733..8d155c61ab 100644
--- a/spec/unit/config_spec.rb
+++ b/spec/unit/config_spec.rb
@@ -1,550 +1,31 @@
-#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
require 'spec_helper'
-require 'chef/exceptions'
-require 'chef/util/path_helper'
-describe Chef::Config do
- describe "config attribute writer: chef_server_url" do
- before do
- Chef::Config.chef_server_url = "https://junglist.gen.nz"
- end
-
- it "sets the server url" do
- expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz")
- end
-
- context "when the url has a leading space" do
- before do
- Chef::Config.chef_server_url = " https://junglist.gen.nz"
- end
-
- it "strips the space from the url when setting" do
- expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz")
- end
-
- end
-
- context "when the url is a frozen string" do
- before do
- Chef::Config.chef_server_url = " https://junglist.gen.nz".freeze
- end
-
- it "strips the space from the url when setting without raising an error" do
- expect(Chef::Config.chef_server_url).to eq("https://junglist.gen.nz")
- end
- end
-
- end
-
- describe "when configuring formatters" do
- # if TTY and not(force-logger)
- # formatter = configured formatter or default formatter
- # formatter goes to STDOUT/ERR
- # if log file is writeable
- # log level is configured level or info
- # log location is file
- # else
- # log level is warn
- # log location is STDERR
- # end
- # elsif not(TTY) and force formatter
- # formatter = configured formatter or default formatter
- # if log_location specified
- # formatter goes to log_location
- # else
- # formatter goes to STDOUT/ERR
- # end
- # else
- # formatter = "null"
- # log_location = configured-value or defualt
- # log_level = info or defualt
- # end
- #
- it "has an empty list of formatters by default" do
- expect(Chef::Config.formatters).to eq([])
- end
+require 'chef/config'
- it "configures a formatter with a short name" do
- Chef::Config.add_formatter(:doc)
- expect(Chef::Config.formatters).to eq([[:doc, nil]])
- end
+RSpec.describe Chef::Config do
- it "configures a formatter with a file output" do
- Chef::Config.add_formatter(:doc, "/var/log/formatter.log")
- expect(Chef::Config.formatters).to eq([[:doc, "/var/log/formatter.log"]])
+ 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 "class method: manage_secret_key" do
- before do
- allow(Chef::FileCache).to receive(:load).and_return(true)
- allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(false)
+ describe ":log_level" do
+ include_examples "deprecated by ohai but not deprecated" do
+ let(:option) { :log_level }
+ let(:value) { :debug }
end
-
- it "should generate and store a chef server cookie id" do
- expect(Chef::FileCache).to receive(:store).with("chef_server_cookie_id", /\w{40}/).and_return(true)
- Chef::Config.manage_secret_key
- end
-
- describe "when the filecache has a chef server cookie id key" do
- before do
- allow(Chef::FileCache).to receive(:has_key?).with("chef_server_cookie_id").and_return(true)
- end
-
- it "should not generate and store a chef server cookie id" do
- expect(Chef::FileCache).not_to receive(:store).with("chef_server_cookie_id", /\w{40}/)
- Chef::Config.manage_secret_key
- end
- end
-
end
- [ false, true ].each do |is_windows|
-
- context "On #{is_windows ? 'Windows' : 'Unix'}" do
- def to_platform(*args)
- Chef::Config.platform_specific_path(*args)
- end
-
- before :each do
- allow(Chef::Platform).to receive(:windows?).and_return(is_windows)
- end
-
- describe "class method: platform_specific_path" do
- if is_windows
- it "should return a windows path on windows systems" do
- path = "/etc/chef/cookbooks"
- allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' })
- # match on a regex that looks for the base path with an optional
- # system drive at the beginning (c:)
- # system drive is not hardcoded b/c it can change and b/c it is not present on linux systems
- expect(Chef::Config.platform_specific_path(path)).to eq("C:\\chef\\cookbooks")
- end
- else
- it "should return given path on non-windows systems" do
- path = "/etc/chef/cookbooks"
- expect(Chef::Config.platform_specific_path(path)).to eq("/etc/chef/cookbooks")
- end
- end
- end
-
- describe "default values" do
- let :primary_cache_path do
- if is_windows
- "#{Chef::Config.env['SYSTEMDRIVE']}\\chef"
- else
- "/var/chef"
- end
- end
-
- let :secondary_cache_path do
- if is_windows
- "#{Chef::Config[:user_home]}\\.chef"
- else
- "#{Chef::Config[:user_home]}/.chef"
- end
- end
-
- before do
- if is_windows
- allow(Chef::Config).to receive(:env).and_return({ 'SYSTEMDRIVE' => 'C:' })
- Chef::Config[:user_home] = 'C:\Users\charlie'
- else
- Chef::Config[:user_home] = '/Users/charlie'
- end
-
- allow(Chef::Config).to receive(:path_accessible?).and_return(false)
- end
-
- describe "Chef::Config[:cache_path]" do
- context "when /var/chef exists and is accessible" do
- it "defaults to /var/chef" do
- allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var/chef")).and_return(true)
- expect(Chef::Config[:cache_path]).to eq(primary_cache_path)
- end
- end
-
- context "when /var/chef does not exist and /var is accessible" do
- it "defaults to /var/chef" do
- allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false)
- allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(true)
- expect(Chef::Config[:cache_path]).to eq(primary_cache_path)
- end
- end
-
- context "when /var/chef does not exist and /var is not accessible" do
- it "defaults to $HOME/.chef" do
- allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(false)
- allow(Chef::Config).to receive(:path_accessible?).with(to_platform("/var")).and_return(false)
- expect(Chef::Config[:cache_path]).to eq(secondary_cache_path)
- end
- end
-
- context "when /var/chef exists and is not accessible" do
- it "defaults to $HOME/.chef" do
- allow(File).to receive(:exists?).with(to_platform("/var/chef")).and_return(true)
- allow(File).to receive(:readable?).with(to_platform("/var/chef")).and_return(true)
- allow(File).to receive(:writable?).with(to_platform("/var/chef")).and_return(false)
-
- expect(Chef::Config[:cache_path]).to eq(secondary_cache_path)
- end
- end
-
- context "when chef is running in local mode" do
- before do
- Chef::Config.local_mode = true
- end
-
- context "and config_dir is /a/b/c" do
- before do
- Chef::Config.config_dir to_platform('/a/b/c')
- end
-
- it "cache_path is /a/b/c/local-mode-cache" do
- expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache'))
- end
- end
-
- context "and config_dir is /a/b/c/" do
- before do
- Chef::Config.config_dir to_platform('/a/b/c/')
- end
-
- it "cache_path is /a/b/c/local-mode-cache" do
- expect(Chef::Config.cache_path).to eq(to_platform('/a/b/c/local-mode-cache'))
- end
- end
- end
- end
-
- it "Chef::Config[:file_backup_path] defaults to /var/chef/backup" do
- allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path)
- backup_path = is_windows ? "#{primary_cache_path}\\backup" : "#{primary_cache_path}/backup"
- expect(Chef::Config[:file_backup_path]).to eq(backup_path)
- end
-
- it "Chef::Config[:ssl_verify_mode] defaults to :verify_peer" do
- expect(Chef::Config[:ssl_verify_mode]).to eq(:verify_peer)
- end
-
- it "Chef::Config[:ssl_ca_path] defaults to nil" do
- expect(Chef::Config[:ssl_ca_path]).to be_nil
- end
-
- # TODO can this be removed?
- if !is_windows
- it "Chef::Config[:ssl_ca_file] defaults to nil" do
- expect(Chef::Config[:ssl_ca_file]).to be_nil
- end
- end
-
- it "Chef::Config[:data_bag_path] defaults to /var/chef/data_bags" do
- allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path)
- data_bag_path = is_windows ? "#{primary_cache_path}\\data_bags" : "#{primary_cache_path}/data_bags"
- expect(Chef::Config[:data_bag_path]).to eq(data_bag_path)
- end
-
- it "Chef::Config[:environment_path] defaults to /var/chef/environments" do
- allow(Chef::Config).to receive(:cache_path).and_return(primary_cache_path)
- environment_path = is_windows ? "#{primary_cache_path}\\environments" : "#{primary_cache_path}/environments"
- expect(Chef::Config[:environment_path]).to eq(environment_path)
- end
-
- describe "setting the config dir" do
-
- context "when the config file is /etc/chef/client.rb" do
-
- before do
- Chef::Config.config_file = to_platform("/etc/chef/client.rb")
- end
-
- it "config_dir is /etc/chef" do
- expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef"))
- end
-
- context "and chef is running in local mode" do
- before do
- Chef::Config.local_mode = true
- end
-
- it "config_dir is /etc/chef" do
- expect(Chef::Config.config_dir).to eq(to_platform("/etc/chef"))
- end
- end
-
- context "when config_dir is set to /other/config/dir/" do
- before do
- Chef::Config.config_dir = to_platform("/other/config/dir/")
- end
-
- it "yields the explicit value" do
- expect(Chef::Config.config_dir).to eq(to_platform("/other/config/dir/"))
- end
- end
-
- end
-
- context "when the user's home dir is /home/charlie/" do
- before do
- Chef::Config.user_home = to_platform("/home/charlie")
- end
-
- it "config_dir is /home/charlie/.chef/" do
- expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), ''))
- end
-
- context "and chef is running in local mode" do
- before do
- Chef::Config.local_mode = true
- end
-
- it "config_dir is /home/charlie/.chef/" do
- expect(Chef::Config.config_dir).to eq(Chef::Util::PathHelper.join(to_platform("/home/charlie/.chef"), ''))
- end
- end
- end
-
- end
-
- if is_windows
- describe "finding the windows embedded dir" do
- let(:default_config_location) { "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
- let(:alternate_install_location) { "c:/my/alternate/install/place/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
- let(:non_omnibus_location) { "c:/my/dev/stuff/lib/ruby/gems/1.9.1/gems/chef-11.6.0/lib/chef/config.rb" }
-
- let(:default_ca_file) { "c:/opscode/chef/embedded/ssl/certs/cacert.pem" }
-
- it "finds the embedded dir in the default location" do
- allow(Chef::Config).to receive(:_this_file).and_return(default_config_location)
- expect(Chef::Config.embedded_dir).to eq("c:/opscode/chef/embedded")
- end
-
- it "finds the embedded dir in a custom install location" do
- allow(Chef::Config).to receive(:_this_file).and_return(alternate_install_location)
- expect(Chef::Config.embedded_dir).to eq("c:/my/alternate/install/place/chef/embedded")
- end
-
- it "doesn't error when not in an omnibus install" do
- allow(Chef::Config).to receive(:_this_file).and_return(non_omnibus_location)
- expect(Chef::Config.embedded_dir).to be_nil
- end
-
- it "sets the ssl_ca_cert path if the cert file is available" do
- allow(Chef::Config).to receive(:_this_file).and_return(default_config_location)
- allow(File).to receive(:exist?).with(default_ca_file).and_return(true)
- expect(Chef::Config.ssl_ca_file).to eq(default_ca_file)
- end
- end
- end
- end
-
- describe "Chef::Config[:user_home]" do
- it "should set when HOME is provided" do
- expected = to_platform("/home/kitten")
- allow(Chef::Config).to receive(:env).and_return({ 'HOME' => expected })
- expect(Chef::Config[:user_home]).to eq(expected)
- end
-
- it "should be set when only USERPROFILE is provided" do
- expected = to_platform("/users/kitten")
- allow(Chef::Config).to receive(:env).and_return({ 'USERPROFILE' => expected })
- expect(Chef::Config[:user_home]).to eq(expected)
- end
-
- it "falls back to the current working directory when HOME and USERPROFILE is not set" do
- allow(Chef::Config).to receive(:env).and_return({})
- expect(Chef::Config[:user_home]).to eq(Dir.pwd)
- end
- end
-
- describe "Chef::Config[:encrypted_data_bag_secret]" do
- let(:db_secret_default_path){ to_platform("/etc/chef/encrypted_data_bag_secret") }
-
- before do
- allow(File).to receive(:exist?).with(db_secret_default_path).and_return(secret_exists)
- end
-
- context "/etc/chef/encrypted_data_bag_secret exists" do
- let(:secret_exists) { true }
- it "sets the value to /etc/chef/encrypted_data_bag_secret" do
- expect(Chef::Config[:encrypted_data_bag_secret]).to eq db_secret_default_path
- end
- end
-
- context "/etc/chef/encrypted_data_bag_secret does not exist" do
- let(:secret_exists) { false }
- it "sets the value to nil" do
- expect(Chef::Config[:encrypted_data_bag_secret]).to be_nil
- end
- end
- end
-
- describe "Chef::Config[:event_handlers]" do
- it "sets a event_handlers to an empty array by default" do
- expect(Chef::Config[:event_handlers]).to eq([])
- end
- it "should be able to add custom handlers" do
- o = Object.new
- Chef::Config[:event_handlers] << o
- expect(Chef::Config[:event_handlers]).to be_include(o)
- end
- end
-
- describe "Chef::Config[:user_valid_regex]" do
- context "on a platform that is not Windows" do
- it "allows one letter usernames" do
- any_match = Chef::Config[:user_valid_regex].any? { |regex| regex.match('a') }
- expect(any_match).to be_truthy
- end
- end
- end
-
- describe "Chef::Config[:internal_locale]" do
- let(:shell_out) do
- double("Chef::Mixin::ShellOut double", :exitstatus => 0, :stdout => locales)
- end
-
- let(:locales) { locale_array.join("\n") }
-
- before do
- allow(Chef::Config).to receive(:shell_out_with_systems_locale!).with("locale -a").and_return(shell_out)
- end
-
- shared_examples_for "a suitable locale" do
- it "returns an English UTF-8 locale" do
- expect(Chef::Log).to_not receive(:warn).with(/Please install an English UTF-8 locale for Chef to use/)
- expect(Chef::Log).to_not receive(:debug).with(/Defaulting to locale en_US.UTF-8 on Windows/)
- expect(Chef::Log).to_not receive(:debug).with(/No usable locale -a command found/)
- expect(Chef::Config.guess_internal_locale).to eq expected_locale
- end
- end
-
- context "when the result includes 'C.UTF-8'" do
- include_examples "a suitable locale" do
- let(:locale_array) { [expected_locale, "en_US.UTF-8"] }
- let(:expected_locale) { "C.UTF-8" }
- end
- end
-
- context "when the result includes 'en_US.UTF-8'" do
- include_examples "a suitable locale" do
- let(:locale_array) { ["en_CA.UTF-8", expected_locale, "en_NZ.UTF-8"] }
- let(:expected_locale) { "en_US.UTF-8" }
- end
- end
-
- context "when the result includes 'en_US.utf8'" do
- include_examples "a suitable locale" do
- let(:locale_array) { ["en_CA.utf8", "en_US.utf8", "en_NZ.utf8"] }
- let(:expected_locale) { "en_US.UTF-8" }
- end
- end
-
- context "when the result includes 'en.UTF-8'" do
- include_examples "a suitable locale" do
- let(:locale_array) { ["en.ISO8859-1", expected_locale] }
- let(:expected_locale) { "en.UTF-8" }
- end
- end
-
- context "when the result includes 'en_*.UTF-8'" do
- include_examples "a suitable locale" do
- let(:locale_array) { [expected_locale, "en_CA.UTF-8", "en_GB.UTF-8"] }
- let(:expected_locale) { "en_AU.UTF-8" }
- end
- end
-
- context "when the result includes 'en_*.utf8'" do
- include_examples "a suitable locale" do
- let(:locale_array) { ["en_AU.utf8", "en_CA.utf8", "en_GB.utf8"] }
- let(:expected_locale) { "en_AU.UTF-8" }
- end
- end
-
- context "when the result does not include 'en_*.UTF-8'" do
- let(:locale_array) { ["af_ZA", "af_ZA.ISO8859-1", "af_ZA.ISO8859-15", "af_ZA.UTF-8"] }
-
- it "should fall back to C locale" do
- expect(Chef::Log).to receive(:warn).with("Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support.")
- expect(Chef::Config.guess_internal_locale).to eq 'C'
- end
- end
-
- context "on error" do
- let(:locale_array) { [] }
-
- before do
- allow(Chef::Config).to receive(:shell_out_with_systems_locale!).and_raise("THIS IS AN ERROR")
- end
-
- it "should default to 'en_US.UTF-8'" do
- if is_windows
- expect(Chef::Log).to receive(:debug).with("Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else.")
- else
- expect(Chef::Log).to receive(:debug).with("No usable locale -a command found, assuming you have en_US.UTF-8 installed.")
- end
- expect(Chef::Config.guess_internal_locale).to eq "en_US.UTF-8"
- end
- end
- end
+ describe ":log_location" do
+ include_examples "deprecated by ohai but not deprecated" do
+ let(:option) { :log_location }
+ let(:value) { "path/to/log" }
end
end
- describe "Treating deprecation warnings as errors" do
-
- context "when using our default RSpec configuration" do
-
- it "defaults to treating deprecation warnings as errors" do
- expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true)
- end
-
- it "sets CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS environment variable" do
- expect(ENV['CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS']).to eq("1")
- end
-
- it "treats deprecation warnings as errors in child processes when testing" do
- # Doing a full integration test where we launch a child process is slow
- # and liable to break for weird reasons (bundler env stuff, etc.), so
- # we're just checking that the presence of the environment variable
- # causes treat_deprecation_warnings_as_errors to be set to true after a
- # config reset.
- Chef::Config.reset
- expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(true)
- end
-
- end
-
- context "outside of our test environment" do
-
- before do
- ENV.delete('CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS')
- Chef::Config.reset
- end
-
- it "defaults to NOT treating deprecation warnings as errors" do
- expect(Chef::Config[:treat_deprecation_warnings_as_errors]).to be(false)
- end
- end
-
-
- end
end
diff --git a/spec/unit/cookbook/cookbook_version_loader_spec.rb b/spec/unit/cookbook/cookbook_version_loader_spec.rb
index 2c4ad11787..23ffc21f7f 100644
--- a/spec/unit/cookbook/cookbook_version_loader_spec.rb
+++ b/spec/unit/cookbook/cookbook_version_loader_spec.rb
@@ -20,7 +20,7 @@ require 'spec_helper'
describe Chef::Cookbook::CookbookVersionLoader do
before do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
end
describe "loading a cookbook" do
diff --git a/spec/unit/cookbook/file_vendor_spec.rb b/spec/unit/cookbook/file_vendor_spec.rb
index 4fad7d5808..145541a63f 100644
--- a/spec/unit/cookbook/file_vendor_spec.rb
+++ b/spec/unit/cookbook/file_vendor_spec.rb
@@ -21,9 +21,6 @@ describe Chef::Cookbook::FileVendor do
let(:file_vendor_class) { Class.new(described_class) }
- # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest
- let(:manifest) { {:cookbook_name => "bob"} }
-
context "when configured to fetch files over http" do
let(:http) { double("Chef::REST") }
@@ -40,19 +37,42 @@ describe Chef::Cookbook::FileVendor do
expect(file_vendor_class.initialization_options).to eq(http)
end
- it "creates a RemoteFileVendor for a given manifest" do
- file_vendor = file_vendor_class.create_from_manifest(manifest)
- expect(file_vendor).to be_a_kind_of(Chef::Cookbook::RemoteFileVendor)
- expect(file_vendor.rest).to eq(http)
- expect(file_vendor.cookbook_name).to eq("bob")
+ context "with a manifest from a cookbook version" do
+
+ # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest
+ let(:manifest) { {:cookbook_name => "bob", :name => "bob-1.2.3"} }
+
+ it "creates a RemoteFileVendor for a given manifest" do
+ file_vendor = file_vendor_class.create_from_manifest(manifest)
+ expect(file_vendor).to be_a_kind_of(Chef::Cookbook::RemoteFileVendor)
+ expect(file_vendor.rest).to eq(http)
+ expect(file_vendor.cookbook_name).to eq("bob")
+ end
+
end
+ context "with a manifest from a cookbook artifact" do
+
+ # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest
+ let(:manifest) { {:name => "bob"} }
+
+ it "creates a RemoteFileVendor for a given manifest" do
+ file_vendor = file_vendor_class.create_from_manifest(manifest)
+ expect(file_vendor).to be_a_kind_of(Chef::Cookbook::RemoteFileVendor)
+ expect(file_vendor.rest).to eq(http)
+ expect(file_vendor.cookbook_name).to eq("bob")
+ end
+
+ end
end
context "when configured to load files from disk" do
let(:cookbook_path) { %w[/var/chef/cookbooks /var/chef/other_cookbooks] }
+ # A manifest is a Hash of the format defined by Chef::CookbookVersion#manifest
+ let(:manifest) { {:cookbook_name => "bob"} }
+
before do
file_vendor_class.fetch_from_disk(cookbook_path)
end
diff --git a/spec/unit/cookbook/metadata_spec.rb b/spec/unit/cookbook/metadata_spec.rb
index 760ae5dd2a..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
@@ -304,6 +309,21 @@ describe Chef::Cookbook::Metadata do
end
end
end
+
+ it "strips out self-dependencies", :chef_lt_13_only do
+ metadata.name('foo')
+ expect(Chef::Log).to receive(:warn).with(
+ "Ignoring self-dependency in cookbook foo, please remove it (in the future this will be fatal)."
+ )
+ metadata.depends('foo')
+ expect(metadata.dependencies).to eql({})
+ end
+
+ it "errors on self-dependencies", :chef_gte_13_only do
+ metadata.name('foo')
+ expect { metadata.depends('foo') }.to raise_error
+ # FIXME: add the error type
+ end
end
describe "attribute groupings" do
@@ -345,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
@@ -386,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'])
@@ -684,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))
@@ -719,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 471fc01831..764829c387 100644
--- a/spec/unit/cookbook/syntax_check_spec.rb
+++ b/spec/unit/cookbook/syntax_check_spec.rb
@@ -21,7 +21,7 @@ require "chef/cookbook/syntax_check"
describe Chef::Cookbook::SyntaxCheck do
before do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
end
let(:cookbook_path) { File.join(CHEF_SPEC_DATA, 'cookbooks', 'openldap') }
@@ -53,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
openldap_variable_stuff.conf.erb
test.erb
diff --git a/spec/unit/cookbook_loader_spec.rb b/spec/unit/cookbook_loader_spec.rb
index 45a985bafd..b1384bffe7 100644
--- a/spec/unit/cookbook_loader_spec.rb
+++ b/spec/unit/cookbook_loader_spec.rb
@@ -20,7 +20,7 @@ require 'spec_helper'
describe Chef::CookbookLoader do
before do
- allow(Chef::Platform).to receive(:windows?) {false}
+ allow(ChefConfig).to receive(:windows?) {false}
end
let(:repo_paths) do
[
diff --git a/spec/unit/cookbook_manifest_spec.rb b/spec/unit/cookbook_manifest_spec.rb
index 938f72c743..f985942e09 100644
--- a/spec/unit/cookbook_manifest_spec.rb
+++ b/spec/unit/cookbook_manifest_spec.rb
@@ -24,6 +24,8 @@ describe Chef::CookbookManifest do
let(:version) { "1.2.3" }
+ let(:identifier) { "9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b" }
+
let(:metadata) do
Chef::Cookbook::Metadata.new.tap do |m|
m.version(version)
@@ -35,6 +37,7 @@ describe Chef::CookbookManifest do
let(:cookbook_version) do
Chef::CookbookVersion.new("tatft", cookbook_root).tap do |c|
c.metadata = metadata
+ c.identifier = identifier
end
end
@@ -212,12 +215,26 @@ describe Chef::CookbookManifest do
let(:policy_mode) { true }
+ let(:cookbook_manifest_hash) { cookbook_manifest.to_hash }
+
+ it "sets the identifier in the manifest data" do
+ expect(cookbook_manifest_hash["identifier"]).to eq("9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b")
+ end
+
+ it "sets the name to just the name" do
+ expect(cookbook_manifest_hash["name"]).to eq("tatft")
+ end
+
+ it "does not set a 'cookbook_name' field" do
+ expect(cookbook_manifest_hash).to_not have_key("cookbook_name")
+ end
+
it "gives the save URL" do
- expect(cookbook_manifest.save_url).to eq("cookbook_artifacts/tatft/1.2.3")
+ expect(cookbook_manifest.save_url).to eq("cookbook_artifacts/tatft/9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b")
end
it "gives the force save URL" do
- expect(cookbook_manifest.force_save_url).to eq("cookbook_artifacts/tatft/1.2.3?force=true")
+ expect(cookbook_manifest.force_save_url).to eq("cookbook_artifacts/tatft/9e10455ce2b4a4e29424b7064b1d67a1a25c9d3b?force=true")
end
end
diff --git a/spec/unit/cookbook_site_streaming_uploader_spec.rb b/spec/unit/cookbook_site_streaming_uploader_spec.rb
index ef0f649163..0041a142dc 100644
--- a/spec/unit/cookbook_site_streaming_uploader_spec.rb
+++ b/spec/unit/cookbook_site_streaming_uploader_spec.rb
@@ -121,27 +121,6 @@ describe Chef::CookbookSiteStreamingUploader do
})
end
- describe "http verify mode" do
- before do
- @uri = "https://cookbooks.dummy.com/api/v1/cookbooks"
- uri_info = URI.parse(@uri)
- @http = Net::HTTP.new(uri_info.host, uri_info.port)
- expect(Net::HTTP).to receive(:new).with(uri_info.host, uri_info.port).and_return(@http)
- end
-
- it "should be VERIFY_NONE when ssl_verify_mode is :verify_none" do
- Chef::Config[:ssl_verify_mode] = :verify_none
- Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
- expect(@http.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
- end
-
- it "should be VERIFY_PEER when ssl_verify_mode is :verify_peer" do
- Chef::Config[:ssl_verify_mode] = :verify_peer
- Chef::CookbookSiteStreamingUploader.make_request(:post, @uri, 'bill', @secret_filename)
- expect(@http.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER)
- end
- end
-
end # make_request
describe "StreamPart" do
diff --git a/spec/unit/cookbook_spec.rb b/spec/unit/cookbook_spec.rb
index 7b3cda2af1..f36b031309 100644
--- a/spec/unit/cookbook_spec.rb
+++ b/spec/unit/cookbook_spec.rb
@@ -59,15 +59,6 @@ describe Chef::CookbookVersion do
expect(@cookbook.fully_qualified_recipe_names.include?("openldap::three")).to eq(true)
end
- it "should find a preferred file" do
- skip
- end
-
- it "should not return an unchanged preferred file" do
- pending
- expect(@cookbook.preferred_filename(@node, :files, 'a-filename', 'the-checksum')).to be_nil
- end
-
it "should raise an ArgumentException if you try to load a bad recipe name" do
expect { @cookbook.load_recipe("doesnt_exist", @node) }.to raise_error(ArgumentError)
end
diff --git a/spec/unit/cookbook_uploader_spec.rb b/spec/unit/cookbook_uploader_spec.rb
index 152e5373f0..76727c18e2 100644
--- a/spec/unit/cookbook_uploader_spec.rb
+++ b/spec/unit/cookbook_uploader_spec.rb
@@ -25,11 +25,17 @@ describe Chef::CookbookUploader do
let(:cookbook_loader) do
loader = Chef::CookbookLoader.new(File.join(CHEF_SPEC_DATA, "cookbooks"))
loader.load_cookbooks
+ loader.cookbooks_by_name["apache2"].identifier = apache2_identifier
+ loader.cookbooks_by_name["java"].identifier = java_identifier
loader
end
+ let(:apache2_identifier) { "6644e6cb2ade90b8aff2ebb44728958fbc939ebf" }
+
let(:apache2_cookbook) { cookbook_loader.cookbooks_by_name["apache2"] }
+ let(:java_identifier) { "edd40c30c4e0ebb3658abde4620597597d2e9c17" }
+
let(:java_cookbook) { cookbook_loader.cookbooks_by_name["java"] }
let(:cookbooks_to_upload) { [apache2_cookbook, java_cookbook] }
@@ -175,7 +181,7 @@ describe Chef::CookbookUploader do
let(:policy_mode) { true }
def expected_save_url(cookbook)
- "cookbook_artifacts/#{cookbook.name}/#{cookbook.version}"
+ "cookbook_artifacts/#{cookbook.name}/#{cookbook.identifier}"
end
it "uploads all files in a sandbox transaction, then creates cookbooks on the server using cookbook_artifacts API" do
diff --git a/spec/unit/cookbook_version_spec.rb b/spec/unit/cookbook_version_spec.rb
index 440dd9da6c..2bccddcaec 100644
--- a/spec/unit/cookbook_version_spec.rb
+++ b/spec/unit/cookbook_version_spec.rb
@@ -306,26 +306,6 @@ describe Chef::CookbookVersion do
subject(:cbv) { Chef::CookbookVersion.new("version validation", '/tmp/blah') }
- describe "HTTP Resource behaviors", pending: "will be deprected when CookbookManifest API is stablized" do
-
- it "errors on #save_url" do
- expect { cbv.save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- it "errors on #force_save_url" do
- expect { cbv.force_save_url }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- it "errors on #to_hash" do
- expect { cbv.to_hash }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- it "errors on #to_json" do
- expect { cbv.to_json }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
- end
-
- end
-
it "errors on #status and #status=" do
expect { cbv.status = :wat }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
expect { cbv.status }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
@@ -356,7 +336,7 @@ describe Chef::CookbookVersion do
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { Chef::CookbookVersion.new("tatft", '/tmp/blah') }
end
diff --git a/spec/unit/data_bag_item_spec.rb b/spec/unit/data_bag_item_spec.rb
index 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 f6db1e222a..13b835d120 100644
--- a/spec/unit/data_bag_spec.rb
+++ b/spec/unit/data_bag_spec.rb
@@ -22,7 +22,7 @@ require 'chef/data_bag'
describe Chef::DataBag do
before(:each) do
@data_bag = Chef::DataBag.new
- allow(Chef::Platform)::to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
end
describe "initialize" do
@@ -73,7 +73,7 @@ describe Chef::DataBag do
expect(@deserial.send(t.to_sym)).to eq(@data_bag.send(t.to_sym))
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @data_bag }
end
end
diff --git a/spec/unit/deprecation_spec.rb b/spec/unit/deprecation_spec.rb
index f824cb7c76..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
@@ -95,4 +92,59 @@ describe Chef::Deprecation do
expect { test_instance.deprecated_method(10) }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
end
+ context "When a class has deprecated_attr, _reader and _writer" do
+ before(:context) do
+ class DeprecatedAttrTest
+ extend Chef::Mixin::Deprecation
+ def initialize
+ @a = @r = @w = 1
+ end
+ deprecated_attr :a, "a"
+ deprecated_attr_reader :r, "r"
+ deprecated_attr_writer :w, "w"
+ end
+ end
+
+ it "The deprecated_attr emits warnings" do
+ test = DeprecatedAttrTest.new
+ expect { test.a = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ expect { test.a }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ end
+
+ it "The deprecated_attr_writer emits warnings, and does not create a reader" do
+ test = DeprecatedAttrTest.new
+ expect { test.w = 10 }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ expect { test.w }.to raise_error(NoMethodError)
+ end
+
+ it "The deprecated_attr_reader emits warnings, and does not create a writer" do
+ test = DeprecatedAttrTest.new
+ expect { test.r = 10 }.to raise_error(NoMethodError)
+ expect { test.r }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
+ end
+
+ context "With deprecation warnings not throwing exceptions" do
+ before do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ it "The deprecated_attr can be written to and read from" do
+ test = DeprecatedAttrTest.new
+ test.a = 10
+ expect(test.a).to eq 10
+ end
+
+ it "The deprecated_attr_reader can be read from" do
+ test = DeprecatedAttrTest.new
+ expect(test.r).to eq 1
+ end
+
+ it "The deprecated_attr_writer can be written to" do
+ test = DeprecatedAttrTest.new
+ test.w = 10
+ expect(test.instance_eval { @w }).to eq 10
+ end
+ end
+ end
+
end
diff --git a/spec/unit/dsl/reboot_pending_spec.rb b/spec/unit/dsl/reboot_pending_spec.rb
index 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/dsl/resources_spec.rb b/spec/unit/dsl/resources_spec.rb
new file mode 100644
index 0000000000..581c835290
--- /dev/null
+++ b/spec/unit/dsl/resources_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Noah Kantrowitz (<noah@coderanger.net>)
+# Copyright:: Copyright (c) 2015 Noah Kantrowitz
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/dsl/resources'
+
+describe Chef::DSL::Resources do
+ let(:declared_resources) { [] }
+ let(:test_class) do
+ r = declared_resources
+ Class.new do
+ include Chef::DSL::Resources
+ define_method(:declare_resource) do |dsl_name, name, _created_at, &_block|
+ r << [dsl_name, name]
+ end
+ end
+ end
+ subject { declared_resources }
+ after do
+ # Always clean up after ourselves.
+ described_class.remove_resource_dsl(:test_resource)
+ end
+
+ context 'with a resource added' do
+ before do
+ Chef::DSL::Resources.add_resource_dsl(:test_resource)
+ test_class.new.instance_eval do
+ test_resource 'test_name' do
+ end
+ end
+ end
+ it { is_expected.to eq [[:test_resource, 'test_name']]}
+ end
+
+ context 'with no resource added' do
+ subject do
+ test_class.new.instance_eval do
+ test_resource 'test_name' do
+ end
+ end
+ end
+
+ it { expect { subject }.to raise_error NoMethodError }
+ end
+
+ context 'with a resource added and removed' do
+ before do
+ Chef::DSL::Resources.add_resource_dsl(:test_resource)
+ Chef::DSL::Resources.remove_resource_dsl(:test_resource)
+ end
+ subject do
+ test_class.new.instance_eval do
+ test_resource 'test_name' do
+ end
+ end
+ end
+
+ it { expect { subject }.to raise_error NoMethodError }
+ end
+
+ context 'with a nameless resource' do
+ before do
+ Chef::DSL::Resources.add_resource_dsl(:test_resource)
+ test_class.new.instance_eval do
+ test_resource { }
+ end
+ end
+ it { is_expected.to eq [[:test_resource, nil]]}
+ end
+end
diff --git a/spec/unit/environment_spec.rb b/spec/unit/environment_spec.rb
index 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
new file mode 100644
index 0000000000..1014feea89
--- /dev/null
+++ b/spec/unit/event_dispatch/dispatcher_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/event_dispatch/dispatcher'
+
+describe Chef::EventDispatch::Dispatcher do
+
+ subject(:dispatcher) { Chef::EventDispatch::Dispatcher.new }
+
+ let(:event_sink) { instance_double("Chef::EventDispatch::Base") }
+
+ it "has no subscribers by default" do
+ expect(dispatcher.subscribers).to be_empty
+ end
+
+ context "when an event sink is registered" do
+
+ before do
+ dispatcher.register(event_sink)
+ end
+
+ it "it has the event sink as a subscriber" do
+ expect(dispatcher.subscribers.size).to eq(1)
+ expect(dispatcher.subscribers.first).to eq(event_sink)
+ end
+
+ it "forwards events to the subscribed event sink" do
+ # the events all have different arity and such so we just hit a few different events:
+
+ expect(event_sink).to receive(:run_start).with("12.4.0")
+ dispatcher.run_start("12.4.0")
+
+ cookbook_version = double("cookbook_version")
+ expect(event_sink).to receive(:synchronized_cookbook).with("apache2", cookbook_version)
+ dispatcher.synchronized_cookbook("apache2", cookbook_version)
+
+ exception = StandardError.new("foo")
+ expect(event_sink).to receive(:recipe_file_load_failed).with("/path/to/file.rb", exception)
+ dispatcher.recipe_file_load_failed("/path/to/file.rb", exception)
+ end
+
+ context "when an event sink has fewer arguments for an event" do
+ # Can't use a double because they don't report arity correctly.
+ let(:event_sink) do
+ Class.new(Chef::EventDispatch::Base) do
+ attr_reader :synchronized_cookbook_args
+ def synchronized_cookbook(cookbook_name)
+ @synchronized_cookbook_args = [cookbook_name]
+ end
+ end.new
+ end
+
+ it "trims the arugment list" do
+ cookbook_version = double("cookbook_version")
+ dispatcher.synchronized_cookbook("apache2", cookbook_version)
+ expect(event_sink.synchronized_cookbook_args).to eq ["apache2"]
+ end
+ end
+
+ end
+
+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 d35ecc8ec8..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
@@ -113,7 +113,7 @@ describe Chef::Exceptions do
context "initialized with 1 error and nil" do
let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), nil) }
let(:num_errors) { 1 }
- let(:backtrace) { ["1) RuntimeError - foo", ""] }
+ let(:backtrace) { ["1) RuntimeError - foo"] }
include_examples "RunFailedWrappingError expectations"
end
@@ -121,7 +121,7 @@ describe Chef::Exceptions do
context "initialized with 2 errors" do
let(:e) { Chef::Exceptions::RunFailedWrappingError.new(RuntimeError.new("foo"), RuntimeError.new("bar")) }
let(:num_errors) { 2 }
- let(:backtrace) { ["1) RuntimeError - foo", "", "2) RuntimeError - bar", ""] }
+ let(:backtrace) { ["1) RuntimeError - foo", "", "2) RuntimeError - bar"] }
include_examples "RunFailedWrappingError expectations"
end
diff --git a/spec/unit/file_content_management/deploy/mv_windows_spec.rb b/spec/unit/file_content_management/deploy/mv_windows_spec.rb
index c52001cd26..2d1981befc 100644
--- a/spec/unit/file_content_management/deploy/mv_windows_spec.rb
+++ b/spec/unit/file_content_management/deploy/mv_windows_spec.rb
@@ -115,6 +115,66 @@ describe Chef::FileContentManagement::Deploy::MvWindows do
end
+ context "and the target file has null dacl and sacl" do
+
+ before do
+ allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true)
+ allow(target_file_security_descriptor).to receive(:dacl).and_return(nil)
+ allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false)
+
+ allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true)
+ allow(target_file_security_descriptor).to receive(:sacl).and_return(nil)
+ allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false)
+
+ expect(updated_target_security_object).to receive(:set_dacl).with(nil, false)
+ expect(updated_target_security_object).to receive(:set_sacl).with(nil, false)
+ end
+
+
+ it "fixes up permissions and moves the file into place" do
+ content_deployer.deploy(staging_file_path, target_file_path)
+ end
+
+ end
+
+ context "and the target has an empty dacl and sacl" do
+ let(:original_target_file_dacl) { [] }
+ let(:original_target_file_sacl) { [] }
+
+ let(:empty_dacl) { double("Windows ACL with no dacl ACEs") }
+ let(:empty_sacl) { double("Windows ACL with no sacl ACEs") }
+
+ before do
+ allow(target_file_security_descriptor).to receive(:dacl_present?).and_return(true)
+ allow(target_file_security_descriptor).to receive(:dacl_inherits?).and_return(false)
+
+ allow(target_file_security_descriptor).to receive(:dacl).and_return(original_target_file_dacl)
+ expect(Chef::ReservedNames::Win32::Security::ACL).
+ to receive(:create).
+ with([]).
+ and_return(empty_dacl)
+
+
+ allow(target_file_security_descriptor).to receive(:sacl_present?).and_return(true)
+ allow(target_file_security_descriptor).to receive(:sacl_inherits?).and_return(false)
+
+ allow(target_file_security_descriptor).to receive(:sacl).and_return(original_target_file_sacl)
+ expect(Chef::ReservedNames::Win32::Security::ACL).
+ to receive(:create).
+ with([]).
+ and_return(empty_sacl)
+
+
+ expect(updated_target_security_object).to receive(:set_dacl).with(empty_dacl, false)
+ expect(updated_target_security_object).to receive(:set_sacl).with(empty_sacl, false)
+ end
+
+
+ it "fixes up permissions and moves the file into place" do
+ content_deployer.deploy(staging_file_path, target_file_path)
+ end
+ end
+
context "and the target has a dacl and sacl" do
let(:inherited_dacl_ace) { double("Windows dacl ace (inherited)", :inherited? => true) }
let(:not_inherited_dacl_ace) { double("Windows dacl ace (not inherited)", :inherited? => false) }
diff --git a/spec/unit/formatters/doc_spec.rb b/spec/unit/formatters/doc_spec.rb
new file mode 100644
index 0000000000..eb98f5abd3
--- /dev/null
+++ b/spec/unit/formatters/doc_spec.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Formatters::Base do
+
+ let(:out) { StringIO.new }
+ let(:err) { StringIO.new }
+
+ subject(:formatter) { Chef::Formatters::Doc.new(out, err) }
+
+ it "prints a policyfile's name and revision ID" do
+ minimal_policyfile = {
+ "revision_id"=> "613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073",
+ "name"=> "jenkins",
+ "run_list"=> [
+ "recipe[apt::default]",
+ "recipe[java::default]",
+ "recipe[jenkins::master]",
+ "recipe[policyfile_demo::default]"
+ ],
+ "cookbook_locks"=> { }
+ }
+
+ formatter.policyfile_loaded(minimal_policyfile)
+ expect(out.string).to include("Using policy 'jenkins' at revision '613f803bdd035d574df7fa6da525b38df45a74ca82b38b79655efed8a189e073'")
+ end
+
+ it "prints cookbook name and version" do
+ cookbook_version = double(name: "apache2", version: "1.2.3")
+ formatter.synchronized_cookbook("apache2", cookbook_version)
+ expect(out.string).to include("- apache2 (1.2.3")
+ end
+
+end
diff --git a/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
new file mode 100644
index 0000000000..b8c2de2b8b
--- /dev/null
+++ b/spec/unit/formatters/error_inspectors/api_error_formatting_spec.rb
@@ -0,0 +1,77 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/formatters/error_inspectors/api_error_formatting'
+
+describe Chef::Formatters::APIErrorFormatting do
+ let(:class_instance) { (Class.new { include Chef::Formatters::APIErrorFormatting }).new }
+ let(:error_description) { instance_double(Chef::Formatters::ErrorDescription) }
+ let(:response) { double("response") }
+ before do
+ allow(response).to receive(:body)
+ end
+
+
+ context "when describe_406_error is called" do
+ context "when response['x-ops-server-api-version'] exists" do
+ let(:min_version) { "2" }
+ let(:max_version) { "5" }
+ let(:request_version) { "30" }
+ let(:return_hash) {
+ {
+ "min_version" => min_version,
+ "max_version" => max_version,
+ "request_version" => request_version
+ }
+ }
+
+ before do
+ # mock out the header
+ allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash))
+ end
+
+ it "prints an error about client and server API version incompatibility with a min API version" do
+ expect(error_description).to receive(:section).with("Incompatible server API version:",/a min API version of #{min_version}/)
+ class_instance.describe_406_error(error_description, response)
+ end
+
+ it "prints an error about client and server API version incompatibility with a max API version" do
+ expect(error_description).to receive(:section).with("Incompatible server API version:",/a max API version of #{max_version}/)
+ class_instance.describe_406_error(error_description, response)
+ end
+
+ it "prints an error describing the request API version" do
+ expect(error_description).to receive(:section).with("Incompatible server API version:",/a request with an API version of #{request_version}/)
+ class_instance.describe_406_error(error_description, response)
+ end
+ end
+
+ context "when response.body['error'] != 'invalid-x-ops-server-api-version'" do
+
+ before do
+ allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(nil)
+ end
+
+ it "forwards the error_description to describe_http_error" do
+ expect(class_instance).to receive(:describe_http_error).with(error_description)
+ class_instance.describe_406_error(error_description, response)
+ end
+ end
+ end
+end
diff --git a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
index ac19e91922..3c8d5dfa29 100644
--- a/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
+++ b/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb
@@ -37,69 +37,148 @@ end
E
describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do
- before do
- @node_name = "test-node.example.com"
- @description = Chef::Formatters::ErrorDescription.new("Error Evaluating File:")
- @exception = NoMethodError.new("undefined method `this_is_not_a_valid_method' for Chef::Resource::File")
- @outputter = Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR)
- #@outputter = Chef::Formatters::IndentableOutputStream.new(STDOUT, STDERR)
- end
+ let(:node_name) { "test-node.example.com" }
- describe "when scrubbing backtraces" do
- it "shows backtrace lines from cookbook files" do
- # Error inspector originally used file_cache_path which is incorrect on
- # chef-solo. Using cookbook_path should do the right thing for client and
- # solo.
- allow(Chef::Config).to receive(:cookbook_path).and_return([ "/home/someuser/dev-laptop/cookbooks" ])
- @trace = [
- "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
- "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
- "/home/someuser/.multiruby/gems/chef/lib/chef/client.rb:123:in `run'"
- ]
- @exception.set_backtrace(@trace)
- @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
- @inspector = described_class.new(@path, @exception)
+ let(:description) { Chef::Formatters::ErrorDescription.new("Error Evaluating File:") }
- @expected_filtered_trace = [
- "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
- "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
- ]
- expect(@inspector.filtered_bt).to eq(@expected_filtered_trace)
- end
+ let(:exception) do
+ e = NoMethodError.new("undefined method `this_is_not_a_valid_method' for Chef::Resource::File")
+ e.set_backtrace(trace)
+ e
end
- describe "when explaining an error in the compile phase" do
- before do
- allow(Chef::Config).to receive(:cookbook_path).and_return([ "/var/chef/cache/cookbooks" ])
- recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
- expect(IO).to receive(:readlines).with("/var/chef/cache/cookbooks/syntax-err/recipes/default.rb").and_return(recipe_lines)
- @trace = [
- "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
- "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
- "/usr/local/lib/ruby/gems/chef/lib/chef/client.rb:123:in `run'" # should not display
- ]
- @exception.set_backtrace(@trace)
- @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
- @inspector = described_class.new(@path, @exception)
- @inspector.add_explanation(@description)
+ # Change to $stdout to print error messages for manual inspection
+ let(:stdout) { StringIO.new }
+
+ let(:outputter) { Chef::Formatters::IndentableOutputStream.new(StringIO.new, STDERR) }
+
+ subject(:inspector) { described_class.new(path_to_failed_file, exception) }
+
+ describe "finding the code responsible for the error" do
+
+ context "when the stacktrace includes cookbook files" do
+
+ let(:trace) do
+ [
+ "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
+ "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
+ "/home/someuser/.multiruby/gems/chef/lib/chef/client.rb:123:in `run'"
+ ]
+ end
+
+ let(:expected_filtered_trace) do
+ [
+ "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:14:in `from_file'",
+ "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb:11:in `from_file'",
+ ]
+ end
+
+ let(:path_to_failed_file) { "/home/someuser/dev-laptop/cookbooks/syntax-err/recipes/default.rb" }
+
+ before do
+ # Error inspector originally used file_cache_path which is incorrect on
+ # chef-solo. Using cookbook_path should do the right thing for client and
+ # solo.
+ allow(Chef::Config).to receive(:cookbook_path).and_return([ "/home/someuser/dev-laptop/cookbooks" ])
+ end
+
+ describe "when scrubbing backtraces" do
+ it "shows backtrace lines from cookbook files" do
+ expect(inspector.filtered_bt).to eq(expected_filtered_trace)
+ end
+ end
+
+ describe "when explaining an error in the compile phase" do
+ before do
+ recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
+ expect(IO).to receive(:readlines).with(path_to_failed_file).and_return(recipe_lines)
+ inspector.add_explanation(description)
+ end
+
+ it "reports the error was not located within cookbooks" do
+ expect(inspector.found_error_in_cookbooks?).to be(true)
+ end
+
+ it "finds the line number of the error from the stacktrace" do
+ expect(inspector.culprit_line).to eq(14)
+ end
+
+ it "prints a pretty message" do
+ description.display(outputter)
+ end
+ end
end
- it "finds the line number of the error from the stacktrace" do
- expect(@inspector.culprit_line).to eq(14)
+ context "when the error is a RuntimeError about frozen object" do
+ let(:exception) do
+ e = RuntimeError.new("can't modify frozen Array")
+ e.set_backtrace(trace)
+ e
+ end
+
+ let(:path_to_failed_file) { "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb" }
+
+ let(:trace) do
+ [
+ "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:2:in `block in from_file'",
+ "/tmp/kitchen/cache/cookbooks/foo/recipes/default.rb:1:in `from_file'"
+ ]
+ end
+
+ describe "when explaining a runtime error in the compile phase" do
+ it "correctly detects RuntimeError for frozen objects" do
+ expect(inspector.exception_message_modifying_frozen?).to be(true)
+ end
+
+ # could also test for description.section to be called, but would have
+ # to adjust every other test to begin using a test double for description
+ end
end
- it "prints a pretty message" do
- @description.display(@outputter)
+ context "when the error does not contain any lines from cookbooks" do
+
+ let(:trace) do
+ [
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'",
+ "/opt/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'"
+ ]
+ end
+
+ let(:exception) do
+ e = Chef::Exceptions::RecipeNotFound.new("recipe nope:nope not found")
+ e.set_backtrace(trace)
+ e
+ end
+
+ let(:path_to_failed_file) { nil }
+
+ it "gives a full, non-filtered trace" do
+ expect(inspector.filtered_bt).to eq(trace)
+ end
+
+ it "does not error when displaying the error" do
+ expect { description.display(outputter) }.to_not raise_error
+ end
+
+ it "reports the error was not located within cookbooks" do
+ expect(inspector.found_error_in_cookbooks?).to be(false)
+ end
+
end
end
describe "when explaining an error on windows" do
- before do
- allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
- recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
- expect(IO).to receive(:readlines).at_least(1).times.with(/:\/opscode\/chef\/var\/cache\/cookbooks\/foo\/recipes\/default.rb/).and_return(recipe_lines)
- @trace = [
+
+ let(:trace_with_upcase_drive) do
+ [
"C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb:14 in `from_file'",
"C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'",
"C:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'",
@@ -122,81 +201,65 @@ describe Chef::Formatters::ErrorInspectors::CompileErrorInspector do
"C:/opscode/chef/bin/chef-client:19:in `load'",
"C:/opscode/chef/bin/chef-client:19:in `<main>'"
]
- @exception.set_backtrace(@trace)
- @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
- @inspector = described_class.new(@path, @exception)
- @inspector.add_explanation(@description)
end
+ let(:trace) { trace_with_upcase_drive }
+
+ let(:path_to_failed_file) { "/var/cache/cookbooks/foo/recipes/default.rb" }
- describe "and examining the stack trace for a recipe" do
- it "find the culprit recipe name when the drive letter is upper case" do
- expect(@inspector.culprit_file).to eq("C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
+ before do
+ allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
+ recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
+ expect(IO).to receive(:readlines).at_least(1).times.with(full_path_to_failed_file).and_return(recipe_lines)
+ inspector.add_explanation(description)
+ end
+
+ context "when the drive letter in the path is uppercase" do
+
+ let(:full_path_to_failed_file) { "C:/opscode/chef#{path_to_failed_file}" }
+
+ it "reports the error was not located within cookbooks" do
+ expect(inspector.found_error_in_cookbooks?).to be(true)
end
- it "find the culprit recipe name when the drive letter is lower case" do
- @trace.each { |line| line.gsub!(/^C:/, "c:") }
- @exception.set_backtrace(@trace)
- @inspector = described_class.new(@path, @exception)
- @inspector.add_explanation(@description)
- expect(@inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
+ it "finds the culprit recipe name" do
+ expect(inspector.culprit_file).to eq("C:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
end
- end
- it "finds the line number of the error from the stack trace" do
- expect(@inspector.culprit_line).to eq(14)
- end
+ it "finds the line number of the error from the stack trace" do
+ expect(inspector.culprit_line).to eq(14)
+ end
- it "prints a pretty message" do
- @description.display(@outputter)
+ it "prints a pretty message" do
+ description.display(outputter)
+ end
end
- end
- describe "when explaining an error on windows, and the backtrace lowercases the drive letter" do
- before do
- allow(Chef::Config).to receive(:cookbook_path).and_return([ "C:/opscode/chef/var/cache/cookbooks" ])
- recipe_lines = BAD_RECIPE.split("\n").map {|l| l << "\n" }
- expect(IO).to receive(:readlines).with("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb").and_return(recipe_lines)
- @trace = [
- "c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb:14 in `from_file'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:144:in `rescue in block in load_libraries'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:138:in `block in load_libraries'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `call'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:230:in `block (2 levels) in foreach_cookbook_load_segment'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `each'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:229:in `block in foreach_cookbook_load_segment'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `each'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:227:in `foreach_cookbook_load_segment'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:137:in `load_libraries'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/run_context.rb:62:in `load'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:198:in `setup_run_context'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:418:in `do_run'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/client.rb:176:in `run'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:283:in `block in run_application'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `loop'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application/client.rb:270:in `run_application'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/lib/chef/application.rb:70:in `run'",
- "c:/opscode/chef/embedded/lib/ruby/gems/1.9.1/gems/chef-10.14.0/bin/chef-client:26:in `<top (required)>'",
- "c:/opscode/chef/bin/chef-client:19:in `load'",
- "c:/opscode/chef/bin/chef-client:19:in `<main>'"
- ]
- @exception.set_backtrace(@trace)
- @path = "/var/chef/cache/cookbooks/syntax-err/recipes/default.rb"
- @inspector = described_class.new(@path, @exception)
- @inspector.add_explanation(@description)
- end
+ context "when the drive letter in the path is lowercase" do
- it "finds the culprit recipe name from the stacktrace" do
- expect(@inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
- end
+ let(:trace) do
+ trace_with_upcase_drive.map { |line| line.gsub(/^C:/, "c:") }
+ end
- it "finds the line number of the error from the stack trace" do
- expect(@inspector.culprit_line).to eq(14)
- end
+ let(:full_path_to_failed_file) { "c:/opscode/chef#{path_to_failed_file}" }
+
+ it "reports the error was not located within cookbooks" do
+ expect(inspector.found_error_in_cookbooks?).to be(true)
+ end
+
+ it "finds the culprit recipe name from the stacktrace" do
+ expect(inspector.culprit_file).to eq("c:/opscode/chef/var/cache/cookbooks/foo/recipes/default.rb")
+ end
- it "prints a pretty message" do
- @description.display(@outputter)
+ it "finds the line number of the error from the stack trace" do
+ expect(inspector.culprit_line).to eq(14)
+ end
+
+ it "prints a pretty message" do
+ description.display(outputter)
+ end
end
+
end
end
diff --git a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
index a42d234601..5594d6e18a 100644
--- a/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
+++ b/spec/unit/formatters/error_inspectors/resource_failure_inspector_spec.rb
@@ -126,6 +126,13 @@ describe Chef::Formatters::ErrorInspectors::ResourceFailureInspector do
expect(@inspector.recipe_snippet).to match(/^# In C:\/Users\/btm/)
end
+ it "parses a Windows path" do
+ source_line = "C:\\Windows\\Temp\\packer\\cookbooks\\fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)"
+ @resource.source_line = source_line
+ @inspector = Chef::Formatters::ErrorInspectors::ResourceFailureInspector.new(@resource, :create, @exception)
+ expect(@inspector.recipe_snippet).to match(/^# In C:\\Windows\\Temp\\packer\\/)
+ end
+
it "parses a unix path" do
source_line = "/home/btm/src/chef/chef/spec/unit/fake_file.rb:2: undefined local variable or method `non_existent' for main:Object (NameError)"
@resource.source_line = source_line
diff --git a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
index 4cf3ba827a..acf1b15fd8 100644
--- a/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
+++ b/spec/unit/guard_interpreter/resource_guard_interpreter_spec.rb
@@ -24,6 +24,7 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do
node.default["kernel"] = Hash.new
node.default["kernel"][:machine] = :x86_64.to_s
+ node.automatic[:os] = 'windows'
node
end
@@ -83,6 +84,14 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do
expect(guard_interpreter.evaluate).to eq(true)
end
+ it "does not corrupt the run_context of the node" do
+ node_run_context_before_guard_execution = parent_resource.run_context
+ expect(node_run_context_before_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id)
+ guard_interpreter.evaluate
+ node_run_context_after_guard_execution = parent_resource.run_context
+ expect(node_run_context_after_guard_execution.object_id).to eq(parent_resource.node.run_context.object_id)
+ end
+
describe "script command opts switch" do
let(:command_opts) { {} }
let(:guard_interpreter) { Chef::GuardInterpreter::ResourceGuardInterpreter.new(parent_resource, "exit 0", command_opts) }
@@ -144,4 +153,3 @@ describe Chef::GuardInterpreter::ResourceGuardInterpreter do
end
end
end
-
diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb
new file mode 100644
index 0000000000..48bbdcf76c
--- /dev/null
+++ b/spec/unit/http/authenticator_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/http/authenticator'
+
+describe Chef::HTTP::Authenticator do
+ let(:class_instance) { Chef::HTTP::Authenticator.new }
+ let(:method) { double("method") }
+ let(:url) { double("url") }
+ let(:headers) { Hash.new }
+ let(:data) { double("data") }
+
+ before do
+ allow(class_instance).to receive(:authentication_headers).and_return({})
+ end
+
+ context "when handle_request is called" do
+ shared_examples_for "merging the server API version into the headers" do
+ it "merges the default version of X-Ops-Server-API-Version into the headers" do
+ # headers returned
+ expect(class_instance.handle_request(method, url, headers, data)[2]).
+ to include({'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION})
+ end
+
+ context "when api_version is set to something other than the default" do
+ let(:class_instance) { Chef::HTTP::Authenticator.new({:api_version => '-10'}) }
+
+ it "merges the requested version of X-Ops-Server-API-Version into the headers" do
+ expect(class_instance.handle_request(method, url, headers, data)[2]).
+ to include({'X-Ops-Server-API-Version' => '-10'})
+ end
+ end
+ end
+
+ context "when !sign_requests?" do
+ before do
+ allow(class_instance).to receive(:sign_requests?).and_return(false)
+ end
+
+ it_behaves_like "merging the server API version into the headers"
+
+ it "authentication_headers is not called" do
+ expect(class_instance).to_not receive(:authentication_headers)
+ class_instance.handle_request(method, url, headers, data)
+ end
+
+ end
+
+ context "when sign_requests?" do
+ before do
+ allow(class_instance).to receive(:sign_requests?).and_return(true)
+ end
+
+ it_behaves_like "merging the server API version into the headers"
+
+ it "calls authentication_headers with the proper input" do
+ expect(class_instance).to receive(:authentication_headers).with(method, url, data).and_return({})
+ class_instance.handle_request(method, url, headers, data)
+ end
+ end
+ end
+end
diff --git a/spec/unit/http/basic_client_spec.rb b/spec/unit/http/basic_client_spec.rb
index eb133f943e..b7552f54aa 100644
--- a/spec/unit/http/basic_client_spec.rb
+++ b/spec/unit/http/basic_client_spec.rb
@@ -21,7 +21,7 @@ require 'chef/http/basic_client'
describe "HTTP Connection" do
let(:uri) { URI("https://example.com:4443") }
- subject { Chef::HTTP::BasicClient.new(uri) }
+ subject(:basic_client) { Chef::HTTP::BasicClient.new(uri) }
describe ".new" do
it "creates an instance" do
@@ -45,11 +45,6 @@ describe "HTTP Connection" do
let(:proxy_port) { 8080 }
let(:proxy) { "#{proxy_prefix}#{proxy_host}:#{proxy_port}" }
- before do
- Chef::Config["#{uri.scheme}_proxy"] = proxy
- Chef::Config[:no_proxy] = nil
- end
-
it "should contain the host" do
proxy_uri = subject.proxy_uri
expect(proxy_uri.host).to eq(proxy_host)
@@ -63,13 +58,71 @@ describe "HTTP Connection" do
context "when the config setting is normalized (does not contain the scheme)" do
include_examples "a proxy uri" do
+
let(:proxy_prefix) { "" }
+
+ before do
+ Chef::Config["#{uri.scheme}_proxy"] = proxy
+ Chef::Config[:no_proxy] = nil
+ end
+
end
end
context "when the config setting is not normalized (contains the scheme)" do
include_examples "a proxy uri" do
let(:proxy_prefix) { "#{uri.scheme}://" }
+
+ before do
+ Chef::Config["#{uri.scheme}_proxy"] = proxy
+ Chef::Config[:no_proxy] = nil
+ end
+
+ end
+ end
+
+ context "when the proxy is set by the environment" do
+
+ include_examples "a proxy uri" do
+
+ let(:env) do
+ {
+ "https_proxy" => "https://proxy.mycorp.com:8080",
+ "https_proxy_user" => "jane_username",
+ "https_proxy_pass" => "opensesame"
+ }
+ end
+
+ let(:proxy_uri) { URI.parse(env["https_proxy"]) }
+
+ before do
+ allow(basic_client).to receive(:env).and_return(env)
+ end
+
+ it "sets the proxy user" do
+ expect(basic_client.http_proxy_user(proxy_uri)).to eq("jane_username")
+ end
+
+ it "sets the proxy pass" do
+ expect(basic_client.http_proxy_pass(proxy_uri)).to eq("opensesame")
+ end
+ end
+
+ end
+
+ context "when an empty proxy is set by the environment" do
+ let(:env) do
+ {
+ "https_proxy" => ""
+ }
+ end
+
+ before do
+ allow(subject).to receive(:env).and_return(env)
+ end
+
+ it "to not fail with URI parse exception" do
+ expect { subject.proxy_uri }.to_not raise_error
end
end
end
diff --git a/spec/unit/http/socketless_chef_zero_client_spec.rb b/spec/unit/http/socketless_chef_zero_client_spec.rb
new file mode 100644
index 0000000000..963cc9e8c4
--- /dev/null
+++ b/spec/unit/http/socketless_chef_zero_client_spec.rb
@@ -0,0 +1,174 @@
+#--
+# Author:: Daniel DeLeo (<dan@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/http/socketless_chef_zero_client'
+
+describe Chef::HTTP::SocketlessChefZeroClient do
+
+ let(:relative_url) { "" }
+ let(:uri_str) { "chefzero://localhost:1/#{relative_url}" }
+ let(:uri) { URI(uri_str) }
+
+ subject(:zero_client) { Chef::HTTP::SocketlessChefZeroClient.new(uri) }
+
+ it "has a host" do
+ expect(zero_client.host).to eq("localhost")
+ end
+
+ it "has a port" do
+ expect(zero_client.port).to eq(1)
+ end
+
+ describe "converting requests to rack format" do
+
+ let(:expected_rack_req) do
+ {
+ "SCRIPT_NAME" => "",
+ "SERVER_NAME" => "localhost",
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "PATH_INFO" => uri.path,
+ "QUERY_STRING" => uri.query,
+ "SERVER_PORT" => uri.port,
+ "HTTP_HOST" => "localhost:#{uri.port}",
+ "rack.url_scheme" => "chefzero",
+ }
+ end
+
+ context "when the request has no body" do
+
+ let(:method) { :GET }
+ let(:relative_url) { "clients" }
+ let(:headers) { { "Accept" => "application/json" } }
+ let(:body) { false }
+ let(:expected_body_str) { "" }
+
+ let(:rack_req) { zero_client.req_to_rack(method, uri, body, headers) }
+
+ it "creates a rack request env" do
+ # StringIO doesn't implement == in a way that we can compare, so we
+ # check rack.input individually and then iterate over everything else
+ expect(rack_req["rack.input"].string).to eq(expected_body_str)
+ expected_rack_req.each do |key, value|
+ expect(rack_req[key]).to eq(value)
+ end
+ end
+
+ end
+
+ context "when the request has a body" do
+
+ let(:method) { :PUT }
+ let(:relative_url) { "clients/foo" }
+ let(:headers) { { "Accept" => "application/json" } }
+ let(:body) { "bunch o' JSON" }
+ let(:expected_body_str) { "bunch o' JSON" }
+
+ let(:rack_req) { zero_client.req_to_rack(method, uri, body, headers) }
+
+ it "creates a rack request env" do
+ # StringIO doesn't implement == in a way that we can compare, so we
+ # check rack.input individually and then iterate over everything else
+ expect(rack_req["rack.input"].string).to eq(expected_body_str)
+ expected_rack_req.each do |key, value|
+ expect(rack_req[key]).to eq(value)
+ end
+ end
+
+ end
+
+ end
+
+ describe "converting responses to Net::HTTP objects" do
+
+ let(:net_http_response) { zero_client.to_net_http(code, headers, body) }
+
+ context "when the request was successful (2XX)" do
+
+ let(:code) { 200 }
+ let(:headers) { { "Content-Type" => "Application/JSON" } }
+ let(:body) { [ "bunch o' JSON" ] }
+
+ it "creates a Net::HTTP success response object" do
+ expect(net_http_response).to be_a_kind_of(Net::HTTPOK)
+ expect(net_http_response.read_body).to eq("bunch o' JSON")
+ expect(net_http_response["content-type"]).to eq("Application/JSON")
+ end
+
+ it "does not fail when calling read_body with a block" do
+ expect(net_http_response.read_body {|chunk| chunk }).to eq("bunch o' JSON")
+ end
+
+ end
+
+ context "when the requested object doesn't exist (404)" do
+
+ let(:code) { 404 }
+ let(:headers) { { "Content-Type" => "Application/JSON" } }
+ let(:body) { [ "nope" ] }
+
+ it "creates a Net::HTTPNotFound response object" do
+ expect(net_http_response).to be_a_kind_of(Net::HTTPNotFound)
+ end
+ end
+
+ end
+
+ describe "request-response round trip" do
+
+ let(:method) { :GET }
+ let(:relative_url) { "clients" }
+ let(:headers) { { "Accept" => "application/json" } }
+ let(:body) { false }
+
+ let(:expected_rack_req) do
+ {
+ "SCRIPT_NAME" => "",
+ "SERVER_NAME" => "localhost",
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "PATH_INFO" => uri.path,
+ "QUERY_STRING" => uri.query,
+ "SERVER_PORT" => uri.port,
+ "HTTP_HOST" => "localhost:#{uri.port}",
+ "rack.url_scheme" => "chefzero",
+ "rack.input" => an_instance_of(StringIO),
+ }
+ end
+
+
+ let(:response_code) { 200 }
+ let(:response_headers) { { "Content-Type" => "Application/JSON" } }
+ let(:response_body) { [ "bunch o' JSON" ] }
+
+ let(:rack_response) { [ response_code, response_headers, response_body ] }
+
+ let(:response) { zero_client.request(method, uri, body, headers) }
+
+ before do
+ expect(ChefZero::SocketlessServerMap).to receive(:request).with(1, expected_rack_req).and_return(rack_response)
+ end
+
+ it "makes a rack request to Chef Zero and returns the response as a Net::HTTP object" do
+ _client, net_http_response = response
+ expect(net_http_response).to be_a_kind_of(Net::HTTPOK)
+ expect(net_http_response.code).to eq("200")
+ expect(net_http_response.body).to eq("bunch o' JSON")
+ end
+
+ end
+
+end
diff --git a/spec/unit/http_spec.rb b/spec/unit/http_spec.rb
index ddfc56583d..4d851df951 100644
--- a/spec/unit/http_spec.rb
+++ b/spec/unit/http_spec.rb
@@ -20,6 +20,7 @@ require 'spec_helper'
require 'chef/http'
require 'chef/http/basic_client'
+require 'chef/http/socketless_chef_zero_client'
class Chef::HTTP
public :create_url
@@ -27,6 +28,19 @@ end
describe Chef::HTTP do
+ context "when given a chefzero:// URL" do
+
+ let(:uri) { URI("chefzero://localhost:1") }
+
+ subject(:http) { Chef::HTTP.new(uri) }
+
+ it "uses the SocketlessChefZeroClient to handle requests" do
+ expect(http.http_client).to be_a_kind_of(Chef::HTTP::SocketlessChefZeroClient)
+ expect(http.http_client.url).to eq(uri)
+ end
+
+ end
+
describe "create_url" do
it 'should return a correctly formatted url 1/3 CHEF-5261' do
diff --git a/spec/unit/json_compat_spec.rb b/spec/unit/json_compat_spec.rb
index 65d931df70..fd6469c146 100644
--- a/spec/unit/json_compat_spec.rb
+++ b/spec/unit/json_compat_spec.rb
@@ -67,37 +67,25 @@ 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
- describe "with a file with 300 or less nested entries" do
- let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'big_json.json')) }
+ 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) }
- describe "when a big json file is loaded" do
+ describe "when the 252 json file is loaded" do
it "should create a Hash from the file" do
expect(hash).to be_kind_of(Hash)
end
- it "should has 'test' as a 300th 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']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['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')
- end
- end
- end
-
- describe "with a file with more than 300 nested entries" do
- let(:json) { IO.read(File.join(CHEF_SPEC_DATA, 'big_json_plus_one.json')) }
- let(:hash) { Chef::JSONCompat.from_json(json, {:max_nesting => 301}) }
-
- describe "when a big json file is loaded" do
- it "should create a Hash from the file" do
- expect(hash).to be_kind_of(Hash)
- end
-
- it "should has 'test' as a 301st 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']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['key']['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')
+ it "should has 'test' as a 252 nested value" do
+ v = 252.times.inject(hash) do |memo, _|
+ memo['key']
+ end
+ expect(v).to eq('test')
end
end
end
diff --git a/spec/unit/key_spec.rb b/spec/unit/key_spec.rb
new file mode 100644
index 0000000000..94ebbf6ae8
--- /dev/null
+++ b/spec/unit/key_spec.rb
@@ -0,0 +1,634 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+require 'chef/key'
+
+describe Chef::Key do
+ # whether user or client irrelevent to these tests
+ let(:key) { Chef::Key.new("original_actor", "user") }
+ let(:public_key_string) do
+ <<EOS
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----
+EOS
+ end
+
+ shared_examples_for "fields with username type validation" do
+ context "when invalid input is passed" do
+ # It is not feasible to check all invalid characters. Here are a few
+ # that we probably care about.
+ it "should raise an ArgumentError" do
+ # capital letters
+ expect { key.send(field, "Bar") }.to raise_error(ArgumentError)
+ # slashes
+ expect { key.send(field, "foo/bar") }.to raise_error(ArgumentError)
+ # ?
+ expect { key.send(field, "foo?") }.to raise_error(ArgumentError)
+ # &
+ expect { key.send(field, "foo&") }.to raise_error(ArgumentError)
+ # spaces
+ expect { key.send(field, "foo ") }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ shared_examples_for "string fields that are settable" do
+ context "when it is set with valid input" do
+ it "should set the field" do
+ key.send(field, valid_input)
+ expect(key.send(field)).to eq(valid_input)
+ end
+ end
+
+ context "when you feed it anything but a string" do
+ it "should raise an ArgumentError" do
+ expect { key.send(field, Hash.new) }.to raise_error(ArgumentError)
+ end
+ end
+ end
+
+
+ describe "when a new Chef::Key object is initialized with invalid input" do
+ it "should raise an InvalidKeyArgument" do
+ expect { Chef::Key.new("original_actor", "not_a_user_or_client") }.to raise_error(Chef::Exceptions::InvalidKeyArgument)
+ end
+ end
+
+ describe "when a new Chef::Key object is initialized with valid input" do
+ it "should be a Chef::Key" do
+ expect(key).to be_a_kind_of(Chef::Key)
+ end
+
+ it "should properly set the actor" do
+ expect(key.actor).to eq("original_actor")
+ end
+ end
+
+ describe "when actor field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :actor }
+ let(:valid_input) { "new_field_value" }
+ end
+
+ it_should_behave_like "fields with username type validation" do
+ let(:field) { :actor }
+ end
+ end
+
+ describe "when the name field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :name }
+ let(:valid_input) { "new_field_value" }
+ end
+ end
+
+ describe "when the private_key field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :private_key }
+ let(:valid_input) { "new_field_value" }
+ end
+ end
+
+ describe "when the public_key field is set" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :public_key }
+ let(:valid_input) { "new_field_value" }
+ end
+
+ context "when create_key is true" do
+ before do
+ key.create_key true
+ end
+
+ it "should raise an InvalidKeyAttribute" do
+ expect { key.public_key public_key_string }.to raise_error(Chef::Exceptions::InvalidKeyAttribute)
+ end
+ end
+ end
+
+ describe "when the create_key field is set" do
+ context "when it is set to true" do
+ it "should set the field" do
+ key.create_key(true)
+ expect(key.create_key).to eq(true)
+ end
+ end
+
+ context "when it is set to false" do
+ it "should set the field" do
+ key.create_key(false)
+ expect(key.create_key).to eq(false)
+ end
+ end
+
+ context "when anything but a TrueClass or FalseClass is passed" do
+ it "should raise an ArgumentError" do
+ expect { key.create_key "not_a_boolean" }.to raise_error(ArgumentError)
+ end
+ end
+
+ context "when public_key is defined" do
+ before do
+ key.public_key public_key_string
+ end
+
+ it "should raise an InvalidKeyAttribute" do
+ expect { key.create_key true }.to raise_error(Chef::Exceptions::InvalidKeyAttribute)
+ end
+ end
+ end
+
+ describe "when the expiration_date field is set" do
+ context "when a valid date is passed" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :public_key }
+ let(:valid_input) { "2020-12-24T21:00:00Z" }
+ end
+ end
+
+ context "when infinity is passed" do
+ it_should_behave_like "string fields that are settable" do
+ let(:field) { :public_key }
+ let(:valid_input) { "infinity" }
+ end
+ end
+
+ context "when an invalid date is passed" do
+ it "should raise an ArgumentError" do
+ expect { key.expiration_date "invalid_date" }.to raise_error(ArgumentError)
+ # wrong years
+ expect { key.expiration_date "20-12-24T21:00:00Z" }.to raise_error(ArgumentError)
+ end
+
+ context "when it is a valid UTC date missing a Z" do
+ it "should raise an ArgumentError" do
+ expect { key.expiration_date "2020-12-24T21:00:00" }.to raise_error(ArgumentError)
+ end
+ end
+ end
+ end # when the expiration_date field is set
+
+ describe "when serializing to JSON" do
+ shared_examples_for "common json operations" do
+ it "should serializes as a JSON object" do
+ expect(json).to match(/^\{.+\}$/)
+ end
+
+ it "should include the actor value under the key relative to the actor_field_name passed" do
+ expect(json).to include(%Q("#{new_key.actor_field_name}":"original_actor"))
+ end
+
+ it "should include the name field when present" do
+ new_key.name("monkeypants")
+ expect(new_key.to_json).to include(%q{"name":"monkeypants"})
+ end
+
+ it "should not include the name if not present" do
+ expect(json).to_not include("name")
+ end
+
+ it "should include the public_key field when present" do
+ new_key.public_key "this_public_key"
+ expect(new_key.to_json).to include(%q("public_key":"this_public_key"))
+ end
+
+ it "should not include the public_key if not present" do
+ expect(json).to_not include("public_key")
+ end
+
+ it "should include the private_key field when present" do
+ new_key.private_key "this_public_key"
+ expect(new_key.to_json).to include(%q("private_key":"this_public_key"))
+ end
+
+ it "should not include the private_key if not present" do
+ expect(json).to_not include("private_key")
+ end
+
+ it "should include the expiration_date field when present" do
+ new_key.expiration_date "2020-12-24T21:00:00Z"
+ expect(new_key.to_json).to include(%Q("expiration_date":"2020-12-24T21:00:00Z"))
+ end
+
+ it "should not include the expiration_date if not present" do
+ expect(json).to_not include("expiration_date")
+ end
+
+ it "should include the create_key field when present" do
+ new_key.create_key true
+ expect(new_key.to_json).to include(%q("create_key":true))
+ end
+
+ it "should not include the create_key if not present" do
+ expect(json).to_not include("create_key")
+ end
+ end
+
+ context "when key is for a user" do
+ it_should_behave_like "common json operations" do
+ let(:new_key) { Chef::Key.new("original_actor", "user") }
+ let(:json) do
+ new_key.to_json
+ end
+ end
+ end
+
+ context "when key is for a client" do
+ it_should_behave_like "common json operations" do
+ let(:new_key) { Chef::Key.new("original_actor", "client") }
+ let(:json) do
+ new_key.to_json
+ end
+ end
+ end
+
+ end # when serializing to JSON
+
+ describe "when deserializing from JSON" do
+ shared_examples_for "a deserializable object" do
+ it "deserializes to a Chef::Key object" do
+ expect(key).to be_a_kind_of(Chef::Key)
+ end
+
+ it "preserves the actor" do
+ expect(key.actor).to eq("turtle")
+ end
+
+ it "preserves the name" do
+ expect(key.name).to eq("key_name")
+ end
+
+ it "includes the public key if present" do
+ expect(key.public_key).to eq(public_key_string)
+ end
+
+ it "includes the expiration_date if present" do
+ expect(key.expiration_date).to eq("infinity")
+ end
+
+ it "includes the private_key if present" do
+ expect(key.private_key).to eq("some_private_key")
+ end
+
+ it "includes the create_key if present" do
+ expect(key_with_create_key_field.create_key).to eq(true)
+ end
+ end
+
+ context "when deserializing a key for a user" do
+ it_should_behave_like "a deserializable object" do
+ let(:key) do
+ o = { "user" => "turtle",
+ "name" => "key_name",
+ "public_key" => public_key_string,
+ "private_key" => "some_private_key",
+ "expiration_date" => "infinity"}
+ Chef::Key.from_json(o.to_json)
+ end
+ let(:key_with_create_key_field) do
+ o = { "user" => "turtle",
+ "create_key" => true }
+ Chef::Key.from_json(o.to_json)
+ end
+ end
+ end
+
+ context "when deserializing a key for a client" do
+ it_should_behave_like "a deserializable object" do
+ let(:key) do
+ o = { "client" => "turtle",
+ "name" => "key_name",
+ "public_key" => public_key_string,
+ "private_key" => "some_private_key",
+ "expiration_date" => "infinity"}
+ Chef::Key.from_json(o.to_json)
+ end
+ let(:key_with_create_key_field) do
+ o = { "client" => "turtle",
+ "create_key" => true }
+ Chef::Key.from_json(o.to_json)
+ end
+ end
+ end
+ end # when deserializing from JSON
+
+
+ describe "API Interactions" do
+ let(:rest) do
+ Chef::Config[:chef_server_root] = "http://www.example.com"
+ Chef::Config[:chef_server_url] = "http://www.example.com/organizations/test_org"
+ r = double('rest')
+ allow(Chef::REST).to receive(:new).and_return(r)
+ r
+ end
+
+ let(:user_key) do
+ o = Chef::Key.new("foobar", "user")
+ o
+ end
+
+ let(:client_key) do
+ o = Chef::Key.new("foobar", "client")
+ o
+ end
+
+ describe "list" do
+ context "when listing keys for a user" do
+ let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] }
+ let(:inflated_response) { {"foobar" => user_key} }
+
+ it "lists all keys" do
+ expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_user("foobar")).to eq(response)
+ end
+
+ it "inflate all keys" do
+ allow(Chef::Key).to receive(:load_by_user).with(user_key.actor, "foobar").and_return(user_key)
+ expect(rest).to receive(:get_rest).with("users/#{user_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_user("foobar", true)).to eq(inflated_response)
+ end
+
+ end
+
+ context "when listing keys for a client" do
+ let(:response) { [{"uri" => "http://www.example.com/users/keys/foobar", "name"=>"foobar", "expired"=>false}] }
+ let(:inflated_response) { {"foobar" => client_key} }
+
+ it "lists all keys" do
+ expect(rest).to receive(:get_rest).with("clients/#{client_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_client("foobar")).to eq(response)
+ end
+
+ it "inflate all keys" do
+ allow(Chef::Key).to receive(:load_by_client).with(client_key.actor, "foobar").and_return(client_key)
+ expect(rest).to receive(:get_rest).with("clients/#{user_key.actor}/keys").and_return(response)
+ expect(Chef::Key.list_by_client("foobar", true)).to eq(inflated_response)
+ end
+
+ end
+ end
+
+
+ describe "create" do
+ shared_examples_for "create key" do
+ context "when a field is missing" do
+ it "should raise a MissingKeyAttribute" do
+ expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+
+ context "when the name field is missing" do
+ before do
+ key.public_key public_key_string
+ key.expiration_date "2020-12-24T21:00:00Z"
+ end
+
+ it "creates a new key via the API with the fingerprint as the name" do
+ expect(rest).to receive(:post_rest).with(url,
+ {"name" => "12:3e:33:73:0b:f4:ec:72:dc:f0:4c:51:62:27:08:76:96:24:f4:4a",
+ "public_key" => key.public_key,
+ "expiration_date" => key.expiration_date}).and_return({})
+ key.create
+ end
+ end
+
+ context "when every field is populated" do
+ before do
+ key.name "key_name"
+ key.public_key public_key_string
+ key.expiration_date "2020-12-24T21:00:00Z"
+ key.create_key false
+ end
+
+ context "when create_key is false" do
+ it "creates a new key via the API" do
+ expect(rest).to receive(:post_rest).with(url,
+ {"name" => key.name,
+ "public_key" => key.public_key,
+ "expiration_date" => key.expiration_date}).and_return({})
+ key.create
+ end
+ end
+
+ context "when create_key is true and public_key is nil" do
+
+ before do
+ key.delete_public_key
+ key.create_key true
+ $expected_output = {
+ actor_type => "foobar",
+ "name" => key.name,
+ "create_key" => true,
+ "expiration_date" => key.expiration_date
+ }
+ $expected_input = {
+ "name" => key.name,
+ "create_key" => true,
+ "expiration_date" => key.expiration_date
+ }
+ end
+
+ it "should create a new key via the API" do
+ expect(rest).to receive(:post_rest).with(url, $expected_input).and_return({})
+ key.create
+ end
+
+ context "when the server returns the private_key via key.create" do
+ before do
+ allow(rest).to receive(:post_rest).with(url, $expected_input).and_return({"private_key" => "this_private_key"})
+ end
+
+ it "key.create returns the original key plus the private_key" do
+ expect(key.create.to_hash).to eq($expected_output.merge({"private_key" => "this_private_key"}))
+ end
+ end
+ end
+
+ context "when create_key is false and public_key is nil" do
+ before do
+ key.delete_public_key
+ key.create_key false
+ end
+ it "should raise an InvalidKeyArgument" do
+ expect { key.create }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+ end
+ end
+
+ context "when creating a user key" do
+ it_should_behave_like "create key" do
+ let(:url) { "users/#{key.actor}/keys" }
+ let(:key) { user_key }
+ let(:actor_type) { "user" }
+ end
+ end
+
+ context "when creating a client key" do
+ it_should_behave_like "create key" do
+ let(:url) { "clients/#{client_key.actor}/keys" }
+ let(:key) { client_key }
+ let(:actor_type) { "client" }
+ end
+ end
+ end # create
+
+ describe "update" do
+ shared_examples_for "update key" do
+ context "when name is missing and no argument was passed to update" do
+ it "should raise an MissingKeyAttribute" do
+ expect { key.update }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+
+ context "when some fields are populated" do
+ before do
+ key.name "key_name"
+ key.expiration_date "2020-12-24T21:00:00Z"
+ end
+
+ it "should update the key via the API" do
+ expect(rest).to receive(:put_rest).with(url, key.to_hash).and_return({})
+ key.update
+ end
+ end
+
+ context "when @name is not nil and a arg is passed to update" do
+ before do
+ key.name "new_name"
+ end
+
+ it "passes @name in the body and the arg in the PUT URL" do
+ expect(rest).to receive(:put_rest).with(update_name_url, key.to_hash).and_return({})
+ key.update("old_name")
+ end
+ end
+
+ context "when the server returns a public_key and create_key is true" do
+ before do
+ key.name "key_name"
+ key.create_key true
+ allow(rest).to receive(:put_rest).with(url, key.to_hash).and_return({
+ "key" => "key_name",
+ "public_key" => public_key_string
+ })
+
+ end
+
+ it "returns a key with public_key populated" do
+ new_key = key.update
+ expect(new_key.public_key).to eq(public_key_string)
+ end
+
+ it "returns a key without create_key set" do
+ new_key = key.update
+ expect(new_key.create_key).to be_nil
+ end
+ end
+ end
+
+ context "when updating a user key" do
+ it_should_behave_like "update key" do
+ let(:url) { "users/#{key.actor}/keys/#{key.name}" }
+ let(:update_name_url) { "users/#{key.actor}/keys/old_name" }
+ let(:key) { user_key }
+ end
+ end
+
+ context "when updating a client key" do
+ it_should_behave_like "update key" do
+ let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" }
+ let(:update_name_url) { "clients/#{client_key.actor}/keys/old_name" }
+ let(:key) { client_key }
+ end
+ end
+
+ end #update
+
+ describe "load" do
+ shared_examples_for "load" do
+ it "should load a named key from the API" do
+ expect(rest).to receive(:get_rest).with(url).and_return({"user" => "foobar", "name" => "test_key_name", "public_key" => public_key_string, "expiration_date" => "infinity"})
+ key = Chef::Key.send(load_method, "foobar", "test_key_name")
+ expect(key.actor).to eq("foobar")
+ expect(key.name).to eq("test_key_name")
+ expect(key.public_key).to eq(public_key_string)
+ expect(key.expiration_date).to eq("infinity")
+ end
+ end
+
+ describe "load_by_user" do
+ it_should_behave_like "load" do
+ let(:load_method) { :load_by_user }
+ let(:url) { "users/foobar/keys/test_key_name" }
+ end
+ end
+
+ describe "load_by_client" do
+ it_should_behave_like "load" do
+ let(:load_method) { :load_by_client }
+ let(:url) { "clients/foobar/keys/test_key_name" }
+ end
+ end
+
+ end #load
+
+ describe "destroy" do
+ shared_examples_for "destroy key" do
+ context "when name is missing" do
+ it "should raise an MissingKeyAttribute" do
+ expect { Chef::Key.new("username", "user").destroy }.to raise_error(Chef::Exceptions::MissingKeyAttribute)
+ end
+ end
+
+ before do
+ key.name "key_name"
+ end
+ context "when name is not missing" do
+ it "should delete the key via the API" do
+ expect(rest).to receive(:delete_rest).with(url).and_return({})
+ key.destroy
+ end
+ end
+ end
+
+ context "when destroying a user key" do
+ it_should_behave_like "destroy key" do
+ let(:url) { "users/#{key.actor}/keys/#{key.name}" }
+ let(:key) { user_key }
+ end
+ end
+
+ context "when destroying a client key" do
+ it_should_behave_like "destroy key" do
+ let(:url) { "clients/#{client_key.actor}/keys/#{key.name}" }
+ let(:key) { client_key }
+ end
+ end
+ end
+ end # API Interactions
+end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index 848af11db5..0195e6d406 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -23,7 +23,7 @@ require 'net/ssh'
describe Chef::Knife::Bootstrap do
before do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
end
let(:knife) do
Chef::Log.logger = Logger.new(StringIO.new)
@@ -115,7 +115,7 @@ describe Chef::Knife::Bootstrap do
end
def configure_env_home
- ENV['HOME'] = "/env/home"
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path)
end
def configure_gem_files
@@ -123,15 +123,9 @@ describe Chef::Knife::Bootstrap do
end
before(:each) do
- @original_home = ENV['HOME']
- ENV['HOME'] = nil
expect(File).to receive(:exists?).with(bootstrap_template).and_return(false)
end
- after(:each) do
- ENV['HOME'] = @original_home
- end
-
context "when file is available everywhere" do
before do
configure_chef_config_dir
@@ -161,7 +155,7 @@ describe Chef::Knife::Bootstrap do
end
end
- context "when file is available in ENV['HOME']" do
+ context "when file is available in home directory" do
before do
configure_chef_config_dir
configure_env_home
@@ -180,10 +174,28 @@ describe Chef::Knife::Bootstrap do
context "when file is available in Gem files" do
before do
configure_chef_config_dir
+ configure_env_home
configure_gem_files
expect(File).to receive(:exists?).with(builtin_template_path).and_return(false)
expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(false)
+ expect(File).to receive(:exists?).with(env_home_template_path).and_return(false)
+ expect(File).to receive(:exists?).with(gem_files_template_path).and_return(true)
+ end
+
+ it "should load the template from Gem files" do
+ expect(knife.find_template).to eq(gem_files_template_path)
+ end
+ end
+
+ context "when file is available in Gem files and home dir doesn't exist" do
+ before do
+ configure_chef_config_dir
+ configure_gem_files
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil)
+
+ expect(File).to receive(:exists?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exists?).with(chef_config_dir_template_path).and_return(false)
expect(File).to receive(:exists?).with(gem_files_template_path).and_return(true)
end
@@ -519,6 +531,7 @@ describe Chef::Knife::Bootstrap do
describe "when running the bootstrap" do
let(:knife_ssh) do
knife.name_args = ["foo.example.com"]
+ knife.config[:chef_node_name] = "foo.example.com"
knife.config[:ssh_user] = "rooty"
knife.config[:identity_file] = "~/.ssh/me.rsa"
allow(knife).to receive(:render_template).and_return("")
@@ -578,6 +591,12 @@ describe Chef::Knife::Bootstrap do
expect(knife.chef_vault_handler).not_to receive(:run).with(node_name: knife.config[:chef_node_name])
knife.run
end
+
+ it "raises an exception if the config[:chef_node_name] is not present" do
+ knife.config[:chef_node_name] = nil
+
+ expect { knife.run }.to raise_error(SystemExit)
+ end
end
context "when the validation key is not present" do
@@ -592,6 +611,12 @@ describe Chef::Knife::Bootstrap do
expect(knife.chef_vault_handler).to receive(:run).with(node_name: knife.config[:chef_node_name])
knife.run
end
+
+ it "raises an exception if the config[:chef_node_name] is not present" do
+ knife.config[:chef_node_name] = nil
+
+ expect { knife.run }.to raise_error(SystemExit)
+ end
end
context "when the validation_key is nil" do
diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb
index 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 10d386b5ff..a1dcc564e2 100644
--- a/spec/unit/knife/client_create_spec.rb
+++ b/spec/unit/knife/client_create_spec.rb
@@ -22,6 +22,8 @@ Chef::Knife::ClientCreate.load_deps
describe Chef::Knife::ClientCreate do
let(:stderr) { StringIO.new }
+ let(:stdout) { StringIO.new }
+
let(:default_client_hash) do
{
@@ -32,84 +34,153 @@ describe Chef::Knife::ClientCreate do
end
let(:client) do
- c = double("Chef::ApiClient")
- allow(c).to receive(:save).and_return({"private_key" => ""})
- allow(c).to receive(:to_s).and_return("client[adam]")
- c
+ Chef::ApiClientV1.new
end
let(:knife) do
k = Chef::Knife::ClientCreate.new
- k.name_args = [ "adam" ]
- k.ui.config[:disable_editing] = true
+ k.name_args = []
+ allow(k).to receive(:client).and_return(client)
+ allow(k).to receive(:edit_data).with(client).and_return(client)
allow(k.ui).to receive(:stderr).and_return(stderr)
- allow(k.ui).to receive(:stdout).and_return(stderr)
+ allow(k.ui).to receive(:stdout).and_return(stdout)
k
end
+ before do
+ allow(client).to receive(:to_s).and_return("client[adam]")
+ allow(knife).to receive(:create_client).and_return(client)
+ end
+
before(:each) do
Chef::Config[:node_name] = "webmonkey.example.com"
end
describe "run" do
- it "should create and save the ApiClient" do
- expect(Chef::ApiClient).to receive(:from_hash).and_return(client)
- expect(client).to receive(:save)
- knife.run
+ context "when nothing is passed" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { [] }
+ let(:fieldname) { 'client name' }
+ end
end
- it "should print a message upon creation" do
- expect(Chef::ApiClient).to receive(:from_hash).and_return(client)
- expect(client).to receive(:save)
- knife.run
- expect(stderr.string).to match /Created client.*adam/i
- end
+ context "when clientname is passed" do
+ before do
+ knife.name_args = ['adam']
+ end
- it "should set the Client name" do
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("name" => "adam")).and_return(client)
- knife.run
- end
+ context "when public_key and prevent_keygen are passed" do
+ before do
+ knife.config[:public_key] = "some_key"
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match /You cannot pass --public-key and --prevent-keygen/
+ end
+ end
- it "by default it is not an admin" do
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => false)).and_return(client)
- knife.run
- end
+ it "should create the ApiClient" do
+ expect(knife).to receive(:create_client)
+ knife.run
+ end
- it "by default it is not a validator" do
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => false)).and_return(client)
- knife.run
- end
+ it "should print a message upon creation" do
+ expect(knife).to receive(:create_client)
+ knife.run
+ expect(stderr.string).to match /Created client.*adam/i
+ end
- it "should allow you to edit the data" do
- expect(knife).to receive(:edit_hash).with(default_client_hash).and_return(default_client_hash)
- allow(Chef::ApiClient).to receive(:from_hash).and_return(client)
- knife.run
- end
+ it "should set the Client name" do
+ knife.run
+ expect(client.name).to eq("adam")
+ end
- describe "with -f or --file" do
- it "should write the private key to a file" do
- knife.config[:file] = "/tmp/monkeypants"
- allow_any_instance_of(Chef::ApiClient).to receive(:save).and_return({ 'private_key' => "woot" })
- filehandle = double("Filehandle")
- expect(filehandle).to receive(:print).with('woot')
- expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+ it "by default it is not an admin" do
knife.run
+ expect(client.admin).to be_falsey
end
- end
- describe "with -a or --admin" do
- it "should create an admin client" do
- knife.config[:admin] = true
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("admin" => true)).and_return(client)
+ it "by default it is not a validator" do
knife.run
+ expect(client.admin).to be_falsey
end
- end
- describe "with --validator" do
- it "should create an validator client" do
- knife.config[:validator] = true
- expect(Chef::ApiClient).to receive(:from_hash).with(hash_including("validator" => true)).and_return(client)
+ it "by default it should set create_key to true" do
knife.run
+ expect(client.create_key).to be_truthy
+ end
+
+ it "should allow you to edit the data" do
+ expect(knife).to receive(:edit_data).with(client).and_return(client)
+ knife.run
+ end
+
+ describe "with -f or --file" do
+ before do
+ client.private_key "woot"
+ end
+
+ it "should write the private key to a file" do
+ knife.config[:file] = "/tmp/monkeypants"
+ filehandle = double("Filehandle")
+ expect(filehandle).to receive(:print).with('woot')
+ expect(File).to receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle)
+ knife.run
+ end
+ end
+
+ describe "with -a or --admin" do
+ before do
+ knife.config[:admin] = true
+ end
+
+ it "should create an admin client" do
+ knife.run
+ expect(client.admin).to be_truthy
+ end
+ end
+
+ describe "with -p or --public-key" do
+ before do
+ knife.config[:public_key] = 'some_key'
+ allow(File).to receive(:read).and_return('some_key')
+ allow(File).to receive(:expand_path)
+ end
+
+ it "sets the public key" do
+ knife.run
+ expect(client.public_key).to eq('some_key')
+ end
+ end
+
+ describe "with -k or --prevent-keygen" do
+ before do
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "does not set create_key" do
+ knife.run
+ expect(client.create_key).to be_falsey
+ end
+ end
+
+ describe "with --validator" do
+ before do
+ knife.config[:validator] = true
+ end
+
+ it "should create an validator client" do
+ knife.run
+ expect(client.validator).to be_truthy
+ end
end
end
end
diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb
index 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/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 df42cff2ea..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");
@@ -19,206 +18,47 @@
require 'spec_helper'
describe Chef::Knife::SubcommandLoader do
- before do
- allow(Chef::Platform).to receive(:windows?) { false }
- @home = File.join(CHEF_SPEC_DATA, 'knife-home')
- @env = {'HOME' => @home}
- @loader = Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'), @env)
- end
-
- it "builds a list of the core subcommand file require paths" do
- 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
+ let(:loader) { Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands')) }
+ let(:home) { File.join(CHEF_SPEC_DATA, 'knife-home') }
+ let(:plugin_dir) { File.join(home, '.chef', 'plugins', 'knife') }
- 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)
+ before do
+ allow(ChefConfig).to receive(:windows?) { false }
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, home)
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)
+ after do
+ Chef::Util::PathHelper.class_variable_set(:@@home_dir, nil)
end
- # https://github.com/opscode/chef-dk/issues/227
- #
- # `knife` in ChefDK isn't from a gem install, it's directly run from a clone
- # of the source, but there can be one or more versions of chef also installed
- # as a gem. If the gem install contains a command that doesn't exist in the
- # source tree of the "primary" chef install, it can be loaded and cause an
- # error. We also want to ensure that we only load builtin commands from the
- # "primary" chef install.
- context "when a different version of chef is also installed as a gem" do
-
- let(:all_found_commands) do
- [
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
-
- # We use the fake version 1.0.0 because that version doesn't exist,
- # which ensures it won't ever equal "chef-#{Chef::VERSION}"
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/client_create.rb",
-
- # Test that we don't accept a version number that is different only in
- # trailing characters, e.g. we are running Chef 12.0.0 but there is a
- # Chef 12.0.0.rc.0 gem also:
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-#{Chef::VERSION}.rc.0/lib/chef/knife/thing.rb",
-
- # This command is "extra" compared to what's in the embedded/apps/chef install:
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-1.0.0/lib/chef/knife/data_bag_secret_options.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
-
- # These are fake commands that have names designed to test that the
- # regex is strict enough
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
-
- # 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(:config_dir) { File.join(CHEF_SPEC_DATA, 'knife-site-subcommands') }
- let(:expected_valid_commands) do
- [
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/bootstrap.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_bulk_delete.rb",
- "/opt/chefdk/embedded/apps/chef/lib/chef/knife/client_create.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-vault-2.2.4/lib/chef/knife/decrypt.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/knife-spork-1.4.1/lib/chef/knife/spork-bump.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-foo-#{Chef::VERSION}/lib/chef/knife/chef-foo.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/foo-chef-#{Chef::VERSION}/lib/chef/knife/foo-chef.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/chef-bar-1.0.0/lib/chef/knife/chef-bar.rb",
- "/opt/chefdk/embedded/lib/ruby/gems/2.1.0/gems/bar-chef-1.0.0/lib/chef/knife/bar-chef.rb"
- ]
- end
-
- before do
- expect(@loader).to receive(:find_files_latest_gems).with("chef/knife/*.rb").and_return(all_found_commands)
- expect(@loader).to receive(:find_subcommands_via_dirglob).and_return({})
- end
-
- it "ignores commands from the non-matching gem install" do
- expect(@loader.find_subcommands_via_rubygems.values).to eq(expected_valid_commands)
- end
-
- end
-
- describe "finding 3rd party plugins" do
- let(:env_home) { "/home/alice" }
- let(:manifest_path) { env_home + "/.chef/plugin_manifest.json" }
-
- before do
- env_dup = ENV.to_hash
- allow(ENV).to receive(:[]) { |key| env_dup[key] }
- allow(ENV).to receive(:[]).with("HOME").and_return(env_home)
- end
-
- context "when there is not a ~/.chef/plugin_manifest.json file" do
+ 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(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
+ it "creates a CustomManifestLoader with then manifest has a key other than _autogenerated_command_paths" do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ allow(File).to receive(:read).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return("{ \"plugins\": {}}")
+ expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::CustomManifestLoader
end
-
end
- context "when there is a ~/.chef/plugin_manifest.json file" do
- let(:ec2_server_create_plugin) { "/usr/lib/ruby/gems/knife-ec2-0.5.12/lib/chef/knife/ec2_server_create.rb" }
-
- let(:manifest_content) do
- { "plugins" => {
- "knife-ec2" => {
- "paths" => [
- ec2_server_create_plugin
- ]
- }
- }
- }
- end
-
- let(:manifest_json) { Chef::JSONCompat.to_json(manifest_content) }
-
+ context "when ~/.chef/plugin_manifest.json does not exist" do
before do
- allow(File).to receive(:exist?).with(manifest_path).and_return(true)
- allow(File).to receive(:read).with(manifest_path).and_return(manifest_json)
+ allow(File).to receive(:exist?).with(File.join(home, '.chef', 'plugin_manifest.json')).and_return(false)
end
- it "uses paths from the manifest instead of searching gems" do
- expect(Gem::Specification).not_to receive(:latest_specs).and_call_original
- expect(@loader.subcommand_files).to include(ec2_server_create_plugin)
+ it "creates a GemGlobLoader" do
+ expect(Chef::Knife::SubcommandLoader.for_config(config_dir)).to be_a Chef::Knife::SubcommandLoader::GemGlobLoader
end
-
end
end
-
end
diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb
index ac42ad6dd6..ab420518a3 100644
--- a/spec/unit/knife/core/ui_spec.rb
+++ b/spec/unit/knife/core/ui_spec.rb
@@ -368,6 +368,20 @@ EOM
@ui.config[:attribute] = "keys.keys"
expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { "keys.keys" => "values" } })
end
+
+ it "should return the name attribute" do
+ allow_any_instance_of(Chef::Node).to receive(:name).and_return("chef.localdomain")
+ input = Chef::Node.new
+ @ui.config[:attribute] = "name"
+ expect(@ui.format_for_display(input)).to eq( {"chef.localdomain"=>{"name"=>"chef.localdomain"} })
+ end
+
+ it "returns nil when given an attribute path that isn't a name or attribute" do
+ input = { "keys" => {"keys" => "values"}, "hi" => "ho", "id" => "sample-data-bag-item" }
+ non_existing_path = "nope.nada.nothingtoseehere"
+ @ui.config[:attribute] = non_existing_path
+ expect(@ui.format_for_display(input)).to eq({ "sample-data-bag-item" => { non_existing_path => nil } })
+ end
end
describe "with --run-list passed" do
@@ -420,7 +434,7 @@ EOM
before(:each) do
stdout = double('StringIO', :tty? => true)
allow(@ui).to receive(:stdout).and_return(stdout)
- allow(Chef::Platform).to receive(:windows?) { true }
+ allow(ChefConfig).to receive(:windows?) { true }
Chef::Config.reset
end
diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb
index 3882bff349..8b6502145c 100644
--- a/spec/unit/knife/data_bag_from_file_spec.rb
+++ b/spec/unit/knife/data_bag_from_file_spec.rb
@@ -26,7 +26,7 @@ Chef::Knife::DataBagFromFile.load_deps
describe Chef::Knife::DataBagFromFile do
before :each do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
Chef::Config[:node_name] = "webmonkey.example.com"
FileUtils.mkdir_p([db_folder, db_folder2])
db_file.write(Chef::JSONCompat.to_json(plain_data))
diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb
index d150e5ee64..11ad23c919 100644
--- a/spec/unit/knife/environment_from_file_spec.rb
+++ b/spec/unit/knife/environment_from_file_spec.rb
@@ -23,7 +23,7 @@ Chef::Knife::EnvironmentFromFile.load_deps
describe Chef::Knife::EnvironmentFromFile do
before(:each) do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
@knife = Chef::Knife::EnvironmentFromFile.new
@stdout = StringIO.new
allow(@knife.ui).to receive(:stdout).and_return(@stdout)
diff --git a/spec/unit/knife/key_create_spec.rb b/spec/unit/knife/key_create_spec.rb
new file mode 100644
index 0000000000..5998e10274
--- /dev/null
+++ b/spec/unit/knife/key_create_spec.rb
@@ -0,0 +1,224 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/knife/user_key_create'
+require 'chef/knife/client_key_create'
+require 'chef/knife/key_create'
+require 'chef/key'
+
+describe "key create commands that inherit knife" do
+ shared_examples_for "a key create command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do
+ expect(Chef::Knife::KeyCreate).to receive(:new).
+ with("charmander", command.actor_field_name, command.ui, command.config).
+ and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key create command
+
+ describe Chef::Knife::UserKeyCreate do
+ it_should_behave_like "a key create command"
+ # defined in key_helper.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+ let(:params) { ["charmander"] }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyCreate do
+ it_should_behave_like "a key create command"
+ # defined in key_helper.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyCreate) }
+ let(:params) { ["charmander"] }
+ end
+ end
+end
+
+describe Chef::Knife::KeyCreate do
+ let(:public_key) {
+ "-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----"
+ }
+ let(:config) { Hash.new }
+ let(:actor) { "charmander" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key create run command" do
+ let(:key_create_object) {
+ described_class.new(actor, actor_field_name, ui, config)
+ }
+
+ context "when public_key and key_name weren't passed" do
+ it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+ expect{ key_create_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_create_object.public_key_or_key_name_error_msg)
+ end
+ end
+
+ context "when the command is run" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander"
+ }
+ }
+
+ before do
+ allow(File).to receive(:read).and_return(public_key)
+ allow(File).to receive(:expand_path)
+
+ allow(key_create_object).to receive(:output_private_key_to_file)
+ allow(key_create_object).to receive(:display_private_key)
+ allow(key_create_object).to receive(:edit_data).and_return(expected_hash)
+ allow(key_create_object).to receive(:create_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ allow(key_create_object).to receive(:display_info)
+ end
+
+ context "when a valid hash is passed" do
+ let(:key_name) { "charmander-key" }
+ let(:valid_expiration_date) { "2020-12-24T21:00:00Z" }
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "expiration_date" => valid_expiration_date,
+ "key_name" => key_name
+ }
+ }
+ before do
+ key_create_object.config[:public_key] = "public_key_path"
+ key_create_object.config[:expiration_Date] = valid_expiration_date,
+ key_create_object.config[:key_name] = key_name
+ end
+
+ it "creates the proper hash" do
+ expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
+ key_create_object.run
+ end
+ end
+
+ context "when public_key is passed" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key
+ }
+ }
+ before do
+ key_create_object.config[:public_key] = "public_key_path"
+ end
+
+ it "calls File.expand_path with the public_key input" do
+ expect(File).to receive(:expand_path).with("public_key_path")
+ key_create_object.run
+ end
+ end # when public_key is passed
+
+ context "when public_key isn't passed and key_name is" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "name" => "charmander-key",
+ "create_key" => true
+ }
+ }
+ before do
+ key_create_object.config[:key_name] = "charmander-key"
+ end
+
+ it "should set create_key to true" do
+ expect(key_create_object).to receive(:create_key_from_hash).with(expected_hash)
+ key_create_object.run
+ end
+ end
+
+ context "when the server returns a private key" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "private_key" => "super_private"
+ }
+ }
+
+ before do
+ key_create_object.config[:public_key] = "public_key_path"
+ end
+
+ context "when file is not passed" do
+ it "calls display_private_key with the private_key" do
+ expect(key_create_object).to receive(:display_private_key).with("super_private")
+ key_create_object.run
+ end
+ end
+
+ context "when file is passed" do
+ before do
+ key_create_object.config[:file] = "/fake/file"
+ end
+
+ it "calls output_private_key_to_file with the private_key" do
+ expect(key_create_object).to receive(:output_private_key_to_file).with("super_private")
+ key_create_object.run
+ end
+ end
+ end # when the server returns a private key
+ end # when the command is run
+ end #key create run command"
+
+ context "when actor_field_name is 'user'" do
+ it_should_behave_like "key create run command" do
+ let(:actor_field_name) { "user" }
+ end
+ end
+
+ context "when actor_field_name is 'client'" do
+ it_should_behave_like "key create run command" do
+ let(:actor_field_name) { "client" }
+ end
+ end
+end
+
diff --git a/spec/unit/knife/key_delete_spec.rb b/spec/unit/knife/key_delete_spec.rb
new file mode 100644
index 0000000000..1d4b9f825f
--- /dev/null
+++ b/spec/unit/knife/key_delete_spec.rb
@@ -0,0 +1,135 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/knife/user_key_delete'
+require 'chef/knife/client_key_delete'
+require 'chef/knife/key_delete'
+require 'chef/key'
+
+describe "key delete commands that inherit knife" do
+ shared_examples_for "a key delete command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander", "charmander-key"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyDelete with the correct args" do
+ expect(Chef::Knife::KeyDelete).to receive(:new).
+ with("charmander-key", "charmander", command.actor_field_name, command.ui).
+ and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key delete command
+
+ describe Chef::Knife::UserKeyDelete do
+ it_should_behave_like "a key delete command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+ let(:params) { ["charmander", "charmander-key"] }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyDelete do
+ it_should_behave_like "a key delete command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyDelete) }
+ let(:params) { ["charmander", "charmander-key"] }
+ end
+ end
+end
+
+describe Chef::Knife::KeyDelete do
+ let(:actor) { "charmander" }
+ let(:keyname) { "charmander-key" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key delete run command" do
+ let(:key_delete_object) {
+ described_class.new(keyname, actor, actor_field_name, ui)
+ }
+
+ before do
+ allow_any_instance_of(Chef::Key).to receive(:destroy)
+ allow(key_delete_object).to receive(:print_destroyed)
+ allow(key_delete_object).to receive(:confirm!)
+ end
+
+ context "when the command is run" do
+ it "calls Chef::Key.new with the proper input" do
+ expect(Chef::Key).to receive(:new).with(actor, actor_field_name).and_call_original
+ key_delete_object.run
+ end
+
+ it "calls name on the Chef::Key instance with the proper input" do
+ expect_any_instance_of(Chef::Key).to receive(:name).with(keyname)
+ key_delete_object.run
+ end
+
+ it "calls destroy on the Chef::Key instance" do
+ expect_any_instance_of(Chef::Key).to receive(:destroy).once
+ key_delete_object.run
+ end
+
+ it "calls confirm!" do
+ expect(key_delete_object).to receive(:confirm!)
+ key_delete_object.run
+ end
+
+ it "calls print_destroyed" do
+ expect(key_delete_object).to receive(:print_destroyed)
+ key_delete_object.run
+ end
+ end # when the command is run
+
+
+ end # key delete run command
+
+ context "when actor_field_name is 'user'" do
+ it_should_behave_like "key delete run command" do
+ let(:actor_field_name) { "user" }
+ end
+ end
+
+ context "when actor_field_name is 'client'" do
+ it_should_behave_like "key delete run command" do
+ let(:actor_field_name) { "client" }
+ end
+ end
+end
+
diff --git a/spec/unit/knife/key_edit_spec.rb b/spec/unit/knife/key_edit_spec.rb
new file mode 100644
index 0000000000..538b91de2d
--- /dev/null
+++ b/spec/unit/knife/key_edit_spec.rb
@@ -0,0 +1,267 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/knife/user_key_edit'
+require 'chef/knife/client_key_edit'
+require 'chef/knife/key_edit'
+require 'chef/key'
+
+describe "key edit commands that inherit knife" do
+ shared_examples_for "a key edit command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander", "charmander-key"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyEdit with the correct args" do
+ expect(Chef::Knife::KeyEdit).to receive(:new).
+ with("charmander-key", "charmander", command.actor_field_name, command.ui, command.config).
+ and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key edit command
+
+ describe Chef::Knife::UserKeyEdit do
+ it_should_behave_like "a key edit command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+ let(:params) { ["charmander", "charmander-key"] }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyEdit do
+ it_should_behave_like "a key edit command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyEdit) }
+ let(:params) { ["charmander", "charmander-key"] }
+ end
+ end
+end
+
+describe Chef::Knife::KeyEdit do
+ let(:public_key) {
+ "-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvPo+oNPB7uuNkws0fC02
+KxSwdyqPLu0fhI1pOweNKAZeEIiEz2PkybathHWy8snSXGNxsITkf3eyvIIKa8OZ
+WrlqpI3yv/5DOP8HTMCxnFuMJQtDwMcevlqebX4bCxcByuBpNYDcAHjjfLGSfMjn
+E5lZpgYWwnpic4kSjYcL9ORK9nYvlWV9P/kCYmRhIjB4AhtpWRiOfY/TKi3P2LxT
+IjSmiN/ihHtlhV/VSnBJ5PzT/lRknlrJ4kACoz7Pq9jv+aAx5ft/xE9yDa2DYs0q
+Tfuc9dUYsFjptWYrV6pfEQ+bgo1OGBXORBFcFL+2D7u9JYquKrMgosznHoEkQNLo
+0wIDAQAB
+-----END PUBLIC KEY-----"
+ }
+ let(:config) { Hash.new }
+ let(:actor) { "charmander" }
+ let(:keyname) { "charmander-key" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key edit run command" do
+ let(:key_edit_object) {
+ described_class.new(keyname, actor, actor_field_name, ui, config)
+ }
+
+ context "when the command is run" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander"
+ }
+ }
+ let(:new_keyname) { "charizard-key" }
+
+ before do
+ allow(File).to receive(:read).and_return(public_key)
+ allow(File).to receive(:expand_path)
+
+ allow(key_edit_object).to receive(:output_private_key_to_file)
+ allow(key_edit_object).to receive(:display_private_key)
+ allow(key_edit_object).to receive(:edit_data).and_return(expected_hash)
+ allow(key_edit_object).to receive(:display_info)
+ end
+
+
+ context "when public_key and create_key are passed" do
+ before do
+ key_edit_object.config[:public_key] = "public_key_path"
+ key_edit_object.config[:create_key] = true
+ end
+
+ it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+ expect{ key_edit_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_edit_object.public_key_and_create_key_error_msg)
+ end
+ end
+
+ context "when key_name is passed" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "name" => new_keyname
+ }
+ }
+ before do
+ key_edit_object.config[:key_name] = new_keyname
+ allow_any_instance_of(Chef::Key).to receive(:update)
+ end
+
+ it "update_key_from_hash gets passed a hash with new key name" do
+ expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.run
+ end
+
+ it "Chef::Key.update is passed a string containing the original keyname" do
+ expect_any_instance_of(Chef::Key).to receive(:update).with(/#{keyname}/).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.run
+ end
+
+ it "Chef::Key.update is not passed a string containing the new keyname" do
+ expect_any_instance_of(Chef::Key).not_to receive(:update).with(/#{new_keyname}/)
+ allow_any_instance_of(Chef::Key).to receive(:update).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.run
+ end
+ end
+
+ context "when public_key, key_name, and expiration_date are passed" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "name" => new_keyname,
+ "expiration_date" => "infinity"
+ }
+ }
+ before do
+ key_edit_object.config[:public_key] = "this-public-key"
+ key_edit_object.config[:key_name] = new_keyname
+ key_edit_object.config[:expiration_date] = "infinity"
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ end
+
+ it "passes the right hash to update_key_from_hash" do
+ expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
+ key_edit_object.run
+ end
+ end
+
+ context "when create_key is passed" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "create_key" => true
+ }
+ }
+
+ before do
+ key_edit_object.config[:create_key] = true
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ end
+
+ it "passes the right hash to update_key_from_hash" do
+ expect(key_edit_object).to receive(:update_key_from_hash).with(expected_hash)
+ key_edit_object.run
+ end
+ end
+
+ context "when public_key is passed" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key
+ }
+ }
+ before do
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.config[:public_key] = "public_key_path"
+ end
+
+ it "calls File.expand_path with the public_key input" do
+ expect(File).to receive(:expand_path).with("public_key_path")
+ key_edit_object.run
+ end
+ end # when public_key is passed
+
+ context "when the server returns a private key" do
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "public_key" => public_key,
+ "private_key" => "super_private"
+ }
+ }
+
+ before do
+ allow(key_edit_object).to receive(:update_key_from_hash).and_return(Chef::Key.from_hash(expected_hash))
+ key_edit_object.config[:public_key] = "public_key_path"
+ end
+
+ context "when file is not passed" do
+ it "calls display_private_key with the private_key" do
+ expect(key_edit_object).to receive(:display_private_key).with("super_private")
+ key_edit_object.run
+ end
+ end
+
+ context "when file is passed" do
+ before do
+ key_edit_object.config[:file] = "/fake/file"
+ end
+
+ it "calls output_private_key_to_file with the private_key" do
+ expect(key_edit_object).to receive(:output_private_key_to_file).with("super_private")
+ key_edit_object.run
+ end
+ end
+ end # when the server returns a private key
+
+ end # when the command is run
+
+
+
+ end # key edit run command
+
+ context "when actor_field_name is 'user'" do
+ it_should_behave_like "key edit run command" do
+ let(:actor_field_name) { "user" }
+ end
+ end
+
+ context "when actor_field_name is 'client'" do
+ it_should_behave_like "key edit run command" do
+ let(:actor_field_name) { "client" }
+ end
+ end
+end
diff --git a/spec/unit/knife/key_helper.rb b/spec/unit/knife/key_helper.rb
new file mode 100644
index 0000000000..36ababc09a
--- /dev/null
+++ b/spec/unit/knife/key_helper.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+shared_examples_for "a knife key command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "before apply_params! is called" do
+ context "when apply_params! is called with invalid args" do
+ it "shows the usage" do
+ expect(command).to receive(:show_usage)
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+
+ it "outputs the proper error" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ expect(stderr.string).to include(command.actor_missing_error)
+ end
+
+ it "exits 1" do
+ expect { command.apply_params!(params) }.to exit_with_code(1)
+ end
+ end
+ end # before apply_params! is called
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ it "properly defines the actor" do
+ expect(command.actor).to eq("charmander")
+ end
+ end # after apply_params! is called with valid args
+
+ context "when the command is run" do
+ before do
+ allow(command).to receive(:service_object).and_return(service_object)
+ allow(command).to receive(:name_args).and_return(["charmander"])
+ end
+
+ context "when the command is successful" do
+ before do
+ expect(service_object).to receive(:run)
+ end
+ end
+ end
+end # a knife key command
diff --git a/spec/unit/knife/key_list_spec.rb b/spec/unit/knife/key_list_spec.rb
new file mode 100644
index 0000000000..aabe02ac02
--- /dev/null
+++ b/spec/unit/knife/key_list_spec.rb
@@ -0,0 +1,216 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/knife/user_key_list'
+require 'chef/knife/client_key_list'
+require 'chef/knife/key_list'
+require 'chef/key'
+
+describe "key list commands that inherit knife" do
+ shared_examples_for "a key list command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyList) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyList with the correct args" do
+ expect(Chef::Knife::KeyList).to receive(:new).
+ with("charmander", command.list_method, command.ui, command.config).
+ and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key list command
+
+ describe Chef::Knife::UserKeyList do
+ it_should_behave_like "a key list command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyList) }
+ let(:params) { ["charmander"] }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyList do
+ it_should_behave_like "a key list command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyList) }
+ let(:params) { ["charmander"] }
+ end
+ end
+end
+
+describe Chef::Knife::KeyList do
+ let(:config) { Hash.new }
+ let(:actor) { "charmander" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+
+ shared_examples_for "key list run command" do
+ let(:key_list_object) {
+ described_class.new(actor, list_method, ui, config)
+ }
+
+ before do
+ allow(Chef::Key).to receive(list_method).and_return(http_response)
+ allow(key_list_object).to receive(:display_info)
+ # simply pass the string though that colorize takes in
+ allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input|
+ input
+ end
+ end
+
+ context "when only_expired and only_non_expired were both passed" do
+ before do
+ key_list_object.config[:only_expired] = true
+ key_list_object.config[:only_non_expired] = true
+ end
+
+ it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do
+ expect{ key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg)
+ end
+ end
+
+ context "when the command is run" do
+ before do
+ key_list_object.config[:only_expired] = false
+ key_list_object.config[:only_non_expired] = false
+ key_list_object.config[:with_details] = false
+ end
+
+ it "calls Chef::Key with the proper list command and input" do
+ expect(Chef::Key).to receive(list_method).with(actor)
+ key_list_object.run
+ end
+
+ it "displays all the keys" do
+ expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
+ expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
+ key_list_object.run
+ end
+
+ context "when only_expired is called" do
+ before do
+ key_list_object.config[:only_expired] = true
+ end
+
+ it "excludes displaying non-expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times
+ key_list_object.run
+ end
+
+ it "displays the expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/out-of-date/).once
+ key_list_object.run
+ end
+ end # when only_expired is called
+
+ context "when only_non_expired is called" do
+ before do
+ key_list_object.config[:only_non_expired] = true
+ end
+
+ it "excludes displaying expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times
+ key_list_object.run
+ end
+
+ it "displays the non-expired keys" do
+ expect(key_list_object).to receive(:display_info).with(/non-expired/).twice
+ key_list_object.run
+ end
+ end # when only_expired is called
+
+ context "when with_details is false" do
+ before do
+ key_list_object.config[:with_details] = false
+ end
+
+ it "does not display the uri" do
+ expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times
+ key_list_object.run
+ end
+
+ it "does not display the expired status" do
+ expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times
+ key_list_object.run
+ end
+ end # when with_details is false
+
+ context "when with_details is true" do
+ before do
+ key_list_object.config[:with_details] = true
+ end
+
+ it "displays the uri" do
+ expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times
+ key_list_object.run
+ end
+
+ it "displays the expired status" do
+ expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once
+ key_list_object.run
+ end
+ end # when with_details is true
+
+ end # when the command is run
+
+ end # key list run command
+
+ context "when list_method is :list_by_user" do
+ it_should_behave_like "key list run command" do
+ let(:list_method) { :list_by_user }
+ let(:http_response) {
+ [
+ {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false},
+ {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false},
+ {"uri"=>"https://api.opscode.piab/users/mary/keys/out-of-date", "name"=>"out-of-date", "expired"=>true}
+ ]
+ }
+ end
+ end
+
+ context "when list_method is :list_by_client" do
+ it_should_behave_like "key list run command" do
+ let(:list_method) { :list_by_client }
+ let(:http_response) {
+ [
+ {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false},
+ {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false},
+ {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date", "name"=>"out-of-date", "expired"=>true}
+ ]
+ }
+ end
+ end
+end
diff --git a/spec/unit/knife/key_show_spec.rb b/spec/unit/knife/key_show_spec.rb
new file mode 100644
index 0000000000..5a0d839e4f
--- /dev/null
+++ b/spec/unit/knife/key_show_spec.rb
@@ -0,0 +1,126 @@
+#
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/knife/user_key_show'
+require 'chef/knife/client_key_show'
+require 'chef/knife/key_show'
+require 'chef/key'
+
+describe "key show commands that inherit knife" do
+ shared_examples_for "a key show command" do
+ let(:stderr) { StringIO.new }
+ let(:params) { [] }
+ let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+ let(:command) do
+ c = described_class.new([])
+ c.ui.config[:disable_editing] = true
+ allow(c.ui).to receive(:stderr).and_return(stderr)
+ allow(c.ui).to receive(:stdout).and_return(stderr)
+ allow(c).to receive(:show_usage)
+ c
+ end
+
+ context "after apply_params! is called with valid args" do
+ let(:params) { ["charmander", "charmander-key"] }
+ before do
+ command.apply_params!(params)
+ end
+
+ context "when the service object is called" do
+ it "creates a new instance of Chef::Knife::KeyShow with the correct args" do
+ expect(Chef::Knife::KeyShow).to receive(:new).
+ with("charmander-key", "charmander", command.load_method, command.ui).
+ and_return(service_object)
+ command.service_object
+ end
+ end # when the service object is called
+ end # after apply_params! is called with valid args
+ end # a key show command
+
+ describe Chef::Knife::UserKeyShow do
+ it_should_behave_like "a key show command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+ let(:params) { ["charmander", "charmander-key"] }
+ end
+ end
+
+ describe Chef::Knife::ClientKeyShow do
+ it_should_behave_like "a key show command"
+ # defined in key_helpers.rb
+ it_should_behave_like "a knife key command with a keyname as the second arg"
+ it_should_behave_like "a knife key command" do
+ let(:service_object) { instance_double(Chef::Knife::KeyShow) }
+ let(:params) { ["charmander", "charmander-key"] }
+ end
+ end
+end
+
+describe Chef::Knife::KeyShow do
+ let(:actor) { "charmander" }
+ let(:keyname) { "charmander" }
+ let(:ui) { instance_double("Chef::Knife::UI") }
+ let(:expected_hash) {
+ {
+ actor_field_name => "charmander",
+ "name" => "charmander-key",
+ "public_key" => "some-public-key",
+ "expiration_date" => "infinity"
+ }
+ }
+
+ shared_examples_for "key show run command" do
+ let(:key_show_object) {
+ described_class.new(keyname, actor, load_method, ui)
+ }
+
+ before do
+ allow(key_show_object).to receive(:display_output)
+ allow(Chef::Key).to receive(load_method).and_return(Chef::Key.from_hash(expected_hash))
+ end
+
+ context "when the command is run" do
+ it "loads the key using the proper method and args" do
+ expect(Chef::Key).to receive(load_method).with(actor, keyname)
+ key_show_object.run
+ end
+
+ it "displays the key" do
+ expect(key_show_object).to receive(:display_output)
+ key_show_object.run
+ end
+ end
+ end
+
+ context "when load_method is :load_by_user" do
+ it_should_behave_like "key show run command" do
+ let(:load_method) { :load_by_user }
+ let(:actor_field_name) { 'user' }
+ end
+ end
+
+ context "when load_method is :load_by_client" do
+ it_should_behave_like "key show run command" do
+ let(:load_method) { :load_by_client }
+ let(:actor_field_name) { 'user' }
+ end
+ end
+end
diff --git a/spec/unit/knife/osc_user_create_spec.rb b/spec/unit/knife/osc_user_create_spec.rb
new file mode 100644
index 0000000000..e4ed78fe2b
--- /dev/null
+++ b/spec/unit/knife/osc_user_create_spec.rb
@@ -0,0 +1,93 @@
+#
+# 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'
+
+Chef::Knife::OscUserCreate.load_deps
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_create_spec.rb.
+
+describe Chef::Knife::OscUserCreate do
+ before(:each) do
+ @knife = Chef::Knife::OscUserCreate.new
+
+ @stdout = StringIO.new
+ @stderr = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+
+ @knife.name_args = [ 'a_user' ]
+ @knife.config[:user_password] = "foobar"
+ @user = Chef::User.new
+ @user.name "a_user"
+ @user_with_private_key = Chef::User.new
+ @user_with_private_key.name "a_user"
+ @user_with_private_key.private_key 'private_key'
+ allow(@user).to receive(:create).and_return(@user_with_private_key)
+ allow(Chef::User).to receive(:new).and_return(@user)
+ allow(Chef::User).to receive(:from_hash).and_return(@user)
+ allow(@knife).to receive(:edit_data).and_return(@user.to_hash)
+ end
+
+ it "creates a new user" do
+ expect(Chef::User).to receive(:new).and_return(@user)
+ expect(@user).to receive(:create)
+ @knife.run
+ expect(@stderr.string).to match /created user.+a_user/i
+ end
+
+ it "sets the password" do
+ @knife.config[:user_password] = "a_password"
+ expect(@user).to receive(:password).with("a_password")
+ @knife.run
+ end
+
+ it "exits with an error if password is blank" do
+ @knife.config[:user_password] = ''
+ expect { @knife.run }.to raise_error SystemExit
+ expect(@stderr.string).to match /You must specify a non-blank password/
+ end
+
+ it "sets the user name" do
+ expect(@user).to receive(:name).with("a_user")
+ @knife.run
+ end
+
+ it "sets the public key if given" do
+ @knife.config[:user_key] = "/a/filename"
+ allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key")
+ expect(@user).to receive(:public_key).with("a_key")
+ @knife.run
+ end
+
+ it "allows you to edit the data" do
+ expect(@knife).to receive(:edit_data).with(@user)
+ @knife.run
+ end
+
+ it "writes the private key to a file when --file is specified" do
+ @knife.config[:file] = "/tmp/a_file"
+ filehandle = double("filehandle")
+ expect(filehandle).to receive(:print).with('private_key')
+ expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle)
+ @knife.run
+ end
+end
diff --git a/spec/unit/knife/osc_user_delete_spec.rb b/spec/unit/knife/osc_user_delete_spec.rb
new file mode 100644
index 0000000000..4a3ec4228f
--- /dev/null
+++ b/spec/unit/knife/osc_user_delete_spec.rb
@@ -0,0 +1,44 @@
+#
+# 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'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_delete_spec.rb.
+
+describe Chef::Knife::OscUserDelete do
+ before(:each) do
+ Chef::Knife::OscUserDelete.load_deps
+ @knife = Chef::Knife::OscUserDelete.new
+ @knife.name_args = [ 'my_user' ]
+ end
+
+ it 'deletes the user' do
+ expect(@knife).to receive(:delete_object).with(Chef::User, 'my_user')
+ @knife.run
+ end
+
+ it 'prints usage and exits when a user name is not provided' do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+end
diff --git a/spec/unit/knife/osc_user_edit_spec.rb b/spec/unit/knife/osc_user_edit_spec.rb
new file mode 100644
index 0000000000..279f2e30ef
--- /dev/null
+++ b/spec/unit/knife/osc_user_edit_spec.rb
@@ -0,0 +1,52 @@
+#
+# 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'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_edit_spec.rb.
+
+describe Chef::Knife::OscUserEdit do
+ before(:each) do
+ @stderr = StringIO.new
+ @stdout = StringIO.new
+
+ Chef::Knife::OscUserEdit.load_deps
+ @knife = Chef::Knife::OscUserEdit.new
+ allow(@knife.ui).to receive(:stderr).and_return(@stderr)
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ @knife.name_args = [ 'my_user' ]
+ @knife.config[:disable_editing] = true
+ end
+
+ it 'loads and edits the user' do
+ data = { :name => "my_user" }
+ allow(Chef::User).to receive(:load).with("my_user").and_return(data)
+ expect(@knife).to receive(:edit_data).with(data).and_return(data)
+ @knife.run
+ end
+
+ it 'prints usage and exits when a user name is not provided' do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+end
diff --git a/spec/unit/provider/powershell_spec.rb b/spec/unit/knife/osc_user_list_spec.rb
index 60dbcf80b0..f496a414b8 100644
--- a/spec/unit/provider/powershell_spec.rb
+++ b/spec/unit/knife/osc_user_list_spec.rb
@@ -1,6 +1,6 @@
#
-# Author:: Adam Edwards (<adamed@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Author:: Steven Danna
+# Copyright:: Copyright (c) 2012 Opscode, Inc
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,22 +17,21 @@
#
require 'spec_helper'
-describe Chef::Provider::PowershellScript, "action_run" do
- before(:each) do
- @node = Chef::Node.new
-
- @node.default["kernel"] = Hash.new
- @node.default["kernel"][:machine] = :x86_64.to_s
-
- @run_context = Chef::RunContext.new(@node, {}, @events)
- @new_resource = Chef::Resource::PowershellScript.new('run some powershell code', @run_context)
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_list_spec.rb.
- @provider = Chef::Provider::PowershellScript.new(@new_resource, @run_context)
+describe Chef::Knife::OscUserList do
+ before(:each) do
+ Chef::Knife::OscUserList.load_deps
+ @knife = Chef::Knife::OscUserList.new
end
- it "should set the -File flag as the last flag" do
- expect(@provider.flags.split(' ').pop).to eq("-File")
+ it 'lists the users' do
+ expect(Chef::User).to receive(:list)
+ expect(@knife).to receive(:format_list_for_display)
+ @knife.run
end
-
end
diff --git a/spec/unit/knife/osc_user_reregister_spec.rb b/spec/unit/knife/osc_user_reregister_spec.rb
new file mode 100644
index 0000000000..989eb180f1
--- /dev/null
+++ b/spec/unit/knife/osc_user_reregister_spec.rb
@@ -0,0 +1,58 @@
+#
+# 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'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur in user_reregister_spec.rb.
+
+describe Chef::Knife::OscUserReregister do
+ before(:each) do
+ Chef::Knife::OscUserReregister.load_deps
+ @knife = Chef::Knife::OscUserReregister.new
+ @knife.name_args = [ 'a_user' ]
+ @user_mock = double('user_mock', :private_key => "private_key")
+ allow(Chef::User).to receive(:load).and_return(@user_mock)
+ @stdout = StringIO.new
+ allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ end
+
+ it 'prints usage and exits when a user name is not provided' do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+
+ it 'reregisters the user and prints the key' do
+ expect(@user_mock).to receive(:reregister).and_return(@user_mock)
+ @knife.run
+ expect(@stdout.string).to match( /private_key/ )
+ end
+
+ it 'writes the private key to a file when --file is specified' do
+ expect(@user_mock).to receive(:reregister).and_return(@user_mock)
+ @knife.config[:file] = '/tmp/a_file'
+ filehandle = StringIO.new
+ expect(File).to receive(:open).with('/tmp/a_file', 'w').and_yield(filehandle)
+ @knife.run
+ expect(filehandle.string).to eq("private_key")
+ end
+end
diff --git a/spec/unit/knife/osc_user_show_spec.rb b/spec/unit/knife/osc_user_show_spec.rb
new file mode 100644
index 0000000000..18d2086099
--- /dev/null
+++ b/spec/unit/knife/osc_user_show_spec.rb
@@ -0,0 +1,46 @@
+#
+# 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'
+
+# DEPRECATION NOTE
+# This code only remains to support users still operating with
+# Open Source Chef Server 11 and should be removed once support
+# for OSC 11 ends. New development should occur user_show_spec.rb.
+
+describe Chef::Knife::OscUserShow do
+ before(:each) do
+ Chef::Knife::OscUserShow.load_deps
+ @knife = Chef::Knife::OscUserShow.new
+ @knife.name_args = [ 'my_user' ]
+ @user_mock = double('user_mock')
+ end
+
+ it 'loads and displays the user' do
+ expect(Chef::User).to receive(:load).with('my_user').and_return(@user_mock)
+ expect(@knife).to receive(:format_for_display).with(@user_mock)
+ @knife.run
+ end
+
+ it 'prints usage and exits when a user name is not provided' do
+ @knife.name_args = []
+ expect(@knife).to receive(:show_usage)
+ expect(@knife.ui).to receive(:fatal)
+ expect { @knife.run }.to raise_error(SystemExit)
+ end
+end
diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb
index 501b02c933..723280bead 100644
--- a/spec/unit/knife/ssh_spec.rb
+++ b/spec/unit/knife/ssh_spec.rb
@@ -28,10 +28,10 @@ describe Chef::Knife::Ssh do
before do
@knife = Chef::Knife::Ssh.new
@knife.merge_configs
- @knife.config[:attribute] = "fqdn"
@node_foo = Chef::Node.new
@node_foo.automatic_attrs[:fqdn] = "foo.example.org"
@node_foo.automatic_attrs[:ipaddress] = "10.0.0.1"
+
@node_bar = Chef::Node.new
@node_bar.automatic_attrs[:fqdn] = "bar.example.org"
@node_bar.automatic_attrs[:ipaddress] = "10.0.0.2"
@@ -52,15 +52,15 @@ describe Chef::Knife::Ssh do
def self.should_return_specified_attributes
it "returns an array of the attributes specified on the command line OR config file, if only one is set" do
@knife.config[:attribute] = "ipaddress"
- @knife.config[:attribute_from_cli] = "ipaddress"
+ Chef::Config[:knife][:ssh_attribute] = "ipaddress" # this value will be in the config file
configure_query([@node_foo, @node_bar])
expect(@knife).to receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
@knife.configure_session
end
it "returns an array of the attributes specified on the command line even when a config value is set" do
- @knife.config[:attribute] = "config_file" # this value will be the config file
- @knife.config[:attribute_from_cli] = "ipaddress" # this is the value of the command line via #configure_attribute
+ Chef::Config[:knife][:ssh_attribute] = "config_file" # this value will be in the config file
+ @knife.config[:attribute] = "ipaddress" # this is the value of the command line via #configure_attribute
configure_query([@node_foo, @node_bar])
expect(@knife).to receive(:session_from_list).with([['10.0.0.1', nil], ['10.0.0.2', nil]])
@knife.configure_session
@@ -83,7 +83,6 @@ describe Chef::Knife::Ssh do
@node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com"
@node_bar.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-2.compute-1.amazonaws.com"
end
-
it "returns an array of cloud public hostnames" do
configure_query([@node_foo, @node_bar])
expect(@knife).to receive(:session_from_list).with([
@@ -96,6 +95,24 @@ describe Chef::Knife::Ssh do
should_return_specified_attributes
end
+ context "when cloud hostnames are available but empty" do
+ before do
+ @node_foo.automatic_attrs[:cloud][:public_hostname] = ''
+ @node_bar.automatic_attrs[:cloud][:public_hostname] = ''
+ end
+
+ it "returns an array of fqdns" do
+ configure_query([@node_foo, @node_bar])
+ expect(@knife).to receive(:session_from_list).with([
+ ['foo.example.org', nil],
+ ['bar.example.org', nil]
+ ])
+ @knife.configure_session
+ end
+
+ should_return_specified_attributes
+ end
+
it "should raise an error if no host are found" do
configure_query([ ])
expect(@knife.ui).to receive(:fatal)
@@ -132,42 +149,40 @@ describe Chef::Knife::Ssh do
end
end
- describe "#configure_attribute" do
+ describe "#get_ssh_attribute" do
+ # Order of precedence for ssh target
+ # 1) command line attribute
+ # 2) configuration file
+ # 3) cloud attribute
+ # 4) fqdn
before do
Chef::Config[:knife][:ssh_attribute] = nil
@knife.config[:attribute] = nil
+ @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com"
+ @node_bar.automatic_attrs[:cloud][:public_hostname] = ''
end
it "should return fqdn by default" do
- @knife.configure_attribute
- expect(@knife.config[:attribute]).to eq("fqdn")
+ expect(@knife.get_ssh_attribute(Chef::Node.new)).to eq("fqdn")
end
- it "should return the value set in the configuration file" do
- Chef::Config[:knife][:ssh_attribute] = "config_file"
- @knife.configure_attribute
- expect(@knife.config[:attribute]).to eq("config_file")
+ it "should return cloud.public_hostname attribute if available" do
+ expect(@knife.get_ssh_attribute(@node_foo)).to eq("cloud.public_hostname")
end
- it "should return the value set on the command line" do
+ it "should favor to attribute_from_cli over config file and cloud" do
@knife.config[:attribute] = "command_line"
- @knife.configure_attribute
- expect(@knife.config[:attribute]).to eq("command_line")
+ Chef::Config[:knife][:ssh_attribute] = "config_file"
+ expect( @knife.get_ssh_attribute(@node_foo)).to eq("command_line")
end
- it "should set attribute_from_cli to the value of attribute from the command line" do
- @knife.config[:attribute] = "command_line"
- @knife.configure_attribute
- expect(@knife.config[:attribute]).to eq("command_line")
- expect(@knife.config[:attribute_from_cli]).to eq("command_line")
+ it "should favor config file over cloud and default" do
+ Chef::Config[:knife][:ssh_attribute] = "config_file"
+ expect( @knife.get_ssh_attribute(@node_foo)).to eq("config_file")
end
- it "should prefer the command line over the config file for the value of attribute_from_cli" do
- Chef::Config[:knife][:ssh_attribute] = "config_file"
- @knife.config[:attribute] = "command_line"
- @knife.configure_attribute
- expect(@knife.config[:attribute]).to eq("command_line")
- expect(@knife.config[:attribute_from_cli]).to eq("command_line")
+ it "should return fqdn if cloud.hostname is empty" do
+ expect( @knife.get_ssh_attribute(@node_bar)).to eq("fqdn")
end
end
diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb
index 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/status_spec.rb b/spec/unit/knife/status_spec.rb
index 2522bc61b1..ee44f3b3fd 100644
--- a/spec/unit/knife/status_spec.rb
+++ b/spec/unit/knife/status_spec.rb
@@ -24,15 +24,81 @@ describe Chef::Knife::Status do
n.automatic_attrs["fqdn"] = "foobar"
n.automatic_attrs["ohai_time"] = 1343845969
end
- query = double("Chef::Search::Query")
- allow(query).to receive(:search).and_yield(node)
- allow(Chef::Search::Query).to receive(:new).and_return(query)
+ allow(Time).to receive(:now).and_return(Time.at(1428573420))
+ @query = double("Chef::Search::Query")
+ allow(@query).to receive(:search).and_yield(node)
+ allow(Chef::Search::Query).to receive(:new).and_return(@query)
@knife = Chef::Knife::Status.new
@stdout = StringIO.new
allow(@knife.ui).to receive(:stdout).and_return(@stdout)
end
describe "run" do
+ let(:opts) {{filter_result:
+ { name: ["name"], ipaddress: ["ipaddress"], ohai_time: ["ohai_time"],
+ ec2: ["ec2"], run_list: ["run_list"], platform: ["platform"],
+ platform_version: ["platform_version"], chef_environment: ["chef_environment"]}}}
+
+ it "should default to searching for everything" do
+ expect(@query).to receive(:search).with(:node, "*:*", opts)
+ @knife.run
+ end
+
+ it "should filter healthy nodes" do
+ @knife.config[:hide_healthy] = true
+ expect(@query).to receive(:search).with(:node, "NOT ohai_time:[1428569820 TO 1428573420]", opts)
+ @knife.run
+ end
+
+ it "should filter by environment" do
+ @knife.config[:environment] = "production"
+ expect(@query).to receive(:search).with(:node, "chef_environment:production", opts)
+ @knife.run
+ end
+
+ it "should filter by environment and health" do
+ @knife.config[:environment] = "production"
+ @knife.config[:hide_healthy] = true
+ expect(@query).to receive(:search).with(:node, "chef_environment:production NOT ohai_time:[1428569820 TO 1428573420]", opts)
+ @knife.run
+ end
+
+ it "should not use partial search with long output" do
+ @knife.config[:long_output] = true
+ expect(@query).to receive(:search).with(:node, "*:*", {})
+ @knife.run
+ end
+
+ context "with a custom query" do
+ before :each do
+ @knife.instance_variable_set(:@name_args, ["name:my_custom_name"])
+ end
+
+ it "should allow a custom query to be specified" do
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name", opts)
+ @knife.run
+ end
+
+ it "should filter healthy nodes" do
+ @knife.config[:hide_healthy] = true
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name NOT ohai_time:[1428569820 TO 1428573420]", opts)
+ @knife.run
+ end
+
+ it "should filter by environment" do
+ @knife.config[:environment] = "production"
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production", opts)
+ @knife.run
+ end
+
+ it "should filter by environment and health" do
+ @knife.config[:environment] = "production"
+ @knife.config[:hide_healthy] = true
+ expect(@query).to receive(:search).with(:node, "name:my_custom_name AND chef_environment:production NOT ohai_time:[1428569820 TO 1428573420]", opts)
+ @knife.run
+ end
+ end
+
it "should not colorize output unless it's writing to a tty" do
@knife.run
expect(@stdout.string.match(/foobar/)).not_to be_nil
diff --git a/spec/unit/knife/user_create_spec.rb b/spec/unit/knife/user_create_spec.rb
index ad8821cd0e..fa5c8324b4 100644
--- a/spec/unit/knife/user_create_spec.rb
+++ b/spec/unit/knife/user_create_spec.rb
@@ -1,6 +1,7 @@
#
-# Author:: Steven Danna (<steve@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Author:: Steven Danna (<steve@chef.io>)
+# Author:: Tyler Cloke (<tyler@chef.io>)
+# Copyright:: Copyright (c) 2012, 2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,68 +22,193 @@ require 'spec_helper'
Chef::Knife::UserCreate.load_deps
describe Chef::Knife::UserCreate do
+ let(:knife) { Chef::Knife::UserCreate.new }
+
+ let(:stderr) {
+ StringIO.new
+ }
+
+ let(:stdout) {
+ StringIO.new
+ }
+
before(:each) do
- @knife = Chef::Knife::UserCreate.new
-
- @stdout = StringIO.new
- @stderr = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
-
- @knife.name_args = [ 'a_user' ]
- @knife.config[:user_password] = "foobar"
- @user = Chef::User.new
- @user.name "a_user"
- @user_with_private_key = Chef::User.new
- @user_with_private_key.name "a_user"
- @user_with_private_key.private_key 'private_key'
- allow(@user).to receive(:create).and_return(@user_with_private_key)
- allow(Chef::User).to receive(:new).and_return(@user)
- allow(Chef::User).to receive(:from_hash).and_return(@user)
- allow(@knife).to receive(:edit_data).and_return(@user.to_hash)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife.ui).to receive(:stderr).and_return(stderr)
+ allow(knife.ui).to receive(:warn)
end
- it "creates a new user" do
- expect(Chef::User).to receive(:new).and_return(@user)
- expect(@user).to receive(:create)
- @knife.run
- expect(@stderr.string).to match /created user.+a_user/i
- end
+ # delete this once OSC11 support is gone
+ context "when only one name_arg is passed" do
+ before do
+ knife.name_args = ['some_user']
+ allow(knife).to receive(:run_osc_11_user_create).and_raise(SystemExit)
+ end
+
+ it "displays the osc warning" do
+ expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
+
+ it "calls knife osc_user create" do
+ expect(knife).to receive(:run_osc_11_user_create)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
- it "sets the password" do
- @knife.config[:user_password] = "a_password"
- expect(@user).to receive(:password).with("a_password")
- @knife.run
end
- it "exits with an error if password is blank" do
- @knife.config[:user_password] = ''
- expect { @knife.run }.to raise_error SystemExit
- expect(@stderr.string).to match /You must specify a non-blank password/
+ context "when USERNAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { [] }
+ let(:fieldname) { 'username' }
+ end
end
- it "sets the user name" do
- expect(@user).to receive(:name).with("a_user")
- @knife.run
+ # uncomment once OSC11 support is gone,
+ # pending doesn't work for shared_examples_for by default
+ #
+ # context "when DISPLAY_NAME isn't specified" do
+ # # from spec/support/shared/unit/knife_shared.rb
+ # it_should_behave_like "mandatory field missing" do
+ # let(:name_args) { ['some_user'] }
+ # let(:fieldname) { 'display name' }
+ # end
+ # end
+
+ context "when FIRST_NAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name'] }
+ let(:fieldname) { 'first name' }
+ end
end
- it "sets the public key if given" do
- @knife.config[:user_key] = "/a/filename"
- allow(File).to receive(:read).with(File.expand_path("/a/filename")).and_return("a_key")
- expect(@user).to receive(:public_key).with("a_key")
- @knife.run
+ context "when LAST_NAME isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name', 'some_first_name'] }
+ let(:fieldname) { 'last name' }
+ end
end
- it "allows you to edit the data" do
- expect(@knife).to receive(:edit_data).with(@user)
- @knife.run
+ context "when EMAIL isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name'] }
+ let(:fieldname) { 'email' }
+ end
end
- it "writes the private key to a file when --file is specified" do
- @knife.config[:file] = "/tmp/a_file"
- filehandle = double("filehandle")
- expect(filehandle).to receive(:print).with('private_key')
- expect(File).to receive(:open).with("/tmp/a_file", "w").and_yield(filehandle)
- @knife.run
+ context "when PASSWORD isn't specified" do
+ # from spec/support/shared/unit/knife_shared.rb
+ it_should_behave_like "mandatory field missing" do
+ let(:name_args) { ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email'] }
+ let(:fieldname) { 'password' }
+ end
end
+
+ context "when all mandatory fields are validly specified" do
+ before do
+ knife.name_args = ['some_user', 'some_display_name', 'some_first_name', 'some_last_name', 'some_email', 'some_password']
+ allow(knife).to receive(:edit_data).and_return(knife.user.to_hash)
+ allow(knife).to receive(:create_user_from_hash).and_return(knife.user)
+ end
+
+ before(:each) do
+ # reset the user field every run
+ knife.user_field = nil
+ end
+
+ it "sets all the mandatory fields" do
+ knife.run
+ expect(knife.user.username).to eq('some_user')
+ expect(knife.user.display_name).to eq('some_display_name')
+ expect(knife.user.first_name).to eq('some_first_name')
+ expect(knife.user.last_name).to eq('some_last_name')
+ expect(knife.user.email).to eq('some_email')
+ expect(knife.user.password).to eq('some_password')
+ end
+
+ context "when user_key and prevent_keygen are passed" do
+ before do
+ knife.config[:user_key] = "some_key"
+ knife.config[:prevent_keygen] = true
+ end
+ it "prints the usage" do
+ expect(knife).to receive(:show_usage)
+ expect { knife.run }.to raise_error(SystemExit)
+ end
+
+ it "prints a relevant error message" do
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match /You cannot pass --user-key and --prevent-keygen/
+ end
+ end
+
+ context "when --prevent-keygen is passed" do
+ before do
+ knife.config[:prevent_keygen] = true
+ end
+
+ it "does not set user.create_key" do
+ knife.run
+ expect(knife.user.create_key).to be_falsey
+ end
+ end
+
+ context "when --prevent-keygen is not passed" do
+ it "sets user.create_key to true" do
+ knife.run
+ expect(knife.user.create_key).to be_truthy
+ end
+ end
+
+ context "when --user-key is passed" do
+ before do
+ knife.config[:user_key] = 'some_key'
+ allow(File).to receive(:read).and_return('some_key')
+ allow(File).to receive(:expand_path)
+ end
+
+ it "sets user.public_key" do
+ knife.run
+ expect(knife.user.public_key).to eq('some_key')
+ end
+ end
+
+ context "when --user-key is not passed" do
+ it "does not set user.public_key" do
+ knife.run
+ expect(knife.user.public_key).to be_nil
+ end
+ end
+
+ context "when a private_key is returned" do
+ before do
+ allow(knife).to receive(:create_user_from_hash).and_return(Chef::UserV1.from_hash(knife.user.to_hash.merge({"private_key" => "some_private_key"})))
+ end
+
+ context "when --file is passed" do
+ before do
+ knife.config[:file] = '/some/path'
+ end
+
+ it "creates a new file of the path passed" do
+ filehandle = double('filehandle')
+ expect(filehandle).to receive(:print).with('some_private_key')
+ expect(File).to receive(:open).with('/some/path', 'w').and_yield(filehandle)
+ knife.run
+ end
+ end
+
+ context "when --file is not passed" do
+ it "prints the private key to stdout" do
+ expect(knife.ui).to receive(:msg).with('some_private_key')
+ knife.run
+ end
+ end
+ end
+
+ end # when all mandatory fields are validly specified
end
diff --git a/spec/unit/knife/user_delete_spec.rb b/spec/unit/knife/user_delete_spec.rb
index 94cfbf3db1..a24160624a 100644
--- a/spec/unit/knife/user_delete_spec.rb
+++ b/spec/unit/knife/user_delete_spec.rb
@@ -19,21 +19,47 @@
require 'spec_helper'
describe Chef::Knife::UserDelete do
+ let(:knife) { Chef::Knife::UserDelete.new }
+ let(:user) { double('user_object') }
+ let(:stdout) { StringIO.new }
+
before(:each) do
Chef::Knife::UserDelete.load_deps
- @knife = Chef::Knife::UserDelete.new
- @knife.name_args = [ 'my_user' ]
+ knife.name_args = [ 'my_user' ]
+ allow(Chef::UserV1).to receive(:load).and_return(user)
+ allow(user).to receive(:username).and_return('my_user')
+ allow(knife.ui).to receive(:stderr).and_return(stdout)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ end
+
+ # delete this once OSC11 support is gone
+ context "when the username field is not supported by the server" do
+ before do
+ allow(knife).to receive(:run_osc_11_user_delete).and_raise(SystemExit)
+ allow(user).to receive(:username).and_return(nil)
+ end
+
+ it "displays the osc warning" do
+ expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
+
+ it "forwards the command to knife osc_user edit" do
+ expect(knife).to receive(:run_osc_11_user_delete)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
end
it 'deletes the user' do
- expect(@knife).to receive(:delete_object).with(Chef::User, 'my_user')
- @knife.run
+ #expect(knife).to receive(:delete_object).with(Chef::UserV1, 'my_user')
+ expect(knife).to receive(:delete_object).with('my_user')
+ knife.run
end
it 'prints usage and exits when a user name is not provided' do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
end
end
diff --git a/spec/unit/knife/user_edit_spec.rb b/spec/unit/knife/user_edit_spec.rb
index 0eb75cfa9b..a21d982d29 100644
--- a/spec/unit/knife/user_edit_spec.rb
+++ b/spec/unit/knife/user_edit_spec.rb
@@ -19,29 +19,48 @@
require 'spec_helper'
describe Chef::Knife::UserEdit do
+ let(:knife) { Chef::Knife::UserEdit.new }
+
before(:each) do
@stderr = StringIO.new
@stdout = StringIO.new
Chef::Knife::UserEdit.load_deps
- @knife = Chef::Knife::UserEdit.new
- allow(@knife.ui).to receive(:stderr).and_return(@stderr)
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
- @knife.name_args = [ 'my_user' ]
- @knife.config[:disable_editing] = true
+ allow(knife.ui).to receive(:stderr).and_return(@stderr)
+ allow(knife.ui).to receive(:stdout).and_return(@stdout)
+ knife.name_args = [ 'my_user' ]
+ knife.config[:disable_editing] = true
+ end
+
+ # delete this once OSC11 support is gone
+ context "when the username field is not supported by the server" do
+ before do
+ allow(knife).to receive(:run_osc_11_user_edit).and_raise(SystemExit)
+ allow(Chef::UserV1).to receive(:load).and_return({"username" => nil})
+ end
+
+ it "displays the osc warning" do
+ expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
+
+ it "forwards the command to knife osc_user edit" do
+ expect(knife).to receive(:run_osc_11_user_edit)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
end
it 'loads and edits the user' do
- data = { :name => "my_user" }
- allow(Chef::User).to receive(:load).with("my_user").and_return(data)
- expect(@knife).to receive(:edit_data).with(data).and_return(data)
- @knife.run
+ data = { "username" => "my_user" }
+ allow(Chef::UserV1).to receive(:load).with("my_user").and_return(data)
+ expect(knife).to receive(:edit_data).with(data).and_return(data)
+ knife.run
end
it 'prints usage and exits when a user name is not provided' do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
end
end
diff --git a/spec/unit/knife/user_list_spec.rb b/spec/unit/knife/user_list_spec.rb
index db097a5c16..fa2bac426e 100644
--- a/spec/unit/knife/user_list_spec.rb
+++ b/spec/unit/knife/user_list_spec.rb
@@ -19,14 +19,18 @@
require 'spec_helper'
describe Chef::Knife::UserList do
+ let(:knife) { Chef::Knife::UserList.new }
+ let(:stdout) { StringIO.new }
+
before(:each) do
Chef::Knife::UserList.load_deps
- @knife = Chef::Knife::UserList.new
+ allow(knife.ui).to receive(:stderr).and_return(stdout)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
end
it 'lists the users' do
- expect(Chef::User).to receive(:list)
- expect(@knife).to receive(:format_list_for_display)
- @knife.run
+ expect(Chef::UserV1).to receive(:list)
+ expect(knife).to receive(:format_list_for_display)
+ knife.run
end
end
diff --git a/spec/unit/knife/user_reregister_spec.rb b/spec/unit/knife/user_reregister_spec.rb
index 1268716f40..89aa6726cd 100644
--- a/spec/unit/knife/user_reregister_spec.rb
+++ b/spec/unit/knife/user_reregister_spec.rb
@@ -19,35 +19,56 @@
require 'spec_helper'
describe Chef::Knife::UserReregister do
- before(:each) do
+ let(:knife) { Chef::Knife::UserReregister.new }
+ let(:user_mock) { double('user_mock', :private_key => "private_key") }
+ let(:stdout) { StringIO.new }
+
+ before do
Chef::Knife::UserReregister.load_deps
- @knife = Chef::Knife::UserReregister.new
- @knife.name_args = [ 'a_user' ]
- @user_mock = double('user_mock', :private_key => "private_key")
- allow(Chef::User).to receive(:load).and_return(@user_mock)
- @stdout = StringIO.new
- allow(@knife.ui).to receive(:stdout).and_return(@stdout)
+ knife.name_args = [ 'a_user' ]
+ allow(Chef::UserV1).to receive(:load).and_return(user_mock)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ allow(knife.ui).to receive(:stderr).and_return(stdout)
+ allow(user_mock).to receive(:username).and_return('a_user')
+ end
+
+ # delete this once OSC11 support is gone
+ context "when the username field is not supported by the server" do
+ before do
+ allow(knife).to receive(:run_osc_11_user_reregister).and_raise(SystemExit)
+ allow(user_mock).to receive(:username).and_return(nil)
+ end
+
+ it "displays the osc warning" do
+ expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
+
+ it "forwards the command to knife osc_user edit" do
+ expect(knife).to receive(:run_osc_11_user_reregister)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
end
it 'prints usage and exits when a user name is not provided' do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
end
it 'reregisters the user and prints the key' do
- expect(@user_mock).to receive(:reregister).and_return(@user_mock)
- @knife.run
- expect(@stdout.string).to match( /private_key/ )
+ expect(user_mock).to receive(:reregister).and_return(user_mock)
+ knife.run
+ expect(stdout.string).to match( /private_key/ )
end
it 'writes the private key to a file when --file is specified' do
- expect(@user_mock).to receive(:reregister).and_return(@user_mock)
- @knife.config[:file] = '/tmp/a_file'
+ expect(user_mock).to receive(:reregister).and_return(user_mock)
+ knife.config[:file] = '/tmp/a_file'
filehandle = StringIO.new
expect(File).to receive(:open).with('/tmp/a_file', 'w').and_yield(filehandle)
- @knife.run
+ knife.run
expect(filehandle.string).to eq("private_key")
end
end
diff --git a/spec/unit/knife/user_show_spec.rb b/spec/unit/knife/user_show_spec.rb
index f97cbc3f13..7c39e428c0 100644
--- a/spec/unit/knife/user_show_spec.rb
+++ b/spec/unit/knife/user_show_spec.rb
@@ -19,23 +19,47 @@
require 'spec_helper'
describe Chef::Knife::UserShow do
- before(:each) do
+ let(:knife) { Chef::Knife::UserShow.new }
+ let(:user_mock) { double('user_mock') }
+ let(:stdout) { StringIO.new }
+
+ before do
Chef::Knife::UserShow.load_deps
- @knife = Chef::Knife::UserShow.new
- @knife.name_args = [ 'my_user' ]
- @user_mock = double('user_mock')
+ knife.name_args = [ 'my_user' ]
+ allow(user_mock).to receive(:username).and_return('my_user')
+ allow(knife.ui).to receive(:stderr).and_return(stdout)
+ allow(knife.ui).to receive(:stdout).and_return(stdout)
+ end
+
+ # delete this once OSC11 support is gone
+ context "when the username field is not supported by the server" do
+ before do
+ allow(knife).to receive(:run_osc_11_user_show).and_raise(SystemExit)
+ allow(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock)
+ allow(user_mock).to receive(:username).and_return(nil)
+ end
+
+ it "displays the osc warning" do
+ expect(knife.ui).to receive(:warn).with(knife.osc_11_warning)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
+
+ it "forwards the command to knife osc_user edit" do
+ expect(knife).to receive(:run_osc_11_user_show)
+ expect{ knife.run }.to raise_error(SystemExit)
+ end
end
it 'loads and displays the user' do
- expect(Chef::User).to receive(:load).with('my_user').and_return(@user_mock)
- expect(@knife).to receive(:format_for_display).with(@user_mock)
- @knife.run
+ expect(Chef::UserV1).to receive(:load).with('my_user').and_return(user_mock)
+ expect(knife).to receive(:format_for_display).with(user_mock)
+ knife.run
end
it 'prints usage and exits when a user name is not provided' do
- @knife.name_args = []
- expect(@knife).to receive(:show_usage)
- expect(@knife.ui).to receive(:fatal)
- expect { @knife.run }.to raise_error(SystemExit)
+ knife.name_args = []
+ expect(knife).to receive(:show_usage)
+ expect(knife.ui).to receive(:fatal)
+ expect { knife.run }.to raise_error(SystemExit)
end
end
diff --git a/spec/unit/knife_spec.rb b/spec/unit/knife_spec.rb
index 2ccf8493ad..022256f370 100644
--- a/spec/unit/knife_spec.rb
+++ b/spec/unit/knife_spec.rb
@@ -30,11 +30,20 @@ describe Chef::Knife do
let(:knife) { Chef::Knife.new }
+ let(:config_location) { File.expand_path("~/.chef/config.rb") }
+
+ let(:config_loader) do
+ instance_double("WorkstationConfigLoader", load: nil, no_config_found?: false, config_location: config_location)
+ end
+
before(:each) do
Chef::Log.logger = Logger.new(StringIO.new)
Chef::Config[:node_name] = "webmonkey.example.com"
+ allow(Chef::WorkstationConfigLoader).to receive(:new).and_return(config_loader)
+ allow(config_loader).to receive(:explicit_config_file=)
+
# Prevent gratuitous code reloading:
allow(Chef::Knife).to receive(:load_commands)
allow(knife.ui).to receive(:puts)
@@ -130,7 +139,8 @@ describe Chef::Knife do
"Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
'X-Chef-Version' => Chef::VERSION,
"Host"=>"api.opscode.piab",
- "X-REMOTE-REQUEST-ID"=>request_id}}
+ "X-REMOTE-REQUEST-ID"=>request_id,
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
let(:request_id) {"1234"}
@@ -251,6 +261,18 @@ describe Chef::Knife do
:default => "default-value")
end
+ it "sets the default log_location to STDERR for Chef::Log warnings" do
+ knife_command = KnifeSpecs::TestYourself.new([])
+ knife_command.configure_chef
+ expect(Chef::Config[:log_location]).to eq(STDERR)
+ end
+
+ it "sets the default log_level to warn so we can issue Chef::Log.warn" do
+ knife_command = KnifeSpecs::TestYourself.new([])
+ knife_command.configure_chef
+ expect(Chef::Config[:log_level]).to eql(:warn)
+ end
+
it "prefers the default value if no config or command line value is present" do
knife_command = KnifeSpecs::TestYourself.new([]) #empty argv
knife_command.configure_chef
@@ -271,6 +293,11 @@ describe Chef::Knife do
expect(knife_command.config[:opt_with_default]).to eq("from-cli")
end
+ it "merges `listen` config to Chef::Config" do
+ Chef::Knife.run(%w[test yourself --no-listen], Chef::Application::Knife.options)
+ expect(Chef::Config[:listen]).to be(false)
+ end
+
context "verbosity is greater than zero" do
let(:fake_config) { "/does/not/exist/knife.rb" }
@@ -369,6 +396,22 @@ describe Chef::Knife do
expect(stderr.string).to match(%r[Response: nothing to see here])
end
+ it "formats 406s (non-supported API version error) nicely" do
+ response = Net::HTTPNotAcceptable.new("1.1", "406", "Not Acceptable")
+ response.instance_variable_set(:@read, true) # I hate you, net/http.
+
+ # set the header
+ response["x-ops-server-api-version"] = Chef::JSONCompat.to_json(:min_version => "0", :max_version => "1", :request_version => "10000000")
+
+ allow(response).to receive(:body).and_return(Chef::JSONCompat.to_json(:error => "sad trombone"))
+ allow(knife).to receive(:run).and_raise(Net::HTTPServerException.new("406 Not Acceptable", response))
+
+ knife.run_with_pretty_exceptions
+ expect(stderr.string).to include('The request that Knife sent was using API version 10000000')
+ expect(stderr.string).to include('The Chef server you sent the request to supports a min API verson of 0 and a max API version of 1')
+ expect(stderr.string).to include('Please either update your Chef client or server to be a compatible set')
+ end
+
it "formats 500s nicely" do
response = Net::HTTPInternalServerError.new("1.1", "500", "Internal Server Error")
response.instance_variable_set(:@read, true) # I hate you, net/http.
diff --git a/spec/unit/log/syslog_spec.rb b/spec/unit/log/syslog_spec.rb
new file mode 100644
index 0000000000..3db90e50c6
--- /dev/null
+++ b/spec/unit/log/syslog_spec.rb
@@ -0,0 +1,53 @@
+#
+# Author:: SAWANOBORI Yukihiko (<sawanoboriyu@higanworks.com>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef'
+
+describe "Chef::Log::Syslog", :unix_only => true do
+ let(:syslog) { Chef::Log::Syslog.new }
+ let(:app) { Chef::Application.new }
+
+ before do
+ Chef::Log.init(MonoLogger.new(syslog))
+ @old_log_level = Chef::Log.level
+ Chef::Log.level = :info
+ @old_loggers = Chef::Log.loggers
+ Chef::Log.use_log_devices([syslog])
+ end
+
+ after do
+ Chef::Log.level = @old_log_level
+ Chef::Log.use_log_devices(@old_loggers)
+ end
+
+ it "should send message with severity info to syslog." do
+ expect(syslog).to receive(:info).with("*** Chef 12.4.0.dev.0 ***")
+ Chef::Log.info("*** Chef 12.4.0.dev.0 ***")
+ end
+
+ it "should send message with severity warning to syslog." do
+ expect(syslog).to receive(:warn).with("No config file found or specified on command line, using command line options.")
+ Chef::Log.warn("No config file found or specified on command line, using command line options.")
+ end
+
+ it "should fallback into send message with severity info to syslog when wrong format." do
+ expect(syslog).to receive(:info).with("chef message")
+ syslog.write("chef message")
+ end
+end
diff --git a/spec/unit/log/winevt_spec.rb b/spec/unit/log/winevt_spec.rb
new file mode 100644
index 0000000000..867ef55900
--- /dev/null
+++ b/spec/unit/log/winevt_spec.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Jay Mundrawala (jdm@chef.io)
+# Author:: SAWANOBORI Yukihiko (<sawanoboriyu@higanworks.com>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Log::WinEvt do
+ let(:evtlog) { instance_double("Win32::EventLog")}
+ let(:winevt) { Chef::Log::WinEvt.new(evtlog) }
+ let(:app) { Chef::Application.new }
+
+ before do
+
+ Chef::Log.init(MonoLogger.new(winevt))
+ @old_log_level = Chef::Log.level
+ Chef::Log.level = :info
+ @old_loggers = Chef::Log.loggers
+ Chef::Log.use_log_devices([winevt])
+ end
+
+ after do
+ Chef::Log.level = @old_log_level
+ Chef::Log.use_log_devices(@old_loggers)
+ end
+
+ it "should send message with severity info to Windows Event Log." do
+ expect(winevt).to receive(:info).with("*** Chef 12.4.0.dev.0 ***")
+ Chef::Log.info("*** Chef 12.4.0.dev.0 ***")
+ end
+
+ it "should send message with severity warning to Windows Event Log." do
+ expect(winevt).to receive(:warn).with("No config file found or specified on command line, using command line options.")
+ Chef::Log.warn("No config file found or specified on command line, using command line options.")
+ end
+
+ it "should fallback into send message with severity info to Windows Event Log when wrong format." do
+ expect(winevt).to receive(:info).with("chef message")
+ winevt.write("chef message")
+ end
+end
diff --git a/spec/unit/lwrp_spec.rb b/spec/unit/lwrp_spec.rb
index ec39174da6..bcb64cb21e 100644
--- a/spec/unit/lwrp_spec.rb
+++ b/spec/unit/lwrp_spec.rb
@@ -17,20 +17,40 @@
#
require 'spec_helper'
+require 'tmpdir'
+require 'fileutils'
+require 'chef/mixin/convert_to_class_name'
module LwrpConstScopingConflict
end
describe "LWRP" do
+ include Chef::Mixin::ConvertToClassName
+
before do
@original_VERBOSE = $VERBOSE
$VERBOSE = nil
+ Chef::Resource::LWRPBase.class_eval { @loaded_lwrps = {} }
end
after do
$VERBOSE = @original_VERBOSE
end
+ def get_lwrp(name)
+ Chef::ResourceResolver.resolve(name)
+ end
+
+ def get_lwrp_provider(name)
+ old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ begin
+ Chef::Provider.const_get(convert_to_class_name(name.to_s))
+ ensure
+ Chef::Config[:treat_deprecation_warnings_as_errors] = old_treat_deprecation_warnings_as_errors
+ end
+ end
+
describe "when overriding an existing class" do
before :each do
allow($stderr).to receive(:write)
@@ -43,7 +63,6 @@ describe "LWRP" do
expect(Chef::Log).not_to receive(:debug).with(/anymore/)
Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
Object.send(:remove_const, 'LwrpFoo')
- Chef::Resource.send(:remove_const, 'LwrpFoo')
end
it "should not skip loading a provider when there's a top level symbol of the same name" do
@@ -53,7 +72,6 @@ describe "LWRP" do
expect(Chef::Log).not_to receive(:debug).with(/anymore/)
Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil)
Object.send(:remove_const, 'LwrpBuckPasser')
- Chef::Provider.send(:remove_const, 'LwrpBuckPasser')
end
# @todo: we need a before block to manually remove_const all of the LWRPs that we
@@ -67,7 +85,6 @@ describe "LWRP" do
Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
expect(Chef::Log).to receive(:info).with(/Skipping/)
- expect(Chef::Log).to receive(:debug).with(/anymore/)
Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
end
end
@@ -79,7 +96,6 @@ describe "LWRP" do
Dir[File.expand_path( "lwrp/providers/*", CHEF_SPEC_DATA)].each do |file|
expect(Chef::Log).to receive(:info).with(/Skipping/)
- expect(Chef::Log).to receive(:debug).with(/anymore/)
Chef::Provider::LWRPBase.build_from_file("lwrp", file, nil)
end
end
@@ -90,7 +106,7 @@ describe "LWRP" do
Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
end
- first_lwr_foo_class = Chef::Resource::LwrpFoo
+ first_lwr_foo_class = get_lwrp(:lwrp_foo)
expect(Chef::Resource.resource_classes).to include(first_lwr_foo_class)
Dir[File.expand_path( "lwrp/resources/*", CHEF_SPEC_DATA)].each do |file|
Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
@@ -106,40 +122,91 @@ describe "LWRP" do
end
- describe "Lightweight Chef::Resource" do
+ context "When an LWRP resource in cookbook l-w-r-p is loaded" do
+ before do
+ @tmpdir = Dir.mktmpdir("lwrp_test")
+ resource_path = File.join(@tmpdir, "foo.rb")
+ IO.write(resource_path, "default_action :create")
+ provider_path = File.join(@tmpdir, "foo.rb")
+ IO.write(provider_path, <<-EOM)
+ action :create do
+ raise "hi"
+ end
+ EOM
+ end
+
+ it "Can find the resource at l_w_r_p_foo" do
+ end
+ end
+ context "When an LWRP resource lwrp_foo is loaded" do
before do
- Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file|
- Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
+ @tmpdir = Dir.mktmpdir("lwrp_test")
+ @lwrp_path = File.join(@tmpdir, "foo.rb")
+ content = IO.read(File.expand_path("../../data/lwrp/resources/foo.rb", __FILE__))
+ IO.write(@lwrp_path, content)
+ Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil)
+ @original_resource = Chef::ResourceResolver.resolve(:lwrp_foo)
+ end
+
+ after do
+ FileUtils.remove_entry @tmpdir
+ end
+
+ context "And the LWRP is asked to load again, this time with different code" do
+ before do
+ content = IO.read(File.expand_path("../../data/lwrp_override/resources/foo.rb", __FILE__))
+ IO.write(@lwrp_path, content)
+ Chef::Resource::LWRPBase.build_from_file("lwrp", @lwrp_path, nil)
+ end
+
+ it "Should load the old content, and not the new" do
+ resource = Chef::ResourceResolver.resolve(:lwrp_foo)
+ expect(resource).to eq @original_resource
+ expect(resource.default_action).to eq([:pass_buck])
+ expect(Chef.method_defined?(:method_created_by_override_lwrp_foo)).to be_falsey
end
+ end
+ end
+
+ describe "Lightweight Chef::Resource" do
- Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file|
+ before do
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file|
Chef::Resource::LWRPBase.build_from_file("lwrp", file, nil)
end
end
- it "should load the resource into a properly-named class" do
- expect(Chef::Resource.const_get("LwrpFoo")).to be_kind_of(Class)
+ it "should be resolvable with Chef::ResourceResolver.resolve(:lwrp_foo)" do
+ expect(Chef::ResourceResolver.resolve(:lwrp_foo, node: Chef::Node.new)).to eq(get_lwrp(:lwrp_foo))
end
it "should set resource_name" do
- expect(Chef::Resource::LwrpFoo.new("blah").resource_name).to eql(:lwrp_foo)
+ expect(get_lwrp(:lwrp_foo).new("blah").resource_name).to eql(:lwrp_foo)
+ end
+
+ it "should output the resource_name in .to_s" do
+ expect(get_lwrp(:lwrp_foo).new("blah").to_s).to eq "lwrp_foo[blah]"
+ end
+
+ it "should have a class that outputs a reasonable string" do
+ expect(get_lwrp(:lwrp_foo).to_s).to eq "LWRP resource lwrp_foo from cookbook lwrp"
end
it "should add the specified actions to the allowed_actions array" do
- expect(Chef::Resource::LwrpFoo.new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs)
+ expect(get_lwrp(:lwrp_foo).new("blah").allowed_actions).to include(:pass_buck, :twiddle_thumbs)
end
it "should set the specified action as the default action" do
- expect(Chef::Resource::LwrpFoo.new("blah").action).to eq(:pass_buck)
+ expect(get_lwrp(:lwrp_foo).new("blah").action).to eq([:pass_buck])
end
it "should create a method for each attribute" do
- expect(Chef::Resource::LwrpFoo.new("blah").methods.map{ |m| m.to_sym}).to include(:monkey)
+ expect(get_lwrp(:lwrp_foo).new("blah").methods.map{ |m| m.to_sym}).to include(:monkey)
end
it "should build attribute methods that respect validation rules" do
- expect { Chef::Resource::LwrpFoo.new("blah").monkey(42) }.to raise_error(ArgumentError)
+ expect { get_lwrp(:lwrp_foo).new("blah").monkey(42) }.to raise_error(ArgumentError)
end
it "should have access to the run context and node during class definition" do
@@ -151,7 +218,7 @@ describe "LWRP" do
Chef::Resource::LWRPBase.build_from_file("lwrp", file, run_context)
end
- cls = Chef::Resource.const_get("LwrpNodeattr")
+ cls = get_lwrp(:lwrp_nodeattr)
expect(cls.node).to be_kind_of(Chef::Node)
expect(cls.run_context).to be_kind_of(Chef::RunContext)
expect(cls.node[:penguin_name]).to eql("jackass")
@@ -175,14 +242,6 @@ describe "LWRP" do
expect(klass.resource_name).to eq(:foo)
end
- context "when creating a new instance" do
- it "raises an exception if resource_name is nil" do
- expect {
- klass.new('blah')
- }.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
- end
- end
-
context "lazy default values" do
let(:klass) do
Class.new(Chef::Resource::LWRPBase) do
@@ -225,17 +284,17 @@ describe "LWRP" do
end
end
- context "when the child does not defined the methods" do
+ context "when the child does not define the methods" do
let(:child) do
Class.new(parent)
end
it "delegates #actions to the parent" do
- expect(child.actions).to eq([:eat, :sleep])
+ expect(child.actions).to eq([:nothing, :eat, :sleep])
end
it "delegates #default_action to the parent" do
- expect(child.default_action).to eq(:eat)
+ expect(child.default_action).to eq([:eat])
end
end
@@ -248,11 +307,11 @@ describe "LWRP" do
end
it "does not delegate #actions to the parent" do
- expect(child.actions).to eq([:dont_eat, :dont_sleep])
+ expect(child.actions).to eq([:nothing, :dont_eat, :dont_sleep])
end
it "does not delegate #default_action to the parent" do
- expect(child.default_action).to eq(:dont_eat)
+ expect(child.default_action).to eq([:dont_eat])
end
end
@@ -273,110 +332,193 @@ describe "LWRP" do
it "amends actions when they are already defined" do
raise_if_deprecated!
- expect(child.actions).to eq([:eat, :sleep, :drink])
+ expect(child.actions).to eq([:nothing, :eat, :sleep, :drink])
+ end
+ end
+ end
+
+ describe "when actions is set to an array" do
+ let(:resource_class) do
+ Class.new(Chef::Resource::LWRPBase) do
+ actions [ :eat, :sleep ]
end
end
+ let(:resource) do
+ resource_class.new('blah')
+ end
+ it "actions includes those actions" do
+ expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "allowed_actions includes those actions" do
+ expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "resource.allowed_actions includes those actions" do
+ expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
end
+ describe "when allowed_actions is set to an array" do
+ let(:resource_class) do
+ Class.new(Chef::Resource::LWRPBase) do
+ allowed_actions [ :eat, :sleep ]
+ end
+ end
+ let(:resource) do
+ resource_class.new('blah')
+ end
+ it "actions includes those actions" do
+ expect(resource_class.actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "allowed_actions includes those actions" do
+ expect(resource_class.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ it "resource.allowed_actions includes those actions" do
+ expect(resource.allowed_actions).to eq [ :nothing, :eat, :sleep ]
+ end
+ end
end
describe "Lightweight Chef::Provider" do
- before do
- @node = Chef::Node.new
- @node.automatic[:platform] = :ubuntu
- @node.automatic[:platform_version] = '8.10'
- @events = Chef::EventDispatch::Dispatcher.new
- @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new({}), @events)
- @runner = Chef::Runner.new(@run_context)
- end
- before(:each) do
- Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file|
- Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context)
+ let(:node) do
+ Chef::Node.new.tap do |n|
+ n.automatic[:platform] = :ubuntu
+ n.automatic[:platform_version] = '8.10'
end
+ end
- Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "resources", "*"))].each do |file|
- Chef::Resource::LWRPBase.build_from_file("lwrp", file, @run_context)
- end
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
- Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "providers", "*"))].each do |file|
- Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context)
- end
+ let(:run_context) { Chef::RunContext.new(node, Chef::CookbookCollection.new({}), events) }
+
+ let(:runner) { Chef::Runner.new(run_context) }
+
+ let(:lwrp_cookbok_name) { "lwrp" }
+
+ before do
+ Chef::Provider::LWRPBase.class_eval { @loaded_lwrps = {} }
+ end
- Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp_override", "providers", "*"))].each do |file|
- Chef::Provider::LWRPBase.build_from_file("lwrp", file, @run_context)
+ before(:each) do
+ Dir[File.expand_path(File.expand_path("../../data/lwrp/resources/*", __FILE__))].each do |file|
+ Chef::Resource::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context)
end
+ Dir[File.expand_path(File.expand_path("../../data/lwrp/providers/*", __FILE__))].each do |file|
+ Chef::Provider::LWRPBase.build_from_file(lwrp_cookbok_name, file, run_context)
+ end
end
it "should properly handle a new_resource reference" do
- resource = Chef::Resource::LwrpFoo.new("morpheus")
+ resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
resource.monkey("bob")
- resource.provider(:lwrp_monkey_name_printer)
- resource.run_context = @run_context
+ resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer))
provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
provider.action_twiddle_thumbs
end
- it "should load the provider into a properly-named class" do
- expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class)
- end
+ context "provider class created" do
+ before do
+ @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
- it "should create a method for each attribute" do
- new_resource = double("new resource").as_null_object
- expect(Chef::Provider::LwrpBuckPasser.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_pass_buck)
- expect(Chef::Provider::LwrpThumbTwiddler.new(nil, new_resource).methods.map{|m|m.to_sym}).to include(:action_twiddle_thumbs)
+ after do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors
+ end
+
+ it "should load the provider into a properly-named class" do
+ expect(Chef::Provider.const_get("LwrpBuckPasser")).to be_kind_of(Class)
+ expect(Chef::Provider::LwrpBuckPasser <= Chef::Provider::LWRPBase).to be_truthy
+ end
+
+ it "should create a method for each action" do
+ expect(get_lwrp_provider(:lwrp_buck_passer).instance_methods).to include(:action_pass_buck)
+ expect(get_lwrp_provider(:lwrp_thumb_twiddler).instance_methods).to include(:action_twiddle_thumbs)
+ end
+
+ it "sets itself as a provider for a resource of the same name" do
+ found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :lwrp_buck_passer)
+ # we bypass the per-file loading to get the file to load each time,
+ # which creates the LWRP class repeatedly. New things get prepended to
+ # the list of providers.
+ expect(found_providers.first).to eq(get_lwrp_provider(:lwrp_buck_passer))
+ end
+
+ context "with a cookbook with an underscore in the name" do
+
+ let(:lwrp_cookbok_name) { "l_w_r_p" }
+
+ it "sets itself as a provider for a resource of the same name" do
+ found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer)
+ expect(found_providers.size).to eq(1)
+ expect(found_providers.last).to eq(get_lwrp_provider(:l_w_r_p_buck_passer))
+ end
+ end
+
+ context "with a cookbook with a hypen in the name" do
+
+ let(:lwrp_cookbok_name) { "l-w-r-p" }
+
+ it "sets itself as a provider for a resource of the same name" do
+ incorrect_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :'l-w-r-p_buck_passer')
+ expect(incorrect_providers).to eq([])
+
+ found_providers = Chef::Platform::ProviderHandlerMap.instance.list(node, :l_w_r_p_buck_passer)
+ expect(found_providers.first).to eq(get_lwrp_provider(:l_w_r_p_buck_passer))
+ end
+ end
end
it "should insert resources embedded in the provider into the middle of the resource collection" do
- injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+ injector = get_lwrp(:lwrp_foo).new("morpheus", run_context)
injector.action(:pass_buck)
- injector.provider(:lwrp_buck_passer)
- dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context)
+ injector.provider(get_lwrp_provider(:lwrp_buck_passer))
+ dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context)
dummy.provider(Chef::Provider::Easy)
- @run_context.resource_collection.insert(injector)
- @run_context.resource_collection.insert(dummy)
+ run_context.resource_collection.insert(injector)
+ run_context.resource_collection.insert(dummy)
- Chef::Runner.new(@run_context).converge
+ Chef::Runner.new(run_context).converge
- expect(@run_context.resource_collection[0]).to eql(injector)
- expect(@run_context.resource_collection[1].name).to eql('prepared_thumbs')
- expect(@run_context.resource_collection[2].name).to eql('twiddled_thumbs')
- expect(@run_context.resource_collection[3]).to eql(dummy)
+ expect(run_context.resource_collection[0]).to eql(injector)
+ expect(run_context.resource_collection[1].name).to eql('prepared_thumbs')
+ expect(run_context.resource_collection[2].name).to eql('twiddled_thumbs')
+ expect(run_context.resource_collection[3]).to eql(dummy)
end
it "should insert embedded resources from multiple providers, including from the last position, properly into the resource collection" do
- injector = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+ injector = get_lwrp(:lwrp_foo).new("morpheus", run_context)
injector.action(:pass_buck)
- injector.provider(:lwrp_buck_passer)
+ injector.provider(get_lwrp_provider(:lwrp_buck_passer))
- injector2 = Chef::Resource::LwrpBar.new("tank", @run_context)
+ injector2 = get_lwrp(:lwrp_bar).new("tank", run_context)
injector2.action(:pass_buck)
- injector2.provider(:lwrp_buck_passer_2)
+ injector2.provider(get_lwrp_provider(:lwrp_buck_passer_2))
- dummy = Chef::Resource::ZenMaster.new("keanu reeves", @run_context)
+ dummy = Chef::Resource::ZenMaster.new("keanu reeves", run_context)
dummy.provider(Chef::Provider::Easy)
- @run_context.resource_collection.insert(injector)
- @run_context.resource_collection.insert(dummy)
- @run_context.resource_collection.insert(injector2)
+ run_context.resource_collection.insert(injector)
+ run_context.resource_collection.insert(dummy)
+ run_context.resource_collection.insert(injector2)
- Chef::Runner.new(@run_context).converge
+ Chef::Runner.new(run_context).converge
- expect(@run_context.resource_collection[0]).to eql(injector)
- expect(@run_context.resource_collection[1].name).to eql('prepared_thumbs')
- expect(@run_context.resource_collection[2].name).to eql('twiddled_thumbs')
- expect(@run_context.resource_collection[3]).to eql(dummy)
- expect(@run_context.resource_collection[4]).to eql(injector2)
- expect(@run_context.resource_collection[5].name).to eql('prepared_eyes')
- expect(@run_context.resource_collection[6].name).to eql('dried_paint_watched')
+ expect(run_context.resource_collection[0]).to eql(injector)
+ expect(run_context.resource_collection[1].name).to eql('prepared_thumbs')
+ expect(run_context.resource_collection[2].name).to eql('twiddled_thumbs')
+ expect(run_context.resource_collection[3]).to eql(dummy)
+ expect(run_context.resource_collection[4]).to eql(injector2)
+ expect(run_context.resource_collection[5].name).to eql('prepared_eyes')
+ expect(run_context.resource_collection[6].name).to eql('dried_paint_watched')
end
it "should properly handle a new_resource reference" do
- resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+ resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
resource.monkey("bob")
- resource.provider(:lwrp_monkey_name_printer)
+ resource.provider(get_lwrp_provider(:lwrp_monkey_name_printer))
provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
provider.action_twiddle_thumbs
@@ -385,9 +527,9 @@ describe "LWRP" do
end
it "should properly handle an embedded Resource accessing the enclosing Provider's scope" do
- resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+ resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
resource.monkey("bob")
- resource.provider(:lwrp_embedded_resource_accesses_providers_scope)
+ resource.provider(get_lwrp_provider(:lwrp_embedded_resource_accesses_providers_scope))
provider = Chef::Platform.provider_for_resource(resource, :twiddle_thumbs)
#provider = @runner.build_provider(resource)
@@ -404,15 +546,15 @@ describe "LWRP" do
# Side effect of lwrp_inline_compiler provider for testing notifications.
$interior_ruby_block_2 = nil
# resource type doesn't matter, so make an existing resource type work with provider.
- @resource = Chef::Resource::LwrpFoo.new("morpheus", @run_context)
+ @resource = get_lwrp(:lwrp_foo).new("morpheus", run_context)
@resource.allowed_actions << :test
@resource.action(:test)
- @resource.provider(:lwrp_inline_compiler)
+ @resource.provider(get_lwrp_provider(:lwrp_inline_compiler))
end
it "does not add interior resources to the exterior resource collection" do
@resource.run_action(:test)
- expect(@run_context.resource_collection).to be_empty
+ expect(run_context.resource_collection).to be_empty
end
context "when interior resources are updated" do
@@ -437,7 +579,144 @@ describe "LWRP" do
end
end
-
end
+ context "resource class created" do
+ before(:context) do
+ @tmpdir = Dir.mktmpdir("lwrp_test")
+ resource_path = File.join(@tmpdir, "once.rb")
+ IO.write(resource_path, "default_action :create")
+
+ @old_treat_deprecation_warnings_as_errors = Chef::Config[:treat_deprecation_warnings_as_errors]
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ Chef::Resource::LWRPBase.build_from_file("lwrp", resource_path, nil)
+ end
+
+ after(:context) do
+ FileUtils.remove_entry @tmpdir
+ Chef::Config[:treat_deprecation_warnings_as_errors] = @old_treat_deprecation_warnings_as_errors
+ end
+
+ it "should load the resource into a properly-named class" do
+ expect(Chef::Resource::LwrpOnce).to be_kind_of(Class)
+ expect(Chef::Resource::LwrpOnce <= Chef::Resource::LWRPBase).to be_truthy
+ end
+
+ it "get_lwrp(:lwrp_once).new is a Chef::Resource::LwrpOnce" do
+ lwrp = get_lwrp(:lwrp_once).new('hi')
+ expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ end
+
+ it "Chef::Resource::LwrpOnce.new is a get_lwrp(:lwrp_once)" do
+ lwrp = Chef::Resource::LwrpOnce.new('hi')
+ expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ end
+
+ it "works even if LwrpOnce exists in the top level" do
+ module ::LwrpOnce
+ end
+ expect(Chef::Resource::LwrpOnce).not_to eq(::LwrpOnce)
+ end
+
+ it "allows monkey patching of the lwrp through Chef::Resource" do
+ monkey = Module.new do
+ def issue_3607
+ end
+ end
+ Chef::Resource::LwrpOnce.send(:include, monkey)
+ expect { get_lwrp(:lwrp_once).new("blah").issue_3607 }.not_to raise_error
+ end
+
+ context "with a subclass of get_lwrp(:lwrp_once)" do
+ let(:subclass) do
+ Class.new(get_lwrp(:lwrp_once))
+ end
+
+ it "subclass.new is a subclass" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_truthy
+ expect(lwrp.is_a?(subclass)).to be_truthy
+ expect(subclass === lwrp).to be_truthy
+ expect(lwrp.class === subclass)
+ end
+ it "subclass.new is a Chef::Resource::LwrpOnce" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ expect(lwrp.class === Chef::Resource::LwrpOnce)
+ end
+ it "subclass.new is a get_lwrp(:lwrp_once)" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(lwrp.class === get_lwrp(:lwrp_once))
+ end
+ it "Chef::Resource::LwrpOnce.new is *not* a subclass" do
+ lwrp = Chef::Resource::LwrpOnce.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === Chef::Resource::LwrpOnce).to be_falsey
+ end
+ it "get_lwrp(:lwrp_once).new is *not* a subclass" do
+ lwrp = get_lwrp(:lwrp_once).new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === get_lwrp(:lwrp_once)).to be_falsey
+ end
+ end
+
+ context "with a subclass of Chef::Resource::LwrpOnce" do
+ let(:subclass) do
+ Class.new(Chef::Resource::LwrpOnce)
+ end
+
+ it "subclass.new is a subclass" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_truthy
+ expect(lwrp.is_a?(subclass)).to be_truthy
+ expect(subclass === lwrp).to be_truthy
+ expect(lwrp.class === subclass)
+ end
+ it "subclass.new is a Chef::Resource::LwrpOnce" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(lwrp.is_a?(Chef::Resource::LwrpOnce)).to be_truthy
+ expect(Chef::Resource::LwrpOnce === lwrp).to be_truthy
+ expect(lwrp.class === Chef::Resource::LwrpOnce)
+ end
+ it "subclass.new is a get_lwrp(:lwrp_once)" do
+ lwrp = subclass.new('hi')
+ expect(lwrp.kind_of?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(lwrp.is_a?(get_lwrp(:lwrp_once))).to be_truthy
+ expect(get_lwrp(:lwrp_once) === lwrp).to be_truthy
+ expect(lwrp.class === get_lwrp(:lwrp_once))
+ end
+ it "Chef::Resource::LwrpOnce.new is *not* a subclass" do
+ lwrp = Chef::Resource::LwrpOnce.new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === Chef::Resource::LwrpOnce).to be_falsey
+ end
+ it "get_lwrp(:lwrp_once).new is *not* a subclass" do
+ lwrp = get_lwrp(:lwrp_once).new('hi')
+ expect(lwrp.kind_of?(subclass)).to be_falsey
+ expect(lwrp.is_a?(subclass)).to be_falsey
+ expect(subclass === lwrp.class).to be_falsey
+ expect(subclass === get_lwrp(:lwrp_once)).to be_falsey
+ end
+ end
+ end
end
+
+
diff --git a/spec/unit/mixin/api_version_request_handling_spec.rb b/spec/unit/mixin/api_version_request_handling_spec.rb
new file mode 100644
index 0000000000..cc5340e424
--- /dev/null
+++ b/spec/unit/mixin/api_version_request_handling_spec.rb
@@ -0,0 +1,127 @@
+#
+# Author:: Tyler Cloke (tyler@chef.io)
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Mixin::ApiVersionRequestHandling do
+ let(:dummy_class) { Class.new { include Chef::Mixin::ApiVersionRequestHandling } }
+ let(:object) { dummy_class.new }
+
+ describe ".server_client_api_version_intersection" do
+ let(:default_supported_client_versions) { [0,1,2] }
+
+
+ context "when the response code is not 406" do
+ let(:response) { OpenStruct.new(:code => '405') }
+ let(:exception) { Net::HTTPServerException.new("405 Something Else", response) }
+
+ it "returns nil" do
+ expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)).
+ to be_nil
+ end
+
+ end # when the response code is not 406
+
+ context "when the response code is 406" do
+ let(:response) { OpenStruct.new(:code => '406') }
+ let(:exception) { Net::HTTPServerException.new("406 Not Acceptable", response) }
+
+ context "when x-ops-server-api-version header does not exist" do
+ it "returns nil" do
+ expect(object.server_client_api_version_intersection(exception, default_supported_client_versions)).
+ to be_nil
+ end
+ end # when x-ops-server-api-version header does not exist
+
+ context "when x-ops-server-api-version header exists" do
+ let(:min_server_version) { 2 }
+ let(:max_server_version) { 4 }
+ let(:return_hash) {
+ {
+ "min_version" => min_server_version,
+ "max_version" => max_server_version
+ }
+ }
+
+ before(:each) do
+ allow(response).to receive(:[]).with('x-ops-server-api-version').and_return(Chef::JSONCompat.to_json(return_hash))
+ end
+
+ context "when there is no intersection between client and server versions" do
+ shared_examples_for "no intersection between client and server versions" do
+ it "return an array" do
+ expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+ to be_a_kind_of(Array)
+ end
+
+ it "returns an empty array" do
+ expect(object.server_client_api_version_intersection(exception, supported_client_versions).length).
+ to eq(0)
+ end
+
+ end
+
+ context "when all the versions are higher than the max" do
+ it_should_behave_like "no intersection between client and server versions" do
+ let(:supported_client_versions) { [5,6,7] }
+ end
+ end
+
+ context "when all the versions are lower than the min" do
+ it_should_behave_like "no intersection between client and server versions" do
+ let(:supported_client_versions) { [0,1] }
+ end
+ end
+
+ end # when there is no intersection between client and server versions
+
+ context "when there is an intersection between client and server versions" do
+ context "when multiple versions intersect" do
+ let(:supported_client_versions) { [1,2,3,4,5] }
+
+ it "includes all of the intersection" do
+ expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+ to eq([2,3,4])
+ end
+ end # when multiple versions intersect
+
+ context "when only the min client version intersects" do
+ let(:supported_client_versions) { [0,1,2] }
+
+ it "includes the intersection" do
+ expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+ to eq([2])
+ end
+ end # when only the min client version intersects
+
+ context "when only the max client version intersects" do
+ let(:supported_client_versions) { [4,5,6] }
+
+ it "includes the intersection" do
+ expect(object.server_client_api_version_intersection(exception, supported_client_versions)).
+ to eq([4])
+ end
+ end # when only the max client version intersects
+
+ end # when there is an intersection between client and server versions
+
+ end # when x-ops-server-api-version header exists
+ end # when the response code is 406
+
+ end # .server_client_api_version_intersection
+end # Chef::Mixin::ApiVersionRequestHandling
diff --git a/spec/unit/mixin/command_spec.rb b/spec/unit/mixin/command_spec.rb
index e198e3addd..050b261256 100644
--- a/spec/unit/mixin/command_spec.rb
+++ b/spec/unit/mixin/command_spec.rb
@@ -22,7 +22,7 @@ describe Chef::Mixin::Command, :volatile do
if windows?
- pending("TODO MOVE: this is a platform specific integration test.")
+ skip("TODO MOVE: this is a platform specific integration test.")
else
@@ -61,7 +61,6 @@ describe Chef::Mixin::Command, :volatile do
it "returns immediately after the first child process exits" do
expect {Timeout.timeout(10) do
- pid, stdin,stdout,stderr = nil,nil,nil,nil
evil_forker="exit if fork; 10.times { sleep 1}"
popen4("ruby -e '#{evil_forker}'") do |pid,stdin,stdout,stderr|
end
diff --git a/spec/unit/mixin/params_validate_spec.rb b/spec/unit/mixin/params_validate_spec.rb
index 1b61f9b238..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,91 +333,77 @@ 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
- def self.test_set_or_return_method(method)
- # caller is responsible for passing in the right arg to get 'return' behavior
- return_arg = method == :nillable_set_or_return ? TinyClass::NULL_ARG : nil
-
- it "#{method} should set and return a value, then return the same value" do
- value = "meow"
- expect(@vo.send(method,:test, value, {}).object_id).to eq(value.object_id)
- expect(@vo.send(method,:test, return_arg, {}).object_id).to eq(value.object_id)
- end
-
- it "#{method} should set and return a default value when the argument is nil, then return the same value" do
- value = "meow"
- expect(@vo.send(method,:test, return_arg, { :default => value }).object_id).to eq(value.object_id)
- expect(@vo.send(method,:test, return_arg, {}).object_id).to eq(value.object_id)
- end
-
- it "#{method} should raise an ArgumentError when argument is nil and required is true" do
- expect {
- @vo.send(method,:test, return_arg, { :required => true })
- }.to raise_error(ArgumentError)
- end
-
- it "#{method} should not raise an error when argument is nil and required is false" do
- expect {
- @vo.send(method,:test, return_arg, { :required => false })
- }.not_to raise_error
- end
-
- it "#{method} should set and return @name, then return @name for foo when argument is nil" do
- value = "meow"
- expect(@vo.send(method,:name, value, { }).object_id).to eq(value.object_id)
- expect(@vo.send(method,:foo, return_arg, { :name_attribute => true }).object_id).to eq(value.object_id)
- end
-
- it "#{method} should allow DelayedEvaluator instance to be set for value regardless of restriction" do
- value = Chef::DelayedEvaluator.new{ 'test' }
- @vo.send(method,:test, value, {:kind_of => Numeric})
- end
-
- it "#{method} should raise an error when delayed evaluated attribute is not valid" do
- value = Chef::DelayedEvaluator.new{ 'test' }
- @vo.send(method,:test, value, {:kind_of => Numeric})
- expect do
- @vo.send(method,:test, return_arg, {:kind_of => Numeric})
- end.to raise_error(Chef::Exceptions::ValidationFailed)
- end
-
- it "#{method} should create DelayedEvaluator instance when #lazy is used" do
- @vo.send(method,:delayed, @vo.lazy{ 'test' }, {})
- expect(@vo.instance_variable_get(:@delayed)).to be_a(Chef::DelayedEvaluator)
- end
-
- it "#{method} should execute block on each call when DelayedEvaluator" do
- value = 'fubar'
- @vo.send(method,:test, @vo.lazy{ value }, {})
- expect(@vo.send(method,:test, return_arg, {})).to eq('fubar')
- value = 'foobar'
- expect(@vo.send(method,:test, return_arg, {})).to eq('foobar')
- value = 'fauxbar'
- expect(@vo.send(method,:test, return_arg, {})).to eq('fauxbar')
- end
-
- it "#{method} should not evaluate non DelayedEvaluator instances" do
- value = lambda{ 'test' }
- @vo.send(method,:test, value, {})
- expect(@vo.send(method,:test, return_arg, {}).object_id).to eq(value.object_id)
- expect(@vo.send(method,:test, return_arg, {})).to be_a(Proc)
- end
+ it "should set and return a value, then return the same value" do
+ value = "meow"
+ expect(@vo.set_or_return(:test, value, {}).object_id).to eq(value.object_id)
+ expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id)
+ end
+
+ it "should set and return a default value when the argument is nil, then return the same value" do
+ value = "meow"
+ expect(@vo.set_or_return(:test, nil, { :default => value }).object_id).to eq(value.object_id)
+ expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id)
+ end
+
+ it "should raise an ArgumentError when argument is nil and required is true" do
+ expect {
+ @vo.set_or_return(:test, nil, { :required => true })
+ }.to raise_error(ArgumentError)
+ end
+
+ it "should not raise an error when argument is nil and required is false" do
+ expect {
+ @vo.set_or_return(:test, nil, { :required => false })
+ }.not_to raise_error
end
- test_set_or_return_method(:set_or_return)
- test_set_or_return_method(:nillable_set_or_return)
+ it "should set and return @name, then return @name for foo when argument is nil" do
+ value = "meow"
+ expect(@vo.set_or_return(:name, value, { }).object_id).to eq(value.object_id)
+ expect(@vo.set_or_return(:foo, nil, { :name_attribute => true }).object_id).to eq(value.object_id)
+ end
- it "nillable_set_or_return supports nilling values" do
- expect(@vo.nillable_set_or_return(:test, "meow", {})).to eq("meow")
- expect(@vo.nillable_set_or_return(:test, TinyClass::NULL_ARG, {})).to eq("meow")
- expect(@vo.nillable_set_or_return(:test, nil, {})).to be_nil
- expect(@vo.nillable_set_or_return(:test, TinyClass::NULL_ARG, {})).to be_nil
+ it "should allow DelayedEvaluator instance to be set for value regardless of restriction" do
+ value = Chef::DelayedEvaluator.new{ 'test' }
+ @vo.set_or_return(:test, value, {:kind_of => Numeric})
end
+
+ it "should raise an error when delayed evaluated attribute is not valid" do
+ value = Chef::DelayedEvaluator.new{ 'test' }
+ @vo.set_or_return(:test, value, {:kind_of => Numeric})
+ expect do
+ @vo.set_or_return(:test, nil, {:kind_of => Numeric})
+ end.to raise_error(Chef::Exceptions::ValidationFailed)
+ end
+
+ it "should create DelayedEvaluator instance when #lazy is used" do
+ @vo.set_or_return(:delayed, @vo.lazy{ 'test' }, {})
+ expect(@vo.instance_variable_get(:@delayed)).to be_a(Chef::DelayedEvaluator)
+ end
+
+ it "should execute block on each call when DelayedEvaluator" do
+ value = 'fubar'
+ @vo.set_or_return(:test, @vo.lazy{ value }, {})
+ expect(@vo.set_or_return(:test, nil, {})).to eq('fubar')
+ value = 'foobar'
+ expect(@vo.set_or_return(:test, nil, {})).to eq('foobar')
+ value = 'fauxbar'
+ expect(@vo.set_or_return(:test, nil, {})).to eq('fauxbar')
+ end
+
+ it "should not evaluate non DelayedEvaluator instances" do
+ value = lambda{ 'test' }
+ @vo.set_or_return(:test, value, {})
+ expect(@vo.set_or_return(:test, nil, {}).object_id).to eq(value.object_id)
+ expect(@vo.set_or_return(:test, nil, {})).to be_a(Proc)
+ end
+
end
diff --git a/spec/unit/mixin/path_sanity_spec.rb b/spec/unit/mixin/path_sanity_spec.rb
index ec8e182e3d..3a924b9538 100644
--- a/spec/unit/mixin/path_sanity_spec.rb
+++ b/spec/unit/mixin/path_sanity_spec.rb
@@ -35,7 +35,7 @@ describe Chef::Mixin::PathSanity do
@gem_bindir = '/some/gem/bin'
allow(Gem).to receive(:bindir).and_return(@gem_bindir)
allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(@ruby_bindir)
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
end
it "adds all useful PATHs even if environment is an empty hash" do
@@ -77,7 +77,7 @@ describe Chef::Mixin::PathSanity do
gem_bindir = 'C:\gems\bin'
allow(Gem).to receive(:bindir).and_return(gem_bindir)
allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return(ruby_bindir)
- allow(Chef::Platform).to receive(:windows?).and_return(true)
+ allow(ChefConfig).to receive(:windows?).and_return(true)
env = {"PATH" => 'C:\Windows\system32;C:\mr\softie'}
@sanity.enforce_path_sanity(env)
expect(env["PATH"]).to eq("C:\\Windows\\system32;C:\\mr\\softie;#{ruby_bindir};#{gem_bindir}")
diff --git a/spec/unit/mixin/powershell_out_spec.rb b/spec/unit/mixin/powershell_out_spec.rb
new file mode 100644
index 0000000000..0fede582fa
--- /dev/null
+++ b/spec/unit/mixin/powershell_out_spec.rb
@@ -0,0 +1,70 @@
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/powershell_out'
+
+describe Chef::Mixin::PowershellOut do
+ let(:shell_out_class) { Class.new { include Chef::Mixin::PowershellOut } }
+ subject(:object) { shell_out_class.new }
+ let(:architecture) { "something" }
+ let(:flags) {
+ "-NoLogo -NonInteractive -NoProfile -ExecutionPolicy Unrestricted -InputFormat None"
+ }
+
+ describe "#powershell_out" do
+ it "runs a command and returns the shell_out object" do
+ ret = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ {}
+ ).and_return(ret)
+ expect(object.powershell_out("Get-Process")).to eql(ret)
+ end
+
+ it "passes options" do
+ ret = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ timeout: 600
+ ).and_return(ret)
+ expect(object.powershell_out("Get-Process", timeout: 600)).to eql(ret)
+ end
+ end
+
+ describe "#powershell_out!" do
+ it "runs a command and returns the shell_out object" do
+ mixlib_shellout = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ {}
+ ).and_return(mixlib_shellout)
+ expect(mixlib_shellout).to receive(:error!)
+ expect(object.powershell_out!("Get-Process")).to eql(mixlib_shellout)
+ end
+
+ it "passes options" do
+ mixlib_shellout = double("Mixlib::ShellOut")
+ expect(object).to receive(:shell_out).with(
+ "powershell.exe #{flags} -Command \"Get-Process\"",
+ timeout: 600
+ ).and_return(mixlib_shellout)
+ expect(mixlib_shellout).to receive(:error!)
+ expect(object.powershell_out!("Get-Process", timeout: 600)).to eql(mixlib_shellout)
+ end
+ end
+end
diff --git a/spec/unit/mixin/powershell_type_coercions_spec.rb b/spec/unit/mixin/powershell_type_coercions_spec.rb
new file mode 100644
index 0000000000..988c3926c1
--- /dev/null
+++ b/spec/unit/mixin/powershell_type_coercions_spec.rb
@@ -0,0 +1,72 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/powershell_type_coercions'
+require 'base64'
+
+class Chef::PSTypeTester
+ include Chef::Mixin::PowershellTypeCoercions
+end
+
+describe Chef::Mixin::PowershellTypeCoercions do
+ let (:test_class) { Chef::PSTypeTester.new }
+
+ describe '#translate_type' do
+ it 'should single quote a string' do
+ expect(test_class.translate_type('foo')).to eq("'foo'")
+ end
+
+ ["'", '"', '#', '`'].each do |c|
+ it "should base64 encode a string that contains #{c}" do
+ expect(test_class.translate_type("#{c}")).to match(Base64.strict_encode64(c))
+ end
+ end
+
+ it 'should not quote an integer' do
+ expect(test_class.translate_type(123)).to eq('123')
+ end
+
+ it 'should not quote a floating point number' do
+ expect(test_class.translate_type(123.4)).to eq('123.4')
+ end
+
+ it 'should return $false when an instance of FalseClass is provided' do
+ expect(test_class.translate_type(false)).to eq('$false')
+ end
+
+ it 'should return $true when an instance of TrueClass is provided' do
+ expect(test_class.translate_type(true)).to eq('$true')
+ end
+
+ it 'should translate all members of a hash and wrap them in @{} separated by ;' do
+ expect(test_class.translate_type({"a" => 1, "b" => 1.2, "c" => false, "d" => true
+ })).to eq("@{a=1;b=1.2;c=$false;d=$true}")
+ end
+
+ it 'should translat all members of an array and them by a ,' do
+ expect(test_class.translate_type([true, false])).to eq('@($true,$false)')
+ end
+
+ it 'should fall back :to_psobject if we have not defined at explicit rule' do
+ ps_obj = double("PSObject")
+ expect(ps_obj).to receive(:to_psobject).and_return('$true')
+ expect(test_class.translate_type(ps_obj)).to eq('($true)')
+ end
+ end
+end
diff --git a/spec/unit/mixin/template_spec.rb b/spec/unit/mixin/template_spec.rb
index f02bd34b8f..6a867b5f9a 100644
--- a/spec/unit/mixin/template_spec.rb
+++ b/spec/unit/mixin/template_spec.rb
@@ -39,7 +39,7 @@ describe Chef::Mixin::Template, "render_template" do
describe "when running on windows" do
before do
- allow(Chef::Platform).to receive(:windows?).and_return(true)
+ allow(ChefConfig).to receive(:windows?).and_return(true)
end
it "should render the templates with windows line endings" do
@@ -54,7 +54,7 @@ describe Chef::Mixin::Template, "render_template" do
describe "when running on unix" do
before do
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
end
it "should render the templates with unix line endings" do
diff --git a/spec/unit/mixin/unformatter_spec.rb b/spec/unit/mixin/unformatter_spec.rb
new file mode 100644
index 0000000000..2eae0ac9bb
--- /dev/null
+++ b/spec/unit/mixin/unformatter_spec.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/unformatter'
+
+class Chef::UnformatterTest
+ include Chef::Mixin::Unformatter
+
+ def foo
+ end
+
+end
+
+describe Chef::Mixin::Unformatter do
+ let (:unformatter) { Chef::UnformatterTest.new }
+ let (:message) { "Test Message" }
+
+ describe "#write" do
+ context "with a timestamp" do
+ it "sends foo to itself when the message is of severity foo" do
+ expect(unformatter).to receive(:foo).with(message)
+ unformatter.write("[time] foo: #{message}")
+ end
+
+ it "sends foo to itself when the message is of severity FOO" do
+ expect(unformatter).to receive(:foo).with(message)
+ unformatter.write("[time] FOO: #{message}")
+ end
+ end
+
+ context "without a timestamp" do
+ it "sends foo to itself when the message is of severity foo" do
+ expect(unformatter).to receive(:foo).with(message)
+ unformatter.write("foo: #{message}")
+ end
+
+ it "sends foo to itself when the message is of severity FOO" do
+ expect(unformatter).to receive(:foo).with(message)
+ unformatter.write("FOO: #{message}")
+ end
+ end
+
+ end
+
+end
diff --git a/spec/unit/mixin/uris_spec.rb b/spec/unit/mixin/uris_spec.rb
new file mode 100644
index 0000000000..d4985c4f67
--- /dev/null
+++ b/spec/unit/mixin/uris_spec.rb
@@ -0,0 +1,57 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+require 'chef/mixin/uris'
+
+class Chef::UrisTest
+ include Chef::Mixin::Uris
+end
+
+describe Chef::Mixin::Uris do
+ let (:uris) { Chef::UrisTest.new }
+
+ describe "#uri_scheme?" do
+ it "matches 'scheme://foo.com'" do
+ expect(uris.uri_scheme?('scheme://foo.com')).to eq(true)
+ end
+
+ it "does not match 'c:/foo.com'" do
+ expect(uris.uri_scheme?('c:/foo.com')).to eq(false)
+ end
+
+ it "does not match '/usr/bin/foo.com'" do
+ expect(uris.uri_scheme?('/usr/bin/foo.com')).to eq(false)
+ end
+
+ it "does not match 'c:/foo.com://bar.com'" do
+ expect(uris.uri_scheme?('c:/foo.com://bar.com')).to eq(false)
+ end
+ end
+
+ describe "#as_uri" do
+ it "parses a file scheme uri with spaces" do
+ expect{ uris.as_uri("file:///c:/foo bar.txt") }.not_to raise_exception
+ end
+
+ it "returns a URI object" do
+ expect( uris.as_uri("file:///c:/foo bar.txt") ).to be_a(URI)
+ end
+ end
+
+end
diff --git a/spec/unit/mixin/windows_architecture_helper_spec.rb b/spec/unit/mixin/windows_architecture_helper_spec.rb
index 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 fe7372961b..7b37ea59f4 100644
--- a/spec/unit/node_map_spec.rb
+++ b/spec/unit/node_map_spec.rb
@@ -131,9 +131,25 @@ describe Chef::NodeMap do
allow(node).to receive(:[]).with(:platform_version).and_return("6.0")
expect(node_map.get(node, :thing)).to eql(nil)
end
+
+ context "when there is a less specific definition" do
+ before do
+ node_map.set(:thing, :bar, platform_family: "rhel")
+ end
+
+ it "returns the value when the node matches" do
+ allow(node).to receive(:[]).with(:platform_family).and_return("rhel")
+ allow(node).to receive(:[]).with(:platform_version).and_return("7.0")
+ expect(node_map.get(node, :thing)).to eql(:foo)
+ end
+ end
end
describe "resource back-compat testing" do
+ before :each do
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
it "should handle :on_platforms => :all" do
node_map.set(:chef_gem, :foo, :on_platforms => :all)
allow(node).to receive(:[]).with(:platform).and_return("windows")
@@ -152,4 +168,3 @@ describe Chef::NodeMap do
end
end
-
diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb
index 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/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb
index 7aafc287ea..33d4c2c3b7 100644
--- a/spec/unit/platform/query_helpers_spec.rb
+++ b/spec/unit/platform/query_helpers_spec.rb
@@ -20,7 +20,7 @@ require 'spec_helper'
describe "Chef::Platform#windows_server_2003?" do
it "returns false early when not on windows" do
- allow(Chef::Platform).to receive(:windows?).and_return(false)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
expect(Chef::Platform).not_to receive(:require)
expect(Chef::Platform.windows_server_2003?).to be_falsey
end
@@ -53,3 +53,25 @@ describe 'Chef::Platform#supports_dsc?' do
end
end
end
+
+describe 'Chef::Platform#supports_dsc_invoke_resource?' do
+ it 'returns false if powershell is not present' do
+ node = Chef::Node.new
+ expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey
+ end
+
+ ['1.0', '2.0', '3.0', '4.0', '5.0.10017.9'].each do |version|
+ it "returns false for Powershell #{version}" do
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = version
+ expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_falsey
+ end
+ end
+
+ it "returns true for Powershell 5.0.10018.0" do
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = "5.0.10018.0"
+ expect(Chef::Platform.supports_dsc_invoke_resource?(node)).to be_truthy
+ end
+end
+
diff --git a/spec/unit/platform_spec.rb b/spec/unit/platform_spec.rb
index fb65ef0fea..34b46f657f 100644
--- a/spec/unit/platform_spec.rb
+++ b/spec/unit/platform_spec.rb
@@ -18,34 +18,6 @@
require 'spec_helper'
-describe "Chef::Platform supports" do
- [
- :mac_os_x,
- :mac_os_x_server,
- :freebsd,
- :ubuntu,
- :debian,
- :centos,
- :fedora,
- :suse,
- :opensuse,
- :redhat,
- :oracle,
- :gentoo,
- :arch,
- :solaris,
- :mswin,
- :mingw32,
- :windows,
- :gcel,
- :ibm_powerkvm
- ].each do |platform|
- it "#{platform}" do
- expect(Chef::Platform.platforms).to have_key(platform)
- end
- end
-end
-
describe Chef::Platform do
context "while testing with fake data" do
@@ -131,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
@@ -266,41 +238,4 @@ describe Chef::Platform do
end
- context "while testing the configured platform data" do
-
- it "should use the solaris package provider on Solaris <11" do
- pmap = Chef::Platform.find("Solaris2", "5.9")
- expect(pmap[:package]).to eql(Chef::Provider::Package::Solaris)
- end
-
- it "should use the IPS package provider on Solaris 11" do
- pmap = Chef::Platform.find("Solaris2", "5.11")
- expect(pmap[:package]).to eql(Chef::Provider::Package::Ips)
- end
-
- it "should use the Redhat service provider on SLES11" do
- 1.upto(3) do |sp|
- pmap = Chef::Platform.find("SUSE", "11.#{sp}")
- expect(pmap[:service]).to eql(Chef::Provider::Service::Redhat)
- end
- end
-
- it "should use the Systemd service provider on SLES12" do
- pmap = Chef::Platform.find("SUSE", "12.0")
- expect(pmap[:service]).to eql(Chef::Provider::Service::Systemd)
- end
-
- it "should use the SUSE group provider on SLES11" do
- 1.upto(3) do |sp|
- pmap = Chef::Platform.find("SUSE", "11.#{sp}")
- expect(pmap[:group]).to eql(Chef::Provider::Group::Suse)
- end
- end
-
- it "should use the Gpasswd group provider on SLES12" do
- pmap = Chef::Platform.find("SUSE", "12.0")
- expect(pmap[:group]).to eql(Chef::Provider::Group::Gpasswd)
- end
- end
-
end
diff --git a/spec/unit/policy_builder/policyfile_spec.rb b/spec/unit/policy_builder/policyfile_spec.rb
index 8b6e928a46..5fa00d8f2b 100644
--- a/spec/unit/policy_builder/policyfile_spec.rb
+++ b/spec/unit/policy_builder/policyfile_spec.rb
@@ -166,13 +166,17 @@ describe Chef::PolicyBuilder::Policyfile do
end
before do
- # TODO: agree on this name and logic.
+ Chef::Config[:policy_document_native_api] = false
Chef::Config[:deployment_group] = "example-policy-stage"
allow(policy_builder).to receive(:http_api).and_return(http_api)
end
describe "when using compatibility mode (policy_document_native_api == false)" do
+ before do
+ Chef::Config[:deployment_group] = "example-policy-stage"
+ end
+
context "when the deployment group cannot be loaded" do
let(:error404) { Net::HTTPServerException.new("404 message", :body) }
@@ -256,7 +260,7 @@ describe Chef::PolicyBuilder::Policyfile do
context "and policy_name and policy_group are configured" do
- let(:policy_relative_url) { "policies/policy-stage/example" }
+ let(:policy_relative_url) { "policy_groups/policy-stage/policies/example" }
before do
expect(http_api).to receive(:get).with(policy_relative_url).and_return(parsed_policyfile_json)
@@ -386,8 +390,11 @@ describe Chef::PolicyBuilder::Policyfile do
describe "fetching the desired cookbook set" do
- let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook") }
- let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook") }
+ let(:example1_cookbook_data) { double("CookbookVersion Hash for example1 cookbook") }
+ let(:example2_cookbook_data) { double("CookbookVersion Hash for example2 cookbook") }
+
+ let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook", version: "0.1.2") }
+ let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook", version: "1.2.3") }
let(:expected_cookbook_hash) do
{ "example1" => example1_cookbook_object, "example2" => example2_cookbook_object }
@@ -396,9 +403,12 @@ describe Chef::PolicyBuilder::Policyfile do
let(:example1_xyz_version) { example1_lock_data["dotted_decimal_identifier"] }
let(:example2_xyz_version) { example2_lock_data["dotted_decimal_identifier"] }
+ let(:example1_identifier) { example1_lock_data["identifier"] }
+ let(:example2_identifier) { example2_lock_data["identifier"] }
+
let(:cookbook_synchronizer) { double("Chef::CookbookSynchronizer") }
- shared_examples_for "fetching cookbooks" do
+ shared_examples "fetching cookbooks when they don't exist" do
context "and a cookbook is missing" do
let(:error404) { Net::HTTPServerException.new("404 message", :body) }
@@ -418,7 +428,9 @@ describe Chef::PolicyBuilder::Policyfile do
end
end
+ end
+ shared_examples_for "fetching cookbooks when they exist" do
context "and the cookbooks can be fetched" do
before do
expect(Chef::Node).to receive(:find_or_create).with(node_name).and_return(node)
@@ -426,11 +438,6 @@ describe Chef::PolicyBuilder::Policyfile do
policy_builder.load_node
policy_builder.build_node
- expect(http_api).to receive(:get).with(cookbook1_url).
- and_return(example1_cookbook_object)
- expect(http_api).to receive(:get).with(cookbook2_url).
- and_return(example2_cookbook_object)
-
allow(Chef::CookbookSynchronizer).to receive(:new).
with(expected_cookbook_hash, events).
and_return(cookbook_synchronizer)
@@ -457,11 +464,23 @@ describe Chef::PolicyBuilder::Policyfile do
end # shared_examples_for "fetching cookbooks"
context "when using compatibility mode (policy_document_native_api == false)" do
- include_examples "fetching cookbooks" do
+ let(:cookbook1_url) { "cookbooks/example1/#{example1_xyz_version}" }
+ let(:cookbook2_url) { "cookbooks/example2/#{example2_xyz_version}" }
+
+ context "when the cookbooks don't exist on the server" do
+ include_examples "fetching cookbooks when they don't exist"
+ end
- let(:cookbook1_url) { "cookbooks/example1/#{example1_xyz_version}" }
- let(:cookbook2_url) { "cookbooks/example2/#{example2_xyz_version}" }
+ context "when the cookbooks exist on the server" do
+ before do
+ expect(http_api).to receive(:get).with(cookbook1_url).
+ and_return(example1_cookbook_object)
+ expect(http_api).to receive(:get).with(cookbook2_url).
+ and_return(example2_cookbook_object)
+ end
+
+ include_examples "fetching cookbooks when they exist"
end
end
@@ -474,13 +493,33 @@ describe Chef::PolicyBuilder::Policyfile do
Chef::Config[:policy_name] = "example"
end
- include_examples "fetching cookbooks" do
+ let(:cookbook1_url) { "cookbook_artifacts/example1/#{example1_identifier}" }
+ let(:cookbook2_url) { "cookbook_artifacts/example2/#{example2_identifier}" }
+
+ context "when the cookbooks don't exist on the server" do
+ include_examples "fetching cookbooks when they don't exist"
+ end
+
+
+ context "when the cookbooks exist on the server" do
+
+ before do
+ expect(http_api).to receive(:get).with(cookbook1_url).
+ and_return(example1_cookbook_data)
+ expect(http_api).to receive(:get).with(cookbook2_url).
+ and_return(example2_cookbook_data)
+
+ expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example1_cookbook_data).
+ and_return(example1_cookbook_object)
+ expect(Chef::CookbookVersion).to receive(:from_cb_artifact_data).with(example2_cookbook_data).
+ and_return(example2_cookbook_object)
+ end
- let(:cookbook1_url) { "cookbook_artifacts/example1/#{example1_xyz_version}" }
- let(:cookbook2_url) { "cookbook_artifacts/example2/#{example2_xyz_version}" }
+ include_examples "fetching cookbooks when they exist"
end
+
end
end
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/revision_spec.rb b/spec/unit/provider/deploy/revision_spec.rb
index 4ca64e3445..caa60878e1 100644
--- a/spec/unit/provider/deploy/revision_spec.rb
+++ b/spec/unit/provider/deploy/revision_spec.rb
@@ -21,7 +21,7 @@ require 'spec_helper'
describe Chef::Provider::Deploy::Revision do
before do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
@temp_dir = Dir.mktmpdir
Chef::Config[:file_cache_path] = @temp_dir
@resource = Chef::Resource::Deploy.new("/my/deploy/dir")
diff --git a/spec/unit/provider/deploy_spec.rb b/spec/unit/provider/deploy_spec.rb
index c95a9b3d57..f6bb78823f 100644
--- a/spec/unit/provider/deploy_spec.rb
+++ b/spec/unit/provider/deploy_spec.rb
@@ -21,7 +21,7 @@ require 'spec_helper'
describe Chef::Provider::Deploy do
before do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
@release_time = Time.utc( 2004, 8, 15, 16, 23, 42)
allow(Time).to receive(:now).and_return(@release_time)
@expected_release_dir = "/my/deploy/dir/releases/20040815162342"
@@ -622,7 +622,7 @@ describe Chef::Provider::Deploy do
gems = @provider.send(:gem_packages)
- expect(gems.map { |g| g.action }).to eq([[:install]])
+ expect(gems.map { |g| g.action }).to eq([%i{install}])
expect(gems.map { |g| g.name }).to eq(%w{eventmachine})
expect(gems.map { |g| g.version }).to eq(%w{0.12.9})
end
diff --git a/spec/unit/provider/directory_spec.rb b/spec/unit/provider/directory_spec.rb
index 13c57bfe56..4fad8c8906 100644
--- a/spec/unit/provider/directory_spec.rb
+++ b/spec/unit/provider/directory_spec.rb
@@ -16,173 +16,272 @@
# limitations under the License.
#
-require 'ostruct'
-
require 'spec_helper'
require 'tmpdir'
describe Chef::Provider::Directory do
- before(:each) do
- @new_resource = Chef::Resource::Directory.new(Dir.tmpdir)
- if !windows?
- @new_resource.owner(500)
- @new_resource.group(500)
- @new_resource.mode(0644)
+ let(:tmp_dir) { Dir.mktmpdir }
+ let(:new_resource) { Chef::Resource::Directory.new(tmp_dir) }
+ let(:node) { Chef::Node.new }
+ let(:events) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, events) }
+ let(:directory) { Chef::Provider::Directory.new(new_resource, run_context) }
+
+ describe "#load_current_resource" do
+ describe "scanning file security metadata"
+ describe "on unix", unix_only: true do
+ describe "when the directory exists" do
+ let(:dir_stat) { File::Stat.new(tmp_dir) }
+ let(:expected_uid) { dir_stat.uid }
+ let(:expected_gid) { dir_stat.gid }
+ let(:expected_mode) { "0%o" % ( dir_stat.mode & 007777 ) }
+ let(:expected_pwnam) { Etc.getpwuid(expected_uid).name }
+ let(:expected_grnam) { Etc.getgrgid(expected_gid).name }
+
+ it "describes the access mode as a String of octal integers" do
+ directory.load_current_resource
+ expect(directory.current_resource.mode).to eq(expected_mode)
+ end
+
+ it "when the new_resource.owner is numeric, describes the owner as a numeric uid" do
+ new_resource.owner(500)
+ directory.load_current_resource
+ expect(directory.current_resource.owner).to eql(expected_uid)
+ end
+
+ it "when the new_resource.group is numeric, describes the group as a numeric gid" do
+ new_resource.group(500)
+ directory.load_current_resource
+ expect(directory.current_resource.group).to eql(expected_gid)
+ end
+
+ it "when the new_resource.owner is a string, describes the owner as a string" do
+ new_resource.owner("foo")
+ directory.load_current_resource
+ expect(directory.current_resource.owner).to eql(expected_pwnam)
+ end
+
+ it "when the new_resource.group is a string, describes the group as a string" do
+ new_resource.group("bar")
+ directory.load_current_resource
+ expect(directory.current_resource.group).to eql(expected_grnam)
+ end
+ end
end
- @node = Chef::Node.new
- @events = Chef::EventDispatch::Dispatcher.new
- @run_context = Chef::RunContext.new(@node, {}, @events)
- @directory = Chef::Provider::Directory.new(@new_resource, @run_context)
- end
+ describe "on windows", windows_only: true do
+ describe "when the directory exists" do
+ it "the mode is always nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.mode).to be nil
+ end
+
+ it "the owner is always nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.owner).to be nil
+ end
+ it "the group is always nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.group).to be nil
+ end
- describe "scanning file security metadata on windows" do
- before do
+ it "rights are always nil (incorrectly)" do
+ directory.load_current_resource
+ expect(directory.current_resource.rights).to be nil
+ end
+
+ it "inherits is always nil (incorrectly)" do
+ directory.load_current_resource
+ expect(directory.current_resource.inherits).to be nil
+ end
+ end
end
- it "describes the directory's access rights" do
- skip
+ describe "when the directory does not exist" do
+ before do
+ FileUtils.rmdir tmp_dir
+ end
+
+ it "sets the mode, group and owner to nil" do
+ directory.load_current_resource
+ expect(directory.current_resource.mode).to eq(nil)
+ expect(directory.current_resource.group).to eq(nil)
+ expect(directory.current_resource.owner).to eq(nil)
+ end
end
+
end
- describe "scanning file security metadata on unix" do
- before do
- allow(Chef::Platform).to receive(:windows?).and_return(false)
- end
- let(:mock_stat) do
- cstats = double("stats")
- allow(cstats).to receive(:uid).and_return(500)
- allow(cstats).to receive(:gid).and_return(500)
- allow(cstats).to receive(:mode).and_return(0755)
- cstats
+ describe "#define_resource_requirements" do
+ describe "on unix", unix_only: true do
+ it "raises an exception if the user does not exist" do
+ new_resource.owner("arglebargle_iv")
+ expect(Etc).to receive(:getpwnam).with("arglebargle_iv").and_raise(ArgumentError)
+ directory.action = :create
+ directory.load_current_resource
+ expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original
+ directory.define_resource_requirements
+ expect { directory.process_resource_requirements }.to raise_error(ArgumentError)
+ end
+
+ it "raises an exception if the group does not exist" do
+ new_resource.group("arglebargle_iv")
+ expect(Etc).to receive(:getgrnam).with("arglebargle_iv").and_raise(ArgumentError)
+ directory.action = :create
+ directory.load_current_resource
+ expect(directory.access_controls).to receive(:define_resource_requirements).and_call_original
+ directory.define_resource_requirements
+ expect { directory.process_resource_requirements }.to raise_error(ArgumentError)
+ end
end
+ end
+
+ describe "#run_action(:create)" do
+ describe "when the directory exists" do
+ it "does not create the directory" do
+ expect(Dir).not_to receive(:mkdir).with(new_resource.path)
+ directory.run_action(:create)
+ end
- it "describes the access mode as a String of octal integers" do
- allow(File).to receive(:exists?).and_return(true)
- expect(File).to receive(:stat).and_return(mock_stat)
- @directory.load_current_resource
- expect(@directory.current_resource.mode).to eq("0755")
+ it "should not set the resource as updated" do
+ directory.run_action(:create)
+ expect(new_resource).not_to be_updated
+ end
end
- context "when user and group are specified with UID/GID" do
- it "describes the current owner and group as UID and GID" do
- allow(File).to receive(:exists?).and_return(true)
- expect(File).to receive(:stat).and_return(mock_stat)
- @directory.load_current_resource
- expect(@directory.current_resource.path).to eql(@new_resource.path)
- expect(@directory.current_resource.owner).to eql(500)
- expect(@directory.current_resource.group).to eql(500)
+ describe "when the directory does not exist" do
+ before do
+ FileUtils.rmdir tmp_dir
+ end
+
+ it "creates the directory" do
+ directory.run_action(:create)
+ expect(File.exist?(tmp_dir)).to be true
+ end
+
+ it "sets the new resource as updated" do
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
end
end
- context "when user/group are specified with user/group names" do
+ describe "when the parent directory does not exist" do
+ before do
+ new_resource.path "#{tmp_dir}/foobar"
+ FileUtils.rmdir tmp_dir
+ end
+
+ it "raises an exception when recursive is false" do
+ new_resource.recursive false
+ expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
+
+ it "creates the directories when recursive is true" do
+ new_resource.recursive true
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
+ expect(File.exist?("#{tmp_dir}/foobar")).to be true
+ end
+
+ it "raises an exception when the parent directory is a file and recursive is true" do
+ FileUtils.touch tmp_dir
+ new_resource.recursive true
+ expect { directory.run_action(:create) }.to raise_error
+ end
+
+ it "raises the right exception when the parent directory is a file and recursive is true" do
+ pending "this seems to return the wrong error" # FIXME
+ FileUtils.touch tmp_dir
+ new_resource.recursive true
+ expect { directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ end
end
- end
- # Unix only for now. While file security attribute reporting for windows is
- # disabled, unix and windows differ in the number of exists? calls that are
- # made by the provider.
- it "should create a new directory on create, setting updated to true", :unix_only do
- @new_resource.path "/tmp/foo"
+ describe "on OS X" do
+ before do
+ allow(node).to receive(:[]).with("platform").and_return('mac_os_x')
+ new_resource.path "/usr/bin/chef_test"
+ new_resource.recursive false
+ allow_any_instance_of(Chef::Provider::File).to receive(:do_selinux)
+ end
- expect(File).to receive(:exists?).at_least(:once).and_return(false)
- expect(File).to receive(:directory?).with("/tmp").and_return(true)
- expect(Dir).to receive(:mkdir).with(@new_resource.path).once.and_return(true)
+ it "os x 10.10 can write to sip locations" do
+ allow(node).to receive(:[]).with("platform_version").and_return('10.10')
+ allow(Dir).to receive(:mkdir).and_return([true], [])
+ allow(::File).to receive(:directory?).and_return(true)
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(true)
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
+ end
- expect(@directory).to receive(:do_acl_changes)
- allow(@directory).to receive(:do_selinux)
- @directory.run_action(:create)
- expect(@directory.new_resource).to be_updated
- end
+ it "os x 10.11 cannot write to sip locations" do
+ allow(node).to receive(:[]).with("platform_version").and_return('10.11')
+ allow(::File).to receive(:directory?).and_return(true)
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+ expect {directory.run_action(:create) }.to raise_error(Chef::Exceptions::InsufficientPermissions)
+ end
- it "should raise an exception if the parent directory does not exist and recursive is false" do
- @new_resource.path "/tmp/some/dir"
- @new_resource.recursive false
- expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
+ it "os x 10.11 can write to sip exlcusions" do
+ new_resource.path "/usr/local/chef_test"
+ allow(node).to receive(:[]).with("platform_version").and_return('10.11')
+ allow(::File).to receive(:directory?).and_return(true)
+ allow(Dir).to receive(:mkdir).and_return([true], [])
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+ directory.run_action(:create)
+ expect(new_resource).to be_updated
+ end
+ end
end
- # Unix only for now. While file security attribute reporting for windows is
- # disabled, unix and windows differ in the number of exists? calls that are
- # made by the provider.
- it "should create a new directory when parent directory does not exist if recursive is true and permissions are correct", :unix_only do
- @new_resource.path "/path/to/dir"
- @new_resource.recursive true
- expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false)
-
- expect(File).to receive(:exists?).with('/path/to').ordered.and_return(false)
- expect(File).to receive(:exists?).with('/path').ordered.and_return(true)
- expect(Chef::FileAccessControl).to receive(:writable?).with('/path').ordered.and_return(true)
- expect(File).to receive(:exists?).with(@new_resource.path).ordered.and_return(false)
-
- expect(FileUtils).to receive(:mkdir_p).with(@new_resource.path).and_return(true)
- expect(@directory).to receive(:do_acl_changes)
- allow(@directory).to receive(:do_selinux)
- @directory.run_action(:create)
- expect(@new_resource).to be_updated
- end
+ describe "#run_action(:create)" do
+ describe "when the directory exists" do
+ it "deletes the directory" do
+ directory.run_action(:delete)
+ expect(File.exist?(tmp_dir)).to be false
+ end
+ it "sets the new resource as updated" do
+ directory.run_action(:delete)
+ expect(new_resource).to be_updated
+ end
+ end
- it "should raise an error when creating a directory when parent directory is a file" do
- expect(File).to receive(:directory?).and_return(false)
- expect(Dir).not_to receive(:mkdir).with(@new_resource.path)
- expect { @directory.run_action(:create) }.to raise_error(Chef::Exceptions::EnclosingDirectoryDoesNotExist)
- expect(@directory.new_resource).not_to be_updated
- end
+ describe "when the directory does not exist" do
+ before do
+ FileUtils.rmdir tmp_dir
+ end
- # Unix only for now. While file security attribute reporting for windows is
- # disabled, unix and windows differ in the number of exists? calls that are
- # made by the provider.
- it "should not create the directory if it already exists", :unix_only do
- stub_file_cstats
- @new_resource.path "/tmp/foo"
- expect(File).to receive(:directory?).at_least(:once).and_return(true)
- expect(Chef::FileAccessControl).to receive(:writable?).with("/tmp").and_return(true)
- expect(File).to receive(:exists?).at_least(:once).and_return(true)
- expect(Dir).not_to receive(:mkdir).with(@new_resource.path)
- expect(@directory).to receive(:do_acl_changes)
- @directory.run_action(:create)
- end
+ it "does not delete the directory" do
+ expect(Dir).not_to receive(:delete).with(new_resource.path)
+ directory.run_action(:delete)
+ end
- it "should delete the directory if it exists, and is writable with action_delete" do
- expect(File).to receive(:directory?).and_return(true)
- expect(Chef::FileAccessControl).to receive(:writable?).once.and_return(true)
- expect(Dir).to receive(:delete).with(@new_resource.path).once.and_return(true)
- @directory.run_action(:delete)
- end
+ it "sets the new resource as updated" do
+ directory.run_action(:delete)
+ expect(new_resource).not_to be_updated
+ end
+ end
- it "should raise an exception if it cannot delete the directory due to bad permissions" do
- allow(File).to receive(:exists?).and_return(true)
- allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
- expect { @directory.run_action(:delete) }.to raise_error(RuntimeError)
- end
+ describe "when the directory is not writable" do
+ before do
+ allow(Chef::FileAccessControl).to receive(:writable?).and_return(false)
+ end
- it "should take no action when deleting a target directory that does not exist" do
- @new_resource.path "/an/invalid/path"
- allow(File).to receive(:exists?).and_return(false)
- expect(Dir).not_to receive(:delete).with(@new_resource.path)
- @directory.run_action(:delete)
- expect(@directory.new_resource).not_to be_updated
- end
+ it "cannot delete it and raises an exception" do
+ expect { directory.run_action(:delete) }.to raise_error(RuntimeError)
+ end
+ end
- it "should raise an exception when deleting a directory when target directory is a file" do
- stub_file_cstats
- @new_resource.path "/an/invalid/path"
- allow(File).to receive(:exists?).and_return(true)
- expect(File).to receive(:directory?).and_return(false)
- expect(Dir).not_to receive(:delete).with(@new_resource.path)
- expect { @directory.run_action(:delete) }.to raise_error(RuntimeError)
- expect(@directory.new_resource).not_to be_updated
- end
+ describe "when the target directory is a file" do
+ before do
+ FileUtils.rmdir tmp_dir
+ FileUtils.touch tmp_dir
+ end
- def stub_file_cstats
- cstats = double("stats")
- allow(cstats).to receive(:uid).and_return(500)
- allow(cstats).to receive(:gid).and_return(500)
- allow(cstats).to receive(:mode).and_return(0755)
- # File.stat is called in:
- # - Chef::Provider::File.load_current_resource_attrs
- # - Chef::ScanAccessControl via Chef::Provider::File.setup_acl
- allow(File).to receive(:stat).and_return(cstats)
+ it "cannot delete it and raises an exception" do
+ expect { directory.run_action(:delete) }.to raise_error(RuntimeError)
+ end
+ end
end
end
diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb
new file mode 100644
index 0000000000..65c1c019f0
--- /dev/null
+++ b/spec/unit/provider/dsc_resource_spec.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Jay Mundrawala (<jdm@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'
+require 'spec_helper'
+
+describe Chef::Provider::DscResource do
+ let (:events) { Chef::EventDispatch::Dispatcher.new }
+ let (:run_context) { Chef::RunContext.new(node, {}, events) }
+ let (:resource) { Chef::Resource::DscResource.new("dscresource", run_context) }
+ let (:provider) do
+ Chef::Provider::DscResource.new(resource, run_context)
+ end
+
+ context 'when Powershell does not support Invoke-DscResource' do
+ let (:node) {
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = '4.0'
+ node
+ }
+
+ it 'raises a ProviderNotFound exception' do
+ expect(provider).not_to receive(:meta_configuration)
+ expect{provider.run_action(:run)}.to raise_error(
+ Chef::Exceptions::ProviderNotFound, /5\.0\.10018\.0/)
+ end
+ end
+
+ context 'when Powershell supports Invoke-DscResource' do
+ let (:node) {
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = '5.0.10018.0'
+ node
+ }
+
+ context 'when RefreshMode is not set to Disabled' do
+ let (:meta_configuration) { {'RefreshMode' => 'AnythingElse'}}
+
+ it 'raises an exception' do
+ expect(provider).to receive(:meta_configuration).and_return(
+ meta_configuration)
+ expect { provider.run_action(:run) }.to raise_error(
+ Chef::Exceptions::ProviderNotFound, /Disabled/)
+ end
+ end
+
+ context 'when RefreshMode is set to Disabled' do
+ let (:meta_configuration) { {'RefreshMode' => 'Disabled'}}
+
+ it 'does not update the resource if it is up to date' do
+ expect(provider).to receive(:meta_configuration).and_return(
+ meta_configuration)
+ expect(provider).to receive(:test_resource).and_return(true)
+ provider.run_action(:run)
+ expect(resource).not_to be_updated
+ end
+
+ it 'converges the resource if it is not up to date' do
+ expect(provider).to receive(:meta_configuration).and_return(
+ meta_configuration)
+ expect(provider).to receive(:test_resource).and_return(false)
+ expect(provider).to receive(:set_resource)
+ provider.run_action(:run)
+ expect(resource).to be_updated
+ end
+ end
+ 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/execute_spec.rb b/spec/unit/provider/execute_spec.rb
index 51305b6225..1274203ce3 100644
--- a/spec/unit/provider/execute_spec.rb
+++ b/spec/unit/provider/execute_spec.rb
@@ -39,7 +39,7 @@ describe Chef::Provider::Execute do
let(:new_resource) { Chef::Resource::Execute.new("foo_resource", run_context) }
before do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
@original_log_level = Chef::Log.level
Chef::Log.level = :info
allow(STDOUT).to receive(:tty?).and_return(true)
diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb
index 351e734040..0c02ae9cd4 100644
--- a/spec/unit/provider/ifconfig/debian_spec.rb
+++ b/spec/unit/provider/ifconfig/debian_spec.rb
@@ -144,11 +144,6 @@ EOF
expect(IO.read(tempfile.path)).to eq(expected_string)
end
- it "should not mark the resource as updated" do
- provider.run_action(:add)
- pending "superclass ifconfig provider is not idempotent"
- expect(new_resource.updated_by_last_action?).to be_falsey
- end
end
context "when the /etc/network/interfaces file does not have the source line" do
@@ -280,11 +275,6 @@ another line
expect(IO.read(tempfile.path)).to eq(expected_string)
end
- it "should not mark the resource as updated" do
- provider.run_action(:add)
- pending "superclass ifconfig provider is not idempotent"
- expect(new_resource.updated_by_last_action?).to be_falsey
- end
end
context "when the /etc/network/interfaces file does not have the source line" do
diff --git a/spec/unit/provider/ifconfig_spec.rb b/spec/unit/provider/ifconfig_spec.rb
index 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/aix_spec.rb b/spec/unit/provider/package/aix_spec.rb
index 5bc861b849..13992cb8d1 100644
--- a/spec/unit/provider/package/aix_spec.rb
+++ b/spec/unit/provider/package/aix_spec.rb
@@ -36,23 +36,27 @@ describe Chef::Provider::Package::Aix do
@bffinfo ="/usr/lib/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX:
/etc/objrepos:samba.base:3.3.12.0::COMMITTED:I:Samba for AIX:"
- @status = double("Status", :stdout => "", :exitstatus => 0)
+ @empty_status = double("Status", :stdout => "", :exitstatus => 0)
end
it "should create a current resource with the name of new_resource" do
- allow(@provider).to receive(:shell_out).and_return(@status)
+ status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
+ expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+ expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
@provider.load_current_resource
expect(@provider.current_resource.name).to eq("samba.base")
end
it "should set the current resource bff package name to the new resource bff package name" do
- allow(@provider).to receive(:shell_out).and_return(@status)
+ status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
+ expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+ expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
@provider.load_current_resource
expect(@provider.current_resource.package_name).to eq("samba.base")
end
it "should raise an exception if a source is supplied but not found" do
- allow(@provider).to receive(:shell_out).and_return(@status)
+ allow(@provider).to receive(:shell_out).and_return(@empty_status)
allow(::File).to receive(:exists?).and_return(false)
@provider.load_current_resource
@provider.define_resource_requirements
@@ -61,8 +65,8 @@ describe Chef::Provider::Package::Aix do
it "should get the source package version from lslpp if provided" do
status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
- expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(status)
- expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+ expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
@provider.load_current_resource
expect(@provider.current_resource.package_name).to eq("samba.base")
@@ -73,8 +77,8 @@ describe Chef::Provider::Package::Aix do
status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
@stdout = StringIO.new(@bffinfo)
@stdin, @stderr = StringIO.new, StringIO.new
- expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(@status)
- expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(status)
+ expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+ expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(status)
@provider.load_current_resource
expect(@provider.current_resource.version).to eq("3.3.12.0")
end
@@ -94,12 +98,20 @@ describe Chef::Provider::Package::Aix do
end
it "should return a current resource with a nil version if the package is not found" do
- status = double(:stdout => "", :exitstatus => 0)
- expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base").and_return(status)
- expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base").and_return(status)
+ status = double("Status", :stdout => @bffinfo, :exitstatus => 0)
+ expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+ expect(@provider).to receive(:shell_out).with("lslpp -lcq samba.base", timeout: 900).and_return(@empty_status)
@provider.load_current_resource
expect(@provider.current_resource.version).to be_nil
end
+
+ it "should raise an exception if the source doesn't provide the requested package" do
+ wrongbffinfo = "/usr/lib/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer:
+/etc/objrepos:openssl.base:0.9.8.2400::COMMITTED:I:Open Secure Socket Layer:"
+ status = double("Status", :stdout => wrongbffinfo, :exitstatus => 0)
+ expect(@provider).to receive(:shell_out).with("installp -L -d /tmp/samba.base", timeout: 900).and_return(status)
+ expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package)
+ end
end
describe "candidate_version" do
@@ -125,7 +137,7 @@ describe Chef::Provider::Package::Aix do
describe "install and upgrade" do
it "should run installp -aYF -d with the package source to install" do
- expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base")
+ expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base samba.base", timeout: 900)
@provider.install_package("samba.base", "3.3.12.0")
end
@@ -133,26 +145,26 @@ describe Chef::Provider::Package::Aix do
@new_resource = Chef::Resource::Package.new("/tmp/samba.base")
@provider = Chef::Provider::Package::Aix.new(@new_resource, @run_context)
expect(@new_resource.source).to eq("/tmp/samba.base")
- expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base")
+ expect(@provider).to receive(:shell_out!).with("installp -aYF -d /tmp/samba.base /tmp/samba.base", timeout: 900)
@provider.install_package("/tmp/samba.base", "3.3.12.0")
end
it "should run installp with -eLogfile option." do
allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log")
- expect(@provider).to receive(:shell_out!).with("installp -aYF -e/tmp/installp.log -d /tmp/samba.base samba.base")
+ expect(@provider).to receive(:shell_out!).with("installp -aYF -e/tmp/installp.log -d /tmp/samba.base samba.base", timeout: 900)
@provider.install_package("samba.base", "3.3.12.0")
end
end
describe "remove" do
it "should run installp -u samba.base to remove the package" do
- expect(@provider).to receive(:shell_out!).with("installp -u samba.base")
+ expect(@provider).to receive(:shell_out!).with("installp -u samba.base", timeout: 900)
@provider.remove_package("samba.base", "3.3.12.0")
end
it "should run installp -u -e/tmp/installp.log with options -e/tmp/installp.log" do
allow(@new_resource).to receive(:options).and_return("-e/tmp/installp.log")
- expect(@provider).to receive(:shell_out!).with("installp -u -e/tmp/installp.log samba.base")
+ expect(@provider).to receive(:shell_out!).with("installp -u -e/tmp/installp.log samba.base", timeout: 900)
@provider.remove_package("samba.base", "3.3.12.0")
end
diff --git a/spec/unit/provider/package/dpkg_spec.rb b/spec/unit/provider/package/dpkg_spec.rb
index 3fd86218d2..b868128147 100644
--- a/spec/unit/provider/package/dpkg_spec.rb
+++ b/spec/unit/provider/package/dpkg_spec.rb
@@ -51,10 +51,9 @@ describe Chef::Provider::Package::Dpkg do
describe 'gets the source package version from dpkg-deb' do
def check_version(version)
@status = double(:stdout => "wget\t#{version}", :exitstatus => 0)
- allow(@provider).to receive(:shell_out).with("dpkg-deb -W #{@new_resource.source}").and_return(@status)
+ allow(@provider).to receive(:shell_out).with("dpkg-deb -W #{@new_resource.source}", timeout: 900).and_return(@status)
@provider.load_current_resource
expect(@provider.current_resource.package_name).to eq("wget")
- expect(@new_resource.version).to eq(version)
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
@@ -106,7 +113,7 @@ Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5)
Conflicts: wget-ssl
DPKG_S
status = double(:stdout => stdout, :exitstatus => 1)
- allow(@provider).to receive(:shell_out).with("dpkg -s wget").and_return(status)
+ allow(@provider).to receive(:shell_out).with("dpkg -s wget", timeout: 900).and_return(status)
@provider.load_current_resource
expect(@provider.current_resource.version).to eq("1.11.4-1ubuntu1")
diff --git a/spec/unit/provider/package/freebsd/pkg_spec.rb b/spec/unit/provider/package/freebsd/pkg_spec.rb
index f67161930f..d1f5a649bc 100644
--- a/spec/unit/provider/package/freebsd/pkg_spec.rb
+++ b/spec/unit/provider/package/freebsd/pkg_spec.rb
@@ -77,7 +77,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
it "should return the version number when it is installed" do
pkg_info = OpenStruct.new(:stdout => "zsh-4.3.6_7")
- expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+ expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info)
#@provider.should_receive(:popen4).with('pkg_info -E "zsh*"').and_yield(@pid, @stdin, ["zsh-4.3.6_7"], @stderr).and_return(@status)
allow(@provider).to receive(:package_name).and_return("zsh")
expect(@provider.current_installed_version).to eq("4.3.6_7")
@@ -85,14 +85,14 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
it "does not set the current version number when the package is not installed" do
pkg_info = OpenStruct.new(:stdout => "")
- expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+ expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info)
allow(@provider).to receive(:package_name).and_return("zsh")
expect(@provider.current_installed_version).to be_nil
end
it "should return the port path for a valid port name" do
whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
- expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+ expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
#@provider.should_receive(:popen4).with("whereis -s zsh").and_yield(@pid, @stdin, ["zsh: /usr/ports/shells/zsh"], @stderr).and_return(@status)
allow(@provider).to receive(:port_name).and_return("zsh")
expect(@provider.port_path).to eq("/usr/ports/shells/zsh")
@@ -102,7 +102,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
it "should return the ports candidate version when given a valid port path" do
allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh")
make_v = OpenStruct.new(:stdout => "4.3.6\n", :exitstatus => 0)
- expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", {:cwd=>"/usr/ports/shells/zsh", :returns=>[0, 1], :env=>nil}).and_return(make_v)
+ expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", {cwd: "/usr/ports/shells/zsh", returns: [0, 1], env: nil, timeout: 900}).and_return(make_v)
expect(@provider.ports_candidate_version).to eq("4.3.6")
end
@@ -110,7 +110,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
allow(::File).to receive(:exist?).with('/usr/ports/Makefile').and_return(true)
allow(@provider).to receive(:port_path).and_return("/usr/ports/shells/zsh")
make_v = OpenStruct.new(:stdout => "zsh-4.3.6_7\n", :exitstatus => 0)
- expect(@provider).to receive(:shell_out!).with("make -V PKGNAME", {:cwd=>"/usr/ports/shells/zsh", :env=>nil, :returns=>[0, 1]}).and_return(make_v)
+ expect(@provider).to receive(:shell_out!).with("make -V PKGNAME", {cwd: "/usr/ports/shells/zsh", env: nil, returns: [0, 1], timeout: 900}).and_return(make_v)
#@provider.should_receive(:ports_makefile_variable_value).with("PKGNAME").and_return("zsh-4.3.6_7")
expect(@provider.package_name).to eq("zsh")
end
@@ -127,7 +127,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
end
it "should run pkg_add -r with the package name" do
- expect(@provider).to receive(:shell_out!).with("pkg_add -r zsh", :env => nil).and_return(@cmd_result)
+ expect(@provider).to receive(:shell_out!).with("pkg_add -r zsh", env: nil, timeout: 900).and_return(@cmd_result)
@provider.install_package("zsh", "4.3.6_7")
end
end
@@ -142,7 +142,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
it "should figure out the port path from the package_name using whereis" do
whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh")
- expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env=>nil).and_return(whereis)
+ expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
expect(@provider.port_path).to eq("/usr/ports/shells/zsh")
end
@@ -178,7 +178,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
end
it "should run pkg_add -r with the package name" do
- expect(@provider).to receive(:shell_out!).with("pkg_add -r ruby18-iconv", :env => nil).and_return(@install_result)
+ expect(@provider).to receive(:shell_out!).with("pkg_add -r ruby18-iconv", env: nil, timeout: 900).and_return(@install_result)
@provider.install_package("ruby-iconv", "1.0")
end
end
@@ -193,7 +193,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
end
it "should run pkg_delete with the package name and version" do
- expect(@provider).to receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", :env => nil).and_return(@pkg_delete)
+ expect(@provider).to receive(:shell_out!).with("pkg_delete zsh-4.3.6_7", env: nil, timeout: 900).and_return(@pkg_delete)
@provider.remove_package("zsh", "4.3.6_7")
end
end
@@ -213,14 +213,14 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
it "should return the port path for a valid port name" do
whereis = OpenStruct.new(:stdout => "bonnie++: /usr/ports/benchmarks/bonnie++")
- expect(@provider).to receive(:shell_out!).with("whereis -s bonnie++", :env => nil).and_return(whereis)
+ expect(@provider).to receive(:shell_out!).with("whereis -s bonnie++", env: nil, timeout: 900).and_return(whereis)
allow(@provider).to receive(:port_name).and_return("bonnie++")
expect(@provider.port_path).to eq("/usr/ports/benchmarks/bonnie++")
end
it "should return the version number when it is installed" do
pkg_info = OpenStruct.new(:stdout => "bonnie++-1.96")
- expect(@provider).to receive(:shell_out!).with('pkg_info -E "bonnie++*"', :env => nil, :returns => [0,1]).and_return(pkg_info)
+ expect(@provider).to receive(:shell_out!).with('pkg_info -E "bonnie++*"', env: nil, returns: [0,1], timeout: 900).and_return(pkg_info)
allow(@provider).to receive(:package_name).and_return("bonnie++")
expect(@provider.current_installed_version).to eq("1.96")
end
@@ -253,7 +253,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
allow(@provider).to receive(:latest_link_name).and_return("perl")
cmd = OpenStruct.new(:status => true)
- expect(@provider).to receive(:shell_out!).with("pkg_add -r perl", :env => nil).and_return(cmd)
+ expect(@provider).to receive(:shell_out!).with("pkg_add -r perl", env: nil, timeout: 900).and_return(cmd)
@provider.install_package("perl5.8", "5.8.8_1")
end
@@ -267,7 +267,7 @@ describe Chef::Provider::Package::Freebsd::Pkg, "load_current_resource" do
allow(@provider).to receive(:latest_link_name).and_return("mysql50-server")
cmd = OpenStruct.new(:status => true)
- expect(@provider).to receive(:shell_out!).with("pkg_add -r mysql50-server", :env=>nil).and_return(cmd)
+ expect(@provider).to receive(:shell_out!).with("pkg_add -r mysql50-server", env: nil, timeout: 900).and_return(cmd)
@provider.install_package("mysql50-server", "5.0.45_1")
end
end
diff --git a/spec/unit/provider/package/freebsd/pkgng_spec.rb b/spec/unit/provider/package/freebsd/pkgng_spec.rb
index 0c1e89c7ab..59215f855b 100644
--- a/spec/unit/provider/package/freebsd/pkgng_spec.rb
+++ b/spec/unit/provider/package/freebsd/pkgng_spec.rb
@@ -71,7 +71,7 @@ describe Chef::Provider::Package::Freebsd::Port do
end
it "should query pkg database" do
- expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info)
+ expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info)
expect(@provider.current_installed_version).to eq("3.1.7")
end
end
@@ -80,14 +80,14 @@ describe Chef::Provider::Package::Freebsd::Port do
describe "determining candidate version" do
it "should query repository" do
pkg_query = OpenStruct.new(:stdout => "5.0.5\n", :exitstatus => 0)
- expect(@provider).to receive(:shell_out!).with("pkg rquery '%v' zsh", :env => nil).and_return(pkg_query)
+ expect(@provider).to receive(:shell_out!).with("pkg rquery '%v' zsh", env: nil, timeout: 900).and_return(pkg_query)
expect(@provider.candidate_version).to eq("5.0.5")
end
it "should query specified repository when given option" do
@provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration.
pkg_query = OpenStruct.new(:stdout => "5.0.3\n", :exitstatus => 0)
- expect(@provider).to receive(:shell_out!).with("pkg rquery -r LocalMirror '%v' zsh", :env => nil).and_return(pkg_query)
+ expect(@provider).to receive(:shell_out!).with("pkg rquery -r LocalMirror '%v' zsh", env: nil, timeout: 900).and_return(pkg_query)
expect(@provider.candidate_version).to eq("5.0.3")
end
@@ -106,7 +106,7 @@ describe Chef::Provider::Package::Freebsd::Port do
it "should handle package source from file" do
@provider.new_resource.source("/nas/pkg/repo/zsh-5.0.1.txz")
expect(@provider).to receive(:shell_out!).
- with("pkg add /nas/pkg/repo/zsh-5.0.1.txz", :env => { 'LC_ALL' => nil }).
+ with("pkg add /nas/pkg/repo/zsh-5.0.1.txz", env: { 'LC_ALL' => nil }, timeout: 900).
and_return(@install_result)
@provider.install_package("zsh", "5.0.1")
end
@@ -114,21 +114,21 @@ describe Chef::Provider::Package::Freebsd::Port do
it "should handle package source over ftp or http" do
@provider.new_resource.source("http://repo.example.com/zsh-5.0.1.txz")
expect(@provider).to receive(:shell_out!).
- with("pkg add http://repo.example.com/zsh-5.0.1.txz", :env => { 'LC_ALL' => nil }).
+ with("pkg add http://repo.example.com/zsh-5.0.1.txz", env: { 'LC_ALL' => nil }, timeout: 900).
and_return(@install_result)
@provider.install_package("zsh", "5.0.1")
end
it "should handle a package name" do
expect(@provider).to receive(:shell_out!).
- with("pkg install -y zsh", :env => { 'LC_ALL' => nil }).and_return(@install_result)
+ with("pkg install -y zsh", env: { 'LC_ALL' => nil }, timeout: 900).and_return(@install_result)
@provider.install_package("zsh", "5.0.1")
end
it "should handle a package name with a specified repo" do
@provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration.
expect(@provider).to receive(:shell_out!).
- with("pkg install -y -r LocalMirror zsh", :env => { 'LC_ALL' => nil }).and_return(@install_result)
+ with("pkg install -y -r LocalMirror zsh", env: { 'LC_ALL' => nil }, timeout: 900).and_return(@install_result)
@provider.install_package("zsh", "5.0.1")
end
end
@@ -141,14 +141,14 @@ describe Chef::Provider::Package::Freebsd::Port do
it "should call pkg delete" do
expect(@provider).to receive(:shell_out!).
- with("pkg delete -y zsh-5.0.1", :env => nil).and_return(@install_result)
+ with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result)
@provider.remove_package("zsh", "5.0.1")
end
it "should not include repo option in pkg delete" do
@provider.new_resource.options('-r LocalMirror') # This requires LocalMirror repo configuration.
expect(@provider).to receive(:shell_out!).
- with("pkg delete -y zsh-5.0.1", :env => nil).and_return(@install_result)
+ with("pkg delete -y zsh-5.0.1", env: nil, timeout: 900).and_return(@install_result)
@provider.remove_package("zsh", "5.0.1")
end
end
diff --git a/spec/unit/provider/package/freebsd/port_spec.rb b/spec/unit/provider/package/freebsd/port_spec.rb
index 2e32e88f97..4b23575740 100644
--- a/spec/unit/provider/package/freebsd/port_spec.rb
+++ b/spec/unit/provider/package/freebsd/port_spec.rb
@@ -72,7 +72,7 @@ describe Chef::Provider::Package::Freebsd::Port do
it "should check 'pkg_info' if system uses pkg_* tools" do
allow(@new_resource).to receive(:supports_pkgng?)
expect(@new_resource).to receive(:supports_pkgng?).and_return(false)
- expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', :env => nil, :returns => [0,1]).and_return(@pkg_info)
+ expect(@provider).to receive(:shell_out!).with('pkg_info -E "zsh*"', env: nil, returns: [0,1], timeout: 900).and_return(@pkg_info)
expect(@provider.current_installed_version).to eq("3.1.7")
end
@@ -80,8 +80,8 @@ describe Chef::Provider::Package::Freebsd::Port do
pkg_enabled = OpenStruct.new(:stdout => "yes\n")
[1000016, 1000000, 901503, 902506, 802511].each do |__freebsd_version|
@node.automatic_attrs[:os_version] = __freebsd_version
- expect(@new_resource).to receive(:shell_out!).with('make -V WITH_PKGNG', :env => nil).and_return(pkg_enabled)
- expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info)
+ expect(@new_resource).to receive(:shell_out!).with('make -V WITH_PKGNG', env: nil).and_return(pkg_enabled)
+ expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info)
expect(@provider.current_installed_version).to eq("3.1.7")
end
end
@@ -89,7 +89,7 @@ describe Chef::Provider::Package::Freebsd::Port do
it "should check 'pkg info' if the freebsd version is greater than or equal to 1000017" do
__freebsd_version = 1000017
@node.automatic_attrs[:os_version] = __freebsd_version
- expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', :env => nil, :returns => [0,70]).and_return(@pkg_info)
+ expect(@provider).to receive(:shell_out!).with('pkg info "zsh"', env: nil, returns: [0,70], timeout: 900).and_return(@pkg_info)
expect(@provider.current_installed_version).to eq("3.1.7")
end
end
@@ -102,7 +102,7 @@ describe Chef::Provider::Package::Freebsd::Port do
it "should return candidate version if port exists" do
allow(::File).to receive(:exist?).with('/usr/ports/Makefile').and_return(true)
allow(@provider).to receive(:port_dir).and_return('/usr/ports/shells/zsh')
- expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", :cwd => "/usr/ports/shells/zsh", :env => nil, :returns => [0,1]).
+ expect(@provider).to receive(:shell_out!).with("make -V PORTVERSION", cwd: "/usr/ports/shells/zsh", env: nil, returns: [0,1], timeout: 900).
and_return(@port_version)
expect(@provider.candidate_version).to eq("5.0.5")
end
@@ -127,13 +127,13 @@ describe Chef::Provider::Package::Freebsd::Port do
it "should query system for path given just a name" do
whereis = OpenStruct.new(:stdout => "zsh: /usr/ports/shells/zsh\n")
- expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+ expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
expect(@provider.port_dir).to eq("/usr/ports/shells/zsh")
end
it "should raise exception if not found" do
whereis = OpenStruct.new(:stdout => "zsh:\n")
- expect(@provider).to receive(:shell_out!).with("whereis -s zsh", :env => nil).and_return(whereis)
+ expect(@provider).to receive(:shell_out!).with("whereis -s zsh", env: nil, timeout: 900).and_return(whereis)
expect { @provider.port_dir }.to raise_error(Chef::Exceptions::Package, "Could not find port with the name zsh")
end
end
diff --git a/spec/unit/provider/package/ips_spec.rb b/spec/unit/provider/package/ips_spec.rb
index 342ac4c040..ad69dffb10 100644
--- a/spec/unit/provider/package/ips_spec.rb
+++ b/spec/unit/provider/package/ips_spec.rb
@@ -65,28 +65,28 @@ PKG_STATUS
context "when loading current resource" do
it "should create a current resource with the name of the new_resource" do
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
@provider.load_current_resource
end
it "should set the current resources package name to the new resources package name" do
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
@provider.load_current_resource
expect(@current_resource.package_name).to eq(@new_resource.package_name)
end
it "should run pkg info with the package name" do
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
@provider.load_current_resource
end
it "should set the installed version to nil on the current resource if package state is not installed" do
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
@provider.load_current_resource
expect(@current_resource.version).to be_nil
end
@@ -108,27 +108,27 @@ Packaging Date: October 19, 2011 09:14:50 AM
Size: 8.07 MB
FMRI: pkg://solaris/crypto/gnupg@2.0.17,5.11-0.175.0.0.0.2.537:20111019T091450Z
INSTALLED
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
@provider.load_current_resource
expect(@current_resource.version).to eq("2.0.17")
end
it "should return the current resource" do
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote_output)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote_output)
expect(@provider.load_current_resource).to eql(@current_resource)
end
end
context "when installing a package" do
it "should run pkg install with the package name and version" do
- expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17")
+ expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17", timeout: 900)
@provider.install_package("crypto/gnupg", "2.0.17")
end
it "should run pkg install with the package name and version and options if specified" do
- expect(@provider).to receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg@2.0.17")
+ expect(@provider).to receive(:shell_out).with("pkg --no-refresh install -q crypto/gnupg@2.0.17", timeout: 900)
allow(@new_resource).to receive(:options).and_return("--no-refresh")
@provider.install_package("crypto/gnupg", "2.0.17")
end
@@ -147,8 +147,8 @@ Packaging Date: April 1, 2012 05:55:52 PM
Size: 2.57 MB
FMRI: pkg://omnios/security/sudo@1.8.4.1,5.11-0.151002:20120401T175552Z
PKG_STATUS
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local_output)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local_output)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote)
@provider.load_current_resource
expect(@current_resource.version).to be_nil
expect(@provider.candidate_version).to eql("1.8.4.1")
@@ -188,8 +188,8 @@ Packaging Date: October 19, 2011 09:14:50 AM
FMRI: pkg://solaris/crypto/gnupg@2.0.18,5.11-0.175.0.0.0.2.537:20111019T091450Z
REMOTE
- expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}").and_return(local)
- expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}").and_return(remote)
+ expect(@provider).to receive(:shell_out).with("pkg info #{@new_resource.package_name}", timeout: 900).and_return(local)
+ expect(@provider).to receive(:shell_out!).with("pkg info -r #{@new_resource.package_name}", timeout: 900).and_return(remote)
expect(@provider).to receive(:install_package).exactly(0).times
@provider.run_action(:install)
end
@@ -200,7 +200,7 @@ REMOTE
end
it "should run pkg install with the --accept flag" do
- expect(@provider).to receive(:shell_out).with("pkg install -q --accept crypto/gnupg@2.0.17")
+ expect(@provider).to receive(:shell_out).with("pkg install -q --accept crypto/gnupg@2.0.17", timeout: 900)
@provider.install_package("crypto/gnupg", "2.0.17")
end
end
@@ -208,19 +208,19 @@ REMOTE
context "when upgrading a package" do
it "should run pkg install with the package name and version" do
- expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17")
+ expect(@provider).to receive(:shell_out).with("pkg install -q crypto/gnupg@2.0.17", timeout: 900)
@provider.upgrade_package("crypto/gnupg", "2.0.17")
end
end
context "when uninstalling a package" do
it "should run pkg uninstall with the package name and version" do
- expect(@provider).to receive(:shell_out!).with("pkg uninstall -q crypto/gnupg@2.0.17")
+ expect(@provider).to receive(:shell_out!).with("pkg uninstall -q crypto/gnupg@2.0.17", timeout: 900)
@provider.remove_package("crypto/gnupg", "2.0.17")
end
it "should run pkg uninstall with the package name and version and options if specified" do
- expect(@provider).to receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg@2.0.17")
+ expect(@provider).to receive(:shell_out!).with("pkg --no-refresh uninstall -q crypto/gnupg@2.0.17", timeout: 900)
allow(@new_resource).to receive(:options).and_return("--no-refresh")
@provider.remove_package("crypto/gnupg", "2.0.17")
end
diff --git a/spec/unit/provider/package/macports_spec.rb b/spec/unit/provider/package/macports_spec.rb
index 9822fb3928..eef84113b4 100644
--- a/spec/unit/provider/package/macports_spec.rb
+++ b/spec/unit/provider/package/macports_spec.rb
@@ -105,7 +105,7 @@ EOF
it "should run the port install command with the correct version" do
expect(@current_resource).to receive(:version).and_return("4.1.6")
@provider.current_resource = @current_resource
- expect(@provider).to receive(:shell_out!).with("port install zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port install zsh @4.2.7", timeout: 900)
@provider.install_package("zsh", "4.2.7")
end
@@ -122,7 +122,7 @@ EOF
expect(@current_resource).to receive(:version).and_return("4.1.6")
@provider.current_resource = @current_resource
allow(@new_resource).to receive(:options).and_return("-f")
- expect(@provider).to receive(:shell_out!).with("port -f install zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port -f install zsh @4.2.7", timeout: 900)
@provider.install_package("zsh", "4.2.7")
end
@@ -130,36 +130,36 @@ EOF
describe "purge_package" do
it "should run the port uninstall command with the correct version" do
- expect(@provider).to receive(:shell_out!).with("port uninstall zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port uninstall zsh @4.2.7", timeout: 900)
@provider.purge_package("zsh", "4.2.7")
end
it "should purge the currently active version if no explicit version is passed in" do
- expect(@provider).to receive(:shell_out!).with("port uninstall zsh")
+ expect(@provider).to receive(:shell_out!).with("port uninstall zsh", timeout: 900)
@provider.purge_package("zsh", nil)
end
it "should add options to the port command when specified" do
allow(@new_resource).to receive(:options).and_return("-f")
- expect(@provider).to receive(:shell_out!).with("port -f uninstall zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port -f uninstall zsh @4.2.7", timeout: 900)
@provider.purge_package("zsh", "4.2.7")
end
end
describe "remove_package" do
it "should run the port deactivate command with the correct version" do
- expect(@provider).to receive(:shell_out!).with("port deactivate zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port deactivate zsh @4.2.7", timeout: 900)
@provider.remove_package("zsh", "4.2.7")
end
it "should remove the currently active version if no explicit version is passed in" do
- expect(@provider).to receive(:shell_out!).with("port deactivate zsh")
+ expect(@provider).to receive(:shell_out!).with("port deactivate zsh", timeout: 900)
@provider.remove_package("zsh", nil)
end
it "should add options to the port command when specified" do
allow(@new_resource).to receive(:options).and_return("-f")
- expect(@provider).to receive(:shell_out!).with("port -f deactivate zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port -f deactivate zsh @4.2.7", timeout: 900)
@provider.remove_package("zsh", "4.2.7")
end
end
@@ -169,7 +169,7 @@ EOF
expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6")
@provider.current_resource = @current_resource
- expect(@provider).to receive(:shell_out!).with("port upgrade zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port upgrade zsh @4.2.7", timeout: 900)
@provider.upgrade_package("zsh", "4.2.7")
end
@@ -195,7 +195,7 @@ EOF
expect(@current_resource).to receive(:version).at_least(:once).and_return("4.1.6")
@provider.current_resource = @current_resource
- expect(@provider).to receive(:shell_out!).with("port -f upgrade zsh @4.2.7")
+ expect(@provider).to receive(:shell_out!).with("port -f upgrade zsh @4.2.7", timeout: 900)
@provider.upgrade_package("zsh", "4.2.7")
end
diff --git a/spec/unit/provider/package/openbsd_spec.rb b/spec/unit/provider/package/openbsd_spec.rb
index ee9c9e89fb..8407f83785 100644
--- a/spec/unit/provider/package/openbsd_spec.rb
+++ b/spec/unit/provider/package/openbsd_spec.rb
@@ -21,28 +21,95 @@ require 'ostruct'
describe Chef::Provider::Package::Openbsd do
+ let(:node) do
+ node = Chef::Node.new
+ node.default['kernel'] = {'name' => 'OpenBSD', 'release' => '5.5', 'machine' => 'amd64'}
+ node
+ end
+
+ let (:provider) do
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ Chef::Provider::Package::Openbsd.new(new_resource, run_context)
+ end
+
+ let(:new_resource) { Chef::Resource::Package.new(name)}
+
before(:each) do
- @node = Chef::Node.new
- @node.default['kernel'] = {'name' => 'OpenBSD', 'release' => '5.5', 'machine' => 'amd64'}
- @events = Chef::EventDispatch::Dispatcher.new
- @run_context = Chef::RunContext.new(@node, {}, @events)
ENV['PKG_PATH'] = nil
end
describe "install a package" do
- before do
- @name = 'ihavetoes'
- @new_resource = Chef::Resource::Package.new(@name)
- @current_resource = Chef::Resource::Package.new(@name)
- @provider = Chef::Provider::Package::Openbsd.new(@new_resource, @run_context)
- @provider.current_resource = @current_resource
- end
- it "should run the installation command" do
- expect(@provider).to receive(:shell_out!).with(
- "pkg_add -r #{@name}",
- {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}}
- ) {OpenStruct.new :status => true}
- @provider.install_package(@name, nil)
+ let(:name) { 'ihavetoes' }
+ let(:version) {'0.0'}
+
+ context 'when not already installed' do
+ before do
+ allow(provider).to receive(:shell_out!).with("pkg_info -e \"#{name}->0\"", anything()).and_return(instance_double('shellout', :stdout => ''))
+ end
+
+ context 'when there is a single candidate' do
+
+ context 'when source is not provided' do
+ it 'should run the installation command' do
+ expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return(
+ instance_double('shellout', :stdout => "#{name}-#{version}\n"))
+ expect(provider).to receive(:shell_out!).with(
+ "pkg_add -r #{name}-#{version}",
+ {:env => {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900}
+ ) {OpenStruct.new :status => true}
+ provider.run_action(:install)
+ end
+ end
+ end
+
+ context 'when there are multiple candidates' do
+ let(:flavor_a) { 'flavora' }
+ let(:flavor_b) { 'flavorb' }
+
+ context 'if no version is specified' do
+ it 'should raise an exception' do
+ expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return(
+ instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor_a}\n#{name}-#{version}-#{flavor_b}\n"))
+ expect { provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package, /multiple matching candidates/)
+ end
+ end
+
+ context 'if a flavor is specified' do
+
+ let(:flavor) { 'flavora' }
+ let(:package_name) {'ihavetoes' }
+ let(:name) { "#{package_name}--#{flavor}" }
+
+ context 'if no version is specified' do
+ it 'should run the installation command' do
+ expect(provider).to receive(:shell_out!).with("pkg_info -e \"#{package_name}->0\"", anything()).and_return(instance_double('shellout', :stdout => ''))
+ expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}\"", anything()).and_return(
+ instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor}\n"))
+ expect(provider).to receive(:shell_out!).with(
+ "pkg_add -r #{name}-#{version}-#{flavor}",
+ {env: {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900}
+ ) {OpenStruct.new :status => true}
+ provider.run_action(:install)
+ end
+ end
+
+ end
+
+ context 'if a version is specified' do
+ it 'should use the flavor from the version' do
+ expect(provider).to receive(:shell_out!).with("pkg_info -I \"#{name}-#{version}-#{flavor_b}\"", anything()).and_return(
+ instance_double('shellout', :stdout => "#{name}-#{version}-#{flavor_a}\n"))
+
+ new_resource.version("#{version}-#{flavor_b}")
+ expect(provider).to receive(:shell_out!).with(
+ "pkg_add -r #{name}-#{version}-#{flavor_b}",
+ {env: {"PKG_PATH" => "http://ftp.OpenBSD.org/pub/OpenBSD/5.5/packages/amd64/"}, timeout: 900}
+ ) {OpenStruct.new :status => true}
+ provider.run_action(:install)
+ end
+ end
+ end
end
end
@@ -56,11 +123,10 @@ describe Chef::Provider::Package::Openbsd do
end
it "should run the command to delete the installed package" do
expect(@provider).to receive(:shell_out!).with(
- "pkg_delete #{@name}", :env=>nil
+ "pkg_delete #{@name}", env: nil, timeout: 900
) {OpenStruct.new :status => true}
@provider.remove_package(@name, nil)
end
end
end
-
diff --git a/spec/unit/provider/package/pacman_spec.rb b/spec/unit/provider/package/pacman_spec.rb
index 3b8848c41b..fcb9f8a86c 100644
--- a/spec/unit/provider/package/pacman_spec.rb
+++ b/spec/unit/provider/package/pacman_spec.rb
@@ -51,7 +51,7 @@ ERR
end
it "should run pacman query with the package name" do
- expect(@provider).to receive(:shell_out).with("pacman -Qi #{@new_resource.package_name}").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("pacman -Qi #{@new_resource.package_name}", {timeout: 900}).and_return(@status)
@provider.load_current_resource
end
@@ -152,12 +152,12 @@ PACMAN_CONF
describe Chef::Provider::Package::Pacman, "install_package" do
it "should run pacman install with the package name and version" do
- expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano")
+ expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar nano", {timeout: 900})
@provider.install_package("nano", "1.0")
end
it "should run pacman install with the package name and version and options if specified" do
- expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano")
+ expect(@provider).to receive(:shell_out!).with("pacman --sync --noconfirm --noprogressbar --debug nano", {timeout: 900})
allow(@new_resource).to receive(:options).and_return("--debug")
@provider.install_package("nano", "1.0")
@@ -173,12 +173,12 @@ PACMAN_CONF
describe Chef::Provider::Package::Pacman, "remove_package" do
it "should run pacman remove with the package name" do
- expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano")
+ expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar nano", {timeout: 900})
@provider.remove_package("nano", "1.0")
end
it "should run pacman remove with the package name and options if specified" do
- expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano")
+ expect(@provider).to receive(:shell_out!).with("pacman --remove --noconfirm --noprogressbar --debug nano", {timeout: 900})
allow(@new_resource).to receive(:options).and_return("--debug")
@provider.remove_package("nano", "1.0")
diff --git a/spec/unit/provider/package/rpm_spec.rb b/spec/unit/provider/package/rpm_spec.rb
index 411afd3755..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").and_return(status)
- expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++").and_return(status)
- provider.load_current_resource
- expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
- expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+ it "should raise an exception when attempting any action" do
+ allow(::File).to receive(:exists?).with(package_source).and_return(false)
+ expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
end
+ end
- it "should return the current version installed if found by rpm" do
- expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm").and_return(status)
- expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' ImageMagick-c++").and_return(status)
- provider.load_current_resource
- expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+ context "when the source is an unsupported URI scheme" do
+
+ let(:package_source) { "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+ it "should raise an exception if an uri formed source is non-supported scheme" do
+ allow(::File).to receive(:exists?).with(package_source).and_return(false)
+
+ # verify let bindings are as we expect
+ expect(new_resource.source).to eq("foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ expect(provider.load_current_resource).to be_nil
+ expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
end
end
- context "source is uri formed" do
- before(:each) do
- allow(::File).to receive(:exists?).and_return(false)
+ end
+
+ describe "when the package source is valid" do
+
+ before do
+ expect(provider).to receive(:shell_out!).
+ with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_source}", timeout: 900).
+ and_return(rpm_qp_status)
+
+ expect(provider).to receive(:shell_out).
+ with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{package_name}", timeout: 900).
+ and_return(rpm_q_status)
+ end
+
+ context "when rpm fails when querying package installed state" do
+
+ before do
+ allow(::File).to receive(:exists?).with(package_source).and_return(true)
end
- %w(http HTTP https HTTPS ftp FTP).each do |scheme|
- it "should accept uri formed source (#{scheme})" do
- new_resource.source "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
- expect(provider.load_current_resource).not_to be_nil
+ let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+ let(:rpm_q_stdout) { "" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { -1 }
+
+ it "raises an exception when attempting any action" do
+ expected_message = "Unable to determine current version due to RPM failure."
+
+ expect { provider.run_action(:install) }.to raise_error do |error|
+ expect(error).to be_a_kind_of(Chef::Exceptions::Package)
+ expect(error.to_s).to include(expected_message)
end
end
+ end
+
+
+ context "when the package is installed" do
+
+ let(:rpm_qp_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+ let(:rpm_q_stdout) { "ImageMagick-c++ 6.5.4.7-7.el6_5" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
- %w(file FILE).each do |scheme|
- it "should accept uri formed source (#{scheme})" do
- new_resource.source "#{scheme}:///ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
- expect(provider.load_current_resource).not_to be_nil
+ let(:action) { :install }
+
+ context "when the source is a file system path" do
+
+ before do
+ allow(::File).to receive(:exists?).with(package_source).and_return(true)
+
+ provider.action = action
+
+ provider.load_current_resource
+ provider.define_resource_requirements
+ provider.process_resource_requirements
end
- end
- it "should raise an exception if an uri formed source is non-supported scheme" do
- new_resource.source "foobar://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm"
- expect(provider.load_current_resource).to be_nil
- expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
- end
- end
+ it "should get the source package version from rpm if provided" do
+ expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
+ expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
- context "source is not defiend" do
- let(:new_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+ it "should return the current version installed if found by rpm" do
+ expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
+
+ describe "action install" do
+
+ context "when at the desired version already" do
+ it "does nothing when the correct version is installed" do
+ expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+
+ provider.action_install
+ end
+ end
+
+ context "when a newer version is desired" do
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" }
+
+ it "runs rpm -u with the package source to upgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_install
+ end
+ end
+
+ context "when an older version is desired" do
+ let(:new_resource) do
+ Chef::Resource::RpmPackage.new(package_name).tap do |r|
+ r.source(package_source)
+ r.allow_downgrade(true)
+ end
+ end
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" }
+
+ it "should run rpm -u --oldpackage with the package source to downgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_install
+ end
+
+ end
+
+ end
+
+ describe "action upgrade" do
+
+ let(:action) { :upgrade }
+
+ context "when at the desired version already" do
+ it "does nothing when the correct version is installed" do
+ expect(provider).to_not receive(:shell_out!).with("rpm -i /tmp/imagemagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+
+ provider.action_upgrade
+ end
+ end
+
+ context "when a newer version is desired" do
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 0.5.4.7-7.el6_5" }
+
+ it "runs rpm -u with the package source to upgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_upgrade
+ end
+ end
+
+ context "when an older version is desired" do
+ let(:new_resource) do
+ Chef::Resource::RpmPackage.new(package_name).tap do |r|
+ r.source(package_source)
+ r.allow_downgrade(true)
+ end
+ end
+
+ let(:rpm_q_stdout) { "imagemagick-c++ 21.4-19.el6_5" }
+
+ it "should run rpm -u --oldpackage with the package source to downgrade" do
+ expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.action_upgrade
+ end
+
+ end
+ end
+
+ describe "action :remove" do
+
+ let(:action) { :remove }
+
+ it "should remove the package" do
+ expect(provider).to receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900)
+ provider.action_remove
+ end
+ end
+
+
+ context "when the package name contains a tilde (chef#3503)" do
+
+ let(:package_name) { "supermarket" }
+
+ let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" }
+
+ let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+ let(:rpm_q_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
+
+ it "should correctly determine the candidate version and installed version" do
+ expect(provider.current_resource.package_name).to eq("supermarket")
+ expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5")
+ end
+ end
- it "should raise an exception if the source is not set but we are installing" do
- expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
end
- end
- context "installation does not exist" do
- let(:stdout) { String.new("package openssh-askpass is not installed") }
- let(:exitstatus) { -1 }
- let(:new_resource) do
- Chef::Resource::Package.new("openssh-askpass").tap do |resource|
- resource.source "openssh-askpass"
+ context "when the source is given as an URI" do
+ before(:each) do
+ allow(::File).to receive(:exists?).with(package_source).and_return(false)
+
+ provider.action = action
+
+ provider.load_current_resource
+ provider.define_resource_requirements
+ provider.process_resource_requirements
+ end
+
+ %w(http HTTP https HTTPS ftp FTP file FILE).each do |scheme|
+
+ context "when the source URI uses protocol scheme '#{scheme}'" do
+
+ let(:package_source) { "#{scheme}://example.com/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm" }
+
+ it "should get the source package version from rpm if provided" do
+ expect(provider.current_resource.package_name).to eq("ImageMagick-c++")
+ expect(provider.new_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
+
+ it "should return the current version installed if found by rpm" do
+ expect(provider.current_resource.version).to eq("6.5.4.7-7.el6_5")
+ end
+
+ end
end
+
end
- it "should raise an exception if rpm fails to run" do
- allow(provider).to receive(:shell_out).and_return(status)
- expect { provider.run_action(:any) }.to raise_error(Chef::Exceptions::Package)
+ end
+
+ context "when the package is not installed" do
+
+ let(:package_name) { "openssh-askpass" }
+
+ let(:package_source) { "/tmp/openssh-askpass-1.2.3-4.el6_5.x86_64.rpm" }
+
+ let(:rpm_qp_stdout) { "openssh-askpass 1.2.3-4.el6_5" }
+ let(:rpm_q_stdout) { "package openssh-askpass is not installed" }
+
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
+
+ let(:action) { :install }
+
+ before do
+ allow(File).to receive(:exists?).with(package_source).and_return(true)
+
+ provider.action = action
+
+ provider.load_current_resource
+ provider.define_resource_requirements
+ provider.process_resource_requirements
end
it "should not detect the package name as version when not installed" do
- expect(provider).to receive(:shell_out!).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_return(status)
- expect(provider).to receive(:shell_out).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_return(status)
- provider.load_current_resource
expect(provider.current_resource.version).to be_nil
end
- end
- end
- describe "after the current resource is loaded" do
- let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
- let(:provider) do
- Chef::Provider::Package::Rpm.new(new_resource, run_context).tap do |provider|
- provider.current_resource = current_resource
- end
- end
+ context "when the package name contains a tilde (chef#3503)" do
- describe "when installing or upgrading" do
- it "should run rpm -i with the package source to install" do
- expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.install_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ let(:package_name) { "supermarket" }
- it "should run rpm -U with the package source to upgrade" do
- current_resource.version("21.4-19.el5")
- expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ let(:package_source) { "/tmp/supermarket-1.10.1~alpha.0-1.el5.x86_64.rpm" }
- it "should install package if missing and set to upgrade" do
- current_resource.version("ImageMagick-c++")
- expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ let(:rpm_qp_stdout) { "supermarket 1.10.1~alpha.0-1.el5" }
+ let(:rpm_q_stdout) { "package supermarket is not installed" }
- context "allowing downgrade" do
- let(:new_resource) { Chef::Resource::RpmPackage.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") }
- let(:current_resource) { Chef::Resource::RpmPackage.new("ImageMagick-c++") }
+ let(:rpm_qp_exitstatus) { 0 }
+ let(:rpm_q_exitstatus) { 0 }
- it "should run rpm -U --oldpackage with the package source to downgrade" do
- new_resource.allow_downgrade(true)
- current_resource.version("21.4-19.el5")
- expect(provider).to receive(:shell_out!).with("rpm -U --oldpackage /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.upgrade_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
+ it "should correctly determine the candidate version" do
+ expect(provider.new_resource.version).to eq("1.10.1~alpha.0-1.el5")
end
end
- context "installing when the name is a path" do
- let(:new_resource) { Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm") }
- let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+ describe "managing the package" do
+
+ describe "action install" do
+
+ it "installs the package" do
+ expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900)
- it "should install from a path when the package is a path and the source is nil" do
- expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.current_resource = current_resource
- expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+ provider.action_install
+ end
+
+ context "when custom resource options are given" do
+ it "installs with custom options specified in the resource" do
+ new_resource.options("--dbpath /var/lib/rpm")
+ expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i #{package_source}", timeout: 900)
+ provider.action_install
+ end
+ end
end
- it "should uprgrade from a path when the package is a path and the source is nil" do
- expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- current_resource.version("21.4-19.el5")
- provider.current_resource = current_resource
- expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+ describe "action upgrade" do
+
+ let(:action) { :upgrade }
+
+ it "installs the package" do
+ expect(provider).to receive(:shell_out!).with("rpm -i #{package_source}", timeout: 900)
+
+ provider.action_upgrade
+ end
+ end
+
+ describe "when removing the package" do
+
+ let(:action) { :remove }
+
+ it "should do nothing" do
+ expect(provider).to_not receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5", timeout: 900)
+ provider.action_remove
+ end
end
- end
- it "installs with custom options specified in the resource" do
- provider.candidate_version = '11'
- new_resource.options("--dbpath /var/lib/rpm")
- expect(provider).to receive(:shell_out!).with("rpm --dbpath /var/lib/rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
- provider.install_package(new_resource.name, provider.candidate_version)
end
+
+
end
+ end
- describe "when removing the package" do
- it "should run rpm -e to remove the package" do
- expect(provider).to receive(:shell_out!).with("rpm -e ImageMagick-c++-6.5.4.7-7.el6_5")
- provider.remove_package("ImageMagick-c++", "6.5.4.7-7.el6_5")
- end
+ context "when the resource name is the path to the package" do
+
+ let(:new_resource) do
+ # When we pass a source in as the name, then #initialize in the
+ # provider will call File.exists?. Because of the ordering in our
+ # let() bindings and such, we have to set the stub here and not in a
+ # before block.
+ allow(::File).to receive(:exists?).with(package_source).and_return(true)
+ Chef::Resource::Package.new("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ end
+
+ let(:current_resource) { Chef::Resource::Package.new("ImageMagick-c++") }
+
+ it "should install from a path when the package is a path and the source is nil" do
+ expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ provider.current_resource = current_resource
+ expect(provider).to receive(:shell_out!).with("rpm -i /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.install_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
+ end
+
+ it "should uprgrade from a path when the package is a path and the source is nil" do
+ expect(new_resource.source).to eq("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm")
+ current_resource.version("21.4-19.el5")
+ provider.current_resource = current_resource
+ expect(provider).to receive(:shell_out!).with("rpm -U /tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", timeout: 900)
+ provider.upgrade_package("/tmp/ImageMagick-c++-6.5.4.7-7.el6_5.x86_64.rpm", "6.5.4.7-7.el6_5")
end
end
+
+
end
+
diff --git a/spec/unit/provider/package/rubygems_spec.rb b/spec/unit/provider/package/rubygems_spec.rb
index b17c216ddd..f790bdb1ce 100644
--- a/spec/unit/provider/package/rubygems_spec.rb
+++ b/spec/unit/provider/package/rubygems_spec.rb
@@ -107,38 +107,6 @@ describe Chef::Provider::Package::Rubygems::CurrentGemEnvironment do
expect(@gem_env.candidate_version_from_remote(Gem::Dependency.new('rspec', '>= 0'))).to eq(Gem::Version.new('1.3.0'))
end
- context "when rubygems was upgraded from 1.8->2.0" do
- # https://github.com/rubygems/rubygems/issues/404
- # tl;dr rubygems 1.8 and 2.0 can both be in the load path, which means that
- # require "rubygems/format" will load even though rubygems 2.0 doesn't have
- # that file.
-
- before do
- if defined?(Gem::Format)
- # tests are running under rubygems 1.8, or 2.0 upgraded from 1.8
- @remove_gem_format = false
- else
- Gem.const_set(:Format, Object.new)
- @remove_gem_format = true
- end
- allow(Gem::Package).to receive(:respond_to?).and_call_original
- allow(Gem::Package).to receive(:respond_to?).with(:open).and_return(false)
- end
-
- after do
- if @remove_gem_format
- Gem.send(:remove_const, :Format)
- end
- end
-
- it "finds a matching gem candidate version on rubygems 2.0+ with some rubygems 1.8 code loaded" do
- package = double("Gem::Package", :spec => "a gemspec from package")
- expect(Gem::Package).to receive(:new).with("/path/to/package.gem").and_return(package)
- expect(@gem_env.spec_from_file("/path/to/package.gem")).to eq("a gemspec from package")
- end
-
- end
-
it "gives the candidate version as nil if none is found" do
dep = Gem::Dependency.new('rspec', '>= 0')
latest = []
@@ -222,8 +190,6 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
end
it "uses the cached result for gem paths when available" do
- gem_env_output = ['/path/to/gems', '/another/path/to/gems'].join(File::PATH_SEPARATOR)
- shell_out_result = OpenStruct.new(:stdout => gem_env_output)
expect(@gem_env).not_to receive(:shell_out!)
expected = ['/path/to/gems', '/another/path/to/gems']
Chef::Provider::Package::Rubygems::AlternateGemEnvironment.gempath_cache['/usr/weird/bin/gem']= expected
@@ -261,7 +227,7 @@ describe Chef::Provider::Package::Rubygems::AlternateGemEnvironment do
else
`which gem`.strip
end
- pending("cant find your gem executable") if path_to_gem.empty?
+ skip("cant find your gem executable") if path_to_gem.empty?
gem_env = Chef::Provider::Package::Rubygems::AlternateGemEnvironment.new(path_to_gem)
expected = ['rspec-core', Gem::Version.new(RSpec::Core::Version::STRING)]
actual = gem_env.installed_versions(Gem::Dependency.new('rspec-core', nil)).map { |s| [s.name, s.version] }
@@ -403,6 +369,24 @@ describe Chef::Provider::Package::Rubygems do
expect(provider.gem_env.gem_binary_location).to eq('/usr/weird/bin/gem')
end
+ it "recognizes chef as omnibus" do
+ allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ expect(provider.is_omnibus?).to be true
+ end
+
+ it "recognizes opscode as omnibus" do
+ allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/opscode/embedded/bin")
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ expect(provider.is_omnibus?).to be true
+ end
+
+ it "recognizes chefdk as omnibus" do
+ allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chefdk/embedded/bin")
+ provider = Chef::Provider::Package::Rubygems.new(@new_resource, @run_context)
+ expect(provider.is_omnibus?).to be true
+ end
+
it "searches for a gem binary when running on Omnibus on Unix" do
platform_mock :unix do
allow(RbConfig::CONFIG).to receive(:[]).with('bindir').and_return("/opt/chef/embedded/bin")
@@ -542,7 +526,26 @@ describe Chef::Provider::Package::Rubygems do
it "installs the gem by shelling out when options are provided as a String" do
@new_resource.options('-i /alt/install/location')
expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" -i /alt/install/location"
- expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
+ expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "installs the gem with rubygems.org as an added source" do
+ @new_resource.gem_binary('/foo/bar')
+ @new_resource.source('http://mirror.ops.rhcloud.com/mirror/ruby')
+ expected ="/foo/bar install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" --source=#{@new_resource.source} --source=https://rubygems.org"
+ expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
+ @provider.run_action(:install)
+ expect(@new_resource).to be_updated_by_last_action
+ end
+
+ it "installs the gem with cleared sources and explict source when specified" do
+ @new_resource.gem_binary('/foo/bar')
+ @new_resource.source('http://mirror.ops.rhcloud.com/mirror/ruby')
+ @new_resource.clear_sources(true)
+ expected ="/foo/bar install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\" --clear-sources --source=#{@new_resource.source}"
+ expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
@provider.run_action(:install)
expect(@new_resource).to be_updated_by_last_action
end
@@ -553,7 +556,7 @@ describe Chef::Provider::Package::Rubygems do
it "installs the gem by shelling out when options are provided but no version is given" do
@new_resource.options('-i /alt/install/location')
expected ="gem install rspec-core -q --no-rdoc --no-ri -v \"#{@provider.candidate_version}\" -i /alt/install/location"
- expect(@provider).to receive(:shell_out!).with(expected, :env => nil)
+ expect(@provider).to receive(:shell_out!).with(expected, env: nil, timeout: 900)
@provider.run_action(:install)
expect(@new_resource).to be_updated_by_last_action
end
@@ -599,7 +602,7 @@ describe Chef::Provider::Package::Rubygems do
describe "in an alternate gem environment" do
it "installs the gem by shelling out to gem install" do
@new_resource.gem_binary('/usr/weird/bin/gem')
- expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", :env=>nil)
+ expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install rspec-core -q --no-rdoc --no-ri -v \"#{@spec_version}\"", env: nil, timeout: 900)
@provider.run_action(:install)
expect(@new_resource).to be_updated_by_last_action
end
@@ -608,7 +611,7 @@ describe Chef::Provider::Package::Rubygems do
@new_resource.gem_binary('/usr/weird/bin/gem')
@new_resource.source(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
@new_resource.version('>= 0')
- expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
+ expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", env: nil, timeout: 900)
@provider.run_action(:install)
expect(@new_resource).to be_updated_by_last_action
end
@@ -620,7 +623,7 @@ describe Chef::Provider::Package::Rubygems do
@new_resource.gem_binary('/usr/weird/bin/gem')
@new_resource.version('>= 0')
expect(@new_resource.source).to eq(CHEF_SPEC_DATA + '/gems/chef-integration-test-0.1.0.gem')
- expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", :env=>nil)
+ expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem install #{CHEF_SPEC_DATA}/gems/chef-integration-test-0.1.0.gem -q --no-rdoc --no-ri -v \">= 0\"", env: nil, timeout: 900)
@provider.run_action(:install)
expect(@new_resource).to be_updated_by_last_action
end
@@ -659,7 +662,7 @@ describe Chef::Provider::Package::Rubygems do
it "uninstalls via the gem command when options are given as a String" do
@new_resource.options('-i /alt/install/location')
- expect(@provider).to receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", :env=>nil)
+ expect(@provider).to receive(:shell_out!).with("gem uninstall rspec -q -x -I -a -i /alt/install/location", env: nil, timeout: 900)
@provider.action_remove
end
@@ -673,7 +676,7 @@ describe Chef::Provider::Package::Rubygems do
describe "in an alternate gem environment" do
it "uninstalls via the gem command" do
@new_resource.gem_binary('/usr/weird/bin/gem')
- expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", :env=>nil)
+ expect(@provider).to receive(:shell_out!).with("/usr/weird/bin/gem uninstall rspec -q -x -I -a", env: nil, timeout: 900)
@provider.action_remove
end
end
diff --git a/spec/unit/provider/package/smartos_spec.rb b/spec/unit/provider/package/smartos_spec.rb
index db39589b85..8f2d2bb8ea 100644
--- a/spec/unit/provider/package/smartos_spec.rb
+++ b/spec/unit/provider/package/smartos_spec.rb
@@ -29,45 +29,45 @@ describe Chef::Provider::Package::SmartOS, "load_current_resource" do
@current_resource = Chef::Resource::Package.new("varnish")
- @status = double("Status", :exitstatus => 0)
- @provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context)
- allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
- @stdin = StringIO.new
- @stdout = "varnish-2.1.5nb2\n"
- @stderr = StringIO.new
- @pid = 10
- @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0)
+ @status = double("Status", :exitstatus => 0)
+ @provider = Chef::Provider::Package::SmartOS.new(@new_resource, @run_context)
+ allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
+ @stdin = StringIO.new
+ @stdout = "varnish-2.1.5nb2\n"
+ @stderr = StringIO.new
+ @pid = 10
+ @shell_out = OpenStruct.new(:stdout => @stdout, :stdin => @stdin, :stderr => @stderr, :status => @status, :exitstatus => 0)
end
- describe "when loading current resource" do
+ describe "when loading current resource" do
it "should create a current resource with the name of the new_resource" do
- expect(@provider).to receive(:shell_out!).and_return(@shell_out)
- expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
- @provider.load_current_resource
+ expect(@provider).to receive(:shell_out!).and_return(@shell_out)
+ expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
+ @provider.load_current_resource
end
- it "should set the current resource package name" do
- expect(@provider).to receive(:shell_out!).and_return(@shell_out)
- expect(@current_resource).to receive(:package_name).with(@new_resource.package_name)
- @provider.load_current_resource
- end
+ it "should set the current resource package name" do
+ expect(@provider).to receive(:shell_out!).and_return(@shell_out)
+ expect(@current_resource).to receive(:package_name).with(@new_resource.package_name)
+ @provider.load_current_resource
+ end
- it "should set the installed version if it is installed" do
- expect(@provider).to receive(:shell_out!).and_return(@shell_out)
- @provider.load_current_resource
- expect(@current_resource.version).to eq("2.1.5nb2")
- end
+ it "should set the installed version if it is installed" do
+ expect(@provider).to receive(:shell_out!).and_return(@shell_out)
+ @provider.load_current_resource
+ expect(@current_resource.version).to eq("2.1.5nb2")
+ end
- it "should set the installed version to nil if it's not installed" do
- out = OpenStruct.new(:stdout => nil)
- expect(@provider).to receive(:shell_out!).and_return(out)
- @provider.load_current_resource
- expect(@current_resource.version).to eq(nil)
- end
+ it "should set the installed version to nil if it's not installed" do
+ out = OpenStruct.new(:stdout => nil)
+ expect(@provider).to receive(:shell_out!).and_return(out)
+ @provider.load_current_resource
+ expect(@current_resource.version).to eq(nil)
+ end
- end
+ end
describe "candidate_version" do
it "should return the candidate_version variable if already setup" do
@@ -76,27 +76,37 @@ describe Chef::Provider::Package::SmartOS, "load_current_resource" do
@provider.candidate_version
end
- it "should lookup the candidate_version if the variable is not already set" do
+ it "should lookup the candidate_version if the variable is not already set (pkgin separated by spaces)" do
search = double()
expect(search).to receive(:each_line).
- and_yield("something-varnish-1.1.1 something varnish like\n").
- and_yield("varnish-2.3.4 actual varnish\n")
+ and_yield("something-varnish-1.1.1 something varnish like\n").
+ and_yield("varnish-2.3.4 actual varnish\n")
@shell_out = double('shell_out!', :stdout => search)
- expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin se varnish', :env => nil, :returns => [0,1]).and_return(@shell_out)
+ expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin', 'se', 'varnish', :env => nil, :returns => [0,1], :timeout=>900).and_return(@shell_out)
+ expect(@provider.candidate_version).to eq("2.3.4")
+ end
+
+ it "should lookup the candidate_version if the variable is not already set (pkgin separated by semicolons)" do
+ search = double()
+ expect(search).to receive(:each_line).
+ and_yield("something-varnish-1.1.1;;something varnish like\n").
+ and_yield("varnish-2.3.4;;actual varnish\n")
+ @shell_out = double('shell_out!', :stdout => search)
+ expect(@provider).to receive(:shell_out!).with('/opt/local/bin/pkgin', 'se', 'varnish', :env => nil, :returns => [0,1], :timeout=>900).and_return(@shell_out)
expect(@provider.candidate_version).to eq("2.3.4")
end
end
- describe "when manipulating a resource" do
+ describe "when manipulating a resource" do
- it "run pkgin and install the package" do
- out = OpenStruct.new(:stdout => nil)
- expect(@provider).to receive(:shell_out!).with("/opt/local/sbin/pkg_info -E \"varnish*\"", {:env => nil, :returns=>[0,1]}).and_return(@shell_out)
- expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin -y install varnish-2.1.5nb2", {:env=>nil}).and_return(out)
+ it "run pkgin and install the package" do
+ out = OpenStruct.new(:stdout => nil)
+ expect(@provider).to receive(:shell_out!).with("/opt/local/sbin/pkg_info", "-E", "varnish*", {:env => nil, :returns=>[0,1], :timeout=>900}).and_return(@shell_out)
+ expect(@provider).to receive(:shell_out!).with("/opt/local/bin/pkgin", "-y", "install", "varnish-2.1.5nb2", {:env=>nil, :timeout=>900}).and_return(out)
@provider.load_current_resource
@provider.install_package("varnish", "2.1.5nb2")
- end
+ end
- end
+ end
end
diff --git a/spec/unit/provider/package/solaris_spec.rb b/spec/unit/provider/package/solaris_spec.rb
index c348d665e8..ae6c96da00 100644
--- a/spec/unit/provider/package/solaris_spec.rb
+++ b/spec/unit/provider/package/solaris_spec.rb
@@ -71,8 +71,8 @@ PKGINFO
it "should get the source package version from pkginfo if provided" do
status = double(:stdout => @pkginfo, :exitstatus => 0)
- expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(status)
- expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(status)
+ expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status)
@provider.load_current_resource
expect(@provider.current_resource.package_name).to eq("SUNWbash")
@@ -81,8 +81,8 @@ PKGINFO
it "should return the current version installed if found by pkginfo" do
status = double(:stdout => @pkginfo, :exitstatus => 0)
- expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
- expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(status)
+ expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status)
+ expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(status)
@provider.load_current_resource
expect(@provider.current_resource.version).to eq("11.10.0,REV=2005.01.08.05.16")
end
@@ -101,8 +101,8 @@ PKGINFO
end
it "should return a current resource with a nil version if the package is not found" do
- expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash").and_return(@status)
- expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("pkginfo -l -d /tmp/bash.pkg SUNWbash", { timeout: 900 }).and_return(@status)
+ expect(@provider).to receive(:shell_out).with("pkginfo -l SUNWbash", { timeout: 900 }).and_return(@status)
@provider.load_current_resource
expect(@provider.current_resource.version).to be_nil
end
@@ -132,7 +132,7 @@ PKGINFO
describe "install and upgrade" do
it "should run pkgadd -n -d with the package source to install" do
- expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all")
+ expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 })
@provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
end
@@ -140,26 +140,26 @@ PKGINFO
@new_resource = Chef::Resource::Package.new("/tmp/bash.pkg")
@provider = Chef::Provider::Package::Solaris.new(@new_resource, @run_context)
expect(@new_resource.source).to eq("/tmp/bash.pkg")
- expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all")
+ expect(@provider).to receive(:shell_out!).with("pkgadd -n -d /tmp/bash.pkg all", { timeout: 900 })
@provider.install_package("/tmp/bash.pkg", "11.10.0,REV=2005.01.08.05.16")
end
it "should run pkgadd -n -a /tmp/myadmin -d with the package options -a /tmp/myadmin" do
allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin")
- expect(@provider).to receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all")
+ expect(@provider).to receive(:shell_out!).with("pkgadd -n -a /tmp/myadmin -d /tmp/bash.pkg all", { timeout: 900 })
@provider.install_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
end
end
describe "remove" do
it "should run pkgrm -n to remove the package" do
- expect(@provider).to receive(:shell_out!).with("pkgrm -n SUNWbash")
+ expect(@provider).to receive(:shell_out!).with("pkgrm -n SUNWbash", { timeout: 900 })
@provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
end
it "should run pkgrm -n -a /tmp/myadmin with options -a /tmp/myadmin" do
allow(@new_resource).to receive(:options).and_return("-a /tmp/myadmin")
- expect(@provider).to receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash")
+ expect(@provider).to receive(:shell_out!).with("pkgrm -n -a /tmp/myadmin SUNWbash", { timeout: 900 })
@provider.remove_package("SUNWbash", "11.10.0,REV=2005.01.08.05.16")
end
diff --git a/spec/unit/provider/package/windows_spec.rb b/spec/unit/provider/package/windows_spec.rb
index d402113d72..e5acc87694 100644
--- a/spec/unit/provider/package/windows_spec.rb
+++ b/spec/unit/provider/package/windows_spec.rb
@@ -19,50 +19,129 @@
require 'spec_helper'
describe Chef::Provider::Package::Windows, :windows_only do
+ before(:each) do
+ allow(Chef::Util::PathHelper).to receive(:windows?).and_return(true)
+ allow(Chef::FileCache).to receive(:create_cache_path).with("package/").and_return(cache_path)
+ end
+
let(:node) { double('Chef::Node') }
let(:events) { double('Chef::Events').as_null_object } # mock all the methods
let(:run_context) { double('Chef::RunContext', :node => node, :events => events) }
- let(:new_resource) { Chef::Resource::WindowsPackage.new("calculator.msi") }
+ let(:resource_source) { 'calculator.msi' }
+ let(:new_resource) { Chef::Resource::WindowsPackage.new(resource_source) }
let(:provider) { Chef::Provider::Package::Windows.new(new_resource, run_context) }
+ let(:cache_path) { 'c:\\cache\\' }
describe "load_current_resource" do
- before(:each) do
- allow(Chef::Util::PathHelper).to receive(:validate_path)
- allow(provider).to receive(:package_provider).and_return(double('package_provider',
+ shared_examples "a local file" do
+ before(:each) do
+ allow(Chef::Util::PathHelper).to receive(:validate_path)
+ allow(provider).to receive(:package_provider).and_return(double('package_provider',
:installed_version => "1.0", :package_version => "2.0"))
- end
+ end
- it "creates a current resource with the name of the new resource" do
- provider.load_current_resource
- expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage)
- expect(provider.current_resource.name).to eql("calculator.msi")
- end
+ it "creates a current resource with the name of the new resource" do
+ provider.load_current_resource
+ expect(provider.current_resource).to be_a(Chef::Resource::WindowsPackage)
+ expect(provider.current_resource.name).to eql(resource_source)
+ end
+
+ it "sets the current version if the package is installed" do
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql("1.0")
+ end
- it "sets the current version if the package is installed" do
- provider.load_current_resource
- expect(provider.current_resource.version).to eql("1.0")
+ it "sets the version to be installed" do
+ provider.load_current_resource
+ expect(provider.new_resource.version).to eql("2.0")
+ end
end
- it "sets the version to be installed" do
- provider.load_current_resource
- expect(provider.new_resource.version).to eql("2.0")
+ context "when the source is a uri" do
+ let(:resource_source) { 'https://foo.bar/calculator.msi' }
+
+ context "when the source has not been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:downloadable_file_missing?).and_return(true)
+ end
+ it "sets the current version to unknown" do
+ provider.load_current_resource
+ expect(provider.current_resource.version).to eql("unknown")
+ end
+ end
+
+ context "when the source has been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:downloadable_file_missing?).and_return(false)
+ end
+ it_behaves_like "a local file"
+ end
+
+ context "when remote_file_attributes are provided" do
+ let (:remote_file_attributes) { {:path => 'C:\\foobar.msi'} }
+ before(:each) do
+ new_resource.remote_file_attributes(remote_file_attributes)
+ end
+
+ it 'should override the attributes of the remote file resource used' do
+ expect(::File).to receive(:exists?).with(remote_file_attributes[:path])
+ provider.load_current_resource
+ end
+
+ end
end
- it "checks that the source path is valid" do
- expect(Chef::Util::PathHelper).to receive(:validate_path)
- provider.load_current_resource
+ context "when source is a local file" do
+ it_behaves_like "a local file"
end
end
describe "package_provider" do
- it "sets the package provider to MSI if the the installer type is :msi" do
- allow(provider).to receive(:installer_type).and_return(:msi)
- expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI)
+ shared_examples "a local file" do
+ it "checks that the source path is valid" do
+ expect(Chef::Util::PathHelper).to receive(:validate_path)
+ provider.package_provider
+ end
+
+ it "sets the package provider to MSI if the the installer type is :msi" do
+ allow(provider).to receive(:installer_type).and_return(:msi)
+ expect(provider.package_provider).to be_a(Chef::Provider::Package::Windows::MSI)
+ end
+
+ it "raises an error if the installer_type is unknown" do
+ allow(provider).to receive(:installer_type).and_return(:apt_for_windows)
+ expect { provider.package_provider }.to raise_error
+ end
+ end
+
+ context "when the source is a uri" do
+ let(:resource_source) { 'https://foo.bar/calculator.msi' }
+
+ context "when the source has not been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:should_download?).and_return(true)
+ end
+
+ it "should create a package provider with source pointing at the local file" do
+ expect(Chef::Provider::Package::Windows::MSI).to receive(:new) do |r|
+ expect(r.source).to eq("#{cache_path}#{::File.basename(resource_source)}")
+ end
+ provider.package_provider
+ end
+
+ it_behaves_like "a local file"
+ end
+
+ context "when the source has been downloaded" do
+ before(:each) do
+ allow(provider).to receive(:should_download?).and_return(false)
+ end
+ it_behaves_like "a local file"
+ end
end
- it "raises an error if the installer_type is unknown" do
- allow(provider).to receive(:installer_type).and_return(:apt_for_windows)
- expect { provider.package_provider }.to raise_error
+ context "when source is a local file" do
+ it_behaves_like "a local file"
end
end
diff --git a/spec/unit/provider/package/yum_spec.rb b/spec/unit/provider/package/yum_spec.rb
index cd2b3decf4..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");
@@ -17,13 +17,14 @@
#
require 'spec_helper'
+require 'securerandom'
describe Chef::Provider::Package::Yum do
before(:each) do
@node = Chef::Node.new
@events = Chef::EventDispatch::Dispatcher.new
@run_context = Chef::RunContext.new(@node, {}, @events)
- @new_resource = Chef::Resource::Package.new('cups')
+ @new_resource = Chef::Resource::YumPackage.new('cups')
@status = double("Status", :exitstatus => 0)
@yum_cache = double(
'Chef::Provider::Yum::YumCache',
@@ -38,6 +39,7 @@ describe Chef::Provider::Package::Yum do
:disable_extra_repo_control => true
)
allow(Chef::Provider::Package::Yum::YumCache).to receive(:instance).and_return(@yum_cache)
+ allow(@yum_cache).to receive(:yum_binary=).with("yum")
@provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
@pid = double("PID")
end
@@ -73,6 +75,60 @@ describe Chef::Provider::Package::Yum do
expect(@provider.load_current_resource).to eql(@provider.current_resource)
end
+ describe "when source is provided" do
+ it "should set the candidate version" do
+ @new_resource = Chef::Resource::YumPackage.new('testing.source')
+ @new_resource.source "chef-server-core-12.0.5-1.rpm"
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ allow(File).to receive(:exists?).with(@new_resource.source).and_return(true)
+ allow(@yum_cache).to receive(:installed_version).and_return(nil)
+ shellout_double = double(:stdout => 'chef-server-core 12.0.5-1')
+ allow(@provider).to receive(:shell_out!).and_return(shellout_double)
+ @provider.load_current_resource
+ expect(@provider.candidate_version).to eql('12.0.5-1')
+ 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')
@@ -94,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")
@@ -108,6 +165,26 @@ describe Chef::Provider::Package::Yum do
expect(@provider.arch).to eq("noarch")
end
+ describe "when version constraint in package_name" do
+ it "should set package_version if no existing package_name is found and new_package_name is available" do
+ @new_resource = Chef::Resource::Package.new('cups = 1.2.4-11.18.el5_2.3')
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ allow(@yum_cache).to receive(:package_available?) { |pkg| pkg == 'cups' ? true : false }
+ allow(@yum_cache).to receive(:packages_from_require) do |pkg|
+ [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base"),
+ Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.2", "noarch", [], false, true, "base"),]
+ end
+ expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{checking yum info})
+ expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{installed version})
+ expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{matched 2 packages,})
+ @provider.load_current_resource
+ expect(@provider.new_resource.package_name).to eq("cups")
+ expect(@provider.new_resource.version).to eq("1.2.4-11.18.el5_2.3")
+ expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3"])
+ expect(@provider.send(:package_name_array)).to eq(["cups"])
+ end
+ end
+
it "should not set the arch when an existing package_name is found" do
@new_resource = Chef::Resource::YumPackage.new('testing.beta3')
@yum_cache = double(
@@ -128,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
@@ -159,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")
@@ -194,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")
@@ -246,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
@@ -317,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)
@@ -338,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')
@@ -360,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
@@ -384,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)
@@ -403,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)
@@ -427,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
@@ -439,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
@@ -447,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
@@ -458,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
@@ -468,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
@@ -479,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
@@ -496,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
@@ -514,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})
@@ -533,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
@@ -556,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
@@ -569,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")
@@ -580,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")
@@ -593,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
@@ -604,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
@@ -622,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})
@@ -671,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
@@ -679,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
@@ -688,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
@@ -702,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
@@ -712,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
@@ -724,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
@@ -736,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
@@ -1645,6 +1750,14 @@ describe Chef::Provider::Package::Yum::YumCache do
end
end
+ let(:yum_exe) {
+ StringIO.new("#!/usr/bin/python\n\naldsjfa\ldsajflkdsjf\lajsdfj")
+ }
+
+ let(:bin_exe) {
+ StringIO.new(SecureRandom.random_bytes)
+ }
+
before(:each) do
@stdin = double("STDIN", :nil_object => true)
@stdout = double("STDOUT", :nil_object => true)
@@ -1690,12 +1803,20 @@ file: file://///etc/yum.repos.d/CentOS-Base.repo, line: 12
'qeqwewe\n'
EOF
@status = double("Status", :exitstatus => 0, :stdin => @stdin, :stdout => @stdout_good, :stderr => @stderr)
-
# new singleton each time
Chef::Provider::Package::Yum::YumCache.reset_instance
@yc = Chef::Provider::Package::Yum::YumCache.instance
# load valid data
+ @yc.yum_binary = "yum"
allow(@yc).to receive(:shell_out!).and_return(@status)
+ allow_any_instance_of(described_class).to receive(:which).with("yum").and_return("/usr/bin/yum")
+ allow(::File).to receive(:open).with("/usr/bin/yum", "r") do |&block|
+ res = block.call(yum_exe)
+ # a bit of a hack. rewind this since it seem that no matter what
+ # I do, we get the same StringIO objects on multiple calls to
+ # ::File.open
+ yum_exe.rewind; res
+ end
end
describe "initialize" do
@@ -1712,6 +1833,24 @@ EOF
end
end
+ describe "python_bin" do
+ it "should return the default python if an error occurs" do
+ allow(::File).to receive(:open).with("/usr/bin/yum", "r").and_raise(StandardError)
+ expect(@yc.python_bin).to eq("/usr/bin/python")
+ end
+
+ it "should return the default python if the yum-executable doesn't start with #!" do
+ allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(bin_exe); bin_exe.rewind; r}
+ expect(@yc.python_bin).to eq("/usr/bin/python")
+ end
+
+ it "should return the interpreter for yum" do
+ other = StringIO.new("#!/usr/bin/super_python\n\nlasjdfdsaljf\nlasdjfs")
+ allow(::File).to receive(:open).with("/usr/bin/yum", "r") { |&b| r = b.call(other); other.rewind; r}
+ expect(@yc.python_bin).to eq("/usr/bin/super_python")
+ end
+ end
+
describe "refresh" do
it "should implicitly call yum-dump.py only once by default after being instantiated" do
expect(@yc).to receive(:shell_out!).once
@@ -1985,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
@@ -2027,6 +2168,36 @@ describe "Chef::Provider::Package::Yum - Multi" do
it "should return the current resouce" do
expect(@provider.load_current_resource).to eql(@provider.current_resource)
end
+
+ describe "when version constraint in package_name" do
+ it "should set package_version if no existing package_name is found and new_package_name is available" do
+ @new_resource = Chef::Resource::Package.new(['cups = 1.2.4-11.18.el5_2.3', 'emacs = 24.4'])
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ allow(@yum_cache).to receive(:package_available?) { |pkg| %w(cups emacs).include?(pkg) ? true : false }
+ allow(@yum_cache).to receive(:candidate_version) do |pkg|
+ if pkg == 'cups'
+ "1.2.4-11.18.el5_2.3"
+ elsif pkg == 'emacs'
+ "24.4"
+ end
+ end
+ allow(@yum_cache).to receive(:packages_from_require) do |pkg|
+ if pkg.name == 'cups'
+ [Chef::Provider::Package::Yum::RPMDbPackage.new("cups", "1.2.4-11.18.el5_2.3", "noarch", [], false, true, "base")]
+ elsif pkg.name == 'emacs'
+ [Chef::Provider::Package::Yum::RPMDbPackage.new("emacs", "24.4", "noarch", [], false, true, "base")]
+ end
+ end
+ expect(Chef::Log).to receive(:debug).exactly(2).times.with(%r{matched 1 package,})
+ expect(Chef::Log).to receive(:debug).exactly(1).times.with(%r{candidate version: \["1.2.4-11.18.el5_2.3", "24.4"\]})
+ expect(Chef::Log).to receive(:debug).at_least(2).times.with(%r{checking yum info})
+ @provider.load_current_resource
+ expect(@provider.new_resource.package_name).to eq(["cups", "emacs"])
+ expect(@provider.new_resource.version).to eq(["1.2.4-11.18.el5_2.3", "24.4"])
+ expect(@provider.send(:package_name_array)).to eq(["cups", "emacs"])
+ expect(@provider.send(:new_version_array)).to eq(["1.2.4-11.18.el5_2.3", "24.4"])
+ end
+ end
end
describe "when installing a package" do
@@ -2036,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
@@ -2046,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
@@ -2057,10 +2228,36 @@ describe "Chef::Provider::Package::Yum - Multi" do
allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
expect(@provider).to receive(:yum_command).with(
- "yum -d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0"
+ "-d0 -e0 -y --disablerepo epmd install cups-1.2.4-11.19.el5 vim-1.0"
)
allow(@new_resource).to receive(:options).and_return("--disablerepo epmd")
@provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
end
+
+ it "should run yum install with the package name and version when name has arch" do
+ @new_resource = Chef::Resource::Package.new(['cups.x86_64', 'vim'])
+ @provider = Chef::Provider::Package::Yum.new(@new_resource, @run_context)
+ allow(Chef::Provider::Package::Yum::RPMUtils).to receive(:rpmvercmp).and_return(-1)
+
+ # Inside of load_current_resource() we'll call parse_arch for cups,
+ # and we need to craft the right response. The default mock setup above
+ # will just return valid versions all the time which won't work for this
+ # test.
+ allow(@yum_cache).to receive(:installed_version).with('cups', 'x86_64').and_return('XXXX')
+ allow(@yum_cache).to receive(:candidate_version).with('cups', 'x86_64').and_return('1.2.4-11.18.el5')
+ allow(@yum_cache).to receive(:installed_version).with('cups.x86_64').and_return(nil)
+ allow(@yum_cache).to receive(:candidate_version).with('cups.x86_64').and_return(nil)
+
+ # Normal mock's for the idempotency check
+ allow(@yum_cache).to receive(:installed_version).with('cups', nil).and_return('1.2.4-11.18.el5')
+ allow(@yum_cache).to receive(:installed_version).with('vim', nil).and_return('0.9')
+
+ @provider.load_current_resource
+ expect(@provider).to receive(:yum_command).with(
+ "-d0 -e0 -y install cups-1.2.4-11.19.el5.x86_64 vim-1.0"
+ )
+ @provider.install_package(["cups", "vim"], ["1.2.4-11.19.el5", '1.0'])
+ end
+
end
end
diff --git a/spec/unit/provider/package/zypper_spec.rb b/spec/unit/provider/package/zypper_spec.rb
index 706ad722dd..18ff739bc6 100644
--- a/spec/unit/provider/package/zypper_spec.rb
+++ b/spec/unit/provider/package/zypper_spec.rb
@@ -19,126 +19,150 @@
require 'spec_helper'
describe Chef::Provider::Package::Zypper do
+ let!(:new_resource) { Chef::Resource::ZypperPackage.new("cups") }
+
+ let!(:current_resource) { Chef::Resource::ZypperPackage.new("cups") }
+
+ let(:provider) do
+ node = Chef::Node.new
+ events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, events)
+ Chef::Provider::Package::Zypper.new(new_resource, run_context)
+ end
+
+ let(:status) { double(:stdout => "\n", :exitstatus => 0) }
+
before(:each) do
- @node = Chef::Node.new
- @events = Chef::EventDispatch::Dispatcher.new
- @run_context = Chef::RunContext.new(@node, {}, @events)
- @new_resource = Chef::Resource::Package.new("cups")
-
- @current_resource = Chef::Resource::Package.new("cups")
-
- @provider = Chef::Provider::Package::Zypper.new(@new_resource, @run_context)
- allow(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
- @status = double(:stdout => "\n", :exitstatus => 0)
- allow(@provider).to receive(:shell_out).and_return(@status)
- allow(@provider).to receive(:`).and_return("2.0")
+ allow(Chef::Resource::Package).to receive(:new).and_return(current_resource)
+ allow(provider).to receive(:shell_out).and_return(status)
+ allow(provider).to receive(:`).and_return("2.0")
+ end
+
+ def shell_out_expectation(command, options=nil)
+ options ||= { timeout: 900 }
+ expect(provider).to receive(:shell_out).with(command, options)
+ end
+
+ def shell_out_expectation!(command, options=nil)
+ options ||= { timeout: 900 }
+ expect(provider).to receive(:shell_out!).with(command, options)
end
describe "when loading the current package state" do
it "should create a current resource with the name of the new_resource" do
- expect(Chef::Resource::Package).to receive(:new).and_return(@current_resource)
- @provider.load_current_resource
+ expect(Chef::Resource::Package).to receive(:new).with(new_resource.name).and_return(current_resource)
+ provider.load_current_resource
end
it "should set the current resources package name to the new resources package name" do
- expect(@current_resource).to receive(:package_name).with(@new_resource.package_name)
- @provider.load_current_resource
+ expect(current_resource).to receive(:package_name).with(new_resource.package_name)
+ provider.load_current_resource
end
it "should run zypper info with the package name" do
- expect(@provider).to receive(:shell_out).with("zypper --non-interactive info #{@new_resource.package_name}").and_return(@status)
- @provider.load_current_resource
+ shell_out_expectation(
+ "zypper --non-interactive info #{new_resource.package_name}"
+ ).and_return(status)
+ provider.load_current_resource
end
it "should set the installed version to nil on the current resource if zypper info installed version is (none)" do
- allow(@provider).to receive(:shell_out).and_return(@status)
- expect(@current_resource).to receive(:version).with(nil).and_return(true)
- @provider.load_current_resource
+ allow(provider).to receive(:shell_out).and_return(status)
+ expect(current_resource).to receive(:version).with(nil).and_return(true)
+ provider.load_current_resource
end
it "should set the installed version if zypper info has one" do
status = double(:stdout => "Version: 1.0\nInstalled: Yes\n", :exitstatus => 0)
- allow(@provider).to receive(:shell_out).and_return(status)
- expect(@current_resource).to receive(:version).with("1.0").and_return(true)
- @provider.load_current_resource
+ allow(provider).to receive(:shell_out).and_return(status)
+ expect(current_resource).to receive(:version).with("1.0").and_return(true)
+ provider.load_current_resource
end
it "should set the candidate version if zypper info has one" do
status = double(:stdout => "Version: 1.0\nInstalled: No\nStatus: out-of-date (version 0.9 installed)", :exitstatus => 0)
- allow(@provider).to receive(:shell_out).and_return(status)
- @provider.load_current_resource
- expect(@provider.candidate_version).to eql("1.0")
+ allow(provider).to receive(:shell_out).and_return(status)
+ provider.load_current_resource
+ expect(provider.candidate_version).to eql("1.0")
end
it "should raise an exception if zypper info fails" do
- expect(@status).to receive(:exitstatus).and_return(1)
- expect { @provider.load_current_resource }.to raise_error(Chef::Exceptions::Package)
+ expect(status).to receive(:exitstatus).and_return(1)
+ expect { provider.load_current_resource }.to raise_error(Chef::Exceptions::Package)
end
it "should not raise an exception if zypper info succeeds" do
- expect(@status).to receive(:exitstatus).and_return(0)
- expect { @provider.load_current_resource }.not_to raise_error
+ expect(status).to receive(:exitstatus).and_return(0)
+ expect { provider.load_current_resource }.not_to raise_error
end
it "should return the current resouce" do
- expect(@provider.load_current_resource).to eql(@current_resource)
+ expect(provider.load_current_resource).to eql(current_resource)
end
end
describe "install_package" do
it "should run zypper install with the package name and version" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0")
- @provider.install_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0"
+ )
+ provider.install_package("emacs", "1.0")
end
it "should run zypper install without gpg checks" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
- expect(@provider).to receive(:shell_out!).with(
+ shell_out_expectation!(
"zypper --non-interactive --no-gpg-checks install "+
- "--auto-agree-with-licenses emacs=1.0")
- @provider.install_package("emacs", "1.0")
+ "--auto-agree-with-licenses emacs=1.0"
+ )
+ provider.install_package("emacs", "1.0")
end
it "should warn about gpg checks on zypper install" do
expect(Chef::Log).to receive(:warn).with(
- /All packages will be installed without gpg signature checks/)
- expect(@provider).to receive(:shell_out!).with(
+ /All packages will be installed without gpg signature checks/
+ )
+ shell_out_expectation!(
"zypper --non-interactive --no-gpg-checks install "+
- "--auto-agree-with-licenses emacs=1.0")
- @provider.install_package("emacs", "1.0")
+ "--auto-agree-with-licenses emacs=1.0"
+ )
+ provider.install_package("emacs", "1.0")
end
end
describe "upgrade_package" do
it "should run zypper update with the package name and version" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0")
- @provider.upgrade_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --non-interactive install --auto-agree-with-licenses emacs=1.0"
+ )
+ provider.upgrade_package("emacs", "1.0")
end
it "should run zypper update without gpg checks" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
- expect(@provider).to receive(:shell_out!).with(
+ shell_out_expectation!(
"zypper --non-interactive --no-gpg-checks install "+
- "--auto-agree-with-licenses emacs=1.0")
- @provider.upgrade_package("emacs", "1.0")
+ "--auto-agree-with-licenses emacs=1.0"
+ )
+ provider.upgrade_package("emacs", "1.0")
end
it "should warn about gpg checks on zypper upgrade" do
expect(Chef::Log).to receive(:warn).with(
- /All packages will be installed without gpg signature checks/)
- expect(@provider).to receive(:shell_out!).with(
+ /All packages will be installed without gpg signature checks/
+ )
+ shell_out_expectation!(
"zypper --non-interactive --no-gpg-checks install "+
- "--auto-agree-with-licenses emacs=1.0")
- @provider.upgrade_package("emacs", "1.0")
+ "--auto-agree-with-licenses emacs=1.0"
+ )
+ provider.upgrade_package("emacs", "1.0")
end
it "should run zypper upgrade without gpg checks" do
- expect(@provider).to receive(:shell_out!).with(
+ shell_out_expectation!(
"zypper --non-interactive --no-gpg-checks install "+
- "--auto-agree-with-licenses emacs=1.0")
-
- @provider.upgrade_package("emacs", "1.0")
+ "--auto-agree-with-licenses emacs=1.0"
+ )
+ provider.upgrade_package("emacs", "1.0")
end
end
@@ -147,83 +171,94 @@ describe Chef::Provider::Package::Zypper do
context "when package version is not explicitly specified" do
it "should run zypper remove with the package name" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive remove emacs")
- @provider.remove_package("emacs", nil)
+ shell_out_expectation!(
+ "zypper --non-interactive remove emacs"
+ )
+ provider.remove_package("emacs", nil)
end
end
context "when package version is explicitly specified" do
it "should run zypper remove with the package name" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(true)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive remove emacs=1.0")
- @provider.remove_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --non-interactive remove emacs=1.0"
+ )
+ provider.remove_package("emacs", "1.0")
end
it "should run zypper remove without gpg checks" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive --no-gpg-checks remove emacs=1.0")
- @provider.remove_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --non-interactive --no-gpg-checks remove emacs=1.0"
+ )
+ provider.remove_package("emacs", "1.0")
end
it "should warn about gpg checks on zypper remove" do
expect(Chef::Log).to receive(:warn).with(
- /All packages will be installed without gpg signature checks/)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive --no-gpg-checks remove emacs=1.0")
-
- @provider.remove_package("emacs", "1.0")
+ /All packages will be installed without gpg signature checks/
+ )
+ shell_out_expectation!(
+ "zypper --non-interactive --no-gpg-checks remove emacs=1.0"
+ )
+ provider.remove_package("emacs", "1.0")
end
end
end
describe "purge_package" do
it "should run remove_package with the name and version" do
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0")
- @provider.purge_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0"
+ )
+ provider.purge_package("emacs", "1.0")
end
it "should run zypper purge without gpg checks" do
allow(Chef::Config).to receive(:[]).with(:zypper_check_gpg).and_return(false)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0")
- @provider.purge_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0"
+ )
+ provider.purge_package("emacs", "1.0")
end
it "should warn about gpg checks on zypper purge" do
expect(Chef::Log).to receive(:warn).with(
- /All packages will be installed without gpg signature checks/)
- expect(@provider).to receive(:shell_out!).with(
- "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0")
- @provider.purge_package("emacs", "1.0")
+ /All packages will be installed without gpg signature checks/
+ )
+ shell_out_expectation!(
+ "zypper --non-interactive --no-gpg-checks remove --clean-deps emacs=1.0"
+ )
+ provider.purge_package("emacs", "1.0")
end
end
describe "on an older zypper" do
before(:each) do
- allow(@provider).to receive(:`).and_return("0.11.6")
+ allow(provider).to receive(:`).and_return("0.11.6")
end
describe "install_package" do
it "should run zypper install with the package name and version" do
- expect(@provider).to receive(:shell_out!).with(
- "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs")
- @provider.install_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs"
+ )
+ provider.install_package("emacs", "1.0")
end
end
describe "upgrade_package" do
it "should run zypper update with the package name and version" do
- expect(@provider).to receive(:shell_out!).with(
- "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs")
- @provider.upgrade_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --no-gpg-checks install --auto-agree-with-licenses -y emacs"
+ )
+ provider.upgrade_package("emacs", "1.0")
end
end
describe "remove_package" do
it "should run zypper remove with the package name" do
- expect(@provider).to receive(:shell_out!).with(
- "zypper --no-gpg-checks remove -y emacs")
- @provider.remove_package("emacs", "1.0")
+ shell_out_expectation!(
+ "zypper --no-gpg-checks remove -y emacs"
+ )
+ provider.remove_package("emacs", "1.0")
end
end
end
diff --git a/spec/unit/provider/package_spec.rb b/spec/unit/provider/package_spec.rb
index 1633d18f9d..432d968906 100644
--- a/spec/unit/provider/package_spec.rb
+++ b/spec/unit/provider/package_spec.rb
@@ -37,6 +37,12 @@ describe Chef::Provider::Package do
allow(@provider).to receive(:install_package).and_return(true)
end
+ it "raises a Chef::Exceptions::InvalidResourceSpecification if both multipackage and source are provided" do
+ @new_resource.package_name(['a', 'b'])
+ @new_resource.source('foo')
+ expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
+ end
+
it "should raise a Chef::Exceptions::Package if no version is specified, and no candidate is available" do
@provider.candidate_version = nil
expect { @provider.run_action(:install) }.to raise_error(Chef::Exceptions::Package)
@@ -698,4 +704,38 @@ describe "Chef::Provider::Package - Multi" do
expect(@new_resource).not_to be_updated_by_last_action
end
end
+
+ describe "shell_out helpers" do
+ [ :shell_out_with_timeout, :shell_out_with_timeout! ].each do |method|
+ stubbed_method = method == :shell_out_with_timeout! ? :shell_out! : :shell_out
+ [ %w{command arg1 arg2}, "command arg1 arg2" ].each do |command|
+ it "#{method} defaults to 900 seconds" do
+ expect(@provider).to receive(stubbed_method).with(*command, timeout: 900)
+ @provider.send(method, *command)
+ end
+ it "#{method} overrides the default timeout with its options" do
+ expect(@provider).to receive(stubbed_method).with(*command, timeout: 1)
+ @provider.send(method, *command, timeout: 1)
+ end
+ it "#{method} overrides both timeouts with the new_resource.timeout" do
+ @new_resource.timeout(99)
+ expect(@provider).to receive(stubbed_method).with(*command, timeout: 99)
+ @provider.send(method, *command, timeout: 1)
+ end
+ it "#{method} defaults to 900 seconds and preserves options" do
+ expect(@provider).to receive(stubbed_method).with(*command, env: nil, timeout: 900)
+ @provider.send(method, *command, env: nil)
+ end
+ it "#{method} overrides the default timeout with its options and preserves options" do
+ expect(@provider).to receive(stubbed_method).with(*command, timeout: 1, env: nil)
+ @provider.send(method, *command, timeout: 1, env: nil)
+ end
+ it "#{method} overrides both timeouts with the new_resource.timeout and preseves options" do
+ @new_resource.timeout(99)
+ expect(@provider).to receive(stubbed_method).with(*command, timeout: 99, env: nil)
+ @provider.send(method, *command, timeout: 1, env: nil)
+ end
+ end
+ end
+ end
end
diff --git a/spec/unit/provider/powershell_script_spec.rb b/spec/unit/provider/powershell_script_spec.rb
new file mode 100644
index 0000000000..855c18af9b
--- /dev/null
+++ b/spec/unit/provider/powershell_script_spec.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Adam Edwards (<adamed@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+describe Chef::Provider::PowershellScript, "action_run" do
+
+ let(:powershell_version) { nil }
+ let(:node) {
+ node = Chef::Node.new
+ node.default["kernel"] = Hash.new
+ node.default["kernel"][:machine] = :x86_64.to_s
+ if ! powershell_version.nil?
+ node.default[:languages] = { :powershell => { :version => powershell_version } }
+ end
+ node
+ }
+
+ let(:provider) {
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ run_context = Chef::RunContext.new(node, {}, empty_events)
+ new_resource = Chef::Resource::PowershellScript.new('run some powershell code', run_context)
+ Chef::Provider::PowershellScript.new(new_resource, run_context)
+ }
+
+ context 'when setting interpreter flags' do
+ it "should set the -File flag as the last flag" do
+ expect(provider.flags.split(' ').pop).to eq("-File")
+ end
+
+ let(:execution_policy_flag) do
+ execution_policy_index = 0
+ provider_flags = provider.flags.split(' ')
+ execution_policy_specified = false
+
+ provider_flags.find do | value |
+ execution_policy_index += 1
+ execution_policy_specified = value.downcase == '-ExecutionPolicy'.downcase
+ end
+
+ execution_policy = execution_policy_specified ? provider_flags[execution_policy_index] : nil
+ end
+
+ context 'when running with an unspecified PowerShell version' do
+ let(:powershell_version) { nil }
+ it "should set the -ExecutionPolicy flag to 'Unrestricted' by default" do
+ expect(execution_policy_flag.downcase).to eq('unrestricted'.downcase)
+ end
+ end
+
+ { '2.0' => 'Unrestricted',
+ '2.5' => 'Unrestricted',
+ '3.0' => 'Bypass',
+ '3.6' => 'Bypass',
+ '4.0' => 'Bypass',
+ '5.0' => 'Bypass' }.each do | version_policy |
+ let(:powershell_version) { version_policy[0].to_f }
+ context "when running PowerShell version #{version_policy[0]}" do
+ let(:powershell_version) { version_policy[0].to_f }
+ it "should set the -ExecutionPolicy flag to '#{version_policy[1]}'" do
+ expect(execution_policy_flag.downcase).to eq(version_policy[1].downcase)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/provider/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/remote_directory_spec.rb b/spec/unit/provider/remote_directory_spec.rb
index 4434714ebc..99e2fe285c 100644
--- a/spec/unit/provider/remote_directory_spec.rb
+++ b/spec/unit/provider/remote_directory_spec.rb
@@ -194,8 +194,8 @@ describe Chef::Provider::RemoteDirectory do
expect(::File.exist?(symlinked_dir_path)).to be_falsey
expect(::File.exist?(tmp_dir)).to be_truthy
- rescue Chef::Exceptions::Win32APIError => e
- pending "This must be run as an Administrator to create symlinks"
+ rescue Chef::Exceptions::Win32APIError
+ skip "This must be run as an Administrator to create symlinks"
end
end
end
diff --git a/spec/unit/provider/remote_file/fetcher_spec.rb b/spec/unit/provider/remote_file/fetcher_spec.rb
index c049848fbf..8bd3b7c625 100644
--- a/spec/unit/provider/remote_file/fetcher_spec.rb
+++ b/spec/unit/provider/remote_file/fetcher_spec.rb
@@ -24,6 +24,26 @@ describe Chef::Provider::RemoteFile::Fetcher do
let(:new_resource) { double("new resource") }
let(:fetcher_instance) { double("fetcher") }
+ describe "when passed a network share" do
+ before do
+ expect(Chef::Provider::RemoteFile::NetworkFile).to receive(:new).and_return(fetcher_instance)
+ end
+
+ context "when host is a name" do
+ let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" }
+ it "returns a network file fetcher" do
+ expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance)
+ end
+ end
+
+ context "when host is an ip" do
+ let(:source) { "\\\\127.0.0.1\\fooshare\\Foo.tar.gz" }
+ it "returns a network file fetcher" do
+ expect(described_class.for_resource(source, new_resource, current_resource)).to eq(fetcher_instance)
+ end
+ end
+ end
+
describe "when passed an http url" do
let(:uri) { double("uri", :scheme => "http" ) }
before do
@@ -72,4 +92,3 @@ describe Chef::Provider::RemoteFile::Fetcher do
end
end
-
diff --git a/spec/unit/provider/remote_file/local_file_spec.rb b/spec/unit/provider/remote_file/local_file_spec.rb
index b33d82f624..575996a540 100644
--- a/spec/unit/provider/remote_file/local_file_spec.rb
+++ b/spec/unit/provider/remote_file/local_file_spec.rb
@@ -25,26 +25,45 @@ describe Chef::Provider::RemoteFile::LocalFile do
let(:new_resource) { Chef::Resource::RemoteFile.new("local file backend test (new_resource)") }
let(:current_resource) { Chef::Resource::RemoteFile.new("local file backend test (current_resource)") }
subject(:fetcher) { Chef::Provider::RemoteFile::LocalFile.new(uri, new_resource, current_resource) }
-
- context "when parsing source path" do
+
+ context "when parsing source path on windows" do
+
+ before do
+ allow(Chef::Platform).to receive(:windows?).and_return(true)
+ end
+
describe "when given local unix path" do
let(:uri) { URI.parse("file:///nyan_cat.png") }
it "returns a correct unix path" do
- expect(fetcher.fix_windows_path(uri.path)).to eq("/nyan_cat.png")
+ expect(fetcher.source_path).to eq("/nyan_cat.png")
end
end
describe "when given local windows path" do
let(:uri) { URI.parse("file:///z:/windows/path/file.txt") }
it "returns a valid windows local path" do
- expect(fetcher.fix_windows_path(uri.path)).to eq("z:/windows/path/file.txt")
+ expect(fetcher.source_path).to eq("z:/windows/path/file.txt")
+ end
+ end
+
+ describe "when given local windows path with spaces" do
+ let(:uri) { URI.parse(URI.escape("file:///z:/windows/path/foo & bar.txt")) }
+ it "returns a valid windows local path" do
+ expect(fetcher.source_path).to eq("z:/windows/path/foo & bar.txt")
end
end
describe "when given unc windows path" do
let(:uri) { URI.parse("file:////server/share/windows/path/file.txt") }
it "returns a valid windows unc path" do
- expect(fetcher.fix_windows_path(uri.path)).to eq("//server/share/windows/path/file.txt")
+ expect(fetcher.source_path).to eq("//server/share/windows/path/file.txt")
+ end
+ end
+
+ describe "when given unc windows path with spaces" do
+ let(:uri) { URI.parse(URI.escape("file:////server/share/windows/path/foo & bar.txt")) }
+ it "returns a valid windows unc path" do
+ expect(fetcher.source_path).to eq("//server/share/windows/path/foo & bar.txt")
end
end
end
@@ -73,7 +92,7 @@ describe Chef::Provider::RemoteFile::LocalFile do
it "stages the local file to a temporary file" do
expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile)
expect(::FileUtils).to receive(:cp).with(uri.path, tempfile.path)
- expect(tempfile).to receive(:close)
+ expect(tempfile).to receive(:close)
result = fetcher.fetch
expect(result).to eq(tempfile)
diff --git a/spec/unit/provider/remote_file/network_file_spec.rb b/spec/unit/provider/remote_file/network_file_spec.rb
new file mode 100644
index 0000000000..3666a47468
--- /dev/null
+++ b/spec/unit/provider/remote_file/network_file_spec.rb
@@ -0,0 +1,45 @@
+#
+# Author:: Jay Mundrawala (<jdm@chef.io>)
+# Copyright:: Copyright (c) 2015 Chef Software
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Provider::RemoteFile::NetworkFile do
+
+ let(:source) { "\\\\foohost\\fooshare\\Foo.tar.gz" }
+
+ let(:new_resource) { Chef::Resource::RemoteFile.new("network file (new_resource)") }
+ let(:current_resource) { Chef::Resource::RemoteFile.new("network file (current_resource)") }
+ subject(:fetcher) { Chef::Provider::RemoteFile::NetworkFile.new(source, new_resource, current_resource) }
+
+ describe "when fetching the object" do
+
+ let(:tempfile) { double("Tempfile", :path => "/tmp/foo/bar/Foo.tar.gz", :close => nil) }
+ let(:chef_tempfile) { double("Chef::FileContentManagement::Tempfile", :tempfile => tempfile) }
+
+ it "stages the local file to a temporary file" do
+ expect(Chef::FileContentManagement::Tempfile).to receive(:new).with(new_resource).and_return(chef_tempfile)
+ expect(::FileUtils).to receive(:cp).with(source, tempfile.path)
+ expect(tempfile).to receive(:close)
+
+ result = fetcher.fetch
+ expect(result).to eq(tempfile)
+ end
+
+ end
+
+end
diff --git a/spec/unit/provider/service/aix_service_spec.rb b/spec/unit/provider/service/aix_service_spec.rb
index 796661145b..5cca7d6f0a 100644
--- a/spec/unit/provider/service/aix_service_spec.rb
+++ b/spec/unit/provider/service/aix_service_spec.rb
@@ -51,22 +51,35 @@ describe Chef::Provider::Service::Aix do
end
it "current resource is running" do
- expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
- expect(@provider).to receive(:is_resource_group?).with(["chef chef 12345 active"])
+ expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status)
+ expect(@provider).to receive(:is_resource_group?).and_return false
@provider.load_current_resource
expect(@current_resource.running).to be_truthy
end
end
- context "when the service is inoprative" do
+ context "when the service is inoperative" do
before do
@status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n")
end
it "current resource is not running" do
- expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
- expect(@provider).to receive(:is_resource_group?).with(["chef chef inoperative"])
+ expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status)
+ expect(@provider).to receive(:is_resource_group?).and_return false
+
+ @provider.load_current_resource
+ expect(@current_resource.running).to be_falsey
+ end
+ end
+
+ context "when there is no such service" do
+ before do
+ @status = double("Status", :exitstatus => 1, :stdout => "0513-085 The chef Subsystem is not on file.\n")
+ end
+ it "current resource is not running" do
+ expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@status)
+ expect(@provider).to receive(:is_resource_group?).and_return false
@provider.load_current_resource
expect(@current_resource.running).to be_falsey
@@ -75,13 +88,13 @@ describe Chef::Provider::Service::Aix do
end
describe "is resource group" do
- context "when there are mutiple subsystems associated with group" do
+ context "when there are multiple subsystems associated with group" do
before do
@status = double("Status", :exitstatus => 0, :stdout => "chef1 chef 12345 active\nchef2 chef 12334 active\nchef3 chef inoperative")
end
it "service is a group" do
- expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status)
@provider.load_current_resource
expect(@provider.instance_eval("@is_resource_group")).to be_truthy
end
@@ -93,19 +106,21 @@ describe Chef::Provider::Service::Aix do
end
it "service is a group" do
- expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@status)
@provider.load_current_resource
expect(@provider.instance_eval("@is_resource_group")).to be_truthy
end
end
- context "when there service is a subsytem" do
+ context "when the service is a subsystem" do
before do
- @status = double("Status", :exitstatus => 0, :stdout => "chef chef123 inoperative\n")
+ @group_status = double("Status", :exitstatus => 1, :stdout => "0513-086 The chef Group is not on file.\n")
+ @service_status = double("Status", :exitstatus => 0, :stdout => "chef chef inoperative\n")
end
it "service is a subsystem" do
- expect(@provider).to receive(:shell_out!).with("lssrc -a | grep -w chef").and_return(@status)
+ expect(@provider).to receive(:shell_out).with("lssrc -g chef").and_return(@group_status)
+ expect(@provider).to receive(:shell_out!).with("lssrc -s chef").and_return(@service_status)
@provider.load_current_resource
expect(@provider.instance_eval("@is_resource_group")).to be_falsey
end
diff --git a/spec/unit/provider/service/freebsd_service_spec.rb b/spec/unit/provider/service/freebsd_service_spec.rb
index 5a55425d87..cfc28c94d5 100644
--- a/spec/unit/provider/service/freebsd_service_spec.rb
+++ b/spec/unit/provider/service/freebsd_service_spec.rb
@@ -189,18 +189,6 @@ PS_SAMPLE
expect(provider.status_load_success).to be_nil
end
- context "when ps command is nil" do
- before do
- node.automatic_attrs[:command] = {:ps => nil}
- end
-
- it "should set running to nil" do
- pending "superclass raises no conversion of nil to string which seems broken"
- provider.determine_current_status!
- expect(current_resource.running).to be_nil
- end
- end
-
context "when ps is empty string" do
before do
node.automatic_attrs[:command] = {:ps => ""}
diff --git a/spec/unit/provider/service/gentoo_service_spec.rb b/spec/unit/provider/service/gentoo_service_spec.rb
index 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 fb751592df..54183bdc3d 100644
--- a/spec/unit/provider/service/macosx_spec.rb
+++ b/spec/unit/provider/service/macosx_spec.rb
@@ -22,17 +22,17 @@ describe Chef::Provider::Service::Macosx do
describe ".gather_plist_dirs" do
context "when HOME directory is set" do
before do
- allow(ENV).to receive(:[]).with('HOME').and_return("/User/someuser")
+ allow(Chef::Util::PathHelper).to receive(:home).with('Library', 'LaunchAgents').and_yield('/Users/someuser/Library/LaunchAgents')
end
it "includes users's LaunchAgents folder" do
- expect(described_class.gather_plist_dirs).to include("#{ENV['HOME']}/Library/LaunchAgents")
+ expect(described_class.gather_plist_dirs).to include("/Users/someuser/Library/LaunchAgents")
end
end
context "when HOME directory is not set" do
before do
- allow(ENV).to receive(:[]).with('HOME').and_return(nil)
+ allow(Chef::Util::PathHelper).to receive(:home).with('Library', 'LaunchAgents').and_return(nil)
end
it "doesn't include user's LaunchAgents folder" do
@@ -58,248 +58,275 @@ describe Chef::Provider::Service::Macosx do
</plist>
XML
- ["redis-server", "io.redis.redis-server"].each do |service_name|
- before do
- allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
- allow(provider).to receive(:shell_out!).
- with("launchctl list", {:group => 1001, :user => 101}).
- and_return(double("Status", :stdout => launchctl_stdout))
- allow(provider).to receive(:shell_out).
- with(/launchctl list /,
- {:group => nil, :user => nil}).
- and_return(double("Status",
- :stdout => launchctl_stdout, :exitstatus => 0))
- allow(provider).to receive(:shell_out!).
- with(/plutil -convert xml1 -o/).
- and_return(double("Status", :stdout => plutil_stdout))
-
- allow(File).to receive(:stat).and_return(double("stat", :gid => 1001, :uid => 101))
- end
-
- context "#{service_name}" do
- let(:new_resource) { Chef::Resource::Service.new(service_name) }
- let!(:current_resource) { Chef::Resource::Service.new(service_name) }
-
- describe "#load_current_resource" do
-
- # CHEF-5223 "you can't glob for a file that hasn't been converged
- # onto the node yet."
- context "when the plist doesn't exist" do
-
- def run_resource_setup_for_action(action)
- new_resource.action(action)
- provider.action = action
- provider.load_current_resource
- provider.define_resource_requirements
- provider.process_resource_requirements
- end
-
- before do
- allow(Dir).to receive(:glob).and_return([])
- allow(provider).to receive(:shell_out!).
- with(/plutil -convert xml1 -o/).
- and_raise(Mixlib::ShellOut::ShellCommandFailed)
- end
-
- it "works for action :nothing" do
- expect { run_resource_setup_for_action(:nothing) }.not_to raise_error
- end
-
- it "works for action :start" do
- expect { run_resource_setup_for_action(:start) }.not_to raise_error
- end
-
- it "errors if action is :enable" do
- expect { run_resource_setup_for_action(:enable) }.to raise_error(Chef::Exceptions::Service)
- end
-
- it "errors if action is :disable" do
- expect { run_resource_setup_for_action(:disable) }.to raise_error(Chef::Exceptions::Service)
+ ["Daemon", "Agent"].each do |service_type|
+ ["redis-server", "io.redis.redis-server"].each do |service_name|
+ ["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 -l igor -c'}
+ if platform_version == "10.9"
+ let(:su_cmd) {'su igor -c'}
end
end
-
- context "when launchctl returns pid in service list" do
- let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
- 12761 - 0x100114220.old.machinit.thing
- 7777 - io.redis.redis-server
- - - com.lol.stopped-thing
- SVC_LIST
-
- before do
- provider.load_current_resource
- end
-
- it "sets resource running state to true" do
- expect(provider.current_resource.running).to be_truthy
- end
-
- it "sets resouce enabled state to true" do
- expect(provider.current_resource.enabled).to be_truthy
- end
+ let(:service_label) {'io.redis.redis-server'}
+ before do
+ allow(Dir).to receive(:glob).and_return([plist], [])
+ allow(Etc).to receive(:getlogin).and_return('igor')
+ allow(node).to receive(:[]).with("platform_version").and_return(platform_version)
+ cmd = "launchctl list #{service_label}"
+ allow(provider).to receive(:shell_out_with_systems_locale).
+ with(/(#{su_cmd} '#{cmd}'|#{cmd})/).
+ and_return(double("Status",
+ :stdout => launchctl_stdout, :exitstatus => 0))
+ allow(File).to receive(:exists?).and_return([true], [])
+ allow(provider).to receive(:shell_out_with_systems_locale!).
+ with(/plutil -convert xml1 -o/).
+ and_return(double("Status", :stdout => plutil_stdout))
end
- describe "running unsupported actions" do
- let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
-12761 - 0x100114220.old.machinit.thing
-7777 - io.redis.redis-server
-- - com.lol.stopped-thing
+ context "#{service_name} that is a #{service_type} running Osx #{platform_version}" do
+ let(:new_resource) { Chef::Resource::MacosxService.new(service_name) }
+ let!(:current_resource) { Chef::Resource::MacosxService.new(service_name) }
+
+ describe "#load_current_resource" do
+
+ # CHEF-5223 "you can't glob for a file that hasn't been converged
+ # onto the node yet."
+ context "when the plist doesn't exist" do
+
+ def run_resource_setup_for_action(action)
+ new_resource.action(action)
+ provider.action = action
+ provider.load_current_resource
+ provider.define_resource_requirements
+ provider.process_resource_requirements
+ end
+
+ before do
+ allow(Dir).to receive(:glob).and_return([])
+ allow(File).to receive(:exists?).and_return([true], [])
+ allow(provider).to receive(:shell_out!).
+ with(/plutil -convert xml1 -o/).
+ and_raise(Mixlib::ShellOut::ShellCommandFailed)
+ end
+
+ it "works for action :nothing" do
+ expect { run_resource_setup_for_action(:nothing) }.not_to raise_error
+ end
+
+ it "works for action :start" do
+ expect { run_resource_setup_for_action(:start) }.not_to raise_error
+ end
+
+ it "errors if action is :enable" do
+ expect { run_resource_setup_for_action(:enable) }.to raise_error(Chef::Exceptions::Service)
+ end
+
+ it "errors if action is :disable" do
+ expect { run_resource_setup_for_action(:disable) }.to raise_error(Chef::Exceptions::Service)
+ end
+ end
+
+ context "when launchctl returns pid in service list" do
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
+{
+ "LimitLoadToSessionType" = "System";
+ "Label" = "io.redis.redis-server";
+ "TimeOut" = 30;
+ "OnDemand" = false;
+ "LastExitStatus" = 0;
+ "PID" = 62803;
+ "Program" = "do_some.sh";
+ "ProgramArguments" = (
+ "path/to/do_something.sh";
+ "-f";
+ );
+};
SVC_LIST
- before do
- allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
- end
- it "should throw an exception when reload action is attempted" do
- expect {provider.run_action(:reload)}.to raise_error(Chef::Exceptions::UnsupportedAction)
- end
- end
- context "when launchctl returns empty service pid" do
- let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
- 12761 - 0x100114220.old.machinit.thing
- - - io.redis.redis-server
- - - com.lol.stopped-thing
- SVC_LIST
-
- before do
- provider.load_current_resource
- end
+ before do
+ provider.load_current_resource
+ end
- it "sets resource running state to false" do
- expect(provider.current_resource.running).to be_falsey
- end
+ it "sets resource running state to true" do
+ expect(provider.current_resource.running).to be_truthy
+ end
- it "sets resouce enabled state to true" do
- expect(provider.current_resource.enabled).to be_truthy
- end
- end
+ it "sets resouce enabled state to true" do
+ expect(provider.current_resource.enabled).to be_truthy
+ end
+ end
- context "when launchctl doesn't return service entry at all" do
- let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
- 12761 - 0x100114220.old.machinit.thing
- - - com.lol.stopped-thing
- SVC_LIST
+ describe "running unsupported actions" do
+ before do
+ allow(Dir).to receive(:glob).and_return(["#{plist}"], [])
+ allow(File).to receive(:exists?).and_return([true], [])
+ end
+ it "should throw an exception when reload action is attempted" do
+ expect {provider.run_action(:reload)}.to raise_error(Chef::Exceptions::UnsupportedAction)
+ end
+ end
+ context "when launchctl returns empty service pid" do
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
+{
+ "LimitLoadToSessionType" = "System";
+ "Label" = "io.redis.redis-server";
+ "TimeOut" = 30;
+ "OnDemand" = false;
+ "LastExitStatus" = 0;
+ "Program" = "do_some.sh";
+ "ProgramArguments" = (
+ "path/to/do_something.sh";
+ "-f";
+ );
+};
+SVC_LIST
- it "sets service running state to false" do
- provider.load_current_resource
- expect(provider.current_resource.running).to be_falsey
- end
+ before do
+ provider.load_current_resource
+ end
- context "and plist for service is not available" do
- before do
- allow(Dir).to receive(:glob).and_return([])
- provider.load_current_resource
- end
+ it "sets resource running state to false" do
+ expect(provider.current_resource.running).to be_falsey
+ end
- it "sets resouce enabled state to false" do
- expect(provider.current_resource.enabled).to be_falsey
+ it "sets resouce enabled state to true" do
+ expect(provider.current_resource.enabled).to be_truthy
+ end
end
- end
- context "and plist for service is available" do
- before do
- allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], [])
- provider.load_current_resource
- end
+ context "when launchctl doesn't return service entry at all" do
+ let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
+Could not find service "io.redis.redis-server" in domain for system
+SVC_LIST
- it "sets resouce enabled state to true" do
- expect(provider.current_resource.enabled).to be_truthy
+ it "sets service running state to false" do
+ provider.load_current_resource
+ expect(provider.current_resource.running).to be_falsey
+ end
+
+ context "and plist for service is not available" do
+ before do
+ allow(Dir).to receive(:glob).and_return([])
+ provider.load_current_resource
+ end
+
+ it "sets resouce enabled state to false" do
+ expect(provider.current_resource.enabled).to be_falsey
+ end
+ end
+
+ context "and plist for service is available" do
+ before do
+ allow(Dir).to receive(:glob).and_return(["#{plist}"], [])
+ provider.load_current_resource
+ end
+
+ it "sets resouce enabled state to true" do
+ expect(provider.current_resource.enabled).to be_truthy
+ end
+ end
+
+ describe "and several plists match service name" do
+ it "throws exception" do
+ allow(Dir).to receive(:glob).and_return(["#{plist}",
+ "/Users/wtf/something.plist"])
+ provider.load_current_resource
+ provider.define_resource_requirements
+ expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service)
+ end
+ end
end
end
-
- describe "and several plists match service name" do
- it "throws exception" do
- allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist",
- "/Users/wtf/something.plist"])
+ describe "#start_service" do
+ before do
+ allow(Chef::Resource::MacosxService).to receive(:new).and_return(current_resource)
provider.load_current_resource
- provider.define_resource_requirements
- expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service)
+ allow(current_resource).to receive(:running).and_return(false)
end
- end
- end
- end
- describe "#start_service" do
- before do
- allow(Chef::Resource::Service).to receive(:new).and_return(current_resource)
- provider.load_current_resource
- allow(current_resource).to receive(:running).and_return(false)
- end
- it "calls the start command if one is specified and service is not running" do
- allow(new_resource).to receive(:start_command).and_return("cowsay dirty")
+ it "calls the start command if one is specified and service is not running" do
+ allow(new_resource).to receive(:start_command).and_return("cowsay dirty")
- expect(provider).to receive(:shell_out_with_systems_locale!).with("cowsay dirty")
- provider.start_service
- end
+ expect(provider).to receive(:shell_out_with_systems_locale!).with("cowsay dirty")
+ provider.start_service
+ end
- it "shows warning message if service is already running" do
- allow(current_resource).to receive(:running).and_return(true)
- expect(Chef::Log).to receive(:debug).with("service[#{service_name}] already running, not starting")
+ it "shows warning message if service is already running" do
+ allow(current_resource).to receive(:running).and_return(true)
+ expect(Chef::Log).to receive(:debug).with("macosx_service[#{service_name}] already running, not starting")
- provider.start_service
- end
+ provider.start_service
+ end
- it "starts service via launchctl if service found" do
- expect(provider).to receive(:shell_out_with_systems_locale!).
- with("launchctl load -w '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'",
- :group => 1001, :user => 101).
- and_return(0)
+ it "starts service via launchctl if service found" do
+ cmd = 'launchctl load -w ' + session + plist
+ expect(provider).to receive(:shell_out_with_systems_locale).
+ with(/(#{su_cmd} .#{cmd}.|#{cmd})/).
+ and_return(0)
- provider.start_service
- end
- end
+ provider.start_service
+ end
+ end
- describe "#stop_service" do
- before do
- allow(Chef::Resource::Service).to receive(:new).and_return(current_resource)
+ describe "#stop_service" do
+ before do
+ allow(Chef::Resource::MacosxService).to receive(:new).and_return(current_resource)
- provider.load_current_resource
- allow(current_resource).to receive(:running).and_return(true)
- end
+ provider.load_current_resource
+ allow(current_resource).to receive(:running).and_return(true)
+ end
- it "calls the stop command if one is specified and service is running" do
- allow(new_resource).to receive(:stop_command).and_return("kill -9 123")
+ it "calls the stop command if one is specified and service is running" do
+ allow(new_resource).to receive(:stop_command).and_return("kill -9 123")
- expect(provider).to receive(:shell_out_with_systems_locale!).with("kill -9 123")
- provider.stop_service
- end
+ expect(provider).to receive(:shell_out_with_systems_locale!).with("kill -9 123")
+ provider.stop_service
+ end
- it "shows warning message if service is not running" do
- allow(current_resource).to receive(:running).and_return(false)
- expect(Chef::Log).to receive(:debug).with("service[#{service_name}] not running, not stopping")
+ it "shows warning message if service is not running" do
+ allow(current_resource).to receive(:running).and_return(false)
+ expect(Chef::Log).to receive(:debug).with("macosx_service[#{service_name}] not running, not stopping")
- provider.stop_service
- end
+ provider.stop_service
+ end
- it "stops the service via launchctl if service found" do
- expect(provider).to receive(:shell_out_with_systems_locale!).
- with("launchctl unload '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'",
- :group => 1001, :user => 101).
- and_return(0)
+ it "stops the service via launchctl if service found" do
+ cmd = 'launchctl unload -w '+ plist
+ expect(provider).to receive(:shell_out_with_systems_locale).
+ with(/(#{su_cmd} .#{cmd}.|#{cmd})/).
+ and_return(0)
- provider.stop_service
- end
- end
+ provider.stop_service
+ end
+ end
- describe "#restart_service" do
- before do
- allow(Chef::Resource::Service).to receive(:new).and_return(current_resource)
+ describe "#restart_service" do
+ before do
+ allow(Chef::Resource::Service).to receive(:new).and_return(current_resource)
- provider.load_current_resource
- allow(current_resource).to receive(:running).and_return(true)
- allow(provider).to receive(:sleep)
- end
+ provider.load_current_resource
+ allow(current_resource).to receive(:running).and_return(true)
+ allow(provider).to receive(:sleep)
+ end
- it "issues a command if given" do
- allow(new_resource).to receive(:restart_command).and_return("reload that thing")
+ it "issues a command if given" do
+ allow(new_resource).to receive(:restart_command).and_return("reload that thing")
- expect(provider).to receive(:shell_out_with_systems_locale!).with("reload that thing")
- provider.restart_service
- end
+ expect(provider).to receive(:shell_out_with_systems_locale!).with("reload that thing")
+ provider.restart_service
+ end
- it "stops and then starts service" do
- expect(provider).to receive(:stop_service)
- expect(provider).to receive(:start_service);
+ it "stops and then starts service" do
+ expect(provider).to receive(:unload_service)
+ expect(provider).to receive(:load_service);
- provider.restart_service
+ provider.restart_service
+ end
+ end
end
end
end
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/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb
index 5ea037d944..32d0812d8c 100644
--- a/spec/unit/provider/user/dscl_spec.rb
+++ b/spec/unit/provider/user/dscl_spec.rb
@@ -24,7 +24,7 @@ require 'mixlib/shellout'
describe Chef::Provider::User::Dscl do
before do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
end
let(:node) {
node = Chef::Node.new
diff --git a/spec/unit/provider/user_spec.rb b/spec/unit/provider/user_spec.rb
index 381168647b..2345ce18fb 100644
--- a/spec/unit/provider/user_spec.rb
+++ b/spec/unit/provider/user_spec.rb
@@ -143,8 +143,8 @@ describe Chef::Provider::User do
begin
require 'rubygems'
require 'shadow'
- rescue LoadError => e
- pending "ruby-shadow gem not installed for dynamic load test"
+ rescue LoadError
+ skip "ruby-shadow gem not installed for dynamic load test"
true
else
false
@@ -161,7 +161,7 @@ describe Chef::Provider::User do
unless shadow_lib_unavail?
context "and we have the ruby-shadow gem" do
- pending "and we are not root (rerun this again as root)", :requires_unprivileged_user => true
+ skip "and we are not root (rerun this again as root)", :requires_unprivileged_user => true
context "and we are root", :requires_root => true do
it "should pass assertions when ruby-shadow can be loaded" do
diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb
index a9fa08ebfd..88df4a20cc 100644
--- a/spec/unit/provider_resolver_spec.rb
+++ b/spec/unit/provider_resolver_spec.rb
@@ -18,32 +18,118 @@
require 'spec_helper'
require 'chef/mixin/convert_to_class_name'
+require 'chef/provider_resolver'
+require 'chef/platform/service_helpers'
include Chef::Mixin::ConvertToClassName
+# Open up Provider so we can write things down easier in here
+#module Chef::Provider
+
describe Chef::ProviderResolver do
+ let(:resource_name) { :service }
+ let(:provider) { nil }
+ let(:action) { :start }
+
let(:node) do
node = Chef::Node.new
- allow(node).to receive(:[]).with(:os).and_return(os)
- allow(node).to receive(:[]).with(:platform_family).and_return(platform_family)
- allow(node).to receive(:[]).with(:platform).and_return(platform)
- allow(node).to receive(:[]).with(:platform_version).and_return(platform_version)
- allow(node).to receive(:is_a?).and_return(Chef::Node)
+ node.automatic[:os] = os
+ node.automatic[:platform_family] = platform_family
+ node.automatic[:platform] = platform
+ node.automatic[:platform_version] = platform_version
+ node.automatic[:kernel] = { machine: 'i386' }
node
end
+ let(:run_context) { Chef::RunContext.new(node, nil, nil) }
let(:provider_resolver) { Chef::ProviderResolver.new(node, resource, action) }
+ let(:resolved_provider) do
+ begin
+ resource ? resource.provider_for_action(action).class : nil
+ rescue Chef::Exceptions::ProviderNotFound
+ nil
+ end
+ end
- let(:action) { :start }
+ let(:resource) do
+ resource_class = Chef::ResourceResolver.resolve(resource_name, node: node)
+ if resource_class
+ resource = resource_class.new('test', run_context)
+ resource.provider = provider if provider
+ end
+ resource
+ end
- let(:resolved_provider) { provider_resolver.resolve }
+ def self.on_platform(platform, *tags,
+ platform_version: '11.0.1',
+ platform_family: nil,
+ os: nil,
+ &block)
+ Array(platform).each do |platform|
+ Array(platform_version).each do |platform_version|
+ on_one_platform(platform, platform_version, platform_family || platform, os || platform_family || platform, *tags, &block)
+ end
+ end
+ end
- let(:provider) { nil }
+ def self.on_one_platform(platform, platform_version, platform_family, os, *tags, &block)
+ describe "on #{platform} #{platform_version}, platform_family: #{platform_family}, os: #{os}", *tags do
+ let(:os) { os }
+ let(:platform) { platform }
+ let(:platform_family) { platform_family }
+ let(:platform_version) { platform_version }
- let(:resource_name) { :service }
+ define_singleton_method(:os) { os }
+ define_singleton_method(:platform) { platform }
+ define_singleton_method(:platform_family) { platform_family }
+ define_singleton_method(:platform_version) { platform_version }
- let(:resource) { double(Chef::Resource, provider: provider, resource_name: resource_name) }
+ instance_eval(&block)
+ end
+ end
+
+ def self.expect_providers(**providers)
+ providers.each do |name, expected|
+ describe name.to_s do
+ let(:resource_name) { name }
+
+ tags = []
+ expected_provider = nil
+ expected_resource = nil
+ Array(expected).each do |p|
+ if p.is_a?(Class) && p <= Chef::Provider
+ expected_provider = p
+ elsif p.is_a?(Class) && p <= Chef::Resource
+ expected_resource = p
+ else
+ tags << p
+ end
+ end
+
+ if expected_resource && expected_provider
+ it "'#{name}' resolves to resource #{expected_resource} and provider #{expected_provider}", *tags do
+ expect(resource.class).to eql(expected_resource)
+ provider = double(expected_provider, class: expected_provider)
+ expect(provider).to receive(:action=).with(action)
+ expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider)
+ expect(resolved_provider).to eql(expected_provider)
+ end
+ elsif expected_provider
+ it "'#{name}' resolves to provider #{expected_provider}", *tags do
+ provider = double(expected_provider)
+ expect(provider).to receive(:action=).with(action)
+ expect(expected_provider).to receive(:new).with(resource, run_context).and_return(provider)
+ expect(resolved_provider).to eql(expected_provider)
+ end
+ else
+ it "'#{name}' fails to resolve (since #{name.inspect} is unsupported on #{platform} #{platform_version})", *tags do
+ expect(resolved_provider).to be_nil
+ end
+ end
+ end
+ end
+ end
describe "resolving service resource" do
def stub_service_providers(*services)
@@ -59,7 +145,6 @@ describe Chef::ProviderResolver do
end
before do
- expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup)
allow(resource).to receive(:service_name).and_return("ntp")
end
@@ -296,257 +381,479 @@ describe Chef::ProviderResolver do
end
end
- describe "on Ubuntu 14.10" do
- let(:os) { "linux" }
- let(:platform) { "ubuntu" }
- let(:platform_family) { "debian" }
- let(:platform_version) { "14.04" }
-
+ on_platform "ubuntu", platform_version: "14.10", platform_family: "debian", os: "linux" do
it_behaves_like "an ubuntu platform with upstart, update-rc.d and systemd"
end
- describe "on Ubuntu 14.04" do
- let(:os) { "linux" }
- let(:platform) { "ubuntu" }
- let(:platform_family) { "debian" }
- let(:platform_version) { "14.04" }
-
+ on_platform "ubuntu", platform_version: "14.04", platform_family: "debian", os: "linux" do
it_behaves_like "an ubuntu platform with upstart and update-rc.d"
end
- describe "on Ubuntu 10.04" do
- let(:os) { "linux" }
- let(:platform) { "ubuntu" }
- let(:platform_family) { "debian" }
- let(:platform_version) { "10.04" }
-
+ on_platform "ubuntu", platform_version: "10.04", platform_family: "debian", os: "linux" do
it_behaves_like "an ubuntu platform with upstart and update-rc.d"
end
# old debian uses the Debian provider (does not have insserv or upstart, or update-rc.d???)
- describe "on Debian 4.0" do
- let(:os) { "linux" }
- let(:platform) { "debian" }
- let(:platform_family) { "debian" }
- let(:platform_version) { "4.0" }
-
+ on_platform "debian", platform_version: "4.0", os: "linux" do
#it_behaves_like "a debian platform using the debian provider"
end
# Debian replaced the debian provider with insserv in the FIXME:VERSION distro
- describe "on Debian 7.0" do
- let(:os) { "linux" }
- let(:platform) { "debian" }
- let(:platform_family) { "debian" }
- let(:platform_version) { "7.0" }
-
+ on_platform "debian", platform_version: "7.0", os: "linux" do
it_behaves_like "a debian platform using the insserv provider"
end
- %w{solaris2 openindiana opensolaris nexentacore omnios smartos}.each do |platform|
- describe "on #{platform}" do
- let(:os) { "solaris2" }
- let(:platform) { platform }
- let(:platform_family) { platform }
- let(:platform_version) { "5.11" }
-
- it "returns a Solaris provider" do
- stub_service_providers
- stub_service_configs
- expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
- end
+ on_platform %w{solaris2 openindiana opensolaris nexentacore omnios smartos}, os: "solaris2", platform_version: "5.11" do
+ it "returns a Solaris provider" do
+ stub_service_providers
+ stub_service_configs
+ expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
+ end
- it "always returns a Solaris provider" do
- # no matter what we stub on the next two lines we should get a Solaris provider
- stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
- stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
- expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
- end
+ it "always returns a Solaris provider" do
+ # no matter what we stub on the next two lines we should get a Solaris provider
+ stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
+ stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
+ expect(resolved_provider).to eql(Chef::Provider::Service::Solaris)
end
end
- %w{mswin mingw32 windows}.each do |platform|
- describe "on #{platform}" do
- let(:os) { "windows" }
- let(:platform) { platform }
- let(:platform_family) { "windows" }
- let(:platform_version) { "5.11" }
-
- it "returns a Windows provider" do
- stub_service_providers
- stub_service_configs
- expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
- end
+ on_platform %w{mswin mingw32 windows}, platform_family: "windows", platform_version: "5.11" do
+ it "returns a Windows provider" do
+ stub_service_providers
+ stub_service_configs
+ expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
+ end
- it "always returns a Windows provider" do
- # no matter what we stub on the next two lines we should get a Windows provider
- stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
- stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
- expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
- end
+ it "always returns a Windows provider" do
+ # no matter what we stub on the next two lines we should get a Windows provider
+ stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
+ stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
+ expect(resolved_provider).to eql(Chef::Provider::Service::Windows)
end
end
- %w{mac_os_x mac_os_x_server}.each do |platform|
- describe "on #{platform}" do
- let(:os) { "darwin" }
- let(:platform) { platform }
- let(:platform_family) { "mac_os_x" }
- let(:platform_version) { "10.9.2" }
-
- it "returns a Macosx provider" do
- stub_service_providers
- stub_service_configs
- expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
- end
+ on_platform %w{mac_os_x mac_os_x_server}, os: "darwin", platform_family: "mac_os_x", platform_version: "10.9.2" do
+ it "returns a Macosx provider" do
+ stub_service_providers
+ stub_service_configs
+ expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
+ end
- it "always returns a Macosx provider" do
- # no matter what we stub on the next two lines we should get a Macosx provider
- stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
- stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
- expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
- end
+ it "always returns a Macosx provider" do
+ # no matter what we stub on the next two lines we should get a Macosx provider
+ stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
+ stub_service_configs(:initd, :upstart, :xinetd, :user_local_etc_rcd, :systemd)
+ expect(resolved_provider).to eql(Chef::Provider::Service::Macosx)
end
end
- %w{freebsd netbsd}.each do |platform|
- describe "on #{platform}" do
- let(:os) { platform }
- let(:platform) { platform }
- let(:platform_family) { platform }
- let(:platform_version) { "10.0-RELEASE" }
-
- it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
- stub_service_providers
- stub_service_configs(:usr_local_etc_rcd)
- expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
- end
+ on_platform %w(freebsd netbsd), platform_version: '3.1.4' do
+ it "returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
+ stub_service_providers
+ stub_service_configs(:usr_local_etc_rcd)
+ expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+ end
- it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do
- stub_service_providers
- stub_service_configs(:etc_rcd)
- expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
- end
+ it "returns a Freebsd provider if it finds the /etc/rc.d initscript" do
+ stub_service_providers
+ stub_service_configs(:etc_rcd)
+ expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+ end
- it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
- # should only care about :usr_local_etc_rcd stub in the service configs
- stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
- stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd)
- expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
- end
+ it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
+ # should only care about :usr_local_etc_rcd stub in the service configs
+ stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
+ stub_service_configs(:usr_local_etc_rcd, :initd, :upstart, :xinetd, :systemd)
+ expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+ end
- it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
- # should only care about :etc_rcd stub in the service configs
- stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
- stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd)
- expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
- end
+ it "always returns a Freebsd provider if it finds the /usr/local/etc/rc.d initscript" do
+ # should only care about :etc_rcd stub in the service configs
+ stub_service_providers(:debian, :invokercd, :insserv, :upstart, :redhat, :systemd)
+ stub_service_configs(:etc_rcd, :initd, :upstart, :xinetd, :systemd)
+ expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
+ end
- it "foo" do
- stub_service_providers
- stub_service_configs
- expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
- end
+ it "foo" do
+ stub_service_providers
+ stub_service_configs
+ expect(resolved_provider).to eql(Chef::Provider::Service::Freebsd)
end
end
end
- describe "resolving static providers" do
- def resource_class(resource)
- Chef::Resource.const_get(convert_to_class_name(resource.to_s))
- end
- static_mapping = {
- apt_package: Chef::Provider::Package::Apt,
- bash: Chef::Provider::Script,
- bff_package: Chef::Provider::Package::Aix,
- breakpoint: Chef::Provider::Breakpoint,
- chef_gem: Chef::Provider::Package::Rubygems,
- cookbook_file: Chef::Provider::CookbookFile,
- csh: Chef::Provider::Script,
- deploy: Chef::Provider::Deploy::Timestamped,
- deploy_revision: Chef::Provider::Deploy::Revision,
- directory: Chef::Provider::Directory,
- dpkg_package: Chef::Provider::Package::Dpkg,
- dsc_script: Chef::Provider::DscScript,
- easy_install_package: Chef::Provider::Package::EasyInstall,
- erl_call: Chef::Provider::ErlCall,
- execute: Chef::Provider::Execute,
- file: Chef::Provider::File,
- gem_package: Chef::Provider::Package::Rubygems,
- git: Chef::Provider::Git,
- homebrew_package: Chef::Provider::Package::Homebrew,
- http_request: Chef::Provider::HttpRequest,
- ips_package: Chef::Provider::Package::Ips,
- link: Chef::Provider::Link,
- log: Chef::Provider::Log::ChefLog,
- macports_package: Chef::Provider::Package::Macports,
- pacman_package: Chef::Provider::Package::Pacman,
- paludis_package: Chef::Provider::Package::Paludis,
- perl: Chef::Provider::Script,
- portage_package: Chef::Provider::Package::Portage,
- python: Chef::Provider::Script,
- remote_directory: Chef::Provider::RemoteDirectory,
- route: Chef::Provider::Route,
- rpm_package: Chef::Provider::Package::Rpm,
- ruby: Chef::Provider::Script,
- ruby_block: Chef::Provider::RubyBlock,
- script: Chef::Provider::Script,
- smartos_package: Chef::Provider::Package::SmartOS,
- solaris_package: Chef::Provider::Package::Solaris,
- subversion: Chef::Provider::Subversion,
- template: Chef::Provider::Template,
- timestamped_deploy: Chef::Provider::Deploy::Timestamped,
- whyrun_safe_ruby_block: Chef::Provider::WhyrunSafeRubyBlock,
- windows_package: Chef::Provider::Package::Windows,
- windows_service: Chef::Provider::Service::Windows,
- yum_package: Chef::Provider::Package::Yum,
+ PROVIDERS =
+ {
+ bash: [ Chef::Resource::Bash, Chef::Provider::Script ],
+ breakpoint: [ Chef::Resource::Breakpoint, Chef::Provider::Breakpoint ],
+ chef_gem: [ Chef::Resource::ChefGem, Chef::Provider::Package::Rubygems ],
+ cookbook_file: [ Chef::Resource::CookbookFile, Chef::Provider::CookbookFile ],
+ csh: [ Chef::Resource::Csh, Chef::Provider::Script ],
+ deploy: [ Chef::Resource::Deploy, Chef::Provider::Deploy::Timestamped ],
+ deploy_revision: [ Chef::Resource::DeployRevision, Chef::Provider::Deploy::Revision ],
+ directory: [ Chef::Resource::Directory, Chef::Provider::Directory ],
+ easy_install_package: [ Chef::Resource::EasyInstallPackage, Chef::Provider::Package::EasyInstall ],
+ erl_call: [ Chef::Resource::ErlCall, Chef::Provider::ErlCall ],
+ execute: [ Chef::Resource::Execute, Chef::Provider::Execute ],
+ file: [ Chef::Resource::File, Chef::Provider::File ],
+ gem_package: [ Chef::Resource::GemPackage, Chef::Provider::Package::Rubygems ],
+ git: [ Chef::Resource::Git, Chef::Provider::Git ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ],
+ homebrew_package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ],
+ http_request: [ Chef::Resource::HttpRequest, Chef::Provider::HttpRequest ],
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+ link: [ Chef::Resource::Link, Chef::Provider::Link ],
+ log: [ Chef::Resource::Log, Chef::Provider::Log::ChefLog ],
+ macports_package: [ Chef::Resource::MacportsPackage, Chef::Provider::Package::Macports ],
+ mdadm: [ Chef::Resource::Mdadm, Chef::Provider::Mdadm ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Mount ],
+ perl: [ Chef::Resource::Perl, Chef::Provider::Script ],
+ portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ],
+ python: [ Chef::Resource::Python, Chef::Provider::Script ],
+ remote_directory: [ Chef::Resource::RemoteDirectory, Chef::Provider::RemoteDirectory ],
+ route: [ Chef::Resource::Route, Chef::Provider::Route ],
+ ruby: [ Chef::Resource::Ruby, Chef::Provider::Script ],
+ ruby_block: [ Chef::Resource::RubyBlock, Chef::Provider::RubyBlock ],
+ script: [ Chef::Resource::Script, Chef::Provider::Script ],
+ subversion: [ Chef::Resource::Subversion, Chef::Provider::Subversion ],
+ template: [ Chef::Resource::Template, Chef::Provider::Template ],
+ timestamped_deploy: [ Chef::Resource::TimestampedDeploy, Chef::Provider::Deploy::Timestamped ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Useradd ],
+ whyrun_safe_ruby_block: [ Chef::Resource::WhyrunSafeRubyBlock, Chef::Provider::WhyrunSafeRubyBlock ],
+
+ # We want to check that these are unsupported:
+ apt_package: nil,
+ bff_package: nil,
+ dpkg_package: nil,
+ dsc_script: nil,
+ ips_package: nil,
+ pacman_package: nil,
+ paludis_package: nil,
+ rpm_package: nil,
+ smartos_package: nil,
+ solaris_package: nil,
+ yum_package: nil,
+ windows_package: nil,
+ windows_service: nil,
+
+ "linux" => {
+ apt_package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ],
+ dpkg_package: [ Chef::Resource::DpkgPackage, Chef::Provider::Package::Dpkg ],
+ pacman_package: [ Chef::Resource::PacmanPackage, Chef::Provider::Package::Pacman ],
+ paludis_package: [ Chef::Resource::PaludisPackage, Chef::Provider::Package::Paludis ],
+ rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ],
+ yum_package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ],
+
+ "debian" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Debian ],
+ package: [ Chef::Resource::AptPackage, Chef::Provider::Package::Apt ],
+# service: [ Chef::Resource::DebianService, Chef::Provider::Service::Debian ],
+
+ "debian" => {
+ "7.0" => {
+ },
+ "6.0" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+# service: [ Chef::Resource::InsservService, Chef::Provider::Service::Insserv ],
+ },
+ "5.0" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+ },
+ },
+ "gcel" => {
+ "3.1.4" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+ },
+ },
+ "linaro" => {
+ "3.1.4" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+ },
+ },
+ "linuxmint" => {
+ "3.1.4" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+# service: [ Chef::Resource::UpstartService, Chef::Provider::Service::Upstart ],
+ },
+ },
+ "raspbian" => {
+ "3.1.4" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+ },
+ },
+ "ubuntu" => {
+ "11.10" => {
+ },
+ "10.04" => {
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig ],
+ },
+ },
+ },
+
+ "arch" => {
+ # TODO should be Chef::Resource::PacmanPackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Pacman ],
+
+ "arch" => {
+ "3.1.4" => {
+ }
+ },
+ },
+
+ "freebsd" => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Pw ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Pw ],
+
+ "freebsd" => {
+ "3.1.4" => {
+ },
+ },
+ },
+ "suse" => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Gpasswd ],
+ "suse" => {
+ "12.0" => {
+ },
+ %w(11.1 11.2 11.3) => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ],
+ },
+ },
+ "opensuse" => {
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+ package: [ Chef::Resource::ZypperPackage, Chef::Provider::Package::Zypper ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+ "12.3" => {
+ },
+ "12.2" => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Suse ],
+ },
+ },
+ },
+
+ "gentoo" => {
+ # TODO should be Chef::Resource::PortagePackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Portage ],
+ portage_package: [ Chef::Resource::PortagePackage, Chef::Provider::Package::Portage ],
+# service: [ Chef::Resource::GentooService, Chef::Provider::Service::Gentoo ],
+
+ "gentoo" => {
+ "3.1.4" => {
+ },
+ },
+ },
+
+ "rhel" => {
+# service: [ Chef::Resource::SystemdService, Chef::Provider::Service::Systemd ],
+ package: [ Chef::Resource::YumPackage, Chef::Provider::Package::Yum ],
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Redhat ],
+
+ %w(amazon xcp xenserver ibm_powerkvm cloudlinux parallels) => {
+ "3.1.4" => {
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+ },
+ },
+ %w(redhat centos scientific oracle) => {
+ "7.0" => {
+ },
+ "6.0" => {
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+ },
+ },
+ "fedora" => {
+ "15.0" => {
+ },
+ "14.0" => {
+# service: [ Chef::Resource::RedhatService, Chef::Provider::Service::Redhat ],
+ },
+ },
+ },
+
+ },
+
+ "darwin" => {
+ %w(mac_os_x mac_os_x_server) => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Dscl ],
+ package: [ Chef::Resource::HomebrewPackage, Chef::Provider::Package::Homebrew ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Dscl ],
+
+ "mac_os_x" => {
+ "10.9.2" => {
+ },
+ },
+ },
+ },
+
+ "windows" => {
+ batch: [ Chef::Resource::Batch, Chef::Provider::Batch ],
+ dsc_script: [ Chef::Resource::DscScript, Chef::Provider::DscScript ],
+ env: [ Chef::Resource::Env, Chef::Provider::Env::Windows ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Windows ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Windows ],
+ package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ],
+ powershell_script: [ Chef::Resource::PowershellScript, Chef::Provider::PowershellScript ],
+ service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Windows ],
+ windows_package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ],
+ windows_service: [ Chef::Resource::WindowsService, Chef::Provider::Service::Windows ],
+
+ "windows" => {
+ %w(mswin mingw32 windows) => {
+ "10.9.2" => {
+ },
+ },
+ },
+ },
+
+ "aix" => {
+ bff_package: [ Chef::Resource::BffPackage, Chef::Provider::Package::Aix ],
+ cron: [ Chef::Resource::Cron, Chef::Provider::Cron::Aix ],
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Aix ],
+ ifconfig: [ Chef::Resource::Ifconfig, Chef::Provider::Ifconfig::Aix ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Aix ],
+ # TODO should be Chef::Resource::BffPackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Aix ],
+ rpm_package: [ Chef::Resource::RpmPackage, Chef::Provider::Package::Rpm ],
+ user: [ Chef::Resource::User, Chef::Provider::User::Aix ],
+# service: [ Chef::Resource::AixService, Chef::Provider::Service::Aix ],
+
+ "aix" => {
+ "aix" => {
+ "5.6" => {
+ },
+ },
+ },
+ },
+
+ "hpux" => {
+ "hpux" => {
+ "hpux" => {
+ "3.1.4" => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ]
+ }
+ }
}
-
- describe "on Ubuntu 14.04" do
- let(:os) { "linux" }
- let(:platform) { "ubuntu" }
- let(:platform_family) { "debian" }
- let(:platform_version) { "14.04" }
-
- supported_providers = [
- :apt_package, :bash, :breakpoint, :chef_gem, :cookbook_file, :csh, :deploy,
- :deploy_revision, :directory, :dpkg_package, :easy_install_package, :erl_call,
- :execute, :file, :gem_package, :git, :homebrew_package, :http_request, :link,
- :log, :macports_package, :pacman_package, :paludis_package, :perl, :python,
- :remote_directory, :route, :rpm_package, :ruby, :ruby_block, :script, :subversion,
- :template, :timestamped_deploy, :whyrun_safe_ruby_block, :yum_package,
- ]
-
- supported_providers.each do |static_resource|
- static_provider = static_mapping[static_resource]
- context "when the resource is a #{static_resource}" do
- let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) }
- let(:action) { :start } # in reality this doesn't matter much
- it "should resolve to a #{static_provider} provider" do
- expect(provider_resolver).not_to receive(:maybe_chef_platform_lookup)
- expect(resolved_provider).to eql(static_provider)
+ },
+
+ "netbsd" => {
+ "netbsd" => {
+ "netbsd" => {
+ "3.1.4" => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Groupmod ],
+ },
+ },
+ },
+ },
+
+ "openbsd" => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+ package: [ Chef::Resource::OpenbsdPackage, Chef::Provider::Package::Openbsd ],
+
+ "openbsd" => {
+ "openbsd" => {
+ "3.1.4" => {
+ },
+ },
+ },
+ },
+
+ "solaris2" => {
+ group: [ Chef::Resource::Group, Chef::Provider::Group::Usermod ],
+ ips_package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ],
+ package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ],
+ mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Solaris ],
+ solaris_package: [ Chef::Resource::SolarisPackage, Chef::Provider::Package::Solaris ],
+
+ "smartos" => {
+ smartos_package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ],
+ package: [ Chef::Resource::SmartosPackage, Chef::Provider::Package::SmartOS ],
+
+ "smartos" => {
+ "3.1.4" => {
+ },
+ },
+ },
+
+ "solaris2" => {
+ "nexentacore" => {
+ "3.1.4" => {
+ },
+ },
+ "omnios" => {
+ "3.1.4" => {
+ user: [ Chef::Resource::User, Chef::Provider::User::Solaris ],
+ }
+ },
+ "openindiana" => {
+ "3.1.4" => {
+ },
+ },
+ "opensolaris" => {
+ "3.1.4" => {
+ },
+ },
+ "solaris2" => {
+ user: [ Chef::Resource::User, Chef::Provider::User::Solaris ],
+ "5.11" => {
+ package: [ Chef::Resource::IpsPackage, Chef::Provider::Package::Ips ],
+ },
+ "5.9" => {
+ },
+ },
+ },
+
+ },
+
+ "solaris" => {
+ "solaris" => {
+ "solaris" => {
+ "3.1.4" => {
+ },
+ },
+ },
+ },
+
+ "exherbo" => {
+ "exherbo" => {
+ "exherbo" => {
+ "3.1.4" => {
+ # TODO should be Chef::Resource::PaludisPackage
+ package: [ Chef::Resource::Package, Chef::Provider::Package::Paludis ]
+ }
+ }
+ }
+ }
+ }
+
+ def self.create_provider_tests(providers, test, expected, filter)
+ expected = expected.merge(providers.select { |key, value| key.is_a?(Symbol) })
+ providers.each do |key, value|
+ if !key.is_a?(Symbol)
+ next_test = test.merge({ filter => key })
+ next_filter =
+ case filter
+ when :os
+ :platform_family
+ when :platform_family
+ :platform
+ when :platform
+ :platform_version
+ when :platform_version
+ nil
+ else
+ raise "Hash too deep; only os, platform_family, platform and platform_version supported"
end
- end
+ create_provider_tests(value, next_test, expected, next_filter)
end
-
- unsupported_providers = [
- :bff_package, :dsc_script, :ips_package, :smartos_package,
- :solaris_package, :windows_package, :windows_service,
- ]
-
- unsupported_providers.each do |static_resource|
- static_provider = static_mapping[static_resource]
- context "when the resource is a #{static_resource}" do
- let(:resource) { double(Chef::Resource, provider: nil, resource_name: static_resource) }
- let(:action) { :start } # in reality this doesn't matter much
- it "should fall back into the old provider mapper code and hooks" do
- retval = Object.new
- expect(provider_resolver).to receive(:maybe_chef_platform_lookup).and_return(retval)
- expect(resolved_provider).to equal(retval)
- end
- end
+ end
+ # If there is no filter, we're as deep as we need to go
+ if !filter
+ on_platform test.delete(:platform), test do
+ expect_providers(expected)
end
end
end
+
+ create_provider_tests(PROVIDERS, {}, {}, :os)
end
diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb
index 5a21b094d0..97b88b1732 100644
--- a/spec/unit/provider_spec.rb
+++ b/spec/unit/provider_spec.rb
@@ -49,6 +49,13 @@ class ConvergeActionDemonstrator < Chef::Provider
end
end
+class CheckResourceSemanticsDemonstrator < ConvergeActionDemonstrator
+ def check_resource_semantics!
+ raise Chef::Exceptions::InvalidResourceSpecification.new("check_resource_semantics!")
+ end
+end
+
+
describe Chef::Provider do
before(:each) do
@cookbook_collection = Chef::CookbookCollection.new([])
@@ -89,6 +96,10 @@ describe Chef::Provider do
expect(@provider.send(:whyrun_supported?)).to eql(false)
end
+ it "should do nothing for check_resource_semantics! by default" do
+ expect { @provider.check_resource_semantics! }.not_to raise_error
+ end
+
it "should return true for action_nothing" do
expect(@provider.action_nothing).to eql(true)
end
@@ -103,9 +114,7 @@ describe Chef::Provider do
end
it "does not re-load recipes when creating the temporary run context" do
- # we actually want to test that RunContext#load is never called, but we
- # can't stub all instances of an object with rspec's mocks. :/
- allow(Chef::RunContext).to receive(:new).and_raise("not supposed to happen")
+ expect_any_instance_of(Chef::RunContext).not_to receive(:load)
snitch = Proc.new {temporary_collection = @run_context.resource_collection}
@provider.send(:recipe_eval, &snitch)
end
@@ -176,6 +185,15 @@ describe Chef::Provider do
expect(@resource).not_to be_updated_by_last_action
end
end
+
+ describe "and the resource is invalid" do
+ let(:provider) { CheckResourceSemanticsDemonstrator.new(@resource, @run_context) }
+
+ it "fails with InvalidResourceSpecification when run" do
+ expect { provider.run_action(:foo) }.to raise_error(Chef::Exceptions::InvalidResourceSpecification)
+ end
+
+ end
end
end
diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb
index 8d0b1bcfd2..ea3ab44c16 100644
--- a/spec/unit/recipe_spec.rb
+++ b/spec/unit/recipe_spec.rb
@@ -20,6 +20,7 @@
#
require 'spec_helper'
+require 'chef/platform/resource_priority_map'
describe Chef::Recipe do
@@ -82,7 +83,7 @@ describe Chef::Recipe do
it "should require a name argument" do
expect {
recipe.cat
- }.to raise_error(ArgumentError, "You must supply a name when declaring a cat resource")
+ }.to raise_error(ArgumentError)
end
it "should allow regular errors (not NameErrors) to pass unchanged" do
@@ -120,7 +121,8 @@ describe Chef::Recipe do
it "locate resource for particular platform" do
ShaunTheSheep = Class.new(Chef::Resource)
- ShaunTheSheep.provides :laughter, :on_platforms => ["television"]
+ ShaunTheSheep.resource_name :shaun_the_sheep
+ ShaunTheSheep.provides :laughter, :platform => ["television"]
node.automatic[:platform] = "television"
node.automatic[:platform_version] = "123"
res = recipe.laughter "timmy"
@@ -130,12 +132,46 @@ 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")
res.kind_of?(YourMom)
end
+ describe "when there is more than one resource that resolves on a node" 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
+ Object.send(:remove_const, :Sounders)
+ Object.send(:remove_const, :TottenhamHotspur)
+ end
+
+ 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(Sounders)
+ end
+
+ it "selects the first one alphabetically even if the declaration order is reversed" do
+ TottenhamHotspur.provides :football2, platform: "nbc_sports"
+ Sounders.provides :football2, platform: "nbc_sports"
+
+ res1 = recipe.football2 "club world cup"
+ expect(res1.name).to eql("club world cup")
+ expect(res1).to be_a_kind_of(Sounders)
+ end
+ end
+
end
end
@@ -237,8 +273,17 @@ describe Chef::Recipe do
action :nothing
end
end
+
+ it "validating resources via build_resource" do
+ expect {recipe.build_resource(:remote_file, "klopp") do
+ source Chef::DelayedEvaluator.new {"http://chef.io"}
+ end}.to_not raise_error
+ end
+
end
+
+
describe "creating resources via declare_resource" do
let(:zm_resource) do
recipe.declare_resource(:zen_master, "klopp") do
@@ -361,7 +406,7 @@ describe Chef::Recipe do
it "does not copy the action from the first resource" do
expect(original_resource.action).to eq([:score])
- expect(duplicated_resource.action).to eq(:nothing)
+ expect(duplicated_resource.action).to eq([:nothing])
end
it "does not copy the source location of the first resource" do
@@ -529,6 +574,36 @@ describe Chef::Recipe do
expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
openldap_recipe.include_recipe "::default"
end
+
+ it "will not load a recipe twice when called first from an LWRP provider" do
+ openldap_recipe = Chef::Recipe.new("openldap", "test", run_context)
+ expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+ allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false)
+ expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context)
+ openldap_recipe.include_recipe "::default"
+ expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
+ openldap_recipe.openldap_includer("do it").run_action(:run)
+ end
+
+ it "will not load a recipe twice when called last from an LWRP provider" do
+ openldap_recipe = Chef::Recipe.new("openldap", "test", run_context)
+ expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+ allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false)
+ expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context)
+ openldap_recipe.openldap_includer("do it").run_action(:run)
+ expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
+ openldap_recipe.include_recipe "::default"
+ end
+
+ it "will not load a recipe twice when called both times from an LWRP provider" do
+ openldap_recipe = Chef::Recipe.new("openldap", "test", run_context)
+ expect(node).to receive(:loaded_recipe).with(:openldap, "default").exactly(:once)
+ allow(run_context).to receive(:unreachable_cookbook?).with(:openldap).and_return(false)
+ expect(cookbook_collection[:openldap]).to receive(:load_recipe).with("default", run_context)
+ openldap_recipe.openldap_includer("do it").run_action(:run)
+ expect(cookbook_collection[:openldap]).not_to receive(:load_recipe).with("default", run_context)
+ openldap_recipe.openldap_includer("do it").run_action(:run)
+ end
end
describe "tags" do
@@ -593,5 +668,9 @@ describe Chef::Recipe do
expect(recipe.singleton_class.included_modules).to include(Chef::DSL::Audit)
expect(recipe.respond_to?(:control_group)).to be true
end
+
+ it "should respond to :ps_credential from Chef::DSL::Powershell" do
+ expect(recipe.respond_to?(:ps_credential)).to be true
+ end
end
end
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/batch_spec.rb b/spec/unit/resource/batch_spec.rb
index 4a056b8735..b8c2897f42 100644
--- a/spec/unit/resource/batch_spec.rb
+++ b/spec/unit/resource/batch_spec.rb
@@ -25,6 +25,7 @@ describe Chef::Resource::Batch do
node.default["kernel"] = Hash.new
node.default["kernel"][:machine] = :x86_64.to_s
+ node.automatic[:os] = 'windows'
run_context = Chef::RunContext.new(node, nil, nil)
diff --git a/spec/unit/resource/breakpoint_spec.rb b/spec/unit/resource/breakpoint_spec.rb
index ed1f3ebcd5..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 07f5f973c0..5b6a452784 100644
--- a/spec/unit/resource/deploy_spec.rb
+++ b/spec/unit/resource/deploy_spec.rb
@@ -30,35 +30,12 @@ describe Chef::Resource::Deploy do
class << self
-
- def resource_has_a_hash_attribute(attr_name)
- it "has a Hash attribute for #{attr_name.to_s}" do
- @resource.send(attr_name, {foo: "bar"})
- expect(@resource.send(attr_name)).to eql({foo: "bar"})
- expect {@resource.send(attr_name, 8675309)}.to raise_error(ArgumentError)
- end
-
- it "the Hash attribute for #{attr_name.to_s} is nillable" do
- @resource.send(attr_name, {foo: "bar"})
- expect(@resource.send(attr_name)).to eql({foo: "bar"})
- @resource.send(attr_name, nil)
- expect(@resource.send(attr_name)).to eql(nil)
- end
- end
-
def resource_has_a_string_attribute(attr_name)
it "has a String attribute for #{attr_name.to_s}" do
@resource.send(attr_name, "this is a string")
expect(@resource.send(attr_name)).to eql("this is a string")
expect {@resource.send(attr_name, 8675309)}.to raise_error(ArgumentError)
end
-
- it "the String attribute for #{attr_name.to_s} is nillable" do
- @resource.send(attr_name, "this is a string")
- expect(@resource.send(attr_name)).to eql("this is a string")
- @resource.send(attr_name, nil)
- expect(@resource.send(attr_name)).to eql(nil)
- end
end
def resource_has_a_boolean_attribute(attr_name, opts={:defaults_to=>false})
@@ -171,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
@@ -212,10 +195,6 @@ describe Chef::Resource::Deploy do
expect(@resource.symlink_before_migrate).to eq({"wtf?" => "wtf is going on"})
end
- resource_has_a_hash_attribute :symlink_before_migrate
- resource_has_a_hash_attribute :symlinks
- resource_has_a_hash_attribute :additional_remotes
-
resource_has_a_callback_attribute :before_migrate
resource_has_a_callback_attribute :before_symlink
resource_has_a_callback_attribute :before_restart
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
new file mode 100644
index 0000000000..06769d86ce
--- /dev/null
+++ b/spec/unit/resource/dsc_resource_spec.rb
@@ -0,0 +1,85 @@
+#
+# Author:: Adam Edwards (<adamed@getchef.com>)
+# Copyright:: Copyright (c) 2014 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'spec_helper'
+
+describe Chef::Resource::DscResource do
+ let(:dsc_test_resource_name) { 'DSCTest' }
+ let(:dsc_test_property_name) { :DSCTestProperty }
+ let(:dsc_test_property_value) { 'DSCTestValue' }
+
+ context 'when Powershell supports Dsc' do
+ let(:dsc_test_run_context) {
+ node = Chef::Node.new
+ node.automatic[:languages][:powershell][:version] = '5.0.10018.0'
+ empty_events = Chef::EventDispatch::Dispatcher.new
+ Chef::RunContext.new(node, {}, empty_events)
+ }
+ let(:dsc_test_resource) {
+ Chef::Resource::DscResource.new(dsc_test_resource_name, dsc_test_run_context)
+ }
+
+ it "has a default action of `:run`" do
+ expect(dsc_test_resource.action).to eq([:run])
+ end
+
+ it "has an allowed_actions attribute with only the `:run` and `:nothing` attributes" do
+ expect(dsc_test_resource.allowed_actions.to_set).to eq([:run,:nothing].to_set)
+ end
+
+ it "allows the resource attribute to be set" do
+ dsc_test_resource.resource(dsc_test_resource_name)
+ expect(dsc_test_resource.resource).to eq(dsc_test_resource_name)
+ end
+
+ it "allows the module_name attribute to be set" do
+ dsc_test_resource.module_name(dsc_test_resource_name)
+ expect(dsc_test_resource.module_name).to eq(dsc_test_resource_name)
+ end
+
+ context "when setting a dsc property" do
+ it "allows setting a dsc property with a property name of type Symbol" do
+ dsc_test_resource.property(dsc_test_property_name, dsc_test_property_value)
+ expect(dsc_test_resource.property(dsc_test_property_name)).to eq(dsc_test_property_value)
+ expect(dsc_test_resource.properties[dsc_test_property_name]).to eq(dsc_test_property_value)
+ end
+
+ it "raises a TypeError if property_name is not a symbol" do
+ expect{
+ dsc_test_resource.property('Foo', dsc_test_property_value)
+ }.to raise_error(TypeError)
+ end
+
+ context "when using DelayedEvaluators" do
+ it "allows setting a dsc property with a property name of type Symbol" do
+ dsc_test_resource.property(dsc_test_property_name, Chef::DelayedEvaluator.new {
+ dsc_test_property_value
+ })
+ expect(dsc_test_resource.property(dsc_test_property_name)).to eq(dsc_test_property_value)
+ expect(dsc_test_resource.properties[dsc_test_property_name]).to eq(dsc_test_property_value)
+ end
+ end
+ end
+
+ context 'Powershell DSL methods' do
+ it "responds to :ps_credential" do
+ expect(dsc_test_resource.respond_to?(:ps_credential)).to be true
+ end
+ end
+ end
+end
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 8ec182665f..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 db52e35004..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/ifconfig_spec.rb b/spec/unit/resource/ifconfig_spec.rb
index ea5282acd5..e3e1f6daa2 100644
--- a/spec/unit/resource/ifconfig_spec.rb
+++ b/spec/unit/resource/ifconfig_spec.rb
@@ -47,21 +47,23 @@ describe Chef::Resource::Ifconfig do
end
end
- shared_examples "being a platform using the default ifconfig provider" do |platform, version|
+ shared_examples "being a platform based on an old Debian" do |platform, version|
before do
+ @node.automatic_attrs[:os] = 'linux'
+ @node.automatic_attrs[:platform_family] = 'debian'
@node.automatic_attrs[:platform] = platform
@node.automatic_attrs[:platform_version] = version
end
it "should use an ordinary Provider::Ifconfig as a provider for #{platform} #{version}" do
- expect(@resource.provider_for_action(:add)).to be_a_kind_of(Chef::Provider::Ifconfig)
- expect(@resource.provider_for_action(:add)).not_to be_a_kind_of(Chef::Provider::Ifconfig::Debian)
- expect(@resource.provider_for_action(:add)).not_to be_a_kind_of(Chef::Provider::Ifconfig::Redhat)
+ expect(@resource.provider_for_action(:add).class).to eq(Chef::Provider::Ifconfig)
end
end
shared_examples "being a platform based on RedHat" do |platform, version|
before do
+ @node.automatic_attrs[:os] = 'linux'
+ @node.automatic_attrs[:platform_family] = 'rhel'
@node.automatic_attrs[:platform] = platform
@node.automatic_attrs[:platform_version] = version
end
@@ -73,6 +75,8 @@ describe Chef::Resource::Ifconfig do
shared_examples "being a platform based on a recent Debian" do |platform, version|
before do
+ @node.automatic_attrs[:os] = 'linux'
+ @node.automatic_attrs[:platform_family] = 'debian'
@node.automatic_attrs[:platform] = platform
@node.automatic_attrs[:platform_version] = version
end
@@ -87,7 +91,7 @@ describe Chef::Resource::Ifconfig do
end
describe "when it is an old Debian platform" do
- it_should_behave_like "being a platform using the default ifconfig provider", "debian", "6.0"
+ it_should_behave_like "being a platform based on an old Debian", "debian", "6.0"
end
describe "when it is a new Debian platform" do
@@ -95,7 +99,7 @@ describe Chef::Resource::Ifconfig do
end
describe "when it is an old Ubuntu platform" do
- it_should_behave_like "being a platform using the default ifconfig provider", "ubuntu", "11.04"
+ it_should_behave_like "being a platform based on an old Debian", "ubuntu", "11.04"
end
describe "when it is a new Ubuntu platform" do
diff --git a/spec/unit/resource/link_spec.rb b/spec/unit/resource/link_spec.rb
index 3573a15f31..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|
@@ -53,6 +53,21 @@ describe Chef::Resource::Link do
expect(@resource.target_file).to eql("fakey_fakerton")
end
+ it "should accept a delayed evaluator as the target path" do
+ @resource.target_file Chef::DelayedEvaluator.new { "my_lazy_name" }
+ expect(@resource.target_file).to eql("my_lazy_name")
+ end
+
+ it "should accept a delayed evaluator when accessing via 'path'" do
+ @resource.target_file Chef::DelayedEvaluator.new { "my_lazy_name" }
+ expect(@resource.path).to eql("my_lazy_name")
+ end
+
+ it "should accept a delayed evaluator via 'to'" do
+ @resource.to Chef::DelayedEvaluator.new { "my_lazy_name" }
+ expect(@resource.to).to eql("my_lazy_name")
+ end
+
it "should accept a string as the link source via 'to'" do
expect { @resource.to "/tmp" }.not_to raise_error
end
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 c263172ae6..2505c4a3d7 100644
--- a/spec/unit/resource/powershell_spec.rb
+++ b/spec/unit/resource/powershell_script_spec.rb
@@ -25,6 +25,7 @@ describe Chef::Resource::PowershellScript do
node.default["kernel"] = Hash.new
node.default["kernel"][:machine] = :x86_64.to_s
+ node.automatic[:os] = 'windows'
run_context = Chef::RunContext.new(node, nil, nil)
diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb
index 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/remote_file_spec.rb b/spec/unit/resource/remote_file_spec.rb
index 3731d1aee2..0a379ff574 100644
--- a/spec/unit/resource/remote_file_spec.rb
+++ b/spec/unit/resource/remote_file_spec.rb
@@ -39,6 +39,11 @@ describe Chef::Resource::RemoteFile do
expect(Chef::Platform.find_provider(:noplatform, 'noversion', @resource)).to eq(Chef::Provider::RemoteFile)
end
+ it "says its provider is RemoteFile when the source is a network share" do
+ @resource.source("\\\\fakey\\fakerton\\fake.txt")
+ expect(@resource.provider).to eq(Chef::Provider::RemoteFile)
+ expect(Chef::Platform.find_provider(:noplatform, 'noversion', @resource)).to eq(Chef::Provider::RemoteFile)
+ end
describe "source" do
it "does not have a default value for 'source'" do
@@ -50,6 +55,16 @@ describe Chef::Resource::RemoteFile do
expect(@resource.source).to eql([ "http://opscode.com/" ])
end
+ it "should accept a windows network share source" do
+ @resource.source "\\\\fakey\\fakerton\\fake.txt"
+ expect(@resource.source).to eql([ "\\\\fakey\\fakerton\\fake.txt" ])
+ end
+
+ it 'should accept file URIs with spaces' do
+ @resource.source("file:///C:/foo bar")
+ expect(@resource.source).to eql(["file:///C:/foo bar"])
+ end
+
it "should accept a delayed evalutator (string) for the remote file source" do
@resource.source Chef::DelayedEvaluator.new {"http://opscode.com/"}
expect(@resource.source).to eql([ "http://opscode.com/" ])
diff --git a/spec/unit/resource/ruby_block_spec.rb b/spec/unit/resource/ruby_block_spec.rb
index 9f19fecd4f..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
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/template_spec.rb b/spec/unit/resource/template_spec.rb
index df5ca94b8a..2fd951b72d 100644
--- a/spec/unit/resource/template_spec.rb
+++ b/spec/unit/resource/template_spec.rb
@@ -98,7 +98,7 @@ describe Chef::Resource::Template do
context "on windows", :windows_only do
# according to Chef::Resource::File, windows state attributes are rights + deny_rights
- pending "it describes its state"
+ skip "it describes its state"
end
it "returns the file path as its identity" do
diff --git a/spec/unit/resource/timestamped_deploy_spec.rb b/spec/unit/resource/timestamped_deploy_spec.rb
index eca6c570d4..4ebfdaf059 100644
--- a/spec/unit/resource/timestamped_deploy_spec.rb
+++ b/spec/unit/resource/timestamped_deploy_spec.rb
@@ -23,11 +23,10 @@ describe Chef::Resource::TimestampedDeploy, "initialize" do
static_provider_resolution(
resource: Chef::Resource::TimestampedDeploy,
provider: Chef::Provider::Deploy::Timestamped,
- name: :deploy,
+ name: :timestamped_deploy,
action: :deploy,
os: 'linux',
platform_family: 'rhel',
)
end
-
diff --git a/spec/unit/resource/user_spec.rb b/spec/unit/resource/user_spec.rb
index 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_package_spec.rb b/spec/unit/resource/windows_package_spec.rb
index 1e02f2449b..6aa5d357ea 100644
--- a/spec/unit/resource/windows_package_spec.rb
+++ b/spec/unit/resource/windows_package_spec.rb
@@ -63,9 +63,9 @@ describe Chef::Resource::WindowsPackage, "initialize" do
end
it "coverts a source to an absolute path" do
- allow(::File).to receive(:absolute_path).and_return("c:\\Files\\frost.msi")
+ allow(::File).to receive(:absolute_path).and_return("c:\\files\\frost.msi")
resource.source("frost.msi")
- expect(resource.source).to eql "c:\\Files\\frost.msi"
+ expect(resource.source).to eql "c:\\files\\frost.msi"
end
it "converts slashes to backslashes in the source path" do
@@ -78,4 +78,18 @@ describe Chef::Resource::WindowsPackage, "initialize" do
# it's a little late to stub out File.absolute_path
expect(resource.source).to include("solitaire.msi")
end
+
+ it "supports the checksum attribute" do
+ resource.checksum('somechecksum')
+ expect(resource.checksum).to eq('somechecksum')
+ end
+
+ context 'when a URL is used' do
+ let(:resource_source) { 'https://foo.bar/solitare.msi' }
+ let(:resource) { Chef::Resource::WindowsPackage.new(resource_source) }
+
+ it "should return the source unmodified" do
+ expect(resource.source).to eq(resource_source)
+ end
+ end
end
diff --git a/spec/unit/resource/yum_package_spec.rb b/spec/unit/resource/yum_package_spec.rb
index 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 8214021f65..b9ba80068b 100644
--- a/spec/unit/resource_spec.rb
+++ b/spec/unit/resource_spec.rb
@@ -21,10 +21,6 @@
require 'spec_helper'
-class ResourceTestHarness < Chef::Resource
- provider_base Chef::Provider::Package
-end
-
describe Chef::Resource do
before(:each) do
@cookbook_repo_path = File.join(CHEF_SPEC_DATA, 'cookbooks')
@@ -35,6 +31,18 @@ describe Chef::Resource do
@resource = Chef::Resource.new("funk", @run_context)
end
+ it "should mixin shell_out" do
+ expect(@resource.respond_to?(:shell_out)).to be true
+ end
+
+ it "should mixin shell_out!" do
+ expect(@resource.respond_to?(:shell_out!)).to be true
+ end
+
+ it "should mixin shell_out_with_systems_locale" do
+ expect(@resource.respond_to?(:shell_out_with_systems_locale)).to be true
+ end
+
describe "when inherited" do
it "adds an entry to a list of subclasses" do
@@ -51,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
@@ -324,6 +332,86 @@ describe Chef::Resource do
end
end
+ describe "self.resource_name" do
+ context "When resource_name is not set" do
+ it "and there are no provides lines, resource_name is nil" do
+ c = Class.new(Chef::Resource) do
+ end
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to be_nil
+ expect(r.resource_name).to be_nil
+ expect(r.declared_type).to eq :d
+ end
+
+ it "and there are no provides lines, @resource_name is used" do
+ c = Class.new(Chef::Resource) do
+ def initialize(*args, &block)
+ @resource_name = :blah
+ super
+ end
+ end
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to be_nil
+ expect(r.resource_name).to eq :blah
+ expect(r.declared_type).to eq :d
+ end
+
+ it "and the resource class gets a late-bound name, resource_name is nil" do
+ c = Class.new(Chef::Resource) do
+ def self.name
+ "ResourceSpecNameTest"
+ end
+ end
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to be_nil
+ expect(r.resource_name).to be_nil
+ expect(r.declared_type).to eq :d
+ end
+ end
+
+ it "resource_name without provides is honored" do
+ c = Class.new(Chef::Resource) do
+ resource_name 'blah'
+ end
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to eq :blah
+ expect(r.resource_name).to eq :blah
+ expect(r.declared_type).to eq :d
+ end
+ it "setting class.resource_name with 'resource_name = blah' overrides declared_type" do
+ c = Class.new(Chef::Resource) do
+ provides :self_resource_name_test_2
+ end
+ c.resource_name = :blah
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to eq :blah
+ expect(r.resource_name).to eq :blah
+ expect(r.declared_type).to eq :d
+ end
+ it "setting class.resource_name with 'resource_name blah' overrides declared_type" do
+ c = Class.new(Chef::Resource) do
+ resource_name :blah
+ provides :self_resource_name_test_3
+ end
+
+ r = c.new('hi')
+ r.declared_type = :d
+ expect(c.resource_name).to eq :blah
+ expect(r.resource_name).to eq :blah
+ expect(r.declared_type).to eq :d
+ end
+ end
+
describe "is" do
it "should return the arguments passed with 'is'" do
zm = Chef::Resource::ZenMaster.new("coffee")
@@ -343,7 +431,7 @@ describe Chef::Resource do
expect(json).to match(/instance_vars/)
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @resource }
end
end
@@ -447,8 +535,21 @@ describe Chef::Resource do
expect(Chef::Resource.provider_base).to eq(Chef::Provider)
end
- it "allows the base provider to be overriden by a " do
- expect(ResourceTestHarness.provider_base).to eq(Chef::Provider::Package)
+ it "allows the base provider to be overridden" do
+ Chef::Config.treat_deprecation_warnings_as_errors(false)
+ class OverrideProviderBaseTest < Chef::Resource
+ provider_base Chef::Provider::Package
+ end
+
+ expect(OverrideProviderBaseTest.provider_base).to eq(Chef::Provider::Package)
+ end
+
+ it "warns when setting provider_base" do
+ expect {
+ class OverrideProviderBaseTest2 < Chef::Resource
+ provider_base Chef::Provider::Package
+ end
+ }.to raise_error(Chef::Exceptions::DeprecatedFeatureError)
end
end
@@ -709,21 +810,21 @@ describe Chef::Resource do
end
it 'adds mappings for a single platform' do
- expect(Chef::Resource.node_map).to receive(:set).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::Resource.node_map).to receive(:set).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::Resource.node_map).to receive(:set).with(
+ expect(Chef.resource_handler_map).to receive(:set).with(
:tape_deck, Chef::Resource::Klz, {}
)
klz.provides :tape_deck
@@ -731,35 +832,51 @@ describe Chef::Resource do
end
- describe "lookups from the platform map" do
- let(:klz1) { Class.new(Chef::Resource) }
- let(:klz2) { Class.new(Chef::Resource) }
+ describe "resource_for_node" do
+ describe "lookups from the platform map" do
+ let(:klz1) { Class.new(Chef::Resource) }
+
+ before(:each) do
+ Chef::Resource::Klz1 = klz1
+ @node = Chef::Node.new
+ @node.name("bumblebee")
+ @node.automatic[:platform] = "autobots"
+ @node.automatic[:platform_version] = "6.1"
+ Object.const_set('Soundwave', klz1)
+ klz1.provides :soundwave
+ end
- before(:each) do
- Chef::Resource::Klz1 = klz1
- Chef::Resource::Klz2 = klz2
- @node = Chef::Node.new
- @node.name("bumblebee")
- @node.automatic[:platform] = "autobots"
- @node.automatic[:platform_version] = "6.1"
- Object.const_set('Soundwave', klz1)
- klz2.provides :dinobot, :on_platforms => ['autobots']
- Object.const_set('Grimlock', klz2)
- end
+ after(:each) do
+ Object.send(:remove_const, :Soundwave)
+ Chef::Resource.send(:remove_const, :Klz1)
+ end
- after(:each) do
- Object.send(:remove_const, :Soundwave)
- Object.send(:remove_const, :Grimlock)
- Chef::Resource.send(:remove_const, :Klz1)
- Chef::Resource.send(:remove_const, :Klz2)
+ it "returns a resource by short_name if nothing else matches" do
+ expect(Chef::Resource.resource_for_node(:soundwave, @node)).to eql(klz1)
+ end
end
- describe "resource_for_node" do
- it "returns a resource by short_name and node" do
- expect(Chef::Resource.resource_for_node(:dinobot, @node)).to eql(Grimlock)
+ describe "lookups from the platform map" do
+ let(:klz2) { Class.new(Chef::Resource) }
+
+ before(:each) do
+ Chef::Resource::Klz2 = klz2
+ @node = Chef::Node.new
+ @node.name("bumblebee")
+ @node.automatic[:platform] = "autobots"
+ @node.automatic[:platform_version] = "6.1"
+ klz2.provides :dinobot, :platform => ['autobots']
+ Object.const_set('Grimlock', klz2)
+ klz2.provides :grimlock
end
- it "returns a resource by short_name if nothing else matches" do
- expect(Chef::Resource.resource_for_node(:soundwave, @node)).to eql(Soundwave)
+
+ after(:each) do
+ Object.send(:remove_const, :Grimlock)
+ Chef::Resource.send(:remove_const, :Klz2)
+ end
+
+ it "returns a resource by short_name and node" do
+ expect(Chef::Resource.resource_for_node(:dinobot, @node)).to eql(klz2)
end
end
@@ -860,4 +977,90 @@ describe Chef::Resource do
end
end
+
+ describe "#action" do
+ let(:resource_class) do
+ Class.new(described_class) do
+ allowed_actions(%i{one two})
+ end
+ end
+ let(:resource) { resource_class.new('test', nil) }
+ subject { resource.action }
+
+ context "with a no action" do
+ it { is_expected.to eq [:nothing] }
+ end
+
+ context "with a default action" do
+ let(:resource_class) do
+ Class.new(described_class) do
+ default_action(:one)
+ end
+ end
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with a symbol action" do
+ before { resource.action(:one) }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with a string action" do
+ before { resource.action('two') }
+ it { is_expected.to eq [:two] }
+ end
+
+ context "with an array action" do
+ before { resource.action([:two, :one]) }
+ it { is_expected.to eq [:two, :one] }
+ end
+
+ context "with an assignment" do
+ before { resource.action = :one }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with an array assignment" do
+ before { resource.action = [:two, :one] }
+ it { is_expected.to eq [:two, :one] }
+ end
+
+ context "with an invalid action" do
+ it { expect { resource.action(:three) }.to raise_error Chef::Exceptions::ValidationFailed }
+ end
+
+ context "with an invalid assignment action" do
+ it { expect { resource.action = :three }.to raise_error Chef::Exceptions::ValidationFailed }
+ end
+ end
+
+ describe ".default_action" do
+ let(:default_action) { }
+ let(:resource_class) do
+ actions = default_action
+ Class.new(described_class) do
+ default_action(actions) if actions
+ end
+ end
+ subject { resource_class.default_action }
+
+ context "with no default actions" do
+ it { is_expected.to eq [:nothing] }
+ end
+
+ context "with a symbol default action" do
+ let(:default_action) { :one }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with a string default action" do
+ let(:default_action) { 'one' }
+ it { is_expected.to eq [:one] }
+ end
+
+ context "with an array default action" do
+ let(:default_action) { [:two, :one] }
+ it { is_expected.to eq [:two, :one] }
+ end
+ end
end
diff --git a/spec/unit/rest_spec.rb b/spec/unit/rest_spec.rb
index 1aa7ac84ee..3b04981610 100644
--- a/spec/unit/rest_spec.rb
+++ b/spec/unit/rest_spec.rb
@@ -69,8 +69,8 @@ describe Chef::REST do
rest
end
- let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}}
- let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id}}
+ let(:standard_read_headers) {{"Accept"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
+ let(:standard_write_headers) {{"Accept"=>"application/json", "Content-Type"=>"application/json", "Accept"=>"application/json", "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-REMOTE-REQUEST-ID"=>request_id, 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION}}
before(:each) do
Chef::Log.init(log_stringio)
@@ -92,6 +92,15 @@ describe Chef::REST do
Chef::REST.new(base_url, nil, nil, options)
end
+ context 'when created with a chef zero URL' do
+
+ let(:url) { "chefzero://localhost:1" }
+
+ it "does not load the signing key" do
+ expect { Chef::REST.new(url) }.to_not raise_error
+ end
+ end
+
describe "calling an HTTP verb on a path or absolute URL" do
it "adds a relative URL to the base url it was initialized with" do
expect(rest.create_url("foo/bar/baz")).to eq(URI.parse(base_url + "/foo/bar/baz"))
@@ -268,19 +277,6 @@ describe Chef::REST do
rest
end
- let(:base_headers) do
- {
- 'Accept' => 'application/json',
- 'X-Chef-Version' => Chef::VERSION,
- 'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
- 'X-REMOTE-REQUEST-ID' => request_id
- }
- end
-
- let (:req_with_body_headers) do
- base_headers.merge("Content-Type" => "application/json", "Content-Length" => '13')
- end
-
before(:each) do
Chef::Config[:ssl_client_cert] = nil
Chef::Config[:ssl_client_key] = nil
@@ -295,7 +291,8 @@ describe Chef::REST do
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
'Host' => host_header,
- 'X-REMOTE-REQUEST-ID' => request_id
+ 'X-REMOTE-REQUEST-ID' => request_id,
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
}
end
@@ -539,7 +536,7 @@ describe Chef::REST do
end
end
end
- end
+ end # as JSON API requests
context "when streaming downloads to a tempfile" do
let!(:tempfile) { Tempfile.open("chef-rspec-rest_spec-line-@{__LINE__}--") }
@@ -577,7 +574,8 @@ describe Chef::REST do
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
'Host' => host_header,
- 'X-REMOTE-REQUEST-ID'=> request_id
+ 'X-REMOTE-REQUEST-ID'=> request_id,
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
}
expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
rest.streaming_request(url, {})
@@ -588,7 +586,8 @@ describe Chef::REST do
'X-Chef-Version' => Chef::VERSION,
'Accept-Encoding' => Chef::REST::RESTRequest::ENCODING_GZIP_DEFLATE,
'Host' => host_header,
- 'X-REMOTE-REQUEST-ID'=> request_id
+ 'X-REMOTE-REQUEST-ID'=> request_id,
+ 'X-Ops-Server-API-Version' => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION
}
expect(Net::HTTP::Get).to receive(:new).with("/?foo=bar", expected_headers).and_return(request_mock)
rest.streaming_request(url, {})
@@ -686,7 +685,7 @@ describe Chef::REST do
expect(block_called).to be_truthy
end
end
- end
+ end # when making REST requests
context "when following redirects" do
let(:rest) do
diff --git a/spec/unit/role_spec.rb b/spec/unit/role_spec.rb
index 5421b5a7b3..ecc7945a08 100644
--- a/spec/unit/role_spec.rb
+++ b/spec/unit/role_spec.rb
@@ -21,7 +21,7 @@ require 'chef/role'
describe Chef::Role do
before(:each) do
- allow(Chef::Platform).to receive(:windows?) { false }
+ allow(ChefConfig).to receive(:windows?) { false }
@role = Chef::Role.new
@role.name("ops_master")
end
@@ -217,7 +217,7 @@ describe Chef::Role do
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @role }
end
end
diff --git a/spec/unit/run_context/child_run_context_spec.rb b/spec/unit/run_context/child_run_context_spec.rb
new file mode 100644
index 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 d656111a7d..99801575ef 100644
--- a/spec/unit/run_context_spec.rb
+++ b/spec/unit/run_context_spec.rb
@@ -53,6 +53,44 @@ describe Chef::RunContext do
expect(run_context.node).to eq(node)
end
+ it "loads up node[:cookbooks]" do
+ expect(run_context.node[:cookbooks]).to eql(
+ {
+ "circular-dep1" => {
+ "version" => "0.0.0",
+ },
+ "circular-dep2" => {
+ "version" => "0.0.0",
+ },
+ "dependency1" => {
+ "version" => "0.0.0",
+ },
+ "dependency2" => {
+ "version" => "0.0.0",
+ },
+ "include" => {
+ "version" => "0.0.0",
+ },
+ "no-default-attr" => {
+ "version" => "0.0.0",
+ },
+ "test" => {
+ "version" => "0.0.0",
+ },
+ "test-with-circular-deps" => {
+ "version" => "0.0.0",
+ },
+ "test-with-deps" => {
+ "version" => "0.0.0",
+ },
+ }
+ )
+ end
+
+ it "has a nil parent_run_context" do
+ expect(run_context.parent_run_context).to be_nil
+ end
+
describe "loading cookbooks for a run list" do
before do
@@ -159,4 +197,45 @@ describe Chef::RunContext do
expect(run_context.reboot_requested?).to be_falsey
end
end
+
+ describe "notifications" do
+ let(:notification) { Chef::Resource::Notification.new(nil, nil, notifying_resource) }
+
+ shared_context "notifying resource is a Chef::Resource" do
+ let(:notifying_resource) { Chef::Resource.new("gerbil") }
+
+ it "should be keyed off the resource name" do
+ run_context.send(setter, notification)
+ expect(run_context.send(getter, notifying_resource)).to eq([notification])
+ end
+ end
+
+ shared_context "notifying resource is a subclass of Chef::Resource" do
+ let(:declared_type) { :alpaca }
+ let(:notifying_resource) {
+ r = Class.new(Chef::Resource).new("guinea pig")
+ r.declared_type = declared_type
+ r
+ }
+
+ it "should be keyed off the resource declared key" do
+ run_context.send(setter, notification)
+ expect(run_context.send(getter, notifying_resource)).to eq([notification])
+ end
+ end
+
+ describe "of the immediate kind" do
+ let(:setter) { :notifies_immediately }
+ let(:getter) { :immediate_notifications }
+ include_context "notifying resource is a Chef::Resource"
+ include_context "notifying resource is a subclass of Chef::Resource"
+ end
+
+ describe "of the delayed kind" do
+ let(:setter) { :notifies_delayed }
+ let(:getter) { :delayed_notifications }
+ include_context "notifying resource is a Chef::Resource"
+ include_context "notifying resource is a subclass of Chef::Resource"
+ end
+ end
end
diff --git a/spec/unit/run_list/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/search/query_spec.rb b/spec/unit/search/query_spec.rb
index 2fb197b183..59ac80f228 100644
--- a/spec/unit/search/query_spec.rb
+++ b/spec/unit/search/query_spec.rb
@@ -81,6 +81,9 @@ describe Chef::Search::Query do
end
describe "search" do
+ let(:query_string) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0" }
+ let(:query_string_continue) { "search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=4" }
+
let(:response) { {
"rows" => [
{ "name" => "my-name-is-node",
@@ -140,6 +143,19 @@ describe Chef::Search::Query do
"total" => 4
} }
+ let(:big_response) {
+ r = response.dup
+ r["total"] = 8
+ r
+ }
+
+ let(:big_response_end) {
+ r = response.dup
+ r["start"] = 4
+ r["total"] = 8
+ r
+ }
+
it "accepts a type as the first argument" do
expect { query.search("node") }.not_to raise_error
expect { query.search(:node) }.not_to raise_error
@@ -195,6 +211,14 @@ describe Chef::Search::Query do
query.search(:node, "*:*", sort: nil, start: 0, rows: 1) { |r| @call_me.do(r) }
end
+ it "sends multiple API requests when the server indicates there is more data" do
+ expect(rest).to receive(:get_rest).with(query_string).and_return(big_response)
+ expect(rest).to receive(:get_rest).with(query_string_continue).and_return(big_response_end)
+ query.search(:node, "platform:rhel") do |r|
+ nil
+ end
+ end
+
context "when :filter_result is provided as a result" do
include_context "filtered search" do
let(:filter_key) { :filter_result }
diff --git a/spec/unit/shell_spec.rb b/spec/unit/shell_spec.rb
index 0e028f4359..379043a017 100644
--- a/spec/unit/shell_spec.rb
+++ b/spec/unit/shell_spec.rb
@@ -43,6 +43,8 @@ describe Shell do
before do
Shell.irb_conf = {}
allow(Shell::ShellSession.instance).to receive(:reset!)
+ allow(ChefConfig).to receive(:windows?).and_return(false)
+ allow(Chef::Util::PathHelper).to receive(:home).and_return('/home/foo')
end
describe "reporting its status" do
@@ -56,7 +58,7 @@ describe Shell do
describe "configuring IRB" do
it "configures irb history" do
Shell.configure_irb
- expect(Shell.irb_conf[:HISTORY_FILE]).to eq("~/.chef/chef_shell_history")
+ expect(Shell.irb_conf[:HISTORY_FILE]).to eq(Chef::Util::PathHelper.home('.chef', 'chef_shell_history'))
expect(Shell.irb_conf[:SAVE_HISTORY]).to eq(1000)
end
@@ -69,7 +71,7 @@ describe Shell do
Shell.irb_conf[:IRB_RC].call(conf)
expect(conf.prompt_c).to eq("chef > ")
expect(conf.return_format).to eq(" => %s \n")
- expect(conf.prompt_i).to eq("chef > ")
+ expect(conf.prompt_i).to eq("chef (#{Chef::VERSION})> ")
expect(conf.prompt_n).to eq("chef ?> ")
expect(conf.prompt_s).to eq("chef%l> ")
expect(conf.use_tracer).to eq(false)
@@ -83,7 +85,7 @@ describe Shell do
conf.main = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, events))
Shell.irb_conf[:IRB_RC].call(conf)
expect(conf.prompt_c).to eq("chef:recipe > ")
- expect(conf.prompt_i).to eq("chef:recipe > ")
+ expect(conf.prompt_i).to eq("chef:recipe (#{Chef::VERSION})> ")
expect(conf.prompt_n).to eq("chef:recipe ?> ")
expect(conf.prompt_s).to eq("chef:recipe%l> ")
end
@@ -95,7 +97,7 @@ describe Shell do
conf.main = Chef::Node.new
Shell.irb_conf[:IRB_RC].call(conf)
expect(conf.prompt_c).to eq("chef:attributes > ")
- expect(conf.prompt_i).to eq("chef:attributes > ")
+ expect(conf.prompt_i).to eq("chef:attributes (#{Chef::VERSION})> ")
expect(conf.prompt_n).to eq("chef:attributes ?> ")
expect(conf.prompt_s).to eq("chef:attributes%l> ")
end
diff --git a/spec/unit/user_spec.rb b/spec/unit/user_spec.rb
index d451531b16..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'
@@ -155,7 +160,7 @@ describe Chef::User do
expect(@json).not_to include("password")
end
- include_examples "to_json equalivent to Chef::JSONCompat.to_json" do
+ include_examples "to_json equivalent to Chef::JSONCompat.to_json" do
let(:jsonable) { @user }
end
end
@@ -200,8 +205,8 @@ describe Chef::User do
before (:each) do
@user = Chef::User.new
@user.name "foobar"
- @http_client = double("Chef::REST mock")
- allow(Chef::REST).to receive(:new).and_return(@http_client)
+ @http_client = double("Chef::ServerAPI mock")
+ allow(Chef::ServerAPI).to receive(:new).and_return(@http_client)
end
describe "list" do
@@ -214,24 +219,24 @@ describe Chef::User do
end
it "lists all clients on an OSC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
expect(Chef::User.list).to eq(@osc_response)
end
it "inflate all clients on an OSC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@osc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@osc_response)
expect(Chef::User.list(true)).to eq(@osc_inflated_response)
end
it "lists all clients on an OHC/OPC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
# We expect that Chef::User.list will give a consistent response
# so OHC API responses should be transformed to OSC-style output.
expect(Chef::User.list).to eq(@osc_response)
end
it "inflate all clients on an OHC/OPC server" do
- allow(@http_client).to receive(:get_rest).with("users").and_return(@ohc_response)
+ allow(@http_client).to receive(:get).with("users").and_return(@ohc_response)
expect(Chef::User.list(true)).to eq(@osc_inflated_response)
end
end
@@ -239,14 +244,14 @@ describe Chef::User do
describe "create" do
it "creates a new user via the API" do
@user.password "password"
- expect(@http_client).to receive(:post_rest).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({})
+ expect(@http_client).to receive(:post).with("users", {:name => "foobar", :admin => false, :password => "password"}).and_return({})
@user.create
end
end
describe "read" do
it "loads a named user from the API" do
- expect(@http_client).to receive(:get_rest).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
+ expect(@http_client).to receive(:get).with("users/foobar").and_return({"name" => "foobar", "admin" => true, "public_key" => "pubkey"})
user = Chef::User.load("foobar")
expect(user.name).to eq("foobar")
expect(user.admin).to eq(true)
@@ -256,14 +261,14 @@ describe Chef::User do
describe "update" do
it "updates an existing user on via the API" do
- expect(@http_client).to receive(:put_rest).with("users/foobar", {:name => "foobar", :admin => false}).and_return({})
+ expect(@http_client).to receive(:put).with("users/foobar", {:name => "foobar", :admin => false}).and_return({})
@user.update
end
end
describe "destroy" do
it "deletes the specified user via the API" do
- expect(@http_client).to receive(:delete_rest).with("users/foobar")
+ expect(@http_client).to receive(:delete).with("users/foobar")
@user.destroy
end
end
diff --git a/spec/unit/user_v1_spec.rb b/spec/unit/user_v1_spec.rb
new file mode 100644
index 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
diff --git a/spec/unit/util/dsc/resource_store.rb b/spec/unit/util/dsc/resource_store.rb
new file mode 100644
index 0000000000..a89e73fcaa
--- /dev/null
+++ b/spec/unit/util/dsc/resource_store.rb
@@ -0,0 +1,76 @@
+#
+# Author:: Jay Mundrawala <jdm@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'
+require 'chef/util/dsc/resource_store'
+
+describe Chef::Util::DSC::ResourceStore do
+ let(:resource_store) { Chef::Util::DSC::ResourceStore.new }
+ let(:resource_a) { {
+ 'ResourceType' => 'AFoo',
+ 'Name' => 'Foo',
+ 'Module' => {'Name' => 'ModuleA'}
+ }
+ }
+
+ let(:resource_b) { {
+ 'ResourceType' => 'BFoo',
+ 'Name' => 'Foo',
+ 'Module' => {'Name' => 'ModuleB'}
+ }
+ }
+
+ context 'when resources are not cached' do
+ context 'when calling #resources' do
+ it 'returns an empty array' do
+ expect(resource_store.resources).to eql([])
+ end
+ end
+
+ context 'when calling #find' do
+ it 'returns an empty list if it cannot find any matching resources' do
+ expect(resource_store).to receive(:query_resource).and_return([])
+ expect(resource_store.find('foo')).to eql([])
+ end
+
+ it 'returns the resource if it is found (comparisons are case insensitive)' do
+ expect(resource_store).to receive(:query_resource).and_return([resource_a])
+ expect(resource_store.find('foo')).to eql([resource_a])
+ end
+
+ it 'returns multiple resoures if they are found' do
+ expect(resource_store).to receive(:query_resource).and_return([resource_a, resource_b])
+ expect(resource_store.find('foo')).to include(resource_a, resource_b)
+ end
+
+ it 'deduplicates resources by ResourceName' do
+ expect(resource_store).to receive(:query_resource).and_return([resource_a, resource_a])
+ resource_store.find('foo')
+ expect(resource_store.resources).to eq([resource_a])
+ end
+ end
+ end
+
+ context 'when resources are cached' do
+ it 'recalls resources from the cache if present' do
+ expect(resource_store).not_to receive(:query_resource)
+ expect(resource_store).to receive(:resources).and_return([resource_a])
+ resource_store.find('foo')
+ end
+ end
+end
diff --git a/spec/unit/util/path_helper_spec.rb b/spec/unit/util/path_helper_spec.rb
deleted file mode 100644
index 5756c29b90..0000000000
--- a/spec/unit/util/path_helper_spec.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-#
-# Author:: Bryan McLellan <btm@loftninjas.org>
-# Copyright:: Copyright (c) 2014 Chef Software, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/util/path_helper'
-require 'spec_helper'
-
-describe Chef::Util::PathHelper do
- PathHelper = Chef::Util::PathHelper
-
- [ false, true ].each do |is_windows|
- context "on #{is_windows ? "windows" : "unix"}" do
- before(:each) do
- allow(Chef::Platform).to receive(:windows?).and_return(is_windows)
- end
-
- describe "join" do
- it "joins components when some end with separators" do
- expected = PathHelper.cleanpath("/foo/bar/baz")
- expected = "C:#{expected}" if is_windows
- expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar", "baz")).to eq(expected)
- end
-
- it "joins components when some end and start with separators" do
- expected = PathHelper.cleanpath("/foo/bar/baz")
- expected = "C:#{expected}" if is_windows
- expect(PathHelper.join(is_windows ? 'C:\\foo\\' : "/foo/", "bar/", "/baz")).to eq(expected)
- end
-
- it "joins components that don't end in separators" do
- expected = PathHelper.cleanpath("/foo/bar/baz")
- expected = "C:#{expected}" if is_windows
- expect(PathHelper.join(is_windows ? 'C:\\foo' : "/foo", "bar", "baz")).to eq(expected)
- end
-
- it "joins starting with '' resolve to absolute paths" do
- expect(PathHelper.join('', 'a', 'b')).to eq("#{PathHelper.path_separator}a#{PathHelper.path_separator}b")
- end
-
- it "joins ending with '' add a / to the end" do
- expect(PathHelper.join('a', 'b', '')).to eq("a#{PathHelper.path_separator}b#{PathHelper.path_separator}")
- end
-
- if is_windows
- it "joins components on Windows when some end with unix separators" do
- expect(PathHelper.join('C:\\foo/', "bar", "baz")).to eq('C:\\foo\\bar\\baz')
- end
- end
- end
-
- if is_windows
- it "path_separator is \\" do
- expect(PathHelper.path_separator).to eq('\\')
- end
- else
- it "path_separator is /" do
- expect(PathHelper.path_separator).to eq('/')
- end
- end
-
- if is_windows
- it "cleanpath changes slashes into backslashes and leaves backslashes alone" do
- expect(PathHelper.cleanpath('/a/b\\c/d/')).to eq('\\a\\b\\c\\d')
- end
- it "cleanpath does not remove leading double backslash" do
- expect(PathHelper.cleanpath('\\\\a/b\\c/d/')).to eq('\\\\a\\b\\c\\d')
- end
- else
- it "cleanpath removes extra slashes alone" do
- expect(PathHelper.cleanpath('/a///b/c/d/')).to eq('/a/b/c/d')
- end
- end
-
- describe "dirname" do
- it "dirname('abc') is '.'" do
- expect(PathHelper.dirname('abc')).to eq('.')
- end
- it "dirname('/') is '/'" do
- expect(PathHelper.dirname(PathHelper.path_separator)).to eq(PathHelper.path_separator)
- end
- it "dirname('a/b/c') is 'a/b'" do
- expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c'))).to eq(PathHelper.join('a', 'b'))
- end
- it "dirname('a/b/c/') is 'a/b'" do
- expect(PathHelper.dirname(PathHelper.join('a', 'b', 'c', ''))).to eq(PathHelper.join('a', 'b'))
- end
- it "dirname('/a/b/c') is '/a/b'" do
- expect(PathHelper.dirname(PathHelper.join('', 'a', 'b', 'c'))).to eq(PathHelper.join('', 'a', 'b'))
- end
- end
- end
- end
-
- describe "validate_path" do
- context "on windows" do
- before(:each) do
- # pass by default
- allow(Chef::Platform).to receive(:windows?).and_return(true)
- allow(PathHelper).to receive(:printable?).and_return(true)
- allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(false)
- end
-
- it "returns the path if the path passes the tests" do
- expect(PathHelper.validate_path("C:\\ThisIsRigged")).to eql("C:\\ThisIsRigged")
- end
-
- it "does not raise an error if everything looks great" do
- expect { PathHelper.validate_path("C:\\cool path\\dude.exe") }.not_to raise_error
- end
-
- it "raises an error if the path has invalid characters" do
- allow(PathHelper).to receive(:printable?).and_return(false)
- expect { PathHelper.validate_path("Newline!\n") }.to raise_error(Chef::Exceptions::ValidationFailed)
- end
-
- it "Adds the \\\\?\\ prefix if the path exceeds MAX_LENGTH and does not have it" do
- long_path = "C:\\" + "a" * 250 + "\\" + "b" * 250
- prefixed_long_path = "\\\\?\\" + long_path
- allow(PathHelper).to receive(:windows_max_length_exceeded?).and_return(true)
- expect(PathHelper.validate_path(long_path)).to eql(prefixed_long_path)
- end
- end
- end
-
- describe "windows_max_length_exceeded?" do
- it "returns true if the path is too long (259 + NUL) for the API" do
- expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 6)).to be_truthy
- end
-
- it "returns false if the path is not too long (259 + NUL) for the standard API" do
- expect(PathHelper.windows_max_length_exceeded?("C:\\" + "a" * 250 + "\\" + "b" * 5)).to be_falsey
- end
-
- it "returns false if the path is over 259 characters but uses the \\\\?\\ prefix" do
- expect(PathHelper.windows_max_length_exceeded?("\\\\?\\C:\\" + "a" * 250 + "\\" + "b" * 250)).to be_falsey
- end
- end
-
- describe "printable?" do
- it "returns true if the string contains no non-printable characters" do
- expect(PathHelper.printable?("C:\\Program Files (x86)\\Microsoft Office\\Files.lst")).to be_truthy
- end
-
- it "returns true when given 'abc' in unicode" do
- expect(PathHelper.printable?("\u0061\u0062\u0063")).to be_truthy
- end
-
- it "returns true when given japanese unicode" do
- expect(PathHelper.printable?("\uff86\uff87\uff88")).to be_truthy
- end
-
- it "returns false if the string contains a non-printable character" do
- expect(PathHelper.printable?("\my files\work\notes.txt")).to be_falsey
- end
-
- # This isn't necessarily a requirement, but here to be explicit about functionality.
- it "returns false if the string contains a newline or tab" do
- expect(PathHelper.printable?("\tThere's no way,\n\t *no* way,\n\t that you came from my loins.\n")).to be_falsey
- end
- end
-
- describe "canonical_path" do
- context "on windows", :windows_only do
- it "returns an absolute path with backslashes instead of slashes" do
- expect(PathHelper.canonical_path("\\\\?\\C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini")
- end
-
- it "adds the \\\\?\\ prefix if it is missing" do
- expect(PathHelper.canonical_path("C:/windows/win.ini")).to eq("\\\\?\\c:\\windows\\win.ini")
- end
-
- it "returns a lowercase path" do
- expect(PathHelper.canonical_path("\\\\?\\C:\\CASE\\INSENSITIVE")).to eq("\\\\?\\c:\\case\\insensitive")
- end
- end
-
- context "not on windows", :unix_only do
- it "returns a canonical path" do
- expect(PathHelper.canonical_path("/etc//apache.d/sites-enabled/../sites-available/default")).to eq("/etc/apache.d/sites-available/default")
- end
- end
- end
-
- describe "paths_eql?" do
- it "returns true if the paths are the same" do
- allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/bandit/bandit")
- allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit")
- expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_truthy
- end
-
- it "returns false if the paths are different" do
- allow(PathHelper).to receive(:canonical_path).with("bandit").and_return("c:/Bo/Bandit")
- allow(PathHelper).to receive(:canonical_path).with("../bandit/bandit").and_return("c:/bandit/bandit")
- expect(PathHelper.paths_eql?("bandit", "../bandit/bandit")).to be_falsey
- end
- end
-
- describe "escape_glob" do
- it "escapes characters reserved by glob" do
- path = "C:\\this\\*path\\[needs]\\escaping?"
- escaped_path = "C:\\\\this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
- expect(PathHelper.escape_glob(path)).to eq(escaped_path)
- end
-
- context "when given more than one argument" do
- it "joins, cleanpaths, and escapes characters reserved by glob" do
- args = ["this/*path", "[needs]", "escaping?"]
- escaped_path = if windows?
- "this\\\\\\*path\\\\\\[needs\\]\\\\escaping\\?"
- else
- "this/\\*path/\\[needs\\]/escaping\\?"
- end
- expect(PathHelper).to receive(:join).with(*args).and_call_original
- expect(PathHelper).to receive(:cleanpath).and_call_original
- expect(PathHelper.escape_glob(*args)).to eq(escaped_path)
- end
- end
- end
-end
diff --git a/spec/unit/util/powershell/ps_credential_spec.rb b/spec/unit/util/powershell/ps_credential_spec.rb
new file mode 100644
index 0000000000..bac58b02e5
--- /dev/null
+++ b/spec/unit/util/powershell/ps_credential_spec.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Jay Mundrawala <jdm@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'
+require 'chef/util/powershell/ps_credential'
+
+describe Chef::Util::Powershell::PSCredential do
+ let (:username) { 'foo' }
+ let (:password) { 'password' }
+
+ context 'when username and password are provided' do
+ let(:ps_credential) { Chef::Util::Powershell::PSCredential.new(username, password)}
+ context 'when calling to_psobject' do
+ it 'should create the script to create a PSCredential when calling' do
+ allow(ps_credential).to receive(:encrypt).with(password).and_return('encrypted')
+ expect(ps_credential.to_psobject).to eq(
+ "New-Object System.Management.Automation.PSCredential("\
+ "'#{username}',('encrypted' | ConvertTo-SecureString))")
+ end
+ end
+ end
+end
diff --git a/tasks/external_tests.rb b/tasks/external_tests.rb
new file mode 100644
index 0000000000..2ff991ddf7
--- /dev/null
+++ b/tasks/external_tests.rb
@@ -0,0 +1,29 @@
+task :chef_sugar_spec do
+ gem_path = Bundler.environment.specs['chef-sugar'].first.full_gem_path
+ system("cd #{gem_path} && rake")
+end
+
+task :foodcritic_spec do
+ gem_path = Bundler.environment.specs['foodcritic'].first.full_gem_path
+ system("cd #{gem_path} && rake test")
+end
+
+task :chefspec_spec do
+ gem_path = Bundler.environment.specs['chefspec'].first.full_gem_path
+ system("cd #{gem_path} && rake")
+end
+
+task :chef_rewind_spec do
+ gem_path = Bundler.environment.specs['chef-rewind'].first.full_gem_path
+ system("cd #{gem_path} && rake spec")
+end
+
+task :poise_spec do
+ gem_path = Bundler.environment.specs['poise'].first.full_gem_path
+ system("cd #{gem_path} && rake spec")
+end
+
+task :halite_spec do
+ gem_path = Bundler.environment.specs['halite'].first.full_gem_path
+ system("cd #{gem_path} && rake spec")
+end
diff --git a/tasks/maintainers.rb b/tasks/maintainers.rb
new file mode 100644
index 0000000000..5a2c8d9c2d
--- /dev/null
+++ b/tasks/maintainers.rb
@@ -0,0 +1,69 @@
+#
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'rake'
+
+SOURCE = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.toml")
+TARGET = File.join(File.dirname(__FILE__), "..", "MAINTAINERS.md")
+
+begin
+ require 'tomlrb'
+ task :default => :generate
+
+ namespace :maintainers do
+ desc "Generate MarkDown version of MAINTAINERS file"
+ task :generate do
+ maintainers = Tomlrb.load_file SOURCE
+ out = "<!-- This is a generated file. Please do not edit directly -->\n\n"
+ out << "# " + maintainers["Preamble"]["title"] + "\n\n"
+ out << maintainers["Preamble"]["text"] + "\n"
+ out << "# " + maintainers["Org"]["Lead"]["title"] + "\n\n"
+ out << person(maintainers["people"], maintainers["Org"]["Lead"]["person"]) + "\n\n"
+ out << components(maintainers["people"], maintainers["Org"]["Components"])
+ File.open(TARGET, "w") { |fn|
+ fn.write out
+ }
+ end
+ end
+
+ def components(list, cmp)
+ out = "## " + cmp.delete("title") + "\n\n"
+ out << cmp.delete("text") + "\n" if cmp.has_key?("text")
+ if cmp.has_key?("lieutenant")
+ out << "### Lieutenant\n\n"
+ out << person(list, cmp.delete("lieutenant")) + "\n\n"
+ end
+ out << maintainers(list, cmp.delete("maintainers")) + "\n" if cmp.has_key?("maintainers")
+ cmp.delete("paths")
+ cmp.each {|k,v| out << components(list, v) }
+ out
+ end
+
+ def maintainers(list, people)
+ o = "### Maintainers\n\n"
+ people.each do |p|
+ o << person(list, p) + "\n"
+ end
+ o
+ end
+
+ def person(list, person)
+ "* [#{list[person]["Name"]}](https://github.com/#{list[person]["GitHub"]})"
+ end
+rescue LoadError
+ STDERR.puts "\n*** TomlRb not available.\n\n"
+end
diff --git a/tasks/rspec.rb b/tasks/rspec.rb
index a6fc5a9180..6e802d3df8 100644
--- a/tasks/rspec.rb
+++ b/tasks/rspec.rb
@@ -25,13 +25,26 @@ CHEF_ROOT = File.join(File.dirname(__FILE__), "..")
begin
require 'rspec/core/rake_task'
+
+ desc "Run specs for Chef's Components"
+ task :component_specs do
+ Dir.chdir("chef-config") do
+ Bundler.with_clean_env do
+ sh("bundle install --local")
+ sh("bundle exec rake spec")
+ end
+ end
+ end
+
task :default => :spec
+ task :spec => :component_specs
+
desc "Run standard specs (minus long running specs)"
RSpec::Core::RakeTask.new(:spec) do |t|
# right now this just limits to functional + unit, but could also remove
# individual tests marked long-running
- t.pattern = FileList['spec/{functional,unit}/**/*_spec.rb']
+ t.pattern = FileList['spec/**/*_spec.rb']
end
namespace :spec do