summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaniel Steen <dsteen@millennialmedia.com>2016-01-19 18:34:46 -0500
committerDaniel Steen <dsteen@millennialmedia.com>2016-01-19 18:34:46 -0500
commit7ec34e35657ca1f0330667da53bec1366ba5bdf8 (patch)
tree8f9df4bd48111b36baf5f8045be6c99035bc340c /lib
parent3c6bc501334880f01ff41dc3cc8d32122adcaeba (diff)
parent25b2c0559caaf29872d6cfe3ab146cb63c496d00 (diff)
downloadchef-7ec34e35657ca1f0330667da53bec1366ba5bdf8.tar.gz
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'lib')
-rw-r--r--lib/chef.rb29
-rw-r--r--lib/chef/api_client.rb40
-rw-r--r--lib/chef/api_client/registration.rb47
-rw-r--r--lib/chef/api_client_v1.rb325
-rw-r--r--lib/chef/application.rb126
-rw-r--r--lib/chef/application/apply.rb59
-rw-r--r--lib/chef/application/client.rb95
-rw-r--r--lib/chef/application/knife.rb30
-rw-r--r--lib/chef/application/solo.rb56
-rw-r--r--lib/chef/application/windows_service.rb55
-rw-r--r--lib/chef/application/windows_service_manager.rb68
-rw-r--r--lib/chef/applications.rb8
-rw-r--r--lib/chef/audit/audit_event_proxy.rb4
-rw-r--r--lib/chef/audit/audit_reporter.rb33
-rw-r--r--lib/chef/audit/control_group_data.rb8
-rw-r--r--lib/chef/audit/logger.rb36
-rw-r--r--lib/chef/audit/rspec_formatter.rb2
-rw-r--r--lib/chef/audit/runner.rb28
-rw-r--r--lib/chef/chef_class.rb225
-rw-r--r--lib/chef/chef_fs.rb52
-rw-r--r--lib/chef/chef_fs/chef_fs_data_store.rb540
-rw-r--r--lib/chef/chef_fs/command_line.rb12
-rw-r--r--lib/chef/chef_fs/config.rb74
-rw-r--r--lib/chef/chef_fs/data_handler/acl_data_handler.rb20
-rw-r--r--lib/chef/chef_fs/data_handler/client_data_handler.rb24
-rw-r--r--lib/chef/chef_fs/data_handler/container_data_handler.rb12
-rw-r--r--lib/chef/chef_fs/data_handler/cookbook_data_handler.rb30
-rw-r--r--lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb22
-rw-r--r--lib/chef/chef_fs/data_handler/data_handler_base.rb6
-rw-r--r--lib/chef/chef_fs/data_handler/environment_data_handler.rb28
-rw-r--r--lib/chef/chef_fs/data_handler/group_data_handler.rb36
-rw-r--r--lib/chef/chef_fs/data_handler/node_data_handler.rb28
-rw-r--r--lib/chef/chef_fs/data_handler/organization_data_handler.rb18
-rw-r--r--lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb4
-rw-r--r--lib/chef/chef_fs/data_handler/organization_members_data_handler.rb4
-rw-r--r--lib/chef/chef_fs/data_handler/policy_data_handler.rb33
-rw-r--r--lib/chef/chef_fs/data_handler/policy_group_data_handler.rb27
-rw-r--r--lib/chef/chef_fs/data_handler/role_data_handler.rb32
-rw-r--r--lib/chef/chef_fs/data_handler/user_data_handler.rb24
-rw-r--r--lib/chef/chef_fs/file_pattern.rb47
-rw-r--r--lib/chef/chef_fs/file_system.rb10
-rw-r--r--lib/chef/chef_fs/file_system/acl_dir.rb64
-rw-r--r--lib/chef/chef_fs/file_system/acls_dir.rb68
-rw-r--r--lib/chef/chef_fs/file_system/already_exists_error.rb5
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_dir.rb9
-rw-r--r--lib/chef/chef_fs/file_system/base_fs_object.rb19
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb109
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb87
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb89
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb92
-rw-r--r--lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb192
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acl_dir.rb65
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acl_entry.rb60
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/acls_dir.rb70
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb196
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb (renamed from lib/chef/chef_fs/file_system/chef_repository_file_system_policies_dir.rb)20
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb102
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb222
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb84
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb61
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb43
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb102
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb71
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb69
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/environments_dir.rb57
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb (renamed from lib/chef/chef_fs/file_system/acl_entry.rb)45
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/org_entry.rb31
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb61
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb60
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policies_dir.rb160
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb137
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb (renamed from lib/chef/chef_fs/file_system/cookbook_subdir.rb)39
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb25
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb179
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb187
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb45
-rw-r--r--lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb107
-rw-r--r--lib/chef/chef_fs/file_system/chef_server_root_dir.rb159
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_dir.rb224
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_file.rb82
-rw-r--r--lib/chef/chef_fs/file_system/cookbook_frozen_error.rb5
-rw-r--r--lib/chef/chef_fs/file_system/cookbooks_dir.rb164
-rw-r--r--lib/chef/chef_fs/file_system/data_bag_dir.rb69
-rw-r--r--lib/chef/chef_fs/file_system/data_bags_dir.rb73
-rw-r--r--lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb6
-rw-r--r--lib/chef/chef_fs/file_system/environments_dir.rb60
-rw-r--r--lib/chef/chef_fs/file_system/file_system_entry.rb108
-rw-r--r--lib/chef/chef_fs/file_system/file_system_error.rb13
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_dir.rb53
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_file.rb19
-rw-r--r--lib/chef/chef_fs/file_system/memory/memory_root.rb23
-rw-r--r--lib/chef/chef_fs/file_system/memory_dir.rb52
-rw-r--r--lib/chef/chef_fs/file_system/memory_file.rb17
-rw-r--r--lib/chef/chef_fs/file_system/memory_root.rb21
-rw-r--r--lib/chef/chef_fs/file_system/multiplexed_dir.rb19
-rw-r--r--lib/chef/chef_fs/file_system/must_delete_recursively_error.rb5
-rw-r--r--lib/chef/chef_fs/file_system/nodes_dir.rb55
-rw-r--r--lib/chef/chef_fs/file_system/nonexistent_fs_object.rb4
-rw-r--r--lib/chef/chef_fs/file_system/not_found_error.rb5
-rw-r--r--lib/chef/chef_fs/file_system/operation_failed_error.rb6
-rw-r--r--lib/chef/chef_fs/file_system/operation_not_allowed_error.rb30
-rw-r--r--lib/chef/chef_fs/file_system/org_entry.rb34
-rw-r--r--lib/chef/chef_fs/file_system/organization_invites_entry.rb59
-rw-r--r--lib/chef/chef_fs/file_system/organization_members_entry.rb58
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir.rb39
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb (renamed from lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb)26
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifacts_dir.rb (renamed from lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb)16
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb95
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb82
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb84
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir.rb (renamed from lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb)21
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb83
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_policies_dir.rb38
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb210
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir.rb42
-rw-r--r--lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbooks_dir.rb34
-rw-r--r--lib/chef/chef_fs/file_system/repository/file_system_entry.rb117
-rw-r--r--lib/chef/chef_fs/file_system/repository/file_system_root_dir.rb (renamed from lib/chef/chef_fs/file_system/file_system_root_dir.rb)10
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_dir.rb115
-rw-r--r--lib/chef/chef_fs/file_system/rest_list_entry.rb185
-rw-r--r--lib/chef/chef_fs/knife.rb66
-rw-r--r--lib/chef/chef_fs/parallelizer.rb4
-rw-r--r--lib/chef/chef_fs/parallelizer/parallel_enumerable.rb4
-rw-r--r--lib/chef/chef_fs/path_utils.rb103
-rw-r--r--lib/chef/client.rb904
-rw-r--r--lib/chef/config.rb737
-rw-r--r--lib/chef/config_fetcher.rb8
-rw-r--r--lib/chef/constants.rb (renamed from lib/chef/shell/shell_rest.rb)23
-rw-r--r--lib/chef/cookbook/chefignore.rb2
-rw-r--r--lib/chef/cookbook/cookbook_collection.rb17
-rw-r--r--lib/chef/cookbook/cookbook_version_loader.rb30
-rw-r--r--lib/chef/cookbook/file_system_file_vendor.rb2
-rw-r--r--lib/chef/cookbook/metadata.rb252
-rw-r--r--lib/chef/cookbook/remote_file_vendor.rb10
-rw-r--r--lib/chef/cookbook/synchronizer.rb19
-rw-r--r--lib/chef/cookbook/syntax_check.rb16
-rw-r--r--lib/chef/cookbook_loader.rb14
-rw-r--r--lib/chef/cookbook_manifest.rb28
-rw-r--r--lib/chef/cookbook_site_streaming_uploader.rb48
-rw-r--r--lib/chef/cookbook_uploader.rb35
-rw-r--r--lib/chef/cookbook_version.rb164
-rw-r--r--lib/chef/daemon.rb6
-rw-r--r--lib/chef/data_bag.rb43
-rw-r--r--lib/chef/data_bag_item.rb68
-rw-r--r--lib/chef/delayed_evaluator.rb21
-rw-r--r--lib/chef/deprecation/mixin/template.rb7
-rw-r--r--lib/chef/deprecation/provider/cookbook_file.rb2
-rw-r--r--lib/chef/deprecation/provider/file.rb6
-rw-r--r--lib/chef/deprecation/provider/remote_directory.rb52
-rw-r--r--lib/chef/deprecation/provider/remote_file.rb3
-rw-r--r--lib/chef/deprecation/provider/template.rb4
-rw-r--r--lib/chef/deprecation/warnings.rb7
-rw-r--r--lib/chef/digester.rb12
-rw-r--r--lib/chef/dsl.rb12
-rw-r--r--lib/chef/dsl/audit.rb4
-rw-r--r--lib/chef/dsl/chef_provisioning.rb57
-rw-r--r--lib/chef/dsl/cheffish.rb64
-rw-r--r--lib/chef/dsl/data_query.rb12
-rw-r--r--lib/chef/dsl/declare_resource.rb108
-rw-r--r--lib/chef/dsl/definitions.rb44
-rw-r--r--lib/chef/dsl/include_attribute.rb4
-rw-r--r--lib/chef/dsl/include_recipe.rb4
-rw-r--r--lib/chef/dsl/platform_introspection.rb12
-rw-r--r--lib/chef/dsl/powershell.rb4
-rw-r--r--lib/chef/dsl/reboot_pending.rb13
-rw-r--r--lib/chef/dsl/recipe.rb197
-rw-r--r--lib/chef/dsl/resources.rb57
-rw-r--r--lib/chef/encrypted_data_bag_item.rb14
-rw-r--r--lib/chef/encrypted_data_bag_item/assertions.rb4
-rw-r--r--lib/chef/encrypted_data_bag_item/check_encrypted.rb2
-rw-r--r--lib/chef/encrypted_data_bag_item/decryptor.rb20
-rw-r--r--lib/chef/encrypted_data_bag_item/encryptor.rb30
-rw-r--r--lib/chef/environment.rb51
-rw-r--r--lib/chef/event_dispatch/base.rb85
-rw-r--r--lib/chef/event_dispatch/dispatcher.rb36
-rw-r--r--lib/chef/event_dispatch/dsl.rb64
-rw-r--r--lib/chef/event_dispatch/events_output_stream.rb8
-rw-r--r--lib/chef/event_loggers/base.rb2
-rw-r--r--lib/chef/event_loggers/windows_eventlog.rb42
-rw-r--r--lib/chef/exceptions.rb82
-rw-r--r--lib/chef/file_access_control.rb6
-rw-r--r--lib/chef/file_access_control/unix.rb31
-rw-r--r--lib/chef/file_access_control/windows.rb4
-rw-r--r--lib/chef/file_cache.rb32
-rw-r--r--lib/chef/file_content_management/deploy.rb6
-rw-r--r--lib/chef/file_content_management/deploy/cp.rb4
-rw-r--r--lib/chef/file_content_management/deploy/mv_unix.rb8
-rw-r--r--lib/chef/file_content_management/deploy/mv_windows.rb28
-rw-r--r--lib/chef/file_content_management/tempfile.rb2
-rw-r--r--lib/chef/formatters/base.rb20
-rw-r--r--lib/chef/formatters/doc.rb90
-rw-r--r--lib/chef/formatters/error_descriptor.rb4
-rw-r--r--lib/chef/formatters/error_inspectors.rb10
-rw-r--r--lib/chef/formatters/error_inspectors/api_error_formatting.rb22
-rw-r--r--lib/chef/formatters/error_inspectors/compile_error_inspector.rb68
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb4
-rw-r--r--lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb4
-rw-r--r--lib/chef/formatters/error_inspectors/node_load_error_inspector.rb4
-rw-r--r--lib/chef/formatters/error_inspectors/registration_error_inspector.rb6
-rw-r--r--lib/chef/formatters/error_inspectors/resource_failure_inspector.rb14
-rw-r--r--lib/chef/formatters/error_inspectors/run_list_expansion_error_inspector.rb4
-rw-r--r--lib/chef/formatters/indentable_output_stream.rb15
-rw-r--r--lib/chef/formatters/minimal.rb10
-rw-r--r--lib/chef/guard_interpreter.rb4
-rw-r--r--lib/chef/guard_interpreter/default_guard_interpreter.rb2
-rw-r--r--lib/chef/guard_interpreter/resource_guard_interpreter.rb16
-rw-r--r--lib/chef/handler.rb4
-rw-r--r--lib/chef/handler/error_report.rb4
-rw-r--r--lib/chef/handler/json_file.rb4
-rw-r--r--lib/chef/http.rb60
-rw-r--r--lib/chef/http/auth_credentials.rb4
-rw-r--r--lib/chef/http/authenticator.rb14
-rw-r--r--lib/chef/http/basic_client.rb51
-rw-r--r--lib/chef/http/cookie_jar.rb2
-rw-r--r--lib/chef/http/cookie_manager.rb8
-rw-r--r--lib/chef/http/decompressor.rb10
-rw-r--r--lib/chef/http/http_request.rb50
-rw-r--r--lib/chef/http/json_input.rb13
-rw-r--r--lib/chef/http/json_output.rb8
-rw-r--r--lib/chef/http/json_to_model_output.rb2
-rw-r--r--lib/chef/http/remote_request_id.rb4
-rw-r--r--lib/chef/http/simple.rb28
-rw-r--r--lib/chef/http/simple_json.rb43
-rw-r--r--lib/chef/http/socketless_chef_zero_client.rb207
-rw-r--r--lib/chef/http/ssl_policies.rb6
-rw-r--r--lib/chef/http/validate_content_length.rb16
-rw-r--r--lib/chef/json_compat.rb7
-rw-r--r--lib/chef/key.rb272
-rw-r--r--lib/chef/knife.rb193
-rw-r--r--lib/chef/knife/bootstrap.rb156
-rw-r--r--lib/chef/knife/bootstrap/chef_vault_handler.rb34
-rw-r--r--lib/chef/knife/bootstrap/client_builder.rb40
-rw-r--r--lib/chef/knife/bootstrap/templates/README.md7
-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.rb8
-rw-r--r--lib/chef/knife/client_create.rb92
-rw-r--r--lib/chef/knife/client_delete.rb10
-rw-r--r--lib/chef/knife/client_edit.rb16
-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.rb8
-rw-r--r--lib/chef/knife/client_reregister.rb8
-rw-r--r--lib/chef/knife/client_show.rb8
-rw-r--r--lib/chef/knife/configure.rb20
-rw-r--r--lib/chef/knife/configure_client.rb8
-rw-r--r--lib/chef/knife/cookbook_bulk_delete.rb12
-rw-r--r--lib/chef/knife/cookbook_create.rb14
-rw-r--r--lib/chef/knife/cookbook_delete.rb14
-rw-r--r--lib/chef/knife/cookbook_download.rb11
-rw-r--r--lib/chef/knife/cookbook_list.rb4
-rw-r--r--lib/chef/knife/cookbook_metadata.rb12
-rw-r--r--lib/chef/knife/cookbook_metadata_from_file.rb4
-rw-r--r--lib/chef/knife/cookbook_show.rb24
-rw-r--r--lib/chef/knife/cookbook_site_download.rb42
-rw-r--r--lib/chef/knife/cookbook_site_install.rb22
-rw-r--r--lib/chef/knife/cookbook_site_list.rb6
-rw-r--r--lib/chef/knife/cookbook_site_search.rb4
-rw-r--r--lib/chef/knife/cookbook_site_share.rb38
-rw-r--r--lib/chef/knife/cookbook_site_show.rb8
-rw-r--r--lib/chef/knife/cookbook_site_unshare.rb10
-rw-r--r--lib/chef/knife/cookbook_site_vendor.rb6
-rw-r--r--lib/chef/knife/cookbook_test.rb6
-rw-r--r--lib/chef/knife/cookbook_upload.rb26
-rw-r--r--lib/chef/knife/core/bootstrap_context.rb57
-rw-r--r--lib/chef/knife/core/cookbook_scm_repo.rb14
-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.rb54
-rw-r--r--lib/chef/knife/core/hashed_command_loader.rb80
-rw-r--r--lib/chef/knife/core/node_editor.rb95
-rw-r--r--lib/chef/knife/core/node_presenter.rb43
-rw-r--r--lib/chef/knife/core/object_loader.rb11
-rw-r--r--lib/chef/knife/core/status_presenter.rb49
-rw-r--r--lib/chef/knife/core/subcommand_loader.rb283
-rw-r--r--lib/chef/knife/core/text_formatter.rb21
-rw-r--r--lib/chef/knife/core/ui.rb18
-rw-r--r--lib/chef/knife/data_bag_create.rb17
-rw-r--r--lib/chef/knife/data_bag_delete.rb8
-rw-r--r--lib/chef/knife/data_bag_edit.rb10
-rw-r--r--lib/chef/knife/data_bag_from_file.rb16
-rw-r--r--lib/chef/knife/data_bag_list.rb4
-rw-r--r--lib/chef/knife/data_bag_secret_options.rb8
-rw-r--r--lib/chef/knife/data_bag_show.rb58
-rw-r--r--lib/chef/knife/delete.rb12
-rw-r--r--lib/chef/knife/deps.rb32
-rw-r--r--lib/chef/knife/diff.rb16
-rw-r--r--lib/chef/knife/download.rb22
-rw-r--r--lib/chef/knife/edit.rb10
-rw-r--r--lib/chef/knife/environment_compare.rb10
-rw-r--r--lib/chef/knife/environment_create.rb6
-rw-r--r--lib/chef/knife/environment_delete.rb6
-rw-r--r--lib/chef/knife/environment_edit.rb6
-rw-r--r--lib/chef/knife/environment_from_file.rb4
-rw-r--r--lib/chef/knife/environment_list.rb6
-rw-r--r--lib/chef/knife/environment_show.rb6
-rw-r--r--lib/chef/knife/exec.rb10
-rw-r--r--lib/chef/knife/help.rb10
-rw-r--r--lib/chef/knife/index_rebuild.rb6
-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/list.rb27
-rw-r--r--lib/chef/knife/node_bulk_delete.rb6
-rw-r--r--lib/chef/knife/node_create.rb6
-rw-r--r--lib/chef/knife/node_delete.rb6
-rw-r--r--lib/chef/knife/node_edit.rb8
-rw-r--r--lib/chef/knife/node_environment_set.rb4
-rw-r--r--lib/chef/knife/node_from_file.rb10
-rw-r--r--lib/chef/knife/node_list.rb6
-rw-r--r--lib/chef/knife/node_run_list_add.rb10
-rw-r--r--lib/chef/knife/node_run_list_remove.rb23
-rw-r--r--lib/chef/knife/node_run_list_set.rb10
-rw-r--r--lib/chef/knife/node_show.rb8
-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/raw.rb35
-rw-r--r--lib/chef/knife/recipe_list.rb4
-rw-r--r--lib/chef/knife/rehash.rb62
-rw-r--r--lib/chef/knife/role_bulk_delete.rb6
-rw-r--r--lib/chef/knife/role_create.rb6
-rw-r--r--lib/chef/knife/role_delete.rb6
-rw-r--r--lib/chef/knife/role_edit.rb6
-rw-r--r--lib/chef/knife/role_env_run_list_add.rb10
-rw-r--r--lib/chef/knife/role_env_run_list_clear.rb6
-rw-r--r--lib/chef/knife/role_env_run_list_remove.rb6
-rw-r--r--lib/chef/knife/role_env_run_list_replace.rb6
-rw-r--r--lib/chef/knife/role_env_run_list_set.rb10
-rw-r--r--lib/chef/knife/role_from_file.rb8
-rw-r--r--lib/chef/knife/role_list.rb6
-rw-r--r--lib/chef/knife/role_run_list_add.rb10
-rw-r--r--lib/chef/knife/role_run_list_clear.rb6
-rw-r--r--lib/chef/knife/role_run_list_remove.rb6
-rw-r--r--lib/chef/knife/role_run_list_replace.rb6
-rw-r--r--lib/chef/knife/role_run_list_set.rb10
-rw-r--r--lib/chef/knife/role_show.rb6
-rw-r--r--lib/chef/knife/search.rb20
-rw-r--r--lib/chef/knife/serve.rb18
-rw-r--r--lib/chef/knife/show.rb8
-rw-r--r--lib/chef/knife/ssh.rb220
-rw-r--r--lib/chef/knife/ssl_check.rb25
-rw-r--r--lib/chef/knife/ssl_fetch.rb21
-rw-r--r--lib/chef/knife/status.rb60
-rw-r--r--lib/chef/knife/tag_create.rb4
-rw-r--r--lib/chef/knife/tag_delete.rb4
-rw-r--r--lib/chef/knife/tag_list.rb4
-rw-r--r--lib/chef/knife/upload.rb20
-rw-r--r--lib/chef/knife/user_create.rb137
-rw-r--r--lib/chef/knife/user_delete.rb60
-rw-r--r--lib/chef/knife/user_edit.rb51
-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.rb11
-rw-r--r--lib/chef/knife/user_reregister.rb53
-rw-r--r--lib/chef/knife/user_show.rb39
-rw-r--r--lib/chef/knife/xargs.rb42
-rw-r--r--lib/chef/local_mode.rb35
-rw-r--r--lib/chef/log.rb30
-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/checksum.rb4
-rw-r--r--lib/chef/mixin/command.rb14
-rw-r--r--lib/chef/mixin/command/unix.rb2
-rw-r--r--lib/chef/mixin/command/windows.rb2
-rw-r--r--lib/chef/mixin/convert_to_class_name.rb20
-rw-r--r--lib/chef/mixin/create_path.rb2
-rw-r--r--lib/chef/mixin/deprecation.rb24
-rw-r--r--lib/chef/mixin/enforce_ownership_and_permissions.rb2
-rw-r--r--lib/chef/mixin/file_class.rb14
-rw-r--r--lib/chef/mixin/get_source_from_package.rb9
-rw-r--r--lib/chef/mixin/homebrew_user.rb6
-rw-r--r--lib/chef/mixin/language.rb6
-rw-r--r--lib/chef/mixin/language_include_attribute.rb4
-rw-r--r--lib/chef/mixin/language_include_recipe.rb4
-rw-r--r--lib/chef/mixin/params_validate.rb497
-rw-r--r--lib/chef/mixin/path_sanity.rb8
-rw-r--r--lib/chef/mixin/powershell_out.rb98
-rw-r--r--lib/chef/mixin/powershell_type_coercions.rb8
-rw-r--r--lib/chef/mixin/properties.rb302
-rw-r--r--lib/chef/mixin/provides.rb27
-rw-r--r--lib/chef/mixin/proxified_socket.rb38
-rw-r--r--lib/chef/mixin/recipe_definition_dsl_core.rb2
-rw-r--r--lib/chef/mixin/securable.rb18
-rw-r--r--lib/chef/mixin/shell_out.rb16
-rw-r--r--lib/chef/mixin/subclass_directive.rb37
-rw-r--r--lib/chef/mixin/template.rb52
-rw-r--r--lib/chef/mixin/unformatter.rb32
-rw-r--r--lib/chef/mixin/uris.rb44
-rw-r--r--lib/chef/mixin/which.rb6
-rw-r--r--lib/chef/mixin/why_run.rb4
-rw-r--r--lib/chef/mixin/wide_string.rb72
-rw-r--r--lib/chef/mixin/windows_architecture_helper.rb71
-rw-r--r--lib/chef/mixin/windows_env_helper.rb22
-rw-r--r--lib/chef/mixin/xml_escape.rb10
-rw-r--r--lib/chef/mixins.rb26
-rw-r--r--lib/chef/monkey_patches/net-ssh-multi.rb4
-rw-r--r--lib/chef/monkey_patches/net_http.rb2
-rw-r--r--lib/chef/monkey_patches/webrick-utils.rb51
-rw-r--r--lib/chef/monkey_patches/win32/registry.rb72
-rw-r--r--lib/chef/monologger.rb4
-rw-r--r--lib/chef/node.rb229
-rw-r--r--lib/chef/node/attribute.rb18
-rw-r--r--lib/chef/node/attribute_collections.rb8
-rw-r--r--lib/chef/node/immutable_collections.rb4
-rw-r--r--lib/chef/node_map.rb242
-rw-r--r--lib/chef/org.rb36
-rw-r--r--lib/chef/platform.rb4
-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.rb309
-rw-r--r--lib/chef/platform/provider_priority_map.rb80
-rw-r--r--lib/chef/platform/query_helpers.rb84
-rw-r--r--lib/chef/platform/rebooter.rb18
-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.rb88
-rw-r--r--lib/chef/policy_builder.rb13
-rw-r--r--lib/chef/policy_builder/dynamic.rb185
-rw-r--r--lib/chef/policy_builder/expand_node_object.rb85
-rw-r--r--lib/chef/policy_builder/policyfile.rb188
-rw-r--r--lib/chef/property.rb607
-rw-r--r--lib/chef/provider.rb335
-rw-r--r--lib/chef/provider/batch.rb14
-rw-r--r--lib/chef/provider/cookbook_file.rb6
-rw-r--r--lib/chef/provider/cookbook_file/content.rb4
-rw-r--r--lib/chef/provider/cron.rb8
-rw-r--r--lib/chef/provider/cron/unix.rb9
-rw-r--r--lib/chef/provider/deploy.rb16
-rw-r--r--lib/chef/provider/deploy/revision.rb6
-rw-r--r--lib/chef/provider/directory.rb31
-rw-r--r--lib/chef/provider/dsc_resource.rb97
-rw-r--r--lib/chef/provider/dsc_script.rb26
-rw-r--r--lib/chef/provider/env.rb32
-rw-r--r--lib/chef/provider/env/windows.rb4
-rw-r--r--lib/chef/provider/erl_call.rb8
-rw-r--r--lib/chef/provider/execute.rb35
-rw-r--r--lib/chef/provider/file.rb43
-rw-r--r--lib/chef/provider/file/content.rb4
-rw-r--r--lib/chef/provider/git.rb42
-rw-r--r--lib/chef/provider/group.rb10
-rw-r--r--lib/chef/provider/group/aix.rb7
-rw-r--r--lib/chef/provider/group/dscl.rb8
-rw-r--r--lib/chef/provider/group/gpasswd.rb3
-rw-r--r--lib/chef/provider/group/groupadd.rb10
-rw-r--r--lib/chef/provider/group/pw.rb3
-rw-r--r--lib/chef/provider/group/suse.rb4
-rw-r--r--lib/chef/provider/group/usermod.rb13
-rw-r--r--lib/chef/provider/group/windows.rb4
-rw-r--r--lib/chef/provider/http_request.rb14
-rw-r--r--lib/chef/provider/ifconfig.rb30
-rw-r--r--lib/chef/provider/ifconfig/aix.rb3
-rw-r--r--lib/chef/provider/ifconfig/debian.rb6
-rw-r--r--lib/chef/provider/ifconfig/redhat.rb3
-rw-r--r--lib/chef/provider/link.rb20
-rw-r--r--lib/chef/provider/lwrp_base.rb153
-rw-r--r--lib/chef/provider/mdadm.rb4
-rw-r--r--lib/chef/provider/mount.rb31
-rw-r--r--lib/chef/provider/mount/aix.rb21
-rw-r--r--lib/chef/provider/mount/mount.rb30
-rw-r--r--lib/chef/provider/mount/solaris.rb56
-rw-r--r--lib/chef/provider/mount/windows.rb6
-rw-r--r--lib/chef/provider/ohai.rb3
-rw-r--r--lib/chef/provider/osx_profile.rb254
-rw-r--r--lib/chef/provider/package.rb163
-rw-r--r--lib/chef/provider/package/aix.rb25
-rw-r--r--lib/chef/provider/package/apt.rb27
-rw-r--r--lib/chef/provider/package/chocolatey.rb257
-rw-r--r--lib/chef/provider/package/dpkg.rb231
-rw-r--r--lib/chef/provider/package/easy_install.rb23
-rw-r--r--lib/chef/provider/package/freebsd/base.rb10
-rw-r--r--lib/chef/provider/package/freebsd/pkg.rb16
-rw-r--r--lib/chef/provider/package/freebsd/pkgng.rb14
-rw-r--r--lib/chef/provider/package/freebsd/port.rb10
-rw-r--r--lib/chef/provider/package/homebrew.rb29
-rw-r--r--lib/chef/provider/package/ips.rb19
-rw-r--r--lib/chef/provider/package/macports.rb12
-rw-r--r--lib/chef/provider/package/openbsd.rb34
-rw-r--r--lib/chef/provider/package/pacman.rb19
-rw-r--r--lib/chef/provider/package/paludis.rb14
-rw-r--r--lib/chef/provider/package/portage.rb18
-rw-r--r--lib/chef/provider/package/rpm.rb30
-rw-r--r--lib/chef/provider/package/rubygems.rb113
-rw-r--r--lib/chef/provider/package/smartos.rb22
-rw-r--r--lib/chef/provider/package/solaris.rb31
-rw-r--r--lib/chef/provider/package/windows.rb216
-rw-r--r--lib/chef/provider/package/windows/exe.rb117
-rw-r--r--lib/chef/provider/package/windows/msi.rb58
-rw-r--r--lib/chef/provider/package/windows/registry_uninstall_entry.rb89
-rw-r--r--lib/chef/provider/package/yum.rb193
-rw-r--r--lib/chef/provider/package/zypper.rb42
-rw-r--r--lib/chef/provider/powershell_script.rb215
-rw-r--r--lib/chef/provider/reboot.rb7
-rw-r--r--lib/chef/provider/registry_key.rb30
-rw-r--r--lib/chef/provider/remote_directory.rb300
-rw-r--r--lib/chef/provider/remote_file.rb7
-rw-r--r--lib/chef/provider/remote_file/cache_control_data.rb51
-rw-r--r--lib/chef/provider/remote_file/content.rb19
-rw-r--r--lib/chef/provider/remote_file/fetcher.rb30
-rw-r--r--lib/chef/provider/remote_file/ftp.rb20
-rw-r--r--lib/chef/provider/remote_file/http.rb14
-rw-r--r--lib/chef/provider/remote_file/local_file.rb20
-rw-r--r--lib/chef/provider/remote_file/network_file.rb48
-rw-r--r--lib/chef/provider/route.rb84
-rw-r--r--lib/chef/provider/script.rb7
-rw-r--r--lib/chef/provider/service.rb81
-rw-r--r--lib/chef/provider/service/aix.rb27
-rw-r--r--lib/chef/provider/service/aixinit.rb16
-rw-r--r--lib/chef/provider/service/arch.rb4
-rw-r--r--lib/chef/provider/service/debian.rb14
-rw-r--r--lib/chef/provider/service/freebsd.rb14
-rw-r--r--lib/chef/provider/service/gentoo.rb12
-rw-r--r--lib/chef/provider/service/init.rb11
-rw-r--r--lib/chef/provider/service/insserv.rb10
-rw-r--r--lib/chef/provider/service/invokercd.rb8
-rw-r--r--lib/chef/provider/service/macosx.rb114
-rw-r--r--lib/chef/provider/service/openbsd.rb29
-rw-r--r--lib/chef/provider/service/redhat.rb72
-rw-r--r--lib/chef/provider/service/simple.rb18
-rw-r--r--lib/chef/provider/service/solaris.rb66
-rw-r--r--lib/chef/provider/service/systemd.rb14
-rw-r--r--lib/chef/provider/service/upstart.rb25
-rw-r--r--lib/chef/provider/service/windows.rb102
-rw-r--r--lib/chef/provider/subversion.rb32
-rw-r--r--lib/chef/provider/template.rb8
-rw-r--r--lib/chef/provider/template/content.rb26
-rw-r--r--lib/chef/provider/user.rb14
-rw-r--r--lib/chef/provider/user/aix.rb5
-rw-r--r--lib/chef/provider/user/dscl.rb243
-rw-r--r--lib/chef/provider/user/pw.rb13
-rw-r--r--lib/chef/provider/user/solaris.rb38
-rw-r--r--lib/chef/provider/user/useradd.rb13
-rw-r--r--lib/chef/provider/user/windows.rb24
-rw-r--r--lib/chef/provider/windows_script.rb14
-rw-r--r--lib/chef/provider_resolver.rb149
-rw-r--r--lib/chef/providers.rb219
-rw-r--r--lib/chef/recipe.rb37
-rw-r--r--lib/chef/request_id.rb4
-rw-r--r--lib/chef/resource.rb819
-rw-r--r--lib/chef/resource/action_class.rb87
-rw-r--r--lib/chef/resource/apt_package.rb21
-rw-r--r--lib/chef/resource/bash.rb5
-rw-r--r--lib/chef/resource/batch.rb4
-rw-r--r--lib/chef/resource/bff_package.rb12
-rw-r--r--lib/chef/resource/breakpoint.rb10
-rw-r--r--lib/chef/resource/chef_gem.rb41
-rw-r--r--lib/chef/resource/chocolatey_package.rb39
-rw-r--r--lib/chef/resource/conditional.rb4
-rw-r--r--lib/chef/resource/cookbook_file.rb10
-rw-r--r--lib/chef/resource/cron.rb36
-rw-r--r--lib/chef/resource/csh.rb5
-rw-r--r--lib/chef/resource/deploy.rb88
-rw-r--r--lib/chef/resource/deploy_revision.rb14
-rw-r--r--lib/chef/resource/directory.rb16
-rw-r--r--lib/chef/resource/dpkg_package.rb13
-rw-r--r--lib/chef/resource/dsc_resource.rb203
-rw-r--r--lib/chef/resource/dsc_script.rb31
-rw-r--r--lib/chef/resource/easy_install_package.rb36
-rw-r--r--lib/chef/resource/env.rb12
-rw-r--r--lib/chef/resource/erl_call.rb20
-rw-r--r--lib/chef/resource/execute.rb41
-rw-r--r--lib/chef/resource/file.rb125
-rw-r--r--lib/chef/resource/file/verification.rb16
-rw-r--r--lib/chef/resource/freebsd_package.rb18
-rw-r--r--lib/chef/resource/gem_package.rb30
-rw-r--r--lib/chef/resource/git.rb5
-rw-r--r--lib/chef/resource/group.rb20
-rw-r--r--lib/chef/resource/homebrew_package.rb21
-rw-r--r--lib/chef/resource/http_request.rb16
-rw-r--r--lib/chef/resource/ifconfig.rb34
-rw-r--r--lib/chef/resource/ips_package.rb22
-rw-r--r--lib/chef/resource/ksh.rb32
-rw-r--r--lib/chef/resource/link.rb24
-rw-r--r--lib/chef/resource/log.rb15
-rw-r--r--lib/chef/resource/lwrp_base.rb183
-rw-r--r--lib/chef/resource/macosx_service.rb58
-rw-r--r--lib/chef/resource/macports_package.rb11
-rw-r--r--lib/chef/resource/mdadm.rb23
-rw-r--r--lib/chef/resource/mount.rb46
-rw-r--r--lib/chef/resource/ohai.rb9
-rw-r--r--lib/chef/resource/openbsd_package.rb24
-rw-r--r--lib/chef/resource/osx_profile.rb74
-rw-r--r--lib/chef/resource/package.rb86
-rw-r--r--lib/chef/resource/pacman_package.rb10
-rw-r--r--lib/chef/resource/paludis_package.rb15
-rw-r--r--lib/chef/resource/perl.rb6
-rw-r--r--lib/chef/resource/portage_package.rb5
-rw-r--r--lib/chef/resource/powershell_script.rb7
-rw-r--r--lib/chef/resource/python.rb6
-rw-r--r--lib/chef/resource/reboot.rb6
-rw-r--r--lib/chef/resource/registry_key.rb23
-rw-r--r--lib/chef/resource/remote_directory.rb30
-rw-r--r--lib/chef/resource/remote_file.rb27
-rw-r--r--lib/chef/resource/resource_notification.rb17
-rw-r--r--lib/chef/resource/route.rb33
-rw-r--r--lib/chef/resource/rpm_package.rb20
-rw-r--r--lib/chef/resource/ruby.rb7
-rw-r--r--lib/chef/resource/ruby_block.rb11
-rw-r--r--lib/chef/resource/scm.rb41
-rw-r--r--lib/chef/resource/script.rb14
-rw-r--r--lib/chef/resource/service.rb47
-rw-r--r--lib/chef/resource/smartos_package.rb14
-rw-r--r--lib/chef/resource/solaris_package.rb20
-rw-r--r--lib/chef/resource/subversion.rb12
-rw-r--r--lib/chef/resource/template.rb18
-rw-r--r--lib/chef/resource/timestamped_deploy.rb4
-rw-r--r--lib/chef/resource/user.rb37
-rw-r--r--lib/chef/resource/whyrun_safe_ruby_block.rb6
-rw-r--r--lib/chef/resource/windows_package.rb69
-rw-r--r--lib/chef/resource/windows_script.rb19
-rw-r--r--lib/chef/resource/windows_service.rb14
-rw-r--r--lib/chef/resource/yum_package.rb52
-rw-r--r--lib/chef/resource/zypper_package.rb28
-rw-r--r--lib/chef/resource_builder.rb20
-rw-r--r--lib/chef/resource_collection.rb10
-rw-r--r--lib/chef/resource_collection/resource_collection_serialization.rb4
-rw-r--r--lib/chef/resource_collection/resource_list.rb8
-rw-r--r--lib/chef/resource_collection/resource_set.rb4
-rw-r--r--lib/chef/resource_definition.rb7
-rw-r--r--lib/chef/resource_definition_list.rb4
-rw-r--r--lib/chef/resource_reporter.rb38
-rw-r--r--lib/chef/resource_resolver.rb186
-rw-r--r--lib/chef/resources.rb141
-rw-r--r--lib/chef/rest.rb36
-rw-r--r--lib/chef/role.rb60
-rw-r--r--lib/chef/run_context.rb549
-rw-r--r--lib/chef/run_context/cookbook_compiler.rb12
-rw-r--r--lib/chef/run_list.rb14
-rw-r--r--lib/chef/run_list/run_list_expansion.rb61
-rw-r--r--lib/chef/run_list/run_list_item.rb10
-rw-r--r--lib/chef/run_list/versioned_recipe_list.rb40
-rw-r--r--lib/chef/run_lock.rb63
-rw-r--r--lib/chef/run_status.rb6
-rw-r--r--lib/chef/runner.rb33
-rw-r--r--lib/chef/scan_access_control.rb12
-rw-r--r--lib/chef/search/query.rb54
-rw-r--r--lib/chef/server_api.rb43
-rw-r--r--lib/chef/shell.rb46
-rw-r--r--lib/chef/shell/ext.rb90
-rw-r--r--lib/chef/shell/model_wrapper.rb4
-rw-r--r--lib/chef/shell/shell_session.rb29
-rw-r--r--lib/chef/shell_out.rb2
-rw-r--r--lib/chef/tasks/chef_repo.rake118
-rw-r--r--lib/chef/user.rb71
-rw-r--r--lib/chef/user_v1.rb329
-rw-r--r--lib/chef/util/backup.rb14
-rw-r--r--lib/chef/util/diff.rb14
-rw-r--r--lib/chef/util/dsc/configuration_generator.rb14
-rw-r--r--lib/chef/util/dsc/lcm_output_parser.rb8
-rw-r--r--lib/chef/util/dsc/local_configuration_manager.rb14
-rw-r--r--lib/chef/util/dsc/resource_store.rb14
-rw-r--r--lib/chef/util/file_edit.rb6
-rw-r--r--lib/chef/util/path_helper.rb208
-rw-r--r--lib/chef/util/powershell/cmdlet.rb28
-rw-r--r--lib/chef/util/powershell/cmdlet_result.rb2
-rw-r--r--lib/chef/util/powershell/ps_credential.rb5
-rw-r--r--lib/chef/util/selinux.rb4
-rw-r--r--lib/chef/util/threaded_job_queue.rb2
-rw-r--r--lib/chef/util/windows.rb32
-rw-r--r--lib/chef/util/windows/net_group.rb89
-rw-r--r--lib/chef/util/windows/net_use.rb108
-rw-r--r--lib/chef/util/windows/net_user.rb196
-rw-r--r--lib/chef/util/windows/volume.rb40
-rw-r--r--lib/chef/version.rb16
-rw-r--r--lib/chef/version/platform.rb4
-rw-r--r--lib/chef/version_class.rb2
-rw-r--r--lib/chef/version_constraint.rb14
-rw-r--r--lib/chef/version_constraint/platform.rb4
-rw-r--r--lib/chef/whitelist.rb2
-rw-r--r--lib/chef/win32/api.rb15
-rw-r--r--lib/chef/win32/api/crypto.rb126
-rw-r--r--lib/chef/win32/api/error.rb4
-rw-r--r--lib/chef/win32/api/file.rb78
-rw-r--r--lib/chef/win32/api/installer.rb12
-rw-r--r--lib/chef/win32/api/memory.rb4
-rw-r--r--lib/chef/win32/api/net.rb271
-rw-r--r--lib/chef/win32/api/process.rb4
-rw-r--r--lib/chef/win32/api/psapi.rb4
-rw-r--r--lib/chef/win32/api/registry.rb51
-rw-r--r--lib/chef/win32/api/security.rb70
-rw-r--r--lib/chef/win32/api/synchronization.rb4
-rw-r--r--lib/chef/win32/api/system.rb27
-rw-r--r--lib/chef/win32/api/unicode.rb47
-rw-r--r--lib/chef/win32/crypto.rb99
-rw-r--r--lib/chef/win32/error.rb11
-rw-r--r--lib/chef/win32/eventlog.rb31
-rw-r--r--lib/chef/win32/file.rb44
-rw-r--r--lib/chef/win32/file/info.rb2
-rw-r--r--lib/chef/win32/file/version_info.rb93
-rw-r--r--lib/chef/win32/handle.rb8
-rw-r--r--lib/chef/win32/memory.rb4
-rw-r--r--lib/chef/win32/mutex.rb7
-rw-r--r--lib/chef/win32/net.rb344
-rw-r--r--lib/chef/win32/process.rb23
-rw-r--r--lib/chef/win32/registry.rb88
-rw-r--r--lib/chef/win32/security.rb138
-rw-r--r--lib/chef/win32/security/ace.rb8
-rw-r--r--lib/chef/win32/security/acl.rb6
-rw-r--r--lib/chef/win32/security/securable_object.rb6
-rw-r--r--lib/chef/win32/security/security_descriptor.rb6
-rw-r--r--lib/chef/win32/security/sid.rb99
-rw-r--r--lib/chef/win32/security/token.rb8
-rwxr-xr-xlib/chef/win32/system.rb62
-rw-r--r--lib/chef/win32/unicode.rb11
-rw-r--r--lib/chef/win32/version.rb18
-rw-r--r--lib/chef/workstation_config_loader.rb161
724 files changed, 24115 insertions, 11989 deletions
diff --git a/lib/chef.rb b/lib/chef.rb
index 7f54b91f14..37f987991a 100644
--- a/lib/chef.rb
+++ b/lib/chef.rb
@@ -16,19 +16,20 @@
# limitations under the License.
#
-require 'chef/version'
-require 'chef/nil_argument'
-require 'chef/mash'
-require 'chef/exceptions'
-require 'chef/log'
-require 'chef/config'
-require 'chef/providers'
-require 'chef/resources'
-require 'chef/shell_out'
+require "chef/version"
+require "chef/nil_argument"
+require "chef/mash"
+require "chef/exceptions"
+require "chef/log"
+require "chef/config"
+require "chef/providers"
+require "chef/resources"
+require "chef/shell_out"
-require 'chef/daemon'
-
-require 'chef/run_status'
-require 'chef/handler'
-require 'chef/handler/json_file'
+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..9def6199b7 100644
--- a/lib/chef/api_client.rb
+++ b/lib/chef/api_client.rb
@@ -17,13 +17,19 @@
# 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/config"
+require "chef/mixin/params_validate"
+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
@@ -32,7 +38,7 @@ class Chef
# Create a new Chef::ApiClient object.
def initialize
- @name = ''
+ @name = ""
@public_key = nil
@private_key = nil
@admin = false
@@ -47,7 +53,7 @@ class Chef
set_or_return(
:name,
arg,
- :regex => /^[\-[:alnum:]_\.]+$/
+ :regex => /^[\-[:alnum:]_\.]+$/,
)
end
@@ -59,7 +65,7 @@ class Chef
set_or_return(
:admin,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -71,7 +77,7 @@ class Chef
set_or_return(
:public_key,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -84,7 +90,7 @@ class Chef
set_or_return(
:validator,
arg,
- :kind_of => [TrueClass, FalseClass]
+ :kind_of => [TrueClass, FalseClass],
)
end
@@ -96,7 +102,7 @@ class Chef
set_or_return(
:private_key,
arg,
- :kind_of => [String, FalseClass]
+ :kind_of => [String, FalseClass],
)
end
@@ -110,8 +116,8 @@ class Chef
"public_key" => @public_key,
"validator" => @validator,
"admin" => @admin,
- 'json_class' => self.class.name,
- "chef_type" => "client"
+ "json_class" => self.class.name,
+ "chef_type" => "client",
}
result["private_key"] = @private_key if @private_key
result
@@ -143,7 +149,7 @@ class Chef
end
def self.http_api
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
end
def self.reregister(name)
@@ -219,7 +225,7 @@ class Chef
end
def http_api
- @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url], {:api_version => "0"})
end
end
diff --git a/lib/chef/api_client/registration.rb b/lib/chef/api_client/registration.rb
index de5fc7ac3d..2128c3b477 100644
--- a/lib/chef/api_client/registration.rb
+++ b/lib/chef/api_client/registration.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/rest'
-require 'chef/exceptions'
+require "chef/config"
+require "chef/server_api"
+require "chef/exceptions"
class Chef
class ApiClient
@@ -45,7 +45,7 @@ class Chef
#--
# If client creation fails with a 5xx, it is retried up to 5 times. These
# retries are on top of the retries with randomized exponential backoff
- # built in to Chef::REST. The retries here are a workaround for failures
+ # built in to Chef::ServerAPI. The retries here are a workaround for failures
# caused by resource contention in Hosted Chef when creating a very large
# number of clients simultaneously, (e.g., spinning up 100s of ec2 nodes
# at once). Future improvements to the affected component should make
@@ -53,8 +53,9 @@ class Chef
def run
assert_destination_writable!
retries = Config[:client_registration_retries] || 5
+ client = nil
begin
- create_or_update
+ client = api_client(create_or_update)
rescue Net::HTTPFatalError => e
# HTTPFatalError implies 5xx.
raise if retries <= 0
@@ -64,11 +65,13 @@ class Chef
retry
end
write_key
+ client
end
def assert_destination_writable!
if (File.exists?(destination) && !File.writable?(destination)) or !File.writable?(File.dirname(destination))
- raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?"
+ abs_path = File.expand_path(destination)
+ raise Chef::Exceptions::CannotWritePrivateKey, "I can't write your private key to #{abs_path} - check permissions?"
end
end
@@ -105,6 +108,28 @@ class Chef
response
end
+ def api_client(response)
+ return response if response.is_a?(Chef::ApiClient)
+
+ client = Chef::ApiClient.new
+ client.name(name)
+ client.public_key(api_client_key(response, "public_key"))
+ client.private_key(api_client_key(response, "private_key"))
+ client
+ end
+
+ def api_client_key(response, key_name)
+ if response[key_name]
+ if response[key_name].respond_to?(:to_pem)
+ response[key_name].to_pem
+ else
+ response[key_name]
+ end
+ elsif response["chef_key"]
+ response["chef_key"][key_name]
+ end
+ end
+
def put_data
base_put_data = { :name => name, :admin => false }
if self_generate_keys?
@@ -122,9 +147,13 @@ class Chef
end
def http_api
- @http_api ||= Chef::REST.new(Chef::Config[:chef_server_url],
- Chef::Config[:validation_client_name],
- Chef::Config[:validation_key])
+ @http_api ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url],
+ {
+ :api_version => "0",
+ :client_name => Chef::Config[:validation_client_name],
+ :signing_key_filename => Chef::Config[:validation_key],
+ },
+ )
end
# Whether or not to generate keys locally and post the public key to the
diff --git a/lib/chef/api_client_v1.rb b/lib/chef/api_client_v1.rb
new file mode 100644
index 0000000000..6b3db812c0
--- /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..4562d84a5d 100644
--- a/lib/chef/application.rb
+++ b/lib/chef/application.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@opscode.com>)
# Author:: Mark Mzyk (mmzyk@opscode.com)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,18 +16,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'pp'
-require 'uri'
-require 'socket'
-require 'chef/config'
-require 'chef/config_fetcher'
-require 'chef/exceptions'
-require 'chef/local_mode'
-require 'chef/log'
-require 'chef/platform'
-require 'mixlib/cli'
-require 'tmpdir'
-require 'rbconfig'
+require "pp"
+require "socket"
+require "chef/config"
+require "chef/config_fetcher"
+require "chef/exceptions"
+require "chef/local_mode"
+require "chef/log"
+require "chef/platform"
+require "mixlib/cli"
+require "tmpdir"
+require "rbconfig"
class Chef
class Application
@@ -47,7 +46,6 @@ class Chef
def reconfigure
configure_chef
configure_logging
- configure_proxy_environment_variables
configure_encoding
emit_warnings
end
@@ -85,6 +83,7 @@ class Chef
def configure_chef
parse_options
load_config_file
+ Chef::Config.export_proxies
end
# Parse the config file
@@ -93,7 +92,6 @@ class Chef
if config[:config_file].nil?
Chef::Log.warn("No config file found or specified on command line, using command line options.")
elsif config_fetcher.config_missing?
- pp config_missing: true
Chef::Log.warn("*****************************************")
Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
Chef::Log.warn("*****************************************")
@@ -181,14 +179,6 @@ class Chef
end
end
- # Configure and set any proxy environment variables according to the config.
- def configure_proxy_environment_variables
- configure_http_proxy
- configure_https_proxy
- configure_ftp_proxy
- configure_no_proxy
- end
-
# Sets the default external encoding to UTF-8 (users can change this, but they shouldn't)
def configure_encoding
Encoding.default_external = Chef::Config[:ruby_encoding]
@@ -196,18 +186,18 @@ class Chef
# Called prior to starting the application, by the run method
def setup_application
- raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application"
+ raise Chef::Exceptions::Application, "#{self}: you must override setup_application"
end
# Actually run the application
def run_application
- raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application"
+ raise Chef::Exceptions::Application, "#{self}: you must override run_application"
end
# Initializes Chef::Client instance and runs it
def run_chef_client(specific_recipes = [])
unless specific_recipes.respond_to?(:size)
- raise ArgumentError, 'received non-Array like specific_recipes argument'
+ raise ArgumentError, "received non-Array like specific_recipes argument"
end
Chef::LocalMode.with_server_connectivity do
@@ -217,7 +207,7 @@ class Chef
@chef_client_json,
override_runlist: override_runlist,
specific_recipes: specific_recipes,
- runlist: config[:runlist]
+ runlist: config[:runlist],
)
@chef_client_json = nil
@@ -245,7 +235,7 @@ class Chef
# signal to finish the converge and exists.
def run_with_graceful_exit_option
# Override the TERM signal.
- trap('TERM') do
+ trap("TERM") do
Chef::Log.debug("SIGTERM received during converge," +
" finishing converge to exit normally (send SIGINT to terminate immediately)")
end
@@ -259,7 +249,7 @@ class Chef
pid = fork do
# Want to allow forked processes to finish converging when
# TERM singal is received (exit gracefully)
- trap('TERM') do
+ trap("TERM") do
Chef::Log.debug("SIGTERM received during converge," +
" finishing converge to exit normally (send SIGINT to terminate immediately)")
end
@@ -303,79 +293,6 @@ class Chef
Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
end
- # Set ENV['http_proxy']
- def configure_http_proxy
- if http_proxy = Chef::Config[:http_proxy]
- http_proxy_string = configure_proxy("http", http_proxy,
- Chef::Config[:http_proxy_user], Chef::Config[:http_proxy_pass])
- env['http_proxy'] = http_proxy_string unless env['http_proxy']
- env['HTTP_PROXY'] = http_proxy_string unless env['HTTP_PROXY']
- end
- end
-
- # Set ENV['https_proxy']
- def configure_https_proxy
- if https_proxy = Chef::Config[:https_proxy]
- https_proxy_string = configure_proxy("https", https_proxy,
- Chef::Config[:https_proxy_user], Chef::Config[:https_proxy_pass])
- env['https_proxy'] = https_proxy_string unless env['https_proxy']
- env['HTTPS_PROXY'] = https_proxy_string unless env['HTTPS_PROXY']
- end
- end
-
- # Set ENV['ftp_proxy']
- def configure_ftp_proxy
- if ftp_proxy = Chef::Config[:ftp_proxy]
- ftp_proxy_string = configure_proxy("ftp", ftp_proxy,
- Chef::Config[:ftp_proxy_user], Chef::Config[:ftp_proxy_pass])
- env['ftp_proxy'] = ftp_proxy_string unless env['ftp_proxy']
- env['FTP_PROXY'] = ftp_proxy_string unless env['FTP_PROXY']
- end
- end
-
- # Set ENV['no_proxy']
- def configure_no_proxy
- if Chef::Config[:no_proxy]
- env['no_proxy'] = Chef::Config[:no_proxy] unless env['no_proxy']
- env['NO_PROXY'] = Chef::Config[:no_proxy] unless env['NO_PROXY']
- end
- end
-
- # Builds a proxy uri. Examples:
- # http://username:password@hostname:port
- # https://username@hostname:port
- # ftp://hostname:port
- # when
- # scheme = "http", "https", or "ftp"
- # hostport = hostname:port
- # user = username
- # pass = password
- def configure_proxy(scheme, path, user, pass)
- begin
- path = "#{scheme}://#{path}" unless path.include?('://')
- # URI.split returns the following parts:
- # [scheme, userinfo, host, port, registry, path, opaque, query, fragment]
- parts = URI.split(URI.encode(path))
- # URI::Generic.build requires an integer for the port, but URI::split gives
- # returns a string for the port.
- parts[3] = parts[3].to_i if parts[3]
- if user
- userinfo = URI.encode(URI.encode(user), '@:')
- if pass
- userinfo << ":#{URI.encode(URI.encode(pass), '@:')}"
- end
- parts[1] = userinfo
- end
-
- return URI::Generic.build(parts).to_s
- rescue URI::Error => e
- # URI::Error messages generally include the offending string. Including a message
- # for which proxy config item has the issue should help deduce the issue when
- # the URI::Error message is vague.
- raise Chef::Exceptions::BadProxyURI, "Cannot configure #{scheme} proxy. Does not comply with URI scheme. #{e.message}"
- end
- end
-
# This is a hook for testing
def env
ENV
@@ -383,18 +300,19 @@ 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
class << self
def debug_stacktrace(e)
message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
- chef_stacktrace_out = "Generated at #{Time.now.to_s}\n"
+ chef_stacktrace_out = "Generated at #{Time.now}\n"
chef_stacktrace_out += message
Chef::FileCache.store("chef-stacktrace.out", chef_stacktrace_out)
Chef::Log.fatal("Stacktrace dumped to #{Chef::FileCache.load("chef-stacktrace.out", false)}")
+ Chef::Log.fatal("Please provide the contents of the stacktrace.out file if you file a bug report")
Chef::Log.debug(message)
true
end
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb
index e9768b218c..f6348a951b 100644
--- a/lib/chef/application/apply.rb
+++ b/lib/chef/application/apply.rb
@@ -17,19 +17,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef'
-require 'chef/application'
-require 'chef/client'
-require 'chef/config'
-require 'chef/log'
-require 'fileutils'
-require 'tempfile'
-require 'chef/providers'
-require 'chef/resources'
+require "chef"
+require "chef/application"
+require "chef/client"
+require "chef/config"
+require "chef/log"
+require "fileutils"
+require "tempfile"
+require "chef/providers"
+require "chef/resources"
class Chef::Application::Apply < Chef::Application
- banner "Usage: chef-apply [RECIPE_FILE] [-e RECIPE_TEXT] [-s]"
+ banner "Usage: chef-apply [RECIPE_FILE | -e RECIPE_TEXT | -s] [OPTIONS]"
option :execute,
:short => "-e RECIPE_TEXT",
@@ -49,6 +49,24 @@ class Chef::Application::Apply < Chef::Application
:description => "Load attributes from a JSON file or URL",
:proc => nil
+ option :force_logger,
+ :long => "--force-logger",
+ :description => "Use logger output instead of formatter output",
+ :boolean => true,
+ :default => false
+
+ option :force_formatter,
+ :long => "--force-formatter",
+ :description => "Use formatter output instead of logger output",
+ :boolean => true,
+ :default => false
+
+ option :formatter,
+ :short => "-F FORMATTER",
+ :long => "--format FORMATTER",
+ :description => "output format to use",
+ :proc => lambda { |format| Chef::Config.add_formatter(format) }
+
option :log_level,
:short => "-l LEVEL",
:long => "--log_level LEVEL",
@@ -74,15 +92,21 @@ class Chef::Application::Apply < Chef::Application
:exit => 0
option :why_run,
- :short => '-W',
- :long => '--why-run',
- :description => 'Enable whyrun mode',
+ :short => "-W",
+ :long => "--why-run",
+ :description => "Enable whyrun mode",
:boolean => true
+ option :profile_ruby,
+ :long => "--[no-]profile-ruby",
+ :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)",
+ :boolean => true,
+ :default => false
+
option :color,
- :long => '--[no-]color',
+ :long => "--[no-]color",
:boolean => true,
- :default => !Chef::Platform.windows?,
+ :default => true,
:description => "Use colored output, defaults to enabled"
option :minimal_ohai,
@@ -100,7 +124,7 @@ class Chef::Application::Apply < Chef::Application
parse_options
Chef::Config.merge!(config)
configure_logging
- configure_proxy_environment_variables
+ Chef::Config.export_proxies
parse_json
end
@@ -143,7 +167,7 @@ class Chef::Application::Apply < Chef::Application
# write recipe to temp file, so in case of error,
# user gets error w/ context
def temp_recipe_file
- @recipe_fh = Tempfile.open('recipe-temporary-file')
+ @recipe_fh = Tempfile.open("recipe-temporary-file")
@recipe_fh.write(@recipe_text)
@recipe_fh.rewind
@recipe_filename = @recipe_fh.path
@@ -172,6 +196,7 @@ class Chef::Application::Apply < Chef::Application
ensure
@recipe_fh.close
end
+ Chef::Platform::Rebooter.reboot_if_needed!(runner)
end
def run_application
diff --git a/lib/chef/application/client.rb b/lib/chef/application/client.rb
index 9984ad5b9d..ba357b420d 100644
--- a/lib/chef/application/client.rb
+++ b/lib/chef/application/client.rb
@@ -2,7 +2,7 @@
# Author:: AJ Christensen (<aj@opscode.com)
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Mark Mzyk (mmzyk@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,14 +17,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/application'
-require 'chef/client'
-require 'chef/config'
-require 'chef/daemon'
-require 'chef/log'
-require 'chef/config_fetcher'
-require 'chef/handler/error_report'
-require 'chef/workstation_config_loader'
+require "chef/application"
+require "chef/client"
+require "chef/config"
+require "chef/daemon"
+require "chef/log"
+require "chef/config_fetcher"
+require "chef/handler/error_report"
+require "chef/workstation_config_loader"
class Chef::Application::Client < Chef::Application
include Chef::Mixin::ShellOut
@@ -55,11 +55,17 @@ class Chef::Application::Client < Chef::Application
:boolean => true,
:default => false
+ option :profile_ruby,
+ :long => "--[no-]profile-ruby",
+ :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)",
+ :boolean => true,
+ :default => false
+
option :color,
- :long => '--[no-]color',
+ :long => "--[no-]color",
:boolean => true,
- :default => !Chef::Platform.windows?,
- :description => "Use colored output, defaults to false on Windows, true otherwise"
+ :default => true,
+ :description => "Use colored output, defaults to enabled"
option :log_level,
:short => "-l LEVEL",
@@ -160,10 +166,15 @@ class Chef::Application::Client < Chef::Application
:description => "Set the client key file location",
:proc => nil
+ option :named_run_list,
+ :short => "-n NAMED_RUN_LIST",
+ :long => "--named-run-list NAMED_RUN_LIST",
+ :description => "Use a policyfile's named run list instead of the default run list"
+
option :environment,
- :short => '-E ENVIRONMENT',
- :long => '--environment ENVIRONMENT',
- :description => 'Set the Chef Environment on the node'
+ :short => "-E ENVIRONMENT",
+ :long => "--environment ENVIRONMENT",
+ :description => "Set the Chef Environment on the node"
option :version,
:short => "-v",
@@ -178,7 +189,7 @@ class Chef::Application::Client < Chef::Application
:long => "--override-runlist RunlistItem,RunlistItem...",
:description => "Replace current run list with specified items for a single run",
:proc => lambda{|items|
- items = items.split(',')
+ items = items.split(",")
items.compact.map{|item|
Chef::RunList::RunListItem.new(item)
}
@@ -189,15 +200,15 @@ class Chef::Application::Client < Chef::Application
:long => "--runlist RunlistItem,RunlistItem...",
:description => "Permanently replace current run list with specified items",
:proc => lambda{|items|
- items = items.split(',')
+ items = items.split(",")
items.compact.map{|item|
Chef::RunList::RunListItem.new(item)
}
}
option :why_run,
- :short => '-W',
- :long => '--why-run',
- :description => 'Enable whyrun mode',
+ :short => "-W",
+ :long => "--why-run",
+ :description => "Enable whyrun mode",
:boolean => true
option :client_fork,
@@ -258,6 +269,11 @@ class Chef::Application::Client < Chef::Application
: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
@@ -267,13 +283,19 @@ class Chef::Application::Client < Chef::Application
def reconfigure
super
- raise Chef::Exceptions::PIDFileLockfileMatch if Chef::Util::PathHelper.paths_eql? (Chef::Config[:pid_file] || '' ), (Chef::Config[:lockfile] || '')
+ raise Chef::Exceptions::PIDFileLockfileMatch if Chef::Util::PathHelper.paths_eql? (Chef::Config[:pid_file] || "" ), (Chef::Config[:lockfile] || "")
set_specific_recipes
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
@@ -283,7 +305,7 @@ class Chef::Application::Client < Chef::Application
elsif Chef::Config.local_mode && Chef::Config.has_key?(:recipe_url)
Chef::Log.debug "Creating path #{Chef::Config.chef_repo_path} to extract recipes into"
FileUtils.mkdir_p(Chef::Config.chef_repo_path)
- tarball_path = File.join(Chef::Config.chef_repo_path, 'recipes.tgz')
+ tarball_path = File.join(Chef::Config.chef_repo_path, "recipes.tgz")
fetch_recipe_tarball(Chef::Config[:recipe_url], tarball_path)
result = shell_out!("tar zxvf #{tarball_path} -C #{Chef::Config.chef_repo_path}")
Chef::Log.debug "#{result.stdout}"
@@ -315,12 +337,6 @@ class Chef::Application::Client < Chef::Application
unless expected_modes.include?(mode)
Chef::Application.fatal!(unrecognized_audit_mode(mode))
end
-
- unless mode == :disabled
- # This should be removed when audit-mode is enabled by default/no longer
- # an experimental feature.
- Chef::Log.warn(audit_mode_experimental_message)
- end
end
end
@@ -443,31 +459,20 @@ class Chef::Application::Client < Chef::Application
"\nEnable chef-client interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
end
- def audit_mode_settings_explaination
- "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `:audit_mode = :enabled` in your config file." +
- "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `:audit_mode = :disabled` in your config file." +
- "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `:audit_mode = :audit_only` in your config file." +
+ def audit_mode_settings_explanation
+ "\n* To enable audit mode after converge, use command line option `--audit-mode enabled` or set `audit_mode :enabled` in your config file." +
+ "\n* To disable audit mode, use command line option `--audit-mode disabled` or set `audit_mode :disabled` in your config file." +
+ "\n* To only run audit mode, use command line option `--audit-mode audit-only` or set `audit_mode :audit_only` in your config file." +
"\nAudit mode is disabled by default."
end
def unrecognized_audit_mode(mode)
- "Unrecognized setting #{mode} for audit mode." + audit_mode_settings_explaination
- end
-
- def audit_mode_experimental_message
- msg = if Chef::Config[:audit_mode] == :audit_only
- "Chef-client has been configured to skip converge and only audit."
- else
- "Chef-client has been configured to audit after it converges."
- end
- msg += " Audit mode is an experimental feature currently under development. API changes may occur. Use at your own risk."
- msg += audit_mode_settings_explaination
- return msg
+ "Unrecognized setting #{mode} for audit mode." + audit_mode_settings_explanation
end
def fetch_recipe_tarball(url, path)
Chef::Log.debug("Download recipes tarball from #{url} to #{path}")
- File.open(path, 'wb') do |f|
+ File.open(path, "wb") do |f|
open(url) do |r|
f.write(r.read)
end
diff --git a/lib/chef/application/knife.rb b/lib/chef/application/knife.rb
index 1a19a45598..1742223657 100644
--- a/lib/chef/application/knife.rb
+++ b/lib/chef/application/knife.rb
@@ -15,11 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/knife'
-require 'chef/application'
-require 'mixlib/log'
-require 'ohai/config'
-require 'chef/monkey_patches/net_http.rb'
+require "chef/knife"
+require "chef/application"
+require "mixlib/log"
+require "ohai/config"
+require "chef/monkey_patches/net_http.rb"
class Chef::Application::Knife < Chef::Application
@@ -35,17 +35,17 @@ class Chef::Application::Knife < Chef::Application
verbosity_level = 0
option :verbosity,
- :short => '-V',
- :long => '--verbose',
+ :short => "-V",
+ :long => "--verbose",
:description => "More verbose output. Use twice for max verbosity",
:proc => Proc.new { verbosity_level += 1},
:default => 0
option :color,
- :long => '--[no-]color',
+ :long => "--[no-]color",
:boolean => true,
- :default => !Chef::Platform.windows?,
- :description => "Use colored output, defaults to false on Windows, true otherwise"
+ :default => true,
+ :description => "Use colored output, defaults to enabled"
option :environment,
:short => "-E ENVIRONMENT",
@@ -56,7 +56,7 @@ class Chef::Application::Knife < Chef::Application
:short => "-e EDITOR",
:long => "--editor EDITOR",
:description => "Set the editor to use for interactive commands",
- :default => ENV['EDITOR']
+ :default => ENV["EDITOR"]
option :disable_editing,
:short => "-d",
@@ -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
@@ -158,7 +162,7 @@ class Chef::Application::Knife < Chef::Application
print_help_and_exit(1, NO_COMMAND_GIVEN)
elsif no_subcommand_given?
if (want_help? || want_version?)
- print_help_and_exit
+ print_help_and_exit(0)
else
print_help_and_exit(2, NO_COMMAND_GIVEN)
end
diff --git a/lib/chef/application/solo.rb b/lib/chef/application/solo.rb
index dd09d65b42..b3197c3dbe 100644
--- a/lib/chef/application/solo.rb
+++ b/lib/chef/application/solo.rb
@@ -1,7 +1,7 @@
#
# Author:: AJ Christensen (<aj@opscode.com>)
# Author:: Mark Mzyk (mmzyk@opscode.com)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,22 +16,22 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef'
-require 'chef/application'
-require 'chef/client'
-require 'chef/config'
-require 'chef/daemon'
-require 'chef/log'
-require 'chef/rest'
-require 'chef/config_fetcher'
-require 'fileutils'
+require "chef"
+require "chef/application"
+require "chef/client"
+require "chef/config"
+require "chef/daemon"
+require "chef/log"
+require "chef/rest"
+require "chef/config_fetcher"
+require "fileutils"
class Chef::Application::Solo < Chef::Application
option :config_file,
:short => "-c CONFIG",
:long => "--config CONFIG",
- :default => Chef::Config.platform_specific_path('/etc/chef/solo.rb'),
+ :default => Chef::Config.platform_specific_path("/etc/chef/solo.rb"),
:description => "The configuration file to use"
option :formatter,
@@ -52,8 +52,14 @@ class Chef::Application::Solo < Chef::Application
:boolean => true,
:default => false
+ option :profile_ruby,
+ :long => "--[no-]profile-ruby",
+ :description => "Dump complete Ruby call graph stack of entire Chef run (expert only)",
+ :boolean => true,
+ :default => false
+
option :color,
- :long => '--[no-]color',
+ :long => "--[no-]color",
:boolean => true,
:default => !Chef::Platform.windows?,
:description => "Use colored output, defaults to enabled"
@@ -147,7 +153,7 @@ class Chef::Application::Solo < Chef::Application
:long => "--override-runlist RunlistItem,RunlistItem...",
:description => "Replace current run list with specified items",
:proc => lambda{|items|
- items = items.split(',')
+ items = items.split(",")
items.compact.map{|item|
Chef::RunList::RunListItem.new(item)
}
@@ -160,20 +166,20 @@ class Chef::Application::Solo < Chef::Application
:boolean => true
option :why_run,
- :short => '-W',
- :long => '--why-run',
- :description => 'Enable whyrun mode',
+ :short => "-W",
+ :long => "--why-run",
+ :description => "Enable whyrun mode",
:boolean => true
option :ez,
- :long => '--ez',
- :description => 'A memorial for Ezra Zygmuntowicz',
+ :long => "--ez",
+ :description => "A memorial for Ezra Zygmuntowicz",
:boolean => true
option :environment,
- :short => '-E ENVIRONMENT',
- :long => '--environment ENVIRONMENT',
- :description => 'Set the Chef Environment on the node'
+ :short => "-E ENVIRONMENT",
+ :long => "--environment ENVIRONMENT",
+ :description => "Set the Chef Environment on the node"
option :run_lock_timeout,
:long => "--run-lock-timeout SECONDS",
@@ -206,15 +212,15 @@ class Chef::Application::Solo < Chef::Application
if Chef::Config[:recipe_url]
cookbooks_path = Array(Chef::Config[:cookbook_path]).detect{|e| e =~ /\/cookbooks\/*$/ }
- recipes_path = File.expand_path(File.join(cookbooks_path, '..'))
+ recipes_path = File.expand_path(File.join(cookbooks_path, ".."))
Chef::Log.debug "Cleanup path #{recipes_path} before extract recipes into it"
FileUtils.rm_rf(recipes_path, :secure => true)
Chef::Log.debug "Creating path #{recipes_path} to extract recipes into"
FileUtils.mkdir_p(recipes_path)
- tarball_path = File.join(recipes_path, 'recipes.tgz')
+ 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.
@@ -297,7 +303,7 @@ EOH
def fetch_recipe_tarball(url, path)
Chef::Log.debug("Download recipes tarball from #{url} to #{path}")
- File.open(path, 'wb') do |f|
+ File.open(path, "wb") do |f|
open(url) do |r|
f.write(r.read)
end
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb
index b42a01cfdb..a57cf138e4 100644
--- a/lib/chef/application/windows_service.rb
+++ b/lib/chef/application/windows_service.rb
@@ -1,6 +1,6 @@
#
# Author:: Christopher Maier (<maier@lambda.local>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,19 +16,19 @@
# limitations under the License.
#
-require 'chef'
-require 'chef/monologger'
-require 'chef/application'
-require 'chef/client'
-require 'chef/config'
-require 'chef/handler/error_report'
-require 'chef/log'
-require 'chef/rest'
-require 'mixlib/cli'
-require 'socket'
-require 'uri'
-require 'win32/daemon'
-require 'chef/mixin/shell_out'
+require "chef"
+require "chef/monologger"
+require "chef/application"
+require "chef/client"
+require "chef/config"
+require "chef/handler/error_report"
+require "chef/log"
+require "chef/http"
+require "mixlib/cli"
+require "socket"
+require "uri"
+require "win32/daemon"
+require "chef/mixin/shell_out"
class Chef
class Application
@@ -45,8 +45,7 @@ class Chef
option :log_location,
:short => "-L LOGLOCATION",
:long => "--logfile LOGLOCATION",
- :description => "Set the log file location",
- :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+ :description => "Set the log file location"
option :splay,
:short => "-s SECONDS",
@@ -60,6 +59,8 @@ class Chef
:description => "Set the number of seconds to wait between chef-client runs",
:proc => lambda { |s| s.to_i }
+ DEFAULT_LOG_LOCATION ||= "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+
def service_init
@service_action_mutex = Mutex.new
@service_signal = ConditionVariable.new
@@ -187,9 +188,18 @@ class Chef
# Pass config params to the new process
config_params = " --no-fork"
config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil?
- config_params += " -L #{Chef::Config[:log_location]}" unless Chef::Config[:log_location] == STDOUT
+ # log_location might be an event logger and if so we cannot pass as a command argument
+ # but shed no tears! If the logger is an event logger, it must have been configured
+ # as such in the config file and chef-client will use that when no arg is passed here
+ config_params += " -L #{resolve_log_location}" if resolve_log_location.is_a?(String)
+
# Starts a new process and waits till the process exits
- result = shell_out("chef-client #{config_params}", :timeout => Chef::Config[:windows_service][:watchdog_timeout])
+
+ result = shell_out(
+ "chef-client.bat #{config_params}",
+ :timeout => Chef::Config[:windows_service][:watchdog_timeout],
+ :logger => Chef::Log,
+ )
Chef::Log.debug "#{result.stdout}"
Chef::Log.debug "#{result.stderr}"
rescue Mixlib::ShellOut::CommandTimeout => e
@@ -231,7 +241,7 @@ class Chef
# See application.rb for related comments.
def configure_logging
- Chef::Log.init(MonoLogger.new(Chef::Config[:log_location]))
+ Chef::Log.init(MonoLogger.new(resolve_log_location))
if want_additional_logger?
configure_stdout_logger
end
@@ -260,6 +270,11 @@ class Chef
Chef::Config[:log_level] == :auto
end
+ def resolve_log_location
+ # STDOUT is the default log location, but makes no sense for a windows service
+ Chef::Config[:log_location] == STDOUT ? DEFAULT_LOG_LOCATION : Chef::Config[:log_location]
+ end
+
# if log_level is `:auto`, convert it to :warn (when using output formatter)
# or :info (no output formatter). See also +using_output_formatter?+
def resolve_log_level
@@ -293,7 +308,7 @@ class Chef
begin
case config[:config_file]
when /^(http|https):\/\//
- Chef::REST.new("", nil, nil).fetch(config[:config_file]) { |f| apply_config(f.path) }
+ Chef::HTTP.new("").streaming_request(config[:config_file]) { |f| apply_config(f.path) }
else
::File::open(config[:config_file]) { |f| apply_config(f.path) }
end
diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb
index de8ed657c2..b651d622e6 100644
--- a/lib/chef/application/windows_service_manager.rb
+++ b/lib/chef/application/windows_service_manager.rb
@@ -1,6 +1,6 @@
#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,10 +17,10 @@
#
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require 'win32/service'
+ require "win32/service"
end
-require 'chef/config'
-require 'mixlib/cli'
+require "chef/config"
+require "mixlib/cli"
class Chef
class Application
@@ -51,8 +51,7 @@ class Chef
option :log_location,
:short => "-L LOGLOCATION",
:long => "--logfile LOGLOCATION",
- :description => "Set the log file location for chef-service",
- :default => "#{ENV['SYSTEMDRIVE']}/chef/client.log"
+ :description => "Set the log file location for chef-service"
option :help,
:short => "-h",
@@ -78,7 +77,7 @@ class Chef
raise ArgumentError, "Service definition is not provided" if service_options.nil?
- required_options = [:service_name, :service_display_name, :service_name, :service_description, :service_file_path]
+ required_options = [:service_name, :service_display_name, :service_description, :service_file_path]
required_options.each do |req_option|
if !service_options.has_key?(req_option)
@@ -92,17 +91,19 @@ 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)
parse_options(params)
case config[:action]
- when 'install'
+ when "install"
if service_exists?
puts "Service #{@service_name} already exists on the system."
else
- ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
+ ruby = File.join(RbConfig::CONFIG["bindir"], "ruby")
opts = ""
opts << " -c #{config[:config_file]}" if config[:config_file]
@@ -113,42 +114,47 @@ 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'
+ when "status"
if !service_exists?
puts "Service #{@service_name} doesn't exist on the system."
else
puts "State of #{@service_name} service is: #{current_state}"
end
- when 'start'
+ when "start"
# TODO: allow override of startup parameters here?
- take_action('start', RUNNING)
- when 'stop'
- take_action('stop', STOPPED)
- when 'uninstall', 'delete'
- take_action('stop', STOPPED)
+ take_action("start", RUNNING)
+ when "stop"
+ take_action("stop", STOPPED)
+ when "uninstall", "delete"
+ take_action("stop", STOPPED)
unless service_exists?
puts "Service #{@service_name} doesn't exist on the system."
else
::Win32::Service.delete(@service_name)
puts "Service #{@service_name} deleted"
end
- when 'pause'
- take_action('pause', PAUSED)
- when 'resume'
- take_action('resume', RUNNING)
+ when "pause"
+ take_action("pause", PAUSED)
+ when "resume"
+ take_action("resume", RUNNING)
end
end
diff --git a/lib/chef/applications.rb b/lib/chef/applications.rb
index 6a1a2e8a92..97c896e12e 100644
--- a/lib/chef/applications.rb
+++ b/lib/chef/applications.rb
@@ -1,4 +1,4 @@
-require 'chef/application/client'
-require 'chef/application/knife'
-require 'chef/application/solo'
-require 'chef/application/apply'
+require "chef/application/client"
+require "chef/application/knife"
+require "chef/application/solo"
+require "chef/application/apply"
diff --git a/lib/chef/audit/audit_event_proxy.rb b/lib/chef/audit/audit_event_proxy.rb
index b9ca39e5dc..36f0030b84 100644
--- a/lib/chef/audit/audit_event_proxy.rb
+++ b/lib/chef/audit/audit_event_proxy.rb
@@ -60,7 +60,7 @@ class Chef
def build_control_from(example)
described_class = example.metadata[:described_class]
if described_class
- resource_type = described_class.class.name.split(':')[-1]
+ resource_type = described_class.class.name.split(":")[-1]
resource_name = described_class.name
end
@@ -84,7 +84,7 @@ class Chef
:resource_type => resource_type,
:resource_name => resource_name,
:context => describe_groups,
- :line_number => example.metadata[:line_number]
+ :line_number => example.metadata[:line_number],
}
end
diff --git a/lib/chef/audit/audit_reporter.rb b/lib/chef/audit/audit_reporter.rb
index a4f84ed7eb..5dc05055e2 100644
--- a/lib/chef/audit/audit_reporter.rb
+++ b/lib/chef/audit/audit_reporter.rb
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require 'chef/event_dispatch/base'
-require 'chef/audit/control_group_data'
-require 'time'
+require "chef/event_dispatch/base"
+require "chef/audit/control_group_data"
+require "time"
class Chef
class Audit
@@ -28,12 +28,13 @@ class Chef
attr_reader :rest_client, :audit_data, :ordered_control_groups, :run_status
private :rest_client, :audit_data, :ordered_control_groups, :run_status
- PROTOCOL_VERSION = '0.1.1'
+ PROTOCOL_VERSION = "0.1.1"
def initialize(rest_client)
@rest_client = rest_client
# Ruby 1.9.3 and above "enumerate their values in the order that the corresponding keys were inserted."
@ordered_control_groups = Hash.new
+ @audit_phase_error = nil
end
def run_context
@@ -46,7 +47,7 @@ class Chef
@run_status = run_status
end
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
Chef::Log.debug("Audit Reporter completed successfully without errors.")
ordered_control_groups.each do |name, control_group|
audit_data.add_control_group(control_group)
@@ -57,8 +58,9 @@ class Chef
# that runs tests - normal errors are interpreted as EXAMPLE failures and captured.
# We still want to send available audit information to the server so we process the
# known control groups.
- def audit_phase_failed(error)
+ def audit_phase_failed(error, audit_output)
# The stacktrace information has already been logged elsewhere
+ @audit_phase_error = error
Chef::Log.debug("Audit Reporter failed.")
ordered_control_groups.each do |name, control_group|
audit_data.add_control_group(control_group)
@@ -70,7 +72,9 @@ class Chef
end
def run_failed(error)
- post_auditing_data(error)
+ # Audit phase errors are captured when audit_phase_failed gets called.
+ # The error passed here isn't relevant to auditing, so we ignore it.
+ post_auditing_data
end
def control_group_started(name)
@@ -98,7 +102,7 @@ class Chef
private
- def post_auditing_data(error = nil)
+ def post_auditing_data
unless auditing_enabled?
Chef::Log.debug("Audit Reports are disabled. Skipping sending reports.")
return
@@ -116,15 +120,15 @@ 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)}"
- # Since we're posting compressed data we can not directly call post_rest which expects JSON
begin
- audit_url = rest_client.create_url(audit_history_url)
- rest_client.post(audit_url, run_data, headers)
+ rest_client.post(audit_history_url, run_data, headers)
rescue StandardError => e
if e.respond_to? :response
# 404 error code is OK. This means the version of server we're running against doesn't support
@@ -150,7 +154,7 @@ class Chef
end
def headers(additional_headers = {})
- options = {'X-Ops-Audit-Report-Protocol-Version' => PROTOCOL_VERSION}
+ options = {"X-Ops-Audit-Report-Protocol-Version" => PROTOCOL_VERSION}
options.merge(additional_headers)
end
@@ -163,7 +167,6 @@ class Chef
def iso8601ify(time)
time.utc.iso8601.to_s
end
-
end
end
end
diff --git a/lib/chef/audit/control_group_data.rb b/lib/chef/audit/control_group_data.rb
index 204d7f8070..cb1a648cc2 100644
--- a/lib/chef/audit/control_group_data.rb
+++ b/lib/chef/audit/control_group_data.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'securerandom'
+require "securerandom"
class Chef
class Audit
@@ -41,7 +41,7 @@ class Chef
:run_id => run_id,
:start_time => start_time,
:end_time => end_time,
- :control_groups => control_groups.collect { |c| c.to_hash }
+ :control_groups => control_groups.collect { |c| c.to_hash },
}
end
end
@@ -86,7 +86,7 @@ class Chef
:status => status,
:number_succeeded => number_succeeded,
:number_failed => number_failed,
- :controls => controls.collect { |c| c.to_hash }
+ :controls => controls.collect { |c| c.to_hash },
}
# If there is a duplicate key, metadata will overwrite it
add_display_only_data(h).merge(metadata)
@@ -129,7 +129,7 @@ class Chef
:status => status,
:details => details,
:resource_type => resource_type,
- :resource_name => resource_name
+ :resource_name => resource_name,
}
h[:context] = context || []
h
diff --git a/lib/chef/audit/logger.rb b/lib/chef/audit/logger.rb
new file mode 100644
index 0000000000..da335eda98
--- /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/rspec_formatter.rb b/lib/chef/audit/rspec_formatter.rb
index 074a11bed3..5af9a89123 100644
--- a/lib/chef/audit/rspec_formatter.rb
+++ b/lib/chef/audit/rspec_formatter.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'rspec/core'
+require "rspec/core"
class Chef
class Audit
diff --git a/lib/chef/audit/runner.rb b/lib/chef/audit/runner.rb
index 13c2823dca..d2d822eea2 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
@@ -76,16 +78,16 @@ class Chef
# prevents Specinfra and Serverspec from modifying the RSpec configuration
# used by our spec tests.
def require_deps
- 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'
+ 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
@@ -104,6 +106,7 @@ class Chef
RSpec.configure do |c|
c.color = Chef::Config[:color]
c.expose_dsl_globally = false
+ c.project_source_dirs = Array(Chef::Config[:cookbook_path])
c.backtrace_exclusion_patterns << exclusion_pattern
end
end
@@ -115,8 +118,8 @@ class Chef
# the output stream to be changed for a formatter once the formatter has
# been added.
def set_streams
- RSpec.configuration.output_stream = Chef::Config[:log_location]
- RSpec.configuration.error_stream = Chef::Config[:log_location]
+ RSpec.configuration.output_stream = Chef::Audit::Logger
+ RSpec.configuration.error_stream = Chef::Audit::Logger
end
# Add formatters which we use to
@@ -144,6 +147,7 @@ class Chef
def configure_specinfra
if Chef::Platform.windows?
Specinfra.configuration.backend = :cmd
+ Specinfra.configuration.os = { :family => "windows" }
else
Specinfra.configuration.backend = :exec
end
diff --git a/lib/chef/chef_class.rb b/lib/chef/chef_class.rb
new file mode 100644
index 0000000000..a2b572335b
--- /dev/null
+++ b/lib/chef/chef_class.rb
@@ -0,0 +1,225 @@
+#
+# 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=nil)
+ location ||= Chef::Log.caller_location
+ # `run_context.events` is the primary deprecation target if we're in a
+ # run. If we are not yet in a run, print to `Chef::Log`.
+ if run_context && run_context.events
+ run_context.events.deprecation(message, location)
+ else
+ Chef::Log.deprecation(message, location)
+ end
+ end
+ end
+
+ # @api private Only for test dependency injection; not evenly implemented as yet.
+ def self.path_to(path)
+ path
+ end
+
+ reset!
+end
diff --git a/lib/chef/chef_fs.rb b/lib/chef/chef_fs.rb
index bc445e53ad..43a9efd5e2 100644
--- a/lib/chef/chef_fs.rb
+++ b/lib/chef/chef_fs.rb
@@ -1,4 +1,54 @@
-require 'chef/platform'
+require "chef/platform"
+
+#
+# ChefFS was designed to be a near-1:1 translation between Chef server endpoints
+# and local data, so that it could be used for:
+#
+# 1. User editing, diffing and viewing of server content locally
+# 2. knife download, upload and diff (supporting the above scenario)
+# 3. chef-client -z (serving user repository directly)
+#
+# This is the translation between chef-zero data stores (which correspond
+# closely to server endpoints) and the ChefFS repository format.
+#
+# |-----------------------------------|-----------------------------------|
+# | chef-zero DataStore | ChefFS (repository) |
+# |-----------------------------------|-----------------------------------|
+# | <root> | org.json |
+# | association_requests/NAME | invitations.json |
+# | clients/NAME | clients/NAME.json |
+# | cookbooks/NAME/VERSION | cookbooks/NAME/metadata.rb |
+# | containers/NAME | containers/NAME.json |
+# | data/BAG/ITEM | data_bags/BAG/ITEM.json |
+# | environments/NAME | environments/NAME.json |
+# | groups/NAME | groups/NAME.json |
+# | nodes/NAME | nodes/NAME.json |
+# | policies/NAME/REVISION | policies/NAME-REVISION.json |
+# | policy_groups/NAME/policies/PNAME | policy_groups/NAME.json |
+# | roles/NAME | roles/NAME.json |
+# | sandboxes/ID | <not stored on disk, just memory> |
+# | users/NAME | members.json |
+# | file_store/COOKBOOK/VERSION/PATH | cookbooks/COOKBOOK/PATH |
+# | **/_acl | acls/**.json |
+# |-----------------------------------|-----------------------------------|
+#
+#
+# ## The Code
+#
+# There are two main entry points to ChefFS:
+#
+# - ChefServerRootDir represents the chef server (under an org) and surfaces a
+# filesystem-like interface (FSBaseObject / FSBaseDir) that maps the REST API
+# to the same format as you would have on disk.
+# - ChefRepositoryFileSystemRootDir represents the local repository where you
+# put your cookbooks, roles, policies, etc.
+#
+# Because these two map to a common directory structure, diff, upload, download,
+# and other filesystem operations, can easily be done in a generic manner.
+#
+# These are instantiated by Chef::ChefFS::Config's `chef_fs` and `local_fs`
+# methods.
+#
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/chef_fs_data_store.rb b/lib/chef/chef_fs/chef_fs_data_store.rb
index 4084fb80d3..634faaec7e 100644
--- a/lib/chef/chef_fs/chef_fs_data_store.rb
+++ b/lib/chef/chef_fs/chef_fs_data_store.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/cookbook_manifest'
-require 'chef_zero/data_store/memory_store'
-require 'chef_zero/data_store/data_already_exists_error'
-require 'chef_zero/data_store/data_not_found_error'
-require 'chef/chef_fs/file_pattern'
-require 'chef/chef_fs/file_system'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/memory_root'
-require 'fileutils'
+require "chef/cookbook_manifest"
+require "chef_zero/data_store/memory_store"
+require "chef_zero/data_store/data_already_exists_error"
+require "chef_zero/data_store/data_not_found_error"
+require "chef/chef_fs/file_pattern"
+require "chef/chef_fs/file_system"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/file_system/memory/memory_root"
+require "fileutils"
class Chef
module ChefFS
@@ -66,12 +66,65 @@ class Chef
# - ChefFSDataStore lets cookbooks be uploaded into a temporary memory
# storage, and when the cookbook is committed, copies the files onto the
# disk in the correct place (/cookbooks/apache2/recipes/default.rb).
+ #
# 3. Data bags:
# - The Chef server expects data bags in /data/BAG/ITEM
# - The repository stores data bags in /data_bags/BAG/ITEM
#
# 4. JSON filenames are generally NAME.json in the repository (e.g. /nodes/foo.json).
#
+ # 5. Org membership:
+ # chef-zero stores user membership in an org as a series of empty files.
+ # If an org has jkeiser and cdoherty as members, chef-zero expects these
+ # files to exist:
+ #
+ # - `users/jkeiser` (content: '{}')
+ # - `users/cdoherty` (content: '{}')
+ #
+ # ChefFS, on the other hand, stores user membership in an org as a single
+ # file, `members.json`, with content:
+ #
+ # ```json
+ # [
+ # { "user": { "username": "jkeiser" } },
+ # { "user": { "username": "cdoherty" } }
+ # ]
+ # ```
+ #
+ # To translate between the two, we need to intercept requests to `users`
+ # like so:
+ #
+ # - `list(users)` -> `get(/members.json)`
+ # - `get(users/NAME)` -> `get(/members.json)`, see if it's in there
+ # - `create(users/NAME)` -> `get(/members.json)`, add name, `set(/members.json)`
+ # - `delete(users/NAME)` -> `get(/members.json)`, remove name, `set(/members.json)`
+ #
+ # 6. Org invitations:
+ # chef-zero stores org membership invitations as a series of empty files.
+ # If an org has invited jkeiser and cdoherty (and they have not yet accepted
+ # the invite), chef-zero expects these files to exist:
+ #
+ # - `association_requests/jkeiser` (content: '{}')
+ # - `association_requests/cdoherty` (content: '{}')
+ #
+ # ChefFS, on the other hand, stores invitations as a single file,
+ # `invitations.json`, with content:
+ #
+ # ```json
+ # [
+ # { "id" => "jkeiser-chef", 'username' => 'jkeiser' },
+ # { "id" => "cdoherty-chef", 'username' => 'cdoherty' }
+ # ]
+ # ```
+ #
+ # To translate between the two, we need to intercept requests to `users`
+ # like so:
+ #
+ # - `list(association_requests)` -> `get(/invitations.json)`
+ # - `get(association_requests/NAME)` -> `get(/invitations.json)`, see if it's in there
+ # - `create(association_requests/NAME)` -> `get(/invitations.json)`, add name, `set(/invitations.json)`
+ # - `delete(association_requests/NAME)` -> `get(/invitations.json)`, remove name, `set(/invitations.json)`
+ #
class ChefFSDataStore
#
# Create a new ChefFSDataStore
@@ -83,9 +136,10 @@ class Chef
# Generally will be a +ChefFS::FileSystem::ChefRepositoryFileSystemRoot+
# object, created from +ChefFS::Config.local_fs+.
#
- def initialize(chef_fs)
+ def initialize(chef_fs, chef_config=Chef::Config)
@chef_fs = chef_fs
@memory_store = ChefZero::DataStore::MemoryStore.new
+ @repo_mode = chef_config[:repo_mode]
end
def publish_description
@@ -93,14 +147,15 @@ class Chef
end
attr_reader :chef_fs
+ attr_reader :repo_mode
def create_dir(path, name, *options)
if use_memory_store?(path)
@memory_store.create_dir(path, name, *options)
else
- with_dir(path) do |parent|
+ with_parent_dir(path + [name], *options) do |parent, name|
begin
- parent.create_child(chef_fs_filename(path + [name]), nil)
+ parent.create_child(name, nil)
rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e
raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e)
end
@@ -108,21 +163,78 @@ class Chef
end
end
+ #
+ # If you want to get the contents of /data/x/y from the server,
+ # you say chef_fs.child('data').child('x').child('y').read.
+ # It will make exactly one network request: GET /data/x/y
+ # And that will return 404 if it doesn't exist.
+ #
+ # ChefFS objects do not go to the network until you ask them for data.
+ # This means you can construct a /data/x/y ChefFS entry early.
+ #
+ # Alternative:
+ # chef_fs.child('data') could have done a GET /data preemptively,
+ # allowing it to know whether child('x') was valid (GET /data gives you
+ # a list of data bags). Then child('x') could have done a GET /data/x,
+ # allowing it to know whether child('y') (the item) existed. Finally,
+ # we would do the GET /data/x/y to read the contents. Three network
+ # requests instead of 1.
+ #
+
def create(path, name, data, *options)
if use_memory_store?(path)
@memory_store.create(path, name, data, *options)
- elsif path[0] == 'cookbooks' && path.length == 2
+ elsif path[0] == "cookbooks" && path.length == 2
# Do nothing. The entry gets created when the cookbook is created.
+ # /policy_groups/GROUP/policies/NAME
+ elsif path[0] == "policy_groups" && path[2] == "policies"
+ # Just set or create the proper entry in the hash
+ update_json(to_chef_fs_path(path[0..1]), {}, *options) do |group|
+ if policies.has_key?(path[3])
+ raise ChefZero::DataStore::DataAlreadyExistsError.new(path, group)
+ end
+
+ group["policies"] ||= {}
+ group["policies"][path[3]] = { "revision_id" => Chef::JSONCompat.parse(data) }
+ group
+ end
+
+ # create [/organizations/ORG]/users/NAME (with content '{}')
+ # Manipulate the `members.json` file that contains a list of all users
+ elsif is_org? && path == [ "users" ]
+ update_json("members.json", [], *options) do |members|
+ # Format of each entry: { "user": { "username": "jkeiser" } }
+ if members.any? { |member| member["user"]["username"] == name }
+ raise ChefZero::DataStore::DataAlreadyExistsError.new(path, entry)
+ end
+
+ # Actually add the user
+ members << { "user" => { "username" => name } }
+ end
+
+ # create [/organizations/ORG]/association_requests/NAME (with content '{}')
+ # Manipulate the `invitations.json` file that contains a list of all users
+ elsif is_org? && path == [ "association_requests" ]
+ update_json("invitations.json", [], *options) do |invitations|
+ # Format of each entry: { "id" => "jkeiser-chef", 'username' => 'jkeiser' }
+ if invitations.any? { |member| member["username"] == name }
+ raise ChefZero::DataStore::DataAlreadyExistsError.new(path)
+ end
+
+ # Actually add the user (TODO insert org name??)
+ invitations << { "username" => name }
+ end
+
else
if !data.is_a?(String)
raise "set only works with strings"
end
- with_dir(path) do |parent|
+ with_parent_dir(path + [name], *options) do |parent, name|
begin
- parent.create_child(chef_fs_filename(path + [name]), data)
+ parent.create_child(name, data)
rescue Chef::ChefFS::FileSystem::AlreadyExistsError => e
raise ChefZero::DataStore::DataAlreadyExistsError.new(to_zero_path(e.entry), e)
end
@@ -134,50 +246,101 @@ class Chef
if use_memory_store?(path)
@memory_store.get(path)
- elsif path[0] == 'file_store' && path[1] == 'repo'
- entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[2..-1].join('/'))
+ elsif path[0] == "file_store" && path[1] == "repo"
+ entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[2..-1].join("/"))
begin
entry.read
rescue Chef::ChefFS::FileSystem::NotFoundError => e
raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
end
- else
+ # /policy_groups/NAME/policies/POLICYNAME: return the revision of the given policy
+ elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4
+ # Just set or create the proper entry in the hash
+ policy_group = get_json(to_chef_fs_path(path[0..1]), {})
+ if !policy_group["policies"] || !policy_group["policies"][path[3]]
+ raise ChefZero::DataStore::DataNotFoundError.new(path, entry)
+ end
+ # The policy group looks like:
+ # {
+ # "policies": {
+ # "x": { "revision_id": "10" }
+ # }
+ # }
+ Chef::JSONCompat.to_json_pretty(policy_group["policies"][path[3]]["revision_id"])
+
+ # GET [/organizations/ORG]/users/NAME -> /users/NAME
+ # Manipulates members.json
+ elsif is_org? && path[0] == "users" && path.length == 2
+ if get_json("members.json", []).any? { |member| member["user"]["username"] == path[1] }
+ "{}"
+ else
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
+ end
+
+ # GET [/organizations/ORG]/association_requests/NAME -> /users/NAME
+ # Manipulates invites.json
+ elsif is_org? && path[0] == "association_requests" && path.length == 2
+ if get_json("invites.json", []).any? { |member| member["user"]["username"] == path[1] }
+ "{}"
+ else
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
+ end
+
+ # GET /cookbooks/NAME/VERSION or /cookbook_artifacts/NAME/IDENTIFIER
+ elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 3
with_entry(path) do |entry|
- if path[0] == 'cookbooks' && path.length == 3
- # get /cookbooks/NAME/version
- result = nil
- begin
- result = Chef::CookbookManifest.new(entry.chef_object).to_hash
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
- end
+ cookbook_type = path[0]
+ result = nil
+ begin
+ result = Chef::CookbookManifest.new(entry.chef_object, policy_mode: cookbook_type == "cookbook_artifacts").to_hash
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+ end
- result.each_pair do |key, value|
- if value.is_a?(Array)
- value.each do |file|
- if file.is_a?(Hash) && file.has_key?('checksum')
- relative = ['file_store', 'repo', 'cookbooks']
- if chef_fs.versioned_cookbooks
- relative << "#{path[1]}-#{path[2]}"
- else
- relative << path[1]
- end
- relative = relative + file[:path].split('/')
- file['url'] = ChefZero::RestBase::build_uri(request.base_uri, relative)
+ result.each_pair do |key, value|
+ if value.is_a?(Array)
+ value.each do |file|
+ if file.is_a?(Hash) && file.has_key?("checksum")
+ relative = ["file_store", "repo", cookbook_type]
+ if chef_fs.versioned_cookbooks || cookbook_type == "cookbook_artifacts"
+ relative << "#{path[1]}-#{path[2]}"
+ else
+ relative << path[1]
end
+ relative = relative + file[:path].split("/")
+ file["url"] = ChefZero::RestBase::build_uri(request.base_uri, relative)
end
end
end
- Chef::JSONCompat.to_json_pretty(result)
+ end
- else
- begin
- entry.read
- rescue Chef::ChefFS::FileSystem::NotFoundError => e
- raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+ # Cookbook artifacts act as if certain things aren't set if they are empty
+ # (e.g. "definitions" and "libraries" are not set if they are empty, but
+ # "recipes" is)
+ if cookbook_type == "cookbook_artifacts"
+ result.delete_if do |key,value|
+ (value == [] && key != "recipes")
+ end
+ result["metadata"] = result["metadata"].to_hash
+ result["metadata"].delete_if do |key,value|
+ value == [] ||
+ (value == {} && !%w{dependencies attributes recipes}.include?(key)) ||
+ (value == "" && %w{source_url issues_url}.include?(key)) ||
+ (value == false && key == "privacy")
end
end
+
+ Chef::JSONCompat.to_json_pretty(result)
+ end
+
+ else
+ with_entry(path) do |entry|
+ begin
+ entry.read
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ raise ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+ end
end
end
end
@@ -191,15 +354,25 @@ class Chef
end
# Write out the files!
- if path[0] == 'cookbooks' && path.length == 3
+ if %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 3
write_cookbook(path, data, *options)
+
+ # Handle /policy_groups/some_policy_group/policies/some_policy_name
+ elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4
+ # Just set or create the proper entry in the hash
+ update_json(to_chef_fs_path(path[0..1]), {}, *options) do |group|
+ group["policies"] ||= {}
+ group["policies"][path[3]] = { "revision_id" => Chef::JSONCompat.parse(data) }
+ group
+ end
+
else
- with_dir(path[0..-2]) do |parent|
- child = parent.child(chef_fs_filename(path))
+ with_parent_dir(path, *options) do |parent, name|
+ child = parent.child(name)
if child.exists?
child.write(data)
else
- parent.create_child(chef_fs_filename(path), data)
+ parent.create_child(name, data)
end
end
end
@@ -209,10 +382,43 @@ class Chef
def delete(path)
if use_memory_store?(path)
@memory_store.delete(path)
+
+ # DELETE /policy_groups/GROUP/policies/POLICY
+ elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4
+ update_json(to_chef_fs_path(path[0..1]), {}) do |group|
+ unless group["policies"] && group["policies"].has_key?(path[3])
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
+ end
+ group["policies"].delete(path[3])
+ group
+ end
+
+ # DELETE [/organizations/ORG]/users/NAME
+ # Manipulates members.json
+ elsif is_org? && path[0] == "users" && path.length == 2
+ update_json("members.json", []) do |members|
+ result = members.reject { |member| member["user"]["username"] == path[1] }
+ if result.size == members.size
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
+ end
+ result
+ end
+
+ # DELETE [/organizations/ORG]/users/NAME
+ # Manipulates members.json
+ elsif is_org? && path[0] == "association_requests" && path.length == 2
+ update_json("invitations.json", []) do |invitations|
+ result = invitations.reject { |invitation| invitation["username"] == path[1] }
+ if result.size == invitations.size
+ raise ChefZero::DataStore::DataNotFoundError.new(path)
+ end
+ result
+ end
+
else
with_entry(path) do |entry|
begin
- if path[0] == 'cookbooks' && path.length >= 3
+ if %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length >= 3
entry.delete(true)
else
entry.delete(false)
@@ -227,6 +433,24 @@ class Chef
def delete_dir(path, *options)
if use_memory_store?(path)
@memory_store.delete_dir(path, *options)
+
+ # DELETE /policies/POLICY
+ elsif path[0] == "policies" && path.length == 2
+ with_entry(path[0..0]) do |policies|
+ # /policies:
+ # - a-1.0.0.json
+ # - a-1.0.1.json
+ # - b-2.0.0.json
+ found_policy = false
+ policies.children.each do |policy|
+ # We want to delete just the ones that == POLICY
+ next unless policy.name.rpartition("-")[0] == path[1]
+ policy.delete(false)
+ found_policy = true
+ end
+ raise ChefZero::DataStore::DataNotFoundError.new(path) if !found_policy
+ end
+
else
with_entry(path) do |entry|
begin
@@ -242,10 +466,45 @@ class Chef
if use_memory_store?(path)
@memory_store.list(path)
- elsif path[0] == 'cookbooks' && path.length == 1
+ # LIST /policies
+ elsif path == [ "policies" ]
+ with_entry([ path[0] ]) do |policies|
+ policies.children.map { |policy| policy.name[0..-6].rpartition("-")[0] }.uniq
+ end
+
+ # LIST /policies/POLICY/revisions
+ elsif path[0] == "policies" && path[2] == "revisions" && path.length == 3
+ with_entry([ path[0] ]) do |policies|
+ # /policies:
+ # - a-1.0.0.json
+ # - a-1.0.1.json
+ # - b-2.0.0.json
+ revisions = []
+ policies.children.each do |policy|
+ name, dash, revision = policy.name[0..-6].rpartition("-")
+ revisions << revision if name == path[1]
+ end
+ raise ChefZero::DataStore::DataNotFoundError.new(path) if revisions.empty?
+ revisions
+ end
+
+ elsif path[0] == "policy_groups" && path.length == 2
+ with_entry(path) do |entry|
+ [ "policies" ]
+ end
+
+ elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 3
+ with_entry(path[0..1]) do |entry|
+ policies = Chef::JSONCompat.parse(entry.read)["policies"] || {}
+ policies.keys
+ end
+
+ elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 1
with_entry(path) do |entry|
begin
- if chef_fs.versioned_cookbooks
+ if path[0] == "cookbook_artifacts"
+ entry.children.map { |child| child.name.rpartition("-")[0] }.uniq
+ elsif chef_fs.versioned_cookbooks
# /cookbooks/name-version -> /cookbooks/name
entry.children.map { |child| split_name_version(child.name)[0] }.uniq
else
@@ -257,9 +516,9 @@ class Chef
end
end
- elsif path[0] == 'cookbooks' && path.length == 2
- if chef_fs.versioned_cookbooks
- result = with_entry([ 'cookbooks' ]) do |entry|
+ elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 2
+ if chef_fs.versioned_cookbooks || path[0] == "cookbook_artifacts"
+ result = with_entry([ path[0] ]) do |entry|
# list /cookbooks/name = filter /cookbooks/name-version down to name
entry.children.map { |child| split_name_version(child.name) }.
select { |name, version| name == path[1] }.
@@ -276,7 +535,7 @@ class Chef
end
else
- with_entry(path) do |entry|
+ result = with_entry(path) do |entry|
begin
entry.children.map { |c| zero_filename(c) }.sort
rescue Chef::ChefFS::FileSystem::NotFoundError => e
@@ -288,12 +547,25 @@ class Chef
end
end
end
+
+ # Older versions of chef-zero do not understand policies and cookbook_artifacts,
+ # don't give that stuff to them
+ if path == [] && ChefZero::VERSION.to_f < 4.4
+ result.reject! { |child| %w{policies policy_data cookbook_artifacts}.include?(child) }
+ end
+ result
end
end
def exists?(path)
if use_memory_store?(path)
@memory_store.exists?(path)
+
+ # /policy_groups/NAME/policies/POLICYNAME
+ elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 4
+ group = get_json(to_chef_fs_path(path[0..1]), {})
+ group["policies"] && group["policies"].has_key?(path[3])
+
else
path_always_exists?(path) || Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
end
@@ -302,8 +574,18 @@ class Chef
def exists_dir?(path)
if use_memory_store?(path)
@memory_store.exists_dir?(path)
- elsif path[0] == 'cookbooks' && path.length == 2
+
+ elsif %w{cookbooks cookbook_artifacts}.include?(path[0]) && path.length == 2
+ list([ path[0] ]).include?(path[1])
+
+ # /policies/NAME
+ elsif path[0] == "policies" && path.length == 2
list([ path[0] ]).include?(path[1])
+
+ # /policy_groups/NAME/policies
+ elsif path[0] == "policy_groups" && path[2] == "policies" && path.length == 3
+ exists_dir?(path[0..1])
+
else
Chef::ChefFS::FileSystem.resolve_path(chef_fs, to_chef_fs_path(path)).exists?
end
@@ -312,34 +594,35 @@ class Chef
private
def use_memory_store?(path)
- return path[0] == 'sandboxes' || path[0] == 'file_store' && path[1] == 'checksums' || path == [ 'environments', '_default' ]
+ return path[0] == "sandboxes" || path[0] == "file_store" && path[1] == "checksums" || path == [ "environments", "_default" ]
end
def write_cookbook(path, data, *options)
+ cookbook_type = path[0]
if chef_fs.versioned_cookbooks
- cookbook_path = File.join('cookbooks', "#{path[1]}-#{path[2]}")
+ cookbook_path = File.join(cookbook_type, "#{path[1]}-#{path[2]}")
else
- cookbook_path = File.join('cookbooks', path[1])
+ cookbook_path = File.join(cookbook_type, path[1])
end
# Create a little Chef::ChefFS memory filesystem with the data
- cookbook_fs = Chef::ChefFS::FileSystem::MemoryRoot.new('uploading')
+ cookbook_fs = Chef::ChefFS::FileSystem::Memory::MemoryRoot.new("uploading")
cookbook = Chef::JSONCompat.parse(data)
cookbook.each_pair do |key, value|
if value.is_a?(Array)
value.each do |file|
- if file.is_a?(Hash) && file.has_key?('checksum')
- file_data = @memory_store.get(['file_store', 'checksums', file['checksum']])
- cookbook_fs.add_file(File.join(cookbook_path, file['path']), file_data)
+ if file.is_a?(Hash) && file.has_key?("checksum")
+ file_data = @memory_store.get(["file_store", "checksums", file["checksum"]])
+ cookbook_fs.add_file(File.join(cookbook_path, file["path"]), file_data)
end
end
end
end
# Create the .uploaded-cookbook-version.json
- cookbooks = chef_fs.child('cookbooks')
+ cookbooks = chef_fs.child(cookbook_type)
if !cookbooks.exists?
- cookbooks = chef_fs.create_child('cookbooks')
+ cookbooks = chef_fs.create_child(cookbook_type)
end
# We are calling a cookbooks-specific API, so get multiplexed_dirs out of the way if it is there
if cookbooks.respond_to?(:multiplexed_dirs)
@@ -349,14 +632,14 @@ class Chef
end
def split_name_version(entry_name)
- name_version = entry_name.split('-')
- name = name_version[0..-2].join('-')
+ name_version = entry_name.split("-")
+ name = name_version[0..-2].join("-")
version = name_version[-1]
[name,version]
end
def to_chef_fs_path(path)
- _to_chef_fs_path(path).join('/')
+ _to_chef_fs_path(path).join("/")
end
def chef_fs_filename(path)
@@ -364,27 +647,28 @@ class Chef
end
def _to_chef_fs_path(path)
- if path[0] == 'data'
+ # /data -> /data_bags
+ # /data/BAG -> /data_bags/BAG
+ # /data/BAG/ITEM -> /data_bags/BAG/ITEM.json
+ if path[0] == "data"
path = path.dup
- path[0] = 'data_bags'
+ path[0] = "data_bags"
if path.length >= 3
path[2] = "#{path[2]}.json"
end
- elsif path[0] == 'policies'
- path = path.dup
- if path.length >= 3
- path[2] = "#{path[2]}.json"
- end
- elsif path[0] == 'cookbooks'
+
+ # /policies/POLICY/revisions/REVISION -> /policies/POLICY-REVISION.json
+ elsif path[0] == "policies" && path[2] == "revisions" && path.length >= 4
+ path = [ "policies", "#{path[1]}-#{path[3]}.json" ]
+
+ elsif %w{cookbooks cookbook_artifacts}.include?(path[0])
if path.length == 2
raise ChefZero::DataStore::DataNotFoundError.new(path)
- elsif chef_fs.versioned_cookbooks
- if path.length >= 3
+ elsif path.length >= 3
+ if chef_fs.versioned_cookbooks || path[0] == "cookbook_artifacts"
# cookbooks/name/version -> cookbooks/name-version
path = [ path[0], "#{path[1]}-#{path[2]}" ] + path[3..-1]
- end
- else
- if path.length >= 3
+ else
# cookbooks/name/version/... -> /cookbooks/name/... iff metadata says so
version = get_single_cookbook_version(path)
if path[2] == version
@@ -394,24 +678,37 @@ class Chef
end
end
end
+
+ elsif path[0] == "acls"
+ # /acls/containers|nodes|.../x.json
+ # /acls/organization.json
+ if path.length == 3 || path == [ "acls", "organization" ]
+ path = path.dup
+ path[-1] = "#{path[-1]}.json"
+ end
+
+ # /acls/containers|nodes|... do NOT drop into the next elsif, and do
+ # not get .json appended
+
+ # /nodes|clients|.../x.json
elsif path.length == 2
path = path.dup
- path[1] = "#{path[1]}.json"
+ path[-1] = "#{path[-1]}.json"
end
path
end
def to_zero_path(entry)
- path = entry.path.split('/')[1..-1]
- if path[0] == 'data_bags'
+ path = entry.path.split("/")[1..-1]
+ if path[0] == "data_bags"
path = path.dup
- path[0] = 'data'
+ path[0] = "data"
if path.length >= 3
path[2] = path[2][0..-6]
end
- elsif path[0] == 'cookbooks'
- if chef_fs.versioned_cookbooks
+ elsif %w{cookbooks cookbook_artifacts}.include?(path[0])
+ if chef_fs.versioned_cookbooks || path[0] == "cookbook_artifacts"
# cookbooks/name-version/... -> cookbooks/name/version/...
if path.length >= 2
name, version = split_name_version(path[1])
@@ -425,7 +722,14 @@ class Chef
end
end
- elsif path.length == 2 && path[0] != 'cookbooks'
+ # /policies/NAME-REVISION.json -> /policies/NAME/revisions/REVISION
+ elsif path[0] == "policies"
+ if path.length >= 2
+ name, dash, revision = path[1][0..-6].rpartition("-")
+ path = [ "policies", name, "revisions", revision ]
+ end
+
+ elsif path.length == 2 && path[0] != "cookbooks"
path = path.dup
path[1] = path[1][0..-6]
end
@@ -437,7 +741,7 @@ class Chef
end
def path_always_exists?(path)
- return path.length == 1 && %w(clients cookbooks data environments nodes roles users).include?(path[0])
+ return path.length == 1 && %w{clients cookbooks data environments nodes roles users}.include?(path[0])
end
def with_entry(path)
@@ -448,9 +752,20 @@ class Chef
end
end
+ def with_parent_dir(path, *options)
+ path = _to_chef_fs_path(path)
+ begin
+ yield get_dir(path[0..-2], options.include?(:create_dir)), path[-1]
+ rescue Chef::ChefFS::FileSystem::NotFoundError => e
+ err = ChefZero::DataStore::DataNotFoundError.new(to_zero_path(e.entry), e)
+ err.set_backtrace(e.backtrace)
+ raise err
+ end
+ end
+
def with_dir(path)
# Do not automatically create data bags
- create = !(path[0] == 'data' && path.size >= 2)
+ create = !(path[0] == "data" && path.size >= 2)
begin
yield get_dir(_to_chef_fs_path(path), create)
@@ -462,10 +777,10 @@ class Chef
end
def get_dir(path, create=false)
- result = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path.join('/'))
+ result = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path.join("/"))
if result.exists?
result
- elsif create
+ elsif create || path.size == 1
get_dir(path[0..-2], create).create_child(result.name, nil)
else
raise ChefZero::DataStore::DataNotFoundError.new(path)
@@ -473,9 +788,46 @@ class Chef
end
def get_single_cookbook_version(path)
- dir = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[0..1].join('/'))
+ dir = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path[0..1].join("/"))
metadata = ChefZero::CookbookData.metadata_from(dir, path[1], nil, [])
- metadata[:version] || '0.0.0'
+ metadata[:version] || "0.0.0"
+ end
+
+ def update_json(path, default_value, *options)
+ entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path)
+ begin
+ input = Chef::JSONCompat.parse(entry.read)
+ output = yield input
+ entry.write(Chef::JSONCompat.to_json_pretty(output)) if output != Chef::JSONCompat.parse(entry.read)
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ # Send the default value to the caller, and create the entry if the caller updates it
+ output = yield default_value
+ parent = entry.parent
+ parent = ensure_dir(parent) if options.include?(:create_dir)
+ parent.create_child(entry.name, Chef::JSONCompat.to_json_pretty(output)) if output != []
+ end
+ end
+
+ def ensure_dir(entry)
+ return entry if entry.exists?
+ parent = entry.parent
+ if parent
+ ensure_dir(parent)
+ parent.create_child(entry.name)
+ end
+ end
+
+ def get_json(path, default_value)
+ entry = Chef::ChefFS::FileSystem.resolve_path(chef_fs, path)
+ begin
+ Chef::JSONCompat.parse(entry.read)
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ default_value
+ end
+ end
+
+ def is_org?
+ repo_mode == "hosted_everything"
end
end
end
diff --git a/lib/chef/chef_fs/command_line.rb b/lib/chef/chef_fs/command_line.rb
index 8a205eef78..e48d5d67cd 100644
--- a/lib/chef/chef_fs/command_line.rb
+++ b/lib/chef/chef_fs/command_line.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system'
-require 'chef/chef_fs/file_system/operation_failed_error'
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
-require 'chef/util/diff'
+require "chef/chef_fs/file_system"
+require "chef/chef_fs/file_system/operation_failed_error"
+require "chef/chef_fs/file_system/operation_not_allowed_error"
+require "chef/util/diff"
class Chef
module ChefFS
@@ -72,7 +72,7 @@ class Chef
elsif old_value
result = "diff --knife #{old_path} #{new_path}\n"
result << "deleted file\n"
- result << diff_text(old_path, '/dev/null', old_value, '')
+ result << diff_text(old_path, "/dev/null", old_value, "")
yield result
else
yield "Only in #{format_path.call(old_entry.parent)}: #{old_entry.name}\n"
@@ -87,7 +87,7 @@ class Chef
elsif new_value
result = "diff --knife #{old_path} #{new_path}\n"
result << "new file\n"
- result << diff_text('/dev/null', new_path, '', new_value)
+ result << diff_text("/dev/null", new_path, "", new_value)
yield result
else
yield "Only in #{format_path.call(new_entry.parent)}: #{new_entry.name}\n"
diff --git a/lib/chef/chef_fs/config.rb b/lib/chef/chef_fs/config.rb
index 6666a3deee..07c014e2ab 100644
--- a/lib/chef/chef_fs/config.rb
+++ b/lib/chef/chef_fs/config.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/chef_fs/path_utils'
+require "chef/log"
+require "chef/chef_fs/path_utils"
class Chef
module ChefFS
@@ -33,6 +33,7 @@ class Chef
"acls" => "acl",
"clients" => "client",
"cookbooks" => "cookbook",
+ "cookbook_artifacts" => "cookbook_artifact",
"containers" => "container",
"data_bags" => "data_bag",
"environments" => "environment",
@@ -40,7 +41,8 @@ class Chef
"nodes" => "node",
"roles" => "role",
"users" => "user",
- "policies" => "policy"
+ "policies" => "policy",
+ "policy_groups" => "policy_group",
}
INFLECTIONS.each { |k,v| k.freeze; v.freeze }
INFLECTIONS.freeze
@@ -111,10 +113,10 @@ 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?
+ if @chef_config[:repo_mode] == "everything" && is_hosted? && !ui.nil?
ui.warn %Q{You have repo_mode set to 'everything', but your chef_server_url
looks like it might be a hosted setup. If this is the case please use
hosted_everything or allow repo_mode to default}
@@ -122,9 +124,9 @@ class Chef
# Default to getting *everything* from the server.
if !@chef_config[:repo_mode]
if is_hosted?
- @chef_config[:repo_mode] = 'hosted_everything'
+ @chef_config[:repo_mode] = "hosted_everything"
else
- @chef_config[:repo_mode] = 'everything'
+ @chef_config[:repo_mode] = "everything"
end
end
end
@@ -142,8 +144,8 @@ class Chef
end
def create_chef_fs
- require 'chef/chef_fs/file_system/chef_server_root_dir'
- Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", @chef_config, :cookbook_version => @cookbook_version)
+ require "chef/chef_fs/file_system/chef_server/chef_server_root_dir"
+ Chef::ChefFS::FileSystem::ChefServer::ChefServerRootDir.new("remote", @chef_config, :cookbook_version => @cookbook_version)
end
def local_fs
@@ -151,8 +153,8 @@ class Chef
end
def create_local_fs
- require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
- Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths, Array(chef_config[:chef_repo_path]).flatten, @chef_config)
+ require "chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir"
+ Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemRootDir.new(object_paths, Array(chef_config[:chef_repo_path]).flatten, @chef_config)
end
# Returns the given real path's location relative to the server root.
@@ -166,50 +168,48 @@ 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
- return '/'
+ # 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
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
@@ -233,12 +233,12 @@ class Chef
@object_paths ||= begin
result = {}
case @chef_config[:repo_mode]
- when 'static'
- object_names = %w(cookbooks data_bags environments roles policies)
- when 'hosted_everything'
- object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles policies)
+ when "static"
+ object_names = %w{cookbooks data_bags environments roles}
+ when "hosted_everything"
+ object_names = %w{acls clients cookbooks cookbook_artifacts containers data_bags environments groups nodes roles policies policy_groups}
else
- object_names = %w(clients cookbooks data_bags environments nodes roles users policies)
+ object_names = %w{clients cookbooks data_bags environments nodes roles users}
end
object_names.each do |object_name|
# cookbooks -> cookbook_path
diff --git a/lib/chef/chef_fs/data_handler/acl_data_handler.rb b/lib/chef/chef_fs/data_handler/acl_data_handler.rb
index 8def8a543d..91e48101c4 100644
--- a/lib/chef/chef_fs/data_handler/acl_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/acl_data_handler.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
+require "chef/chef_fs/data_handler/data_handler_base"
class Chef
module ChefFS
@@ -7,16 +7,16 @@ class Chef
def normalize(acl, entry)
# Normalize the order of the keys for easier reading
result = normalize_hash(acl, {
- 'create' => {},
- 'read' => {},
- 'update' => {},
- 'delete' => {},
- 'grant' => {}
- })
+ "create" => {},
+ "read" => {},
+ "update" => {},
+ "delete" => {},
+ "grant" => {},
+ },)
result.keys.each do |key|
- result[key] = normalize_hash(result[key], { 'actors' => [], 'groups' => [] })
- result[key]['actors'] = result[key]['actors'].sort
- result[key]['groups'] = result[key]['groups'].sort
+ result[key] = normalize_hash(result[key], { "actors" => [], "groups" => [] })
+ result[key]["actors"] = result[key]["actors"].sort
+ result[key]["groups"] = result[key]["groups"].sort
end
result
end
diff --git a/lib/chef/chef_fs/data_handler/client_data_handler.rb b/lib/chef/chef_fs/data_handler/client_data_handler.rb
index d81f35e861..5e120035ac 100644
--- a/lib/chef/chef_fs/data_handler/client_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/client_data_handler.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
-require 'chef/api_client'
+require "chef/chef_fs/data_handler/data_handler_base"
+require "chef/api_client"
class Chef
module ChefFS
@@ -7,23 +7,25 @@ class Chef
class ClientDataHandler < DataHandlerBase
def normalize(client, entry)
defaults = {
- 'name' => remove_dot_json(entry.name),
- 'clientname' => remove_dot_json(entry.name),
- 'admin' => false,
- 'validator' => false,
- 'chef_type' => 'client'
+ "name" => remove_dot_json(entry.name),
+ "clientname" => remove_dot_json(entry.name),
+ "admin" => false,
+ "validator" => false,
+ "chef_type" => "client",
}
+ # Handle the fact that admin/validator have changed type from string -> boolean
+ client["admin"] = (client["admin"] == "true") if client["admin"].is_a?(String)
+ client["validator"] = (client["validator"] == "true") if client["validator"].is_a?(String)
if entry.respond_to?(:org) && entry.org
- defaults['orgname'] = entry.org
+ defaults["orgname"] = entry.org
end
result = normalize_hash(client, defaults)
- # You can NOT send json_class, or it will fail
- result.delete('json_class')
+ result.delete("json_class")
result
end
def preserve_key?(key)
- return key == 'name'
+ return key == "name"
end
def chef_class
diff --git a/lib/chef/chef_fs/data_handler/container_data_handler.rb b/lib/chef/chef_fs/data_handler/container_data_handler.rb
index 980453cbab..d1e8d2f3af 100644
--- a/lib/chef/chef_fs/data_handler/container_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/container_data_handler.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
+require "chef/chef_fs/data_handler/data_handler_base"
class Chef
module ChefFS
@@ -6,18 +6,18 @@ class Chef
class ContainerDataHandler < DataHandlerBase
def normalize(container, entry)
normalize_hash(container, {
- 'containername' => remove_dot_json(entry.name),
- 'containerpath' => remove_dot_json(entry.name)
- })
+ "containername" => remove_dot_json(entry.name),
+ "containerpath" => remove_dot_json(entry.name),
+ },)
end
def preserve_key?(key)
- return key == 'containername'
+ return key == "containername"
end
def verify_integrity(object, entry, &on_error)
base_name = remove_dot_json(entry.name)
- if object['containername'] != base_name
+ if object["containername"] != base_name
on_error.call("Name in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['name']}')")
end
end
diff --git a/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb b/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
index 56b7e0b765..f75f96f773 100644
--- a/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/cookbook_data_handler.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
-require 'chef/cookbook/metadata'
+require "chef/chef_fs/data_handler/data_handler_base"
+require "chef/cookbook/metadata"
class Chef
module ChefFS
@@ -9,22 +9,22 @@ class Chef
version = entry.name
name = entry.parent.name
result = normalize_hash(cookbook, {
- 'name' => "#{name}-#{version}",
- 'version' => version,
- 'cookbook_name' => name,
- 'json_class' => 'Chef::CookbookVersion',
- 'chef_type' => 'cookbook_version',
- 'frozen?' => false,
- 'metadata' => {}
- })
- result['metadata'] = normalize_hash(result['metadata'], {
- 'version' => version,
- 'name' => name
- })
+ "name" => "#{name}-#{version}",
+ "version" => version,
+ "cookbook_name" => name,
+ "json_class" => "Chef::CookbookVersion",
+ "chef_type" => "cookbook_version",
+ "frozen?" => false,
+ "metadata" => {},
+ },)
+ result["metadata"] = normalize_hash(result["metadata"], {
+ "version" => version,
+ "name" => name,
+ },)
end
def preserve_key?(key)
- return key == 'cookbook_name' || key == 'version'
+ return key == "cookbook_name" || key == "version"
end
def chef_class
diff --git a/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb b/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
index 1306922081..d56f662c5c 100644
--- a/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/data_bag_item_data_handler.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
-require 'chef/data_bag_item'
+require "chef/chef_fs/data_handler/data_handler_base"
+require "chef/data_bag_item"
class Chef
module ChefFS
@@ -7,26 +7,26 @@ class Chef
class DataBagItemDataHandler < DataHandlerBase
def normalize(data_bag_item, entry)
# If it's wrapped with raw_data, unwrap it.
- if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
- data_bag_item = data_bag_item['raw_data']
+ if data_bag_item["json_class"] == "Chef::DataBagItem" && data_bag_item["raw_data"]
+ data_bag_item = data_bag_item["raw_data"]
end
# chef_type and data_bag come back in PUT and POST results, but we don't
# use those in knife-essentials.
normalize_hash(data_bag_item, {
- 'id' => remove_dot_json(entry.name)
- })
+ "id" => remove_dot_json(entry.name)
+ },)
end
def normalize_for_post(data_bag_item, entry)
- if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
- data_bag_item = data_bag_item['raw_data']
+ if data_bag_item["json_class"] == "Chef::DataBagItem" && data_bag_item["raw_data"]
+ data_bag_item = data_bag_item["raw_data"]
end
{
"name" => "data_bag_item_#{entry.parent.name}_#{remove_dot_json(entry.name)}",
"json_class" => "Chef::DataBagItem",
"chef_type" => "data_bag_item",
"data_bag" => entry.parent.name,
- "raw_data" => normalize(data_bag_item, entry)
+ "raw_data" => normalize(data_bag_item, entry),
}
end
@@ -35,7 +35,7 @@ class Chef
end
def preserve_key?(key)
- return key == 'id'
+ return key == "id"
end
def chef_class
@@ -44,7 +44,7 @@ class Chef
def verify_integrity(object, entry, &on_error)
base_name = remove_dot_json(entry.name)
- if object['raw_data']['id'] != base_name
+ if object["raw_data"]["id"] != base_name
on_error.call("ID in #{entry.path_for_printing} must be '#{base_name}' (is '#{object['raw_data']['id']}')")
end
end
diff --git a/lib/chef/chef_fs/data_handler/data_handler_base.rb b/lib/chef/chef_fs/data_handler/data_handler_base.rb
index a3dc92405c..53936979e3 100644
--- a/lib/chef/chef_fs/data_handler/data_handler_base.rb
+++ b/lib/chef/chef_fs/data_handler/data_handler_base.rb
@@ -147,7 +147,7 @@ class Chef
# environment "desert"'
#
def to_ruby_keys(object, keys)
- result = ''
+ result = ""
keys.each do |key|
if object[key]
if object[key].is_a?(Hash)
@@ -158,7 +158,7 @@ class Chef
if first
first = false
else
- result << ' '*key.length
+ result << " "*key.length
end
result << " #{k.inspect} => #{v.inspect}\n"
end
@@ -191,7 +191,7 @@ class Chef
#
def verify_integrity(object, entry, &on_error)
base_name = remove_dot_json(entry.name)
- if object['name'] != base_name
+ if object["name"] != base_name
on_error.call("Name must be '#{base_name}' (is '#{object['name']}')")
end
end
diff --git a/lib/chef/chef_fs/data_handler/environment_data_handler.rb b/lib/chef/chef_fs/data_handler/environment_data_handler.rb
index 5105f2ac49..3b9d5b4ec7 100644
--- a/lib/chef/chef_fs/data_handler/environment_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/environment_data_handler.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
-require 'chef/environment'
+require "chef/chef_fs/data_handler/data_handler_base"
+require "chef/environment"
class Chef
module ChefFS
@@ -7,18 +7,18 @@ class Chef
class EnvironmentDataHandler < DataHandlerBase
def normalize(environment, entry)
normalize_hash(environment, {
- 'name' => remove_dot_json(entry.name),
- 'description' => '',
- 'cookbook_versions' => {},
- 'default_attributes' => {},
- 'override_attributes' => {},
- 'json_class' => 'Chef::Environment',
- 'chef_type' => 'environment'
- })
+ "name" => remove_dot_json(entry.name),
+ "description" => "",
+ "cookbook_versions" => {},
+ "default_attributes" => {},
+ "override_attributes" => {},
+ "json_class" => "Chef::Environment",
+ "chef_type" => "environment",
+ },)
end
def preserve_key?(key)
- return key == 'name'
+ return key == "name"
end
def chef_class
@@ -26,9 +26,9 @@ class Chef
end
def to_ruby(object)
- result = to_ruby_keys(object, %w(name description default_attributes override_attributes))
- if object['cookbook_versions']
- object['cookbook_versions'].each_pair do |name, version|
+ result = to_ruby_keys(object, %w{name description default_attributes override_attributes})
+ if object["cookbook_versions"]
+ object["cookbook_versions"].each_pair do |name, version|
result << "cookbook #{name.inspect}, #{version.inspect}"
end
end
diff --git a/lib/chef/chef_fs/data_handler/group_data_handler.rb b/lib/chef/chef_fs/data_handler/group_data_handler.rb
index 1a36c66eb8..7f38784826 100644
--- a/lib/chef/chef_fs/data_handler/group_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/group_data_handler.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
-require 'chef/api_client'
+require "chef/chef_fs/data_handler/data_handler_base"
+require "chef/api_client"
class Chef
module ChefFS
@@ -7,32 +7,32 @@ class Chef
class GroupDataHandler < DataHandlerBase
def normalize(group, entry)
defaults = {
- 'name' => remove_dot_json(entry.name),
- 'groupname' => remove_dot_json(entry.name),
- 'users' => [],
- 'clients' => [],
- 'groups' => [],
+ "name" => remove_dot_json(entry.name),
+ "groupname" => remove_dot_json(entry.name),
+ "users" => [],
+ "clients" => [],
+ "groups" => [],
}
if entry.org
- defaults['orgname'] = entry.org
+ defaults["orgname"] = entry.org
end
result = normalize_hash(group, defaults)
- if result['actors'] && result['actors'].sort.uniq == (result['users'] + result['clients']).sort.uniq
- result.delete('actors')
+ if result["actors"] && result["actors"].sort.uniq == (result["users"] + result["clients"]).sort.uniq
+ result.delete("actors")
end
result
end
def normalize_for_put(group, entry)
result = super(group, entry)
- result['actors'] = {
- 'users' => result['users'],
- 'clients' => result['clients'],
- 'groups' => result['groups']
+ result["actors"] = {
+ "users" => result["users"],
+ "clients" => result["clients"],
+ "groups" => result["groups"],
}
- result.delete('users')
- result.delete('clients')
- result.delete('groups')
+ result.delete("users")
+ result.delete("clients")
+ result.delete("groups")
result
end
@@ -41,7 +41,7 @@ class Chef
end
def preserve_key?(key)
- return key == 'name'
+ return key == "name"
end
def chef_class
diff --git a/lib/chef/chef_fs/data_handler/node_data_handler.rb b/lib/chef/chef_fs/data_handler/node_data_handler.rb
index 04faa527f0..fcec74d2fb 100644
--- a/lib/chef/chef_fs/data_handler/node_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/node_data_handler.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
-require 'chef/node'
+require "chef/chef_fs/data_handler/data_handler_base"
+require "chef/node"
class Chef
module ChefFS
@@ -7,22 +7,22 @@ class Chef
class NodeDataHandler < DataHandlerBase
def normalize(node, entry)
result = normalize_hash(node, {
- 'name' => remove_dot_json(entry.name),
- 'json_class' => 'Chef::Node',
- 'chef_type' => 'node',
- 'chef_environment' => '_default',
- 'override' => {},
- 'normal' => {},
- 'default' => {},
- 'automatic' => {},
- 'run_list' => []
- })
- result['run_list'] = normalize_run_list(result['run_list'])
+ "name" => remove_dot_json(entry.name),
+ "json_class" => "Chef::Node",
+ "chef_type" => "node",
+ "chef_environment" => "_default",
+ "override" => {},
+ "normal" => {},
+ "default" => {},
+ "automatic" => {},
+ "run_list" => [],
+ },)
+ result["run_list"] = normalize_run_list(result["run_list"])
result
end
def preserve_key?(key)
- return key == 'name'
+ return key == "name"
end
def chef_class
diff --git a/lib/chef/chef_fs/data_handler/organization_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_data_handler.rb
index da911c08f0..d9f97b29ac 100644
--- a/lib/chef/chef_fs/data_handler/organization_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/organization_data_handler.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
+require "chef/chef_fs/data_handler/data_handler_base"
class Chef
module ChefFS
@@ -6,21 +6,21 @@ class Chef
class OrganizationDataHandler < DataHandlerBase
def normalize(organization, entry)
result = normalize_hash(organization, {
- 'name' => entry.org,
- 'full_name' => entry.org,
- 'org_type' => 'Business',
- 'clientname' => "#{entry.org}-validator",
- 'billing_plan' => 'platform-free',
- })
+ "name" => entry.org,
+ "full_name" => entry.org,
+ "org_type" => "Business",
+ "clientname" => "#{entry.org}-validator",
+ "billing_plan" => "platform-free",
+ },)
result
end
def preserve_key?(key)
- return key == 'name'
+ return key == "name"
end
def verify_integrity(object, entry, &on_error)
- if entry.org != object['name']
+ if entry.org != object["name"]
on_error.call("Name must be '#{entry.org}' (is '#{object['name']}')")
end
end
diff --git a/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb
index db56ecc504..c5a5f873c5 100644
--- a/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/organization_invites_data_handler.rb
@@ -1,11 +1,11 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
+require "chef/chef_fs/data_handler/data_handler_base"
class Chef
module ChefFS
module DataHandler
class OrganizationInvitesDataHandler < DataHandlerBase
def normalize(invites, entry)
- invites.map { |invite| invite.is_a?(Hash) ? invite['username'] : invite }.sort.uniq
+ invites.map { |invite| invite.is_a?(Hash) ? invite["username"] : invite }.sort.uniq
end
def minimize(invites, entry)
diff --git a/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb b/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb
index afa331775c..8e452a413c 100644
--- a/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/organization_members_data_handler.rb
@@ -1,11 +1,11 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
+require "chef/chef_fs/data_handler/data_handler_base"
class Chef
module ChefFS
module DataHandler
class OrganizationMembersDataHandler < DataHandlerBase
def normalize(members, entry)
- members.map { |member| member.is_a?(Hash) ? member['user']['username'] : member }.sort.uniq
+ members.map { |member| member.is_a?(Hash) ? member["user"]["username"] : member }.sort.uniq
end
def minimize(members, entry)
diff --git a/lib/chef/chef_fs/data_handler/policy_data_handler.rb b/lib/chef/chef_fs/data_handler/policy_data_handler.rb
index 769c13c364..477d2bf637 100644
--- a/lib/chef/chef_fs/data_handler/policy_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/policy_data_handler.rb
@@ -1,15 +1,42 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
+require "chef/chef_fs/data_handler/data_handler_base"
class Chef
module ChefFS
module DataHandler
class PolicyDataHandler < DataHandlerBase
+ def name_and_revision(name)
+ # foo-1.0.0 = foo, 1.0.0
+ name = remove_dot_json(name)
+ if name =~ /^(.*)-([^-]*)$/
+ name, revision_id = $1, $2
+ end
+ revision_id ||= "0.0.0"
+ [ name, revision_id ]
+ end
def normalize(policy, entry)
- policy
+ # foo-1.0.0 = foo, 1.0.0
+ name, revision_id = name_and_revision(entry.name)
+ defaults = {
+ "name" => name,
+ "revision_id" => revision_id,
+ "run_list" => [],
+ "cookbook_locks" => {},
+ }
+ normalize_hash(policy, defaults)
+ end
+
+ def verify_integrity(object_data, entry, &on_error)
+ name, revision = name_and_revision(entry.name)
+ if object_data["name"] != name
+ on_error.call("Object name '#{object_data['name']}' doesn't match entry '#{entry.name}'.")
+ end
+
+ if object_data["revision_id"] != revision
+ on_error.call("Object revision ID '#{object_data['revision']}' doesn't match entry '#{entry.name}'.")
+ end
end
end
end
end
end
-
diff --git a/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb b/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb
new file mode 100644
index 0000000000..e5c430ab64
--- /dev/null
+++ b/lib/chef/chef_fs/data_handler/policy_group_data_handler.rb
@@ -0,0 +1,27 @@
+require "chef/chef_fs/data_handler/data_handler_base"
+
+class Chef
+ module ChefFS
+ module DataHandler
+ class PolicyGroupDataHandler < DataHandlerBase
+
+ def normalize(policy_group, entry)
+ defaults = {
+ "name" => remove_dot_json(entry.name),
+ "policies" => {},
+ }
+ result = normalize_hash(policy_group, defaults)
+ result.delete("uri") # not useful data
+ result
+ end
+
+ def verify_integrity(object_data, entry, &on_error)
+ if object_data["policies"].empty?
+ on_error.call("Policy group #{object_data["name"]} does not have any policies in it.")
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/data_handler/role_data_handler.rb b/lib/chef/chef_fs/data_handler/role_data_handler.rb
index 21c3013e9f..eb10f7bb4a 100644
--- a/lib/chef/chef_fs/data_handler/role_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/role_data_handler.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
-require 'chef/role'
+require "chef/chef_fs/data_handler/data_handler_base"
+require "chef/role"
class Chef
module ChefFS
@@ -7,24 +7,24 @@ class Chef
class RoleDataHandler < DataHandlerBase
def normalize(role, entry)
result = normalize_hash(role, {
- 'name' => remove_dot_json(entry.name),
- 'description' => '',
- 'json_class' => 'Chef::Role',
- 'chef_type' => 'role',
- 'default_attributes' => {},
- 'override_attributes' => {},
- 'run_list' => [],
- 'env_run_lists' => {}
- })
- result['run_list'] = normalize_run_list(result['run_list'])
- result['env_run_lists'].each_pair do |env, run_list|
- result['env_run_lists'][env] = normalize_run_list(run_list)
+ "name" => remove_dot_json(entry.name),
+ "description" => "",
+ "json_class" => "Chef::Role",
+ "chef_type" => "role",
+ "default_attributes" => {},
+ "override_attributes" => {},
+ "run_list" => [],
+ "env_run_lists" => {},
+ },)
+ result["run_list"] = normalize_run_list(result["run_list"])
+ result["env_run_lists"].each_pair do |env, run_list|
+ result["env_run_lists"][env] = normalize_run_list(run_list)
end
result
end
def preserve_key?(key)
- return key == 'name'
+ return key == "name"
end
def chef_class
@@ -32,7 +32,7 @@ class Chef
end
def to_ruby(object)
- to_ruby_keys(object, %w(name description default_attributes override_attributes run_list env_run_lists))
+ to_ruby_keys(object, %w{name description default_attributes override_attributes run_list env_run_lists})
end
end
end
diff --git a/lib/chef/chef_fs/data_handler/user_data_handler.rb b/lib/chef/chef_fs/data_handler/user_data_handler.rb
index f6859faccd..392e67f822 100644
--- a/lib/chef/chef_fs/data_handler/user_data_handler.rb
+++ b/lib/chef/chef_fs/data_handler/user_data_handler.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/data_handler/data_handler_base'
+require "chef/chef_fs/data_handler/data_handler_base"
class Chef
module ChefFS
@@ -6,20 +6,20 @@ class Chef
class UserDataHandler < DataHandlerBase
def normalize(user, entry)
normalize_hash(user, {
- 'name' => remove_dot_json(entry.name),
- 'username' => remove_dot_json(entry.name),
- 'display_name' => remove_dot_json(entry.name),
- 'admin' => false,
- 'json_class' => 'Chef::WebUIUser',
- 'chef_type' => 'webui_user',
- 'salt' => nil,
- 'password' => nil,
- 'openid' => nil
- })
+ "name" => remove_dot_json(entry.name),
+ "username" => remove_dot_json(entry.name),
+ "display_name" => remove_dot_json(entry.name),
+ "admin" => false,
+ "json_class" => "Chef::WebUIUser",
+ "chef_type" => "webui_user",
+ "salt" => nil,
+ "password" => nil,
+ "openid" => nil,
+ },)
end
def preserve_key?(key)
- return key == 'name'
+ return key == "name"
end
# There is no chef_class for users, nor does to_ruby work.
diff --git a/lib/chef/chef_fs/file_pattern.rb b/lib/chef/chef_fs/file_pattern.rb
index 134d22cbd5..f6081a5f33 100644
--- a/lib/chef/chef_fs/file_pattern.rb
+++ b/lib/chef/chef_fs/file_pattern.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/chef_fs'
-require 'chef/chef_fs/path_utils'
+require "chef/chef_fs"
+require "chef/chef_fs/path_utils"
class Chef
module ChefFS
@@ -70,9 +70,9 @@ class Chef
# abc/def.could_match_children?('x') == false
# a**z.could_match_children?('ab/cd') == true
def could_match_children?(path)
- return false if path == '' # Empty string is not a path
+ 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]
@@ -125,7 +125,7 @@ class Chef
def exact_path
return nil if has_double_star || exact_parts.any? { |part| part.nil? }
result = Chef::ChefFS::PathUtils::join(*exact_parts)
- is_absolute ? Chef::ChefFS::PathUtils::join('', result) : result
+ is_absolute ? Chef::ChefFS::PathUtils::join("", result) : result
end
# Returns the normalized version of the pattern, with / as the directory
@@ -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 = []
@@ -210,12 +199,12 @@ class Chef
end
# Skip // and /./ (pretend it's not there)
- if exact == '' || exact == '.'
+ if exact == "" || exact == "."
next
end
# Back up when you see .. (unless the prior part has ** in it, in which case .. must be preserved)
- if exact == '..'
+ if exact == ".."
if @is_absolute && normalized_parts.length == 0
# If we are at the root, just pretend the .. isn't there
next
@@ -245,7 +234,7 @@ class Chef
@regexp = Regexp.new("^#{full_regexp_parts.join(Chef::ChefFS::PathUtils::regexp_path_separator)}$")
@normalized_pattern = Chef::ChefFS::PathUtils.join(*normalized_parts)
- @normalized_pattern = Chef::ChefFS::PathUtils.join('', @normalized_pattern) if @is_absolute
+ @normalized_pattern = Chef::ChefFS::PathUtils.join("", @normalized_pattern) if @is_absolute
end
end
@@ -260,7 +249,7 @@ class Chef
end
def self.regexp_escape_characters
- [ '[', '\\', '^', '$', '.', '|', '?', '*', '+', '(', ')', '{', '}' ]
+ [ "[", '\\', "^", "$", ".", "|", "?", "*", "+", "(", ")", "{", "}" ]
end
def self.pattern_to_regexp(pattern)
@@ -275,16 +264,16 @@ class Chef
else
case part
# **, * and ? happen on both platforms.
- when '**'
+ when "**"
exact = nil
has_double_star = true
- regexp << '.*'
- when '*'
+ regexp << ".*"
+ when "*"
exact = nil
regexp << '[^\/]*'
- when '?'
+ when "?"
exact = nil
- regexp << '.'
+ regexp << "."
else
if part[0,1] == '\\' && part.length == 2
# backslash escapes are only supported on Unix, and are handled here by leaving the escape on (it means the same thing in a regex)
@@ -294,7 +283,7 @@ class Chef
else
regexp << part[1,1]
end
- elsif part[0,1] == '[' && part.length > 1
+ elsif part[0,1] == "[" && part.length > 1
# [...] happens only on Unix, and is handled here by *not* backslashing (it means the same thing in and out of regex)
exact = nil
regexp << part
diff --git a/lib/chef/chef_fs/file_system.rb b/lib/chef/chef_fs/file_system.rb
index 3ab59046cc..f2d6fe1026 100644
--- a/lib/chef/chef_fs/file_system.rb
+++ b/lib/chef/chef_fs/file_system.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/chef_fs/path_utils'
-require 'chef/chef_fs/file_system/default_environment_cannot_be_modified_error'
-require 'chef/chef_fs/file_system/operation_failed_error'
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
-require 'chef/chef_fs/parallelizer'
+require "chef/chef_fs/path_utils"
+require "chef/chef_fs/file_system/default_environment_cannot_be_modified_error"
+require "chef/chef_fs/file_system/operation_failed_error"
+require "chef/chef_fs/file_system/operation_not_allowed_error"
+require "chef/chef_fs/parallelizer"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/acl_dir.rb b/lib/chef/chef_fs/file_system/acl_dir.rb
deleted file mode 100644
index c2354d478d..0000000000
--- a/lib/chef/chef_fs/file_system/acl_dir.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/acl_entry'
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
-
-class Chef
- module ChefFS
- module FileSystem
- class AclDir < BaseFSDir
- def api_path
- parent.parent.child(name).api_path
- end
-
- def child(name)
- result = @children.select { |child| child.name == name }.first if @children
- result ||= can_have_child?(name, false) ?
- AclEntry.new(name, self) : NonexistentFSObject.new(name, self)
- end
-
- def can_have_child?(name, is_dir)
- name =~ /\.json$/ && !is_dir
- end
-
- def children
- if @children.nil?
- # Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
- names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
- @children = names.map { |name| AclEntry.new(name, self, true) }
- end
- @children
- end
-
- def create_child(name, file_contents)
- raise OperationNotAllowedError.new(:create_child, self), "ACLs can only be updated, and can only be created when the corresponding object is created."
- end
-
- def data_handler
- parent.data_handler
- end
-
- def rest
- parent.rest
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/acls_dir.rb b/lib/chef/chef_fs/file_system/acls_dir.rb
deleted file mode 100644
index 938bf73fb2..0000000000
--- a/lib/chef/chef_fs/file_system/acls_dir.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/acl_dir'
-require 'chef/chef_fs/file_system/cookbooks_acl_dir'
-require 'chef/chef_fs/file_system/acl_entry'
-require 'chef/chef_fs/data_handler/acl_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- class AclsDir < BaseFSDir
- ENTITY_TYPES = %w(clients containers cookbooks data_bags environments groups nodes roles) # we don't read sandboxes, so we don't read their acls
-
- def initialize(parent)
- super('acls', parent)
- end
-
- def data_handler
- @data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new
- end
-
- def api_path
- parent.api_path
- end
-
- def can_have_child?(name, is_dir)
- is_dir ? ENTITY_TYPES.include(name) : name == 'organization.json'
- end
-
- def children
- if @children.nil?
- @children = ENTITY_TYPES.map do |entity_type|
- case entity_type
- when 'cookbooks'
- CookbooksAclDir.new(entity_type, self)
- else
- AclDir.new(entity_type, self)
- end
- end
- @children << AclEntry.new('organization.json', self, true) # the org acl is retrieved as GET /organizations/ORGNAME/ANYTHINGATALL/_acl
- end
- @children
- end
-
- def rest
- parent.rest
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/already_exists_error.rb b/lib/chef/chef_fs/file_system/already_exists_error.rb
index bf8994fdf3..ef93ed7729 100644
--- a/lib/chef/chef_fs/file_system/already_exists_error.rb
+++ b/lib/chef/chef_fs/file_system/already_exists_error.rb
@@ -16,15 +16,12 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/operation_failed_error'
+require "chef/chef_fs/file_system/operation_failed_error"
class Chef
module ChefFS
module FileSystem
class AlreadyExistsError < OperationFailedError
- def initialize(operation, entry, cause = nil)
- super(operation, entry, cause)
- end
end
end
end
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..2c3f0bf5ae 100644
--- a/lib/chef/chef_fs/file_system/base_fs_dir.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_dir.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/base_fs_object'
-require 'chef/chef_fs/file_system/nonexistent_fs_object'
+require "chef/chef_fs/file_system/base_fs_object"
+require "chef/chef_fs/file_system/nonexistent_fs_object"
class Chef
module ChefFS
@@ -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..5af8a99dd3 100644
--- a/lib/chef/chef_fs/file_system/base_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/base_fs_object.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/chef_fs/path_utils'
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
+require "chef/chef_fs/path_utils"
+require "chef/chef_fs/file_system/operation_not_allowed_error"
class Chef
module ChefFS
@@ -29,10 +29,10 @@ class Chef
if parent
@path = Chef::ChefFS::PathUtils::join(parent.path, name)
else
- if name != ''
+ if name != ""
raise ArgumentError, "Name of root object must be empty string: was '#{name}' instead"
end
- @path = '/'
+ @path = "/"
end
end
@@ -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.
@@ -143,7 +146,7 @@ class Chef
def path_for_printing
if parent
parent_path = parent.path_for_printing
- if parent_path == '.'
+ if parent_path == "."
name
else
Chef::ChefFS::PathUtils::join(parent.path_for_printing, name)
@@ -171,10 +174,10 @@ 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
end
-require 'chef/chef_fs/file_system/nonexistent_fs_object'
+require "chef/chef_fs/file_system/nonexistent_fs_object"
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
deleted file mode 100644
index a7f1d733b1..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry'
-require 'chef/chef_fs/file_system/cookbook_dir'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/cookbook/chefignore'
-require 'chef/cookbook/cookbook_version_loader'
-
-class Chef
- module ChefFS
- module FileSystem
- class ChefRepositoryFileSystemCookbookDir < ChefRepositoryFileSystemCookbookEntry
- def initialize(name, parent, file_path = nil)
- super(name, parent, file_path)
- end
-
- def chef_object
- begin
- loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
- # We need the canonical cookbook name if we are using versioned cookbooks, but we don't
- # want to spend a lot of time adding code to the main Chef libraries
- if root.versioned_cookbooks
- canonical_name = canonical_cookbook_name(File.basename(file_path))
- fail "When versioned_cookbooks mode is on, cookbook #{file_path} must match format <cookbook_name>-x.y.z" unless canonical_name
-
- # KLUDGE: We shouldn't have to use instance_variable_set
- loader.instance_variable_set(:@cookbook_name, canonical_name)
- end
-
- loader.load_cookbooks
- cb = loader.cookbook_version
- if !cb
- Chef::Log.error("Cookbook #{file_path} empty.")
- raise "Cookbook #{file_path} empty."
- end
- cb
- rescue => e
- Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{e}")
- Chef::Log.error(e.backtrace.join("\n"))
- raise
- end
- end
-
- def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select { |entry| !(entry.dir? && entry.children.size == 0) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
- def can_have_child?(name, is_dir)
- if is_dir
- # Only the given directories will be uploaded.
- return CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != 'root_files'
- elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE
- return false
- end
- super(name, is_dir)
- end
-
- # Exposed as a class method so that it can be used elsewhere
- def self.canonical_cookbook_name(entry_name)
- name_match = Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME.match(entry_name)
- return nil if name_match.nil?
- return name_match[1]
- end
-
- def canonical_cookbook_name(entry_name)
- self.class.canonical_cookbook_name(entry_name)
- end
-
- def uploaded_cookbook_version_path
- File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
- end
-
- def can_upload?
- File.exists?(uploaded_cookbook_version_path) || children.size > 0
- end
-
- protected
-
- def make_child(child_name)
- segment_info = CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
- ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
deleted file mode 100644
index 66709ccf68..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
-require 'chef/chef_fs/file_system/not_found_error'
-
-class Chef
- module ChefFS
- module FileSystem
- class ChefRepositoryFileSystemCookbookEntry < ChefRepositoryFileSystemEntry
- def initialize(name, parent, file_path = nil, ruby_only = false, recursive = false)
- super(name, parent, file_path)
- @ruby_only = ruby_only
- @recursive = recursive
- end
-
- attr_reader :ruby_only
- attr_reader :recursive
-
- def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select { |entry| !(entry.dir? && entry.children.size == 0) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
- def can_have_child?(name, is_dir)
- if is_dir
- return recursive && name != '.' && name != '..'
- elsif ruby_only
- return false if name[-3..-1] != '.rb'
- end
-
- # Check chefignore
- ignorer = parent
- loop do
- if ignorer.is_a?(ChefRepositoryFileSystemCookbooksDir)
- # Grab the path from entry to child
- path_to_child = name
- child = self
- while child.parent != ignorer
- path_to_child = PathUtils.join(child.name, path_to_child)
- child = child.parent
- end
- # Check whether that relative path is ignored
- return !ignorer.chefignore || !ignorer.chefignore.ignored?(path_to_child)
- end
- ignorer = ignorer.parent
- break unless ignorer
- end
-
- true
- end
-
- def write_pretty_json
- false
- end
-
- protected
-
- def make_child(child_name)
- ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
deleted file mode 100644
index 7c60b51114..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir'
-require 'chef/cookbook/chefignore'
-
-class Chef
- module ChefFS
- module FileSystem
- class ChefRepositoryFileSystemCookbooksDir < ChefRepositoryFileSystemEntry
- def initialize(name, parent, file_path)
- super(name, parent, file_path)
- begin
- @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
- rescue Errno::EISDIR
- rescue Errno::EACCES
- # Work around a bug in Chefignore when chefignore is a directory
- end
- end
-
- attr_reader :chefignore
-
- def children
- begin
- Dir.entries(file_path).sort.
- select { |child_name| can_have_child?(child_name, File.directory?(File.join(file_path, child_name))) }.
- map { |child_name| make_child(child_name) }.
- select do |entry|
- # empty cookbooks and cookbook directories are ignored
- if !entry.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, $!)
- end
- end
-
- def can_have_child?(name, is_dir)
- is_dir && !name.start_with?('.')
- end
-
- def write_cookbook(cookbook_path, cookbook_version_json, from_fs)
- cookbook_name = File.basename(cookbook_path)
- child = make_child(cookbook_name)
-
- # Use the copy/diff algorithm to copy it down so we don't destroy
- # chefignored data. This is terribly un-thread-safe.
- Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), from_fs, child, nil, {:purge => true})
-
- # Write out .uploaded-cookbook-version.json
- cookbook_file_path = File.join(file_path, cookbook_name)
- if !File.exists?(cookbook_file_path)
- FileUtils.mkdir_p(cookbook_file_path)
- end
- uploaded_cookbook_version_path = File.join(cookbook_file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
- File.open(uploaded_cookbook_version_path, 'w') do |file|
- file.write(cookbook_version_json)
- end
- end
-
- protected
-
- def make_child(child_name)
- ChefRepositoryFileSystemCookbookDir.new(child_name, self)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
deleted file mode 100644
index 0b14750744..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Author:: Ho-Sheng Hsiao (<hosh@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/file_system_entry'
-require 'chef/chef_fs/file_system/not_found_error'
-
-class Chef
- module ChefFS
- module FileSystem
- # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
- # except can inflate Chef objects
- class ChefRepositoryFileSystemEntry < FileSystemEntry
- def initialize(name, parent, file_path = nil, data_handler = nil)
- super(name, parent, file_path)
- @data_handler = data_handler
- end
-
- def write_pretty_json=(value)
- @write_pretty_json = value
- end
-
- def write_pretty_json
- @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json
- end
-
- def data_handler
- @data_handler || parent.data_handler
- end
-
- def chef_object
- begin
- return data_handler.chef_object(Chef::JSONCompat.parse(read))
- rescue
- Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
- end
- nil
- end
-
- def can_have_child?(name, is_dir)
- !is_dir && name[-5..-1] == '.json'
- end
-
- def write(file_contents)
- if file_contents && write_pretty_json && name[-5..-1] == '.json'
- file_contents = minimize(file_contents, self)
- end
- super(file_contents)
- end
-
- def minimize(file_contents, entry)
- object = Chef::JSONCompat.parse(file_contents)
- object = data_handler.normalize(object, entry)
- object = data_handler.minimize(object, entry)
- 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)
- ChefRepositoryFileSystemEntry.new(child_name, self)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
deleted file mode 100644
index d03baf91fe..0000000000
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
-require 'chef/chef_fs/file_system/chef_repository_file_system_acls_dir'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir'
-require 'chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir'
-require 'chef/chef_fs/file_system/chef_repository_file_system_policies_dir'
-require 'chef/chef_fs/file_system/multiplexed_dir'
-require 'chef/chef_fs/data_handler/client_data_handler'
-require 'chef/chef_fs/data_handler/environment_data_handler'
-require 'chef/chef_fs/data_handler/node_data_handler'
-require 'chef/chef_fs/data_handler/role_data_handler'
-require 'chef/chef_fs/data_handler/user_data_handler'
-require 'chef/chef_fs/data_handler/group_data_handler'
-require 'chef/chef_fs/data_handler/container_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
-
- #
- # Represents the root of a local Chef repository, with directories for
- # nodes, cookbooks, roles, etc. under it.
- #
- class ChefRepositoryFileSystemRootDir < BaseFSDir
- #
- # Create a new Chef Repository File System root.
- #
- # == Parameters
- # [child_paths]
- # A hash of child paths, e.g.:
- # "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ],
- # "roles" => [ '/var/roles' ],
- # ...
- # [root_paths]
- # An array of paths representing the top level, where
- # +org.json+, +members.json+, and +invites.json+ will be stored.
- # [chef_config] - a hash of options that looks suspiciously like the ones
- # stored in Chef::Config, containing at least these keys:
- # :versioned_cookbooks:: whether to include versions in cookbook names
- def initialize(child_paths, root_paths=[], chef_config=Chef::Config)
- super("", nil)
- @child_paths = child_paths
- @root_paths = root_paths
- @versioned_cookbooks = chef_config[:versioned_cookbooks]
- end
-
- attr_accessor :write_pretty_json
-
- attr_reader :root_paths
- attr_reader :child_paths
- attr_reader :versioned_cookbooks
-
- CHILDREN = %w(invitations.json members.json org.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 }
- end
- end
-
- def can_have_child?(name, is_dir)
- if is_dir
- child_paths.has_key?(name)
- elsif root_dir
- CHILDREN.include?(name)
- else
- false
- end
- end
-
- def create_child(name, file_contents = nil)
- if file_contents
- child = root_dir.create_child(name, file_contents)
- else
- child_paths[name].each do |path|
- begin
- Dir.mkdir(path)
- rescue Errno::EEXIST
- end
- end
- child = make_child_entry(name)
- end
- @children = nil
- child
- end
-
- def json_class
- nil
- end
-
- # Used to print out a human-readable file system description
- def fs_description
- repo_paths = root_paths || [ File.dirname(child_paths['cookbooks'][0]) ]
- result = "repository at #{repo_paths.join(', ')}\n"
- if versioned_cookbooks
- result << " Multiple versions per cookbook\n"
- else
- result << " One version per cookbook\n"
- end
- child_paths.each_pair do |name, paths|
- if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
- result << " #{name} at #{paths.join(', ')}\n"
- end
- end
- result
- end
-
- private
-
- #
- # A FileSystemEntry representing the root path where invites.json,
- # members.json and org.json may be found.
- #
- def root_dir
- existing_paths = root_paths.select { |path| File.exists?(path) }
- if existing_paths.size > 0
- MultiplexedDir.new(existing_paths.map do |path|
- dir = ChefRepositoryFileSystemEntry.new(name, parent, path)
- dir.write_pretty_json = !!write_pretty_json
- dir
- end)
- end
- end
-
- #
- # Create a child entry of the appropriate type:
- # cookbooks, data_bags, acls, etc. All will be multiplexed (i.e. if
- # you have multiple paths for cookbooks, the multiplexed dir will grab
- # 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)
- end
- if paths.size == 0
- return nil
- end
- if name == 'cookbooks'
- dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
- elsif name == 'data_bags'
- dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
- elsif name == 'policies'
- dirs = paths.map { |path| ChefRepositoryFileSystemPoliciesDir.new(name, self, path) }
- elsif name == 'acls'
- dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
- else
- data_handler = case name
- when 'clients'
- Chef::ChefFS::DataHandler::ClientDataHandler.new
- when 'environments'
- Chef::ChefFS::DataHandler::EnvironmentDataHandler.new
- when 'nodes'
- Chef::ChefFS::DataHandler::NodeDataHandler.new
- when 'roles'
- Chef::ChefFS::DataHandler::RoleDataHandler.new
- when 'users'
- Chef::ChefFS::DataHandler::UserDataHandler.new
- when 'groups'
- Chef::ChefFS::DataHandler::GroupDataHandler.new
- when 'containers'
- Chef::ChefFS::DataHandler::ContainerDataHandler.new
- else
- raise "Unknown top level path #{name}"
- end
- dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, data_handler) }
- end
- MultiplexedDir.new(dirs)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb b/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb
new file mode 100644
index 0000000000..65d21a90b0
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/acl_dir.rb
@@ -0,0 +1,65 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/acl_entry"
+require "chef/chef_fs/file_system/operation_not_allowed_error"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class AclDir < BaseFSDir
+ def api_path
+ parent.parent.child(name).api_path
+ end
+
+ def make_child_entry(name, exists = nil)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || AclEntry.new(name, self, exists)
+ end
+
+ def can_have_child?(name, is_dir)
+ name =~ /\.json$/ && !is_dir
+ end
+
+ def children
+ if @children.nil?
+ # Grab the ACTUAL children (/nodes, /containers, etc.) and get their names
+ names = parent.parent.child(name).children.map { |child| child.dir? ? "#{child.name}.json" : child.name }
+ @children = names.map { |name| make_child_entry(name, true) }
+ end
+ @children
+ end
+
+ def create_child(name, file_contents)
+ raise OperationNotAllowedError.new(:create_child, self, nil, "ACLs can only be updated, and can only be created when the corresponding object is created.")
+ end
+
+ def data_handler
+ parent.data_handler
+ end
+
+ def rest
+ parent.rest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb b/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb
new file mode 100644
index 0000000000..4fec51a9b1
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/acl_entry.rb
@@ -0,0 +1,60 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/file_system/operation_not_allowed_error"
+require "chef/chef_fs/file_system/operation_failed_error"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class AclEntry < RestListEntry
+ PERMISSIONS = %w{create read update delete grant}
+
+ def api_path
+ "#{super}/_acl"
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self, nil, "ACLs cannot be deleted")
+ end
+
+ def write(file_contents)
+ # ACL writes are fun.
+ acls = data_handler.normalize(Chef::JSONCompat.parse(file_contents), self)
+ PERMISSIONS.each do |permission|
+ begin
+ rest.put("#{api_path}/#{permission}", { permission => acls[permission] })
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb b/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb
new file mode 100644
index 0000000000..4f8fff1bda
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/acls_dir.rb
@@ -0,0 +1,70 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/acl_dir"
+require "chef/chef_fs/file_system/chef_server/cookbooks_acl_dir"
+require "chef/chef_fs/file_system/chef_server/acl_entry"
+require "chef/chef_fs/data_handler/acl_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class AclsDir < BaseFSDir
+ ENTITY_TYPES = %w{clients containers cookbooks data_bags environments groups nodes roles} # we don't read sandboxes, so we don't read their acls
+
+ def data_handler
+ @data_handler ||= Chef::ChefFS::DataHandler::AclDataHandler.new
+ end
+
+ def api_path
+ 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"
+ end
+
+ def children
+ if @children.nil?
+ @children = ENTITY_TYPES.map do |entity_type|
+ case entity_type
+ when "cookbooks"
+ CookbooksAclDir.new(entity_type, self)
+ else
+ AclDir.new(entity_type, self)
+ end
+ end
+ @children << AclEntry.new("organization.json", self, true) # the org acl is retrieved as GET /organizations/ORGNAME/ANYTHINGATALL/_acl
+ end
+ @children
+ end
+
+ def rest
+ parent.rest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb
new file mode 100644
index 0000000000..969cea876e
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/chef_server_root_dir.rb
@@ -0,0 +1,196 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/server_api"
+require "chef/chef_fs/file_system/chef_server/acls_dir"
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/rest_list_dir"
+require "chef/chef_fs/file_system/chef_server/cookbooks_dir"
+require "chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir"
+require "chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir"
+require "chef/chef_fs/file_system/chef_server/data_bags_dir"
+require "chef/chef_fs/file_system/chef_server/nodes_dir"
+require "chef/chef_fs/file_system/chef_server/org_entry"
+require "chef/chef_fs/file_system/chef_server/organization_invites_entry"
+require "chef/chef_fs/file_system/chef_server/organization_members_entry"
+require "chef/chef_fs/file_system/chef_server/policies_dir"
+require "chef/chef_fs/file_system/chef_server/policy_groups_dir"
+require "chef/chef_fs/file_system/chef_server/environments_dir"
+require "chef/chef_fs/data_handler/acl_data_handler"
+require "chef/chef_fs/data_handler/client_data_handler"
+require "chef/chef_fs/data_handler/environment_data_handler"
+require "chef/chef_fs/data_handler/node_data_handler"
+require "chef/chef_fs/data_handler/role_data_handler"
+require "chef/chef_fs/data_handler/user_data_handler"
+require "chef/chef_fs/data_handler/group_data_handler"
+require "chef/chef_fs/data_handler/container_data_handler"
+require "chef/chef_fs/data_handler/policy_group_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ #
+ # Represents the root of a Chef server (or organization), under which
+ # nodes, roles, cookbooks, etc. can be found.
+ #
+ class ChefServerRootDir < BaseFSDir
+ #
+ # Create a new Chef server root.
+ #
+ # == Parameters
+ #
+ # [root_name]
+ # A friendly name for the root, for printing--like "remote" or "chef_central".
+ # [chef_config]
+ # A hash with options that look suspiciously like Chef::Config, including the
+ # following keys:
+ # :chef_server_url:: The URL to the Chef server or top of the organization
+ # :node_name:: The username to authenticate to the Chef server with
+ # :client_key:: The private key for the user for authentication
+ # :environment:: The environment in which you are presently working
+ # :repo_mode::
+ # The repository mode, :hosted_everything, :everything or :static.
+ # This determines the set of subdirectories the Chef server will
+ # offer up.
+ # :versioned_cookbooks:: whether or not to include versions in cookbook names
+ # [options]
+ # Other options:
+ # :cookbook_version:: when cookbooks are retrieved, grab this version for them.
+ # :freeze:: freeze cookbooks on upload
+ #
+ def initialize(root_name, chef_config, options = {})
+ super("", nil)
+ @chef_server_url = chef_config[:chef_server_url]
+ @chef_username = chef_config[:node_name]
+ @chef_private_key = chef_config[:client_key]
+ @environment = chef_config[:environment]
+ @repo_mode = chef_config[:repo_mode]
+ @versioned_cookbooks = chef_config[:versioned_cookbooks]
+ @root_name = root_name
+ @cookbook_version = options[:cookbook_version] # Used in knife diff and download for server cookbook version
+ end
+
+ attr_reader :chef_server_url
+ attr_reader :chef_username
+ attr_reader :chef_private_key
+ attr_reader :environment
+ attr_reader :repo_mode
+ attr_reader :cookbook_version
+ attr_reader :versioned_cookbooks
+
+ def fs_description
+ "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}"
+ end
+
+ def rest
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true, :api_version => "0")
+ end
+
+ def get_json(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
+ Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key)
+ end
+
+ def api_path
+ ""
+ end
+
+ def path_for_printing
+ "#{@root_name}/"
+ end
+
+ def can_have_child?(name, is_dir)
+ result = children.select { |child| child.name == name }.first
+ result && !!result.dir? == !!is_dir
+ end
+
+ def org
+ @org ||= begin
+ path = Pathname.new(URI.parse(chef_server_url).path).cleanpath
+ if File.dirname(path) == "/organizations"
+ File.basename(path)
+ else
+ # In Chef 12, everything is in an org.
+ "chef"
+ end
+ end
+ end
+
+ def make_child_entry(name)
+ children.select { |child| child.name == name }.first
+ end
+
+ def children
+ @children ||= begin
+ result = [
+ # /cookbooks
+ versioned_cookbooks ? VersionedCookbooksDir.new("cookbooks", self) : CookbooksDir.new("cookbooks", self),
+ # /data_bags
+ DataBagsDir.new("data_bags", self, "data"),
+ # /environments
+ EnvironmentsDir.new("environments", self, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new),
+ # /roles
+ RestListDir.new("roles", self, nil, Chef::ChefFS::DataHandler::RoleDataHandler.new),
+ ]
+ if repo_mode == "hosted_everything"
+ result += [
+ # /acls
+ AclsDir.new("acls", self),
+ # /clients
+ RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
+ # /containers
+ RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new),
+ # /cookbook_artifacts
+ CookbookArtifactsDir.new("cookbook_artifacts", self),
+ # /groups
+ RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new),
+ # /nodes
+ NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new),
+ # /org.json
+ OrgEntry.new("org.json", self),
+ # /members.json
+ OrganizationMembersEntry.new("members.json", self),
+ # /invitations.json
+ OrganizationInvitesEntry.new("invitations.json", self),
+ # /policies
+ PoliciesDir.new("policies", self, nil, Chef::ChefFS::DataHandler::PolicyDataHandler.new),
+ # /policy_groups
+ PolicyGroupsDir.new("policy_groups", self, nil, Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new),
+ ]
+ elsif repo_mode != "static"
+ result += [
+ # /clients
+ RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
+ # /nodes
+ NodesDir.new("nodes", self, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new),
+ # /users
+ RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new),
+ ]
+ end
+ result.sort_by { |child| child.name }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_policies_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb
index 42768f10b7..197e412810 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_policies_dir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifact_dir.rb
@@ -16,23 +16,23 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
-require 'chef/chef_fs/data_handler/policy_data_handler'
+require "chef/chef_fs/file_system/chef_server/cookbook_dir"
class Chef
module ChefFS
module FileSystem
+ module ChefServer
+ class CookbookArtifactDir < CookbookDir
+ def initialize(name, parent, options = {})
+ super(name, parent)
+ @cookbook_name, dash, @version = name.rpartition("-")
+ end
- class ChefRepositoryFileSystemPoliciesDir < ChefRepositoryFileSystemEntry
- def initialize(name, parent, path = nil)
- super(name, parent, path, Chef::ChefFS::DataHandler::PolicyDataHandler.new)
- end
-
- def can_have_child?(name, is_dir)
- is_dir && !name.start_with?('.')
+ def copy_from(other, options = {})
+ raise OperationNotAllowedError.new(:write, self, nil, "cannot be updated: cookbook artifacts are immutable once uploaded")
+ end
end
end
end
end
end
-
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb
new file mode 100644
index 0000000000..9dbd3fea4d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_artifacts_dir.rb
@@ -0,0 +1,102 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/cookbooks_dir"
+require "chef/chef_fs/file_system/chef_server/cookbook_artifact_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ #
+ # /cookbook_artifacts
+ #
+ # Example children of /cookbook_artifacts:
+ #
+ # - apache2-ab234098245908ddf324a
+ # - apache2-295387a9823745feff239
+ # - mysql-1a2b9e1298734dfe90444
+ #
+ class CookbookArtifactsDir < CookbooksDir
+
+ def make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || CookbookArtifactDir.new(name, self)
+ end
+
+ def children
+ @children ||= begin
+ result = []
+ root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks|
+ cookbooks["versions"].each do |cookbook_version|
+ result << CookbookArtifactDir.new("#{cookbook_name}-#{cookbook_version['identifier']}", self)
+ end
+ end
+ result.sort_by(&:name)
+ end
+ end
+
+ # Knife currently does not understand versioned cookbooks
+ # Cookbook Version uploader also requires a lot of refactoring
+ # to make this work. So instead, we make a temporary cookbook
+ # symlinking back to real cookbook, and upload the proxy.
+ def upload_cookbook(other, options)
+ cookbook_name, dash, identifier = other.name.rpartition("-")
+
+ Dir.mktmpdir do |temp_cookbooks_path|
+ proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
+
+ # Make a symlink
+ file_class.symlink other.file_path, proxy_cookbook_path
+
+ # Instantiate a proxy loader using the temporary symlink
+ proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore)
+ proxy_loader.load_cookbooks
+
+ cookbook_to_upload = proxy_loader.cookbook_version
+ cookbook_to_upload.identifier = identifier
+ cookbook_to_upload.freeze_version if options[:freeze]
+
+ # Instantiate a new uploader based on the proxy loader
+ uploader = Chef::CookbookUploader.new(cookbook_to_upload, force: options[:force], rest: root.chef_rest, policy_mode: true)
+
+ with_actual_cookbooks_dir(temp_cookbooks_path) do
+ uploader.upload_cookbooks
+ end
+
+ #
+ # When the temporary directory is being deleted on
+ # windows, the contents of the symlink under that
+ # directory is also deleted. So explicitly remove
+ # the symlink without removing the original contents if we
+ # are running on windows
+ #
+ if Chef::Platform.windows?
+ Dir.rmdir proxy_cookbook_path
+ end
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir && name.include?("-")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb
new file mode 100644
index 0000000000..18bf748d87
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_dir.rb
@@ -0,0 +1,222 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/command_line"
+require "chef/chef_fs/file_system/chef_server/rest_list_dir"
+require "chef/chef_fs/file_system/chef_server/cookbook_subdir"
+require "chef/chef_fs/file_system/chef_server/cookbook_file"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/cookbook_version"
+require "chef/cookbook_uploader"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # Unversioned cookbook.
+ #
+ # /cookbooks/NAME
+ #
+ # Children look like:
+ #
+ # - metadata.rb
+ # - attributes/
+ # - libraries/
+ # - recipes/
+ #
+ class CookbookDir < BaseFSDir
+ def initialize(name, parent, options = {})
+ super(name, parent)
+ @exists = options[:exists]
+ @cookbook_name = name
+ @version = root.cookbook_version # nil unless --cookbook-version specified in download/diff
+ end
+
+ attr_reader :cookbook_name, :version
+
+ COOKBOOK_SEGMENT_INFO = {
+ :attributes => { :ruby_only => true },
+ :definitions => { :ruby_only => true },
+ :recipes => { :ruby_only => true },
+ :libraries => { :ruby_only => true },
+ :templates => { :recursive => true },
+ :files => { :recursive => true },
+ :resources => { :ruby_only => true, :recursive => true },
+ :providers => { :ruby_only => true, :recursive => true },
+ :root_files => { },
+ }
+
+ def add_child(child)
+ @children << child
+ end
+
+ def api_path
+ "#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
+ end
+
+ 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
+ children.select { |child| child.name == name }.first
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ nil
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ # A cookbook's root may not have directories unless they are segment directories
+ return name != "root_files" && COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) if is_dir
+ return true
+ end
+
+ def children
+ if @children.nil?
+ @children = []
+ manifest = chef_object.manifest
+ COOKBOOK_SEGMENT_INFO.each do |segment, segment_info|
+ next unless manifest.has_key?(segment)
+
+ # Go through each file in the manifest for the segment, and
+ # add cookbook subdirs and files for it.
+ manifest[segment].each do |segment_file|
+ parts = segment_file[:path].split("/")
+ # Get or create the path to the file
+ container = self
+ parts[0,parts.length-1].each do |part|
+ old_container = container
+ container = old_container.children.select { |child| part == child.name }.first
+ if !container
+ container = CookbookSubdir.new(part, old_container, segment_info[:ruby_only], segment_info[:recursive])
+ old_container.add_child(container)
+ end
+ end
+ # Create the file itself
+ container.add_child(CookbookFile.new(parts[parts.length-1], container, segment_file))
+ end
+ end
+ @children = @children.sort_by { |c| c.name }
+ end
+ @children
+ end
+
+ def dir?
+ exists?
+ end
+
+ def delete(recurse)
+ if recurse
+ begin
+ rest.delete(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ rescue Net::HTTPServerException
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}")
+ end
+ end
+ else
+ raise NotFoundError.new(self) if !exists?
+ raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively")
+ end
+ end
+
+ # In versioned cookbook mode, actually check if the version exists
+ # Probably want to cache this.
+ def exists?
+ if @exists.nil?
+ @exists = parent.children.any? { |child| child.name == name }
+ end
+ @exists
+ end
+
+ def compare_to(other)
+ if !other.dir?
+ return [ !exists?, nil, nil ]
+ end
+ are_same = true
+ Chef::ChefFS::CommandLine::diff_entries(self, other, nil, :name_only).each do |type, old_entry, new_entry|
+ if [ :directory_to_file, :file_to_directory, :deleted, :added, :modified ].include?(type)
+ are_same = false
+ end
+ end
+ [ are_same, nil, nil ]
+ end
+
+ def copy_from(other, options = {})
+ parent.upload_cookbook_from(other, options)
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def chef_object
+ # We cheat and cache here, because it seems like a good idea to keep
+ # the cookbook view consistent with the directory structure.
+ return @chef_object if @chef_object
+
+ # The negative (not found) response is cached
+ if @could_not_get_chef_object
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+ end
+
+ begin
+ # We want to fail fast, for now, because of the 500 issue :/
+ # This will make things worse for parallelism, a little, because
+ # Chef::Config is global and this could affect other requests while
+ # this request is going on. (We're not parallel yet, but we will be.)
+ # Chef bug http://tickets.opscode.com/browse/CHEF-3066
+ old_retry_count = Chef::Config[:http_retry_count]
+ begin
+ Chef::Config[:http_retry_count] = 0
+ @chef_object ||= Chef::CookbookVersion.json_create(root.get_json(api_path))
+ ensure
+ Chef::Config[:http_retry_count] = old_retry_count
+ end
+
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
+
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ @could_not_get_chef_object = e
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
+ end
+
+ # Chef bug http://tickets.opscode.com/browse/CHEF-3066 ... instead of 404 we get 500 right now.
+ # Remove this when that bug is fixed.
+ rescue Net::HTTPFatalError => e
+ if e.response.code == "500"
+ @could_not_get_chef_object = e
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb
new file mode 100644
index 0000000000..fc9ad46d71
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_file.rb
@@ -0,0 +1,84 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_object"
+require "chef/http/simple"
+require "openssl"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class CookbookFile < BaseFSObject
+ def initialize(name, parent, file)
+ super(name, parent)
+ @file = file
+ end
+
+ attr_reader :file
+
+ def checksum
+ file[:checksum]
+ end
+
+ def read
+ begin
+ tmpfile = rest.streaming_request(file[:url])
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading #{file[:url]}: #{e}")
+ rescue Net::HTTPServerException => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "#{e.message} retrieving #{file[:url]}")
+ end
+
+ begin
+ tmpfile.open
+ tmpfile.read
+ ensure
+ tmpfile.close!
+ end
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def compare_to(other)
+ other_value = nil
+ if other.respond_to?(:checksum)
+ other_checksum = other.checksum
+ else
+ begin
+ other_value = other.read
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ false, nil, :none ]
+ end
+ other_checksum = calc_checksum(other_value)
+ end
+ [ checksum == other_checksum, nil, other_value ]
+ end
+
+ private
+
+ def calc_checksum(value)
+ OpenSSL::Digest::MD5.hexdigest(value)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb
new file mode 100644
index 0000000000..9bc1821c48
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbook_subdir.rb
@@ -0,0 +1,61 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class CookbookSubdir < BaseFSDir
+ def initialize(name, parent, ruby_only, recursive)
+ super(name, parent)
+ @children = []
+ @ruby_only = ruby_only
+ @recursive = recursive
+ end
+
+ attr_reader :versions
+ attr_reader :children
+
+ def add_child(child)
+ @children << child
+ end
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ return false if !@recursive
+ else
+ return false if @ruby_only && name !~ /\.rb$/
+ end
+ true
+ end
+
+ def make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || NonexistentFSObject.new(name, self)
+ end
+
+ def rest
+ parent.rest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb
new file mode 100644
index 0000000000..2460aba47f
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbooks_acl_dir.rb
@@ -0,0 +1,43 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/acl_dir"
+require "chef/chef_fs/file_system/chef_server/acl_entry"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class CookbooksAclDir < AclDir
+ # If versioned_cookbooks is on, the list of cookbooks will have versions
+ # in them. But all versions of a cookbook have the same acl, so even if
+ # we have cookbooks/apache2-1.0.0 and cookbooks/apache2-1.1.2, we will
+ # only have one acl: acls/cookbooks/apache2.json. Thus, the list of
+ # children of acls/cookbooks is a unique list of cookbook *names*.
+ def children
+ if @children.nil?
+ names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
+ @children = names.uniq.map { |name| make_child_entry(name, true) }
+ end
+ @children
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb
new file mode 100644
index 0000000000..ce74ac1a5c
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/cookbooks_dir.rb
@@ -0,0 +1,102 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/rest_list_dir"
+require "chef/chef_fs/file_system/chef_server/cookbook_dir"
+require "chef/chef_fs/file_system/operation_failed_error"
+require "chef/chef_fs/file_system/cookbook_frozen_error"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
+require "chef/mixin/file_class"
+
+require "tmpdir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ #
+ # /cookbooks
+ #
+ # Example children:
+ # apache2/
+ # mysql/
+ #
+ class CookbooksDir < RestListDir
+
+ include Chef::Mixin::FileClass
+
+ def make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || CookbookDir.new(name, self)
+ end
+
+ def children
+ @children ||= begin
+ result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, exists: true) }
+ result.sort_by(&:name)
+ end
+ end
+
+ def create_child_from(other, options = {})
+ @children = nil
+ upload_cookbook_from(other, options)
+ end
+
+ def upload_cookbook_from(other, options = {})
+ upload_cookbook(other, options)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
+ rescue Net::HTTPServerException => e
+ case e.response.code
+ when "409"
+ raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen")
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
+ end
+ rescue Chef::Exceptions::CookbookFrozen => e
+ raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e, "Cookbook #{other.name} is frozen")
+ end
+
+ def upload_cookbook(other, options)
+ cookbook_to_upload = other.chef_object
+ cookbook_to_upload.freeze_version if options[:freeze]
+ uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
+
+ with_actual_cookbooks_dir(other.parent.file_path) do
+ uploader.upload_cookbooks
+ end
+ end
+
+ # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet)
+ def with_actual_cookbooks_dir(actual_cookbook_path)
+ old_cookbook_path = Chef::Config.cookbook_path
+ Chef::Config.cookbook_path = actual_cookbook_path if !Chef::Config.cookbook_path
+
+ yield
+ ensure
+ Chef::Config.cookbook_path = old_cookbook_path
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb b/lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb
new file mode 100644
index 0000000000..60875d7b4c
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/data_bag_dir.rb
@@ -0,0 +1,71 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/rest_list_dir"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/file_system/must_delete_recursively_error"
+require "chef/chef_fs/data_handler/data_bag_item_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class DataBagDir < RestListDir
+ def initialize(name, parent, exists = nil)
+ super(name, parent, nil, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
+ @exists = nil
+ end
+
+ def dir?
+ exists?
+ end
+
+ def read
+ # This will only be called if dir? is false, which means exists? is false.
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self)
+ end
+
+ def exists?
+ if @exists.nil?
+ @exists = parent.children.any? { |child| child.name == name }
+ end
+ @exists
+ end
+
+ def delete(recurse)
+ if !recurse
+ raise NotFoundError.new(self) if !exists?
+ raise MustDeleteRecursivelyError.new(self, "#{path_for_printing} must be deleted recursively")
+ end
+ begin
+ rest.delete(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "HTTP error deleting: #{e}")
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb b/lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb
new file mode 100644
index 0000000000..710c8f0a72
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/data_bags_dir.rb
@@ -0,0 +1,69 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/rest_list_dir"
+require "chef/chef_fs/file_system/chef_server/data_bag_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class DataBagsDir < RestListDir
+ def make_child_entry(name, exists = false)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || DataBagDir.new(name, self, exists)
+ end
+
+ def children
+ begin
+ @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
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error getting children: #{e}")
+ end
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir
+ end
+
+ def create_child(name, file_contents)
+ begin
+ rest.post(api_path, { "name" => name })
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating child '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Cannot create #{name} under #{path}: already exists")
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "HTTP error creating child '#{name}': #{e}")
+ end
+ end
+ @children = nil
+ DataBagDir.new(name, self, true)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb b/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb
new file mode 100644
index 0000000000..aa9423a43d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/environments_dir.rb
@@ -0,0 +1,57 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/file_system/default_environment_cannot_be_modified_error"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class EnvironmentsDir < RestListDir
+ def make_child_entry(name, exists = nil)
+ if name == "_default.json"
+ DefaultEnvironmentEntry.new(name, self, exists)
+ else
+ super
+ end
+ end
+
+ class DefaultEnvironmentEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def delete(recurse)
+ raise NotFoundError.new(self) if !exists?
+ raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self)
+ end
+
+ def write(file_contents)
+ raise NotFoundError.new(self) if !exists?
+ raise DefaultEnvironmentCannotBeModifiedError.new(:write, self)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/acl_entry.rb b/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb
index b2545af5ae..596a63b0b8 100644
--- a/lib/chef/chef_fs/file_system/acl_entry.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/nodes_dir.rb
@@ -1,6 +1,6 @@
#
# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,41 +16,36 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
-require 'chef/chef_fs/file_system/operation_failed_error'
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/data_handler/node_data_handler"
class Chef
module ChefFS
module FileSystem
- class AclEntry < RestListEntry
- PERMISSIONS = %w(create read update delete grant)
-
- def api_path
- "#{super}/_acl"
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self), "ACLs cannot be deleted."
- end
-
- def write(file_contents)
- # ACL writes are fun.
- acls = data_handler.normalize(Chef::JSONCompat.parse(file_contents), self)
- PERMISSIONS.each do |permission|
+ module ChefServer
+ class NodesDir < RestListDir
+ # Identical to RestListDir.children, except supports environments
+ def children
begin
- rest.put("#{api_path}/#{permission}", { permission => acls[permission] })
+ @children ||= root.get_json(env_api_path).keys.sort.map do |key|
+ make_child_entry("#{key}.json", true)
+ end
rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}"
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
end
end
end
+
+ def env_api_path
+ environment ? "environments/#{environment}/#{api_path}" : api_path
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_server/org_entry.rb b/lib/chef/chef_fs/file_system/chef_server/org_entry.rb
new file mode 100644
index 0000000000..87be36b932
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/org_entry.rb
@@ -0,0 +1,31 @@
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/data_handler/organization_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /organizations/NAME/org.json
+ # Represents the actual data at /organizations/NAME (the full name, etc.)
+ class OrgEntry < RestListEntry
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationDataHandler.new
+ end
+
+ # /organizations/foo/org.json -> GET /organizations/foo
+ def api_path
+ parent.api_path
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb b/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb
new file mode 100644
index 0000000000..986a04ff4a
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb
@@ -0,0 +1,61 @@
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/data_handler/organization_invites_data_handler"
+require "chef/json_compat"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /organizations/NAME/invitations.json
+ # read data from:
+ # - GET /organizations/NAME/association_requests
+ # write data to:
+ # - remove from list: DELETE /organizations/NAME/association_requests/id
+ # - add to list: POST /organizations/NAME/association_requests
+ class OrganizationInvitesEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationInvitesDataHandler.new
+ end
+
+ # /organizations/foo/invites.json -> /organizations/foo/association_requests
+ def api_path
+ File.join(parent.api_path, "association_requests")
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+
+ def write(contents)
+ desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
+ actual_invites = _read_json.inject({}) { |h,val| h[val["username"]] = val["id"]; h }
+ invites = actual_invites.keys
+ (desired_invites - invites).each do |invite|
+ begin
+ rest.post(api_path, { "user" => invite })
+ rescue Net::HTTPServerException => e
+ if e.response.code == "409"
+ Chef::Log.warn("Could not invite #{invite} to organization #{org}: #{api_error_text(e.response)}")
+ else
+ raise
+ end
+ end
+ end
+ (invites - desired_invites).each do |invite|
+ rest.delete(File.join(api_path, actual_invites[invite]))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb b/lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb
new file mode 100644
index 0000000000..2e45b74450
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/organization_members_entry.rb
@@ -0,0 +1,60 @@
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/data_handler/organization_members_data_handler"
+require "chef/json_compat"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /organizations/NAME/members.json
+ # reads data from:
+ # - GET /organizations/NAME/users
+ # writes data to:
+ # - remove from list: DELETE /organizations/NAME/users/name
+ # - add to list: POST /organizations/NAME/users/name
+ class OrganizationMembersEntry < RestListEntry
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ Chef::ChefFS::DataHandler::OrganizationMembersDataHandler.new
+ end
+
+ # /organizations/foo/members.json -> /organizations/foo/users
+ def api_path
+ File.join(parent.api_path, "users")
+ end
+
+ def exists?
+ parent.exists?
+ end
+
+ def delete(recurse)
+ raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
+ end
+
+ def write(contents)
+ desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
+ members = minimize_value(_read_json)
+ (desired_members - members).each do |member|
+ begin
+ rest.post(api_path, "username" => member)
+ rescue Net::HTTPServerException => e
+ if %w{404 405}.include?(e.response.code)
+ raise "Chef server at #{api_path} does not allow you to directly add members. Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json."
+ else
+ raise
+ end
+ end
+ end
+ (members - desired_members).each do |member|
+ rest.delete(File.join(api_path, member))
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb b/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb
new file mode 100644
index 0000000000..ea2a048fbf
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/policies_dir.rb
@@ -0,0 +1,160 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/rest_list_dir"
+require "chef/chef_fs/file_system/chef_server/policy_revision_entry"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ #
+ # Server API:
+ # /policies - list of policies by name
+ # - /policies/NAME - represents a policy with all revisions
+ # - /policies/NAME/revisions - list of revisions for that policy
+ # - /policies/NAME/revisions/REVISION - actual policy-revision document
+ #
+ # Local Repository and ChefFS:
+ # /policies - PoliciesDir - maps to server API /policies
+ # - /policies/NAME-REVISION.json - PolicyRevision - maps to /policies/NAME/revisions/REVISION
+ #
+ class PoliciesDir < RestListDir
+ # Children: NAME-REVISION.json for all revisions of all policies
+ #
+ # /nodes: {
+ # "node1": "https://api.opscode.com/organizations/myorg/nodes/node1",
+ # "node2": "https://api.opscode.com/organizations/myorg/nodes/node2",
+ # }
+ #
+ # /policies: {
+ # "foo": {}
+ # }
+
+ def make_child_entry(name, exists = nil)
+ @children.select { |child| child.name == name }.first if @children
+ PolicyRevisionEntry.new(name, self, exists)
+ end
+
+ # Children come from /policies in this format:
+ # {
+ # "foo": {
+ # "uri": "https://api.opscode.com/organizations/essentials/policies/foo",
+ # "revisions": {
+ # "1.0.0": {
+ #
+ # },
+ # "1.0.1": {
+ #
+ # }
+ # }
+ # }
+ # }
+ def children
+ begin
+ # Grab the names of the children, append json, and make child entries
+ @children ||= begin
+ result = []
+ data = root.get_json(api_path)
+ data.keys.sort.each do |policy_name|
+ data[policy_name]["revisions"].keys.each do |policy_revision|
+ filename = "#{policy_name}-#{policy_revision}.json"
+ result << make_child_entry(filename, true)
+ end
+ end
+ result
+ end
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if $!.response.code == "404"
+ # GET /organizations/ORG/policies returned 404, but that just might be because
+ # we are talking to an older version of the server that doesn't support policies.
+ # Do GET /orgqanizations/ORG to find out if the org exists at all.
+ # TODO use server API version instead of a second network request.
+ begin
+ root.get_json(parent.api_path)
+ # Return empty list if the organization exists but /policies didn't work
+ []
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ end
+ end
+
+ #
+ # Does POST <api_path> with file_contents
+ #
+ def create_child(name, file_contents)
+ # Parse the contents to ensure they are valid JSON
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
+ end
+
+ # Create the child entry that will be returned
+ result = make_child_entry(name, true)
+
+ # Normalize the file_contents before post (add defaults, etc.)
+ if data_handler
+ object = data_handler.normalize_for_post(object, result)
+ data_handler.verify_integrity(object, result) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
+ end
+ end
+
+ # POST /api_path with the normalized file_contents
+ begin
+ policy_name, policy_revision = data_handler.name_and_revision(name)
+ rest.post("#{api_path}/#{policy_name}/revisions", object)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ # 409 = AlreadyExistsError
+ elsif $!.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
+ end
+ end
+
+ # Clear the cache of children so that if someone asks for children
+ # again, we will get it again
+ @children = nil
+
+ result
+ end
+
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb b/lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb
new file mode 100644
index 0000000000..3858ef66c4
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/policy_group_entry.rb
@@ -0,0 +1,137 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/already_exists_error"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/file_system/operation_failed_error"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # Represents an entire policy group.
+ # Server path: /organizations/ORG/policy_groups/GROUP
+ # Repository path: policy_groups\GROUP.json
+ # Format:
+ # {
+ # "policies": {
+ # "a": { "revision_id": "1.0.0" }
+ # }
+ # }
+ class PolicyGroupEntry < RestListEntry
+ # delete is handled normally:
+ # DELETE /organizations/ORG/policy_groups/GROUP
+
+ # read is handled normally:
+ # GET /organizations/ORG/policy_groups/GROUP
+
+ # write is different.
+ # For each policy:
+ # - PUT /organizations/ORG/policy_groups/GROUP/policies/POLICY
+ # For each policy on the server but not the client:
+ # - DELETE /organizations/ORG/policy_groups/GROUP/policies/POLICY
+ # If the server has associations for a, b and c,
+ # And the client wants associations for a, x and y,
+ # We must PUT a, x and y
+ # And DELETE b and c
+ def write(file_contents)
+ # Parse the contents to ensure they are valid JSON
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
+ end
+
+ if data_handler
+ object = data_handler.normalize_for_put(object, self)
+ data_handler.verify_integrity(object, self) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
+ end
+ end
+
+ begin
+
+ # this should all get carted to PolicyGroupEntry#write.
+
+ # the server demands the full policy data, but we want users' local policy_group documents to just
+ # have the data you'd get from GET /policy_groups/POLICY_GROUP. so we try to fetch that.
+
+ # ordinarily this would be POST to the normal URL, but we do PUT to
+ # /organizations/{organization}/policy_groups/{policy_group}/policies/{policy_name} with the full
+ # policy data, for each individual policy.
+ policy_datas = {}
+
+ object["policies"].each do |policy_name, policy_data|
+ policy_path = "/policies/#{policy_name}/revisions/#{policy_data["revision_id"]}"
+
+ get_data = begin
+ rest.get(policy_path)
+ rescue Net::HTTPServerException => e
+ raise "Could not find policy '#{policy_name}'' with revision '#{policy_data["revision_id"]}'' on the server"
+ end
+
+ # GET policy data
+ server_policy_data = Chef::JSONCompat.parse(get_data)
+
+ # if it comes back 404, raise an Exception with "Policy file X does not exist with revision Y on the server"
+
+ # otherwise, add it to the list of policyfile datas.
+ policy_datas[policy_name] = server_policy_data
+ end
+
+ begin
+ existing_group = Chef::JSONCompat.parse(self.read)
+ rescue NotFoundError
+ # It's OK if the group doesn't already exist, just means no existing policies
+ end
+
+ # now we have the fullpolicy data for each policies, which is what the PUT endpoint demands.
+ policy_datas.each do |policy_name, policy_data|
+ # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME
+ rest.put("#{api_path}/policies/#{policy_name}", policy_data)
+ end
+
+ # Now we need to remove any policies that are *not* in our current group.
+ if existing_group && existing_group["policies"]
+ (existing_group["policies"].keys - policy_datas.keys).each do |policy_name|
+ rest.delete("#{api_path}/policies/#{policy_name}")
+ end
+ end
+
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ # 409 = AlreadyExistsError
+ elsif $!.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
+ end
+ end
+
+ self
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbook_subdir.rb b/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb
index 73c709e01e..71e0fc4579 100644
--- a/lib/chef/chef_fs/file_system/cookbook_subdir.rb
+++ b/lib/chef/chef_fs/file_system/chef_server/policy_groups_dir.rb
@@ -16,37 +16,26 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/base_fs_dir'
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/file_system/chef_server/policy_group_entry"
class Chef
module ChefFS
module FileSystem
- class CookbookSubdir < BaseFSDir
- def initialize(name, parent, ruby_only, recursive)
- super(name, parent)
- @children = []
- @ruby_only = ruby_only
- @recursive = recursive
- end
-
- attr_reader :versions
- attr_reader :children
-
- def add_child(child)
- @children << child
- end
-
- def can_have_child?(name, is_dir)
- if is_dir
- return false if !@recursive
- else
- return false if @ruby_only && name !~ /\.rb$/
+ module ChefServer
+ class PolicyGroupsDir < RestListDir
+ def make_child_entry(name, exists = nil)
+ PolicyGroupEntry.new(name, self, exists)
end
- true
- end
- def rest
- parent.rest
+ def create_child(name, file_contents)
+ entry = make_child_entry(name, true)
+ entry.write(file_contents)
+ @children = nil
+ entry
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb b/lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb
new file mode 100644
index 0000000000..a51a1ff5c9
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/policy_revision_entry.rb
@@ -0,0 +1,25 @@
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/data_handler/policy_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ # /policies/NAME-REVISION.json
+ # Represents the actual data at /organizations/ORG/policies/NAME/revisions/REVISION
+ class PolicyRevisionEntry < RestListEntry
+
+ # /policies/foo-1.0.0.json -> /policies/foo/revisions/1.0.0
+ def api_path(options={})
+ policy_name, revision_id = data_handler.name_and_revision(name)
+ "#{parent.api_path}/#{policy_name}/revisions/#{revision_id}"
+ end
+
+ def write(file_contents)
+ raise OperationNotAllowedError.new(:write, self, nil, "cannot be updated: policy revisions are immutable once uploaded. If you want to change the policy, create a new revision with your changes")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb b/lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb
new file mode 100644
index 0000000000..573f107e54
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/rest_list_dir.rb
@@ -0,0 +1,179 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/rest_list_entry"
+require "chef/chef_fs/file_system/not_found_error"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class RestListDir < BaseFSDir
+ def initialize(name, parent, api_path = nil, data_handler = nil)
+ super(name, parent)
+ @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
+ @data_handler = data_handler
+ end
+
+ attr_reader :api_path
+ attr_reader :data_handler
+
+ def can_have_child?(name, is_dir)
+ name =~ /\.json$/ && !is_dir
+ end
+
+ #
+ # When talking to a modern (12.0+) Chef server
+ # knife list /
+ # -> /nodes
+ # -> /policies
+ # -> /policy_groups
+ # -> /roles
+ #
+ # 12.0 or 12.1 will fail when you do this:
+ # knife list / --recursive
+ # Because it thinks /policies exists, and when it tries to list its children
+ # it gets a 404 (indicating it actually doesn't exist).
+ #
+ # With this change, knife list / --recursive will list /policies as a real, empty directory.
+ #
+ # Alternately, we could have done some sort of detection when we listed the top level
+ # and determined which endpoints the server would support, and returned only those.
+ # So you wouldn't see /policies in that case at all.
+ # The issue with that is there's no efficient way to do it because we can't find out
+ # the server version directly, and can't ask the server for a list of the endpoints it supports.
+ #
+
+
+ #
+ # Does GET /<api_path>, assumes the result is of the format:
+ #
+ # {
+ # "foo": "<api_path>/foo",
+ # "bar": "<api_path>/bar",
+ # }
+ #
+ # Children are foo.json and bar.json in this case.
+ #
+ def children
+ begin
+ # Grab the names of the children, append json, and make child entries
+ @children ||= root.get_json(api_path).keys.sort.map do |key|
+ make_child_entry("#{key}.json", true)
+ end
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "Timeout retrieving children: #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if $!.response.code == "404"
+
+ if parent.is_a?(ChefServerRootDir)
+ # GET /organizations/ORG/<container> returned 404, but that just might be because
+ # we are talking to an older version of the server that doesn't support policies.
+ # Do GET /orgqanizations/ORG to find out if the org exists at all.
+ # TODO use server API version instead of a second network request.
+ begin
+ root.get_json(parent.api_path)
+ # Return empty list if the organization exists but /policies didn't work
+ []
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ else
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e, "HTTP error retrieving children: #{e}")
+ end
+ end
+ end
+
+ #
+ # Does POST <api_path> with file_contents
+ #
+ def create_child(name, file_contents)
+ # Parse the contents to ensure they are valid JSON
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Parse error reading JSON creating child '#{name}': #{e}")
+ end
+
+ # Create the child entry that will be returned
+ result = make_child_entry(name, true)
+
+ # Normalize the file_contents before post (add defaults, etc.)
+ if data_handler
+ object = data_handler.normalize_for_post(object, result)
+ data_handler.verify_integrity(object, result) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, nil, "Error creating '#{name}': #{error}")
+ end
+ end
+
+ # POST /api_path with the normalized file_contents
+ begin
+ rest.post(api_path, object)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Timeout creating '#{name}': #{e}")
+ rescue Net::HTTPServerException => e
+ # 404 = NotFoundError
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ # 409 = AlreadyExistsError
+ elsif $!.response.code == "409"
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e, "Failure creating '#{name}': #{path}/#{name} already exists")
+ # Anything else is unexpected (OperationFailedError)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e, "Failure creating '#{name}': #{e.message}")
+ end
+ end
+
+ # Clear the cache of children so that if someone asks for children
+ # again, we will get it again
+ @children = nil
+
+ result
+ end
+
+ def org
+ parent.org
+ end
+
+ def environment
+ parent.environment
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def make_child_entry(name, exists = nil)
+ @children.select { |child| child.name == name }.first if @children
+ RestListEntry.new(name, self, exists)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb b/lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb
new file mode 100644
index 0000000000..692b6cfc73
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/rest_list_entry.rb
@@ -0,0 +1,187 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_object"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/file_system/operation_failed_error"
+require "chef/role"
+require "chef/node"
+require "chef/json_compat"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class RestListEntry < BaseFSObject
+ def initialize(name, parent, exists = nil)
+ super(name, parent)
+ @exists = exists
+ end
+
+ def data_handler
+ parent.data_handler
+ end
+
+ def api_child_name
+ if name.length < 5 || name[-5,5] != ".json"
+ raise "Invalid name #{path}: must end in .json"
+ end
+ name[0,name.length-5]
+ end
+
+ def api_path
+ "#{parent.api_path}/#{api_child_name}"
+ end
+
+ def org
+ parent.org
+ end
+
+ def environment
+ parent.environment
+ end
+
+ def exists?
+ if @exists.nil?
+ begin
+ @exists = parent.children.any? { |child| child.name == name }
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ @exists = false
+ end
+ end
+ @exists
+ end
+
+ def delete(recurse)
+ begin
+ rest.delete(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e, "Timeout deleting: #{e}")
+ end
+ end
+ end
+
+ def read
+ Chef::JSONCompat.to_json_pretty(minimize_value(_read_json))
+ end
+
+ def _read_json
+ begin
+ # Minimize the value (get rid of defaults) so the results don't look terrible
+ root.get_json(api_path)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "Timeout reading: #{e}")
+ rescue Net::HTTPServerException => e
+ if $!.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e, "HTTP error reading: #{e}")
+ end
+ end
+ end
+
+ def chef_object
+ # REST will inflate the Chef object using json_class
+ data_handler.json_class.json_create(read)
+ end
+
+ def minimize_value(value)
+ data_handler.minimize(data_handler.normalize(value, self), self)
+ end
+
+ def compare_to(other)
+ # TODO this pair of reads can be parallelized
+
+ # Grab the other value
+ begin
+ other_value_json = other.read
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ nil, nil, :none ]
+ end
+
+ # Grab this value
+ begin
+ value = _read_json
+ rescue Chef::ChefFS::FileSystem::NotFoundError
+ return [ false, :none, other_value_json ]
+ end
+
+ # Minimize (and normalize) both values for easy and beautiful diffs
+ value = minimize_value(value)
+ value_json = Chef::JSONCompat.to_json_pretty(value)
+ begin
+ other_value = Chef::JSONCompat.parse(other_value_json)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}")
+ return [ nil, value_json, other_value_json ]
+ end
+ other_value = minimize_value(other_value)
+ other_value_json = Chef::JSONCompat.to_json_pretty(other_value)
+
+ [ value == other_value, value_json, other_value_json ]
+ end
+
+ def rest
+ parent.rest
+ end
+
+ def write(file_contents)
+ begin
+ object = Chef::JSONCompat.parse(file_contents)
+ rescue Chef::Exceptions::JSON::ParseError => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Parse error reading JSON: #{e}")
+ end
+
+ if data_handler
+ object = data_handler.normalize_for_put(object, self)
+ data_handler.verify_integrity(object, self) do |error|
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, nil, "#{error}")
+ end
+ end
+
+ begin
+ rest.put(api_path, object)
+ rescue Timeout::Error => e
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "Timeout writing: #{e}")
+ rescue Net::HTTPServerException => e
+ if e.response.code == "404"
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
+ else
+ raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e, "HTTP error writing: #{e}")
+ end
+ end
+ end
+
+ def api_error_text(response)
+ begin
+ Chef::JSONCompat.parse(response.body)["error"].join("\n")
+ rescue
+ response.body
+ end
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb
new file mode 100644
index 0000000000..0ffd156577
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbook_dir.rb
@@ -0,0 +1,45 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/cookbook_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ class VersionedCookbookDir < CookbookDir
+ # See Erchef code
+ # https://github.com/opscode/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94
+ VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/
+
+ def initialize(name, parent, options = {})
+ super(name, parent)
+ # If the name is apache2-1.0.0 and versioned_cookbooks is on, we know
+ # the actual cookbook_name and version.
+ if name =~ VALID_VERSIONED_COOKBOOK_NAME
+ @cookbook_name = $1
+ @version = $2
+ else
+ @exists = false
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb
new file mode 100644
index 0000000000..0b1480cb2d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/chef_server/versioned_cookbooks_dir.rb
@@ -0,0 +1,107 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/chef_server/cookbooks_dir"
+require "chef/chef_fs/file_system/chef_server/versioned_cookbook_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module ChefServer
+ #
+ # /cookbooks or /cookbook_artifacts
+ #
+ # Example children of /cookbooks:
+ #
+ # - apache2-1.0.0
+ # - apache2-1.0.1
+ # - mysql-2.0.5
+ #
+ # Example children of /cookbook_artifacts:
+ #
+ # - apache2-ab234098245908ddf324a
+ # - apache2-295387a9823745feff239
+ # - mysql-1a2b9e1298734dfe90444
+ #
+ class VersionedCookbooksDir < CookbooksDir
+
+ def make_child_entry(name)
+ result = @children.select { |child| child.name == name }.first if @children
+ result || VersionedCookbookDir.new(name, self)
+ end
+
+ def children
+ @children ||= begin
+ result = []
+ root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks|
+ cookbooks["versions"].each do |cookbook_version|
+ result << VersionedCookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self)
+ end
+ end
+ result.sort_by(&:name)
+ end
+ end
+
+ # Knife currently does not understand versioned cookbooks
+ # Cookbook Version uploader also requires a lot of refactoring
+ # to make this work. So instead, we make a temporary cookbook
+ # symlinking back to real cookbook, and upload the proxy.
+ def upload_cookbook(other, options)
+ cookbook_name = Chef::ChefFS::FileSystem::Repository::ChefRepositoryFileSystemCookbookDir.canonical_cookbook_name(other.name)
+
+ Dir.mktmpdir do |temp_cookbooks_path|
+ proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
+
+ # Make a symlink
+ file_class.symlink other.file_path, proxy_cookbook_path
+
+ # Instantiate a proxy loader using the temporary symlink
+ proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore)
+ proxy_loader.load_cookbooks
+
+ cookbook_to_upload = proxy_loader.cookbook_version
+ cookbook_to_upload.freeze_version if options[:freeze]
+
+ # Instantiate a new uploader based on the proxy loader
+ uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
+
+ with_actual_cookbooks_dir(temp_cookbooks_path) do
+ uploader.upload_cookbooks
+ end
+
+ #
+ # When the temporary directory is being deleted on
+ # windows, the contents of the symlink under that
+ # directory is also deleted. So explicitly remove
+ # the symlink without removing the original contents if we
+ # are running on windows
+ #
+ if Chef::Platform.windows?
+ Dir.rmdir proxy_cookbook_path
+ end
+ end
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir && name =~ Chef::ChefFS::FileSystem::ChefServer::VersionedCookbookDir::VALID_VERSIONED_COOKBOOK_NAME
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb b/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
deleted file mode 100644
index 370308ee0a..0000000000
--- a/lib/chef/chef_fs/file_system/chef_server_root_dir.rb
+++ /dev/null
@@ -1,159 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/server_api'
-require 'chef/chef_fs/file_system/acls_dir'
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/rest_list_dir'
-require 'chef/chef_fs/file_system/cookbooks_dir'
-require 'chef/chef_fs/file_system/data_bags_dir'
-require 'chef/chef_fs/file_system/nodes_dir'
-require 'chef/chef_fs/file_system/org_entry'
-require 'chef/chef_fs/file_system/organization_invites_entry'
-require 'chef/chef_fs/file_system/organization_members_entry'
-require 'chef/chef_fs/file_system/environments_dir'
-require 'chef/chef_fs/data_handler/client_data_handler'
-require 'chef/chef_fs/data_handler/role_data_handler'
-require 'chef/chef_fs/data_handler/user_data_handler'
-require 'chef/chef_fs/data_handler/group_data_handler'
-require 'chef/chef_fs/data_handler/container_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- #
- # Represents the root of a Chef server (or organization), under which
- # nodes, roles, cookbooks, etc. can be found.
- #
- class ChefServerRootDir < BaseFSDir
- #
- # Create a new Chef server root.
- #
- # == Parameters
- #
- # [root_name]
- # A friendly name for the root, for printing--like "remote" or "chef_central".
- # [chef_config]
- # A hash with options that look suspiciously like Chef::Config, including the
- # following keys:
- # :chef_server_url:: The URL to the Chef server or top of the organization
- # :node_name:: The username to authenticate to the Chef server with
- # :client_key:: The private key for the user for authentication
- # :environment:: The environment in which you are presently working
- # :repo_mode::
- # The repository mode, :hosted_everything, :everything or :static.
- # This determines the set of subdirectories the Chef server will
- # offer up.
- # :versioned_cookbooks:: whether or not to include versions in cookbook names
- # [options]
- # Other options:
- # :cookbook_version:: when cookbooks are retrieved, grab this version for them.
- # :freeze:: freeze cookbooks on upload
- #
- def initialize(root_name, chef_config, options = {})
- super("", nil)
- @chef_server_url = chef_config[:chef_server_url]
- @chef_username = chef_config[:node_name]
- @chef_private_key = chef_config[:client_key]
- @environment = chef_config[:environment]
- @repo_mode = chef_config[:repo_mode]
- @versioned_cookbooks = chef_config[:versioned_cookbooks]
- @root_name = root_name
- @cookbook_version = options[:cookbook_version] # Used in knife diff and download for server cookbook version
- end
-
- attr_reader :chef_server_url
- attr_reader :chef_username
- attr_reader :chef_private_key
- attr_reader :environment
- attr_reader :repo_mode
- attr_reader :cookbook_version
- attr_reader :versioned_cookbooks
-
- def fs_description
- "Chef server at #{chef_server_url} (user #{chef_username}), repo_mode = #{repo_mode}"
- end
-
- def rest
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key, :raw_output => true)
- end
-
- def get_json(path)
- Chef::ServerAPI.new(chef_server_url, :client_name => chef_username, :signing_key_filename => chef_private_key).get(path)
- end
-
- def chef_rest
- Chef::REST.new(chef_server_url, chef_username, chef_private_key)
- end
-
- def api_path
- ""
- end
-
- def path_for_printing
- "#{@root_name}/"
- end
-
- def can_have_child?(name, is_dir)
- is_dir && children.any? { |child| child.name == name }
- end
-
- def org
- @org ||= begin
- path = Pathname.new(URI.parse(chef_server_url).path).cleanpath
- if File.dirname(path) == '/organizations'
- File.basename(path)
- else
- nil
- end
- end
- end
-
- def children
- @children ||= begin
- result = [
- CookbooksDir.new(self),
- DataBagsDir.new(self),
- EnvironmentsDir.new(self),
- RestListDir.new("roles", self, nil, Chef::ChefFS::DataHandler::RoleDataHandler.new)
- ]
- if repo_mode == 'hosted_everything'
- result += [
- AclsDir.new(self),
- RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
- RestListDir.new("containers", self, nil, Chef::ChefFS::DataHandler::ContainerDataHandler.new),
- RestListDir.new("groups", self, nil, Chef::ChefFS::DataHandler::GroupDataHandler.new),
- NodesDir.new(self),
- OrgEntry.new("org.json", self),
- OrganizationMembersEntry.new("members.json", self),
- OrganizationInvitesEntry.new("invitations.json", self)
- ]
- elsif repo_mode != 'static'
- result += [
- RestListDir.new("clients", self, nil, Chef::ChefFS::DataHandler::ClientDataHandler.new),
- NodesDir.new(self),
- RestListDir.new("users", self, nil, Chef::ChefFS::DataHandler::UserDataHandler.new)
- ]
- end
- result.sort_by { |child| child.name }
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/cookbook_dir.rb b/lib/chef/chef_fs/file_system/cookbook_dir.rb
deleted file mode 100644
index 03652dc376..0000000000
--- a/lib/chef/chef_fs/file_system/cookbook_dir.rb
+++ /dev/null
@@ -1,224 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/rest_list_dir'
-require 'chef/chef_fs/file_system/cookbook_subdir'
-require 'chef/chef_fs/file_system/cookbook_file'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/cookbook_version'
-require 'chef/cookbook_uploader'
-
-class Chef
- module ChefFS
- module FileSystem
- class CookbookDir < BaseFSDir
- def initialize(name, parent, options = {})
- super(name, parent)
- @exists = options[:exists]
- # If the name is apache2-1.0.0 and versioned_cookbooks is on, we know
- # the actual cookbook_name and version.
- if root.versioned_cookbooks
- if name =~ VALID_VERSIONED_COOKBOOK_NAME
- @cookbook_name = $1
- @version = $2
- else
- @exists = false
- end
- else
- @cookbook_name = name
- @version = root.cookbook_version # nil unless --cookbook-version specified in download/diff
- end
- end
-
- attr_reader :cookbook_name, :version
-
- COOKBOOK_SEGMENT_INFO = {
- :attributes => { :ruby_only => true },
- :definitions => { :ruby_only => true },
- :recipes => { :ruby_only => true },
- :libraries => { :ruby_only => true },
- :templates => { :recursive => true },
- :files => { :recursive => true },
- :resources => { :ruby_only => true, :recursive => true },
- :providers => { :ruby_only => true, :recursive => true },
- :root_files => { }
- }
-
- # See Erchef code
- # https://github.com/opscode/chef_objects/blob/968a63344d38fd507f6ace05f73d53e9cd7fb043/src/chef_regex.erl#L94
- VALID_VERSIONED_COOKBOOK_NAME = /^([.a-zA-Z0-9_-]+)-(\d+\.\d+\.\d+)$/
-
- def add_child(child)
- @children << child
- end
-
- def api_path
- "#{parent.api_path}/#{cookbook_name}/#{version || "_latest"}"
- end
-
- def child(name)
- # Since we're ignoring the rules and doing a network request here,
- # we need to make sure we don't rethrow the exception. (child(name)
- # is not supposed to fail.)
- begin
- result = children.select { |child| child.name == name }.first
- return result if result
- rescue Chef::ChefFS::FileSystem::NotFoundError
- end
- return NonexistentFSObject.new(name, self)
- end
-
- def can_have_child?(name, is_dir)
- # A cookbook's root may not have directories unless they are segment directories
- return name != 'root_files' && COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) if is_dir
- return true
- end
-
- def children
- if @children.nil?
- @children = []
- manifest = chef_object.manifest
- COOKBOOK_SEGMENT_INFO.each do |segment, segment_info|
- next unless manifest.has_key?(segment)
-
- # Go through each file in the manifest for the segment, and
- # add cookbook subdirs and files for it.
- manifest[segment].each do |segment_file|
- parts = segment_file[:path].split('/')
- # Get or create the path to the file
- container = self
- parts[0,parts.length-1].each do |part|
- old_container = container
- container = old_container.children.select { |child| part == child.name }.first
- if !container
- container = CookbookSubdir.new(part, old_container, segment_info[:ruby_only], segment_info[:recursive])
- old_container.add_child(container)
- end
- end
- # Create the file itself
- container.add_child(CookbookFile.new(parts[parts.length-1], container, segment_file))
- end
- end
- @children = @children.sort_by { |c| c.name }
- end
- @children
- end
-
- def dir?
- exists?
- end
-
- def delete(recurse)
- if recurse
- begin
- rest.delete(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
- rescue Net::HTTPServerException
- if $!.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "HTTP error deleting: #{e}"
- end
- end
- else
- raise NotFoundError.new(self) if !exists?
- raise MustDeleteRecursivelyError.new(self), "#{path_for_printing} must be deleted recursively"
- end
- end
-
- # In versioned cookbook mode, actually check if the version exists
- # Probably want to cache this.
- def exists?
- if @exists.nil?
- @exists = parent.children.any? { |child| child.name == name }
- end
- @exists
- end
-
- def compare_to(other)
- if !other.dir?
- return [ !exists?, nil, nil ]
- end
- are_same = true
- Chef::ChefFS::CommandLine::diff_entries(self, other, nil, :name_only).each do |type, old_entry, new_entry|
- if [ :directory_to_file, :file_to_directory, :deleted, :added, :modified ].include?(type)
- are_same = false
- end
- end
- [ are_same, nil, nil ]
- end
-
- def copy_from(other, options = {})
- parent.upload_cookbook_from(other, options)
- end
-
- def rest
- parent.rest
- end
-
- def chef_object
- # We cheat and cache here, because it seems like a good idea to keep
- # the cookbook view consistent with the directory structure.
- return @chef_object if @chef_object
-
- # The negative (not found) response is cached
- if @could_not_get_chef_object
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
- end
-
- begin
- # We want to fail fast, for now, because of the 500 issue :/
- # This will make things worse for parallelism, a little, because
- # Chef::Config is global and this could affect other requests while
- # this request is going on. (We're not parallel yet, but we will be.)
- # Chef bug http://tickets.opscode.com/browse/CHEF-3066
- old_retry_count = Chef::Config[:http_retry_count]
- begin
- Chef::Config[:http_retry_count] = 0
- @chef_object ||= Chef::CookbookVersion.json_create(root.get_json(api_path))
- ensure
- Chef::Config[:http_retry_count] = old_retry_count
- end
-
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}"
-
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- @could_not_get_chef_object = e
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}"
- end
-
- # Chef bug http://tickets.opscode.com/browse/CHEF-3066 ... instead of 404 we get 500 right now.
- # Remove this when that bug is fixed.
- rescue Net::HTTPFatalError => e
- if e.response.code == "500"
- @could_not_get_chef_object = e
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, @could_not_get_chef_object)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}"
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/cookbook_file.rb b/lib/chef/chef_fs/file_system/cookbook_file.rb
deleted file mode 100644
index 16203b727c..0000000000
--- a/lib/chef/chef_fs/file_system/cookbook_file.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_object'
-require 'chef/http/simple'
-require 'openssl'
-
-class Chef
- module ChefFS
- module FileSystem
- class CookbookFile < BaseFSObject
- def initialize(name, parent, file)
- super(name, parent)
- @file = file
- end
-
- attr_reader :file
-
- def checksum
- file[:checksum]
- end
-
- def read
- begin
- tmpfile = rest.streaming_request(file[:url])
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading #{file[:url]}: #{e}"
- rescue Net::HTTPServerException => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "#{e.message} retrieving #{file[:url]}"
- end
-
- begin
- tmpfile.open
- tmpfile.read
- ensure
- tmpfile.close!
- end
- end
-
- def rest
- parent.rest
- end
-
- def compare_to(other)
- other_value = nil
- if other.respond_to?(:checksum)
- other_checksum = other.checksum
- else
- begin
- other_value = other.read
- rescue Chef::ChefFS::FileSystem::NotFoundError
- return [ false, nil, :none ]
- end
- other_checksum = calc_checksum(other_value)
- end
- [ checksum == other_checksum, nil, other_value ]
- end
-
- private
-
- def calc_checksum(value)
- OpenSSL::Digest::MD5.hexdigest(value)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb b/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb
index 705673384d..d081249b24 100644
--- a/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb
+++ b/lib/chef/chef_fs/file_system/cookbook_frozen_error.rb
@@ -16,15 +16,12 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/already_exists_error'
+require "chef/chef_fs/file_system/already_exists_error"
class Chef
module ChefFS
module FileSystem
class CookbookFrozenError < AlreadyExistsError
- def initialize(operation, entry, cause = nil)
- super(operation, entry, cause)
- end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_dir.rb b/lib/chef/chef_fs/file_system/cookbooks_dir.rb
deleted file mode 100644
index 27bedd3827..0000000000
--- a/lib/chef/chef_fs/file_system/cookbooks_dir.rb
+++ /dev/null
@@ -1,164 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/rest_list_dir'
-require 'chef/chef_fs/file_system/cookbook_dir'
-require 'chef/chef_fs/file_system/operation_failed_error'
-require 'chef/chef_fs/file_system/cookbook_frozen_error'
-require 'chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir'
-require 'chef/mixin/file_class'
-
-require 'tmpdir'
-
-class Chef
- module ChefFS
- module FileSystem
- class CookbooksDir < RestListDir
-
- include Chef::Mixin::FileClass
-
- def initialize(parent)
- super("cookbooks", parent)
- end
-
- def child(name)
- if @children
- result = self.children.select { |child| child.name == name }.first
- if result
- result
- else
- NonexistentFSObject.new(name, self)
- end
- else
- CookbookDir.new(name, self)
- end
- end
-
- def children
- @children ||= begin
- if root.versioned_cookbooks
- result = []
- root.get_json("#{api_path}/?num_versions=all").each_pair do |cookbook_name, cookbooks|
- cookbooks['versions'].each do |cookbook_version|
- result << CookbookDir.new("#{cookbook_name}-#{cookbook_version['version']}", self, :exists => true)
- end
- end
- else
- result = root.get_json(api_path).keys.map { |cookbook_name| CookbookDir.new(cookbook_name, self, :exists => true) }
- end
- result.sort_by(&:name)
- end
- end
-
- def create_child_from(other, options = {})
- @children = nil
- upload_cookbook_from(other, options)
- end
-
- def upload_cookbook_from(other, options = {})
- root.versioned_cookbooks ? upload_versioned_cookbook(other, options) : upload_unversioned_cookbook(other, options)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}"
- rescue Net::HTTPServerException => e
- case e.response.code
- when "409"
- raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen"
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
- end
- rescue Chef::Exceptions::CookbookFrozen => e
- raise Chef::ChefFS::FileSystem::CookbookFrozenError.new(:write, self, e), "Cookbook #{other.name} is frozen"
- end
-
- # Knife currently does not understand versioned cookbooks
- # Cookbook Version uploader also requires a lot of refactoring
- # to make this work. So instead, we make a temporary cookbook
- # symlinking back to real cookbook, and upload the proxy.
- def upload_versioned_cookbook(other, options)
- cookbook_name = Chef::ChefFS::FileSystem::ChefRepositoryFileSystemCookbookDir.canonical_cookbook_name(other.name)
-
- Dir.mktmpdir do |temp_cookbooks_path|
- proxy_cookbook_path = "#{temp_cookbooks_path}/#{cookbook_name}"
-
- # Make a symlink
- file_class.symlink other.file_path, proxy_cookbook_path
-
- # Instantiate a proxy loader using the temporary symlink
- proxy_loader = Chef::Cookbook::CookbookVersionLoader.new(proxy_cookbook_path, other.parent.chefignore)
- proxy_loader.load_cookbooks
-
- cookbook_to_upload = proxy_loader.cookbook_version
- cookbook_to_upload.freeze_version if options[:freeze]
-
- # Instantiate a new uploader based on the proxy loader
- uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
-
- with_actual_cookbooks_dir(temp_cookbooks_path) do
- upload_cookbook!(uploader)
- end
-
- #
- # When the temporary directory is being deleted on
- # windows, the contents of the symlink under that
- # directory is also deleted. So explicitly remove
- # the symlink without removing the original contents if we
- # are running on windows
- #
- if Chef::Platform.windows?
- Dir.rmdir proxy_cookbook_path
- end
- end
- end
-
- def upload_unversioned_cookbook(other, options)
- cookbook_to_upload = other.chef_object
- cookbook_to_upload.freeze_version if options[:freeze]
- uploader = Chef::CookbookUploader.new(cookbook_to_upload, :force => options[:force], :rest => root.chef_rest)
-
- with_actual_cookbooks_dir(other.parent.file_path) do
- upload_cookbook!(uploader)
- end
- end
-
- # Work around the fact that CookbookUploader doesn't understand chef_repo_path (yet)
- def with_actual_cookbooks_dir(actual_cookbook_path)
- old_cookbook_path = Chef::Config.cookbook_path
- Chef::Config.cookbook_path = actual_cookbook_path if !Chef::Config.cookbook_path
-
- yield
- ensure
- Chef::Config.cookbook_path = old_cookbook_path
- end
-
- def upload_cookbook!(uploader, options = {})
- if uploader.respond_to?(:upload_cookbook)
- uploader.upload_cookbook
- else
- uploader.upload_cookbooks
- end
- end
-
- def can_have_child?(name, is_dir)
- return false if !is_dir
- return false if root.versioned_cookbooks && name !~ Chef::ChefFS::FileSystem::CookbookDir::VALID_VERSIONED_COOKBOOK_NAME
- return true
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/data_bag_dir.rb b/lib/chef/chef_fs/file_system/data_bag_dir.rb
deleted file mode 100644
index 212f76fdb9..0000000000
--- a/lib/chef/chef_fs/file_system/data_bag_dir.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/rest_list_dir'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/must_delete_recursively_error'
-require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- class DataBagDir < RestListDir
- def initialize(name, parent, exists = nil)
- super(name, parent, nil, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
- @exists = nil
- end
-
- def dir?
- exists?
- end
-
- def read
- # This will only be called if dir? is false, which means exists? is false.
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self)
- end
-
- def exists?
- if @exists.nil?
- @exists = parent.children.any? { |child| child.name == name }
- end
- @exists
- end
-
- def delete(recurse)
- if !recurse
- raise NotFoundError.new(self) if !exists?
- raise MustDeleteRecursivelyError.new(self), "#{path_for_printing} must be deleted recursively"
- end
- begin
- rest.delete(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "HTTP error deleting: #{e}"
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/data_bags_dir.rb b/lib/chef/chef_fs/file_system/data_bags_dir.rb
deleted file mode 100644
index 6d0685d3b7..0000000000
--- a/lib/chef/chef_fs/file_system/data_bags_dir.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/rest_list_dir'
-require 'chef/chef_fs/file_system/data_bag_dir'
-
-class Chef
- module ChefFS
- module FileSystem
- class DataBagsDir < RestListDir
- def initialize(parent)
- super("data_bags", parent, "data")
- end
-
- def child(name)
- result = @children.select { |child| child.name == name }.first if @children
- result || DataBagDir.new(name, self)
- end
-
- def children
- begin
- @children ||= root.get_json(api_path).keys.sort.map do |entry|
- DataBagDir.new(entry, self, true)
- end
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout getting children: #{e}"
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error getting children: #{e}"
- end
- end
- end
-
- def can_have_child?(name, is_dir)
- is_dir
- end
-
- def create_child(name, file_contents)
- begin
- rest.post(api_path, { 'name' => name })
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating child '#{name}': #{e}"
- rescue Net::HTTPServerException => e
- if e.response.code == "409"
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e), "Cannot create #{name} under #{path}: already exists"
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "HTTP error creating child '#{name}': #{e}"
- end
- end
- @children = nil
- DataBagDir.new(name, self, true)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb b/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb
index 8ca3b917ca..da5ed90710 100644
--- a/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb
+++ b/lib/chef/chef_fs/file_system/default_environment_cannot_be_modified_error.rb
@@ -16,16 +16,12 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/operation_not_allowed_error'
+require "chef/chef_fs/file_system/operation_not_allowed_error"
class Chef
module ChefFS
module FileSystem
class DefaultEnvironmentCannotBeModifiedError < OperationNotAllowedError
- def initialize(operation, entry, cause = nil)
- super(operation, entry, cause)
- end
-
def reason
result = super
result + " (default environment cannot be modified)"
diff --git a/lib/chef/chef_fs/file_system/environments_dir.rb b/lib/chef/chef_fs/file_system/environments_dir.rb
deleted file mode 100644
index 559dd6af86..0000000000
--- a/lib/chef/chef_fs/file_system/environments_dir.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/default_environment_cannot_be_modified_error'
-require 'chef/chef_fs/data_handler/environment_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- class EnvironmentsDir < RestListDir
- def initialize(parent)
- super("environments", parent, nil, Chef::ChefFS::DataHandler::EnvironmentDataHandler.new)
- end
-
- def _make_child_entry(name, exists = nil)
- if name == '_default.json'
- DefaultEnvironmentEntry.new(name, self, exists)
- else
- super
- end
- end
-
- class DefaultEnvironmentEntry < RestListEntry
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def delete(recurse)
- raise NotFoundError.new(self) if !exists?
- raise DefaultEnvironmentCannotBeModifiedError.new(:delete, self), "#{path_for_printing} cannot be deleted."
- end
-
- def write(file_contents)
- raise NotFoundError.new(self) if !exists?
- raise DefaultEnvironmentCannotBeModifiedError.new(:write, self), "#{path_for_printing} cannot be updated."
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/file_system_entry.rb b/lib/chef/chef_fs/file_system/file_system_entry.rb
deleted file mode 100644
index 1af7e618de..0000000000
--- a/lib/chef/chef_fs/file_system/file_system_entry.rb
+++ /dev/null
@@ -1,108 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/rest_list_dir'
-require 'chef/chef_fs/file_system/already_exists_error'
-require 'chef/chef_fs/file_system/must_delete_recursively_error'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/path_utils'
-require 'fileutils'
-
-class Chef
- module ChefFS
- module FileSystem
- class FileSystemEntry < BaseFSDir
- def initialize(name, parent, file_path = nil)
- super(name, parent)
- @file_path = file_path || "#{parent.file_path}/#{name}"
- end
-
- attr_reader :file_path
-
- def path_for_printing
- file_path
- end
-
- def children
- begin
- Dir.entries(file_path).sort.select { |entry| entry != '.' && entry != '..' }.map { |entry| make_child(entry) }
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
- def create_child(child_name, file_contents=nil)
- child = make_child(child_name)
- if child.exists?
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
- end
- if file_contents
- child.write(file_contents)
- else
- begin
- Dir.mkdir(child.file_path)
- rescue Errno::EEXIST
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
- end
- end
- child
- end
-
- def dir?
- File.directory?(file_path)
- end
-
- def delete(recurse)
- if dir?
- if !recurse
- raise MustDeleteRecursivelyError.new(self, $!)
- end
- FileUtils.rm_rf(file_path)
- else
- File.delete(file_path)
- end
- end
-
- def exists?
- File.exists?(file_path)
- end
-
- def read
- begin
- File.open(file_path, "rb") {|f| f.read}
- rescue Errno::ENOENT
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- end
- end
-
- def write(content)
- File.open(file_path, 'wb') do |file|
- file.write(content)
- end
- end
-
- protected
-
- def make_child(child_name)
- FileSystemEntry.new(child_name, self)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/file_system_error.rb b/lib/chef/chef_fs/file_system/file_system_error.rb
index 80aff35893..6a5cedec34 100644
--- a/lib/chef/chef_fs/file_system/file_system_error.rb
+++ b/lib/chef/chef_fs/file_system/file_system_error.rb
@@ -20,13 +20,24 @@ class Chef
module ChefFS
module FileSystem
class FileSystemError < StandardError
- def initialize(entry, cause = nil)
+ # @param entry The entry which had an issue.
+ # @param cause The wrapped exception (if any).
+ # @param reason A string describing why this exception happened.
+ def initialize(entry, cause = nil, reason = nil)
+ super(reason)
@entry = entry
@cause = cause
+ @reason = reason
end
+ # The entry which had an issue.
attr_reader :entry
+
+ # The wrapped exception (if any).
attr_reader :cause
+
+ # A string describing why this exception happened.
+ attr_reader :reason
end
end
end
diff --git a/lib/chef/chef_fs/file_system/memory/memory_dir.rb b/lib/chef/chef_fs/file_system/memory/memory_dir.rb
new file mode 100644
index 0000000000..beb661448d
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory/memory_dir.rb
@@ -0,0 +1,53 @@
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/memory/memory_file"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Memory
+ class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir
+ def initialize(name, parent)
+ super(name, parent)
+ @children = []
+ end
+
+ attr_reader :children
+
+ def make_child_entry(name)
+ @children.select { |child| child.name == name }.first
+ end
+
+ def add_child(child)
+ @children.push(child)
+ end
+
+ def can_have_child?(name, is_dir)
+ root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true
+ end
+
+ def add_file(path, value)
+ path_parts = path.split("/")
+ dir = add_dir(path_parts[0..-2].join("/"))
+ file = MemoryFile.new(path_parts[-1], dir, value)
+ dir.add_child(file)
+ file
+ end
+
+ def add_dir(path)
+ path_parts = path.split("/")
+ dir = self
+ path_parts.each do |path_part|
+ subdir = dir.child(path_part)
+ if !subdir.exists?
+ subdir = MemoryDir.new(path_part, dir)
+ dir.add_child(subdir)
+ end
+ dir = subdir
+ end
+ dir
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/memory/memory_file.rb b/lib/chef/chef_fs/file_system/memory/memory_file.rb
new file mode 100644
index 0000000000..644273fa9f
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory/memory_file.rb
@@ -0,0 +1,19 @@
+require "chef/chef_fs/file_system/base_fs_object"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Memory
+ class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject
+ def initialize(name, parent, value)
+ super(name, parent)
+ @value = value
+ end
+ def read
+ return @value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/memory/memory_root.rb b/lib/chef/chef_fs/file_system/memory/memory_root.rb
new file mode 100644
index 0000000000..4881b3d951
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/memory/memory_root.rb
@@ -0,0 +1,23 @@
+require "chef/chef_fs/file_system/memory/memory_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Memory
+ class MemoryRoot < MemoryDir
+ def initialize(pretty_name, cannot_be_in_regex = nil)
+ super("", nil)
+ @pretty_name = pretty_name
+ @cannot_be_in_regex = cannot_be_in_regex
+ end
+
+ attr_reader :cannot_be_in_regex
+
+ def path_for_printing
+ @pretty_name
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/memory_dir.rb b/lib/chef/chef_fs/file_system/memory_dir.rb
deleted file mode 100644
index a7eda3c654..0000000000
--- a/lib/chef/chef_fs/file_system/memory_dir.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/nonexistent_fs_object'
-require 'chef/chef_fs/file_system/memory_file'
-
-class Chef
- module ChefFS
- module FileSystem
- class MemoryDir < Chef::ChefFS::FileSystem::BaseFSDir
- def initialize(name, parent)
- super(name, parent)
- @children = []
- end
-
- attr_reader :children
-
- def child(name)
- @children.select { |child| child.name == name }.first || Chef::ChefFS::FileSystem::NonexistentFSObject.new(name, self)
- end
-
- def add_child(child)
- @children.push(child)
- end
-
- def can_have_child?(name, is_dir)
- root.cannot_be_in_regex ? (name !~ root.cannot_be_in_regex) : true
- end
-
- def add_file(path, value)
- path_parts = path.split('/')
- dir = add_dir(path_parts[0..-2].join('/'))
- file = MemoryFile.new(path_parts[-1], dir, value)
- dir.add_child(file)
- file
- end
-
- def add_dir(path)
- path_parts = path.split('/')
- dir = self
- path_parts.each do |path_part|
- subdir = dir.child(path_part)
- if !subdir.exists?
- subdir = MemoryDir.new(path_part, dir)
- dir.add_child(subdir)
- end
- dir = subdir
- end
- dir
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/memory_file.rb b/lib/chef/chef_fs/file_system/memory_file.rb
deleted file mode 100644
index 0c44e703f1..0000000000
--- a/lib/chef/chef_fs/file_system/memory_file.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'chef/chef_fs/file_system/base_fs_object'
-
-class Chef
- module ChefFS
- module FileSystem
- class MemoryFile < Chef::ChefFS::FileSystem::BaseFSObject
- def initialize(name, parent, value)
- super(name, parent)
- @value = value
- end
- def read
- return @value
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/memory_root.rb b/lib/chef/chef_fs/file_system/memory_root.rb
deleted file mode 100644
index 4a83830946..0000000000
--- a/lib/chef/chef_fs/file_system/memory_root.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-require 'chef/chef_fs/file_system/memory_dir'
-
-class Chef
- module ChefFS
- module FileSystem
- class MemoryRoot < MemoryDir
- def initialize(pretty_name, cannot_be_in_regex = nil)
- super('', nil)
- @pretty_name = pretty_name
- @cannot_be_in_regex = cannot_be_in_regex
- end
-
- attr_reader :cannot_be_in_regex
-
- def path_for_printing
- @pretty_name
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/multiplexed_dir.rb b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
index 06d4af705d..e143dde9e8 100644
--- a/lib/chef/chef_fs/file_system/multiplexed_dir.rb
+++ b/lib/chef/chef_fs/file_system/multiplexed_dir.rb
@@ -1,5 +1,5 @@
-require 'chef/chef_fs/file_system/base_fs_object'
-require 'chef/chef_fs/file_system/nonexistent_fs_object'
+require "chef/chef_fs/file_system/base_fs_object"
+require "chef/chef_fs/file_system/nonexistent_fs_object"
class Chef
module ChefFS
@@ -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/must_delete_recursively_error.rb b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
index bfa8ba28ce..b54af68587 100644
--- a/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
+++ b/lib/chef/chef_fs/file_system/must_delete_recursively_error.rb
@@ -16,15 +16,12 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/file_system_error'
+require "chef/chef_fs/file_system/file_system_error"
class Chef
module ChefFS
module FileSystem
class MustDeleteRecursivelyError < FileSystemError
- def initialize(entry, cause = nil)
- super(entry, cause)
- end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/nodes_dir.rb b/lib/chef/chef_fs/file_system/nodes_dir.rb
deleted file mode 100644
index c3c48377cd..0000000000
--- a/lib/chef/chef_fs/file_system/nodes_dir.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/data_handler/node_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- class NodesDir < RestListDir
- def initialize(parent)
- super("nodes", parent, nil, Chef::ChefFS::DataHandler::NodeDataHandler.new)
- end
-
- # Identical to RestListDir.children, except supports environments
- def children
- begin
- @children ||= root.get_json(env_api_path).keys.sort.map do |key|
- _make_child_entry("#{key}.json", true)
- end
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
- rescue Net::HTTPServerException => e
- if $!.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error retrieving children: #{e}"
- end
- end
- end
-
- def env_api_path
- environment ? "environments/#{environment}/#{api_path}" : api_path
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb b/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
index a587ab47a4..7a09c8fd0b 100644
--- a/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
+++ b/lib/chef/chef_fs/file_system/nonexistent_fs_object.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/base_fs_object'
-require 'chef/chef_fs/file_system/not_found_error'
+require "chef/chef_fs/file_system/base_fs_object"
+require "chef/chef_fs/file_system/not_found_error"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/file_system/not_found_error.rb b/lib/chef/chef_fs/file_system/not_found_error.rb
index 9eab3d6131..76b8076da1 100644
--- a/lib/chef/chef_fs/file_system/not_found_error.rb
+++ b/lib/chef/chef_fs/file_system/not_found_error.rb
@@ -16,15 +16,12 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/file_system_error'
+require "chef/chef_fs/file_system/file_system_error"
class Chef
module ChefFS
module FileSystem
class NotFoundError < FileSystemError
- def initialize(entry, cause = nil)
- super(entry, cause)
- end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/operation_failed_error.rb b/lib/chef/chef_fs/file_system/operation_failed_error.rb
index 28d170d628..886a9982bf 100644
--- a/lib/chef/chef_fs/file_system/operation_failed_error.rb
+++ b/lib/chef/chef_fs/file_system/operation_failed_error.rb
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/file_system_error'
+require "chef/chef_fs/file_system/file_system_error"
class Chef
module ChefFS
module FileSystem
class OperationFailedError < FileSystemError
- def initialize(operation, entry, cause = nil)
- super(entry, cause)
+ def initialize(operation, entry, cause = nil, reason = nil)
+ super(entry, cause, reason)
@operation = operation
end
diff --git a/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb b/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb
index 4b4f9742a8..130c10ed5b 100644
--- a/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb
+++ b/lib/chef/chef_fs/file_system/operation_not_allowed_error.rb
@@ -16,32 +16,30 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/file_system_error'
+require "chef/chef_fs/file_system/file_system_error"
class Chef
module ChefFS
module FileSystem
class OperationNotAllowedError < FileSystemError
- def initialize(operation, entry, cause = nil)
- super(entry, cause)
+ def initialize(operation, entry, cause = nil, reason = nil)
+ reason ||=
+ case operation
+ when :delete
+ "cannot be deleted"
+ when :write
+ "cannot be updated"
+ when :create_child
+ "cannot have a child created under it"
+ when :read
+ "cannot be read"
+ end
+ super(entry, cause, reason)
@operation = operation
end
attr_reader :operation
attr_reader :entry
-
- def reason
- case operation
- when :delete
- "cannot be deleted"
- when :write
- "cannot be updated"
- when :create_child
- "cannot have a child created under it"
- when :read
- "cannot be read"
- end
- end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/org_entry.rb b/lib/chef/chef_fs/file_system/org_entry.rb
deleted file mode 100644
index 852956e1e5..0000000000
--- a/lib/chef/chef_fs/file_system/org_entry.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/data_handler/organization_data_handler'
-
-class Chef
- module ChefFS
- module FileSystem
- # /organizations/NAME/org.json
- # Represents the actual data at /organizations/NAME (the full name, etc.)
- class OrgEntry < RestListEntry
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def data_handler
- Chef::ChefFS::DataHandler::OrganizationDataHandler.new
- end
-
- # /organizations/foo/org.json -> GET /organizations/foo
- def api_path
- parent.api_path
- end
-
- def exists?
- parent.exists?
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/organization_invites_entry.rb b/lib/chef/chef_fs/file_system/organization_invites_entry.rb
deleted file mode 100644
index 5df37085cb..0000000000
--- a/lib/chef/chef_fs/file_system/organization_invites_entry.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/data_handler/organization_invites_data_handler'
-require 'chef/json_compat'
-
-class Chef
- module ChefFS
- module FileSystem
- # /organizations/NAME/invitations.json
- # read data from:
- # - GET /organizations/NAME/association_requests
- # write data to:
- # - remove from list: DELETE /organizations/NAME/association_requests/id
- # - add to list: POST /organizations/NAME/association_requests
- class OrganizationInvitesEntry < RestListEntry
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def data_handler
- Chef::ChefFS::DataHandler::OrganizationInvitesDataHandler.new
- end
-
- # /organizations/foo/invites.json -> /organizations/foo/association_requests
- def api_path
- File.join(parent.api_path, 'association_requests')
- end
-
- def exists?
- parent.exists?
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
- end
-
- def write(contents)
- desired_invites = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
- actual_invites = _read_json.inject({}) { |h,val| h[val['username']] = val['id']; h }
- invites = actual_invites.keys
- (desired_invites - invites).each do |invite|
- begin
- rest.post(api_path, { 'user' => invite })
- rescue Net::HTTPServerException => e
- if e.response.code == '409'
- Chef::Log.warn("Could not invite #{invite} to organization #{org}: #{api_error_text(e.response)}")
- else
- raise
- end
- end
- end
- (invites - desired_invites).each do |invite|
- rest.delete(File.join(api_path, actual_invites[invite]))
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/organization_members_entry.rb b/lib/chef/chef_fs/file_system/organization_members_entry.rb
deleted file mode 100644
index 94393b341f..0000000000
--- a/lib/chef/chef_fs/file_system/organization_members_entry.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/data_handler/organization_members_data_handler'
-require 'chef/json_compat'
-
-class Chef
- module ChefFS
- module FileSystem
- # /organizations/NAME/members.json
- # reads data from:
- # - GET /organizations/NAME/users
- # writes data to:
- # - remove from list: DELETE /organizations/NAME/users/name
- # - add to list: POST /organizations/NAME/users/name
- class OrganizationMembersEntry < RestListEntry
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def data_handler
- Chef::ChefFS::DataHandler::OrganizationMembersDataHandler.new
- end
-
- # /organizations/foo/members.json -> /organizations/foo/users
- def api_path
- File.join(parent.api_path, 'users')
- end
-
- def exists?
- parent.exists?
- end
-
- def delete(recurse)
- raise Chef::ChefFS::FileSystem::OperationNotAllowedError.new(:delete, self)
- end
-
- def write(contents)
- desired_members = minimize_value(Chef::JSONCompat.parse(contents, :create_additions => false))
- members = minimize_value(_read_json)
- (desired_members - members).each do |member|
- begin
- rest.post(File.join(api_path, member), {})
- rescue Net::HTTPServerException => e
- if e.response.code == '404'
- raise "Chef server at #{api_path} does not allow you to directly add members. Please either upgrade your Chef server or move the users you want into invitations.json instead of members.json."
- else
- raise
- end
- end
- end
- (members - desired_members).each do |member|
- rest.delete(File.join(api_path, member))
- end
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir.rb
new file mode 100644
index 0000000000..888ef6d3be
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir.rb
@@ -0,0 +1,39 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_entry"
+require "chef/chef_fs/file_system/chef_server/acls_dir"
+require "chef/chef_fs/data_handler/acl_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemAclsDir < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, path = nil)
+ super(name, parent, path, Chef::ChefFS::DataHandler::AclDataHandler.new)
+ end
+
+ def can_have_child?(name, is_dir)
+ is_dir ? Chef::ChefFS::FileSystem::ChefServer::AclsDir::ENTITY_TYPES.include?(name) : name == "organization.json"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb
index d6246f1e60..dbff973f17 100644
--- a/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir.rb
@@ -16,24 +16,24 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/acl_dir'
-require 'chef/chef_fs/file_system/acl_entry'
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
class Chef
module ChefFS
module FileSystem
- class CookbooksAclDir < AclDir
- # If versioned_cookbooks is on, the list of cookbooks will have versions
- # in them. But all versions of a cookbook have the same acl, so even if
- # we have cookbooks/apache2-1.0.0 and cookbooks/apache2-1.1.2, we will
- # only have one acl: acls/cookbooks/apache2.json. Thus, the list of
- # children of acls/cookbooks is a unique list of cookbook *names*.
- def children
- if @children.nil?
- names = parent.parent.child(name).children.map { |child| "#{child.cookbook_name}.json" }
- @children = names.uniq.map { |name| AclEntry.new(name, self, true) }
+ module Repository
+ class ChefRepositoryFileSystemCookbookArtifactDir < ChefRepositoryFileSystemCookbookDir
+ # Override from parent
+ def cookbook_version
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+ cookbook_name, dash, identifier = name.rpartition("-")
+ # KLUDGE: We shouldn't have to use instance_variable_set
+ loader.instance_variable_set(:@cookbook_name, cookbook_name)
+ loader.load_cookbooks
+ cookbook_version = loader.cookbook_version
+ cookbook_version.identifier = identifier
+ cookbook_version
end
- @children
end
end
end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifacts_dir.rb
index 73556b2c0b..a7f83b6d4e 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_data_bags_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifacts_dir.rb
@@ -16,19 +16,17 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
-require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifact_dir"
class Chef
module ChefFS
module FileSystem
- class ChefRepositoryFileSystemDataBagsDir < ChefRepositoryFileSystemEntry
- def initialize(name, parent, path = nil)
- super(name, parent, path, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
- end
-
- def can_have_child?(name, is_dir)
- is_dir && !name.start_with?('.')
+ module Repository
+ class ChefRepositoryFileSystemCookbookArtifactsDir < ChefRepositoryFileSystemCookbooksDir
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemCookbookArtifactDir.new(child_name, self)
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb
new file mode 100644
index 0000000000..17a84ff2bc
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir.rb
@@ -0,0 +1,95 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry"
+require "chef/chef_fs/file_system/chef_server/cookbook_dir"
+require "chef/chef_fs/file_system/chef_server/versioned_cookbook_dir"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/cookbook/chefignore"
+require "chef/cookbook/cookbook_version_loader"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemCookbookDir < ChefRepositoryFileSystemCookbookEntry
+ def chef_object
+ begin
+ cb = cookbook_version
+ if !cb
+ Chef::Log.error("Cookbook #{file_path} empty.")
+ raise "Cookbook #{file_path} empty."
+ end
+ cb
+ rescue => e
+ Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{e}")
+ Chef::Log.error(e.backtrace.join("\n"))
+ raise
+ end
+ end
+
+ def children
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
+ end
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ # Only the given directories will be uploaded.
+ return Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO.keys.include?(name.to_sym) && name != "root_files"
+ elsif name == Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE
+ return false
+ end
+ super(name, is_dir)
+ end
+
+ # Exposed as a class method so that it can be used elsewhere
+ def self.canonical_cookbook_name(entry_name)
+ name_match = Chef::ChefFS::FileSystem::ChefServer::VersionedCookbookDir::VALID_VERSIONED_COOKBOOK_NAME.match(entry_name)
+ return nil if name_match.nil?
+ return name_match[1]
+ end
+
+ def canonical_cookbook_name(entry_name)
+ self.class.canonical_cookbook_name(entry_name)
+ end
+
+ def uploaded_cookbook_version_path
+ File.join(file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
+ end
+
+ def can_upload?
+ File.exists?(uploaded_cookbook_version_path) || children.size > 0
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ segment_info = Chef::ChefFS::FileSystem::ChefServer::CookbookDir::COOKBOOK_SEGMENT_INFO[child_name.to_sym] || {}
+ ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, segment_info[:ruby_only], segment_info[:recursive])
+ end
+
+ def cookbook_version
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+ loader.load_cookbooks
+ cb = loader.cookbook_version
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb
new file mode 100644
index 0000000000..ce11849d42
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_entry.rb
@@ -0,0 +1,82 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_entry"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir"
+require "chef/chef_fs/file_system/not_found_error"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemCookbookEntry < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, file_path = nil, ruby_only = false, recursive = false)
+ super(name, parent, file_path)
+ @ruby_only = ruby_only
+ @recursive = recursive
+ end
+
+ attr_reader :ruby_only
+ attr_reader :recursive
+
+ def children
+ super.select { |entry| !(entry.dir? && entry.children.size == 0 ) }
+ end
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ return recursive && name != "." && name != ".."
+ elsif ruby_only
+ return false if name[-3..-1] != ".rb"
+ end
+
+ # Check chefignore
+ ignorer = parent
+ loop do
+ if ignorer.is_a?(ChefRepositoryFileSystemCookbooksDir)
+ # Grab the path from entry to child
+ path_to_child = name
+ child = self
+ while child.parent != ignorer
+ path_to_child = PathUtils.join(child.name, path_to_child)
+ child = child.parent
+ end
+ # Check whether that relative path is ignored
+ return !ignorer.chefignore || !ignorer.chefignore.ignored?(path_to_child)
+ end
+ ignorer = ignorer.parent
+ break unless ignorer
+ end
+
+ true
+ end
+
+ def write_pretty_json
+ false
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemCookbookEntry.new(child_name, self, nil, ruby_only, recursive)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb
new file mode 100644
index 0000000000..1ed9d6ee0b
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir.rb
@@ -0,0 +1,84 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_entry"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
+require "chef/cookbook/chefignore"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemCookbooksDir < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, file_path)
+ super(name, parent, file_path)
+ begin
+ @chefignore = Chef::Cookbook::Chefignore.new(self.file_path)
+ rescue Errno::EISDIR
+ rescue Errno::EACCES
+ # Work around a bug in Chefignore when chefignore is a directory
+ end
+ end
+
+ attr_reader :chefignore
+
+ def children
+ 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
+
+ def can_have_child?(name, is_dir)
+ is_dir && !name.start_with?(".")
+ end
+
+ def write_cookbook(cookbook_path, cookbook_version_json, from_fs)
+ cookbook_name = File.basename(cookbook_path)
+ 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.
+ Chef::ChefFS::FileSystem.copy_to(Chef::ChefFS::FilePattern.new("/#{cookbook_path}"), from_fs, child, nil, {:purge => true})
+
+ # Write out .uploaded-cookbook-version.json
+ cookbook_file_path = File.join(file_path, cookbook_name)
+ if !File.exists?(cookbook_file_path)
+ FileUtils.mkdir_p(cookbook_file_path)
+ end
+ uploaded_cookbook_version_path = File.join(cookbook_file_path, Chef::Cookbook::CookbookVersionLoader::UPLOADED_COOKBOOK_VERSION_FILE)
+ File.open(uploaded_cookbook_version_path, "w") do |file|
+ file.write(cookbook_version_json)
+ end
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemCookbookDir.new(child_name, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir.rb
index 7d2a930633..0476d78794 100644
--- a/lib/chef/chef_fs/file_system/chef_repository_file_system_acls_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir.rb
@@ -16,22 +16,23 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/chef_repository_file_system_entry'
-require 'chef/chef_fs/file_system/acls_dir'
-require 'chef/chef_fs/data_handler/acl_data_handler'
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_entry"
+require "chef/chef_fs/data_handler/data_bag_item_data_handler"
class Chef
module ChefFS
module FileSystem
- class ChefRepositoryFileSystemAclsDir < ChefRepositoryFileSystemEntry
- def initialize(name, parent, path = nil)
- super(name, parent, path, Chef::ChefFS::DataHandler::AclDataHandler.new)
- end
+ module Repository
+ class ChefRepositoryFileSystemDataBagsDir < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, path = nil)
+ super(name, parent, path, Chef::ChefFS::DataHandler::DataBagItemDataHandler.new)
+ end
- def can_have_child?(name, is_dir)
- is_dir ? Chef::ChefFS::FileSystem::AclsDir::ENTITY_TYPES.include?(name) : name == 'organization.json'
+ def can_have_child?(name, is_dir)
+ is_dir && !name.start_with?(".")
+ end
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb
new file mode 100644
index 0000000000..91813452aa
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_entry.rb
@@ -0,0 +1,83 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Author:: Ho-Sheng Hsiao (<hosh@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/file_system_entry"
+require "chef/chef_fs/file_system/not_found_error"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ # ChefRepositoryFileSystemEntry works just like FileSystemEntry,
+ # except can inflate Chef objects
+ class ChefRepositoryFileSystemEntry < FileSystemEntry
+ def initialize(name, parent, file_path = nil, data_handler = nil)
+ super(name, parent, file_path)
+ @data_handler = data_handler
+ end
+
+ def write_pretty_json=(value)
+ @write_pretty_json = value
+ end
+
+ def write_pretty_json
+ @write_pretty_json.nil? ? root.write_pretty_json : @write_pretty_json
+ end
+
+ def data_handler
+ @data_handler || parent.data_handler
+ end
+
+ def chef_object
+ begin
+ return data_handler.chef_object(Chef::JSONCompat.parse(read))
+ rescue
+ Chef::Log.error("Could not read #{path_for_printing} into a Chef object: #{$!}")
+ end
+ nil
+ end
+
+ def can_have_child?(name, is_dir)
+ !is_dir && name[-5..-1] == ".json"
+ end
+
+ def write(file_contents)
+ if file_contents && write_pretty_json && name[-5..-1] == ".json"
+ file_contents = minimize(file_contents, self)
+ end
+ super(file_contents)
+ end
+
+ def minimize(file_contents, entry)
+ object = Chef::JSONCompat.parse(file_contents)
+ object = data_handler.normalize(object, entry)
+ object = data_handler.minimize(object, entry)
+ Chef::JSONCompat.to_json_pretty(object)
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemEntry.new(child_name, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_policies_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_policies_dir.rb
new file mode 100644
index 0000000000..a4ad7c11c0
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_policies_dir.rb
@@ -0,0 +1,38 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_entry"
+require "chef/chef_fs/data_handler/policy_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemPoliciesDir < ChefRepositoryFileSystemEntry
+ def initialize(name, parent, path = nil)
+ super(name, parent, path, Chef::ChefFS::DataHandler::PolicyDataHandler.new)
+ end
+
+ def can_have_child?(name, is_dir)
+ !is_dir && name.include?("-")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb
new file mode 100644
index 0000000000..f16a499186
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_root_dir.rb
@@ -0,0 +1,210 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012-2016 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/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_acls_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_artifacts_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_data_bags_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_entry"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_policies_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbooks_dir"
+require "chef/chef_fs/file_system/multiplexed_dir"
+require "chef/chef_fs/data_handler/client_data_handler"
+require "chef/chef_fs/data_handler/environment_data_handler"
+require "chef/chef_fs/data_handler/node_data_handler"
+require "chef/chef_fs/data_handler/policy_data_handler"
+require "chef/chef_fs/data_handler/policy_group_data_handler"
+require "chef/chef_fs/data_handler/role_data_handler"
+require "chef/chef_fs/data_handler/user_data_handler"
+require "chef/chef_fs/data_handler/group_data_handler"
+require "chef/chef_fs/data_handler/container_data_handler"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+
+ #
+ # Represents the root of a local Chef repository, with directories for
+ # nodes, cookbooks, roles, etc. under it.
+ #
+ class ChefRepositoryFileSystemRootDir < BaseFSDir
+ #
+ # Create a new Chef Repository File System root.
+ #
+ # == Parameters
+ # [child_paths]
+ # A hash of child paths, e.g.:
+ # "nodes" => [ '/var/nodes', '/home/jkeiser/nodes' ],
+ # "roles" => [ '/var/roles' ],
+ # ...
+ # [root_paths]
+ # An array of paths representing the top level, where
+ # +org.json+, +members.json+, and +invites.json+ will be stored.
+ # [chef_config] - a hash of options that looks suspiciously like the ones
+ # stored in Chef::Config, containing at least these keys:
+ # :versioned_cookbooks:: whether to include versions in cookbook names
+ def initialize(child_paths, root_paths=[], chef_config=Chef::Config)
+ super("", nil)
+ @child_paths = child_paths
+ @root_paths = root_paths
+ @versioned_cookbooks = chef_config[:versioned_cookbooks]
+ end
+
+ attr_accessor :write_pretty_json
+
+ attr_reader :root_paths
+ attr_reader :child_paths
+ attr_reader :versioned_cookbooks
+
+ CHILDREN = %w{org.json invitations.json members.json}
+
+ def children
+ @children ||= begin
+ 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
+
+ def can_have_child?(name, is_dir)
+ if is_dir
+ child_paths.has_key?(name)
+ elsif root_dir
+ CHILDREN.include?(name)
+ else
+ false
+ end
+ end
+
+ def create_child(name, file_contents = nil)
+ if file_contents
+ child = root_dir.create_child(name, file_contents)
+ else
+ child_paths[name].each do |path|
+ begin
+ Dir.mkdir(path)
+ rescue Errno::EEXIST
+ end
+ end
+ child = make_child_entry(name)
+ end
+ @children = nil
+ child
+ end
+
+ def json_class
+ nil
+ end
+
+ # Used to print out a human-readable file system description
+ def fs_description
+ repo_paths = root_paths || [ File.dirname(child_paths["cookbooks"][0]) ]
+ result = "repository at #{repo_paths.join(', ')}\n"
+ if versioned_cookbooks
+ result << " Multiple versions per cookbook\n"
+ else
+ result << " One version per cookbook\n"
+ end
+ child_paths.each_pair do |name, paths|
+ if paths.any? { |path| !repo_paths.include?(File.dirname(path)) }
+ result << " #{name} at #{paths.join(', ')}\n"
+ end
+ end
+ result
+ end
+
+ private
+
+ #
+ # A FileSystemEntry representing the root path where invites.json,
+ # members.json and org.json may be found.
+ #
+ def root_dir
+ existing_paths = root_paths.select { |path| File.exists?(path) }
+ if existing_paths.size > 0
+ MultiplexedDir.new(existing_paths.map do |path|
+ dir = ChefRepositoryFileSystemEntry.new(name, parent, path)
+ dir.write_pretty_json = !!write_pretty_json
+ dir
+ end)
+ end
+ end
+
+ #
+ # Create a child entry of the appropriate type:
+ # cookbooks, data_bags, acls, etc. All will be multiplexed (i.e. if
+ # you have multiple paths for cookbooks, the multiplexed dir will grab
+ # cookbooks from all of them when you list or grab them).
+ #
+ def make_child_entry(name)
+ 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 NonexistentFSObject.new(name, self)
+ end
+ case name
+ when "cookbooks"
+ if versioned_cookbooks
+ dirs = paths.map { |path| ChefRepositoryFileSystemVersionedCookbooksDir.new(name, self, path) }
+ else
+ dirs = paths.map { |path| ChefRepositoryFileSystemCookbooksDir.new(name, self, path) }
+ end
+ when "cookbook_artifacts"
+ dirs = paths.map { |path| ChefRepositoryFileSystemCookbookArtifactsDir.new(name, self, path) }
+ when "policies"
+ dirs = paths.map { |path| ChefRepositoryFileSystemPoliciesDir.new(name, self, path) }
+ when "data_bags"
+ dirs = paths.map { |path| ChefRepositoryFileSystemDataBagsDir.new(name, self, path) }
+ when "acls"
+ dirs = paths.map { |path| ChefRepositoryFileSystemAclsDir.new(name, self, path) }
+ else
+ data_handler = case name
+ when "clients"
+ Chef::ChefFS::DataHandler::ClientDataHandler.new
+ when "environments"
+ Chef::ChefFS::DataHandler::EnvironmentDataHandler.new
+ when "nodes"
+ Chef::ChefFS::DataHandler::NodeDataHandler.new
+ when "policy_groups"
+ Chef::ChefFS::DataHandler::PolicyGroupDataHandler.new
+ when "roles"
+ Chef::ChefFS::DataHandler::RoleDataHandler.new
+ when "users"
+ Chef::ChefFS::DataHandler::UserDataHandler.new
+ when "groups"
+ Chef::ChefFS::DataHandler::GroupDataHandler.new
+ when "containers"
+ Chef::ChefFS::DataHandler::ContainerDataHandler.new
+ else
+ raise "Unknown top level path #{name}"
+ end
+ dirs = paths.map { |path| ChefRepositoryFileSystemEntry.new(name, self, path, data_handler) }
+ end
+ MultiplexedDir.new(dirs)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir.rb
new file mode 100644
index 0000000000..f1dbef8195
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir.rb
@@ -0,0 +1,42 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbook_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemVersionedCookbookDir < ChefRepositoryFileSystemCookbookDir
+ # Override from parent
+ def cookbook_version
+ loader = Chef::Cookbook::CookbookVersionLoader.new(file_path, parent.chefignore)
+ # We need the canonical cookbook name if we are using versioned cookbooks, but we don't
+ # want to spend a lot of time adding code to the main Chef libraries
+ canonical_name = canonical_cookbook_name(File.basename(file_path))
+ raise "When versioned_cookbooks mode is on, cookbook #{file_path} must match format <cookbook_name>-x.y.z" unless canonical_name
+ # KLUDGE: We shouldn't have to use instance_variable_set
+ loader.instance_variable_set(:@cookbook_name, canonical_name)
+ loader.load_cookbooks
+ loader.cookbook_version
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbooks_dir.rb b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbooks_dir.rb
new file mode 100644
index 0000000000..b791957d66
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbooks_dir.rb
@@ -0,0 +1,34 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_cookbooks_dir"
+require "chef/chef_fs/file_system/repository/chef_repository_file_system_versioned_cookbook_dir"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class ChefRepositoryFileSystemVersionedCookbooksDir < ChefRepositoryFileSystemCookbooksDir
+ def make_child_entry(child_name)
+ ChefRepositoryFileSystemVersionedCookbookDir.new(child_name, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/repository/file_system_entry.rb b/lib/chef/chef_fs/file_system/repository/file_system_entry.rb
new file mode 100644
index 0000000000..1e64a41b44
--- /dev/null
+++ b/lib/chef/chef_fs/file_system/repository/file_system_entry.rb
@@ -0,0 +1,117 @@
+#
+# Author:: John Keiser (<jkeiser@opscode.com>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/chef_fs/file_system/base_fs_dir"
+require "chef/chef_fs/file_system/chef_server/rest_list_dir"
+require "chef/chef_fs/file_system/already_exists_error"
+require "chef/chef_fs/file_system/must_delete_recursively_error"
+require "chef/chef_fs/file_system/not_found_error"
+require "chef/chef_fs/path_utils"
+require "fileutils"
+
+class Chef
+ module ChefFS
+ module FileSystem
+ module Repository
+ class FileSystemEntry < BaseFSDir
+ def initialize(name, parent, file_path = nil)
+ super(name, parent)
+ @file_path = file_path || "#{parent.file_path}/#{name}"
+ end
+
+ attr_reader :file_path
+
+ def path_for_printing
+ file_path
+ end
+
+ def children
+ # Except cookbooks and data bag dirs, all things must be json files
+ begin
+ 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_entry(child_name)
+ if child.exists?
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
+ end
+ if file_contents
+ child.write(file_contents)
+ else
+ begin
+ Dir.mkdir(child.file_path)
+ rescue Errno::EEXIST
+ raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, child)
+ end
+ end
+ child
+ end
+
+ def dir?
+ File.directory?(file_path)
+ end
+
+ def delete(recurse)
+ begin
+ if dir?
+ if !recurse
+ raise MustDeleteRecursivelyError.new(self, $!)
+ end
+ FileUtils.rm_r(file_path)
+ else
+ File.delete(file_path)
+ end
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ end
+
+ def exists?
+ File.exists?(file_path) && (parent.nil? || parent.can_have_child?(name, dir?))
+ end
+
+ def read
+ begin
+ File.open(file_path, "rb") {|f| f.read}
+ rescue Errno::ENOENT
+ raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
+ end
+ end
+
+ def write(content)
+ File.open(file_path, "wb") do |file|
+ file.write(content)
+ end
+ end
+
+ protected
+
+ def make_child_entry(child_name)
+ FileSystemEntry.new(child_name, self)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/chef_fs/file_system/file_system_root_dir.rb b/lib/chef/chef_fs/file_system/repository/file_system_root_dir.rb
index afbf7b1901..037f9a8446 100644
--- a/lib/chef/chef_fs/file_system/file_system_root_dir.rb
+++ b/lib/chef/chef_fs/file_system/repository/file_system_root_dir.rb
@@ -16,14 +16,16 @@
# limitations under the License.
#
-require 'chef/chef_fs/file_system/file_system_entry'
+require "chef/chef_fs/file_system/repository/file_system_entry"
class Chef
module ChefFS
module FileSystem
- class FileSystemRootDir < FileSystemEntry
- def initialize(file_path)
- super("", nil, file_path)
+ module Repository
+ class FileSystemRootDir < FileSystemEntry
+ def initialize(file_path)
+ super("", nil, file_path)
+ end
end
end
end
diff --git a/lib/chef/chef_fs/file_system/rest_list_dir.rb b/lib/chef/chef_fs/file_system/rest_list_dir.rb
deleted file mode 100644
index 672fa444f1..0000000000
--- a/lib/chef/chef_fs/file_system/rest_list_dir.rb
+++ /dev/null
@@ -1,115 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_dir'
-require 'chef/chef_fs/file_system/rest_list_entry'
-require 'chef/chef_fs/file_system/not_found_error'
-
-class Chef
- module ChefFS
- module FileSystem
- class RestListDir < BaseFSDir
- def initialize(name, parent, api_path = nil, data_handler = nil)
- super(name, parent)
- @api_path = api_path || (parent.api_path == "" ? name : "#{parent.api_path}/#{name}")
- @data_handler = data_handler
- end
-
- attr_reader :api_path
- attr_reader :data_handler
-
- def child(name)
- result = @children.select { |child| child.name == name }.first if @children
- result ||= can_have_child?(name, false) ?
- _make_child_entry(name) : NonexistentFSObject.new(name, self)
- end
-
- def can_have_child?(name, is_dir)
- name =~ /\.json$/ && !is_dir
- end
-
- def children
- begin
- @children ||= root.get_json(api_path).keys.sort.map do |key|
- _make_child_entry("#{key}.json", true)
- end
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "Timeout retrieving children: #{e}"
- rescue Net::HTTPServerException => e
- if $!.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, $!)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:children, self, e), "HTTP error retrieving children: #{e}"
- end
- end
- end
-
- def create_child(name, file_contents)
- begin
- object = Chef::JSONCompat.parse(file_contents)
- rescue Chef::Exceptions::JSON::ParseError => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Parse error reading JSON creating child '#{name}': #{e}"
- end
-
- result = _make_child_entry(name, true)
-
- if data_handler
- object = data_handler.normalize_for_post(object, result)
- data_handler.verify_integrity(object, result) do |error|
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self), "Error creating '#{name}': #{error}"
- end
- end
-
- begin
- rest.post(api_path, object)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Timeout creating '#{name}': #{e}"
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- elsif $!.response.code == "409"
- raise Chef::ChefFS::FileSystem::AlreadyExistsError.new(:create_child, self, e), "Failure creating '#{name}': #{path}/#{name} already exists"
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:create_child, self, e), "Failure creating '#{name}': #{e.message}"
- end
- end
-
- @children = nil
-
- result
- end
-
- def org
- parent.org
- end
-
- def environment
- parent.environment
- end
-
- def rest
- parent.rest
- end
-
- def _make_child_entry(name, exists = nil)
- RestListEntry.new(name, self, exists)
- end
- end
- end
- end
-end
diff --git a/lib/chef/chef_fs/file_system/rest_list_entry.rb b/lib/chef/chef_fs/file_system/rest_list_entry.rb
deleted file mode 100644
index f68794cb0d..0000000000
--- a/lib/chef/chef_fs/file_system/rest_list_entry.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-#
-# Author:: John Keiser (<jkeiser@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-require 'chef/chef_fs/file_system/base_fs_object'
-require 'chef/chef_fs/file_system/not_found_error'
-require 'chef/chef_fs/file_system/operation_failed_error'
-require 'chef/role'
-require 'chef/node'
-require 'chef/json_compat'
-
-class Chef
- module ChefFS
- module FileSystem
- class RestListEntry < BaseFSObject
- def initialize(name, parent, exists = nil)
- super(name, parent)
- @exists = exists
- end
-
- def data_handler
- parent.data_handler
- end
-
- def api_child_name
- if name.length < 5 || name[-5,5] != ".json"
- raise "Invalid name #{path}: must end in .json"
- end
- name[0,name.length-5]
- end
-
- def api_path
- "#{parent.api_path}/#{api_child_name}"
- end
-
- def org
- parent.org
- end
-
- def environment
- parent.environment
- end
-
- def exists?
- if @exists.nil?
- begin
- @exists = parent.children.any? { |child| child.name == name }
- rescue Chef::ChefFS::FileSystem::NotFoundError
- @exists = false
- end
- end
- @exists
- end
-
- def delete(recurse)
- begin
- rest.delete(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:delete, self, e), "Timeout deleting: #{e}"
- end
- end
- end
-
- def read
- Chef::JSONCompat.to_json_pretty(minimize_value(_read_json))
- end
-
- def _read_json
- begin
- # Minimize the value (get rid of defaults) so the results don't look terrible
- root.get_json(api_path)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "Timeout reading: #{e}"
- rescue Net::HTTPServerException => e
- if $!.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:read, self, e), "HTTP error reading: #{e}"
- end
- end
- end
-
- def chef_object
- # REST will inflate the Chef object using json_class
- data_handler.json_class.json_create(read)
- end
-
- def minimize_value(value)
- data_handler.minimize(data_handler.normalize(value, self), self)
- end
-
- def compare_to(other)
- # TODO this pair of reads can be parallelized
-
- # Grab the other value
- begin
- other_value_json = other.read
- rescue Chef::ChefFS::FileSystem::NotFoundError
- return [ nil, nil, :none ]
- end
-
- # Grab this value
- begin
- value = _read_json
- rescue Chef::ChefFS::FileSystem::NotFoundError
- return [ false, :none, other_value_json ]
- end
-
- # Minimize (and normalize) both values for easy and beautiful diffs
- value = minimize_value(value)
- value_json = Chef::JSONCompat.to_json_pretty(value)
- begin
- other_value = Chef::JSONCompat.parse(other_value_json)
- rescue Chef::Exceptions::JSON::ParseError => e
- Chef::Log.warn("Parse error reading #{other.path_for_printing} as JSON: #{e}")
- return [ nil, value_json, other_value_json ]
- end
- other_value = minimize_value(other_value)
- other_value_json = Chef::JSONCompat.to_json_pretty(other_value)
-
- [ value == other_value, value_json, other_value_json ]
- end
-
- def rest
- parent.rest
- end
-
- def write(file_contents)
- begin
- object = Chef::JSONCompat.parse(file_contents)
- rescue Chef::Exceptions::JSON::ParseError => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Parse error reading JSON: #{e}"
- end
-
- if data_handler
- object = data_handler.normalize_for_put(object, self)
- data_handler.verify_integrity(object, self) do |error|
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self), "#{error}"
- end
- end
-
- begin
- rest.put(api_path, object)
- rescue Timeout::Error => e
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "Timeout writing: #{e}"
- rescue Net::HTTPServerException => e
- if e.response.code == "404"
- raise Chef::ChefFS::FileSystem::NotFoundError.new(self, e)
- else
- raise Chef::ChefFS::FileSystem::OperationFailedError.new(:write, self, e), "HTTP error writing: #{e}"
- end
- end
- end
-
- def api_error_text(response)
- begin
- Chef::JSONCompat.parse(response.body)['error'].join("\n")
- rescue
- response.body
- end
- end
- end
-
- end
- end
-end
diff --git a/lib/chef/chef_fs/knife.rb b/lib/chef/chef_fs/knife.rb
index 86872dab71..4501aeea0f 100644
--- a/lib/chef/chef_fs/knife.rb
+++ b/lib/chef/chef_fs/knife.rb
@@ -16,7 +16,8 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
+require "pathname"
class Chef
module ChefFS
@@ -24,11 +25,11 @@ class Chef
# Workaround for CHEF-3932
def self.deps
super do
- require 'chef/config'
- require 'chef/chef_fs/parallelizer'
- require 'chef/chef_fs/config'
- require 'chef/chef_fs/file_pattern'
- require 'chef/chef_fs/path_utils'
+ require "chef/config"
+ require "chef/chef_fs/parallelizer"
+ require "chef/chef_fs/config"
+ require "chef/chef_fs/file_pattern"
+ require "chef/chef_fs/path_utils"
yield
end
end
@@ -44,16 +45,16 @@ class Chef
end
option :repo_mode,
- :long => '--repo-mode MODE',
+ :long => "--repo-mode MODE",
:description => "Specifies the local repository layout. Values: static, everything, hosted_everything. Default: everything/hosted_everything"
option :chef_repo_path,
- :long => '--chef-repo-path PATH',
- :description => 'Overrides the location of chef repo. Default is specified by chef_repo_path in the config'
+ :long => "--chef-repo-path PATH",
+ :description => "Overrides the location of chef repo. Default is specified by chef_repo_path in the config"
option :concurrency,
- :long => '--concurrency THREADS',
- :description => 'Maximum number of simultaneous requests to send (default: 10)'
+ :long => "--concurrency THREADS",
+ :description => "Maximum number of simultaneous requests to send (default: 10)"
def configure_chef
super
@@ -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)
@@ -117,7 +145,7 @@ class Chef
end
def discover_repo_dir(dir)
- %w(.chef cookbooks data_bags environments roles).each do |subdir|
+ %w{.chef cookbooks data_bags environments roles}.each do |subdir|
return dir if File.directory?(File.join(dir, subdir))
end
# If this isn't it, check the parent
diff --git a/lib/chef/chef_fs/parallelizer.rb b/lib/chef/chef_fs/parallelizer.rb
index 116a626869..6590431d91 100644
--- a/lib/chef/chef_fs/parallelizer.rb
+++ b/lib/chef/chef_fs/parallelizer.rb
@@ -1,5 +1,5 @@
-require 'thread'
-require 'chef/chef_fs/parallelizer/parallel_enumerable'
+require "thread"
+require "chef/chef_fs/parallelizer/parallel_enumerable"
class Chef
module ChefFS
diff --git a/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb b/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb
index 8845b6926a..60f283eabe 100644
--- a/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb
+++ b/lib/chef/chef_fs/parallelizer/parallel_enumerable.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/parallelizer/flatten_enumerable'
+require "chef/chef_fs/parallelizer/flatten_enumerable"
class Chef
module ChefFS
@@ -266,7 +266,7 @@ class Chef
begin
output = @block.call(input)
@unconsumed_output.push([ output, index, input, :result ])
- rescue
+ rescue StandardError, ScriptError
if @options[:stop_on_exception]
@unconsumed_input.clear
end
diff --git a/lib/chef/chef_fs/path_utils.rb b/lib/chef/chef_fs/path_utils.rb
index 9ef75ce2e5..6ac106fa40 100644
--- a/lib/chef/chef_fs/path_utils.rb
+++ b/lib/chef/chef_fs/path_utils.rb
@@ -16,36 +16,36 @@
# limitations under the License.
#
-require 'chef/chef_fs'
-require 'pathname'
+require "chef/chef_fs"
+require "pathname"
class Chef
module ChefFS
class PathUtils
- # If you are in 'source', this is what you would have to type to reach 'dest'
- # relative_to('/a/b/c/d/e', '/a/b/x/y') == '../../c/d/e'
- # relative_to('/a/b', '/a/b') == '.'
- def self.relative_to(dest, source)
- # Skip past the common parts
- source_parts = Chef::ChefFS::PathUtils.split(source)
- dest_parts = Chef::ChefFS::PathUtils.split(dest)
- i = 0
- until i >= source_parts.length || i >= dest_parts.length || source_parts[i] != dest_parts[i]
- i+=1
- end
- # dot-dot up from 'source' to the common ancestor, then
- # descend to 'dest' from the common ancestor
- result = Chef::ChefFS::PathUtils.join(*(['..']*(source_parts.length-i) + dest_parts[i,dest.length-i]))
- result == '' ? '.' : result
- end
+ # 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("/")
# Put the / back on
@@ -57,39 +57,70 @@ class Chef
end
def self.regexp_path_separator
- Chef::ChefFS::windows? ? '[\/\\\\]' : '/'
+ 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 a4f15c271f..a524a9cd54 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");
@@ -18,40 +18,41 @@
# 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/path_sanity'
-require 'chef/log'
-require 'chef/rest'
-require 'chef/api_client'
-require 'chef/api_client/registration'
-require 'chef/audit/runner'
-require 'chef/node'
-require 'chef/role'
-require 'chef/file_cache'
-require 'chef/run_context'
-require 'chef/runner'
-require 'chef/run_status'
-require 'chef/cookbook/cookbook_collection'
-require 'chef/cookbook/file_vendor'
-require 'chef/cookbook/file_system_file_vendor'
-require 'chef/cookbook/remote_file_vendor'
-require 'chef/event_dispatch/dispatcher'
-require 'chef/event_loggers/base'
-require 'chef/event_loggers/windows_eventlog'
-require 'chef/exceptions'
-require 'chef/formatters/base'
-require 'chef/formatters/doc'
-require 'chef/formatters/minimal'
-require 'chef/version'
-require 'chef/resource_reporter'
-require 'chef/audit/audit_reporter'
-require 'chef/run_lock'
-require 'chef/policy_builder'
-require 'chef/request_id'
-require 'chef/platform/rebooter'
-require 'ohai'
-require 'rbconfig'
+require "chef/config"
+require "chef/mixin/params_validate"
+require "chef/mixin/path_sanity"
+require "chef/log"
+require "chef/server_api"
+require "chef/api_client"
+require "chef/api_client/registration"
+require "chef/audit/runner"
+require "chef/node"
+require "chef/role"
+require "chef/file_cache"
+require "chef/run_context"
+require "chef/runner"
+require "chef/run_status"
+require "chef/cookbook/cookbook_collection"
+require "chef/cookbook/file_vendor"
+require "chef/cookbook/file_system_file_vendor"
+require "chef/cookbook/remote_file_vendor"
+require "chef/event_dispatch/dispatcher"
+require "chef/event_loggers/base"
+require "chef/event_loggers/windows_eventlog"
+require "chef/exceptions"
+require "chef/formatters/base"
+require "chef/formatters/doc"
+require "chef/formatters/minimal"
+require "chef/version"
+require "chef/resource_reporter"
+require "chef/audit/audit_reporter"
+require "chef/run_lock"
+require "chef/policy_builder"
+require "chef/request_id"
+require "chef/platform/rebooter"
+require "chef/mixin/deprecation"
+require "ohai"
+require "rbconfig"
class Chef
# == Chef::Client
@@ -60,114 +61,283 @@ class Chef
class Client
include Chef::Mixin::PathSanity
- # IO stream that will be used as 'STDOUT' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDOUT_FD = STDOUT
-
- # IO stream that will be used as 'STDERR' for formatters. Formatters are
- # configured during `initialize`, so this provides a convenience for
- # setting alternative IO stream during tests.
- STDERR_FD = STDERR
+ extend Chef::Mixin::Deprecation
- # Clears all notifications for client run status events.
- # Primarily for testing purposes.
- def self.clear_notifications
- @run_start_notifications = nil
- @run_completed_successfully_notifications = nil
- @run_failed_notifications = nil
- end
-
- # The list of notifications to be run when the client run starts.
- def self.run_start_notifications
- @run_start_notifications ||= []
- end
-
- # The list of notifications to be run when the client run completes
- # successfully.
- def self.run_completed_successfully_notifications
- @run_completed_successfully_notifications ||= []
- end
-
- # The list of notifications to be run when the client run fails.
- def self.run_failed_notifications
- @run_failed_notifications ||= []
- end
-
- # Add a notification for the 'client run started' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_starts(&notification_block)
- run_start_notifications << notification_block
- end
-
- # Add a notification for the 'client run success' event. The notification
- # is provided as a block. The current Chef::RunStatus object will be passed
- # to the notification_block when the event is triggered.
- def self.when_run_completes_successfully(&notification_block)
- run_completed_successfully_notifications << notification_block
- end
+ #
+ # The status of the Chef run.
+ #
+ # @return [Chef::RunStatus]
+ #
+ attr_reader :run_status
- # Add a notification for the 'client run failed' event. The notification
- # is provided as a block. The current Chef::RunStatus is passed to the
- # notification_block when the event is triggered.
- def self.when_run_fails(&notification_block)
- run_failed_notifications << notification_block
+ #
+ # The node represented by this client.
+ #
+ # @return [Chef::Node]
+ #
+ def node
+ run_status.node
end
-
- # Callback to fire notifications that the Chef run is starting
- def run_started
- self.class.run_start_notifications.each do |notification|
- notification.call(run_status)
- end
- @events.run_started(run_status)
+ def node=(value)
+ run_status.node = value
end
- # Callback to fire notifications that the run completed successfully
- def run_completed_successfully
- success_handlers = self.class.run_completed_successfully_notifications
- success_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The ohai system used by this client.
+ #
+ # @return [Ohai::System]
+ #
+ attr_reader :ohai
- # Callback to fire notifications that the Chef run failed
- def run_failed
- failure_handlers = self.class.run_failed_notifications
- failure_handlers.each do |notification|
- notification.call(run_status)
- end
- end
+ #
+ # The rest object used to communicate with the Chef server.
+ #
+ # @return [Chef::ServerAPI]
+ #
+ attr_reader :rest
- attr_accessor :node
- attr_accessor :ohai
- attr_accessor :rest
+ #
+ # The runner used to converge.
+ #
+ # @return [Chef::Runner]
+ #
attr_accessor :runner
+ #
+ # Extra node attributes that were applied to the node.
+ #
+ # @return [Hash]
+ #
attr_reader :json_attribs
- attr_reader :run_status
+
+ #
+ # The event dispatcher for the Chef run, including any configured output
+ # formatters and event loggers.
+ #
+ # @return [EventDispatch::Dispatcher]
+ #
+ # @see Chef::Formatters
+ # @see Chef::Config#formatters
+ # @see Chef::Config#stdout
+ # @see Chef::Config#stderr
+ # @see Chef::Config#force_logger
+ # @see Chef::Config#force_formatter
+ # TODO add stdout, stderr, and default formatters to Chef::Config so the
+ # defaults aren't calculated here. Remove force_logger and force_formatter
+ # from this code.
+ # @see Chef::EventLoggers
+ # @see Chef::Config#disable_event_logger
+ # @see Chef::Config#event_loggers
+ # @see Chef::Config#event_handlers
+ #
attr_reader :events
+ #
# Creates a new Chef::Client.
+ #
+ # @param json_attribs [Hash] Node attributes to layer into the node when it is
+ # fetched.
+ # @param args [Hash] Options:
+ # @option args [Array<RunList::RunListItem>] :override_runlist A runlist to
+ # use instead of the node's embedded run list.
+ # @option args [Array<String>] :specific_recipes A list of recipe file paths
+ # to load after the run list has been loaded.
+ #
def initialize(json_attribs=nil, args={})
@json_attribs = json_attribs || {}
- @node = nil
- @run_status = nil
- @runner = nil
@ohai = Ohai::System.new
event_handlers = configure_formatters + configure_event_loggers
event_handlers += Array(Chef::Config[:event_handlers])
@events = EventDispatch::Dispatcher.new(*event_handlers)
+ # TODO it seems like a bad idea to be deletin' other peoples' hashes.
@override_runlist = args.delete(:override_runlist)
@specific_recipes = args.delete(:specific_recipes)
+ @run_status = Chef::RunStatus.new(nil, events)
if new_runlist = args.delete(:runlist)
@json_attribs["run_list"] = new_runlist
end
end
+ #
+ # Do a full run for this Chef::Client.
+ #
+ # Locks the run while doing its job.
+ #
+ # Fires run_start before doing anything and fires run_completed or
+ # run_failed when finished. Also notifies client listeners of run_started
+ # at the beginning of Compile, and run_completed_successfully or run_failed
+ # when all is complete.
+ #
+ # Phase 1: Setup
+ # --------------
+ # Gets information about the system and the run we are doing.
+ #
+ # 1. Run ohai to collect system information.
+ # 2. Register / connect to the Chef server (unless in solo mode).
+ # 3. Retrieve the node (or create a new one).
+ # 4. Merge in json_attribs, Chef::Config.environment, and override_run_list.
+ #
+ # @see #run_ohai
+ # @see #load_node
+ # @see #build_node
+ # @see Chef::Config#lockfile
+ # @see Chef::RunLock#acquire
+ #
+ # Phase 2: Compile
+ # ----------------
+ # Decides *what* we plan to converge by compiling recipes.
+ #
+ # 1. Sync required cookbooks to the local cache.
+ # 2. Load libraries from all cookbooks.
+ # 3. Load attributes from all cookbooks.
+ # 4. Load LWRPs from all cookbooks.
+ # 5. Load resource definitions from all cookbooks.
+ # 6. Load recipes in the run list.
+ # 7. Load recipes from the command line.
+ #
+ # @see #setup_run_context Syncs and compiles cookbooks.
+ # @see Chef::CookbookCompiler#compile
+ #
+ # Phase 3: Converge
+ # -----------------
+ # Brings the system up to date.
+ #
+ # 1. Converge the resources built from recipes in Phase 2.
+ # 2. Save the node.
+ # 3. Reboot if we were asked to.
+ #
+ # @see #converge_and_save
+ # @see Chef::Runner
+ #
+ # Phase 4: Audit
+ # --------------
+ # Runs 'control_group' audits in recipes. This entire section can be enabled or disabled with config.
+ #
+ # 1. 'control_group' DSL collects audits during Phase 2
+ # 2. Audits are run using RSpec
+ # 3. Errors are collected and reported using the formatters
+ #
+ # @see #run_audits
+ # @see Chef::Audit::Runner#run
+ #
+ # @raise [Chef::Exceptions::RunFailedWrappingError] If converge or audit failed.
+ #
+ # @see Chef::Config#enforce_path_sanity
+ # @see Chef::Config#solo
+ # @see Chef::Config#audit_mode
+ #
+ # @return Always returns true.
+ #
+ def run
+ start_profiling
+
+ run_error = nil
+
+ runlock = RunLock.new(Chef::Config.lockfile)
+ # TODO feels like acquire should have its own block arg for this
+ runlock.acquire
+ # don't add code that may fail before entering this section to be sure to release lock
+ begin
+ runlock.save_pid
+
+ request_id = Chef::RequestID.instance.request_id
+ run_context = nil
+ events.run_start(Chef::VERSION)
+ Chef::Log.info("*** Chef #{Chef::VERSION} ***")
+ Chef::Log.info "Chef-client pid: #{Process.pid}"
+ Chef::Log.debug("Chef-client request_id: #{request_id}")
+ enforce_path_sanity
+ run_ohai
+
+ register unless Chef::Config[:solo]
+
+ load_node
+
+ build_node
+
+ run_status.run_id = request_id
+ run_status.start_clock
+ Chef::Log.info("Starting Chef Run for #{node.name}")
+ run_started
+
+ do_windows_admin_check
+
+ run_context = setup_run_context
+
+ if Chef::Config[:audit_mode] != :audit_only
+ converge_error = converge_and_save(run_context)
+ end
+
+ if Chef::Config[:why_run] == true
+ # why_run should probably be renamed to why_converge
+ Chef::Log.debug("Not running controls in 'why-run' mode - this mode is used to see potential converge changes")
+ elsif Chef::Config[:audit_mode] != :disabled
+ audit_error = run_audits(run_context)
+ end
+
+ # Raise converge_error so run_failed reporters/events are processed.
+ raise converge_error if converge_error
+
+ run_status.stop_clock
+ Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
+ run_completed_successfully
+ events.run_completed(node)
+
+ # keep this inside the main loop to get exception backtraces
+ end_profiling
+
+ # rebooting has to be the last thing we do, no exceptions.
+ Chef::Platform::Rebooter.reboot_if_needed!(node)
+ rescue Exception => run_error
+ # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
+ Chef::Log.debug("Re-raising exception: #{run_error.class} - #{run_error.message}\n#{run_error.backtrace.join("\n ")}")
+ # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
+ if run_status
+ run_status.stop_clock
+ run_status.exception = run_error
+ run_failed
+ end
+ events.run_failed(run_error)
+ ensure
+ Chef::RequestID.instance.reset_request_id
+ request_id = nil
+ @run_status = nil
+ run_context = nil
+ runlock.release
+ end
+
+ # 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 +350,7 @@ class Chef
end
end
+ # @api private
def formatters_for_run
if Chef::Config.formatters.empty?
[default_formatter]
@@ -188,6 +359,7 @@ class Chef
end
end
+ # @api private
def default_formatter
if (STDOUT.tty? && !Chef::Config[:force_logger]) || Chef::Config[:force_formatter]
[:doc]
@@ -196,6 +368,7 @@ class Chef
end
end
+ # @api private
def configure_event_loggers
if Chef::Config.disable_event_logger
[]
@@ -212,54 +385,135 @@ 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),
- Chef::Audit::AuditReporter.new(rest)
+ Chef::Audit::AuditReporter.new(rest),
].each do |r|
events.register(r)
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::Dynamic.new(node_name, ohai.data, json_attribs, override_runlist, events)
end
+ #
+ # Save the updated node to Chef.
+ #
+ # Does not save if we are in solo mode or using override_runlist.
+ #
+ # @see Chef::Node#save
+ # @see Chef::Config#solo
+ #
+ # @api private
+ #
def save_updated_node
if Chef::Config[:solo]
# nothing to do
@@ -267,72 +521,133 @@ class Chef
Chef::Log.warn("Skipping final node save because override_runlist was given")
else
Chef::Log.debug("Saving the current state of node #{node_name}")
- @node.save
+ node.save
end
end
+ #
+ # Run ohai plugins. Runs all ohai plugins unless minimal_ohai is specified.
+ #
+ # Sends the ohai_completed event when finished.
+ #
+ # @see Chef::EventDispatcher#
+ # @see Chef::Config#minimal_ohai
+ #
+ # @api private
+ #
def run_ohai
- filter = Chef::Config[:minimal_ohai] ? %w[fqdn machinename hostname platform platform_version os os_version] : nil
+ filter = Chef::Config[:minimal_ohai] ? %w{fqdn machinename hostname platform platform_version os os_version} : nil
ohai.all_plugins(filter)
- @events.ohai_completed(node)
+ events.ohai_completed(node)
end
+ #
+ # Figure out the node name we are working with.
+ #
+ # It tries these, in order:
+ # - Chef::Config.node_name
+ # - ohai[:fqdn]
+ # - ohai[:machinename]
+ # - ohai[:hostname]
+ #
+ # @raise [Chef::Exceptions::CannotDetermineNodeName] If the node name is not
+ # set and cannot be determined via ohai.
+ #
+ # @see Chef::Config#node_name
+ #
+ # @api private
+ #
def node_name
name = Chef::Config[:node_name] || ohai[:fqdn] || ohai[:machinename] || ohai[:hostname]
Chef::Config[:node_name] = name
raise Chef::Exceptions::CannotDetermineNodeName unless name
- # node names > 90 bytes only work with authentication protocol >= 1.1
- # see discussion in config.rb.
- if name.bytesize > 90
- Chef::Config[:authentication_protocol_version] = "1.1"
- end
-
name
end
#
- # === 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::ServerAPI] 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])
+ @rest = Chef::ServerAPI.new(config[:chef_server_url], client_name: client_name,
+ signing_key_filename: config[:client_key])
register_reporters
rescue Exception => e
+ # TODO this should probably only ever fire if we *started* registration.
+ # Move it to the block above.
# TODO: munge exception so a semantic failure message can be given to the
# user
- @events.registration_failed(client_name, e, config)
+ events.registration_failed(client_name, e, config)
raise
end
- # Converges the node.
#
- # === Returns
- # The thrown exception, if there was one. If this returns nil the converge was successful.
+ # Converges all compiled resources.
+ #
+ # Fires the converge_start, converge_complete and converge_failed events.
+ #
+ # If the exception `:end_client_run_early` is thrown during convergence, it
+ # does not mark the run complete *or* failed, and returns `nil`
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge exception, unless we are in audit mode, in which case
+ # we *return* the exception.
+ #
+ # @see Chef::Runner#converge
+ # @see Chef::Config#audit_mode
+ # @see Chef::EventDispatch#converge_start
+ # @see Chef::EventDispatch#converge_complete
+ # @see Chef::EventDispatch#converge_failed
+ #
+ # @api private
+ #
def converge(run_context)
converge_exception = nil
catch(:end_client_run_early) do
begin
- @events.converge_start(run_context)
+ events.converge_start(run_context)
Chef::Log.debug("Converging node #{node_name}")
@runner = Chef::Runner.new(run_context)
- runner.converge
- @events.converge_complete
+ @runner.converge
+ events.converge_complete
rescue Exception => e
- @events.converge_failed(e)
+ events.converge_failed(e)
raise e if Chef::Config[:audit_mode] == :disabled
converge_exception = e
end
@@ -340,8 +655,28 @@ class Chef
converge_exception
end
+ #
+ # Converge the node via and then save it if successful.
+ #
+ # @param run_context The run context.
+ #
+ # @return The thrown exception, if we are in audit mode. `nil` means the
+ # converge was successful or ended early.
+ #
+ # @raise Any converge or node save exception, unless we are in audit mode,
+ # in which case we *return* the exception.
+ #
+ # @see #converge
+ # @see #save_updated_mode
+ # @see Chef::Config#audit_mode
+ #
+ # @api private
+ #
# We don't want to change the old API on the `converge` method to have it perform
# saving. So we wrap it in this method.
+ # TODO given this seems to be pretty internal stuff, how badly do we need to
+ # split this stuff up?
+ #
def converge_and_save(run_context)
converge_exception = converge(run_context)
unless converge_exception
@@ -355,37 +690,67 @@ class Chef
converge_exception
end
+ #
+ # Run the audit phase.
+ #
+ # Triggers the audit_phase_start, audit_phase_complete and
+ # audit_phase_failed events.
+ #
+ # @param run_context The run context.
+ #
+ # @return Any thrown exceptions. `nil` if successful.
+ #
+ # @see Chef::Audit::Runner#run
+ # @see Chef::EventDispatch#audit_phase_start
+ # @see Chef::EventDispatch#audit_phase_complete
+ # @see Chef::EventDispatch#audit_phase_failed
+ #
+ # @api private
+ #
def run_audits(run_context)
- audit_exception = nil
begin
- @events.audit_phase_start(run_status)
+ events.audit_phase_start(run_status)
Chef::Log.info("Starting audit phase")
auditor = Chef::Audit::Runner.new(run_context)
auditor.run
if auditor.failed?
- raise Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ audit_exception = Chef::Exceptions::AuditsFailed.new(auditor.num_failed, auditor.num_total)
+ @events.audit_phase_failed(audit_exception, Chef::Audit::Logger.read_buffer)
+ else
+ @events.audit_phase_complete(Chef::Audit::Logger.read_buffer)
end
- @events.audit_phase_complete
rescue Exception => e
Chef::Log.error("Audit phase failed with error message: #{e.message}")
- @events.audit_phase_failed(e)
+ @events.audit_phase_failed(e, Chef::Audit::Logger.read_buffer)
audit_exception = e
end
audit_exception
end
- # Expands the run list. Delegates to the policy_builder.
#
- # Normally this does not need to be called from here, it will be called by
- # build_node. This is provided so external users (like the chefspec
- # project) can inject custom behavior into the run process.
+ # Expands the run list.
+ #
+ # @return [Chef::RunListExpansion] The expanded run list.
+ #
+ # @see Chef::PolicyBuilder#expand_run_list
#
- # === Returns
- # RunListExpansion: A RunListExpansion or API compatible object.
def expanded_run_list
policy_builder.expand_run_list
end
+ #
+ # Check if the user has Administrator privileges on windows.
+ #
+ # Throws an error if the user is not an admin, and
+ # `Chef::Config.fatal_windows_admin_check` is true.
+ #
+ # @raise [Chef::Exceptions::WindowsNotAdmin] If the user is not an admin.
+ #
+ # @see Chef::platform#windows?
+ # @see Chef::Config#fatal_windows_admin_check
+ #
+ # @api private
+ #
def do_windows_admin_check
if Chef::Platform.windows?
Chef::Log.debug("Checking for administrator privileges....")
@@ -405,98 +770,142 @@ class Chef
end
end
- # Do a full run for this Chef::Client. Calls:
- #
- # * run_ohai - Collect information about the system
- # * build_node - Get the last known state, merge with local changes
- # * register - If not in solo mode, make sure the server knows about this client
- # * sync_cookbooks - If not in solo mode, populate the local cache with the node's cookbooks
- # * converge - Bring this system up to date
- #
- # === Returns
- # true:: Always returns true.
- def run
- runlock = RunLock.new(Chef::Config.lockfile)
- runlock.acquire
- # don't add code that may fail before entering this section to be sure to release lock
- begin
- runlock.save_pid
+ # Notification registration
+ class<<self
+ #
+ # Add a listener for the 'client run started' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_starts(&notification_block)
+ run_start_notifications << notification_block
+ end
- request_id = Chef::RequestID.instance.request_id
- run_context = nil
- @events.run_start(Chef::VERSION)
- Chef::Log.info("*** Chef #{Chef::VERSION} ***")
- Chef::Log.info "Chef-client pid: #{Process.pid}"
- Chef::Log.debug("Chef-client request_id: #{request_id}")
- enforce_path_sanity
- run_ohai
+ #
+ # Add a listener for the 'client run success' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_completes_successfully(&notification_block)
+ run_completed_successfully_notifications << notification_block
+ end
- register unless Chef::Config[:solo]
+ #
+ # Add a listener for the 'client run failed' event.
+ #
+ # @param notification_block The callback (takes |run_status| parameter).
+ # @yieldparam [Chef::RunStatus] run_status The run status.
+ #
+ def when_run_fails(&notification_block)
+ run_failed_notifications << notification_block
+ end
- load_node
+ #
+ # Clears all listeners for client run status events.
+ #
+ # Primarily for testing purposes.
+ #
+ # @api private
+ #
+ def clear_notifications
+ @run_start_notifications = nil
+ @run_completed_successfully_notifications = nil
+ @run_failed_notifications = nil
+ end
- build_node
+ #
+ # TODO These seem protected to me.
+ #
+
+ #
+ # Listeners to be run when the client run starts.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_start_notifications
+ @run_start_notifications ||= []
+ end
- run_status.run_id = request_id
- run_status.start_clock
- Chef::Log.info("Starting Chef Run for #{node.name}")
- run_started
+ #
+ # Listeners to be run when the client run completes successfully.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_completed_successfully_notifications
+ @run_completed_successfully_notifications ||= []
+ end
- do_windows_admin_check
+ #
+ # Listeners to be run when the client run fails.
+ #
+ # @return [Array<Proc>]
+ #
+ # @api private
+ #
+ def run_failed_notifications
+ @run_failed_notifications ||= []
+ end
+ end
- run_context = setup_run_context
+ #
+ # IO stream that will be used as 'STDOUT' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDOUT_FD = STDOUT
- if Chef::Config[:audit_mode] != :audit_only
- converge_error = converge_and_save(run_context)
- end
+ #
+ # IO stream that will be used as 'STDERR' for formatters. Formatters are
+ # configured during `initialize`, so this provides a convenience for
+ # setting alternative IO stream during tests.
+ #
+ # @api private
+ #
+ STDERR_FD = STDERR
- if Chef::Config[:why_run] == true
- # why_run should probably be renamed to why_converge
- Chef::Log.debug("Not running controls in 'why_run' mode - this mode is used to see potential converge changes")
- elsif Chef::Config[:audit_mode] != :disabled
- audit_error = run_audits(run_context)
- end
+ #
+ # Deprecated writers
+ #
- if converge_error || audit_error
- e = Chef::Exceptions::RunFailedWrappingError.new(converge_error, audit_error)
- e.fill_backtrace
- raise e
- end
+ include Chef::Mixin::Deprecation
+ deprecated_attr_writer :ohai, "There is no alternative. Leave ohai alone!"
+ deprecated_attr_writer :rest, "There is no alternative. Leave rest alone!"
+ deprecated_attr :runner, "There is no alternative. Leave runner alone!"
- run_status.stop_clock
- Chef::Log.info("Chef Run complete in #{run_status.elapsed_time} seconds")
- run_completed_successfully
- @events.run_completed(node)
+ private
- # rebooting has to be the last thing we do, no exceptions.
- Chef::Platform::Rebooter.reboot_if_needed!(node)
+ attr_reader :override_runlist
+ attr_reader :specific_recipes
- true
+ def profiling_prereqs!
+ require "ruby-prof"
+ rescue LoadError
+ raise "You must have the ruby-prof gem installed in order to use --profile-ruby"
+ end
- rescue Exception => e
- # CHEF-3336: Send the error first in case something goes wrong below and we don't know why
- Chef::Log.debug("Re-raising exception: #{e.class} - #{e.message}\n#{e.backtrace.join("\n ")}")
- # If we failed really early, we may not have a run_status yet. Too early for these to be of much use.
- if run_status
- run_status.stop_clock
- run_status.exception = e
- run_failed
- end
- Chef::Application.debug_stacktrace(e)
- @events.run_failed(e)
- raise
- ensure
- Chef::RequestID.instance.reset_request_id
- request_id = nil
- @run_status = nil
- run_context = nil
- runlock.release
- GC.start
- end
- true
+ def start_profiling
+ return unless Chef::Config[:profile_ruby]
+ profiling_prereqs!
+ RubyProf.start
end
- private
+ def end_profiling
+ return unless Chef::Config[:profile_ruby]
+ profiling_prereqs!
+ path = Chef::FileCache.create_cache_path("graph_profile.out", false)
+ File.open(path, "w+") do |file|
+ RubyProf::GraphPrinter.new(RubyProf.stop).print(file, {})
+ end
+ Chef::Log.warn("Ruby execution profile dumped to #{path}")
+ end
def empty_directory?(path)
!File.exists?(path) || (Dir.entries(path).size <= 2)
@@ -525,15 +934,14 @@ class Chef
end
def has_admin_privileges?
- require 'chef/win32/security'
+ require "chef/win32/security"
Chef::ReservedNames::Win32::Security.has_admin_privileges?
end
-
end
end
# HACK cannot load this first, but it must be loaded.
-require 'chef/cookbook_loader'
-require 'chef/cookbook_version'
-require 'chef/cookbook/synchronizer'
+require "chef/cookbook_loader"
+require "chef/cookbook_version"
+require "chef/cookbook/synchronizer"
diff --git a/lib/chef/config.rb b/lib/chef/config.rb
index b897f9fdbd..17ce861d4e 100644
--- a/lib/chef/config.rb
+++ b/lib/chef/config.rb
@@ -19,720 +19,67 @@
# See the License for the specific language governing permissions and
# 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/log"
+require "chef-config/logger"
-class Chef
- class Config
-
- extend Mixlib::Config
- extend Chef::Mixin::ShellOut
-
- PathHelper = Chef::Util::PathHelper
-
- # Evaluates the given string as config.
- #
- # +filename+ is used for context in stacktraces, but doesn't need to be the name of an actual file.
- def self.from_string(string, filename)
- self.instance_eval(string, filename, 1)
- end
-
- # Manages the chef secret session key
- # === Returns
- # <newkey>:: A new or retrieved session key
- #
- def self.manage_secret_key
- newkey = nil
- if Chef::FileCache.has_key?("chef_server_cookie_id")
- newkey = Chef::FileCache.load("chef_server_cookie_id")
- else
- chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
- newkey = ""
- 40.times { |i| newkey << chars[rand(chars.size-1)] }
- Chef::FileCache.store("chef_server_cookie_id", newkey)
- end
- newkey
- end
-
- def self.inspect
- configuration.inspect
- end
-
- def self.platform_specific_path(path)
- path = PathHelper.cleanpath(path)
- if Chef::Platform.windows?
- # turns \etc\chef\client.rb and \var\chef\client.rb into C:/chef/client.rb
- if env['SYSTEMDRIVE'] && path[0] == '\\' && path.split('\\')[2] == 'chef'
- path = PathHelper.join(env['SYSTEMDRIVE'], path.split('\\', 3)[2])
- end
- end
- path
- end
-
- def self.add_formatter(name, file_path=nil)
- formatters << [name, file_path]
- end
-
- def self.add_event_logger(logger)
- event_handlers << logger
- end
-
- # Config file to load (client.rb, knife.rb, etc. defaults set differently in knife, chef-client, etc.)
- configurable(:config_file)
-
- default(:config_dir) do
- if config_file
- PathHelper.dirname(config_file)
- else
- PathHelper.join(user_home, ".chef", "")
- end
- end
-
- default :formatters, []
-
- # Override the config dispatch to set the value of multiple server options simultaneously
- #
- # === Parameters
- # url<String>:: String to be set for all of the chef-server-api URL's
- #
- configurable(:chef_server_url).writes_value { |url| url.to_s.strip }
-
- # When you are using ActiveSupport, they monkey-patch 'daemonize' into Kernel.
- # So while this is basically identical to what method_missing would do, we pull
- # it up here and get a real method written so that things get dispatched
- # properly.
- configurable(:daemonize).writes_value { |v| v }
-
- # The root where all local chef object data is stored. cookbooks, data bags,
- # environments are all assumed to be in separate directories under this.
- # chef-solo uses these directories for input data. knife commands
- # that upload or download files (such as knife upload, knife role from file,
- # etc.) work.
- default :chef_repo_path do
- if self.configuration[:cookbook_path]
- if self.configuration[:cookbook_path].kind_of?(String)
- File.expand_path('..', self.configuration[:cookbook_path])
- else
- self.configuration[:cookbook_path].map do |path|
- File.expand_path('..', path)
- end
- end
- else
- cache_path
- end
- end
-
- def self.find_chef_repo_path(cwd)
- # In local mode, we auto-discover the repo root by looking for a path with "cookbooks" under it.
- # This allows us to run config-free.
- path = cwd
- until File.directory?(PathHelper.join(path, "cookbooks"))
- new_path = File.expand_path('..', path)
- if new_path == path
- Chef::Log.warn("No cookbooks directory found at or above current directory. Assuming #{Dir.pwd}.")
- return Dir.pwd
- end
- path = new_path
- end
- Chef::Log.info("Auto-discovered chef repository at #{path}")
- path
- end
-
- def self.derive_path_from_chef_repo_path(child_path)
- if chef_repo_path.kind_of?(String)
- PathHelper.join(chef_repo_path, child_path)
- else
- chef_repo_path.map { |path| PathHelper.join(path, child_path)}
- end
- end
-
- # Location of acls on disk. String or array of strings.
- # Defaults to <chef_repo_path>/acls.
- # Only applies to Enterprise Chef commands.
- default(:acl_path) { derive_path_from_chef_repo_path('acls') }
-
- # Location of clients on disk. String or array of strings.
- # Defaults to <chef_repo_path>/acls.
- default(:client_path) { derive_path_from_chef_repo_path('clients') }
-
- # Location of cookbooks on disk. String or array of strings.
- # Defaults to <chef_repo_path>/cookbooks. If chef_repo_path
- # is not specified, this is set to [/var/chef/cookbooks, /var/chef/site-cookbooks]).
- default(:cookbook_path) do
- if self.configuration[:chef_repo_path]
- derive_path_from_chef_repo_path('cookbooks')
- else
- Array(derive_path_from_chef_repo_path('cookbooks')).flatten +
- Array(derive_path_from_chef_repo_path('site-cookbooks')).flatten
- end
- end
-
- # Location of containers on disk. String or array of strings.
- # Defaults to <chef_repo_path>/containers.
- # Only applies to Enterprise Chef commands.
- default(:container_path) { derive_path_from_chef_repo_path('containers') }
-
- # Location of data bags on disk. String or array of strings.
- # Defaults to <chef_repo_path>/data_bags.
- default(:data_bag_path) { derive_path_from_chef_repo_path('data_bags') }
-
- # Location of environments on disk. String or array of strings.
- # Defaults to <chef_repo_path>/environments.
- default(:environment_path) { derive_path_from_chef_repo_path('environments') }
-
- # Location of groups on disk. String or array of strings.
- # Defaults to <chef_repo_path>/groups.
- # Only applies to Enterprise Chef commands.
- default(:group_path) { derive_path_from_chef_repo_path('groups') }
-
- # Location of nodes on disk. String or array of strings.
- # Defaults to <chef_repo_path>/nodes.
- default(:node_path) { derive_path_from_chef_repo_path('nodes') }
-
- # Location of roles on disk. String or array of strings.
- # Defaults to <chef_repo_path>/roles.
- default(:role_path) { derive_path_from_chef_repo_path('roles') }
-
- # Location of users on disk. String or array of strings.
- # Defaults to <chef_repo_path>/users.
- # Does not apply to Enterprise Chef commands.
- default(:user_path) { derive_path_from_chef_repo_path('users') }
-
- # Location of policies on disk. String or array of strings.
- # Defaults to <chef_repo_path>/policies.
- default(:policy_path) { derive_path_from_chef_repo_path('policies') }
-
- # Turn on "path sanity" by default. See also: http://wiki.opscode.com/display/chef/User+Environment+PATH+Sanity
- default :enforce_path_sanity, true
-
- # Formatted Chef Client output is a beta feature, disabled by default:
- default :formatter, "null"
-
- # The number of times the client should retry when registering with the server
- default :client_registration_retries, 5
-
- # An array of paths to search for knife exec scripts if they aren't in the current directory
- default :script_path, []
-
- # The root of all caches (checksums, cache and backup). If local mode is on,
- # this is under the user's home directory.
- default(:cache_path) do
- if local_mode
- PathHelper.join(config_dir, 'local-mode-cache')
- else
- primary_cache_root = platform_specific_path("/var")
- primary_cache_path = platform_specific_path("/var/chef")
- # Use /var/chef as the cache path only if that folder exists and we can read and write
- # into it, or /var exists and we can read and write into it (we'll create /var/chef later).
- # Otherwise, we'll create .chef under the user's home directory and use that as
- # the cache path.
- unless path_accessible?(primary_cache_path) || path_accessible?(primary_cache_root)
- secondary_cache_path = PathHelper.join(user_home, '.chef')
- Chef::Log.info("Unable to access cache at #{primary_cache_path}. Switching cache to #{secondary_cache_path}")
- secondary_cache_path
- else
- primary_cache_path
- end
- end
- end
-
- # Returns true only if the path exists and is readable and writeable for the user.
- def self.path_accessible?(path)
- File.exists?(path) && File.readable?(path) && File.writable?(path)
- end
-
- # Where cookbook files are stored on the server (by content checksum)
- default(:checksum_path) { PathHelper.join(cache_path, "checksums") }
-
- # Where chef's cache files should be stored
- default(:file_cache_path) { PathHelper.join(cache_path, "cache") }
-
- # Where backups of chef-managed files should go
- default(:file_backup_path) { PathHelper.join(cache_path, "backup") }
-
- # The chef-client (or solo) lockfile.
- #
- # If your `file_cache_path` resides on a NFS (or non-flock()-supporting
- # fs), it's recommended to set this to something like
- # '/tmp/chef-client-running.pid'
- default(:lockfile) { PathHelper.join(file_cache_path, "chef-client-running.pid") }
-
- ## Daemonization Settings ##
- # What user should Chef run as?
- default :user, nil
- default :group, nil
- default :umask, 0022
-
- # Valid log_levels are:
- # * :debug
- # * :info
- # * :warn
- # * :fatal
- # These work as you'd expect. There is also a special `:auto` setting.
- # When set to :auto, Chef will auto adjust the log verbosity based on
- # context. When a tty is available (usually because the user is running chef
- # in a console), the log level is set to :warn, and output formatters are
- # used as the primary mode of output. When a tty is not available, the
- # logger is the primary mode of output, and the log level is set to :info
- default :log_level, :auto
-
- # Logging location as either an IO stream or string representing log file path
- default :log_location, STDOUT
-
- # Using `force_formatter` causes chef to default to formatter output when STDOUT is not a tty
- default :force_formatter, false
-
- # Using `force_logger` causes chef to default to logger output when STDOUT is a tty
- default :force_logger, false
-
- default :http_retry_count, 5
- default :http_retry_delay, 5
- default :interval, nil
- default :once, nil
- default :json_attribs, nil
- # toggle info level log items that can create a lot of output
- default :verbose_logging, true
- default :node_name, nil
- default :diff_disabled, false
- default :diff_filesize_threshold, 10000000
- default :diff_output_threshold, 1000000
- default :local_mode, false
-
- default :pid_file, nil
-
- config_context :chef_zero do
- config_strict_mode true
- default(:enabled) { Chef::Config.local_mode }
- default :host, 'localhost'
- default :port, 8889.upto(9999) # Will try ports from 8889-9999 until one works
- end
- default :chef_server_url, "https://localhost:443"
-
- default :rest_timeout, 300
- default :yum_timeout, 900
- default :yum_lock_timeout, 30
- default :solo, false
- default :splay, nil
- default :why_run, false
- default :color, false
- default :client_fork, true
- default :ez, false
- default :enable_reporting, true
- default :enable_reporting_url_fatals, false
- # Possible values for :audit_mode
- # :enabled, :disabled, :audit_only,
- #
- # TODO: 11 Dec 2014: Currently audit-mode is an experimental feature
- # and is disabled by default. When users choose to enable audit-mode,
- # a warning is issued in application/client#reconfigure.
- # This can be removed when audit-mode is enabled by default.
- default :audit_mode, :disabled
-
- # Chef only needs ohai to run the hostname plugin for the most basic
- # functionality. If the rest of the ohai plugins are not needed (like in
- # most of our testing scenarios)
- default :minimal_ohai, false
-
- # Policyfile is an experimental feature where a node gets its run list and
- # cookbook version set from a single document on the server instead of
- # expanding the run list and having the server compute the cookbook version
- # set based on environment constraints.
- #
- # Because this feature is experimental, it is not recommended for
- # production use. Developent/release of this feature may not adhere to
- # semver guidelines.
- default :use_policyfile, false
-
- # Set these to enable SSL authentication / mutual-authentication
- # with the server
-
- # Client side SSL cert/key for mutual auth
- default :ssl_client_cert, nil
- default :ssl_client_key, nil
+# DI our logger into ChefConfig before we load the config. Some defaults are
+# auto-detected, and this emits log messages on some systems, all of which will
+# occur at require-time. So we need to set the logger first.
+ChefConfig.logger = Chef::Log
- # Whether 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
+require "chef-config/config"
+require "chef/platform/query_helpers"
- # 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? && !(Chef::Platform.windows_server_2003? ||
+ Chef::Platform.windows_nano_server?)
evt_loggers << :win_evt
end
evt_loggers
end
- # Exception Handlers
- default :exception_handlers, []
-
- # Start handlers
- default :start_handlers, []
-
- # Syntax Check Cache. Knife keeps track of files that is has already syntax
- # checked by storing files in this directory. `syntax_check_cache_path` is
- # the new (and preferred) configuration setting. If not set, knife will
- # fall back to using cache_options[:path], which is deprecated but exists in
- # many client configs generated by pre-Chef-11 bootstrappers.
- default(:syntax_check_cache_path) { cache_options[:path] }
-
- # Deprecated:
- # Move this to the default value of syntax_cache_path when this is removed.
- default(:cache_options) { { :path => PathHelper.join(config_dir, "syntaxcache") } }
-
- # Whether errors should be raised for deprecation warnings. When set to
- # `false` (the default setting), a warning is emitted but code using
- # deprecated methods/features/etc. should work normally otherwise. When set
- # to `true`, usage of deprecated methods/features will raise a
- # `DeprecatedFeatureError`. This is used by Chef's tests to ensure that
- # deprecated functionality is not used internally by Chef. End users
- # should generally leave this at the default setting (especially in
- # production), but it may be useful when testing cookbooks or other code if
- # the user wishes to aggressively address deprecations.
- default(:treat_deprecation_warnings_as_errors) do
- # Using an environment variable allows this setting to be inherited in
- # tests that spawn new processes.
- ENV.key?("CHEF_TREAT_DEPRECATION_WARNINGS_AS_ERRORS")
- end
-
- # knife configuration data
- config_context :knife do
- default :ssh_port, nil
- default :ssh_user, nil
- default :ssh_attribute, nil
- default :ssh_gateway, nil
- default :bootstrap_version, nil
- default :bootstrap_proxy, nil
- default :bootstrap_template, nil
- default :secret, nil
- default :secret_file, nil
- default :identity_file, nil
- default :host_key_verify, nil
- default :forward_agent, nil
- default :sort_status_reverse, nil
- default :hints, {}
- end
-
- def self.set_defaults_for_windows
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- # From http://technet.microsoft.com/en-us/library/cc776019(WS.10).aspx
- principal_valid_regex_part = '[^"\/\\\\\[\]\:;|=,+*?<>]+'
- default :user_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
- default :group_valid_regex, [ /^(#{principal_valid_regex_part}\\)?#{principal_valid_regex_part}$/ ]
-
- default :fatal_windows_admin_check, false
- end
-
- def self.set_defaults_for_nix
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- #
- # user/group cannot start with '-', '+' or '~'
- # user/group cannot contain ':', ',' or non-space-whitespace or null byte
- # everything else is allowed (UTF-8, spaces, etc) and we delegate to your O/S useradd program to barf or not
- # copies: http://anonscm.debian.org/viewvc/pkg-shadow/debian/trunk/debian/patches/506_relaxed_usernames?view=markup
- default :user_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
- default :group_valid_regex, [ /^[^-+~:,\t\r\n\f\0]+[^:,\t\r\n\f\0]*$/ ]
- end
-
- # Those lists of regular expressions define what chef considers a
- # valid user and group name
- if Chef::Platform.windows?
- set_defaults_for_windows
- else
- set_defaults_for_nix
- end
-
- # This provides a hook which rspec can stub so that we can avoid twiddling
- # global state in tests.
- def self.env
- ENV
- end
-
- def self.windows_home_path
- Chef::Log.deprecation("Chef::Config.windows_home_path is now deprecated. Consider using Chef::Util::PathHelper.home instead.")
- PathHelper.home
- end
-
- # returns a platform specific path to the user home dir if set, otherwise default to current directory.
- default( :user_home ) { PathHelper.home || Dir.pwd }
-
- # Enable file permission fixup for selinux. Fixup will be done
- # only if selinux is enabled in the system.
- default :enable_selinux_file_permission_fixup, true
-
- # Use atomic updates (i.e. move operation) while updating contents
- # of the files resources. When set to false copy operation is
- # used to update files.
- default :file_atomic_update, true
-
- # There are 3 possible values for this configuration setting.
- # true => file staging is done in the destination directory
- # false => file staging is done via tempfiles under ENV['TMP']
- # :auto => file staging will try using destination directory if possible and
- # will fall back to ENV['TMP'] if destination directory is not usable.
- default :file_staging_uses_destdir, :auto
-
- # Exit if another run is in progress and the chef-client is unable to
- # get the lock before time expires. If nil, no timeout is enforced. (Exits
- # immediately if 0.)
- default :run_lock_timeout, nil
-
- # Number of worker threads for syncing cookbooks in parallel. Increasing
- # this number can result in gateway errors from the server (namely 503 and 504).
- # If you are seeing this behavior while using the default setting, reducing
- # the number of threads will help.
- default :cookbook_sync_threads, 10
-
- # At the beginning of the Chef Client run, the cookbook manifests are downloaded which
- # contain URLs for every file in every relevant cookbook. Most of the files
- # (recipes, resources, providers, libraries, etc) are immediately synchronized
- # at the start of the run. The handling of "files" and "templates" directories,
- # however, have two modes of operation. They can either all be downloaded immediately
- # at the start of the run (no_lazy_load==true) or else they can be lazily loaded as
- # cookbook_file or template resources are converged which require them (no_lazy_load==false).
- #
- # The advantage of lazily loading these files is that unnecessary files are not
- # synchronized. This may be useful to users with large files checked into cookbooks which
- # are only selectively downloaded to a subset of clients which use the cookbook. However,
- # better solutions are to either isolate large files into individual cookbooks and only
- # include those cookbooks in the run lists of the servers that need them -- or move to
- # using remote_file and a more appropriate backing store like S3 for large file
- # distribution.
+ # Override the default values that were set by Ohai.
#
- # The disadvantages of lazily loading files are that users some time find it
- # confusing that their cookbooks are not fully synchronzied to the cache initially,
- # and more importantly the time-sensitive URLs which are in the manifest may time
- # out on long Chef runs before the resource that uses the file is converged
- # (leading to many confusing 403 errors on template/cookbook_file resources).
- #
- default :no_lazy_load, true
-
- # Default for the chef_gem compile_time attribute. Nil is the same as true but will emit
- # warnings on every use of chef_gem prompting the user to be explicit. If the user sets this to
- # true then the user will get backcompat behavior but with a single nag warning that cookbooks
- # may break with this setting in the future. The false setting is the recommended setting and
- # will become the default.
- default :chef_gem_compile_time, nil
-
- # A whitelisted array of attributes you want sent over the wire when node
- # data is saved.
- # The default setting is nil, which collects all data. Setting to [] will not
- # collect any data for save.
- default :automatic_attribute_whitelist, nil
- default :default_attribute_whitelist, nil
- default :normal_attribute_whitelist, nil
- default :override_attribute_whitelist, nil
-
- config_context :windows_service do
- # Set `watchdog_timeout` to the number of seconds to wait for a chef-client run
- # to finish
- default :watchdog_timeout, 2 * (60 * 60) # 2 hours
- end
+ # REMOVEME once these configurables are removed from the top level of Ohai.
+ default :log_level, LOG_LEVEL
+ default :log_location, LOG_LOCATION
- # Chef requires an English-language UTF-8 locale to function properly. We attempt
- # to use the 'locale -a' command and search through a list of preferences until we
- # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be
- # able to use that even if there is no English locale on the server, but Mac, Solaris,
- # AIX, etc do not have that locale. We then try to find an English locale and fall
- # back to 'C' if we do not. The choice of fallback is pick-your-poison. If we try
- # to do the work to return a non-US UTF-8 locale then we fail inside of providers when
- # things like 'svn info' return Japanese and we can't parse them. OTOH, if we pick 'C' then
- # we will blow up on UTF-8 characters. Between the warn we throw and the Encoding
- # exception that ruby will throw it is more obvious what is broken if we drop UTF-8 by
- # default rather than drop English.
+ # Ohai::Config[:log_level] is deprecated and warns when set. Unfortunately,
+ # there is no way to distinguish between setting log_level and setting
+ # Ohai::Config[:log_level]. Since log_level and log_location are used by
+ # chef-client and other tools (e.g., knife), we will mute the warnings here
+ # by redefining the config_attr_writer to not warn for these options.
#
- # If there is no 'locale -a' then we return 'en_US.UTF-8' since that is the most commonly
- # available English UTF-8 locale. However, all modern POSIXen should support 'locale -a'.
- def self.guess_internal_locale
- # https://github.com/opscode/chef/issues/2181
- # Some systems have the `locale -a` command, but the result has
- # invalid characters for the default encoding.
- #
- # For example, on CentOS 6 with ENV['LANG'] = "en_US.UTF-8",
- # `locale -a`.split fails with ArgumentError invalid UTF-8 encoding.
- locales = shell_out_with_systems_locale!("locale -a").stdout.split
- case
- when locales.include?('C.UTF-8')
- 'C.UTF-8'
- when locales.include?('en_US.UTF-8'), locales.include?('en_US.utf8')
- 'en_US.UTF-8'
- when locales.include?('en.UTF-8')
- 'en.UTF-8'
- else
- # Will match en_ZZ.UTF-8, en_ZZ.utf-8, en_ZZ.UTF8, en_ZZ.utf8
- guesses = locales.select { |l| l =~ /^en_.*UTF-?8$/i }
- unless guesses.empty?
- guessed_locale = guesses.first
- # Transform into the form en_ZZ.UTF-8
- guessed_locale.gsub(/UTF-?8$/i, "UTF-8")
- else
- Chef::Log.warn "Please install an English UTF-8 locale for Chef to use, falling back to C locale and disabling UTF-8 support."
- 'C'
- end
- end
- rescue
- if Chef::Platform.windows?
- Chef::Log.debug "Defaulting to locale en_US.UTF-8 on Windows, until it matters that we do something else."
- else
- Chef::Log.debug "No usable locale -a command found, assuming you have en_US.UTF-8 installed."
+ # REMOVEME once the warnings for these configurables are removed from Ohai.
+ [ :log_level, :log_location ].each do |option|
+ config_attr_writer option do |value|
+ value
end
- 'en_US.UTF-8'
end
- default :internal_locale, guess_internal_locale
-
- # Force UTF-8 Encoding, for when we fire up in the 'C' locale or other strange locales (e.g.
- # japanese windows encodings). If we do not do this, then knife upload will fail when a cookbook's
- # README.md has UTF-8 characters that do not encode in whatever surrounding encoding we have been
- # passed. Effectively, the Chef Ecosystem is globally UTF-8 by default. Anyone who wants to be
- # able to upload Shift_JIS or ISO-8859-1 files needs to mark *those* files explicitly with
- # magic tags to make ruby correctly identify the encoding being used. Changing this default will
- # break Chef community cookbooks and is very highly discouraged.
- default :ruby_encoding, Encoding::UTF_8
-
- # If installed via an omnibus installer, this gives the path to the
- # "embedded" directory which contains all of the software packaged with
- # omnibus. This is used to locate the cacert.pem file on windows.
- def self.embedded_dir
- Pathname.new(_this_file).ascend do |path|
- if path.basename.to_s == "embedded"
- return path.to_s
- end
- end
-
- nil
- end
-
- # Path to this file in the current install.
- def self._this_file
- File.expand_path(__FILE__)
- end
end
end
diff --git a/lib/chef/config_fetcher.rb b/lib/chef/config_fetcher.rb
index a8aad0740d..ac6bdc2249 100644
--- a/lib/chef/config_fetcher.rb
+++ b/lib/chef/config_fetcher.rb
@@ -1,7 +1,7 @@
-require 'chef/application'
-require 'chef/chef_fs/path_utils'
-require 'chef/http/simple'
-require 'chef/json_compat'
+require "chef/application"
+require "chef/chef_fs/path_utils"
+require "chef/http/simple"
+require "chef/json_compat"
class Chef
class ConfigFetcher
diff --git a/lib/chef/shell/shell_rest.rb b/lib/chef/constants.rb
index a485a0a1a8..d39ce4c68d 100644
--- a/lib/chef/shell/shell_rest.rb
+++ b/lib/chef/constants.rb
@@ -1,6 +1,6 @@
-#--
-# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+#
+# 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");
@@ -14,15 +14,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-
-module Shell
- class ShellREST < Chef::REST
-
- alias :get :get_rest
- alias :put :put_rest
- alias :post :post_rest
- alias :delete :delete_rest
+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/chefignore.rb b/lib/chef/cookbook/chefignore.rb
index aa9345e64e..529e209084 100644
--- a/lib/chef/cookbook/chefignore.rb
+++ b/lib/chef/cookbook/chefignore.rb
@@ -61,7 +61,7 @@ class Chef
if File.basename(path) =~ /chefignore/
path
else
- File.join(path, 'chefignore')
+ File.join(path, "chefignore")
end
end
diff --git a/lib/chef/cookbook/cookbook_collection.rb b/lib/chef/cookbook/cookbook_collection.rb
index ae63abfc93..3c43244dba 100644
--- a/lib/chef/cookbook/cookbook_collection.rb
+++ b/lib/chef/cookbook/cookbook_collection.rb
@@ -1,7 +1,7 @@
#--
# Author:: Tim Hinderliter (<tim@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/mash'
+require "chef/mash"
class Chef
# == Chef::CookbookCollection
@@ -41,5 +41,18 @@ class Chef
cookbook_versions.each{ |cookbook_name, cookbook_version| self[cookbook_name] = cookbook_version }
end
+ # Validates that the cookbook metadata allows it to run on this instance.
+ #
+ # Currently checks chef_version and ohai_version in the cookbook metadata
+ # against the running Chef::VERSION and Ohai::VERSION.
+ #
+ # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the Chef::VERSION fails validation
+ # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the Ohai::VERSION fails validation
+ def validate!
+ each do |cookbook_name, cookbook_version|
+ cookbook_version.metadata.validate_chef_version!
+ cookbook_version.metadata.validate_ohai_version!
+ end
+ end
end
end
diff --git a/lib/chef/cookbook/cookbook_version_loader.rb b/lib/chef/cookbook/cookbook_version_loader.rb
index bcbfcbeec8..370a77ecaf 100644
--- a/lib/chef/cookbook/cookbook_version_loader.rb
+++ b/lib/chef/cookbook/cookbook_version_loader.rb
@@ -1,8 +1,8 @@
-require 'chef/cookbook_version'
-require 'chef/cookbook/chefignore'
-require 'chef/cookbook/metadata'
-require 'chef/util/path_helper'
+require "chef/cookbook_version"
+require "chef/cookbook/chefignore"
+require "chef/cookbook/metadata"
+require "chef/util/path_helper"
class Chef
class Cookbook
@@ -51,7 +51,7 @@ class Chef
:library_filenames => {},
:resource_filenames => {},
:provider_filenames => {},
- :root_filenames => {}
+ :root_filenames => {},
}
@metadata_filenames = []
@@ -78,10 +78,10 @@ class Chef
# re-raise any exception that occurred when reading the metadata
raise_metadata_error!
- load_as(:attribute_filenames, 'attributes', '*.rb')
- load_as(:definition_filenames, 'definitions', '*.rb')
- load_as(:recipe_filenames, 'recipes', '*.rb')
- load_recursively_as(:library_filenames, 'libraries', '*.rb')
+ load_as(:attribute_filenames, "attributes", "*.rb")
+ load_as(:definition_filenames, "definitions", "*.rb")
+ load_as(:recipe_filenames, "recipes", "*.rb")
+ load_recursively_as(:library_filenames, "libraries", "*.rb")
load_recursively_as(:template_filenames, "templates", "*")
load_recursively_as(:file_filenames, "files", "*")
load_recursively_as(:resource_filenames, "resources", "*.rb")
@@ -91,7 +91,7 @@ class Chef
remove_ignored_files
if empty?
- Chef::Log.warn "found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
+ Chef::Log.warn "Found a directory #{cookbook_name} in the cookbook path, but it contains no cookbook files. skipping."
end
@cookbook_settings
end
@@ -213,7 +213,7 @@ class Chef
end
def load_root_files
- Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), '*'), File::FNM_DOTMATCH).each do |file|
+ Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), "*"), File::FNM_DOTMATCH).each do |file|
file = Chef::Util::PathHelper.cleanpath(file)
next if File.directory?(file)
next if File.basename(file) == UPLOADED_COOKBOOK_VERSION_FILE
@@ -223,7 +223,7 @@ class Chef
end
def load_recursively_as(category, category_dir, glob)
- file_spec = File.join(Chef::Util::PathHelper.escape_glob(cookbook_path, category_dir), '**', glob)
+ file_spec = File.join(Chef::Util::PathHelper.escape_glob(cookbook_path, category_dir), "**", glob)
Dir.glob(file_spec, File::FNM_DOTMATCH).each do |file|
file = Chef::Util::PathHelper.cleanpath(file)
next if File.directory?(file)
@@ -269,7 +269,7 @@ class Chef
def apply_json_cookbook_version_metadata(file)
begin
data = Chef::JSONCompat.parse(IO.read(file))
- @metadata.from_hash(data['metadata'])
+ @metadata.from_hash(data["metadata"])
# the JSON cookbok metadata file is only used by chef-zero.
# The Chef Server API currently does not enforce that the metadata
# have a `name` field, but that will cause an error when attempting
@@ -278,7 +278,7 @@ class Chef
#
# This behavior can be removed if/when Chef Server enforces that the
# metadata contains a name key.
- @metadata.name(data['cookbook_name']) unless data['metadata'].key?('name')
+ @metadata.name(data["cookbook_name"]) unless data["metadata"].key?("name")
rescue Chef::Exceptions::JSON::ParseError
Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in " + file)
raise
@@ -289,7 +289,7 @@ class Chef
if uploaded_cookbook_version_file
begin
data = Chef::JSONCompat.parse(IO.read(uploaded_cookbook_version_file))
- @frozen = data['frozen?']
+ @frozen = data["frozen?"]
rescue Chef::Exceptions::JSON::ParseError
Chef::Log.error("Couldn't parse cookbook metadata JSON for #@inferred_cookbook_name in #{uploaded_cookbook_version_file}")
raise
diff --git a/lib/chef/cookbook/file_system_file_vendor.rb b/lib/chef/cookbook/file_system_file_vendor.rb
index e351ec4702..227e873e4c 100644
--- a/lib/chef/cookbook/file_system_file_vendor.rb
+++ b/lib/chef/cookbook/file_system_file_vendor.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/cookbook/file_vendor'
+require "chef/cookbook/file_vendor"
class Chef
class Cookbook
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index 781d3b40b0..530254eead 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: AJ Christensen (<aj@opscode.com>)
# Author:: Seth Falcon (<seth@opscode.com>)
-# Copyright:: Copyright 2008-2010 Opscode, Inc.
+# Copyright:: Copyright 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,14 +18,14 @@
# limitations under the License.
#
-require 'chef/exceptions'
-require 'chef/mash'
-require 'chef/mixin/from_file'
-require 'chef/mixin/params_validate'
-require 'chef/log'
-require 'chef/version_class'
-require 'chef/version_constraint'
-require 'chef/json_compat'
+require "chef/exceptions"
+require "chef/mash"
+require "chef/mixin/from_file"
+require "chef/mixin/params_validate"
+require "chef/log"
+require "chef/version_class"
+require "chef/version_constraint"
+require "chef/json_compat"
class Chef
class Cookbook
@@ -35,38 +35,43 @@ class Chef
# about Chef Cookbooks.
class Metadata
- NAME = 'name'.freeze
- DESCRIPTION = 'description'.freeze
- LONG_DESCRIPTION = 'long_description'.freeze
- MAINTAINER = 'maintainer'.freeze
- MAINTAINER_EMAIL = 'maintainer_email'.freeze
- LICENSE = 'license'.freeze
- PLATFORMS = 'platforms'.freeze
- DEPENDENCIES = 'dependencies'.freeze
- RECOMMENDATIONS = 'recommendations'.freeze
- SUGGESTIONS = 'suggestions'.freeze
- CONFLICTING = 'conflicting'.freeze
- PROVIDING = 'providing'.freeze
- REPLACING = 'replacing'.freeze
- ATTRIBUTES = 'attributes'.freeze
- GROUPINGS = 'groupings'.freeze
- RECIPES = 'recipes'.freeze
- VERSION = 'version'.freeze
- SOURCE_URL = 'source_url'.freeze
- ISSUES_URL = 'issues_url'.freeze
+ NAME = "name".freeze
+ DESCRIPTION = "description".freeze
+ LONG_DESCRIPTION = "long_description".freeze
+ MAINTAINER = "maintainer".freeze
+ MAINTAINER_EMAIL = "maintainer_email".freeze
+ LICENSE = "license".freeze
+ PLATFORMS = "platforms".freeze
+ DEPENDENCIES = "dependencies".freeze
+ RECOMMENDATIONS = "recommendations".freeze
+ SUGGESTIONS = "suggestions".freeze
+ CONFLICTING = "conflicting".freeze
+ PROVIDING = "providing".freeze
+ REPLACING = "replacing".freeze
+ ATTRIBUTES = "attributes".freeze
+ GROUPINGS = "groupings".freeze
+ RECIPES = "recipes".freeze
+ VERSION = "version".freeze
+ SOURCE_URL = "source_url".freeze
+ ISSUES_URL = "issues_url".freeze
+ PRIVACY = "privacy".freeze
+ CHEF_VERSIONS = "chef_versions".freeze
+ OHAI_VERSIONS = "ohai_versions".freeze
COMPARISON_FIELDS = [ :name, :description, :long_description, :maintainer,
:maintainer_email, :license, :platforms, :dependencies,
:recommendations, :suggestions, :conflicting, :providing,
:replacing, :attributes, :groupings, :recipes, :version,
- :source_url, :issues_url ]
+ :source_url, :issues_url, :privacy, :chef_versions, :ohai_versions ]
- VERSION_CONSTRAINTS = {:depends => DEPENDENCIES,
- :recommends => RECOMMENDATIONS,
- :suggests => SUGGESTIONS,
- :conflicts => CONFLICTING,
- :provides => PROVIDING,
- :replaces => REPLACING }
+ VERSION_CONSTRAINTS = {:depends => DEPENDENCIES,
+ :recommends => RECOMMENDATIONS,
+ :suggests => SUGGESTIONS,
+ :conflicts => CONFLICTING,
+ :provides => PROVIDING,
+ :replaces => REPLACING,
+ :chef_version => CHEF_VERSIONS,
+ :ohai_version => OHAI_VERSIONS }
include Chef::Mixin::ParamsValidate
include Chef::Mixin::FromFile
@@ -83,6 +88,11 @@ class Chef
attr_reader :recipes
attr_reader :version
+ # @return [Array<Gem::Dependency>] Array of supported Chef versions
+ attr_reader :chef_versions
+ # @return [Array<Gem::Dependency>] Array of supported Ohai versions
+ attr_reader :ohai_versions
+
# Builds a new Chef::Cookbook::Metadata object.
#
# === Parameters
@@ -96,9 +106,9 @@ class Chef
def initialize
@name = nil
- @description = ''
- @long_description = ''
- @license = 'All rights reserved'
+ @description = ""
+ @long_description = ""
+ @license = "All rights reserved"
@maintainer = nil
@maintainer_email = nil
@@ -114,8 +124,11 @@ class Chef
@groupings = Mash.new
@recipes = Mash.new
@version = Version.new("0.0.0")
- @source_url = ''
- @issues_url = ''
+ @source_url = ""
+ @issues_url = ""
+ @privacy = false
+ @chef_versions = []
+ @ohai_versions = []
@errors = []
end
@@ -163,7 +176,7 @@ class Chef
set_or_return(
:maintainer,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -178,7 +191,7 @@ class Chef
set_or_return(
:maintainer_email,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -193,7 +206,7 @@ class Chef
set_or_return(
:license,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -208,7 +221,7 @@ class Chef
set_or_return(
:description,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -223,7 +236,7 @@ class Chef
set_or_return(
:long_description,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -254,7 +267,7 @@ class Chef
set_or_return(
:name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -286,9 +299,13 @@ class Chef
# === Returns
# versions<Array>:: Returns the list of versions for the platform
def depends(cookbook, *version_args)
- version = new_args_format(:depends, cookbook, version_args)
- constraint = validate_version_constraint(:depends, cookbook, version)
- @dependencies[cookbook] = constraint.to_s
+ if cookbook == name
+ Chef::Log.warn "Ignoring self-dependency in cookbook #{name}, please remove it (in the future this will be fatal)."
+ else
+ version = new_args_format(:depends, cookbook, version_args)
+ constraint = validate_version_constraint(:depends, cookbook, version)
+ @dependencies[cookbook] = constraint.to_s
+ end
@dependencies[cookbook]
end
@@ -380,6 +397,28 @@ class Chef
@replacing[cookbook]
end
+ # Metadata DSL to set a valid chef_version. May be declared multiple times
+ # with the result being 'OR'd such that if any statements match, the version
+ # is considered supported. Uses Gem::Requirement for its implementation.
+ #
+ # @param version_args [Array<String>] Version constraint in String form
+ # @return [Array<Gem::Dependency>] Current chef_versions array
+ def chef_version(*version_args)
+ @chef_versions << Gem::Dependency.new("chef", *version_args) unless version_args.empty?
+ @chef_versions
+ end
+
+ # Metadata DSL to set a valid ohai_version. May be declared multiple times
+ # with the result being 'OR'd such that if any statements match, the version
+ # is considered supported. Uses Gem::Requirement for its implementation.
+ #
+ # @param version_args [Array<String>] Version constraint in String form
+ # @return [Array<Gem::Dependency>] Current ohai_versions array
+ def ohai_version(*version_args)
+ @ohai_versions << Gem::Dependency.new("ohai", *version_args) unless version_args.empty?
+ @ohai_versions
+ end
+
# Adds a description for a recipe.
#
# === Parameters
@@ -450,8 +489,9 @@ 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?
validate_choice_array(options)
@@ -467,13 +507,47 @@ class Chef
options,
{
:title => { :kind_of => String },
- :description => { :kind_of => String }
- }
+ :description => { :kind_of => String },
+ },
)
@groupings[name] = options
@groupings[name]
end
+ # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to an Array.
+ #
+ # Gem::Dependencey#to_s is not useful, and there is no #to_json defined on it or its component
+ # objets, so we have to write our own rendering method.
+ #
+ # [ Gem::Dependency.new(">= 12.5"), Gem::Dependency.new(">= 11.18.0", "< 12.0") ]
+ #
+ # results in:
+ #
+ # [ [ ">= 12.5" ], [ ">= 11.18.0", "< 12.0" ] ]
+ #
+ # @param deps [Array<Gem::Dependency>] Multiple Gem-style version constraints
+ # @return [Array<Array<String>]] Simple object representation of version constraints (for json)
+ def gem_requirements_to_array(*deps)
+ deps.map do |dep|
+ dep.requirement.requirements.map do |op, version|
+ "#{op} #{version}"
+ end.sort
+ end
+ end
+
+ # Convert an Array of Gem::Dependency objects (chef_version/ohai_version) to a hash.
+ #
+ # This is the inverse of #gem_requirements_to_array
+ #
+ # @param what [String] What version constraint we are constructing ('chef' or 'ohai' presently)
+ # @param array [Array<Array<String>]] Simple object representation of version constraints (from json)
+ # @return [Array<Gem::Dependency>] Multiple Gem-style version constraints
+ def gem_requirements_from_array(what, array)
+ array.map do |dep|
+ Gem::Dependency.new(what, *dep)
+ end
+ end
+
def to_hash
{
NAME => self.name,
@@ -494,7 +568,10 @@ class Chef
RECIPES => self.recipes,
VERSION => self.version,
SOURCE_URL => self.source_url,
- ISSUES_URL => self.issues_url
+ ISSUES_URL => self.issues_url,
+ PRIVACY => self.privacy,
+ CHEF_VERSIONS => gem_requirements_to_array(*self.chef_versions),
+ OHAI_VERSIONS => gem_requirements_to_array(*self.ohai_versions),
}
end
@@ -528,6 +605,9 @@ class Chef
@version = o[VERSION] if o.has_key?(VERSION)
@source_url = o[SOURCE_URL] if o.has_key?(SOURCE_URL)
@issues_url = o[ISSUES_URL] if o.has_key?(ISSUES_URL)
+ @privacy = o[PRIVACY] if o.has_key?(PRIVACY)
+ @chef_versions = gem_requirements_from_array("chef", o[CHEF_VERSIONS]) if o.has_key?(CHEF_VERSIONS)
+ @ohai_versions = gem_requirements_from_array("ohai", o[OHAI_VERSIONS]) if o.has_key?(OHAI_VERSIONS)
self
end
@@ -567,7 +647,7 @@ class Chef
set_or_return(
:source_url,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -582,12 +662,64 @@ class Chef
set_or_return(
:issues_url,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
+ #
+ # Sets the cookbook's privacy flag, or returns it.
+ #
+ # === Parameters
+ # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+ #
+ # === Returns
+ # privacy<TrueClass,FalseClass>:: Whether this cookbook is private or not
+ #
+ def privacy(arg=nil)
+ set_or_return(
+ :privacy,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ],
+ )
+ end
+
+ # Validates that the Ohai::VERSION of the running chef-client matches one of the
+ # configured ohai_version statements in this cookbooks metadata.
+ #
+ # @raises [Chef::Exceptions::CookbookOhaiVersionMismatch] if the cookbook fails validation
+ def validate_ohai_version!
+ unless gem_dep_matches?("ohai", Gem::Version.new(Ohai::VERSION), *ohai_versions)
+ raise Exceptions::CookbookOhaiVersionMismatch.new(Ohai::VERSION, name, version, *ohai_versions)
+ end
+ end
+
+ # Validates that the Chef::VERSION of the running chef-client matches one of the
+ # configured chef_version statements in this cookbooks metadata.
+ #
+ # @raises [Chef::Exceptions::CookbookChefVersionMismatch] if the cookbook fails validation
+ def validate_chef_version!
+ unless gem_dep_matches?("chef", Gem::Version.new(Chef::VERSION), *chef_versions)
+ raise Exceptions::CookbookChefVersionMismatch.new(Chef::VERSION, name, version, *chef_versions)
+ end
+ end
+
private
+ # Helper to match a gem style version (ohai_version/chef_version) against a set of
+ # Gem::Dependency version constraints. If none are present, it always matches. if
+ # multiple are present, one must match. Returns false if none matches.
+ #
+ # @param what [String] the name of the constraint (e.g. 'chef' or 'ohai')
+ # @param version [String] the version to compare against the constraints
+ # @param deps [Array<Gem::Dependency>] Multiple Gem-style version constraints
+ # @return [Boolean] true if no constraints or a match, false if no match
+ def gem_dep_matches?(what, version, *deps)
+ # always match if we have no chef_version at all
+ return true unless deps.length > 0
+ # match if we match any of the chef_version lines
+ deps.any? { |dep| dep.match?(what, version) }
+ end
+
def run_validation
if name.nil?
@errors = ["The `name' attribute is required in cookbook metadata"]
@@ -603,7 +735,7 @@ class Chef
msg=<<-OBSOLETED
The dependency specification syntax you are using is no longer valid. You may not
specify more than one version constraint for a particular cookbook.
-Consult http://wiki.opscode.com/display/chef/Metadata for the updated syntax.
+Consult https://docs.chef.io/config_rb_metadata.html for the updated syntax.
Called by: #{caller_name} '#{dep_name}', #{version_constraints.map {|vc| vc.inspect}.join(", ")}
Called from:
@@ -614,7 +746,7 @@ OBSOLETED
end
def validate_version_constraint(caller_name, dep_name, constraint_str)
- Chef::VersionConstraint.new(constraint_str)
+ Chef::VersionConstraint::Platform.new(constraint_str)
rescue Chef::Exceptions::InvalidVersionConstraint => e
Log.debug(e)
@@ -622,7 +754,7 @@ OBSOLETED
The version constraint syntax you are using is not valid. If you recently
upgraded to Chef 0.10.0, be aware that you no may longer use "<<" and ">>" for
'less than' and 'greater than'; use '<' and '>' instead.
-Consult http://wiki.opscode.com/display/chef/Metadata for more information.
+Consult https://docs.chef.io/config_rb_metadata.html for more information.
Called by: #{caller_name} '#{dep_name}', '#{constraint_str}'
Called from:
@@ -727,7 +859,7 @@ INVALID
def handle_deprecated_constraints(specification)
specification.inject(Mash.new) do |acc, (cb, constraints)|
constraints = Array(constraints)
- acc[cb] = (constraints.empty? || constraints.size > 1) ? [] : constraints.first.gsub(/>>/, '>').gsub(/<</, '<')
+ acc[cb] = (constraints.empty? || constraints.size > 1) ? [] : constraints.first.gsub(/>>/, ">").gsub(/<</, "<")
acc
end
end
diff --git a/lib/chef/cookbook/remote_file_vendor.rb b/lib/chef/cookbook/remote_file_vendor.rb
index 9d895b168e..eefc25c93b 100644
--- a/lib/chef/cookbook/remote_file_vendor.rb
+++ b/lib/chef/cookbook/remote_file_vendor.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/cookbook/file_vendor'
+require "chef/cookbook/file_vendor"
class Chef
class Cookbook
@@ -48,7 +48,7 @@ class Chef
found_manifest_record = @manifest[segment].find {|manifest_record| manifest_record[:path] == filename }
raise "No such file #{filename} in #{@cookbook_name}" unless found_manifest_record
- cache_filename = File.join("cookbooks", @cookbook_name, found_manifest_record['path'])
+ cache_filename = File.join("cookbooks", @cookbook_name, found_manifest_record["path"])
# update valid_cache_entries so the upstream cache cleaner knows what
# we've used.
@@ -62,14 +62,14 @@ class Chef
# If the checksums are different between on-disk (current) and on-server
# (remote, per manifest), do the update. This will also execute if there
# is no current checksum.
- if current_checksum != found_manifest_record['checksum']
- raw_file = @rest.get_rest(found_manifest_record[:url], true)
+ if current_checksum != found_manifest_record["checksum"]
+ raw_file = @rest.get(found_manifest_record[:url], true)
Chef::Log.debug("Storing updated #{cache_filename} in the cache.")
Chef::FileCache.move_to(raw_file.path, cache_filename)
else
Chef::Log.debug("Not fetching #{cache_filename}, as the cache is up to date.")
- Chef::Log.debug("current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record['checksum']})")
+ Chef::Log.debug("Current checksum: #{current_checksum}; manifest checksum: #{found_manifest_record['checksum']})")
end
full_path_cache_filename = Chef::FileCache.load(cache_filename, false)
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index 1b96d0510b..9955bae6bb 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -1,6 +1,7 @@
-require 'chef/client'
-require 'chef/util/threaded_job_queue'
-require 'singleton'
+require "chef/client"
+require "chef/util/threaded_job_queue"
+require "chef/server_api"
+require "singleton"
class Chef
@@ -131,7 +132,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
@@ -244,14 +245,14 @@ class Chef
# === Returns
# Full path to the cached file as a String
def sync_file(file)
- cache_filename = File.join("cookbooks", file.cookbook.name, file.manifest_record['path'])
+ cache_filename = File.join("cookbooks", file.cookbook.name, file.manifest_record["path"])
mark_cached_file_valid(cache_filename)
# If the checksums are different between on-disk (current) and on-server
# (remote, per manifest), do the update. This will also execute if there
# is no current checksum.
- if !cached_copy_up_to_date?(cache_filename, file.manifest_record['checksum'])
- download_file(file.manifest_record['url'], cache_filename)
+ if !cached_copy_up_to_date?(cache_filename, file.manifest_record["checksum"])
+ download_file(file.manifest_record["url"], cache_filename)
@events.updated_cookbook_file(file.cookbook.name, cache_filename)
else
Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.")
@@ -274,7 +275,7 @@ class Chef
# downloaded to the path +destination+ which is relative to the Chef file
# cache root.
def download_file(url, destination)
- raw_file = server_api.get_rest(url, true)
+ raw_file = server_api.streaming_request(url)
Chef::Log.info("Storing updated #{destination} in the cache.")
cache.move_to(raw_file.path, destination)
@@ -286,7 +287,7 @@ class Chef
end
def server_api
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
end
diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb
index 96fc7e7b84..5528465f0c 100644
--- a/lib/chef/cookbook/syntax_check.rb
+++ b/lib/chef/cookbook/syntax_check.rb
@@ -16,12 +16,12 @@
# limitations under the License.
#
-require 'pathname'
-require 'stringio'
-require 'erubis'
-require 'chef/mixin/shell_out'
-require 'chef/mixin/checksum'
-require 'chef/util/path_helper'
+require "pathname"
+require "stringio"
+require "erubis"
+require "chef/mixin/shell_out"
+require "chef/mixin/checksum"
+require "chef/util/path_helper"
class Chef
class Cookbook
@@ -115,7 +115,7 @@ class Chef
def ruby_files
path = Chef::Util::PathHelper.escape_glob(cookbook_path)
- files = Dir[File.join(path, '**', '*.rb')]
+ files = Dir[File.join(path, "**", "*.rb")]
files = remove_ignored_files(files)
files = remove_uninteresting_ruby_files(files)
files
@@ -133,7 +133,7 @@ class Chef
end
def template_files
- remove_ignored_files Dir[File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), '**/templates/**', '*.erb')]
+ remove_ignored_files Dir[File.join(Chef::Util::PathHelper.escape_glob(cookbook_path), "**/templates/**", "*.erb")]
end
def untested_template_files
diff --git a/lib/chef/cookbook_loader.rb b/lib/chef/cookbook_loader.rb
index c05fedb141..0db136e00e 100644
--- a/lib/chef/cookbook_loader.rb
+++ b/lib/chef/cookbook_loader.rb
@@ -18,12 +18,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/config'
-require 'chef/exceptions'
-require 'chef/cookbook/cookbook_version_loader'
-require 'chef/cookbook_version'
-require 'chef/cookbook/chefignore'
-require 'chef/cookbook/metadata'
+require "chef/config"
+require "chef/exceptions"
+require "chef/cookbook/cookbook_version_loader"
+require "chef/cookbook_version"
+require "chef/cookbook/chefignore"
+require "chef/cookbook/metadata"
#
# CookbookLoader class loads the cookbooks lazily as read
@@ -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}; 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 10654a4945..8a1583bf0e 100644
--- a/lib/chef/cookbook_manifest.rb
+++ b/lib/chef/cookbook_manifest.rb
@@ -14,9 +14,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'forwardable'
-require 'chef/util/path_helper'
-require 'chef/log'
+require "forwardable"
+require "chef/util/path_helper"
+require "chef/log"
class Chef
@@ -127,14 +127,14 @@ class Chef
def to_hash
result = manifest.dup
- result['frozen?'] = frozen_version?
- result['chef_type'] = 'cookbook_version'
+ result["frozen?"] = frozen_version?
+ result["chef_type"] = "cookbook_version"
result.to_hash
end
def to_json(*a)
result = to_hash
- result['json_class'] = "Chef::CookbookVersion"
+ result["json_class"] = "Chef::CookbookVersion"
Chef::JSONCompat.to_json(result, *a)
end
@@ -169,7 +169,7 @@ class Chef
COOKBOOK_SEGMENTS.each do |segment|
next unless @manifest.has_key?(segment)
- filenames = @manifest[segment].map{|manifest_record| manifest_record['name']}
+ filenames = @manifest[segment].map{|manifest_record| manifest_record["name"]}
cookbook_version.replace_segment_filenames(segment, filenames)
end
@@ -193,8 +193,8 @@ class Chef
:templates => Array.new,
:resources => Array.new,
:providers => Array.new,
- :root_files => Array.new
- })
+ :root_files => Array.new,
+ },)
@checksums = {}
if !root_paths || root_paths.size == 0
@@ -215,8 +215,8 @@ class Chef
:name => file_name,
:path => path,
:checksum => csum,
- :specificity => specificity
- })
+ :specificity => specificity,
+ },)
manifest[segment] << rs
end
@@ -243,17 +243,17 @@ class Chef
parts = pathname.each_filename.take(2)
# Check if path is actually under root_path
- next if parts[0] == '..'
+ next if parts[0] == ".."
if segment == :templates || segment == :files
# Check if pathname looks like files/foo or templates/foo (unscoped)
if pathname.each_filename.to_a.length == 2
# Use root_default in case the same path exists at root_default and default
- return [ pathname.to_s, 'root_default' ]
+ return [ pathname.to_s, "root_default" ]
else
return [ pathname.to_s, parts[1] ]
end
else
- return [ pathname.to_s, 'default' ]
+ return [ pathname.to_s, "default" ]
end
end
Chef::Log.error("Cookbook file #{segment_file} not under cookbook root paths #{root_paths.inspect}.")
diff --git a/lib/chef/cookbook_site_streaming_uploader.rb b/lib/chef/cookbook_site_streaming_uploader.rb
index 9e7a55c772..ccd476f6e9 100644
--- a/lib/chef/cookbook_site_streaming_uploader.rb
+++ b/lib/chef/cookbook_site_streaming_uploader.rb
@@ -2,7 +2,7 @@
# Author:: Stanislav Vitvitskiy
# Author:: Nuo Yan (nuo@opscode.com)
# Author:: Christopher Walters (<cw@opscode.com>)
-# Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2009, 2010-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,20 +18,20 @@
# limitations under the License.
#
-require 'uri'
-require 'net/http'
-require 'mixlib/authentication/signedheaderauth'
-require 'openssl'
+require "uri"
+require "net/http"
+require "mixlib/authentication/signedheaderauth"
+require "openssl"
class Chef
# == Chef::CookbookSiteStreamingUploader
# A streaming multipart HTTP upload implementation. Used to upload cookbooks
- # (in tarball form) to http://cookbooks.opscode.com
+ # (in tarball form) to https://supermarket.chef.io
#
# inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
class CookbookSiteStreamingUploader
- DefaultHeaders = { 'accept' => 'application/json', 'x-chef-version' => ::Chef::VERSION }
+ DefaultHeaders = { "accept" => "application/json", "x-chef-version" => ::Chef::VERSION }
class << self
@@ -73,7 +73,7 @@ class Chef
end
def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
- boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ'
+ boundary = "----RubyMultipartClient" + rand(1000000).to_s + "ZZZZZ"
parts = []
content_file = nil
@@ -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
@@ -138,16 +138,11 @@ class Chef
Net::HTTP::Post.new(url.path, headers)
end
req.content_length = body_stream.size
- req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty?
+ 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
@@ -222,7 +206,7 @@ class Chef
def read(how_much, dst_buf = nil)
if @part_no >= @parts.size
- dst_buf.replace('') if dst_buf
+ dst_buf.replace("") if dst_buf
return dst_buf
end
@@ -245,14 +229,14 @@ class Chef
next_part = read(how_much_next_part)
result = current_part + if next_part
next_part
- else
- ''
- end
+ else
+ ""
+ end
else
@part_offset += how_much_current_part
result = current_part
end
- dst_buf ? dst_buf.replace(result || '') : result
+ dst_buf ? dst_buf.replace(result || "") : result
end
end
diff --git a/lib/chef/cookbook_uploader.rb b/lib/chef/cookbook_uploader.rb
index f24ce2cd56..a9f255df43 100644
--- a/lib/chef/cookbook_uploader.rb
+++ b/lib/chef/cookbook_uploader.rb
@@ -1,14 +1,15 @@
-require 'set'
-require 'chef/exceptions'
-require 'chef/knife/cookbook_metadata'
-require 'chef/digester'
-require 'chef/cookbook_manifest'
-require 'chef/cookbook_version'
-require 'chef/cookbook/syntax_check'
-require 'chef/cookbook/file_system_file_vendor'
-require 'chef/util/threaded_job_queue'
-require 'chef/sandbox'
+require "set"
+require "chef/exceptions"
+require "chef/knife/cookbook_metadata"
+require "chef/digester"
+require "chef/cookbook_manifest"
+require "chef/cookbook_version"
+require "chef/cookbook/syntax_check"
+require "chef/cookbook/file_system_file_vendor"
+require "chef/util/threaded_job_queue"
+require "chef/sandbox"
+require "chef/server_api"
class Chef
class CookbookUploader
@@ -31,7 +32,7 @@ class Chef
# uploading the cookbook. This allows frozen CookbookVersion
# documents on the server to be overwritten (otherwise a 409 is
# returned by the server)
- # * :rest A Chef::REST object that you have configured the way you like it.
+ # * :rest A Chef::ServerAPI object that you have configured the way you like it.
# If you don't provide this, one will be created using the values
# in Chef::Config.
# * :concurrency An integer that decided how many threads will be used to
@@ -39,7 +40,7 @@ class Chef
def initialize(cookbooks, opts={})
@opts = opts
@cookbooks = Array(cookbooks)
- @rest = opts[:rest] || Chef::REST.new(Chef::Config[:chef_server_url])
+ @rest = opts[:rest] || Chef::ServerAPI.new(Chef::Config[:chef_server_url])
@concurrency = opts[:concurrency] || 10
@policy_mode = opts[:policy_mode] || false
end
@@ -64,11 +65,11 @@ class Chef
checksums_to_upload = Set.new
# upload the new checksums and commit the sandbox
- new_sandbox['checksums'].each do |checksum, info|
- if info['needs_upload'] == true
+ new_sandbox["checksums"].each do |checksum, info|
+ if info["needs_upload"] == true
checksums_to_upload << checksum
Chef::Log.info("Uploading #{checksum_files[checksum]} (checksum hex = #{checksum}) to #{info['url']}")
- queue << uploader_function_for(checksum_files[checksum], checksum, info['url'], checksums_to_upload)
+ queue << uploader_function_for(checksum_files[checksum], checksum, info["url"], checksums_to_upload)
else
Chef::Log.debug("#{checksum_files[checksum]} has not changed")
end
@@ -76,7 +77,7 @@ class Chef
queue.process(@concurrency)
- sandbox_url = new_sandbox['uri']
+ sandbox_url = new_sandbox["uri"]
Chef::Log.debug("Committing sandbox")
# Retry if S3 is claims a checksum doesn't exist (the eventual
# in eventual consistency)
@@ -122,7 +123,7 @@ class Chef
file_contents = File.open(file, "rb") {|f| f.read}
# Custom headers. 'content-type' disables JSON serialization of the request body.
- headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, "accept" => 'application/json' }
+ headers = { "content-type" => "application/x-binary", "content-md5" => checksum64, "accept" => "application/json" }
begin
rest.put(url, file_contents, headers)
diff --git a/lib/chef/cookbook_version.rb b/lib/chef/cookbook_version.rb
index 8d302eeec2..28f817c8ba 100644
--- a/lib/chef/cookbook_version.rb
+++ b/lib/chef/cookbook_version.rb
@@ -4,7 +4,7 @@
# Author:: Tim Hinderliter (<tim@opscode.com>)
# Author:: Seth Falcon (<seth@opscode.com>)
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright 2008-2011 Opscode, Inc.
+# Copyright:: Copyright 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,12 +19,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/log'
-require 'chef/cookbook/file_vendor'
-require 'chef/cookbook/metadata'
-require 'chef/version_class'
-require 'chef/digester'
-require 'chef/cookbook_manifest'
+require "chef/log"
+require "chef/cookbook/file_vendor"
+require "chef/cookbook/metadata"
+require "chef/version_class"
+require "chef/digester"
+require "chef/cookbook_manifest"
+require "chef/server_api"
class Chef
@@ -51,12 +52,12 @@ class Chef
attr_accessor :metadata_filenames
def status=(new_status)
- Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+ Chef.log_deprecation("Deprecated method `status' called. This method will be removed.")
@status = new_status
end
def status
- Chef::Log.deprecation("Deprecated method `status' called from #{caller(1).first}. This method will be removed")
+ Chef.log_deprecation("Deprecated method `status' called. This method will be removed.")
@status
end
@@ -299,15 +300,15 @@ class Chef
if segment == :files || segment == :templates
error_message = "Cookbook '#{name}' (#{version}) does not contain a file at any of these locations:\n"
error_locations = if filename.is_a?(Array)
- filename.map{|name| " #{File.join(segment.to_s, name)}"}
- else
- [
- " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}",
- " #{segment}/#{node[:platform]}/#{filename}",
- " #{segment}/default/#{filename}",
- " #{segment}/#{filename}",
- ]
- end
+ filename.map{|name| " #{File.join(segment.to_s, name)}"}
+ else
+ [
+ " #{segment}/#{node[:platform]}-#{node[:platform_version]}/#{filename}",
+ " #{segment}/#{node[:platform]}/#{filename}",
+ " #{segment}/default/#{filename}",
+ " #{segment}/#{filename}",
+ ]
+ end
error_message << error_locations.join("\n")
existing_files = segment_filenames(segment)
# Strip the root_dir prefix off all files for readability
@@ -326,10 +327,10 @@ class Chef
def preferred_filename_on_disk_location(node, segment, filename, current_filepath=nil)
manifest_record = preferred_manifest_record(node, segment, filename)
- if current_filepath && (manifest_record['checksum'] == self.class.checksum_cookbook_file(current_filepath))
+ if current_filepath && (manifest_record["checksum"] == self.class.checksum_cookbook_file(current_filepath))
nil
else
- file_vendor.get_filename(manifest_record['path'])
+ file_vendor.get_filename(manifest_record["path"])
end
end
@@ -353,7 +354,7 @@ class Chef
# cookbook find them by filespecificity again. but it's the shortest
# path to "success" for now.
if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
- specificity_dirname = $1
+ specificity_dirname = $1
non_specific_path = manifest_record_path[/#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)}\/(.+)$/, 1]
# Record the specificity_dirname only if it's in the list of
# valid preferences
@@ -384,11 +385,11 @@ class Chef
# extract the preference part from the path.
if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
- # Note the specificy_dirname includes the segment and
- # dirname argument as above, which is what
- # preferences_for_path returns. It could be
- # "files/ubuntu-9.10/dirname", for example.
- specificity_dirname = $1
+ # Note the specificy_dirname includes the segment and
+ # dirname argument as above, which is what
+ # preferences_for_path returns. It could be
+ # "files/ubuntu-9.10/dirname", for example.
+ specificity_dirname = $1
# Record the specificity_dirname only if it's in the list of
# valid preferences
@@ -412,54 +413,60 @@ class Chef
# only files and templates can be platform-specific
if segment.to_sym == :files || segment.to_sym == :templates
relative_search_path = if path.is_a?(Array)
- path
- else
- begin
- platform, version = Chef::Platform.find_platform_and_version(node)
- rescue ArgumentError => e
- # Skip platform/version if they were not found by find_platform_and_version
- if e.message =~ /Cannot find a (?:platform|version)/
- platform = "/unknown_platform/"
- version = "/unknown_platform_version/"
- else
- raise
- end
- end
-
- fqdn = node[:fqdn]
-
- # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ]
- search_versions = []
- parts = version.to_s.split('.')
-
- parts.size.times do
- search_versions << parts.join('.')
- parts.pop
- end
-
- # Most specific to least specific places to find the path
- search_path = [ File.join("host-#{fqdn}", path) ]
- search_versions.each do |v|
- search_path << File.join("#{platform}-#{v}", path)
- end
- search_path << File.join(platform.to_s, path)
- search_path << File.join("default", path)
- search_path << path
-
- search_path
- end
+ path
+ else
+ begin
+ platform, version = Chef::Platform.find_platform_and_version(node)
+ rescue ArgumentError => e
+ # Skip platform/version if they were not found by find_platform_and_version
+ if e.message =~ /Cannot find a (?:platform|version)/
+ platform = "/unknown_platform/"
+ version = "/unknown_platform_version/"
+ else
+ raise
+ end
+ end
+
+ fqdn = node[:fqdn]
+
+ # Break version into components, eg: "5.7.1" => [ "5.7.1", "5.7", "5" ]
+ search_versions = []
+ parts = version.to_s.split(".")
+
+ parts.size.times do
+ search_versions << parts.join(".")
+ parts.pop
+ end
+
+ # Most specific to least specific places to find the path
+ search_path = [ File.join("host-#{fqdn}", path) ]
+ search_versions.each do |v|
+ search_path << File.join("#{platform}-#{v}", path)
+ end
+ search_path << File.join(platform.to_s, path)
+ search_path << File.join("default", path)
+ search_path << path
+
+ search_path
+ end
relative_search_path.map {|relative_path| File.join(segment.to_s, relative_path)}
else
- [File.join(segment, path)]
+ if segment.to_sym == :root_files
+ [path]
+ else
+ [File.join(segment, path)]
+ end
end
end
private :preferences_for_path
- def self.json_create(o)
- cookbook_version = new(o["cookbook_name"])
+ def self.from_hash(o)
+ cookbook_version = new(o["cookbook_name"] || 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"] if o.key?("identifier")
# We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>)
cookbook_version.manifest["metadata"] = Chef::JSONCompat.from_json(Chef::JSONCompat.to_json(cookbook_version.metadata))
@@ -468,19 +475,18 @@ class Chef
cookbook_version
end
+ def self.json_create(o)
+ from_hash(o)
+ 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
+ from_hash(o)
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.")
rendered_manifest = manifest.dup
COOKBOOK_SEGMENTS.each do |segment|
@@ -539,22 +545,22 @@ class Chef
end
def self.chef_server_rest
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def destroy
- chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
+ chef_server_rest.delete("cookbooks/#{name}/#{version}")
self
end
def self.load(name, version="_latest")
version = "_latest" if version == "latest"
- chef_server_rest.get_rest("cookbooks/#{name}/#{version}")
+ from_hash(chef_server_rest.get("cookbooks/#{name}/#{version}"))
end
# The API returns only a single version of each cookbook in the result from the cookbooks method
def self.list
- chef_server_rest.get_rest('cookbooks')
+ chef_server_rest.get("cookbooks")
end
# Alias latest_cookbooks as list
@@ -563,7 +569,7 @@ class Chef
end
def self.list_all_versions
- chef_server_rest.get_rest('cookbooks?num_versions=all')
+ chef_server_rest.get("cookbooks?num_versions=all")
end
##
@@ -573,7 +579,7 @@ class Chef
# [String]:: Array of cookbook versions, which are strings like 'x.y.z'
# nil:: if the cookbook doesn't exist. an error will also be logged.
def self.available_versions(cookbook_name)
- chef_server_rest.get_rest("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb|
+ chef_server_rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb|
cb["version"]
end
rescue Net::HTTPServerException => e
@@ -609,7 +615,7 @@ class Chef
# For each filename, produce a mapping of base filename (i.e. recipe name
# or attribute file) to on disk location
def filenames_by_name(filenames)
- filenames.select{|filename| filename =~ /\.rb$/}.inject({}){|memo, filename| memo[File.basename(filename, '.rb')] = filename ; memo }
+ filenames.select{|filename| filename =~ /\.rb$/}.inject({}){|memo, filename| memo[File.basename(filename, ".rb")] = filename ; memo }
end
def file_vendor
diff --git a/lib/chef/daemon.rb b/lib/chef/daemon.rb
index e5abca29d8..f73b1babca 100644
--- a/lib/chef/daemon.rb
+++ b/lib/chef/daemon.rb
@@ -17,9 +17,9 @@
# I love you Merb (lib/merb-core/server.rb)
-require 'chef/config'
-require 'chef/run_lock'
-require 'etc'
+require "chef/config"
+require "chef/run_lock"
+require "etc"
class Chef
class Daemon
diff --git a/lib/chef/data_bag.rb b/lib/chef/data_bag.rb
index 8475774fa1..66771d325f 100644
--- a/lib/chef/data_bag.rb
+++ b/lib/chef/data_bag.rb
@@ -18,12 +18,13 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/mixin/params_validate'
-require 'chef/mixin/from_file'
-require 'chef/data_bag_item'
-require 'chef/mash'
-require 'chef/json_compat'
+require "chef/config"
+require "chef/mixin/params_validate"
+require "chef/mixin/from_file"
+require "chef/data_bag_item"
+require "chef/mash"
+require "chef/json_compat"
+require "chef/server_api"
class Chef
class DataBag
@@ -43,7 +44,7 @@ class Chef
# Create a new Chef::DataBag
def initialize(chef_server_rest: nil)
- @name = ''
+ @name = ""
@chef_server_rest = chef_server_rest
end
@@ -51,15 +52,15 @@ class Chef
set_or_return(
:name,
arg,
- :regex => VALID_NAME
+ :regex => VALID_NAME,
)
end
def to_hash
result = {
- 'name' => @name,
- 'json_class' => self.class.name,
- 'chef_type' => 'data_bag',
+ "name" => @name,
+ "json_class" => self.class.name,
+ "chef_type" => "data_bag",
}
result
end
@@ -70,15 +71,19 @@ class Chef
end
def chef_server_rest
- @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def self.chef_server_rest
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
# Create a Chef::Role from JSON
def self.json_create(o)
+ from_hash(o)
+ end
+
+ def self.from_hash(o)
bag = new
bag.name(o["name"])
bag
@@ -104,7 +109,7 @@ class Chef
response
end
else
- Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data")
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data")
end
end
end
@@ -120,7 +125,7 @@ class Chef
end
Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(path, name.to_s), "*.json")).inject({}) do |bag, f|
- item = Chef::JSONCompat.from_json(IO.read(f))
+ item = Chef::JSONCompat.parse(IO.read(f))
# Check if we have multiple items with similar names (ids) and raise if their content differs
if data_bag.has_key?(item["id"]) && data_bag[item["id"]] != item
@@ -132,19 +137,19 @@ class Chef
end
return data_bag
else
- Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data/#{name}")
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{name}")
end
end
def destroy
- chef_server_rest.delete_rest("data/#{@name}")
+ chef_server_rest.delete("data/#{@name}")
end
# Save the Data Bag via RESTful API
def save
begin
if Chef::Config[:why_run]
- Chef::Log.warn("In whyrun mode, so NOT performing data bag save.")
+ Chef::Log.warn("In why-run mode, so NOT performing data bag save.")
else
create
end
@@ -156,7 +161,7 @@ class Chef
#create a data bag via RESTful API
def create
- chef_server_rest.post_rest("data", self)
+ chef_server_rest.post("data", self)
self
end
diff --git a/lib/chef/data_bag_item.rb b/lib/chef/data_bag_item.rb
index 9f92e26c50..8688693568 100644
--- a/lib/chef/data_bag_item.rb
+++ b/lib/chef/data_bag_item.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Nuo Yan (<nuo@opscode.com>)
# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2009-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,14 +18,15 @@
# limitations under the License.
#
-require 'forwardable'
+require "forwardable"
-require 'chef/config'
-require 'chef/mixin/params_validate'
-require 'chef/mixin/from_file'
-require 'chef/data_bag'
-require 'chef/mash'
-require 'chef/json_compat'
+require "chef/config"
+require "chef/mixin/params_validate"
+require "chef/mixin/from_file"
+require "chef/data_bag"
+require "chef/mash"
+require "chef/server_api"
+require "chef/json_compat"
class Chef
class DataBagItem
@@ -58,11 +59,11 @@ class Chef
end
def chef_server_rest
- @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def self.chef_server_rest
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def raw_data
@@ -85,7 +86,7 @@ class Chef
set_or_return(
:data_bag,
arg,
- :regex => /^[\-[:alnum:]_]+$/
+ :regex => /^[\-[:alnum:]_]+$/,
)
end
@@ -94,10 +95,10 @@ class Chef
end
def object_name
- raise Exceptions::ValidationFailed, "You must have an 'id' or :id key in the raw data" unless raw_data.has_key?('id')
+ raise Exceptions::ValidationFailed, "You must have an 'id' or :id key in the raw data" unless raw_data.has_key?("id")
raise Exceptions::ValidationFailed, "You must have declared what bag this item belongs to!" unless data_bag
- id = raw_data['id']
+ id = raw_data["id"]
"data_bag_item_#{data_bag}_#{id}"
end
@@ -119,28 +120,29 @@ class Chef
"json_class" => self.class.name,
"chef_type" => "data_bag_item",
"data_bag" => data_bag,
- "raw_data" => raw_data
+ "raw_data" => raw_data,
}
Chef::JSONCompat.to_json(result, *a)
end
def self.from_hash(h)
+ h.delete("chef_type")
+ h.delete("json_class")
+ h.delete("name")
+
item = new
- item.raw_data = h
+ item.data_bag(h.delete("data_bag")) if h.key?("data_bag")
+ if h.key?("raw_data")
+ item.raw_data = Mash.new(h["raw_data"])
+ else
+ item.raw_data = h
+ end
item
end
# Create a Chef::DataBagItem from JSON
def self.json_create(o)
- bag_item = new
- bag_item.data_bag(o["data_bag"])
- o.delete("data_bag")
- o.delete("chef_type")
- o.delete("json_class")
- o.delete("name")
-
- bag_item.raw_data = Mash.new(o["raw_data"])
- bag_item
+ from_hash(o)
end
# Load a Data Bag Item by name via either the RESTful API or local data_bag_path if run in solo mode
@@ -149,7 +151,7 @@ class Chef
bag = Chef::DataBag.load(data_bag)
item = bag[name]
else
- item = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("data/#{data_bag}/#{name}")
+ item = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("data/#{data_bag}/#{name}")
end
if item.kind_of?(DataBagItem)
@@ -161,29 +163,29 @@ class Chef
end
end
- def destroy(data_bag=data_bag(), databag_item=name)
- chef_server_rest.delete_rest("data/#{data_bag}/#{databag_item}")
+ def destroy(data_bag=self.data_bag(), databag_item=name)
+ chef_server_rest.delete("data/#{data_bag}/#{databag_item}")
end
# Save this Data Bag Item via RESTful API
- def save(item_id=@raw_data['id'])
+ def save(item_id=@raw_data["id"])
r = chef_server_rest
begin
if Chef::Config[:why_run]
- Chef::Log.warn("In whyrun mode, so NOT performing data bag item save.")
+ Chef::Log.warn("In why-run mode, so NOT performing data bag item save.")
else
- r.put_rest("data/#{data_bag}/#{item_id}", self)
+ r.put("data/#{data_bag}/#{item_id}", self)
end
rescue Net::HTTPServerException => e
raise e unless e.response.code == "404"
- r.post_rest("data/#{data_bag}", self)
+ r.post("data/#{data_bag}", self)
end
self
end
# Create this Data Bag Item via RESTful API
def create
- chef_server_rest.post_rest("data/#{data_bag}", self)
+ chef_server_rest.post("data/#{data_bag}", self)
self
end
@@ -208,7 +210,7 @@ class Chef
end
def id
- @raw_data['id']
+ @raw_data["id"]
end
end
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..eaa14b6501 100644
--- a/lib/chef/deprecation/mixin/template.rb
+++ b/lib/chef/deprecation/mixin/template.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'tempfile'
-require 'erubis'
+require "tempfile"
+require "erubis"
class Chef
module Deprecation
@@ -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..d37bc4241f 100644
--- a/lib/chef/deprecation/provider/file.rb
+++ b/lib/chef/deprecation/provider/file.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/util/path_helper'
+require "chef/util/path_helper"
class Chef
module Deprecation
@@ -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
@@ -59,7 +59,7 @@ class Chef
@current_resource.path
else
suppress_resource_reporting = true # suppress big diffs going to resource reporting service
- tempfile = Tempfile.new('chef-tempfile')
+ tempfile = Tempfile.new("chef-tempfile")
tempfile.path
end
diff --git a/lib/chef/deprecation/provider/remote_directory.rb b/lib/chef/deprecation/provider/remote_directory.rb
new file mode 100644
index 0000000000..ece5ca0945
--- /dev/null
+++ b/lib/chef/deprecation/provider/remote_directory.rb
@@ -0,0 +1,52 @@
+#
+# Author:: Serdar Sutay (<serdar@opscode.com>)
+# Copyright:: Copyright (c) 2013-2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class Chef
+ module Deprecation
+ module Provider
+ module RemoteDirectory
+
+ def directory_root_in_cookbook_cache
+ Chef.log_deprecation "the Chef::Provider::RemoteDirectory#directory_root_in_cookbook_cache method is deprecated"
+
+ @directory_root_in_cookbook_cache ||=
+ begin
+ cookbook = run_context.cookbook_collection[resource_cookbook]
+ cookbook.preferred_filename_on_disk_location(node, :files, source, path)
+ end
+ end
+
+ # List all excluding . and ..
+ def ls(path)
+ files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), "**", "*"),
+ ::File::FNM_DOTMATCH)
+
+ # Remove current directory and previous directory
+ files = files.reject do |name|
+ basename = Pathname.new(name).basename().to_s
+ [".", ".."].include?(basename)
+ end
+
+ # Clean all the paths... this is required because of the join
+ files.map {|f| Chef::Util::PathHelper.cleanpath(f)}
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/deprecation/provider/remote_file.rb b/lib/chef/deprecation/provider/remote_file.rb
index 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..c95690f2cc 100644
--- a/lib/chef/deprecation/provider/template.rb
+++ b/lib/chef/deprecation/provider/template.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/deprecation/mixin/template'
+require "chef/deprecation/mixin/template"
class Chef
module Deprecation
@@ -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..0b1ec2d5ed 100644
--- a/lib/chef/deprecation/warnings.rb
+++ b/lib/chef/deprecation/warnings.rb
@@ -25,10 +25,9 @@ class Chef
m = instance_method(name)
define_method(name) do |*args|
message = []
- message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 12."
- message << "Please update your cookbooks accordingly. Accessed from:"
- caller[0..3].each {|l| message << l}
- Chef::Log.deprecation message
+ message << "Method '#{name}' of '#{self.class}' is deprecated. It will be removed in Chef 13."
+ message << "Please update your cookbooks accordingly."
+ Chef.log_deprecation(message)
super(*args)
end
end
diff --git a/lib/chef/digester.rb b/lib/chef/digester.rb
index 75c4e76859..a90003c3cd 100644
--- a/lib/chef/digester.rb
+++ b/lib/chef/digester.rb
@@ -18,8 +18,8 @@
# limitations under the License.
#
-require 'openssl'
-require 'singleton'
+require "openssl"
+require "singleton"
class Chef
class Digester
@@ -38,7 +38,11 @@ class Chef
end
def generate_checksum(file)
- checksum_file(file, OpenSSL::Digest::SHA256.new)
+ if file.is_a?(StringIO)
+ checksum_io(file, OpenSSL::Digest::SHA256.new)
+ else
+ checksum_file(file, OpenSSL::Digest::SHA256.new)
+ end
end
def self.generate_md5_checksum_for_file(*args)
@@ -56,7 +60,7 @@ class Chef
private
def checksum_file(file, digest)
- File.open(file, 'rb') { |f| checksum_io(f, digest) }
+ File.open(file, "rb") { |f| checksum_io(f, digest) }
end
def checksum_io(io, digest)
diff --git a/lib/chef/dsl.rb b/lib/chef/dsl.rb
index 7717d99113..1fa0099e91 100644
--- a/lib/chef/dsl.rb
+++ b/lib/chef/dsl.rb
@@ -1,6 +1,6 @@
-require 'chef/dsl/recipe'
-require 'chef/dsl/platform_introspection'
-require 'chef/dsl/data_query'
-require 'chef/dsl/include_recipe'
-require 'chef/dsl/include_attribute'
-require 'chef/dsl/registry_helper'
+require "chef/dsl/recipe"
+require "chef/dsl/platform_introspection"
+require "chef/dsl/data_query"
+require "chef/dsl/include_recipe"
+require "chef/dsl/include_attribute"
+require "chef/dsl/registry_helper"
diff --git a/lib/chef/dsl/audit.rb b/lib/chef/dsl/audit.rb
index d1bf09b313..e6ae3b681e 100644
--- a/lib/chef/dsl/audit.rb
+++ b/lib/chef/dsl/audit.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/exceptions'
+require "chef/exceptions"
class Chef
module DSL
@@ -40,7 +40,7 @@ class Chef
cookbook_name: cookbook_name,
cookbook_version: self.run_context.cookbook_collection[cookbook_name].version,
recipe_name: self.recipe_name,
- line_number: block.source_location[1]
+ line_number: block.source_location[1],
}
run_context.audits[name] = Struct.new(:args, :block, :metadata).new(args, block, metadata)
diff --git a/lib/chef/dsl/chef_provisioning.rb b/lib/chef/dsl/chef_provisioning.rb
new file mode 100644
index 0000000000..27458dc04a
--- /dev/null
+++ b/lib/chef/dsl/chef_provisioning.rb
@@ -0,0 +1,57 @@
+#
+# 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
+ module DSL
+ # Lazy activation for the chef-provisioning gem. Specifically, we set up methods for
+ # each resource and DSL method in chef-provisioning which, when invoked, will
+ # require 'chef-provisioning' (which will define the actual method) and then call the
+ # method chef-provisioning defined.
+ module ChefProvisioning
+ %w{
+ add_machine_options
+ current_image_options
+ current_machine_options
+ load_balancer
+ machine_batch
+ machine_execute
+ machine_file
+ machine_image
+ machine
+ with_driver
+ with_image_options
+ with_machine_options
+ }.each do |method_name|
+ eval(<<-EOM, binding, __FILE__, __LINE__+1)
+ def #{method_name}(*args, &block)
+ Chef::DSL::ChefProvisioning.load_chef_provisioning
+ self.#{method_name}(*args, &block)
+ end
+ EOM
+ end
+
+ def self.load_chef_provisioning
+ # Remove all chef-provisioning methods; they will be added back in by chef-provisioning
+ public_instance_methods(false).each do |method_name|
+ remove_method(method_name)
+ end
+ require "chef/provisioning"
+ end
+ end
+ end
+end
diff --git a/lib/chef/dsl/cheffish.rb b/lib/chef/dsl/cheffish.rb
new file mode 100644
index 0000000000..019dadd3f2
--- /dev/null
+++ b/lib/chef/dsl/cheffish.rb
@@ -0,0 +1,64 @@
+#
+# 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
+ module DSL
+ # Lazy activation for the cheffish gem. Specifically, we set up methods for
+ # each resource and DSL method in cheffish which, when invoked, will
+ # require 'cheffish' (which will define the actual method) and then call the
+ # method cheffish defined.
+ module Cheffish
+ %w{
+ chef_acl
+ chef_client
+ chef_container
+ chef_data_bag
+ chef_environment
+ chef_group
+ chef_mirror
+ chef_node
+ chef_organization
+ chef_role
+ chef_user
+ private_key
+ public_key
+ with_chef_data_bag
+ with_chef_environment
+ with_chef_data_bag_item_encryption
+ with_chef_server
+ with_chef_local_server
+ get_private_key
+ }.each do |method_name|
+ eval(<<-EOM, binding, __FILE__, __LINE__+1)
+ def #{method_name}(*args, &block)
+ Chef::DSL::Cheffish.load_cheffish
+ self.#{method_name}(*args, &block)
+ end
+ EOM
+ end
+
+ def self.load_cheffish
+ # Remove all cheffish methods; they will be added back in by cheffish
+ public_instance_methods(false).each do |method_name|
+ remove_method(method_name)
+ end
+ require "cheffish"
+ end
+ end
+ end
+end
diff --git a/lib/chef/dsl/data_query.rb b/lib/chef/dsl/data_query.rb
index e36784271a..a61d5f1c64 100644
--- a/lib/chef/dsl/data_query.rb
+++ b/lib/chef/dsl/data_query.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/search/query'
-require 'chef/data_bag'
-require 'chef/data_bag_item'
-require 'chef/encrypted_data_bag_item'
-require 'chef/encrypted_data_bag_item/check_encrypted'
+require "chef/search/query"
+require "chef/data_bag"
+require "chef/data_bag_item"
+require "chef/encrypted_data_bag_item"
+require "chef/encrypted_data_bag_item/check_encrypted"
class Chef
module DSL
@@ -86,4 +86,4 @@ end
# **DEPRECATED**
# This used to be part of chef/mixin/language. Load the file to activate the deprecation code.
-require 'chef/mixin/language'
+require "chef/mixin/language"
diff --git a/lib/chef/dsl/declare_resource.rb b/lib/chef/dsl/declare_resource.rb
new file mode 100644
index 0000000000..6f9f913392
--- /dev/null
+++ b/lib/chef/dsl/declare_resource.rb
@@ -0,0 +1,108 @@
+#--
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009-2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR 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
+ module DSL
+ module DeclareResource
+
+ #
+ # Instantiates a resource (via #build_resource), then adds it to the
+ # resource collection. Note that resource classes are looked up directly,
+ # so this will create the resource you intended even if the method name
+ # corresponding to that resource has been overridden.
+ #
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
+ # @param created_at [String] The caller of the resource. Use `caller[0]`
+ # to get the caller of your function. Defaults to the caller of this
+ # function.
+ # @param resource_attrs_block A block that lets you set attributes of the
+ # resource (it is instance_eval'd on the resource instance).
+ #
+ # @return [Chef::Resource] The new resource.
+ #
+ # @example
+ # declare_resource(:file, '/x/y.txy', caller[0]) do
+ # action :delete
+ # end
+ # # Equivalent to
+ # file '/x/y.txt' do
+ # action :delete
+ # end
+ #
+ def declare_resource(type, name, created_at=nil, run_context: self.run_context, create_if_missing: false, &resource_attrs_block)
+ created_at ||= caller[0]
+
+ if create_if_missing
+ begin
+ resource = run_context.resource_collection.find(type => name)
+ return resource
+ rescue Chef::Exceptions::ResourceNotFound
+ end
+ end
+
+ resource = build_resource(type, name, created_at, &resource_attrs_block)
+
+ run_context.resource_collection.insert(resource, resource_type: type, instance_name: name)
+ resource
+ end
+
+ #
+ # Instantiate a resource of the given +type+ with the given +name+ and
+ # attributes as given in the +resource_attrs_block+.
+ #
+ # The resource is NOT added to the resource collection.
+ #
+ # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
+ # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
+ # @param created_at [String] The caller of the resource. Use `caller[0]`
+ # to get the caller of your function. Defaults to the caller of this
+ # function.
+ # @param resource_attrs_block A block that lets you set attributes of the
+ # resource (it is instance_eval'd on the resource instance).
+ #
+ # @return [Chef::Resource] The new resource.
+ #
+ # @example
+ # build_resource(:file, '/x/y.txy', caller[0]) do
+ # action :delete
+ # end
+ #
+ def build_resource(type, name, created_at=nil, run_context: self.run_context, &resource_attrs_block)
+ created_at ||= caller[0]
+ Thread.exclusive do
+ require "chef/resource_builder" unless defined?(Chef::ResourceBuilder)
+ end
+
+ Chef::ResourceBuilder.new(
+ type: type,
+ name: name,
+ created_at: created_at,
+ params: @params,
+ run_context: run_context,
+ cookbook_name: cookbook_name,
+ recipe_name: recipe_name,
+ enclosing_provider: self.is_a?(Chef::Provider) ? self : nil,
+ ).build(&resource_attrs_block)
+ end
+ end
+ 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/include_attribute.rb b/lib/chef/dsl/include_attribute.rb
index 3a95ce7268..6f70b4c893 100644
--- a/lib/chef/dsl/include_attribute.rb
+++ b/lib/chef/dsl/include_attribute.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/log'
+require "chef/log"
class Chef
module DSL
@@ -58,6 +58,6 @@ end
# **DEPRECATED**
# This used to be part of chef/mixin/language_include_attribute. Load the file to activate the deprecation code.
-require 'chef/mixin/language_include_attribute'
+require "chef/mixin/language_include_attribute"
diff --git a/lib/chef/dsl/include_recipe.rb b/lib/chef/dsl/include_recipe.rb
index 5ea1075e67..e51f323c60 100644
--- a/lib/chef/dsl/include_recipe.rb
+++ b/lib/chef/dsl/include_recipe.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/log'
+require "chef/log"
class Chef
module DSL
@@ -41,4 +41,4 @@ end
# **DEPRECATED**
# This used to be part of chef/mixin/language_include_recipe. Load the file to activate the deprecation code.
-require 'chef/mixin/language_include_recipe'
+require "chef/mixin/language_include_recipe"
diff --git a/lib/chef/dsl/platform_introspection.rb b/lib/chef/dsl/platform_introspection.rb
index 2a52010a70..8518809819 100644
--- a/lib/chef/dsl/platform_introspection.rb
+++ b/lib/chef/dsl/platform_introspection.rb
@@ -50,7 +50,7 @@ class Chef
def value_for_node(node)
platform, version = node[:platform].to_s, node[:platform_version].to_s
- # Check if we match a version constraint via Chef::VersionConstraint and Chef::Version::Platform
+ # Check if we match a version constraint via Chef::VersionConstraint::Platform and Chef::Version::Platform
matched_value = match_versions(node)
if @values.key?(platform) && @values[platform].key?(version)
@values[platform][version]
@@ -76,11 +76,11 @@ class Chef
keys = @values[platform].keys
keys.each do |k|
begin
- if Chef::VersionConstraint.new(k).include?(node_version)
+ if Chef::VersionConstraint::Platform.new(k).include?(node_version)
key_matches << k
end
rescue Chef::Exceptions::InvalidVersionConstraint => e
- Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint."
+ Chef::Log.debug "Caught InvalidVersionConstraint. This means that a key in value_for_platform cannot be interpreted as a Chef::VersionConstraint::Platform."
Chef::Log.debug(e)
end
end
@@ -106,7 +106,7 @@ class Chef
end
def set(platforms, value)
- if platforms.to_s == 'default'
+ if platforms.to_s == "default"
@values["default"] = value
else
assert_valid_platform_values!(platforms, value)
@@ -212,7 +212,7 @@ class Chef
private
def set(platform_family, value)
- if platform_family.to_s == 'default'
+ if platform_family.to_s == "default"
@values["default"] = value
else
Array(platform_family).each { |family| @values[family.to_s] = value }
@@ -256,5 +256,5 @@ end
# **DEPRECATED**
# This used to be part of chef/mixin/language. Load the file to activate the deprecation code.
-require 'chef/mixin/language'
+require "chef/mixin/language"
diff --git a/lib/chef/dsl/powershell.rb b/lib/chef/dsl/powershell.rb
index a17971c689..c05d092175 100644
--- a/lib/chef/dsl/powershell.rb
+++ b/lib/chef/dsl/powershell.rb
@@ -16,12 +16,12 @@
# limitations under the License.
#
-require 'chef/util/powershell/ps_credential'
+require "chef/util/powershell/ps_credential"
class Chef
module DSL
module Powershell
- def ps_credential(username='placeholder', password)
+ def ps_credential(username="placeholder", password)
Chef::Util::Powershell::PSCredential.new(username, password)
end
end
diff --git a/lib/chef/dsl/reboot_pending.rb b/lib/chef/dsl/reboot_pending.rb
index 7af67e94a5..1f04a0107c 100644
--- a/lib/chef/dsl/reboot_pending.rb
+++ b/lib/chef/dsl/reboot_pending.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/dsl/platform_introspection'
-require 'chef/dsl/registry_helper'
+require "chef/dsl/platform_introspection"
+require "chef/dsl/registry_helper"
class Chef
module DSL
@@ -38,23 +38,24 @@ class Chef
# due to a file being in use (usually a temporary file and a system file)
# \??\c:\temp\test.sys!\??\c:\winnt\system32\test.sys
# http://technet.microsoft.com/en-us/library/cc960241.aspx
- registry_value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }) ||
+ registry_value_exists?('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => "PendingFileRenameOperations" }) ||
# RebootRequired key contains Update IDs with a value of 1 if they require a reboot.
# The existence of RebootRequired alone is sufficient on my Windows 8.1 workstation in Windows Update
registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') ||
# Vista + Server 2008 and newer may have reboots pending from CBS
- registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired') ||
+ registry_key_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') ||
# The mere existence of the UpdateExeVolatile key should indicate a pending restart for certain updates
# http://support.microsoft.com/kb/832475
- (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') &&
+ Chef::Platform.windows_server_2003? &&
+ (registry_key_exists?('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile') &&
!registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0].nil? &&
[1,2,3].include?(registry_get_values('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').select { |v| v[:name] == "Flags" }[0][:data]))
elsif platform?("ubuntu")
# This should work for Debian as well if update-notifier-common happens to be installed. We need an API for that.
- File.exists?('/var/run/reboot-required')
+ File.exists?("/var/run/reboot-required")
else
false
end
diff --git a/lib/chef/dsl/recipe.rb b/lib/chef/dsl/recipe.rb
index c22f053292..5e39919158 100644
--- a/lib/chef/dsl/recipe.rb
+++ b/lib/chef/dsl/recipe.rb
@@ -1,7 +1,7 @@
#--
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
-# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2008, 2009-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,10 +17,12 @@
# limitations under the License.
#
-require 'chef/mixin/convert_to_class_name'
-require 'chef/exceptions'
-require 'chef/resource_builder'
-require 'chef/mixin/shell_out'
+require "chef/exceptions"
+require "chef/mixin/shell_out"
+require "chef/mixin/powershell_out"
+require "chef/dsl/resources"
+require "chef/dsl/definitions"
+require "chef/dsl/declare_resource"
class Chef
module DSL
@@ -31,118 +33,11 @@ class Chef
module Recipe
include Chef::Mixin::ShellOut
- include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::PowershellOut
- def method_missing(method_symbol, *args, &block)
- # If we have a definition that matches, we want to use that instead. This should
- # let you do some really crazy over-riding of "native" types, if you really want
- # to.
- if has_resource_definition?(method_symbol)
- evaluate_resource_definition(method_symbol, *args, &block)
- elsif have_resource_class_for?(method_symbol)
- # Otherwise, we're rocking the regular resource call route.
- declare_resource(method_symbol, args[0], caller[0], &block)
- else
- begin
- super
- rescue NoMethodError
- raise NoMethodError, "No resource or method named `#{method_symbol}' for #{describe_self_for_error}"
- rescue NameError
- raise NameError, "No resource, method, or local variable named `#{method_symbol}' for #{describe_self_for_error}"
- end
- end
- end
-
- def has_resource_definition?(name)
- run_context.definitions.has_key?(name)
- end
-
- # Processes the arguments and block as a resource definition.
- def evaluate_resource_definition(definition_name, *args, &block)
-
- # This dupes the high level object, but we still need to dup the params
- new_def = run_context.definitions[definition_name].dup
-
- new_def.params = new_def.params.dup
- new_def.node = run_context.node
- # This sets up the parameter overrides
- new_def.instance_eval(&block) if block
-
- new_recipe = Chef::Recipe.new(cookbook_name, recipe_name, run_context)
- new_recipe.params = new_def.params
- new_recipe.params[:name] = args[0]
- new_recipe.instance_eval(&new_def.recipe)
- end
-
- #
- # Instantiates a resource (via #build_resource), then adds it to the
- # resource collection. Note that resource classes are looked up directly,
- # so this will create the resource you intended even if the method name
- # corresponding to that resource has been overridden.
- #
- # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
- # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
- # @param created_at [String] The caller of the resource. Use `caller[0]`
- # to get the caller of your function. Defaults to the caller of this
- # function.
- # @param resource_attrs_block A block that lets you set attributes of the
- # resource (it is instance_eval'd on the resource instance).
- #
- # @return [Chef::Resource] The new resource.
- #
- # @example
- # declare_resource(:file, '/x/y.txy', caller[0]) do
- # action :delete
- # end
- # # Equivalent to
- # file '/x/y.txt' do
- # action :delete
- # end
- #
- def declare_resource(type, name, created_at=nil, &resource_attrs_block)
- created_at ||= caller[0]
-
- resource = build_resource(type, name, created_at, &resource_attrs_block)
-
- run_context.resource_collection.insert(resource, resource_type: type, instance_name: name)
- resource
- end
-
- #
- # Instantiate a resource of the given +type+ with the given +name+ and
- # attributes as given in the +resource_attrs_block+.
- #
- # The resource is NOT added to the resource collection.
- #
- # @param type [Symbol] The type of resource (e.g. `:file` or `:package`)
- # @param name [String] The name of the resource (e.g. '/x/y.txt' or 'apache2')
- # @param created_at [String] The caller of the resource. Use `caller[0]`
- # to get the caller of your function. Defaults to the caller of this
- # function.
- # @param resource_attrs_block A block that lets you set attributes of the
- # resource (it is instance_eval'd on the resource instance).
- #
- # @return [Chef::Resource] The new resource.
- #
- # @example
- # build_resource(:file, '/x/y.txy', caller[0]) do
- # action :delete
- # end
- #
- def build_resource(type, name, created_at=nil, &resource_attrs_block)
- created_at ||= caller[0]
-
- Chef::ResourceBuilder.new(
- type: type,
- name: name,
- created_at: created_at,
- params: @params,
- run_context: run_context,
- cookbook_name: cookbook_name,
- recipe_name: recipe_name,
- enclosing_provider: self.is_a?(Chef::Provider) ? self : nil
- ).build(&resource_attrs_block)
- end
+ include Chef::DSL::Resources
+ include Chef::DSL::Definitions
+ include Chef::DSL::DeclareResource
def resource_class_for(snake_case_name)
Chef::Resource.resource_for_node(snake_case_name, run_context.node)
@@ -156,9 +51,9 @@ class Chef
def describe_self_for_error
if respond_to?(:name)
- %Q[`#{self.class.name} "#{name}"']
+ %Q{`#{self.class} "#{name}"'}
elsif respond_to?(:recipe_name)
- %Q[`#{self.class.name} "#{recipe_name}"']
+ %Q{`#{self.class} "#{recipe_name}"'}
else
to_s
end
@@ -168,14 +63,72 @@ 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)
-require 'chef/resource'
+# Avoid circular references for things that are only used in instance methods
+require "chef/resource"
# **DEPRECATED**
# This used to be part of chef/mixin/recipe_definition_dsl_core. Load the file to activate the deprecation code.
-require 'chef/mixin/recipe_definition_dsl_core'
+require "chef/mixin/recipe_definition_dsl_core"
diff --git a/lib/chef/dsl/resources.rb b/lib/chef/dsl/resources.rb
new file mode 100644
index 0000000000..282937f7a8
--- /dev/null
+++ b/lib/chef/dsl/resources.rb
@@ -0,0 +1,57 @@
+#
+# 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 "chef/dsl/cheffish"
+require "chef/dsl/chef_provisioning"
+
+class Chef
+ module DSL
+ #
+ # Module containing a method for each globally declared Resource
+ #
+ # Depends on declare_resource(name, created_at, &block)
+ #
+ # @api private
+ module Resources
+ # Include the lazy loaders for cheffish and chef-provisioning, so that the
+ # resource DSL is there but the gems aren't activated yet.
+ include Chef::DSL::Cheffish
+ include Chef::DSL::ChefProvisioning
+
+ def self.add_resource_dsl(dsl_name)
+ begin
+ module_eval(<<-EOM, __FILE__, __LINE__+1)
+ def #{dsl_name}(*args, &block)
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (\#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: \#{args}") if args.size > 1
+ declare_resource(#{dsl_name.inspect}, args[0], caller[0], &block)
+ end
+ EOM
+ rescue SyntaxError
+ # Handle the case where dsl_name has spaces, etc.
+ define_method(dsl_name.to_sym) do |*args, &block|
+ Chef.log_deprecation("Cannot create resource #{dsl_name} with more than one argument. All arguments except the name (#{args[0].inspect}) will be ignored. This will cause an error in Chef 13. Arguments: #{args}") if args.size > 1
+ declare_resource(dsl_name, args[0], caller[0], &block)
+ end
+ end
+ end
+ def self.remove_resource_dsl(dsl_name)
+ remove_method(dsl_name)
+ rescue NameError
+ end
+ end
+ end
+end
diff --git a/lib/chef/encrypted_data_bag_item.rb b/lib/chef/encrypted_data_bag_item.rb
index 120eb2a4ae..4b39a4f19f 100644
--- a/lib/chef/encrypted_data_bag_item.rb
+++ b/lib/chef/encrypted_data_bag_item.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/data_bag_item'
-require 'chef/encrypted_data_bag_item/decryptor'
-require 'chef/encrypted_data_bag_item/encryptor'
-require 'open-uri'
+require "chef/config"
+require "chef/data_bag_item"
+require "chef/encrypted_data_bag_item/decryptor"
+require "chef/encrypted_data_bag_item/encryptor"
+require "open-uri"
# An EncryptedDataBagItem represents a read-only data bag item where
# all values, except for the value associated with the id key, have
@@ -47,8 +47,8 @@ require 'open-uri'
# such nodes in the infrastructure.
#
class Chef::EncryptedDataBagItem
- ALGORITHM = 'aes-256-cbc'
- AEAD_ALGORITHM = 'aes-256-gcm'
+ ALGORITHM = "aes-256-cbc"
+ AEAD_ALGORITHM = "aes-256-gcm"
#
# === Synopsis
diff --git a/lib/chef/encrypted_data_bag_item/assertions.rb b/lib/chef/encrypted_data_bag_item/assertions.rb
index ab93f46c10..8ee47c8508 100644
--- a/lib/chef/encrypted_data_bag_item/assertions.rb
+++ b/lib/chef/encrypted_data_bag_item/assertions.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format'
-require 'chef/encrypted_data_bag_item/unsupported_cipher'
+require "chef/encrypted_data_bag_item/unacceptable_encrypted_data_bag_item_format"
+require "chef/encrypted_data_bag_item/unsupported_cipher"
class Chef::EncryptedDataBagItem
diff --git a/lib/chef/encrypted_data_bag_item/check_encrypted.rb b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
index b7cb5841b3..12f7c3aa57 100644
--- a/lib/chef/encrypted_data_bag_item/check_encrypted.rb
+++ b/lib/chef/encrypted_data_bag_item/check_encrypted.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/encrypted_data_bag_item/encryptor'
+require "chef/encrypted_data_bag_item/encryptor"
class Chef::EncryptedDataBagItem
# Common code for checking if a data bag appears encrypted
diff --git a/lib/chef/encrypted_data_bag_item/decryptor.rb b/lib/chef/encrypted_data_bag_item/decryptor.rb
index 425218e271..0066e2b5ec 100644
--- a/lib/chef/encrypted_data_bag_item/decryptor.rb
+++ b/lib/chef/encrypted_data_bag_item/decryptor.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'yaml'
-require 'chef/json_compat'
-require 'openssl'
-require 'base64'
-require 'digest/sha2'
-require 'chef/encrypted_data_bag_item'
-require 'chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format'
-require 'chef/encrypted_data_bag_item/decryption_failure'
-require 'chef/encrypted_data_bag_item/assertions'
+require "yaml"
+require "chef/json_compat"
+require "openssl"
+require "base64"
+require "digest/sha2"
+require "chef/encrypted_data_bag_item"
+require "chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format"
+require "chef/encrypted_data_bag_item/decryption_failure"
+require "chef/encrypted_data_bag_item/assertions"
class Chef::EncryptedDataBagItem
@@ -216,7 +216,7 @@ class Chef::EncryptedDataBagItem
@openssl_decryptor ||= begin
d = super
d.auth_tag = auth_tag
- d.auth_data = ''
+ d.auth_data = ""
d
end
end
diff --git a/lib/chef/encrypted_data_bag_item/encryptor.rb b/lib/chef/encrypted_data_bag_item/encryptor.rb
index 034413c1bd..5edf5b9530 100644
--- a/lib/chef/encrypted_data_bag_item/encryptor.rb
+++ b/lib/chef/encrypted_data_bag_item/encryptor.rb
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require 'base64'
-require 'digest/sha2'
-require 'openssl'
-require 'ffi_yajl'
-require 'chef/encrypted_data_bag_item'
-require 'chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format'
-require 'chef/encrypted_data_bag_item/encryption_failure'
-require 'chef/encrypted_data_bag_item/assertions'
+require "base64"
+require "digest/sha2"
+require "openssl"
+require "ffi_yajl"
+require "chef/encrypted_data_bag_item"
+require "chef/encrypted_data_bag_item/unsupported_encrypted_data_bag_item_format"
+require "chef/encrypted_data_bag_item/encryption_failure"
+require "chef/encrypted_data_bag_item/assertions"
class Chef::EncryptedDataBagItem
@@ -83,7 +83,7 @@ class Chef::EncryptedDataBagItem
"encrypted_data" => encrypted_data,
"iv" => Base64.encode64(iv),
"version" => 1,
- "cipher" => algorithm
+ "cipher" => algorithm,
}
end
@@ -127,7 +127,7 @@ class Chef::EncryptedDataBagItem
end
def self.encryptor_keys
- %w( encrypted_data iv version cipher )
+ %w{ encrypted_data iv version cipher }
end
end
@@ -141,7 +141,7 @@ class Chef::EncryptedDataBagItem
"hmac" => hmac,
"iv" => Base64.encode64(iv),
"version" => 2,
- "cipher" => algorithm
+ "cipher" => algorithm,
}
end
@@ -155,7 +155,7 @@ class Chef::EncryptedDataBagItem
end
def self.encryptor_keys
- super + %w( hmac )
+ super + %w{ hmac }
end
end
@@ -176,7 +176,7 @@ class Chef::EncryptedDataBagItem
"iv" => Base64.encode64(iv),
"auth_tag" => Base64.encode64(auth_tag),
"version" => 3,
- "cipher" => algorithm
+ "cipher" => algorithm,
}
end
@@ -201,7 +201,7 @@ class Chef::EncryptedDataBagItem
def openssl_encryptor
@openssl_encryptor ||= begin
encryptor = super
- encryptor.auth_data = ''
+ encryptor.auth_data = ""
encryptor
end
end
@@ -216,7 +216,7 @@ class Chef::EncryptedDataBagItem
end
def self.encryptor_keys
- super + %w( auth_tag )
+ super + %w{ auth_tag }
end
end
diff --git a/lib/chef/environment.rb b/lib/chef/environment.rb
index 7d4b410639..e41f2b66ac 100644
--- a/lib/chef/environment.rb
+++ b/lib/chef/environment.rb
@@ -19,11 +19,12 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/mash'
-require 'chef/mixin/params_validate'
-require 'chef/mixin/from_file'
-require 'chef/version_constraint'
+require "chef/config"
+require "chef/mash"
+require "chef/mixin/params_validate"
+require "chef/mixin/from_file"
+require "chef/version_constraint"
+require "chef/server_api"
class Chef
class Environment
@@ -38,8 +39,8 @@ class Chef
COMBINED_COOKBOOK_CONSTRAINT = /(.+)(?:[\s]+)((?:#{Chef::VersionConstraint::OPS.join('|')})(?:[\s]+).+)$/.freeze
def initialize(chef_server_rest: nil)
- @name = ''
- @description = ''
+ @name = ""
+ @description = ""
@default_attributes = Mash.new
@override_attributes = Mash.new
@cookbook_versions = Hash.new
@@ -47,11 +48,11 @@ class Chef
end
def chef_server_rest
- @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def self.chef_server_rest
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def name(arg=nil)
@@ -66,7 +67,7 @@ class Chef
set_or_return(
:description,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -74,7 +75,7 @@ class Chef
set_or_return(
:default_attributes,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
@@ -86,7 +87,7 @@ class Chef
set_or_return(
:override_attributes,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
@@ -102,8 +103,8 @@ class Chef
:kind_of => Hash,
:callbacks => {
"should be a valid set of cookbook version requirements" => lambda { |cv| Chef::Environment.validate_cookbook_versions(cv) }
- }
- }
+ },
+ },
)
end
@@ -114,7 +115,7 @@ class Chef
:version => {
:callbacks => { "should be a valid version requirement" => lambda { |v| Chef::Environment.validate_cookbook_version(v) } }
}
- })
+ },)
@cookbook_versions[cookbook] = version
end
@@ -126,7 +127,7 @@ class Chef
"json_class" => self.class.name,
"chef_type" => "environment",
"default_attributes" => @default_attributes,
- "override_attributes" => @override_attributes
+ "override_attributes" => @override_attributes,
}
result
end
@@ -216,6 +217,10 @@ class Chef
end
def self.json_create(o)
+ from_hash(o)
+ end
+
+ def self.from_hash(o)
environment = new
environment.name(o["name"])
environment.description(o["description"])
@@ -233,7 +238,7 @@ class Chef
end
response
else
- chef_server_rest.get_rest("environments")
+ chef_server_rest.get("environments")
end
end
@@ -241,7 +246,7 @@ class Chef
if Chef::Config[:solo]
load_from_file(name)
else
- chef_server_rest.get_rest("environments/#{name}")
+ self.from_hash(chef_server_rest.get("environments/#{name}"))
end
end
@@ -267,26 +272,26 @@ class Chef
end
def destroy
- chef_server_rest.delete_rest("environments/#{@name}")
+ chef_server_rest.delete("environments/#{@name}")
end
def save
begin
- chef_server_rest.put_rest("environments/#{@name}", self)
+ chef_server_rest.put("environments/#{@name}", self)
rescue Net::HTTPServerException => e
raise e unless e.response.code == "404"
- chef_server_rest.post_rest("environments", self)
+ chef_server_rest.post("environments", self)
end
self
end
def create
- chef_server_rest.post_rest("environments", self)
+ chef_server_rest.post("environments", self)
self
end
def self.load_filtered_recipe_list(environment)
- chef_server_rest.get_rest("environments/#{environment}/recipes")
+ chef_server_rest.get("environments/#{environment}/recipes")
end
def to_s
diff --git a/lib/chef/event_dispatch/base.rb b/lib/chef/event_dispatch/base.rb
index 7274105802..585a3db174 100644
--- a/lib/chef/event_dispatch/base.rb
+++ b/lib/chef/event_dispatch/base.rb
@@ -47,14 +47,19 @@ class Chef
def ohai_completed(node)
end
- # Already have a client key, assuming this node has registered.
+ # Announce that we're not going to register the client. Generally because
+ # we already have the private key, or because we're deliberately not using
+ # a key.
def skipping_registration(node_name, config)
end
- # About to attempt to register as +node_name+
+ # About to attempt to create a private key registered to the server with
+ # client +node_name+.
def registration_start(node_name, config)
end
+ # Successfully created the private key and registered this client with the
+ # server.
def registration_completed
end
@@ -82,6 +87,11 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ # Called after the Policyfile was loaded. This event only occurs when
+ # chef is in policyfile mode.
+ def policyfile_loaded(policy)
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
end
@@ -113,8 +123,8 @@ class Chef
def cookbook_sync_start(cookbook_count)
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
end
# Called when an individual file in a cookbook has been updated
@@ -239,13 +249,13 @@ class Chef
end
# Called when audit phase successfully finishes
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
end
# Called if there is an uncaught exception during the audit phase. The audit runner should
# be catching and handling errors from the examples, so this is only uncaught errors (like
# bugs in our handling code)
- def audit_phase_failed(exception)
+ def audit_phase_failed(exception, audit_output)
end
# Signifies the start of a `control_group` block with a defined name
@@ -264,26 +274,37 @@ class Chef
# def notifications_resolved
# end
+ #
+ # Resource events and ordering:
+ #
+ # 1. Start the action
+ # - resource_action_start
+ # 2. Check the guard
+ # - resource_skipped: (goto 7) if only_if/not_if say to skip
+ # 3. Load the current resource
+ # - resource_current_state_loaded
+ # - resource_current_state_load_bypassed (if not why-run safe)
+ # 4. Check if why-run safe
+ # - resource_bypassed: (goto 7) if not why-run safe
+ # 5. During processing:
+ # - resource_update_applied: For each actual change (many per action)
+ # 6. Processing complete status:
+ # - resource_failed if the resource threw an exception while running
+ # - resource_failed_retriable: (goto 3) if resource failed and will be retried
+ # - resource_updated if the resource was updated (resource_update_applied will have been called)
+ # - resource_up_to_date if the resource was up to date (no resource_update_applied)
+ # 7. Processing complete:
+ # - resource_completed
+ #
+
# Called before action is executed on a resource.
def resource_action_start(resource, action, notification_type=nil, notifier=nil)
end
- # Called when a resource fails, but will retry.
- def resource_failed_retriable(resource, action, retry_count, exception)
- end
-
- # Called when a resource fails and will not be retried.
- def resource_failed(resource, action, exception)
- end
-
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
end
- # Called when a resource action has been completed
- def resource_completed(resource)
- end
-
# Called after #load_current_resource has run.
def resource_current_state_loaded(resource, action, current_resource)
end
@@ -297,21 +318,33 @@ class Chef
def resource_bypassed(resource, action, current_resource)
end
- # Called when a resource has no converge actions, e.g., it was already correct.
- def resource_up_to_date(resource, action)
- end
-
# Called when a change has been made to a resource. May be called multiple
# times per resource, e.g., a file may have its content updated, and then
# its permissions updated.
def resource_update_applied(resource, action, update)
end
+ # Called when a resource fails, but will retry.
+ def resource_failed_retriable(resource, action, retry_count, exception)
+ end
+
+ # Called when a resource fails and will not be retried.
+ def resource_failed(resource, action, exception)
+ end
+
# Called after a resource has been completely converged, but only if
# modifications were made.
def resource_updated(resource, action)
end
+ # Called when a resource has no converge actions, e.g., it was already correct.
+ def resource_up_to_date(resource, action)
+ end
+
+ # Called when a resource action has been completed
+ def resource_completed(resource)
+ end
+
# A stream has opened.
def stream_opened(stream, options = {})
end
@@ -347,8 +380,12 @@ class Chef
def whyrun_assumption(action, resource, message)
end
- ## TODO: deprecation warning. this way we can queue them up and present
- # them all at once.
+ # Emit a message about something being deprecated.
+ def deprecation(message, location=caller(2..2)[0])
+ end
+
+ def run_list_expanded(run_list_expansion)
+ end
# An uncategorized message. This supports the case that a user needs to
# pass output that doesn't fit into one of the callbacks above. Note that
diff --git a/lib/chef/event_dispatch/dispatcher.rb b/lib/chef/event_dispatch/dispatcher.rb
index 9f43f14311..dae2c3f6b4 100644
--- a/lib/chef/event_dispatch/dispatcher.rb
+++ b/lib/chef/event_dispatch/dispatcher.rb
@@ -1,4 +1,4 @@
-require 'chef/event_dispatch/base'
+require "chef/event_dispatch/base"
class Chef
module EventDispatch
@@ -9,6 +9,8 @@ class Chef
# the registered subscribers.
class Dispatcher < Base
+ attr_reader :subscribers
+
def initialize(*subscribers)
@subscribers = subscribers
end
@@ -18,23 +20,43 @@ class Chef
@subscribers << subscriber
end
+ # Check to see if we are dispatching to a formatter
+ def formatter?
+ @subscribers.any? { |s| s.respond_to?(:is_formatter?) && s.is_formatter? }
+ end
+
####
# All messages are unconditionally forwarded to all subscribers, so just
# define the forwarding in one go:
#
- # Define a method that will be forwarded to all
- def self.def_forwarding_method(method_name)
- define_method(method_name) do |*args|
- @subscribers.each { |s| s.send(method_name, *args) }
+ def call_subscribers(method_name, *args)
+ @subscribers.each do |s|
+ # Skip new/unsupported event names.
+ next if !s.respond_to?(method_name)
+ mth = s.method(method_name)
+ # Trim arguments to match what the subscriber expects to allow
+ # adding new arguments without breaking compat.
+ if mth.arity < args.size && mth.arity >= 0
+ mth.call(*args.take(mth.arity))
+ else
+ mth.call(*args)
+ end
end
end
(Base.instance_methods - Object.instance_methods).each do |method_name|
- def_forwarding_method(method_name)
+ class_eval <<-EOM
+ def #{method_name}(*args)
+ call_subscribers(#{method_name.inspect}, *args)
+ end
+ EOM
end
+ # Special case deprecation, since it needs to know its caller
+ def deprecation(message, location=caller(2..2)[0])
+ call_subscribers(:deprecation, message, location)
+ end
end
end
end
-
diff --git a/lib/chef/event_dispatch/dsl.rb b/lib/chef/event_dispatch/dsl.rb
new file mode 100644
index 0000000000..4695699c9a
--- /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[:event_handlers]
+ if Chef.run_context && Chef.run_context.events
+ Chef::Log.debug("Registering handler '#{name}' using events api")
+ Chef.run_context.events.register(handler)
+ else
+ Chef::Log.debug("Registering handler '#{name}' using global config")
+ Chef::Config[:event_handlers] << handler
+ end
+ end
+
+ # Adds a new event handler derived from base handler
+ # with user defined block against a chef event
+ #
+ # @return [Chef::EventDispatch::Base] a base handler object
+ def on(event_type, &block)
+ validate!(event_type)
+ handler.define_singleton_method(event_type) do |*args|
+ instance_exec(*args, &block)
+ end
+ end
+
+ private
+ def validate!(event_type)
+ all_event_types = (Chef::EventDispatch::Base.instance_methods - Object.instance_methods)
+ raise Chef::Exceptions::InvalidEventType, "Invalid event type: #{event_type}" unless all_event_types.include?(event_type)
+ end
+ end
+ end
+end
diff --git a/lib/chef/event_dispatch/events_output_stream.rb b/lib/chef/event_dispatch/events_output_stream.rb
index 8de9b0fed1..d9c21642b7 100644
--- a/lib/chef/event_dispatch/events_output_stream.rb
+++ b/lib/chef/event_dispatch/events_output_stream.rb
@@ -21,6 +21,14 @@ class Chef
events.stream_output(self, str, options)
end
+ def <<(str)
+ events.stream_output(self, str, options)
+ end
+
+ def write(str)
+ events.stream_output(self, str, options)
+ end
+
def close
events.stream_closed(self, options)
end
diff --git a/lib/chef/event_loggers/base.rb b/lib/chef/event_loggers/base.rb
index 1f676dd516..3f5c35a74c 100644
--- a/lib/chef/event_loggers/base.rb
+++ b/lib/chef/event_loggers/base.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/event_dispatch/base'
+require "chef/event_dispatch/base"
class Chef
module EventLoggers
diff --git a/lib/chef/event_loggers/windows_eventlog.rb b/lib/chef/event_loggers/windows_eventlog.rb
index 6f5ef627fb..e7a5b204f9 100644
--- a/lib/chef/event_loggers/windows_eventlog.rb
+++ b/lib/chef/event_loggers/windows_eventlog.rb
@@ -16,19 +16,9 @@
# limitations under the License.
#
-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/event_loggers/base"
+require "chef/platform/query_helpers"
+require "chef/win32/eventlog"
class Chef
module EventLoggers
@@ -45,14 +35,14 @@ class Chef
LOG_CATEGORY_ID = 11001
# Since we must install the event logger, this is not really configurable
- SOURCE = 'Chef'
+ SOURCE = "Chef"
def self.available?
return Chef::Platform::windows?
end
def initialize
- @eventlog = ::Win32::EventLog::open('Application')
+ @eventlog = ::Win32::EventLog::open("Application")
end
def run_start(version)
@@ -60,7 +50,7 @@ class Chef
:event_type => ::Win32::EventLog::INFO_TYPE,
:source => SOURCE,
:event_id => RUN_START_EVENT_ID,
- :data => [version]
+ :data => [version],
)
end
@@ -70,7 +60,7 @@ class Chef
:event_type => ::Win32::EventLog::INFO_TYPE,
:source => SOURCE,
:event_id => RUN_STARTED_EVENT_ID,
- :data => [run_status.run_id]
+ :data => [run_status.run_id],
)
end
@@ -79,7 +69,7 @@ class Chef
:event_type => ::Win32::EventLog::INFO_TYPE,
:source => SOURCE,
:event_id => RUN_COMPLETED_EVENT_ID,
- :data => [@run_status.run_id, @run_status.elapsed_time.to_s]
+ :data => [@run_status.run_id, @run_status.elapsed_time.to_s],
)
end
@@ -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 eea6a2f239..f9a717471c 100644
--- a/lib/chef/exceptions.rb
+++ b/lib/chef/exceptions.rb
@@ -2,7 +2,7 @@
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Seth Falcon (<seth@opscode.com>)
# Author:: Kyle Goodwin (<kgoodwin@primerevenue.com>)
-# Copyright:: Copyright 2008-2010 Opscode, Inc.
+# Copyright:: Copyright 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,14 +17,18 @@
# 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'
+ require "mixlib/shellout/exceptions"
def self.const_missing(const_name)
if const_name == :ShellCommandFailed
@@ -68,11 +72,22 @@ class Chef
class DuplicateRole < RuntimeError; end
class ValidationFailed < ArgumentError; end
class InvalidPrivateKey < ArgumentError; end
- class ConfigurationError < ArgumentError; end
+ class MissingKeyAttribute < ArgumentError; end
+ class KeyCommandInputError < ArgumentError; end
+ class BootstrapCommandInputError < ArgumentError
+ def initialize
+ super "You cannot pass both --json-attributes and --json-attribute-file. Please pass one or none."
+ end
+ end
+ class InvalidKeyArgument < ArgumentError; end
+ class InvalidKeyAttribute < ArgumentError; end
+ class InvalidUserAttribute < ArgumentError; end
+ class InvalidClientAttribute < ArgumentError; end
class RedirectLimitExceeded < RuntimeError; end
class AmbiguousRunlistSpecification < ArgumentError; end
class CookbookFrozen < ArgumentError; end
class CookbookNotFound < RuntimeError; end
+ class OnlyApiVersion0SupportedForAction < RuntimeError; end
# Cookbook loader used to raise an argument error when cookbook not found.
# for back compat, need to raise an error that inherits from ArgumentError
class CookbookNotFoundInRepo < ArgumentError; end
@@ -90,7 +105,14 @@ class Chef
class ConflictingMembersInGroup < ArgumentError; end
class InvalidResourceReference < RuntimeError; end
class ResourceNotFound < RuntimeError; end
+ class ProviderNotFound < RuntimeError; end
+ NoProviderAvailable = ProviderNotFound
class VerificationNotFound < RuntimeError; end
+ class InvalidEventType < ArgumentError; end
+ class MultipleIdentityError < RuntimeError; end
+ # Used in Resource::ActionClass#load_current_resource to denote that
+ # the resource doesn't actually exist (for example, the file does not exist)
+ class CurrentValueDoesNotExist < RuntimeError; end
# Can't find a Resource of this type that is valid on this platform.
class NoSuchResourceType < NameError
@@ -99,6 +121,8 @@ class Chef
end
end
+ class InvalidPolicybuilderCall < ArgumentError; end
+
class InvalidResourceSpecification < ArgumentError; end
class SolrConnectionError < RuntimeError; end
class IllegalChecksumRevert < RuntimeError; end
@@ -112,6 +136,23 @@ class Chef
class EnclosingDirectoryDoesNotExist < ArgumentError; end
# Errors originating from calls to the Win32 API
class Win32APIError < RuntimeError; end
+
+ class Win32NetAPIError < Win32APIError
+ attr_reader :msg, :error_code
+ def initialize(msg, error_code)
+ @msg = msg
+ @error_code = error_code
+
+ formatted_message = ""
+ formatted_message << "---- Begin Win32 API output ----\n"
+ formatted_message << "Net Api Error Code: #{error_code}\n"
+ formatted_message << "Net Api Error Message: #{msg}\n"
+ formatted_message << "---- End Win32 API output ----\n"
+
+ super(formatted_message)
+ end
+ end
+
# Thrown when Win32 API layer binds to non-existent Win32 function. Occurs
# when older versions of Windows don't support newer Win32 API functions.
class Win32APIFunctionNotImplemented < NotImplementedError; end
@@ -128,6 +169,8 @@ class Chef
class LCMParser < RuntimeError; end
class CannotDetermineHomebrewOwner < Package; end
+ class CannotDetermineWindowsInstallerType < Package; end
+ class NoWindowsPackageSource < Package; end
# Can not create staging file during file deployment
class FileContentStagingError < RuntimeError
@@ -177,7 +220,7 @@ class Chef
class ImmutableAttributeModification < NoMethodError
def initialize
super "Node attributes are read-only when you do not specify which precedence level to set. " +
- %Q(To set an attribute use code like `node.default["key"] = "value"')
+ %Q{To set an attribute use code like `node.default["key"] = "value"'}
end
end
@@ -211,8 +254,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)")
@@ -233,7 +274,7 @@ class Chef
super
when RunList::RunListExpansion
@expansion = message_or_expansion
- missing_roles = @expansion.errors.join(', ')
+ missing_roles = @expansion.errors.join(", ")
super("The expanded run list includes nonexistent roles: #{missing_roles}")
end
end
@@ -303,7 +344,7 @@ class Chef
result = {
"message" => message,
"non_existent_cookbooks" => non_existent_cookbooks,
- "cookbooks_with_no_versions" => cookbooks_with_no_matching_versions
+ "cookbooks_with_no_versions" => cookbooks_with_no_matching_versions,
}
Chef::JSONCompat.to_json(result, *a)
end
@@ -338,7 +379,7 @@ class Chef
"message" => message,
"unsatisfiable_run_list_item" => run_list_item,
"non_existent_cookbooks" => non_existent_cookbooks,
- "most_constrained_cookbooks" => most_constrained_cookbooks
+ "most_constrained_cookbooks" => most_constrained_cookbooks,
}
Chef::JSONCompat.to_json(result, *a)
end
@@ -356,7 +397,10 @@ class Chef
# length declared in the http response.
class ContentLengthMismatch < RuntimeError
def initialize(response_length, content_length)
- super "Response body length #{response_length} does not match HTTP Content-Length header #{content_length}."
+ super <<-EOF
+Response body length #{response_length} does not match HTTP Content-Length header #{content_length}.
+This error is most often caused by network issues (proxies, etc) outside of chef-client.
+ EOF
end
end
@@ -431,7 +475,7 @@ class Chef
wrapped_errors.each_with_index do |e,i|
backtrace << "#{i+1}) #{e.class} - #{e.message}"
backtrace += e.backtrace if e.backtrace
- backtrace << ""
+ backtrace << "" unless i == wrapped_errors.length - 1
end
set_backtrace(backtrace)
end
@@ -443,12 +487,26 @@ class Chef
end
end
+ class CookbookChefVersionMismatch < RuntimeError
+ def initialize(chef_version, cookbook_name, cookbook_version, *constraints)
+ constraint_str = constraints.map { |c| c.requirement.as_list.to_s }.join(", ")
+ super "Cookbook '#{cookbook_name}' version '#{cookbook_version}' depends on chef version #{constraint_str}, but the running chef version is #{chef_version}"
+ end
+ end
+
+ class CookbookOhaiVersionMismatch < RuntimeError
+ def initialize(ohai_version, cookbook_name, cookbook_version, *constraints)
+ constraint_str = constraints.map { |c| c.requirement.as_list.to_s }.join(", ")
+ super "Cookbook '#{cookbook_name}' version '#{cookbook_version}' depends on ohai version #{constraint_str}, but the running ohai version is #{ohai_version}"
+ end
+ end
+
class MultipleDscResourcesFound < RuntimeError
attr_reader :resources_found
def initialize(resources_found)
@resources_found = resources_found
matches_info = @resources_found.each do |r|
- if r['Module'].nil?
+ if r["Module"].nil?
"Resource #{r['Name']} was found in #{r['Module']['Name']}"
else
"Resource #{r['Name']} is a binary resource"
diff --git a/lib/chef/file_access_control.rb b/lib/chef/file_access_control.rb
index cc7fa8fc1a..be42a228d4 100644
--- a/lib/chef/file_access_control.rb
+++ b/lib/chef/file_access_control.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/log'
+require "chef/log"
class Chef
@@ -27,10 +27,10 @@ class Chef
class FileAccessControl
if RUBY_PLATFORM =~ /mswin|mingw|windows/
- require 'chef/file_access_control/windows'
+ require "chef/file_access_control/windows"
include FileAccessControl::Windows
else
- require 'chef/file_access_control/unix'
+ require "chef/file_access_control/unix"
include FileAccessControl::Unix
end
diff --git a/lib/chef/file_access_control/unix.rb b/lib/chef/file_access_control/unix.rb
index 472f30b752..86247eabad 100644
--- a/lib/chef/file_access_control/unix.rb
+++ b/lib/chef/file_access_control/unix.rb
@@ -18,7 +18,7 @@
# limitations under the License.
#
-require 'chef/log'
+require "chef/log"
class Chef
class FileAccessControl
@@ -79,18 +79,18 @@ class Chef
def should_update_owner?
if target_uid.nil?
# the user has not specified a permission on the new resource, so we never manage it with FAC
- Chef::Log.debug("found target_uid == nil, so no owner was specified on resource, not managing owner")
+ Chef::Log.debug("Found target_uid == nil, so no owner was specified on resource, not managing owner")
return false
elsif current_uid.nil?
# the user has specified a permission, and we are creating a file, so always enforce permissions
- Chef::Log.debug("found current_uid == nil, so we are creating a new file, updating owner")
+ Chef::Log.debug("Found current_uid == nil, so we are creating a new file, updating owner")
return true
elsif target_uid != current_uid
# the user has specified a permission, and it does not match the file, so fix the permission
- Chef::Log.debug("found target_uid != current_uid, updating owner")
+ Chef::Log.debug("Found target_uid != current_uid, updating owner")
return true
else
- Chef::Log.debug("found target_uid == current_uid, not updating owner")
+ Chef::Log.debug("Found target_uid == current_uid, not updating owner")
# the user has specified a permission, but it matches the file, so behave idempotently
return false
end
@@ -138,18 +138,18 @@ class Chef
def should_update_group?
if target_gid.nil?
# the user has not specified a permission on the new resource, so we never manage it with FAC
- Chef::Log.debug("found target_gid == nil, so no group was specified on resource, not managing group")
+ Chef::Log.debug("Found target_gid == nil, so no group was specified on resource, not managing group")
return false
elsif current_gid.nil?
# the user has specified a permission, and we are creating a file, so always enforce permissions
- Chef::Log.debug("found current_gid == nil, so we are creating a new file, updating group")
+ Chef::Log.debug("Found current_gid == nil, so we are creating a new file, updating group")
return true
elsif target_gid != current_gid
# the user has specified a permission, and it does not match the file, so fix the permission
- Chef::Log.debug("found target_gid != current_gid, updating group")
+ Chef::Log.debug("Found target_gid != current_gid, updating group")
return true
else
- Chef::Log.debug("found target_gid == current_gid, not updating group")
+ Chef::Log.debug("Found target_gid == current_gid, not updating group")
# the user has specified a permission, but it matches the file, so behave idempotently
return false
end
@@ -187,18 +187,20 @@ class Chef
def should_update_mode?
if target_mode.nil?
# the user has not specified a permission on the new resource, so we never manage it with FAC
- Chef::Log.debug("found target_mode == nil, so no mode was specified on resource, not managing mode")
+ Chef::Log.debug("Found target_mode == nil, so no mode was specified on resource, not managing mode")
return false
elsif current_mode.nil?
# the user has specified a permission, and we are creating a file, so always enforce permissions
- Chef::Log.debug("found current_mode == nil, so we are creating a new file, updating mode")
+ Chef::Log.debug("Found current_mode == nil, so we are creating a new file, updating mode")
return true
elsif target_mode != current_mode
# the user has specified a permission, and it does not match the file, so fix the permission
- Chef::Log.debug("found target_mode != current_mode, updating mode")
+ Chef::Log.debug("Found target_mode != current_mode, updating mode")
+ return true
+ elsif suid_bit_set? and (should_update_group? or should_update_owner?)
return true
else
- Chef::Log.debug("found target_mode == current_mode, not updating mode")
+ Chef::Log.debug("Found target_mode == current_mode, not updating mode")
# the user has specified a permission, but it matches the file, so behave idempotently
return false
end
@@ -280,6 +282,9 @@ class Chef
return nil
end
+ def suid_bit_set?
+ return target_mode & 04000 > 0
+ end
end
end
end
diff --git a/lib/chef/file_access_control/windows.rb b/lib/chef/file_access_control/windows.rb
index 1781a6fa63..5d36a4339e 100644
--- a/lib/chef/file_access_control/windows.rb
+++ b/lib/chef/file_access_control/windows.rb
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/win32/security'
-require 'chef/win32/file'
+require "chef/win32/security"
+require "chef/win32/file"
class Chef
class FileAccessControl
diff --git a/lib/chef/file_cache.rb b/lib/chef/file_cache.rb
index c2f77bdff6..42f54147d3 100644
--- a/lib/chef/file_cache.rb
+++ b/lib/chef/file_cache.rb
@@ -15,12 +15,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/mixin/params_validate'
-require 'chef/mixin/create_path'
-require 'chef/exceptions'
-require 'chef/json_compat'
-require 'fileutils'
-require 'chef/util/path_helper'
+require "chef/mixin/params_validate"
+require "chef/mixin/create_path"
+require "chef/exceptions"
+require "chef/json_compat"
+require "fileutils"
+require "chef/util/path_helper"
class Chef
class FileCache
@@ -43,12 +43,12 @@ class Chef
validate(
{
:path => path,
- :contents => contents
+ :contents => contents,
},
{
:path => { :kind_of => String },
:contents => { :kind_of => String },
- }
+ },
)
file_path_array = File.split(path)
@@ -69,12 +69,12 @@ class Chef
validate(
{
:file => file,
- :path => path
+ :path => path,
},
{
:file => { :kind_of => String },
:path => { :kind_of => String },
- }
+ },
)
file_path_array = File.split(path)
@@ -82,7 +82,7 @@ class Chef
if File.exists?(file) && File.writable?(file)
FileUtils.mv(
file,
- File.join(create_cache_path(File.join(file_path_array), true), file_name)
+ File.join(create_cache_path(File.join(file_path_array), true), file_name),
)
else
raise RuntimeError, "Cannot move #{file} to #{path}!"
@@ -109,7 +109,7 @@ class Chef
},
{
:path => { :kind_of => String }
- }
+ },
)
cache_path = create_cache_path(path, false)
raise Chef::Exceptions::FileNotFound, "Cannot find #{cache_path} for #{path}!" unless File.exists?(cache_path)
@@ -134,8 +134,8 @@ class Chef
:path => path
},
{
- :path => { :kind_of => String },
- }
+ :path => { :kind_of => String }
+ },
)
cache_path = create_cache_path(path, false)
if File.exists?(cache_path)
@@ -181,8 +181,8 @@ class Chef
:path => path
},
{
- :path => { :kind_of => String },
- }
+ :path => { :kind_of => String }
+ },
)
full_path = create_cache_path(path, false)
if File.exists?(full_path)
diff --git a/lib/chef/file_content_management/deploy.rb b/lib/chef/file_content_management/deploy.rb
index 35ea3c6fc8..fd76c4e63e 100644
--- a/lib/chef/file_content_management/deploy.rb
+++ b/lib/chef/file_content_management/deploy.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/file_content_management/deploy/cp'
-require 'chef/file_content_management/deploy/mv_unix'
+require "chef/file_content_management/deploy/cp"
+require "chef/file_content_management/deploy/mv_unix"
if Chef::Platform.windows?
- require 'chef/file_content_management/deploy/mv_windows'
+ require "chef/file_content_management/deploy/mv_windows"
end
class Chef
diff --git a/lib/chef/file_content_management/deploy/cp.rb b/lib/chef/file_content_management/deploy/cp.rb
index c6b1d6cd11..ea378c2e5d 100644
--- a/lib/chef/file_content_management/deploy/cp.rb
+++ b/lib/chef/file_content_management/deploy/cp.rb
@@ -34,12 +34,12 @@ class Chef
#
class Cp
def create(file)
- Chef::Log.debug("touching #{file} to create it")
+ Chef::Log.debug("Touching #{file} to create it")
FileUtils.touch(file)
end
def deploy(src, dst)
- Chef::Log.debug("copying temporary file #{src} into place at #{dst}")
+ Chef::Log.debug("Copying temporary file #{src} into place at #{dst}")
FileUtils.cp(src, dst)
end
end
diff --git a/lib/chef/file_content_management/deploy/mv_unix.rb b/lib/chef/file_content_management/deploy/mv_unix.rb
index 758c594e50..9712486424 100644
--- a/lib/chef/file_content_management/deploy/mv_unix.rb
+++ b/lib/chef/file_content_management/deploy/mv_unix.rb
@@ -30,19 +30,19 @@ class Chef
def create(file)
# this is very simple, but it ensures that ownership and file modes take
# good defaults, in particular mode needs to obey umask on create
- Chef::Log.debug("touching #{file} to create it")
+ Chef::Log.debug("Touching #{file} to create it")
FileUtils.touch(file)
end
def deploy(src, dst)
# we are only responsible for content so restore the dst files perms
- Chef::Log.debug("reading modes from #{dst} file")
+ Chef::Log.debug("Reading modes from #{dst} file")
stat = ::File.stat(dst)
mode = stat.mode & 07777
uid = stat.uid
gid = stat.gid
- Chef::Log.debug("applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}")
+ Chef::Log.debug("Applying mode = #{mode.to_s(8)}, uid = #{uid}, gid = #{gid} to #{src}")
# i own the inode, so should be able to at least chmod it
::File.chmod(mode, src)
@@ -67,7 +67,7 @@ class Chef
Chef::Log.warn("Could not set gid = #{gid} on #{src}, file modes not preserved")
end
- Chef::Log.debug("moving temporary file #{src} into place at #{dst}")
+ Chef::Log.debug("Moving temporary file #{src} into place at #{dst}")
FileUtils.mv(src, dst)
end
end
diff --git a/lib/chef/file_content_management/deploy/mv_windows.rb b/lib/chef/file_content_management/deploy/mv_windows.rb
index 7504123012..85c1accc63 100644
--- a/lib/chef/file_content_management/deploy/mv_windows.rb
+++ b/lib/chef/file_content_management/deploy/mv_windows.rb
@@ -21,9 +21,9 @@
# ACL information on the dst file.
#
-require 'chef/platform/query_helpers'
+require "chef/platform/query_helpers"
if Chef::Platform.windows?
- require 'chef/win32/security'
+ require "chef/win32/security"
end
class Chef
@@ -35,7 +35,7 @@ class Chef
ACL = Security::ACL
def create(file)
- Chef::Log.debug("touching #{file} to create it")
+ Chef::Log.debug("Touching #{file} to create it")
FileUtils.touch(file)
end
@@ -63,12 +63,22 @@ class Chef
raise Chef::Exceptions::WindowsNotAdmin, "can not get the security information for '#{dst}' due to missing Administrator privileges."
end
- if dst_sd.dacl_present?
- apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+ dacl_present = dst_sd.dacl_present?
+ if dacl_present
+ if dst_sd.dacl.nil?
+ apply_dacl = nil
+ else
+ apply_dacl = ACL.create(dst_sd.dacl.select { |ace| !ace.inherited? })
+ end
end
- if dst_sd.sacl_present?
- apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+ sacl_present = dst_sd.sacl_present?
+ if sacl_present
+ if dst_sd.sacl.nil?
+ apply_sacl = nil
+ else
+ apply_sacl = ACL.create(dst_sd.sacl.select { |ace| !ace.inherited? })
+ end
end
#
@@ -84,8 +94,8 @@ class Chef
dst_so = Security::SecurableObject.new(dst)
dst_so.group = dst_sd.group
dst_so.owner = dst_sd.owner
- dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dst_sd.dacl_present?
- dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if dst_sd.sacl_present?
+ dst_so.set_dacl(apply_dacl, dst_sd.dacl_inherits?) if dacl_present
+ dst_so.set_sacl(apply_sacl, dst_sd.sacl_inherits?) if sacl_present
end
end
diff --git a/lib/chef/file_content_management/tempfile.rb b/lib/chef/file_content_management/tempfile.rb
index 2dde0ce21b..6e1624f9a4 100644
--- a/lib/chef/file_content_management/tempfile.rb
+++ b/lib/chef/file_content_management/tempfile.rb
@@ -49,7 +49,7 @@ class Chef
end
end
- raise Chef::Exceptions::FileContentStagingError(errors) if tf.nil?
+ raise Chef::Exceptions::FileContentStagingError, errors if tf.nil?
# We always process the tempfile in binmode so that we
# preserve the line endings of the content.
diff --git a/lib/chef/formatters/base.rb b/lib/chef/formatters/base.rb
index c901068aa0..c881397b60 100644
--- a/lib/chef/formatters/base.rb
+++ b/lib/chef/formatters/base.rb
@@ -17,11 +17,11 @@
# limitations under the License.
#
-require 'chef/event_dispatch/base'
-require 'chef/formatters/error_inspectors'
-require 'chef/formatters/error_descriptor'
-require 'chef/formatters/error_mapper'
-require 'chef/formatters/indentable_output_stream'
+require "chef/event_dispatch/base"
+require "chef/formatters/error_inspectors"
+require "chef/formatters/error_descriptor"
+require "chef/formatters/error_mapper"
+require "chef/formatters/indentable_output_stream"
class Chef
@@ -212,6 +212,13 @@ class Chef
file_load_failed(path, exception)
end
+ def deprecation(message, location=caller(2..2)[0])
+ Chef::Log.deprecation("#{message} at #{location}")
+ end
+
+ def is_formatter?
+ true
+ end
end
@@ -222,6 +229,9 @@ class Chef
cli_name(:null)
+ def is_formatter?
+ false
+ end
end
end
diff --git a/lib/chef/formatters/doc.rb b/lib/chef/formatters/doc.rb
index 7144d00b5d..5510956754 100644
--- a/lib/chef/formatters/doc.rb
+++ b/lib/chef/formatters/doc.rb
@@ -1,11 +1,11 @@
-require 'chef/formatters/base'
-require 'chef/config'
+require "chef/formatters/base"
+require "chef/config"
class Chef
module Formatters
- #--
- # TODO: not sold on the name, but the output is similar to what rspec calls
- # "specdoc"
+
+ # Formatter similar to RSpec's documentation formatter. Uses indentation to
+ # show context.
class Doc < Formatters::Base
attr_reader :start_time, :end_time, :successful_audits, :failed_audits
@@ -22,18 +22,31 @@ class Chef
@failed_audits = 0
@start_time = Time.now
@end_time = @start_time
+ @skipped_resources = 0
end
def elapsed_time
end_time - start_time
end
+ def pretty_elapsed_time
+ time = elapsed_time
+ if time < 60 then
+ message = Time.at(time).utc.strftime("%S seconds")
+ elsif time < 3600 then
+ message = Time.at(time).utc.strftime("%M minutes %S seconds")
+ else
+ message = Time.at(time).utc.strftime("%H hours %M minutes %S seconds")
+ end
+ message
+ end
+
def run_start(version)
puts_line "Starting Chef Client, version #{version}"
end
def total_resources
- @up_to_date_resources + @updated_resources
+ @up_to_date_resources + @updated_resources + @skipped_resources
end
def total_audits
@@ -42,10 +55,30 @@ class Chef
def run_completed(node)
@end_time = Time.now
+ # Print out deprecations.
+ if !deprecations.empty?
+ puts_line ""
+ puts_line "Deprecated features used!"
+ deprecations.each do |message, locations|
+ if locations.size == 1
+ puts_line " #{message} at #{locations.size} location:"
+ else
+ puts_line " #{message} at #{locations.size} locations:"
+ end
+ locations.each do |location|
+ prefix = " - "
+ Array(location).each do |line|
+ puts_line "#{prefix}#{line}"
+ prefix = " "
+ end
+ end
+ end
+ puts_line ""
+ end
if Chef::Config[:why_run]
puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources would have been updated"
else
- puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{elapsed_time} seconds"
+ puts_line "Chef Client finished, #{@updated_resources}/#{total_resources} resources updated in #{pretty_elapsed_time}"
if total_audits > 0
puts_line " #{successful_audits}/#{total_audits} controls succeeded"
end
@@ -57,7 +90,7 @@ class Chef
if Chef::Config[:why_run]
puts_line "Chef Client failed. #{@updated_resources} resources would have been updated"
else
- puts_line "Chef Client failed. #{@updated_resources} resources updated in #{elapsed_time} seconds"
+ puts_line "Chef Client failed. #{@updated_resources} resources updated in #{pretty_elapsed_time}"
if total_audits > 0
puts_line " #{successful_audits} controls succeeded"
end
@@ -93,6 +126,10 @@ class Chef
def node_load_completed(node, expanded_run_list, config)
end
+ def policyfile_loaded(policy)
+ puts_line "Using policy '#{policy["name"]}' at revision '#{policy["revision_id"]}'"
+ end
+
# Called before the cookbook collection is fetched from the server.
def cookbook_resolution_start(expanded_run_list)
puts_line "resolving cookbooks for run list: #{expanded_run_list.inspect}"
@@ -128,9 +165,9 @@ class Chef
indent
end
- # Called when cookbook +cookbook_name+ has been sync'd
- def synchronized_cookbook(cookbook_name)
- puts_line "- #{cookbook_name}"
+ # Called when cookbook +cookbook+ has been sync'd
+ def synchronized_cookbook(cookbook_name, cookbook)
+ puts_line "- #{cookbook.name} (#{cookbook.version})"
end
# Called when an individual file in a cookbook has been updated
@@ -175,17 +212,21 @@ class Chef
puts_line "Starting audit phase"
end
- def audit_phase_complete
+ def audit_phase_complete(audit_output)
+ puts_line audit_output
puts_line "Auditing complete"
end
- def audit_phase_failed(error)
+ def audit_phase_failed(error, audit_output)
+ puts_line audit_output
puts_line ""
puts_line "Audit phase exception:"
indent
puts_line "#{error.message}"
- error.backtrace.each do |l|
- puts_line l
+ if error.backtrace
+ error.backtrace.each do |l|
+ puts_line l
+ end
end
end
@@ -228,6 +269,7 @@ class Chef
# Called when a resource action has been skipped b/c of a conditional
def resource_skipped(resource, action, conditional)
+ @skipped_resources += 1
# TODO: more info about conditional
puts " (skipped due to #{conditional.short_description})", :stream => resource
unindent
@@ -292,7 +334,7 @@ class Chef
# Called before handlers run
def handlers_start(handler_count)
- puts ''
+ puts ""
puts "Running handlers:"
indent
end
@@ -326,6 +368,16 @@ class Chef
end
end
+ def deprecation(message, location=caller(2..2)[0])
+ if Chef::Config[:treat_deprecation_warnings_as_errors]
+ super
+ end
+
+ # Save deprecations to the screen until the end
+ deprecations[message] ||= Set.new
+ deprecations[message] << location
+ end
+
def indent
indent_by(2)
end
@@ -333,6 +385,12 @@ class Chef
def unindent
indent_by(-2)
end
+
+ protected
+
+ def deprecations
+ @deprecations ||= {}
+ end
end
end
end
diff --git a/lib/chef/formatters/error_descriptor.rb b/lib/chef/formatters/error_descriptor.rb
index c2e656f167..a48f0609a9 100644
--- a/lib/chef/formatters/error_descriptor.rb
+++ b/lib/chef/formatters/error_descriptor.rb
@@ -48,8 +48,8 @@ class Chef
def for_json()
{
- 'title' => @title,
- 'sections' => @sections
+ "title" => @title,
+ "sections" => @sections,
}
end
diff --git a/lib/chef/formatters/error_inspectors.rb b/lib/chef/formatters/error_inspectors.rb
index 418457322d..9221ecda4d 100644
--- a/lib/chef/formatters/error_inspectors.rb
+++ b/lib/chef/formatters/error_inspectors.rb
@@ -1,9 +1,9 @@
-require 'chef/formatters/error_inspectors/node_load_error_inspector'
+require "chef/formatters/error_inspectors/node_load_error_inspector"
require "chef/formatters/error_inspectors/registration_error_inspector"
-require 'chef/formatters/error_inspectors/compile_error_inspector'
-require 'chef/formatters/error_inspectors/resource_failure_inspector'
-require 'chef/formatters/error_inspectors/run_list_expansion_error_inspector'
-require 'chef/formatters/error_inspectors/cookbook_resolve_error_inspector'
+require "chef/formatters/error_inspectors/compile_error_inspector"
+require "chef/formatters/error_inspectors/resource_failure_inspector"
+require "chef/formatters/error_inspectors/run_list_expansion_error_inspector"
+require "chef/formatters/error_inspectors/cookbook_resolve_error_inspector"
require "chef/formatters/error_inspectors/cookbook_sync_error_inspector"
class Chef
diff --git a/lib/chef/formatters/error_inspectors/api_error_formatting.rb b/lib/chef/formatters/error_inspectors/api_error_formatting.rb
index 652d478b40..5f2a912a01 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.
@@ -86,7 +106,7 @@ E
# Parses JSON from the error response sent by Chef Server and returns the
# error message
def format_rest_error
- Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join('; ')
+ Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join("; ")
rescue Exception
safe_format_rest_error
end
diff --git a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
index 93328adbe3..1c0cce9dd7 100644
--- a/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/compile_error_inspector.rb
@@ -30,19 +30,52 @@ class Chef
def initialize(path, exception)
@path, @exception = path, exception
+ @backtrace_lines_in_cookbooks = nil
+ @file_lines = nil
+ @culprit_backtrace_entry = nil
+ @culprit_line = nil
end
def add_explanation(error_description)
- case exception
- when Chef::Exceptions::RecipeNotFound
- error_description.section(exception.class.name, exception.message)
- else
- error_description.section(exception.class.name, exception.message)
+ error_description.section(exception.class.name, exception.message)
+ if found_error_in_cookbooks?
traceback = filtered_bt.map {|line| " #{line}"}.join("\n")
error_description.section("Cookbook Trace:", traceback)
error_description.section("Relevant File Content:", context)
end
+
+ if exception_message_modifying_frozen?
+ msg = <<-MESSAGE
+ Ruby objects are often frozen to prevent further modifications
+ when they would negatively impact the process (e.g. values inside
+ Ruby's ENV class) or to prevent polluting other objects when default
+ values are passed by reference to many instances of an object (e.g.
+ the empty Array as a Chef resource default, passed by reference
+ to every instance of the resource).
+
+ Chef uses Object#freeze to ensure the default values of properties
+ inside Chef resources are not modified, so that when a new instance
+ of a Chef resource is created, and Object#dup copies values by
+ reference, the new resource is not receiving a default value that
+ has been by a previous instance of that resource.
+
+ Instead of modifying an object that contains a default value for all
+ instances of a Chef resource, create a new object and assign it to
+ the resource's parameter, e.g.:
+
+ fruit_basket = resource(:fruit_basket, 'default')
+
+ # BAD: modifies 'contents' object for all new fruit_basket instances
+ fruit_basket.contents << 'apple'
+
+ # GOOD: allocates new array only owned by this fruit_basket instance
+ fruit_basket.contents %w(apple)
+
+ MESSAGE
+
+ error_description.section("Additional information:", msg.gsub(/^ {6}/, ""))
+ end
end
def context
@@ -75,7 +108,7 @@ class Chef
def culprit_backtrace_entry
@culprit_backtrace_entry ||= begin
bt_entry = filtered_bt.first
- Chef::Log.debug("backtrace entry for compile error: '#{bt_entry}'")
+ Chef::Log.debug("Backtrace entry for compile error: '#{bt_entry}'")
bt_entry
end
end
@@ -93,10 +126,25 @@ class Chef
end
def filtered_bt
- filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/ }
- r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
- Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}")
- return r.count > 0 ? r : exception.backtrace
+ backtrace_lines_in_cookbooks.count > 0 ? backtrace_lines_in_cookbooks : exception.backtrace
+ end
+
+ def found_error_in_cookbooks?
+ !backtrace_lines_in_cookbooks.empty?
+ end
+
+ def backtrace_lines_in_cookbooks
+ @backtrace_lines_in_cookbooks ||=
+ begin
+ filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/i }
+ r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
+ Chef::Log.debug("Filtered backtrace of compile error: #{r.join(",")}")
+ r
+ end
+ end
+
+ def exception_message_modifying_frozen?
+ exception.message.include?("can't modify frozen")
end
end
diff --git a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
index aa5eb8485d..a1f2c8ce37 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/formatters/error_inspectors/api_error_formatting'
+require "chef/formatters/error_inspectors/api_error_formatting"
class Chef
module Formatters
@@ -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..30811a6d24 100644
--- a/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/cookbook_sync_error_inspector.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/formatters/error_inspectors/api_error_formatting'
+require "chef/formatters/error_inspectors/api_error_formatting"
class Chef
module Formatters
@@ -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..6371243624 100644
--- a/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/node_load_error_inspector.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/formatters/error_inspectors/api_error_formatting'
+require "chef/formatters/error_inspectors/api_error_formatting"
class Chef
module Formatters
@@ -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..312e35adb6 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.
@@ -130,7 +134,7 @@ E
#--
# TODO: this code belongs in Chef::REST
def format_rest_error
- Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join('; ')
+ Array(Chef::JSONCompat.from_json(exception.response.body)["error"]).join("; ")
rescue Exception
exception.response.body
end
diff --git a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
index 48572d909b..83e22cb410 100644
--- a/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
+++ b/lib/chef/formatters/error_inspectors/resource_failure_inspector.rb
@@ -52,7 +52,7 @@ class Chef
end
if Chef::Platform.windows?
- require 'chef/win32/security'
+ require "chef/win32/security"
if !Chef::ReservedNames::Win32::Security.has_admin_privileges?
error_description.section("Missing Windows Admin Privileges", "chef-client doesn't have administrator privileges. This can be a possible reason for the resource failure.")
@@ -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..7ba5448227 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
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/formatters/error_inspectors/api_error_formatting'
+require "chef/formatters/error_inspectors/api_error_formatting"
class Chef
module Formatters
@@ -98,6 +98,8 @@ E
error_description.section("Possible Causes:",<<-E)
* Your client (#{username}) may have misconfigured authorization permissions.
E
+ when Net::HTTPNotAcceptable
+ describe_406_error(error_description, response)
when Net::HTTPInternalServerError
error_description.section("Unknown Server Error:",<<-E)
The server had a fatal error attempting to load a role.
diff --git a/lib/chef/formatters/indentable_output_stream.rb b/lib/chef/formatters/indentable_output_stream.rb
index 1beb286e7f..e5e84e0f65 100644
--- a/lib/chef/formatters/indentable_output_stream.rb
+++ b/lib/chef/formatters/indentable_output_stream.rb
@@ -19,7 +19,7 @@ class Chef
def highline
@highline ||= begin
- require 'highline'
+ require "highline"
HighLine.new
end
end
@@ -50,6 +50,11 @@ class Chef
print(string, from_args(args, :start_line => true, :end_line => true))
end
+ # Print a raw chunk
+ def <<(obj)
+ print(obj)
+ end
+
# Print a string.
#
# == Arguments
@@ -105,7 +110,7 @@ class Chef
def print_string(string, options)
if string.empty?
if options[:end_line]
- print_line('', options)
+ print_line("", options)
end
else
string.lines.each do |line|
@@ -131,7 +136,7 @@ class Chef
def move_to_next_line
if @line_started
- @out.puts ''
+ @out.puts ""
@line_started = false
end
end
@@ -146,11 +151,11 @@ class Chef
if @current_stream != options[:stream]
@out.print "#{(' ' * indent)}[#{options[:name]}] "
else
- @out.print ' ' * (indent + 3 + options[:name].size)
+ @out.print " " * (indent + 3 + options[:name].size)
end
else
# Otherwise, just print indents.
- @out.print ' ' * indent
+ @out.print " " * indent
end
if @current_stream != options[:stream]
diff --git a/lib/chef/formatters/minimal.rb b/lib/chef/formatters/minimal.rb
index a189cc67eb..2e32968b4b 100644
--- a/lib/chef/formatters/minimal.rb
+++ b/lib/chef/formatters/minimal.rb
@@ -1,4 +1,4 @@
-require 'chef/formatters/base'
+require "chef/formatters/base"
class Chef
@@ -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
@@ -130,7 +130,7 @@ class Chef
# Called after a file in a cookbook is loaded.
def file_loaded(path)
- print '.'
+ print "."
end
def file_load_failed(path, exception)
@@ -157,7 +157,7 @@ class Chef
puts "\n"
puts "resources updated this run:"
updated_resources.each do |resource|
- puts "* #{resource.to_s}"
+ puts "* #{resource}"
updates_by_resource[resource.name].flatten.each do |update|
puts " - #{update}"
end
diff --git a/lib/chef/guard_interpreter.rb b/lib/chef/guard_interpreter.rb
index b968f273b9..ba19a672aa 100644
--- a/lib/chef/guard_interpreter.rb
+++ b/lib/chef/guard_interpreter.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/guard_interpreter/default_guard_interpreter'
-require 'chef/guard_interpreter/resource_guard_interpreter'
+require "chef/guard_interpreter/default_guard_interpreter"
+require "chef/guard_interpreter/resource_guard_interpreter"
class Chef
class GuardInterpreter
diff --git a/lib/chef/guard_interpreter/default_guard_interpreter.rb b/lib/chef/guard_interpreter/default_guard_interpreter.rb
index df91c2b1ad..4ddab8acbc 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..e0fff72254 100644
--- a/lib/chef/guard_interpreter/resource_guard_interpreter.rb
+++ b/lib/chef/guard_interpreter/resource_guard_interpreter.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/guard_interpreter'
+require "chef/guard_interpreter"
class Chef
class GuardInterpreter
@@ -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
@@ -85,16 +88,19 @@ class Chef
resource_class = Chef::Resource.resource_for_node(parent_resource.guard_interpreter, parent_resource.node)
if resource_class.nil?
- raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter.to_s} unknown for this platform"
+ raise ArgumentError, "Specified guard_interpreter resource #{parent_resource.guard_interpreter} unknown for this platform"
end
if ! resource_class.ancestors.include?(Chef::Resource::Execute)
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)
- interpreter_resource = resource_class.new('Guard resource', anonymous_run_context)
+ 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
interpreter_resource
diff --git a/lib/chef/handler.rb b/lib/chef/handler.rb
index c4b729eeca..8ae1da96c1 100644
--- a/lib/chef/handler.rb
+++ b/lib/chef/handler.rb
@@ -15,8 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/client'
-require 'forwardable'
+require "chef/client"
+require "forwardable"
class Chef
# == Chef::Handler
diff --git a/lib/chef/handler/error_report.rb b/lib/chef/handler/error_report.rb
index 8bf676418d..6d0ad32d9f 100644
--- a/lib/chef/handler/error_report.rb
+++ b/lib/chef/handler/error_report.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/handler'
-require 'chef/resource/directory'
+require "chef/handler"
+require "chef/resource/directory"
class Chef
class Handler
diff --git a/lib/chef/handler/json_file.rb b/lib/chef/handler/json_file.rb
index 405c91795e..a695df3219 100644
--- a/lib/chef/handler/json_file.rb
+++ b/lib/chef/handler/json_file.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/handler'
-require 'chef/resource/directory'
+require "chef/handler"
+require "chef/resource/directory"
class Chef
class Handler
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 5e52337aff..9f76b9de3a 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2009, 2010, 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2009, 2010, 2013-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,14 +21,14 @@
# limitations under the License.
#
-require 'tempfile'
-require 'net/https'
-require 'uri'
-require 'chef/http/basic_client'
-require 'chef/monkey_patches/net_http'
-require 'chef/config'
-require 'chef/platform/query_helpers'
-require 'chef/exceptions'
+require "tempfile"
+require "net/https"
+require "uri"
+require "chef/http/basic_client"
+require "chef/monkey_patches/net_http"
+require "chef/config"
+require "chef/platform/query_helpers"
+require "chef/exceptions"
class Chef
@@ -74,6 +74,7 @@ class Chef
attr_reader :sign_on_redirect
attr_reader :redirect_limit
+ attr_reader :options
attr_reader :middlewares
# Create a HTTP client object. The supplied +url+ is used as the base for
@@ -86,6 +87,7 @@ class Chef
@sign_on_redirect = true
@redirects_followed = 0
@redirect_limit = 10
+ @options = options
@middlewares = []
self.class.middlewares.each do |middleware_class|
@@ -196,14 +198,26 @@ class Chef
def http_client(base_url=nil)
base_url ||= url
- BasicClient.new(base_url)
+ if chef_zero_uri?(base_url)
+ # PERFORMANCE CRITICAL: *MUST* lazy require here otherwise we load up webrick
+ # via chef-zero and that hits DNS (at *require* time) which may timeout,
+ # when for most knife/chef-client work we never need/want this loaded.
+ Thread.exclusive {
+ unless defined?(SocketlessChefZeroClient)
+ require "chef/http/socketless_chef_zero_client"
+ end
+ }
+ SocketlessChefZeroClient.new(base_url)
+ else
+ BasicClient.new(base_url, :ssl_policy => Chef::HTTP::APISSLPolicy)
+ 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)
@@ -211,7 +225,7 @@ class Chef
# The regular expressions used here are to make sure '@url' does not have
# any trailing slashes and 'path' does not have any leading slashes. This
# way they are always joined correctly using just one slash.
- URI.parse(@url.gsub(%r{/+$}, '') + '/' + path.gsub(%r{^/+}, ''))
+ URI.parse(@url.gsub(%r{/+$}, "") + "/" + path.gsub(%r{^/+}, ""))
end
end
@@ -292,7 +306,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")
@@ -302,7 +316,7 @@ class Chef
end
return [response, request, return_value]
end
- rescue SocketError, Errno::ETIMEDOUT => e
+ rescue SocketError, Errno::ETIMEDOUT, Errno::ECONNRESET => e
if http_retry_count - http_attempts + 1 > 0
Chef::Log.error("Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
sleep(http_retry_delay)
@@ -324,6 +338,13 @@ class Chef
retry
end
raise Timeout::Error, "Timeout connecting to #{url}, giving up"
+ rescue OpenSSL::SSL::SSLError => e
+ if (http_retry_count - http_attempts + 1 > 0) && !e.message.include?("certificate verify failed")
+ Chef::Log.error("SSL Error connecting to #{url}, retry #{http_attempts}/#{http_retry_count}")
+ sleep(http_retry_delay)
+ retry
+ end
+ raise OpenSSL::SSL::SSLError, "SSL Error connecting to #{url} - #{e.message}"
end
end
@@ -351,16 +372,21 @@ 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
return nil if response.kind_of?(Net::HTTPNotModified)
- response['location']
+ response["location"]
end
def build_headers(method, url, headers={}, json_body=false)
headers = @default_headers.merge(headers)
- headers['Content-Length'] = json_body.bytesize.to_s if json_body
+ headers["Content-Length"] = json_body.bytesize.to_s if json_body
headers.merge!(Chef::Config[:custom_http_headers]) if Chef::Config[:custom_http_headers]
headers
end
@@ -370,7 +396,7 @@ class Chef
if Chef::Platform.windows?
tf.binmode # required for binary files on Windows platforms
end
- Chef::Log.debug("Streaming download from #{url.to_s} to tempfile #{tf.path}")
+ Chef::Log.debug("Streaming download from #{url} to tempfile #{tf.path}")
# Stolen from http://www.ruby-forum.com/topic/166423
# Kudos to _why!
diff --git a/lib/chef/http/auth_credentials.rb b/lib/chef/http/auth_credentials.rb
index bd73524b1f..c583352cfd 100644
--- a/lib/chef/http/auth_credentials.rb
+++ b/lib/chef/http/auth_credentials.rb
@@ -20,8 +20,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/log'
-require 'mixlib/authentication/signedheaderauth'
+require "chef/log"
+require "mixlib/authentication/signedheaderauth"
class Chef
class HTTP
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index 4255f18cbd..02074171f8 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -16,14 +16,16 @@
# limitations under the License.
#
-require 'chef/http/auth_credentials'
-require 'chef/exceptions'
-require 'openssl'
+require "chef/http/auth_credentials"
+require "chef/exceptions"
+require "openssl"
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..defa4976d1 100644
--- a/lib/chef/http/basic_client.rb
+++ b/lib/chef/http/basic_client.rb
@@ -20,10 +20,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'uri'
-require 'net/http'
-require 'chef/http/ssl_policies'
-require 'chef/http/http_request'
+require "uri"
+require "net/http"
+require "chef/http/ssl_policies"
+require "chef/http/http_request"
class Chef
class HTTP
@@ -72,7 +72,7 @@ class Chef
Chef::Log.debug("---- End HTTP Status/Header Data ----")
# For non-400's, log the request and response bodies
- if !response.code || !response.code.start_with?('2')
+ if !response.code || !response.code.start_with?("2")
if response.body
Chef::Log.debug("---- HTTP Response Body ----")
Chef::Log.debug(response.body)
@@ -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/cookie_jar.rb b/lib/chef/http/cookie_jar.rb
index 418fb1d352..3f6d879c1f 100644
--- a/lib/chef/http/cookie_jar.rb
+++ b/lib/chef/http/cookie_jar.rb
@@ -20,7 +20,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'singleton'
+require "singleton"
class Chef
class HTTP
diff --git a/lib/chef/http/cookie_manager.rb b/lib/chef/http/cookie_manager.rb
index e9cb7aa4f7..4b48352382 100644
--- a/lib/chef/http/cookie_manager.rb
+++ b/lib/chef/http/cookie_manager.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/http/cookie_jar'
+require "chef/http/cookie_jar"
class Chef
class HTTP
@@ -34,14 +34,14 @@ class Chef
def handle_request(method, url, headers={}, data=false)
@host, @port = url.host, url.port
if @cookies.has_key?("#{@host}:#{@port}")
- headers['Cookie'] = @cookies["#{@host}:#{@port}"]
+ headers["Cookie"] = @cookies["#{@host}:#{@port}"]
end
[method, url, headers, data]
end
def handle_response(http_response, rest_request, return_value)
- if http_response['set-cookie']
- @cookies["#{@host}:#{@port}"] = http_response['set-cookie']
+ if http_response["set-cookie"]
+ @cookies["#{@host}:#{@port}"] = http_response["set-cookie"]
end
[http_response, rest_request, return_value]
end
diff --git a/lib/chef/http/decompressor.rb b/lib/chef/http/decompressor.rb
index e1d776da60..bc033cc35e 100644
--- a/lib/chef/http/decompressor.rb
+++ b/lib/chef/http/decompressor.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'zlib'
-require 'chef/http/http_request'
+require "zlib"
+require "chef/http/http_request"
class Chef
class HTTP
@@ -79,10 +79,10 @@ class Chef
else
case response[CONTENT_ENCODING]
when GZIP
- Chef::Log.debug "decompressing gzip response"
+ Chef::Log.debug "Decompressing gzip response"
Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body)
when DEFLATE
- Chef::Log.debug "decompressing deflate response"
+ Chef::Log.debug "Decompressing deflate response"
Zlib::Inflate.inflate(response.body)
else
response.body
@@ -132,7 +132,7 @@ class Chef
def handle_options(opts)
opts.each do |name, value|
case name.to_s
- when 'disable_gzip'
+ when "disable_gzip"
@disable_gzip = value
end
end
diff --git a/lib/chef/http/http_request.rb b/lib/chef/http/http_request.rb
index 7582f4458f..2dec4d8267 100644
--- a/lib/chef/http/http_request.rb
+++ b/lib/chef/http/http_request.rb
@@ -5,7 +5,7 @@
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2009, 2010-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,19 +20,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'uri'
-require 'net/http'
+require "uri"
+require "net/http"
# To load faster, we only want ohai's version string.
# However, in ohai before 0.6.0, the version is defined
# in ohai, not ohai/version
begin
- require 'ohai/version' #used in user agent string.
+ require "ohai/version" #used in user agent string.
rescue LoadError
- require 'ohai'
+ require "ohai"
end
-require 'chef/version'
+require "chef/version"
class Chef
class HTTP
@@ -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
@@ -60,7 +60,7 @@ class Chef
HOST_LOWER = "host".freeze
- URI_SCHEME_DEFAULT_PORT = { 'http' => 80, 'https' => 443 }.freeze
+ URI_SCHEME_DEFAULT_PORT = { "http" => 80, "https" => 443 }.freeze
def self.user_agent=(ua)
@user_agent = ua
@@ -125,9 +125,9 @@ class Chef
rescue NoMethodError => e
# http://redmine.ruby-lang.org/issues/show/2708
# http://redmine.ruby-lang.org/issues/show/2758
- if e.to_s =~ /#{Regexp.escape(%q|undefined method `closed?' for nil:NilClass|)}/
+ if e.to_s =~ /#{Regexp.escape(%q{undefined method `closed?' for nil:NilClass})}/
Chef::Log.debug("Rescued error in http connect, re-raising as Errno::ECONNREFUSED to hide bug in net/http")
- Chef::Log.debug("#{e.class.name}: #{e.to_s}")
+ Chef::Log.debug("#{e.class.name}: #{e}")
Chef::Log.debug(e.backtrace.join("\n"))
raise Errno::ECONNREFUSED, "Connection refused attempting to contact #{url.scheme}://#{host}:#{port}"
else
@@ -139,13 +139,13 @@ class Chef
@headers = headers.dup
# No response compression unless we asked for it explicitly:
@headers[HTTPRequest::ACCEPT_ENCODING] ||= "identity"
- @headers['X-Chef-Version'] = ::Chef::VERSION
+ @headers["X-Chef-Version"] = ::Chef::VERSION
# Only include port in Host header when it is not the default port
# for the url scheme (80;443) - Fixes CHEF-5355
host_header = uri_safe_host.dup
host_header << ":#{port}" unless URI_SCHEME_DEFAULT_PORT[@url.scheme] == port.to_i
- @headers['Host'] = host_header unless @headers.keys.any? {|k| k.downcase.to_s == HOST_LOWER }
+ @headers["Host"] = host_header unless @headers.keys.any? {|k| k.downcase.to_s == HOST_LOWER }
@headers
end
@@ -156,19 +156,19 @@ class Chef
req_path << "?#{query}" if query
@http_request = case method.to_s.downcase
- when GET
- Net::HTTP::Get.new(req_path, headers)
- when POST
- Net::HTTP::Post.new(req_path, headers)
- when PUT
- Net::HTTP::Put.new(req_path, headers)
- when DELETE
- Net::HTTP::Delete.new(req_path, headers)
- when HEAD
- Net::HTTP::Head.new(req_path, headers)
- else
- raise ArgumentError, "You must provide :GET, :PUT, :POST, :DELETE or :HEAD as the method"
- end
+ when GET
+ Net::HTTP::Get.new(req_path, headers)
+ when POST
+ Net::HTTP::Post.new(req_path, headers)
+ when PUT
+ Net::HTTP::Put.new(req_path, headers)
+ when DELETE
+ Net::HTTP::Delete.new(req_path, headers)
+ when HEAD
+ Net::HTTP::Head.new(req_path, headers)
+ else
+ raise ArgumentError, "You must provide :GET, :PUT, :POST, :DELETE or :HEAD as the method"
+ end
@http_request.body = request_body if (request_body && @http_request.request_body_permitted?)
# Optionally handle HTTP Basic Authentication
diff --git a/lib/chef/http/json_input.rb b/lib/chef/http/json_input.rb
index 23ccc3a8a7..3bfca38479 100644
--- a/lib/chef/http/json_input.rb
+++ b/lib/chef/http/json_input.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/json_compat'
+require "chef/json_compat"
class Chef
class HTTP
@@ -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)
+ headers.delete_if { |key, _value| key.downcase == "content-type" }
+ headers["Content-Type"] = "application/json"
+ 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/json_output.rb b/lib/chef/http/json_output.rb
index 069eb6a87f..86165d3d55 100644
--- a/lib/chef/http/json_output.rb
+++ b/lib/chef/http/json_output.rb
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/json_compat'
-require 'chef/log'
+require "chef/json_compat"
+require "chef/log"
class Chef
class HTTP
@@ -38,7 +38,7 @@ class Chef
# Ideally this should always set Accept to application/json, but
# Chef::REST is sometimes used to make non-JSON requests, so it sets
# Accept to the desired value before middlewares get called.
- headers['Accept'] ||= 'application/json'
+ headers["Accept"] ||= "application/json"
[method, url, headers, data]
end
@@ -46,7 +46,7 @@ class Chef
# temporary hack, skip processing if return_value is false
# needed to keep conditional get stuff working correctly.
return [http_response, rest_request, return_value] if return_value == false
- if http_response['content-type'] =~ /json/
+ if http_response["content-type"] =~ /json/
if http_response.body.nil?
return_value = nil
elsif raw_output
diff --git a/lib/chef/http/json_to_model_output.rb b/lib/chef/http/json_to_model_output.rb
index 9bc90a52ae..2290c90b0c 100644
--- a/lib/chef/http/json_to_model_output.rb
+++ b/lib/chef/http/json_to_model_output.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/http/json_output'
+require "chef/http/json_output"
class Chef
class HTTP
diff --git a/lib/chef/http/remote_request_id.rb b/lib/chef/http/remote_request_id.rb
index 6bec5dba4f..bfee5d3ded 100644
--- a/lib/chef/http/remote_request_id.rb
+++ b/lib/chef/http/remote_request_id.rb
@@ -15,7 +15,7 @@
# limitations under the License.
#
-require 'chef/request_id'
+require "chef/request_id"
class Chef
class HTTP
@@ -25,7 +25,7 @@ class Chef
end
def handle_request(method, url, headers={}, data=false)
- headers.merge!({'X-REMOTE-REQUEST-ID' => Chef::RequestID.instance.request_id})
+ headers.merge!({"X-REMOTE-REQUEST-ID" => Chef::RequestID.instance.request_id})
[method, url, headers, data]
end
diff --git a/lib/chef/http/simple.rb b/lib/chef/http/simple.rb
index 8519554f2b..d828e98186 100644
--- a/lib/chef/http/simple.rb
+++ b/lib/chef/http/simple.rb
@@ -1,8 +1,26 @@
-require 'chef/http'
-require 'chef/http/authenticator'
-require 'chef/http/decompressor'
-require 'chef/http/cookie_manager'
-require 'chef/http/validate_content_length'
+#
+# 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"
+require "chef/http/authenticator"
+require "chef/http/decompressor"
+require "chef/http/cookie_manager"
+require "chef/http/validate_content_length"
class Chef
class HTTP
diff --git a/lib/chef/http/simple_json.rb b/lib/chef/http/simple_json.rb
new file mode 100644
index 0000000000..5c5a87d387
--- /dev/null
+++ b/lib/chef/http/simple_json.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Thom May (<thom@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"
+require "chef/http/authenticator"
+require "chef/http/decompressor"
+require "chef/http/cookie_manager"
+require "chef/http/validate_content_length"
+
+class Chef
+ class HTTP
+
+ class SimpleJSON < HTTP
+
+ use JSONInput
+ use JSONOutput
+ use CookieManager
+ use Decompressor
+ use RemoteRequestID
+
+ # ValidateContentLength should come after Decompressor
+ # because the order of middlewares is reversed when handling
+ # responses.
+ use ValidateContentLength
+
+ end
+ end
+end
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..e81aff6fce
--- /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/http/ssl_policies.rb b/lib/chef/http/ssl_policies.rb
index 9c180c154e..5247d677cc 100644
--- a/lib/chef/http/ssl_policies.rb
+++ b/lib/chef/http/ssl_policies.rb
@@ -21,8 +21,8 @@
# limitations under the License.
#
-require 'openssl'
-require 'chef/util/path_helper'
+require "openssl"
+require "chef/util/path_helper"
class Chef
class HTTP
@@ -110,7 +110,7 @@ class Chef
def add_trusted_cert(cert)
http_client.cert_store.add_cert(cert)
rescue OpenSSL::X509::StoreError => e
- raise e unless e.message == 'cert already in hash table'
+ raise e unless e.message == "cert already in hash table"
end
end
diff --git a/lib/chef/http/validate_content_length.rb b/lib/chef/http/validate_content_length.rb
index 076194e31a..77fb6194ef 100644
--- a/lib/chef/http/validate_content_length.rb
+++ b/lib/chef/http/validate_content_length.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'pp'
-require 'chef/log'
+require "pp"
+require "chef/log"
class Chef
class HTTP
@@ -73,18 +73,18 @@ class Chef
private
def response_content_length(response)
- return nil if response['content-length'].nil?
- if response['content-length'].is_a?(Array)
- response['content-length'].first.to_i
+ return nil if response["content-length"].nil?
+ if response["content-length"].is_a?(Array)
+ response["content-length"].first.to_i
else
- response['content-length'].to_i
+ response["content-length"].to_i
end
end
def validate(http_response, response_length)
content_length = response_content_length(http_response)
- transfer_encoding = http_response['transfer-encoding']
- content_encoding = http_response['content-encoding']
+ transfer_encoding = http_response["transfer-encoding"]
+ content_encoding = http_response["content-encoding"]
if content_length.nil?
Chef::Log.debug "HTTP server did not include a Content-Length header in response, cannot identify truncated downloads."
diff --git a/lib/chef/json_compat.rb b/lib/chef/json_compat.rb
index d0b3b4c7f8..74059516ab 100644
--- a/lib/chef/json_compat.rb
+++ b/lib/chef/json_compat.rb
@@ -17,10 +17,10 @@
# Wrapper class for interacting with JSON.
-require 'ffi_yajl'
-require 'chef/exceptions'
+require "ffi_yajl"
+require "chef/exceptions"
# We're requiring this to prevent breaking consumers using Hash.to_json
-require 'json'
+require "json"
class Chef
class JSONCompat
@@ -41,6 +41,7 @@ class Chef
CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze
CHEF_RESOURCESET = "Chef::ResourceCollection::ResourceSet".freeze
CHEF_RESOURCELIST = "Chef::ResourceCollection::ResourceList".freeze
+ CHEF_RUNLISTEXPANSION = "Chef::RunListExpansion".freeze
class <<self
diff --git a/lib/chef/key.rb b/lib/chef/key.rb
new file mode 100644
index 0000000000..ba5613e35e
--- /dev/null
+++ b/lib/chef/key.rb
@@ -0,0 +1,272 @@
+#
+# 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"
+require "chef/server_api"
+
+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::ServerAPI 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::ServerAPI.new(Chef::Config[:chef_server_root])
+ else
+ Chef::ServerAPI.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("#{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("#{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("#{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::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys")
+ self.list(keys, actor, :load_by_user, inflate)
+ end
+
+ def self.list_by_client(actor, inflate=false)
+ keys = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("clients/#{actor}/keys")
+ self.list(keys, actor, :load_by_client, inflate)
+ end
+
+ def self.load_by_user(actor, key_name)
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("users/#{actor}/keys/#{key_name}")
+ Chef::Key.from_hash(response.merge({"user" => actor}))
+ end
+
+ def self.load_by_client(actor, key_name)
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("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..a070c6c858 100644
--- a/lib/chef/knife.rb
+++ b/lib/chef/knife.rb
@@ -17,22 +17,25 @@
# limitations under the License.
#
-require 'forwardable'
-require 'chef/version'
-require 'mixlib/cli'
-require 'chef/workstation_config_loader'
-require 'chef/mixin/convert_to_class_name'
-require 'chef/mixin/path_sanity'
-require 'chef/knife/core/subcommand_loader'
-require 'chef/knife/core/ui'
-require 'chef/local_mode'
-require 'chef/rest'
-require 'pp'
+require "forwardable"
+require "chef/version"
+require "mixlib/cli"
+require "chef/workstation_config_loader"
+require "chef/mixin/convert_to_class_name"
+require "chef/mixin/path_sanity"
+require "chef/knife/core/subcommand_loader"
+require "chef/knife/core/ui"
+require "chef/local_mode"
+require "chef/server_api"
+require "chef/http/authenticator"
+require "chef/http/http_request"
+require "chef/http"
+require "pp"
class Chef
class Knife
- Chef::REST::RESTRequest.user_agent = "Chef Knife#{Chef::REST::RESTRequest::UA_COMMON}"
+ Chef::HTTP::HTTPRequest.user_agent = "Chef Knife#{Chef::HTTP::HTTPRequest::UA_COMMON}"
include Mixlib::CLI
include Chef::Mixin::PathSanity
@@ -86,6 +89,16 @@ class Chef
def self.inherited(subclass)
unless subclass.unnamed?
subcommands[subclass.snake_case_name] = subclass
+ subcommand_files[subclass.snake_case_name] +=
+ if subclass.superclass.to_s == "Chef::ChefFS::Knife"
+ # ChefFS-based commands have a superclass that defines an
+ # inhereited method which calls super. This means that the
+ # top of the call stack is not the class definition for
+ # our subcommand. Try the second entry in the call stack.
+ [path_from_caller(caller[1])]
+ else
+ [path_from_caller(caller[0])]
+ end
end
end
@@ -103,15 +116,15 @@ class Chef
end
def self.subcommand_category
- @category || snake_case_name.split('_').first unless unnamed?
+ @category || snake_case_name.split("_").first unless unnamed?
end
def self.snake_case_name
- convert_to_snake_case(name.split('::').last) unless unnamed?
+ convert_to_snake_case(name.split("::").last) unless unnamed?
end
def self.common_name
- snake_case_name.split('_').join(' ')
+ snake_case_name.split("_").join(" ")
end
# Does this class have a name? (Classes created via Class.new don't)
@@ -120,17 +133,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 +166,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
@@ -200,12 +201,11 @@ class Chef
# config file is read may be lost. If the KNIFE_DEBUG variable is set, we
# setup the logger for debug logging to stderr immediately to catch info
# from early in the setup process.
- if ENV['KNIFE_DEBUG']
+ if ENV["KNIFE_DEBUG"]
Chef::Log.init($stderr)
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 +214,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
@@ -258,13 +230,23 @@ class Chef
private
- OFFICIAL_PLUGINS = %w[ec2 rackspace windows openstack terremark bluebox]
+ OFFICIAL_PLUGINS = %w{ec2 rackspace windows openstack terremark bluebox}
+
+ def self.path_from_caller(caller_line)
+ caller_line.split(/:\d+/).first
+ end
# :nodoc:
# Error out and print usage. probably because the arguments given by the
# user could not be resolved to a subcommand.
def self.subcommand_not_found!(args)
- ui.fatal("Cannot find sub command for: '#{args.join(' ')}'")
+ ui.fatal("Cannot find subcommand for: '#{args.join(' ')}'")
+
+ # Mention rehash when the subcommands cache(plugin_manifest.json) is used
+ if subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::HashedCommandLoader) ||
+ subcommand_loader.is_a?(Chef::Knife::SubcommandLoader::CustomManifestLoader)
+ ui.info("If this is a recently installed plugin, please run 'knife rehash' to update the subcommands cache.")
+ end
if category_commands = guess_category(args)
list_commands(category_commands)
@@ -279,6 +261,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
@@ -293,16 +289,16 @@ class Chef
super() # having to call super in initialize is the most annoying anti-pattern :(
@ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, config)
- command_name_words = self.class.snake_case_name.split('_')
+ command_name_words = self.class.snake_case_name.split("_")
# Mixlib::CLI ignores the embedded name_args
@name_args = parse_options(argv)
- @name_args.delete(command_name_words.join('-'))
+ @name_args.delete(command_name_words.join("-"))
@name_args.reject! { |name_arg| command_name_words.delete(name_arg) }
# knife node run_list add requires that we have extra logic to handle
# the case that command name words could be joined by an underscore :/
- command_name_words = command_name_words.join('_')
+ command_name_words = command_name_words.join("_")
@name_args.reject! { |name_arg| command_name_words == name_arg }
if config[:help]
@@ -312,7 +308,7 @@ class Chef
# copy Mixlib::CLI over so that it can be configured in knife.rb
# config file
- Chef::Config[:verbosity] = config[:verbosity]
+ Chef::Config[:verbosity] = config[:verbosity] if config[:verbosity]
end
def parse_options(args)
@@ -358,14 +354,14 @@ 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
Chef::Config[:log_level] = :debug
end
- Chef::Config[:log_level] = :debug if ENV['KNIFE_DEBUG']
+ Chef::Config[:log_level] = :debug if ENV["KNIFE_DEBUG"]
Chef::Config[:node_name] = config[:node_name] if config[:node_name]
Chef::Config[:client_key] = config[:client_key] if config[:client_key]
@@ -373,6 +369,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
@@ -388,20 +387,17 @@ class Chef
Mixlib::Log::Formatter.show_time = false
Chef::Log.init(Chef::Config[:log_location])
Chef::Log.level(Chef::Config[:log_level] || :error)
-
- if Chef::Config[:node_name] && Chef::Config[:node_name].bytesize > 90
- # node names > 90 bytes only work with authentication protocol >= 1.1
- # see discussion in config.rb.
- Chef::Config[:authentication_protocol_version] = "1.1"
- end
end
def configure_chef
+ # knife needs to send logger output to STDERR by default
+ Chef::Config[:log_location] = STDERR
config_loader = self.class.load_config(config[:config_file])
config[:config_file] = config_loader.config_location
merge_configs
apply_computed_config
+ Chef::Config.export_proxies
# This has to be after apply_computed_config so that Mixlib::Log is configured
Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file]
end
@@ -480,6 +476,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)}"
@@ -499,7 +504,7 @@ class Chef
#--
# TODO: this code belongs in Chef::REST
def format_rest_error(response)
- Array(Chef::JSONCompat.from_json(response.body)["error"]).join('; ')
+ Array(Chef::JSONCompat.from_json(response.body)["error"]).join("; ")
rescue Exception
response.body
end
@@ -536,17 +541,27 @@ 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'
- Chef::REST.new(Chef::Config[:chef_server_url])
+ require "chef/server_api"
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
end
def noauth_rest
@rest ||= begin
- require 'chef/rest'
- Chef::REST.new(Chef::Config[:chef_server_url], false, false)
+ require "chef/http/simple_json"
+ Chef::HTTP::SimpleJSON.new(Chef::Config[:chef_server_url])
end
end
diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb
index a4095e8402..23ec98e563 100644
--- a/lib/chef/knife/bootstrap.rb
+++ b/lib/chef/knife/bootstrap.rb
@@ -16,12 +16,12 @@
# limitations under the License.
#
-require 'chef/knife'
-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'
+require "chef/knife"
+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
@@ -32,17 +32,17 @@ class Chef
attr_accessor :chef_vault_handler
deps do
- require 'chef/knife/core/bootstrap_context'
- require 'chef/json_compat'
- require 'tempfile'
- require 'highline'
- require 'net/ssh'
- require 'net/ssh/multi'
- require 'chef/knife/ssh'
+ require "chef/knife/core/bootstrap_context"
+ require "chef/json_compat"
+ require "tempfile"
+ require "highline"
+ require "net/ssh"
+ require "net/ssh/multi"
+ require "chef/knife/ssh"
Chef::Knife::Ssh.load_deps
end
- banner "knife bootstrap FQDN (options)"
+ banner "knife bootstrap [SSH_USER@]FQDN (options)"
option :ssh_user,
:short => "-x USERNAME",
@@ -74,8 +74,12 @@ class Chef
:boolean => true
option :identity_file,
- :short => "-i IDENTITY_FILE",
:long => "--identity-file IDENTITY_FILE",
+ :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead."
+
+ option :ssh_identity_file,
+ :short => "-i IDENTITY_FILE",
+ :long => "--ssh-identity-file IDENTITY_FILE",
:description => "The SSH identity file used for authentication"
option :chef_node_name,
@@ -122,6 +126,11 @@ class Chef
:description => "Execute the bootstrap via sudo",
:boolean => true
+ option :preserve_home,
+ :long => "--sudo-preserve-home",
+ :description => "Preserve non-root user HOME environment variable with sudo",
+ :boolean => true
+
option :use_sudo_password,
:long => "--use-sudo-password",
:description => "Execute the bootstrap via sudo with password",
@@ -143,12 +152,34 @@ class Chef
:proc => lambda { |o| o.split(/[\s,]+/) },
:default => []
+ option :policy_name,
+ :long => "--policy-name POLICY_NAME",
+ :description => "Policyfile name to use (--policy-group must also be given)",
+ :default => nil
+
+ option :policy_group,
+ :long => "--policy-group POLICY_GROUP",
+ :description => "Policy group name to use (--policy-name must also be given)",
+ :default => nil
+
+ option :tags,
+ :long => "--tags TAGS",
+ :description => "Comma separated list of tags to apply to the node",
+ :proc => lambda { |o| o.split(/[\s,]+/) },
+ :default => []
+
option :first_boot_attributes,
:short => "-j JSON_ATTRIBS",
:long => "--json-attributes",
:description => "A JSON string to be added to the first run of chef-client",
:proc => lambda { |o| Chef::JSONCompat.parse(o) },
- :default => {}
+ :default => nil
+
+ option :first_boot_attributes_from_file,
+ :long => "--json-attribute-file FILE",
+ :description => "A JSON file to be used to the first run of chef-client",
+ :proc => lambda { |o| Chef::JSONCompat.parse(File.read(o)) },
+ :default => nil
option :host_key_verify,
:long => "--[no-]host-key-verify",
@@ -201,15 +232,15 @@ class Chef
:boolean => true
option :bootstrap_vault_file,
- :long => '--bootstrap-vault-file VAULT_FILE',
- :description => 'A JSON file with a list of vault(s) and item(s) to be updated'
+ :long => "--bootstrap-vault-file VAULT_FILE",
+ :description => "A JSON file with a list of vault(s) and item(s) to be updated"
option :bootstrap_vault_json,
- :long => '--bootstrap-vault-json VAULT_JSON',
- :description => 'A JSON string with the vault(s) and item(s) to be updated'
+ :long => "--bootstrap-vault-json VAULT_JSON",
+ :description => "A JSON string with the vault(s) and item(s) to be updated"
option :bootstrap_vault_item,
- :long => '--bootstrap-vault-item VAULT_ITEM',
+ :long => "--bootstrap-vault-item VAULT_ITEM",
:description => 'A single vault and item to update as "vault:item"',
:proc => Proc.new { |i|
(vault, item) = i.split(/:/)
@@ -228,7 +259,7 @@ class Chef
)
@chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(
knife_config: config,
- ui: ui
+ ui: ui,
)
end
@@ -240,13 +271,25 @@ class Chef
"chef-full"
end
+ def host_descriptor
+ Array(@name_args).first
+ end
+
# The server_name is the DNS or IP we are going to connect to, it is not necessarily
# the node name, the fqdn, or the hostname of the server. This is a public API hook
# which knife plugins use or inherit and override.
#
# @return [String] The DNS or IP that bootstrap will connect to
def server_name
- Array(@name_args).first
+ if host_descriptor
+ @server_name ||= host_descriptor.split("@").reverse[0]
+ end
+ end
+
+ def user_name
+ if host_descriptor
+ @user_name ||= host_descriptor.split("@").reverse[1]
+ end
end
def bootstrap_template
@@ -267,9 +310,9 @@ class Chef
# Otherwise search the template directories until we find the right one
bootstrap_files = []
- bootstrap_files << File.join(File.dirname(__FILE__), 'bootstrap/templates', "#{template}.erb")
+ 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
- Chef::Util::PathHelper.home('.chef', 'bootstrap', "#{template}.erb") {|p| bootstrap_files << p}
+ 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!
@@ -297,18 +340,28 @@ class Chef
config,
config[:run_list],
Chef::Config,
- secret
+ secret,
)
end
+ def first_boot_attributes
+ @config[:first_boot_attributes] || @config[:first_boot_attributes_from_file] || {}
+ end
+
def render_template
+ @config[:first_boot_attributes] = first_boot_attributes
template_file = find_template
template = IO.read(template_file).chomp
Erubis::Eruby.new(template).evaluate(bootstrap_context)
end
def run
+ if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file]
+ raise Chef::Exceptions::BootstrapCommandInputError
+ end
+
validate_name_args!
+ validate_options!
$stdout.sync = true
@@ -316,9 +369,15 @@ 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])
+ chef_vault_handler.run(client_builder.client)
bootstrap_context.client_pem = client_builder.client_path
else
@@ -335,7 +394,7 @@ class Chef
if config[:ssh_password]
raise
else
- ui.info("Failed to authenticate #{config[:ssh_user]} - trying password auth")
+ ui.info("Failed to authenticate #{knife_ssh.config[:ssh_user]} - trying password auth")
knife_ssh_with_password_auth.run
end
end
@@ -351,16 +410,27 @@ class Chef
end
end
+ def validate_options!
+ if incomplete_policyfile_options?
+ ui.error("--policy-name and --policy-group must be specified together")
+ exit 1
+ elsif policyfile_and_run_list_given?
+ ui.error("Policyfile options and --run-list are exclusive")
+ exit 1
+ end
+ true
+ end
+
def knife_ssh
ssh = Chef::Knife::Ssh.new
ssh.ui = ui
ssh.name_args = [ server_name, ssh_command ]
- ssh.config[:ssh_user] = config[:ssh_user]
+ ssh.config[:ssh_user] = user_name || config[:ssh_user]
ssh.config[:ssh_password] = config[:ssh_password]
ssh.config[:ssh_port] = config[:ssh_port]
ssh.config[:ssh_gateway] = config[:ssh_gateway]
ssh.config[:forward_agent] = config[:forward_agent]
- ssh.config[:identity_file] = config[:identity_file]
+ ssh.config[:ssh_identity_file] = config[:ssh_identity_file] || config[:identity_file]
ssh.config[:manual] = true
ssh.config[:host_key_verify] = config[:host_key_verify]
ssh.config[:on_error] = :raise
@@ -369,7 +439,7 @@ class Chef
def knife_ssh_with_password_auth
ssh = knife_ssh
- ssh.config[:identity_file] = nil
+ ssh.config[:ssh_identity_file] = nil
ssh.config[:ssh_password] = ssh.get_password
ssh
end
@@ -378,11 +448,33 @@ class Chef
command = render_template
if config[:use_sudo]
- command = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -SH #{command}" : "sudo -H #{command}"
+ sudo_prefix = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S " : "sudo "
+ command = config[:preserve_home] ? "#{sudo_prefix} #{command}" : "#{sudo_prefix} -H #{command}"
end
command
end
+
+ private
+
+ # True if policy_name and run_list are both given
+ def policyfile_and_run_list_given?
+ run_list_given? && policyfile_options_given?
+ end
+
+ def run_list_given?
+ !config[:run_list].nil? && !config[:run_list].empty?
+ end
+
+ def policyfile_options_given?
+ !!config[:policy_name]
+ end
+
+ # True if one of policy_name or policy_group was given, but not both
+ def incomplete_policyfile_options?
+ (!!config[:policy_name] ^ config[:policy_group])
+ end
+
end
end
end
diff --git a/lib/chef/knife/bootstrap/chef_vault_handler.rb b/lib/chef/knife/bootstrap/chef_vault_handler.rb
index 749f61e6da..6732c0f28d 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
@@ -27,8 +28,8 @@ class Chef
# @return [Chef::Knife::UI] ui object for output
attr_accessor :ui
- # @return [String] name of the node (technically name of the client)
- attr_reader :node_name
+ # @return [Chef::ApiClient] vault client
+ attr_reader :client
# @param knife_config [Hash] knife merged config, typically @config
# @param ui [Chef::Knife::UI] ui object for output
@@ -37,18 +38,15 @@ class Chef
@ui = ui
end
- # Updates the chef vault items for the newly created node.
+ # Updates the chef vault items for the newly created client.
#
- # @param node_name [String] name of the node (technically name of the client)
- # @todo: node_name should be mandatory (ruby 2.0 compat)
- def run(node_name: nil)
+ # @param client [Chef::ApiClient] vault client
+ def run(client)
return unless doing_chef_vault?
sanity_check
- @node_name = node_name
-
- ui.info("Updating Chef Vault, waiting for client to be searchable..") while wait_for_client
+ @client = client
update_bootstrap_vault_json!
end
@@ -125,7 +123,7 @@ class Chef
def update_vault(vault, item)
require_chef_vault!
bootstrap_vault_item = load_chef_bootstrap_vault_item(vault, item)
- bootstrap_vault_item.clients("name:#{node_name}")
+ bootstrap_vault_item.clients(client)
bootstrap_vault_item.save
end
@@ -140,22 +138,18 @@ class Chef
public :load_chef_bootstrap_vault_item # for stubbing
- # Helper used to spin waiting for the client to appear in search.
- #
- # @return [Boolean] true if the client is searchable
- def wait_for_client
- sleep 1
- !Chef::Search::Query.new.search(:client, "name:#{node_name}")[0]
- end
-
# Helper to very lazily require the chef-vault gem
def require_chef_vault!
@require_chef_vault ||=
begin
- require 'chef-vault'
+ error_message = "Knife bootstrap needs version 2.6.0 or higher of the chef-vault gem to configure chef vault items"
+ require "chef-vault"
+ if Gem::Version.new(ChefVault::VERSION) < Gem::Version.new("2.6.0")
+ raise error_message
+ end
true
rescue LoadError
- raise "Knife bootstrap cannot configure chef vault items when the chef-vault gem is not installed"
+ raise error_message
end
end
diff --git a/lib/chef/knife/bootstrap/client_builder.rb b/lib/chef/knife/bootstrap/client_builder.rb
index b9c1d98bec..a955b759e2 100644
--- a/lib/chef/knife/bootstrap/client_builder.rb
+++ b/lib/chef/knife/bootstrap/client_builder.rb
@@ -16,11 +16,12 @@
# limitations under the License.
#
-require 'chef/node'
-require 'chef/rest'
-require 'chef/api_client/registration'
-require 'chef/api_client'
-require 'tmpdir'
+require "chef/node"
+require "chef/server_api"
+require "chef/api_client/registration"
+require "chef/api_client"
+require "chef/knife/bootstrap"
+require "tmpdir"
class Chef
class Knife
@@ -33,6 +34,8 @@ class Chef
attr_accessor :chef_config
# @return [Chef::Knife::UI] ui object for output
attr_accessor :ui
+ # @return [Chef::ApiClient] client saved on run
+ attr_reader :client
# @param knife_config [Hash] Hash of knife config settings
# @param chef_config [Hash] Hash of chef config settings
@@ -50,7 +53,7 @@ class Chef
ui.info("Creating new client for #{node_name}")
- create_client!
+ @client = create_client!
ui.info("Creating new node for #{node_name}")
@@ -90,6 +93,16 @@ class Chef
knife_config[:run_list]
end
+ # @return [String] policy_name from the knife_config
+ def policy_name
+ knife_config[:policy_name]
+ end
+
+ # @return [String] policy_group from the knife_config
+ def policy_group
+ knife_config[:policy_group]
+ end
+
# @return [Hash,Array] Object representation of json first-boot attributes from the knife_config
def first_boot_attributes
knife_config[:first_boot_attributes]
@@ -140,6 +153,11 @@ class Chef
node.run_list(normalized_run_list)
node.normal_attrs = first_boot_attributes if first_boot_attributes
node.environment(environment) if environment
+ node.policy_name = policy_name if policy_name
+ node.policy_group = policy_group if policy_group
+ (knife_config[:tags] || []).each do |tag|
+ node.tags << tag
+ end
node
end
end
@@ -167,22 +185,22 @@ class Chef
# @param relative_path [String] URI path relative to the chef organization
# @return [Boolean] if the relative path exists or returns a 404
def resource_exists?(relative_path)
- rest.get_rest(relative_path)
+ rest.get(relative_path)
true
rescue Net::HTTPServerException => e
raise unless e.response.code == "404"
false
end
- # @return [Chef::REST] REST client using the client credentials
+ # @return [Chef::ServerAPI] REST client using the client credentials
def client_rest
- @client_rest ||= Chef::REST.new(chef_server_url, node_name, client_path)
+ @client_rest ||= Chef::ServerAPI.new(chef_server_url, :client_name => node_name, :signing_key_filename => client_path)
end
- # @return [Chef::REST] REST client using the cli user's knife credentials
+ # @return [Chef::ServerAPI] REST client using the cli user's knife credentials
# this uses the users's credentials
def rest
- @rest ||= Chef::REST.new(chef_server_url)
+ @rest ||= Chef::ServerAPI.new(chef_server_url)
end
end
end
diff --git a/lib/chef/knife/bootstrap/templates/README.md b/lib/chef/knife/bootstrap/templates/README.md
index 13a0fe7ada..b5bca25d8e 100644
--- a/lib/chef/knife/bootstrap/templates/README.md
+++ b/lib/chef/knife/bootstrap/templates/README.md
@@ -1,12 +1,11 @@
This directory contains bootstrap templates which can be used with the -d flag
to 'knife bootstrap' to install Chef in different ways. To simplify installation,
and reduce the matrix of common installation patterns to support, we have
-standardized on the [Omnibus](https://github.com/opscode/omnibus-ruby) built installation
+standardized on the [Omnibus](https://github.com/chef/omnibus) built installation
packages.
The 'chef-full' template downloads a script which is used to determine the correct
-Omnibus package for this system from the [Omnitruck](https://github.com/opscode/opscode-omnitruck) API. All other templates in this directory are deprecated and will be removed
-in the future.
+Omnibus package for this system from the [Omnitruck](https://docs.chef.io/api_omnitruck.html) API.
You can still utilize custom bootstrap templates on your system if your installation
-needs are unique. Additional information can be found on the [docs site](http://docs.opscode.com/knife_bootstrap.html#custom-templates). \ No newline at end of file
+needs are unique. Additional information can be found on the [docs site](https://docs.chef.io/knife_bootstrap.html#custom-templates).
diff --git a/lib/chef/knife/bootstrap/templates/archlinux-gems.erb b/lib/chef/knife/bootstrap/templates/archlinux-gems.erb
deleted file mode 100644
index 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 a87ab8e544..020645c869 100644
--- a/lib/chef/knife/bootstrap/templates/chef-full.erb
+++ b/lib/chef/knife/bootstrap/templates/chef-full.erb
@@ -1,17 +1,18 @@
-bash -c '
+sh -c '
<%= "export https_proxy=\"#{knife_config[:bootstrap_proxy]}\"" if knife_config[:bootstrap_proxy] -%>
-distro=`uname -s`
-
-if test "x$distro" = "xSunOS"; then
- if test -d "/usr/sfw/bin"; then
- PATH=/usr/sfw/bin:$PATH
- export PATH
- fi
+if test "x$TMPDIR" = "x"; then
+ tmp="/tmp"
+else
+ tmp=$TMPDIR
fi
+# secure-ish temp dir creation without having mktemp available (DDoS-able but not exploitable)
+tmp_dir="$tmp/install.sh.$$"
+(umask 077 && mkdir $tmp_dir) || exit 1
+
exists() {
- if command -v $1 &>/dev/null
+ if command -v $1 >/dev/null 2>&1
then
return 0
else
@@ -19,41 +20,183 @@ exists() {
fi
}
+http_404_error() {
+ echo "ERROR 404: Could not retrieve a valid install.sh!"
+ exit 1
+}
+
+capture_tmp_stderr() {
+ # spool up /tmp/stderr from all the commands we called
+ if test -f "$tmp_dir/stderr"; then
+ output=`cat $tmp_dir/stderr`
+ stderr_results="${stderr_results}\nSTDERR from $1:\n\n$output\n"
+ rm $tmp_dir/stderr
+ fi
+}
+
+# do_wget URL FILENAME
+do_wget() {
+ echo "trying wget..."
+ wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> -O "$2" "$1" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "ERROR 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "wget"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_curl URL FILENAME
+do_curl() {
+ echo "trying curl..."
+ curl -sL <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> -D $tmp_dir/stderr -o "$2" "$1" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "curl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_fetch URL FILENAME
+do_fetch() {
+ echo "trying fetch..."
+ fetch -o "$2" "$1" 2>$tmp_dir/stderr
+ # check for bad return status
+ test $? -ne 0 && return 1
+ return 0
+}
+
+# do_perl URL FILENAME
+do_perl() {
+ echo "trying perl..."
+ perl -e "use LWP::Simple; getprint(shift @ARGV);" "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "404 Not Found" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "perl"
+ return 1
+ fi
+
+ return 0
+}
+
+# do_python URL FILENAME
+do_python() {
+ echo "trying python..."
+ python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" 2>$tmp_dir/stderr
+ rc=$?
+ # check for 404
+ grep "HTTP Error 404" $tmp_dir/stderr 2>&1 >/dev/null
+ if test $? -eq 0; then
+ http_404_error
+ fi
+
+ # check for bad return status or empty output
+ if test $rc -ne 0 || test ! -s "$2"; then
+ capture_tmp_stderr "python"
+ return 1
+ fi
+ return 0
+}
+
+# do_download URL FILENAME
+do_download() {
+ PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sfw/bin:/sbin:/bin:/usr/sbin:/usr/bin
+ export PATH
+
+ echo "downloading $1"
+ echo " to file $2"
+
+ # we try all of these until we get success.
+ # perl, in particular may be present but LWP::Simple may not be installed
+
+ if exists wget; then
+ do_wget $1 $2 && return 0
+ fi
+
+ if exists curl; then
+ do_curl $1 $2 && return 0
+ fi
+
+ if exists fetch; then
+ do_fetch $1 $2 && return 0
+ fi
+
+ if exists perl; then
+ do_perl $1 $2 && return 0
+ fi
+
+ if exists python; then
+ do_python $1 $2 && return 0
+ fi
+
+ echo ">>>>>> wget, curl, fetch, perl, or python not found on this instance."
+
+ if test "x$stderr_results" != "x"; then
+ echo "\nDEBUG OUTPUT FOLLOWS:\n$stderr_results"
+ fi
+
+ return 16
+}
+
<% if knife_config[:bootstrap_install_command] %>
<%= knife_config[:bootstrap_install_command] %>
<% else %>
- install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://www.opscode.com/chef/install.sh" %>"
- if ! exists /usr/bin/chef-client; then
- echo "Installing Chef Client..."
- if exists wget; then
- bash <(wget <%= "--proxy=on " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_wget_options] %> ${install_sh} -O -) <%= latest_current_chef_version_string %>
- elif exists curl; then
- bash <(curl -L <%= "--proxy \"#{knife_config[:bootstrap_proxy]}\" " if knife_config[:bootstrap_proxy] %> <%= knife_config[:bootstrap_curl_options] %> ${install_sh}) <%= latest_current_chef_version_string %>
- else
- echo "Neither wget nor curl found. Please install one and try again." >&2
- exit 1
- fi
+ install_sh="<%= knife_config[:bootstrap_url] ? knife_config[:bootstrap_url] : "https://omnitruck-direct.chef.io/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..9ee9a303df 100644
--- a/lib/chef/knife/client_bulk_delete.rb
+++ b/lib/chef/knife/client_bulk_delete.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class ClientBulkDelete < Knife
deps do
- require 'chef/api_client'
- require 'chef/json_compat'
+ require "chef/api_client_v1"
+ require "chef/json_compat"
end
option :delete_validators,
@@ -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..43f087e5d0 100644
--- a/lib/chef/knife/client_create.rb
+++ b/lib/chef/knife/client_create.rb
@@ -16,70 +16,94 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class ClientCreate < Knife
deps do
- require 'chef/api_client'
- require 'chef/json_compat'
+ 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..f94010a4ce 100644
--- a/lib/chef/knife/client_delete.rb
+++ b/lib/chef/knife/client_delete.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class ClientDelete < Knife
deps do
- require 'chef/api_client'
- require 'chef/json_compat'
+ require "chef/api_client_v1"
+ require "chef/json_compat"
end
option :delete_validators,
@@ -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..df3ede04c9 100644
--- a/lib/chef/knife/client_edit.rb
+++ b/lib/chef/knife/client_edit.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class ClientEdit < Knife
deps do
- require 'chef/api_client'
- require 'chef/json_compat'
+ require "chef/api_client_v1"
+ require "chef/json_compat"
end
banner "knife client edit CLIENT (options)"
@@ -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..bf103678a8
--- /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..c76197a02d
--- /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..5aa4dc4781
--- /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..5679cc43e2
--- /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..7ec32dd761
--- /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..d5bfb4600a 100644
--- a/lib/chef/knife/client_list.rb
+++ b/lib/chef/knife/client_list.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class ClientList < Knife
deps do
- require 'chef/api_client'
- require 'chef/json_compat'
+ require "chef/api_client_v1"
+ require "chef/json_compat"
end
banner "knife client list (options)"
@@ -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..ac52f41fae 100644
--- a/lib/chef/knife/client_reregister.rb
+++ b/lib/chef/knife/client_reregister.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class ClientReregister < Knife
deps do
- require 'chef/api_client'
- require 'chef/json_compat'
+ require "chef/api_client_v1"
+ require "chef/json_compat"
end
banner "knife client reregister CLIENT (options)"
@@ -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..a41ea4d7ce 100644
--- a/lib/chef/knife/client_show.rb
+++ b/lib/chef/knife/client_show.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -25,8 +25,8 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/api_client'
- require 'chef/json_compat'
+ require "chef/api_client_v1"
+ require "chef/json_compat"
end
banner "knife client show CLIENT (options)"
@@ -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/configure.rb b/lib/chef/knife/configure.rb
index 8bb8930aee..f4d539aa13 100644
--- a/lib/chef/knife/configure.rb
+++ b/lib/chef/knife/configure.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -25,7 +25,7 @@ class Chef
attr_reader :chef_repo, :new_client_key, :validation_client_name, :validation_key
deps do
- require 'ohai'
+ require "ohai"
Chef::Knife::ClientCreate.load_deps
Chef::Knife::UserCreate.load_deps
end
@@ -61,7 +61,7 @@ class Chef
def configure_chef
# We are just faking out the system so that you can do this without a key specified
- Chef::Config[:node_name] = 'woot'
+ Chef::Config[:node_name] = "woot"
super
Chef::Config[:node_name] = nil
end
@@ -136,14 +136,14 @@ EOH
@chef_server = config[:chef_server_url] || ask_question("Please enter the chef server URL: ", :default => "https://#{server_name}:443")
if config[:initial]
@new_client_name = config[:node_name] || ask_question("Please enter a name for the new user: ", :default => Etc.getlogin)
- @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", :default => 'admin')
- @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", :default => '/etc/chef-server/admin.pem')
+ @admin_client_name = config[:admin_client_name] || ask_question("Please enter the existing admin name: ", :default => "admin")
+ @admin_client_key = config[:admin_client_key] || ask_question("Please enter the location of the existing admin's private key: ", :default => "/etc/chef-server/admin.pem")
@admin_client_key = File.expand_path(@admin_client_key)
else
@new_client_name = config[:node_name] || ask_question("Please enter an existing username or clientname for the API: ", :default => Etc.getlogin)
end
- @validation_client_name = config[:validation_client_name] || ask_question("Please enter the validation clientname: ", :default => 'chef-validator')
- @validation_key = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => '/etc/chef-server/chef-validator.pem')
+ @validation_client_name = config[:validation_client_name] || ask_question("Please enter the validation clientname: ", :default => "chef-validator")
+ @validation_key = config[:validation_key] || ask_question("Please enter the location of the validation key: ", :default => "/etc/chef-server/chef-validator.pem")
@validation_key = File.expand_path(@validation_key)
@chef_repo = config[:repository] || ask_question("Please enter the path to a chef repository (or leave blank): ")
@@ -154,9 +154,9 @@ EOH
def guess_servername
o = Ohai::System.new
o.load_plugins
- o.require_plugin 'os'
- o.require_plugin 'hostname'
- o[:fqdn] || o[:machinename] || o[:hostname] || 'localhost'
+ o.require_plugin "os"
+ o.require_plugin "hostname"
+ o[:fqdn] || o[:machinename] || o[:hostname] || "localhost"
end
def config_file
diff --git a/lib/chef/knife/configure_client.rb b/lib/chef/knife/configure_client.rb
index 838d9a1f58..d1d1b2f3de 100644
--- a/lib/chef/knife/configure_client.rb
+++ b/lib/chef/knife/configure_client.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -34,13 +34,13 @@ class Chef
FileUtils.mkdir_p(@config_dir)
ui.info("Writing client.rb")
File.open(File.join(@config_dir, "client.rb"), "w") do |file|
- file.puts('log_level :info')
- file.puts('log_location STDOUT')
+ file.puts("log_level :info")
+ file.puts("log_location STDOUT")
file.puts("chef_server_url '#{Chef::Config[:chef_server_url]}'")
file.puts("validation_client_name '#{Chef::Config[:validation_client_name]}'")
end
ui.info("Writing validation.pem")
- File.open(File.join(@config_dir, 'validation.pem'), "w") do |validation|
+ File.open(File.join(@config_dir, "validation.pem"), "w") do |validation|
validation.puts(IO.read(Chef::Config[:validation_key]))
end
end
diff --git a/lib/chef/knife/cookbook_bulk_delete.rb b/lib/chef/knife/cookbook_bulk_delete.rb
index 65fa888486..0a08fd5d46 100644
--- a/lib/chef/knife/cookbook_bulk_delete.rb
+++ b/lib/chef/knife/cookbook_bulk_delete.rb
@@ -17,18 +17,18 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookBulkDelete < Knife
deps do
- require 'chef/knife/cookbook_delete'
- require 'chef/cookbook_version'
+ require "chef/knife/cookbook_delete"
+ require "chef/cookbook_version"
end
- option :purge, :short => '-p', :long => '--purge', :boolean => true, :description => 'Permanently remove files from backing data store'
+ option :purge, :short => "-p", :long => "--purge", :boolean => true, :description => "Permanently remove files from backing data store"
banner "knife cookbook bulk delete REGEX (options)"
@@ -60,9 +60,9 @@ class Chef
cookbooks_names.each do |cookbook_name|
- versions = rest.get_rest("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map {|v| v["version"]}.flatten
+ versions = rest.get("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map {|v| v["version"]}.flatten
versions.each do |version|
- object = rest.delete_rest("cookbooks/#{cookbook_name}/#{version}#{config[:purge] ? "?purge=true" : ""}")
+ object = rest.delete("cookbooks/#{cookbook_name}/#{version}#{config[:purge] ? "?purge=true" : ""}")
ui.info("Deleted cookbook #{cookbook_name.ljust(25)} [#{version}]")
end
end
diff --git a/lib/chef/knife/cookbook_create.rb b/lib/chef/knife/cookbook_create.rb
index e17a54079f..391a9aee85 100644
--- a/lib/chef/knife/cookbook_create.rb
+++ b/lib/chef/knife/cookbook_create.rb
@@ -16,16 +16,16 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookCreate < Knife
deps do
- require 'chef/json_compat'
- require 'uri'
- require 'fileutils'
+ require "chef/json_compat"
+ require "uri"
+ require "fileutils"
end
banner "knife cookbook create COOKBOOK (options)"
@@ -48,7 +48,7 @@ class Chef
option :cookbook_copyright,
:short => "-C COPYRIGHT",
:long => "--copyright COPYRIGHT",
- :description => "Name of Copyright holder"
+ :description => "Name of copyright holder"
option :cookbook_email,
:short => "-m EMAIL",
@@ -182,8 +182,8 @@ EOH
def create_changelog(dir, cookbook_name)
msg("** Creating CHANGELOG for cookbook: #{cookbook_name}")
- unless File.exists?(File.join(dir,cookbook_name,'CHANGELOG.md'))
- open(File.join(dir, cookbook_name, 'CHANGELOG.md'),'w') do |file|
+ unless File.exists?(File.join(dir,cookbook_name,"CHANGELOG.md"))
+ open(File.join(dir, cookbook_name, "CHANGELOG.md"),"w") do |file|
file.puts <<-EOH
#{cookbook_name} CHANGELOG
#{'='*"#{cookbook_name} CHANGELOG".length}
diff --git a/lib/chef/knife/cookbook_delete.rb b/lib/chef/knife/cookbook_delete.rb
index f436d270bd..8112ed11f9 100644
--- a/lib/chef/knife/cookbook_delete.rb
+++ b/lib/chef/knife/cookbook_delete.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -25,12 +25,12 @@ class Chef
attr_accessor :cookbook_name, :version
deps do
- require 'chef/cookbook_version'
+ require "chef/cookbook_version"
end
- option :all, :short => '-a', :long => '--all', :boolean => true, :description => 'delete all versions'
+ option :all, :short => "-a", :long => "--all", :boolean => true, :description => "delete all versions"
- option :purge, :short => '-p', :long => '--purge', :boolean => true, :description => 'Permanently remove files from backing data store'
+ option :purge, :short => "-p", :long => "--purge", :boolean => true, :description => "Permanently remove files from backing data store"
banner "knife cookbook delete COOKBOOK VERSION (options)"
@@ -85,7 +85,7 @@ class Chef
end
def available_versions
- @available_versions ||= rest.get_rest("cookbooks/#{@cookbook_name}").map do |name, url_and_version|
+ @available_versions ||= rest.get("cookbooks/#{@cookbook_name}").map do |name, url_and_version|
url_and_version["versions"].map {|url_by_version| url_by_version["version"]}
end.flatten
rescue Net::HTTPServerException => e
@@ -106,7 +106,7 @@ class Chef
end
valid_responses[(available_versions.size + 1).to_s] = :all
question << "#{available_versions.size + 1}. All versions\n\n"
- responses = ask_question(question).split(',').map { |response| response.strip }
+ responses = ask_question(question).split(",").map { |response| response.strip }
if responses.empty?
ui.error("No versions specified, exiting")
@@ -143,7 +143,7 @@ class Chef
def delete_request(path)
path += "?purge=true" if config[:purge]
- rest.delete_rest(path)
+ rest.delete(path)
end
end
diff --git a/lib/chef/knife/cookbook_download.rb b/lib/chef/knife/cookbook_download.rb
index cb8eeb8edf..0af5e3b89a 100644
--- a/lib/chef/knife/cookbook_download.rb
+++ b/lib/chef/knife/cookbook_download.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -27,7 +27,7 @@ class Chef
attr_accessor :cookbook_name
deps do
- require 'chef/cookbook_version'
+ require "chef/cookbook_version"
end
banner "knife cookbook download COOKBOOK [VERSION] (options)"
@@ -69,7 +69,7 @@ class Chef
ui.info("Downloading #{@cookbook_name} cookbook version #{@version}")
- cookbook = rest.get_rest("cookbooks/#{@cookbook_name}/#{@version}")
+ cookbook = Chef::CookbookVersion.load(@cookbook_name, @version)
manifest = cookbook.manifest
basedir = File.join(config[:download_directory], "#{@cookbook_name}-#{cookbook.version}")
@@ -87,11 +87,10 @@ class Chef
next unless manifest.has_key?(segment)
ui.info("Downloading #{segment}")
manifest[segment].each do |segment_file|
- dest = File.join(basedir, segment_file['path'].gsub('/', File::SEPARATOR))
+ dest = File.join(basedir, segment_file["path"].gsub("/", File::SEPARATOR))
Chef::Log.debug("Downloading #{segment_file['path']} to #{dest}")
FileUtils.mkdir_p(File.dirname(dest))
- rest.sign_on_redirect = false
- tempfile = rest.get_rest(segment_file['url'], true)
+ tempfile = rest.streaming_request(segment_file["url"])
FileUtils.mv(tempfile.path, dest)
end
end
diff --git a/lib/chef/knife/cookbook_list.rb b/lib/chef/knife/cookbook_list.rb
index 75f18a154b..8b8fb163f9 100644
--- a/lib/chef/knife/cookbook_list.rb
+++ b/lib/chef/knife/cookbook_list.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -39,7 +39,7 @@ class Chef
env = config[:environment]
num_versions = config[:all_versions] ? "num_versions=all" : "num_versions=1"
api_endpoint = env ? "/environments/#{env}/cookbooks?#{num_versions}" : "/cookbooks?#{num_versions}"
- cookbook_versions = rest.get_rest(api_endpoint)
+ cookbook_versions = rest.get(api_endpoint)
ui.output(format_cookbook_list_for_display(cookbook_versions))
end
end
diff --git a/lib/chef/knife/cookbook_metadata.rb b/lib/chef/knife/cookbook_metadata.rb
index dfa69ae39f..66cb452d39 100644
--- a/lib/chef/knife/cookbook_metadata.rb
+++ b/lib/chef/knife/cookbook_metadata.rb
@@ -17,15 +17,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookMetadata < Knife
deps do
- require 'chef/cookbook_loader'
- require 'chef/cookbook/metadata'
+ require "chef/cookbook_loader"
+ require "chef/cookbook/metadata"
end
banner "knife cookbook metadata COOKBOOK (options)"
@@ -62,7 +62,7 @@ class Chef
def generate_metadata(cookbook)
Array(config[:cookbook_path]).reverse.each do |path|
- file = File.expand_path(File.join(path, cookbook, 'metadata.rb'))
+ file = File.expand_path(File.join(path, cookbook, "metadata.rb"))
if File.exists?(file)
generate_metadata_from_file(cookbook, file)
else
@@ -76,7 +76,7 @@ class Chef
md = Chef::Cookbook::Metadata.new
md.name(cookbook)
md.from_file(file)
- json_file = File.join(File.dirname(file), 'metadata.json')
+ json_file = File.join(File.dirname(file), "metadata.json")
File.open(json_file, "w") do |f|
f.write(Chef::JSONCompat.to_json_pretty(md))
end
@@ -91,7 +91,7 @@ class Chef
end
def validate_metadata_json(path, cookbook)
- json_file = File.join(path, cookbook, 'metadata.json')
+ json_file = File.join(path, cookbook, "metadata.json")
if File.exist?(json_file)
Chef::Cookbook::Metadata.validate_json(IO.read(json_file))
end
diff --git a/lib/chef/knife/cookbook_metadata_from_file.rb b/lib/chef/knife/cookbook_metadata_from_file.rb
index 8e26251d6e..51f3bdbb84 100644
--- a/lib/chef/knife/cookbook_metadata_from_file.rb
+++ b/lib/chef/knife/cookbook_metadata_from_file.rb
@@ -19,14 +19,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookMetadataFromFile < Knife
deps do
- require 'chef/cookbook/metadata'
+ require "chef/cookbook/metadata"
end
banner "knife cookbook metadata from FILE (options)"
diff --git a/lib/chef/knife/cookbook_show.rb b/lib/chef/knife/cookbook_show.rb
index 7c9cbebdb1..2229bd9d0e 100644
--- a/lib/chef/knife/cookbook_show.rb
+++ b/lib/chef/knife/cookbook_show.rb
@@ -16,16 +16,16 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookShow < Knife
deps do
- require 'chef/json_compat'
- require 'uri'
- require 'chef/cookbook_version'
+ require "chef/json_compat"
+ require "uri"
+ require "chef/cookbook_version"
end
banner "knife cookbook show COOKBOOK [VERSION] [PART] [FILENAME] (options)"
@@ -65,28 +65,28 @@ class Chef
end
cookbook_name, segment, filename = @name_args[0], @name_args[2], @name_args[3]
- cookbook_version = @name_args[1] == 'latest' ? '_latest' : @name_args[1]
+ cookbook_version = @name_args[1] == "latest" ? "_latest" : @name_args[1]
- cookbook = rest.get_rest("cookbooks/#{cookbook_name}/#{cookbook_version}")
+ cookbook = rest.get("cookbooks/#{cookbook_name}/#{cookbook_version}")
manifest_entry = cookbook.preferred_manifest_record(node, segment, filename)
- temp_file = rest.get_rest(manifest_entry[:url], true)
+ temp_file = rest.get(manifest_entry[:url], true)
# the temp file is cleaned up elsewhere
temp_file.open if temp_file.closed?
pretty_print(temp_file.read)
when 3 # We are showing a specific part of the cookbook
- cookbook_version = @name_args[1] == 'latest' ? '_latest' : @name_args[1]
- result = rest.get_rest("cookbooks/#{@name_args[0]}/#{cookbook_version}")
+ cookbook_version = @name_args[1] == "latest" ? "_latest" : @name_args[1]
+ result = rest.get("cookbooks/#{@name_args[0]}/#{cookbook_version}")
output(result.manifest[@name_args[2]])
when 2 # We are showing the whole cookbook data
- cookbook_version = @name_args[1] == 'latest' ? '_latest' : @name_args[1]
- output(rest.get_rest("cookbooks/#{@name_args[0]}/#{cookbook_version}"))
+ cookbook_version = @name_args[1] == "latest" ? "_latest" : @name_args[1]
+ output(rest.get("cookbooks/#{@name_args[0]}/#{cookbook_version}"))
when 1 # We are showing the cookbook versions (all of them)
cookbook_name = @name_args[0]
env = config[:environment]
api_endpoint = env ? "environments/#{env}/cookbooks/#{cookbook_name}" : "cookbooks/#{cookbook_name}"
- output(format_cookbook_list_for_display(rest.get_rest(api_endpoint)))
+ output(format_cookbook_list_for_display(rest.get(api_endpoint)))
when 0
show_usage
ui.fatal("You must specify a cookbook name")
diff --git a/lib/chef/knife/cookbook_site_download.rb b/lib/chef/knife/cookbook_site_download.rb
index c2d72ef8da..2e66495bb5 100644
--- a/lib/chef/knife/cookbook_site_download.rb
+++ b/lib/chef/knife/cookbook_site_download.rb
@@ -1,5 +1,5 @@
# Author:: Adam Jacob (<adam@opscode.com>)
-# Copyright:: Copyright (c) 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2009-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,14 +15,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookSiteDownload < Knife
deps do
- require 'fileutils'
+ require "fileutils"
end
banner "knife cookbook site download COOKBOOK [VERSION] (options)"
@@ -39,12 +39,12 @@ class Chef
def run
if current_cookbook_deprecated?
- message = 'DEPRECATION: This cookbook has been deprecated. '
+ message = "DEPRECATION: This cookbook has been deprecated. "
message << "It has been replaced by #{replacement_cookbook}."
ui.warn message
unless config[:force]
- ui.warn 'Use --force to force download deprecated cookbook.'
+ ui.warn "Use --force to force download deprecated cookbook."
return
end
end
@@ -53,40 +53,40 @@ class Chef
end
def version
- @version = desired_cookbook_data['version']
+ @version = desired_cookbook_data["version"]
end
private
def cookbooks_api_url
- 'https://supermarket.chef.io/api/v1/cookbooks'
+ "https://supermarket.chef.io/api/v1/cookbooks"
end
def current_cookbook_data
@current_cookbook_data ||= begin
- noauth_rest.get_rest "#{cookbooks_api_url}/#{@name_args[0]}"
- end
+ noauth_rest.get "#{cookbooks_api_url}/#{@name_args[0]}"
+ end
end
def current_cookbook_deprecated?
- current_cookbook_data['deprecated'] == true
+ current_cookbook_data["deprecated"] == true
end
def desired_cookbook_data
@desired_cookbook_data ||= begin
- uri = if @name_args.length == 1
- current_cookbook_data['latest_version']
- else
- specific_cookbook_version_url
- end
-
- noauth_rest.get_rest uri
- end
+ uri = if @name_args.length == 1
+ current_cookbook_data["latest_version"]
+ else
+ specific_cookbook_version_url
+ end
+
+ noauth_rest.get uri
+ end
end
def download_cookbook
- ui.info "Downloading #{@name_args[0]} from the cookbooks site at version #{version} to #{download_location}"
+ ui.info "Downloading #{@name_args[0]} from Supermarket at version #{version} to #{download_location}"
noauth_rest.sign_on_redirect = false
- tf = noauth_rest.get_rest desired_cookbook_data["file"], true
+ tf = noauth_rest.get desired_cookbook_data["file"], true
::FileUtils.cp tf.path, download_location
ui.info "Cookbook saved: #{download_location}"
@@ -98,7 +98,7 @@ class Chef
end
def replacement_cookbook
- replacement = File.basename(current_cookbook_data['replacement'])
+ replacement = File.basename(current_cookbook_data["replacement"])
end
def specific_cookbook_version_url
diff --git a/lib/chef/knife/cookbook_site_install.rb b/lib/chef/knife/cookbook_site_install.rb
index d0ab6da3ef..cb6eeffc68 100644
--- a/lib/chef/knife/cookbook_site_install.rb
+++ b/lib/chef/knife/cookbook_site_install.rb
@@ -16,18 +16,18 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/exceptions'
-require 'shellwords'
+require "chef/knife"
+require "chef/exceptions"
+require "shellwords"
class Chef
class Knife
class CookbookSiteInstall < Knife
deps do
- require 'chef/mixin/shell_out'
- require 'chef/knife/core/cookbook_scm_repo'
- require 'chef/cookbook/metadata'
+ require "chef/mixin/shell_out"
+ require "chef/knife/core/cookbook_scm_repo"
+ require "chef/cookbook/metadata"
end
banner "knife cookbook site install COOKBOOK [VERSION] (options)"
@@ -93,7 +93,7 @@ class Chef
# TODO: it'd be better to store these outside the cookbook repo and
# keep them around, e.g., in ~/Library/Caches on OS X.
- ui.info("removing downloaded tarball")
+ ui.info("Removing downloaded tarball")
File.unlink(upstream_file)
if @repo.finalize_updates_to(@cookbook_name, downloader.version)
@@ -142,7 +142,11 @@ class Chef
def extract_cookbook(upstream_file, version)
ui.info("Uncompressing #{@cookbook_name} version #{version}.")
# FIXME: Detect if we have the bad tar from git on Windows: https://github.com/opscode/chef/issues/1753
- shell_out!("tar zxvf #{convert_path upstream_file}", :cwd => @install_path)
+ extract_command="tar zxvf \"#{convert_path upstream_file}\""
+ if Chef::Platform.windows?
+ extract_command << " --force-local"
+ end
+ shell_out!(extract_command, :cwd => @install_path)
end
def clear_existing_files(cookbook_path)
@@ -152,7 +156,7 @@ class Chef
def convert_path(upstream_file)
# converts a Windows path (C:\foo) to a mingw path (/c/foo)
- if ENV['MSYSTEM'] == 'MINGW32'
+ if ENV["MSYSTEM"] == "MINGW32"
return upstream_file.sub(/^([[:alpha:]]):/, '/\1')
else
return Shellwords.escape upstream_file
diff --git a/lib/chef/knife/cookbook_site_list.rb b/lib/chef/knife/cookbook_site_list.rb
index 846123c867..7d3100dcda 100644
--- a/lib/chef/knife/cookbook_site_list.rb
+++ b/lib/chef/knife/cookbook_site_list.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -33,7 +33,7 @@ class Chef
def run
if config[:with_uri]
cookbooks = Hash.new
- get_cookbook_list.each{ |k,v| cookbooks[k] = v['cookbook'] }
+ get_cookbook_list.each{ |k,v| cookbooks[k] = v["cookbook"] }
ui.output(format_for_display(cookbooks))
else
ui.msg(ui.list(get_cookbook_list.keys.sort, :columns_down))
@@ -42,7 +42,7 @@ class Chef
def get_cookbook_list(items=10, start=0, cookbook_collection={})
cookbooks_url = "https://supermarket.chef.io/api/v1/cookbooks?items=#{items}&start=#{start}"
- cr = noauth_rest.get_rest(cookbooks_url)
+ cr = noauth_rest.get(cookbooks_url)
cr["items"].each do |cookbook|
cookbook_collection[cookbook["cookbook_name"]] = cookbook
end
diff --git a/lib/chef/knife/cookbook_site_search.rb b/lib/chef/knife/cookbook_site_search.rb
index 0baaf90f1c..0cade56f7a 100644
--- a/lib/chef/knife/cookbook_site_search.rb
+++ b/lib/chef/knife/cookbook_site_search.rb
@@ -15,7 +15,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -30,7 +30,7 @@ class Chef
def search_cookbook(query, items=10, start=0, cookbook_collection={})
cookbooks_url = "https://supermarket.chef.io/api/v1/search?q=#{query}&items=#{items}&start=#{start}"
- cr = noauth_rest.get_rest(cookbooks_url)
+ cr = noauth_rest.get(cookbooks_url)
cr["items"].each do |cookbook|
cookbook_collection[cookbook["cookbook_name"]] = cookbook
end
diff --git a/lib/chef/knife/cookbook_site_share.rb b/lib/chef/knife/cookbook_site_share.rb
index efd2e7f129..f926a38722 100644
--- a/lib/chef/knife/cookbook_site_share.rb
+++ b/lib/chef/knife/cookbook_site_share.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/mixin/shell_out'
+require "chef/knife"
+require "chef/mixin/shell_out"
class Chef
class Knife
@@ -26,10 +26,10 @@ class Chef
include Chef::Mixin::ShellOut
deps do
- require 'chef/cookbook_loader'
- require 'chef/cookbook_uploader'
- require 'chef/cookbook_site_streaming_uploader'
- require 'mixlib/shellout'
+ require "chef/cookbook_loader"
+ require "chef/cookbook_uploader"
+ require "chef/cookbook_site_streaming_uploader"
+ require "mixlib/shellout"
end
include Chef::Mixin::ShellOut
@@ -44,11 +44,11 @@ class Chef
:proc => lambda { |o| Chef::Config.cookbook_path = o.split(":") }
option :dry_run,
- :long => '--dry-run',
- :short => '-n',
+ :long => "--dry-run",
+ :short => "-n",
:boolean => true,
:default => false,
- :description => "Don't take action, only print what files will be upload to SuperMarket."
+ :description => "Don't take action, only print what files will be uploaded to Supermarket."
def run
config[:cookbook_path] ||= Chef::Config[:cookbook_path]
@@ -94,7 +94,7 @@ class Chef
Chef::Log.debug("Removing local staging directory at #{tmp_cookbook_dir}")
FileUtils.rm_rf tmp_cookbook_dir
rescue => e
- ui.error("Error uploading cookbook #{cookbook_name} to the Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.")
+ ui.error("Error uploading cookbook #{cookbook_name} to Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
Chef::Log.debug("\n#{e.backtrace.join("\n")}")
exit(1)
end
@@ -108,15 +108,15 @@ class Chef
def get_category(cookbook_name)
begin
- data = noauth_rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}")
+ data = noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}")
if !data["category"] && data["error_code"]
- ui.fatal("Received an error from the Opscode Cookbook site: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.")
+ ui.fatal("Received an error from Supermarket: #{data["error_code"]}. On the first time you upload it, you are required to specify the category you want to share this cookbook to.")
exit(1)
else
- data['category']
+ data["category"]
end
rescue => e
- ui.fatal("Unable to reach Opscode Cookbook Site: #{e.message}. Increase log verbosity (-VV) for more information.")
+ ui.fatal("Unable to reach Supermarket: #{e.message}. Increase log verbosity (-VV) for more information.")
Chef::Log.debug("\n#{e.backtrace.join("\n")}")
exit(1)
end
@@ -125,18 +125,18 @@ class Chef
def do_upload(cookbook_filename, cookbook_category, user_id, user_secret_filename)
uri = "https://supermarket.chef.io/api/v1/cookbooks"
- category_string = Chef::JSONCompat.to_json({ 'category'=>cookbook_category })
+ category_string = Chef::JSONCompat.to_json({ "category"=>cookbook_category })
http_resp = Chef::CookbookSiteStreamingUploader.post(uri, user_id, user_secret_filename, {
:tarball => File.open(cookbook_filename),
- :cookbook => category_string
+ :cookbook => category_string,
})
res = Chef::JSONCompat.from_json(http_resp.body)
if http_resp.code.to_i != 201
- if res['error_messages']
- if res['error_messages'][0] =~ /Version already exists/
- ui.error "The same version of this cookbook already exists on the Opscode Cookbook Site."
+ if res["error_messages"]
+ if res["error_messages"][0] =~ /Version already exists/
+ ui.error "The same version of this cookbook already exists on Supermarket."
exit(1)
else
ui.error "#{res['error_messages'][0]}"
diff --git a/lib/chef/knife/cookbook_site_show.rb b/lib/chef/knife/cookbook_site_show.rb
index 6b65b62570..01ed6c2346 100644
--- a/lib/chef/knife/cookbook_site_show.rb
+++ b/lib/chef/knife/cookbook_site_show.rb
@@ -15,7 +15,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -31,15 +31,15 @@ class Chef
def get_cookbook_data
case @name_args.length
when 1
- noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}")
+ noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}")
when 2
- noauth_rest.get_rest("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}")
+ noauth_rest.get("https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}")
end
end
def get_cookbook_list(items=10, start=0, cookbook_collection={})
cookbooks_url = "https://supermarket.chef.io/api/v1/cookbooks?items=#{items}&start=#{start}"
- cr = noauth_rest.get_rest(cookbooks_url)
+ cr = noauth_rest.get(cookbooks_url)
cr["items"].each do |cookbook|
cookbook_collection[cookbook["cookbook_name"]] = cookbook
end
diff --git a/lib/chef/knife/cookbook_site_unshare.rb b/lib/chef/knife/cookbook_site_unshare.rb
index b34282ec32..bb857b7d1d 100644
--- a/lib/chef/knife/cookbook_site_unshare.rb
+++ b/lib/chef/knife/cookbook_site_unshare.rb
@@ -17,14 +17,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookSiteUnshare < Knife
deps do
- require 'chef/json_compat'
+ require "chef/json_compat"
end
banner "knife cookbook site unshare COOKBOOK"
@@ -38,17 +38,17 @@ class Chef
exit 1
end
- confirm "Do you really want to unshare the cookbook #{@cookbook_name}"
+ confirm "Do you really want to unshare all versions of the cookbook #{@cookbook_name}"
begin
- rest.delete_rest "https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}"
+ rest.delete "https://supermarket.chef.io/api/v1/cookbooks/#{@name_args[0]}"
rescue Net::HTTPServerException => e
raise e unless e.message =~ /Forbidden/
ui.error "Forbidden: You must be the maintainer of #{@cookbook_name} to unshare it."
exit 1
end
- ui.info "Unshared cookbook #{@cookbook_name}"
+ ui.info "Unshared all versions of the cookbook #{@cookbook_name}"
end
end
diff --git a/lib/chef/knife/cookbook_site_vendor.rb b/lib/chef/knife/cookbook_site_vendor.rb
index 82575958bd..9f4399f4cb 100644
--- a/lib/chef/knife/cookbook_site_vendor.rb
+++ b/lib/chef/knife/cookbook_site_vendor.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/knife/cookbook_site_install'
+require "chef/knife"
+require "chef/knife/cookbook_site_install"
class Chef::Knife::CookbookSiteVendor < Chef::Knife::CookbookSiteInstall
@@ -41,6 +41,6 @@ DEPRECATED: please use knife cookbook site install
#{superclass.banner}
B
- category 'deprecated'
+ category "deprecated"
end
diff --git a/lib/chef/knife/cookbook_test.rb b/lib/chef/knife/cookbook_test.rb
index 91e0b55c47..23bb7d8d78 100644
--- a/lib/chef/knife/cookbook_test.rb
+++ b/lib/chef/knife/cookbook_test.rb
@@ -18,15 +18,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class CookbookTest < Knife
deps do
- require 'chef/cookbook_loader'
- require 'chef/cookbook/syntax_check'
+ require "chef/cookbook_loader"
+ require "chef/cookbook/syntax_check"
end
banner "knife cookbook test [COOKBOOKS...] (options)"
diff --git a/lib/chef/knife/cookbook_upload.rb b/lib/chef/knife/cookbook_upload.rb
index b2acd74b4b..ba52d311ca 100644
--- a/lib/chef/knife/cookbook_upload.rb
+++ b/lib/chef/knife/cookbook_upload.rb
@@ -18,8 +18,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/cookbook_uploader'
+require "chef/knife"
+require "chef/cookbook_uploader"
class Chef
class Knife
@@ -29,9 +29,9 @@ class Chef
MATCH_CHECKSUM = /[0-9a-f]{32,}/
deps do
- require 'chef/exceptions'
- require 'chef/cookbook_loader'
- require 'chef/cookbook_uploader'
+ require "chef/exceptions"
+ require "chef/cookbook_loader"
+ require "chef/cookbook_uploader"
end
banner "knife cookbook upload [COOKBOOKS...] (options)"
@@ -43,8 +43,8 @@ class Chef
:proc => lambda { |o| o.split(":") }
option :freeze,
- :long => '--freeze',
- :description => 'Freeze this version of the cookbook so that it cannot be overwritten',
+ :long => "--freeze",
+ :description => "Freeze this version of the cookbook so that it cannot be overwritten",
:boolean => true
option :all,
@@ -53,19 +53,19 @@ class Chef
:description => "Upload all cookbooks, rather than just a single cookbook"
option :force,
- :long => '--force',
+ :long => "--force",
:boolean => true,
:description => "Update cookbook versions even if they have been frozen"
option :concurrency,
- :long => '--concurrency NUMBER_OF_THREADS',
+ :long => "--concurrency NUMBER_OF_THREADS",
:description => "How many concurrent threads will be used",
:default => 10,
:proc => lambda { |o| o.to_i }
option :environment,
- :short => '-E',
- :long => '--environment ENVIRONMENT',
+ :short => "-E",
+ :long => "--environment ENVIRONMENT",
:description => "Set ENVIRONMENT's version dependency match the version you're uploading.",
:default => nil
@@ -118,7 +118,7 @@ class Chef
end
ui.info("Uploaded all cookbooks.")
else
- cookbook_path = config[:cookbook_path].respond_to?(:join) ? config[:cookbook_path].join(', ') : config[:cookbook_path]
+ cookbook_path = config[:cookbook_path].respond_to?(:join) ? config[:cookbook_path].join(", ") : config[:cookbook_path]
ui.warn("Could not find any cookbooks in your cookbook path: #{cookbook_path}. Use --cookbook-path to specify the desired path.")
end
else
@@ -287,7 +287,7 @@ WARNING
if @server_side_cookbooks[cookbook_name].nil?
false
else
- versions = @server_side_cookbooks[cookbook_name]['versions'].collect {|versions| versions["version"]}
+ versions = @server_side_cookbooks[cookbook_name]["versions"].collect {|versions| versions["version"]}
Log.debug "Versions of cookbook '#{cookbook_name}' returned by the server: #{versions.join(", ")}"
@server_side_cookbooks[cookbook_name]["versions"].each do |versions_hash|
if Chef::VersionConstraint.new(version).include?(versions_hash["version"])
diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb
index 7197653489..b0a759dd05 100644
--- a/lib/chef/knife/core/bootstrap_context.rb
+++ b/lib/chef/knife/core/bootstrap_context.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/run_list'
-require 'chef/util/path_helper'
+require "chef/run_list"
+require "chef/util/path_helper"
class Chef
class Knife
@@ -40,7 +40,7 @@ class Chef
end
def bootstrap_environment
- @chef_config[:environment] || '_default'
+ @config[:environment]
end
def validation_key
@@ -66,7 +66,7 @@ class Chef
log_location STDOUT
chef_server_url "#{@chef_config[:chef_server_url]}"
validation_client_name "#{@chef_config[:validation_client_name]}"
-CONFIG
+ CONFIG
if @config[:chef_node_name]
client_rb << %Q{node_name "#{@config[:chef_node_name]}"\n}
else
@@ -84,15 +84,15 @@ CONFIG
# or when specified in the knife config.
if @config[:node_ssl_verify_mode] || knife_config.has_key?(:ssl_verify_mode)
value = case @config[:node_ssl_verify_mode]
- when "peer"
- :verify_peer
- when "none"
- :verify_none
- when nil
- knife_config[:ssl_verify_mode]
- else
- nil
- end
+ when "peer"
+ :verify_peer
+ when "none"
+ :verify_none
+ when nil
+ knife_config[:ssl_verify_mode]
+ else
+ nil
+ end
if value
client_rb << %Q{ssl_verify_mode :#{value}\n}
@@ -125,10 +125,11 @@ CONFIG
def start_chef
# If the user doesn't have a client path configure, let bash use the PATH for what it was designed for
- client_path = @chef_config[:chef_client_path] || 'chef-client'
+ client_path = @chef_config[:chef_client_path] || "chef-client"
s = "#{client_path} -j /etc/chef/first-boot.json"
- s << ' -l debug' if @config[:verbosity] and @config[:verbosity] >= 2
- s << " -E #{bootstrap_environment}"
+ s << " -l debug" if @config[:verbosity] and @config[:verbosity] >= 2
+ s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil?
+ s << " --no-color" unless @config[:color]
s
end
@@ -146,10 +147,10 @@ CONFIG
installer_version_string = ["-p"]
else
chef_version_string = if knife_config[:bootstrap_version]
- knife_config[:bootstrap_version]
- else
- Chef::VERSION.split(".").first
- end
+ knife_config[:bootstrap_version]
+ else
+ Chef::VERSION.split(".").first
+ end
installer_version_string = ["-v", chef_version_string]
@@ -163,11 +164,19 @@ CONFIG
end
def first_boot
- (@config[:first_boot_attributes] || {}).merge(:run_list => @run_list)
+ (@config[:first_boot_attributes] || {}).tap do |attributes|
+ if @config[:policy_name] && @config[:policy_group]
+ attributes.merge!(:policy_name => @config[:policy_name], :policy_group => @config[:policy_group])
+ else
+ attributes.merge!(:run_list => @run_list)
+ end
+
+ attributes.merge!(:tags => @config[:tags]) if @config[:tags] && !@config[:tags].empty?
+ end
end
private
-
+
# Returns a string for copying the trusted certificates on the workstation to the system being bootstrapped
# This string should contain both the commands necessary to both create the files, as well as their content
def trusted_certs_content
@@ -175,7 +184,7 @@ CONFIG
if @chef_config[:trusted_certs_dir]
Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(@chef_config[:trusted_certs_dir]), "*.{crt,pem}")).each do |cert|
content << "cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'\n" +
- IO.read(File.expand_path(cert)) + "\nEOP\n"
+ IO.read(File.expand_path(cert)) + "\nEOP\n"
end
end
content
diff --git a/lib/chef/knife/core/cookbook_scm_repo.rb b/lib/chef/knife/core/cookbook_scm_repo.rb
index 727cff3153..f10bc969eb 100644
--- a/lib/chef/knife/core/cookbook_scm_repo.rb
+++ b/lib/chef/knife/core/cookbook_scm_repo.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/mixin/shell_out'
+require "chef/mixin/shell_out"
class Chef
class Knife
@@ -34,7 +34,7 @@ class Chef
def initialize(repo_path, ui, opts={})
@repo_path = repo_path
@ui = ui
- @default_branch = 'master'
+ @default_branch = "master"
@use_current_branch = false
apply_opts(opts)
end
@@ -57,7 +57,7 @@ class Chef
ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks"
exit 1
end
- cmd = git('status --porcelain')
+ cmd = git("status --porcelain")
if cmd.stdout =~ DIRTY_REPO
ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):"
ui.msg cmd.stdout
@@ -124,13 +124,13 @@ class Chef
def get_current_branch()
ref = git("symbolic-ref HEAD").stdout
- ref.chomp.split('/')[2]
+ ref.chomp.split("/")[2]
end
private
def git_repo?(directory)
- if File.directory?(File.join(directory, '.git'))
+ if File.directory?(File.join(directory, ".git"))
return true
elsif File.dirname(directory) == directory
return false
@@ -142,9 +142,9 @@ class Chef
def apply_opts(opts)
opts.each do |option, value|
case option.to_s
- when 'default_branch'
+ when "default_branch"
@default_branch = value
- when 'use_current_branch'
+ when "use_current_branch"
@use_current_branch = value
end
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..da3eee4d90
--- /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..bc987c907b
--- /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..e0c5b5567c 100644
--- a/lib/chef/knife/core/generic_presenter.rb
+++ b/lib/chef/knife/core/generic_presenter.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife/core/text_formatter'
+require "chef/knife/core/text_formatter"
class Chef
class Knife
@@ -80,10 +80,10 @@ class Chef
when :json
Chef::JSONCompat.to_json_pretty(data)
when :yaml
- require 'yaml'
+ require "yaml"
YAML::dump(data)
when :pp
- require 'stringio'
+ require "stringio"
# If you were looking for some attribute and there is only one match
# just dump the attribute value
if config[:attribute] and data.length == 1
@@ -150,18 +150,18 @@ class Chef
def format_data_subset_for_display(data)
subset = if config[:attribute]
- result = {}
- Array(config[:attribute]).each do |nested_value_spec|
- nested_value = extract_nested_value(data, nested_value_spec)
- result[nested_value_spec] = nested_value
- end
- result
- elsif config[:run_list]
- run_list = data.run_list.run_list
- { "run_list" => run_list }
- else
- raise ArgumentError, "format_data_subset_for_display requires attribute, run_list, or id_only config option to be set"
- end
+ result = {}
+ Array(config[:attribute]).each do |nested_value_spec|
+ nested_value = extract_nested_value(data, nested_value_spec)
+ result[nested_value_spec] = nested_value
+ end
+ result
+ elsif config[:run_list]
+ run_list = data.run_list.run_list
+ { "run_list" => run_list }
+ else
+ raise ArgumentError, "format_data_subset_for_display requires attribute, run_list, or id_only config option to be set"
+ end
{name_or_id_for(data) => subset }
end
@@ -178,19 +178,19 @@ class Chef
nested_value_spec.split(".").each do |attr|
if data.nil?
nil # don't get no method error on nil
- # Must check :[] before attr because spec can include
- # `keys` - want the key named `keys`, not a list of
- # available keys.
- elsif data.respond_to?(:[])
+ # Must check :[] before attr because spec can include
+ # `keys` - want the key named `keys`, not a list of
+ # available keys.
+ elsif data.respond_to?(:[]) && data.has_key?(attr)
data = data[attr]
elsif data.respond_to?(attr.to_sym)
data = data.send(attr.to_sym)
else
data = begin
- data.send(attr.to_sym)
- rescue NoMethodError
- nil
- end
+ data.send(attr.to_sym)
+ rescue NoMethodError
+ nil
+ end
end
end
( !data.kind_of?(Array) && data.respond_to?(:to_hash) ) ? data.to_hash : data
@@ -200,14 +200,14 @@ class Chef
if config[:with_uri]
item.inject({}) do |collected, (cookbook, versions)|
collected[cookbook] = Hash.new
- versions['versions'].each do |ver|
- collected[cookbook][ver['version']] = ver['url']
+ versions["versions"].each do |ver|
+ collected[cookbook][ver["version"]] = ver["url"]
end
collected
end
else
versions_by_cookbook = item.inject({}) do |collected, ( cookbook, versions )|
- collected[cookbook] = versions["versions"].map {|v| v['version']}
+ collected[cookbook] = versions["versions"].map {|v| v["version"]}
collected
end
key_length = versions_by_cookbook.empty? ? 0 : versions_by_cookbook.keys.map {|name| name.size }.max + 2
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..fe351e0ea9
--- /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/node_editor.rb b/lib/chef/knife/core/node_editor.rb
index fe14e18d9d..897d00932e 100644
--- a/lib/chef/knife/core/node_editor.rb
+++ b/lib/chef/knife/core/node_editor.rb
@@ -1,6 +1,7 @@
#
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Author:: Jordan Running (<jr@chef.io>)
+# Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,83 +17,103 @@
# limitations under the License.
#
-require 'chef/json_compat'
-require 'chef/node'
-require 'tempfile'
+require "chef/json_compat"
+require "chef/node"
class Chef
class Knife
class NodeEditor
+ attr_reader :node, :ui, :config
+ private :node, :ui, :config
- attr_reader :node
- attr_reader :ui
- attr_reader :config
-
+ # @param node [Chef::Node]
+ # @param ui [Chef::Knife::UI]
+ # @param config [Hash]
def initialize(node, ui, config)
@node, @ui, @config = node, ui, config
end
+ # Opens the node data (as JSON) in the user's editor and returns a new
+ # {Chef::Node} reflecting the user's changes.
+ #
+ # @return [Chef::Node]
def edit_node
abort "You specified the --disable_editing option, nothing to edit" if config[:disable_editing]
assert_editor_set!
- updated_node_data = @ui.edit_data(view)
+ updated_node_data = ui.edit_data(view)
apply_updates(updated_node_data)
@updated_node
end
+ # Returns an array of the names of properties that have been changed or
+ # +false+ if none were changed.
+ #
+ # @return [Array<String>] if any properties have been changed.
+ # @return [false] if no properties have been changed.
def updated?
+ return false if @updated_node.nil?
+
pristine_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(node))
updated_copy = Chef::JSONCompat.parse(Chef::JSONCompat.to_json(@updated_node))
- unless pristine_copy == updated_copy
- updated_properties = %w{name normal chef_environment run_list default override automatic}.reject do |key|
- pristine_copy[key] == updated_copy[key]
- end
+
+ updated_properties = %w{
+ name
+ chef_environment
+ automatic
+ default
+ normal
+ override
+ policy_name
+ policy_group
+ run_list
+ }.reject do |key|
+ pristine_copy[key] == updated_copy[key]
end
- ( pristine_copy != updated_copy ) && updated_properties
- end
- private
+ updated_properties.any? && updated_properties
+ end
+ # @api private
def view
- result = {}
- result["name"] = node.name
- result["chef_environment"] = node.chef_environment
- result["normal"] = node.normal_attrs
- result["run_list"] = node.run_list
+ result = {
+ "name" => node.name,
+ "chef_environment" => node.chef_environment,
+ "normal" => node.normal_attrs,
+ "policy_name" => node.policy_name,
+ "policy_group" => node.policy_group,
+ "run_list" => node.run_list,
+ }
if config[:all_attributes]
result["default"] = node.default_attrs
result["override"] = node.override_attrs
result["automatic"] = node.automatic_attrs
end
+
result
end
+ # @api private
def apply_updates(updated_data)
if node.name and node.name != updated_data["name"]
ui.warn "Changing the name of a node results in a new node being created, #{node.name} will not be modified or removed."
- confirm = ui.confirm "Proceed with creation of new node"
+ ui.confirm "Proceed with creation of new node"
end
- @updated_node = Node.new.tap do |n|
- n.name( updated_data["name"] )
- n.chef_environment( updated_data["chef_environment"] )
- n.run_list( updated_data["run_list"])
- n.normal_attrs = updated_data["normal"]
-
- if config[:all_attributes]
- n.default_attrs = updated_data["default"]
- n.override_attrs = updated_data["override"]
- n.automatic_attrs = updated_data["automatic"]
- else
- n.default_attrs = node.default_attrs
- n.override_attrs = node.override_attrs
- n.automatic_attrs = node.automatic_attrs
- end
+ data = updated_data.dup
+
+ unless config[:all_attributes]
+ data["automatic"] = node.automatic_attrs
+ data["default"] = node.default_attrs
+ data["override"] = node.override_attrs
end
+
+ @updated_node = Node.from_hash(data)
end
+ private
+
def abort(message)
ui.error(message)
exit 1
diff --git a/lib/chef/knife/core/node_presenter.rb b/lib/chef/knife/core/node_presenter.rb
index d1aab592ef..ffd31ed953 100644
--- a/lib/chef/knife/core/node_presenter.rb
+++ b/lib/chef/knife/core/node_presenter.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/knife/core/text_formatter'
-require 'chef/knife/core/generic_presenter'
+require "chef/knife/core/text_formatter"
+require "chef/knife/core/generic_presenter"
class Chef
class Knife
@@ -32,18 +32,18 @@ class Chef
def self.included(includer)
includer.class_eval do
option :medium_output,
- :short => '-m',
- :long => '--medium',
+ :short => "-m",
+ :long => "--medium",
:boolean => true,
:default => false,
- :description => 'Include normal attributes in the output'
+ :description => "Include normal attributes in the output"
option :long_output,
- :short => '-l',
- :long => '--long',
+ :short => "-l",
+ :long => "--long",
:boolean => true,
:default => false,
- :description => 'Include all attributes in the output'
+ :description => "Include all attributes in the output"
end
end
end
@@ -67,7 +67,12 @@ class Chef
result = {}
result["name"] = node.name
- result["chef_environment"] = node.chef_environment
+ if node.policy_name.nil? && node.policy_group.nil?
+ result["chef_environment"] = node.chef_environment
+ else
+ result["policy_name"] = node.policy_name
+ result["policy_group"] = node.policy_group
+ end
result["run_list"] = node.run_list
result["normal"] = node.normal_attrs
@@ -95,14 +100,32 @@ class Chef
summarized=<<-SUMMARY
#{ui.color('Node Name:', :bold)} #{ui.color(node.name, :bold)}
+SUMMARY
+ show_policy = !(node.policy_name.nil? && node.policy_group.nil?)
+ if show_policy
+ summarized << <<-POLICY
+#{key('Policy Name:')} #{node.policy_name}
+#{key('Policy Group:')} #{node.policy_group}
+POLICY
+ else
+ summarized << <<-ENV
#{key('Environment:')} #{node.chef_environment}
+ENV
+ end
+ summarized << <<-SUMMARY
#{key('FQDN:')} #{node[:fqdn]}
#{key('IP:')} #{ip}
#{key('Run List:')} #{node.run_list}
+SUMMARY
+ unless show_policy
+ summarized << <<-ROLES
#{key('Roles:')} #{Array(node[:roles]).join(', ')}
+ROLES
+ end
+ summarized << <<-SUMMARY
#{key('Recipes:')} #{Array(node[:recipes]).join(', ')}
#{key('Platform:')} #{node[:platform]} #{node[:platform_version]}
-#{key('Tags:')} #{Array(node[:tags]).join(', ')}
+#{key('Tags:')} #{node.tags.join(', ')}
SUMMARY
if config[:medium_output] || config[:long_output]
summarized +=<<-MORE
diff --git a/lib/chef/knife/core/object_loader.rb b/lib/chef/knife/core/object_loader.rb
index 698b09ac84..69c2428bd4 100644
--- a/lib/chef/knife/core/object_loader.rb
+++ b/lib/chef/knife/core/object_loader.rb
@@ -16,8 +16,9 @@
# limitations under the License.
#
-require 'ffi_yajl'
-require 'chef/util/path_helper'
+require "ffi_yajl"
+require "chef/util/path_helper"
+require "chef/data_bag_item"
class Chef
class Knife
@@ -70,14 +71,14 @@ class Chef
#
# @api public
def find_all_objects(path)
- path = File.join(Chef::Util::PathHelper.escape_glob(File.expand_path(path)), '*')
- path << '.{json,rb}'
+ path = File.join(Chef::Util::PathHelper.escape_glob(File.expand_path(path)), "*")
+ path << ".{json,rb}"
objects = Dir.glob(path)
objects.map { |o| File.basename(o) }
end
def find_all_object_dirs(path)
- path = File.join(Chef::Util::PathHelper.escape_glob(File.expand_path(path)), '*')
+ path = File.join(Chef::Util::PathHelper.escape_glob(File.expand_path(path)), "*")
objects = Dir.glob(path)
objects.delete_if { |o| !File.directory?(o) }
objects.map { |o| File.basename(o) }
diff --git a/lib/chef/knife/core/status_presenter.rb b/lib/chef/knife/core/status_presenter.rb
index 3298d5e4ac..4451835962 100644
--- a/lib/chef/knife/core/status_presenter.rb
+++ b/lib/chef/knife/core/status_presenter.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/knife/core/text_formatter'
-require 'chef/knife/core/generic_presenter'
+require "chef/knife/core/text_formatter"
+require "chef/knife/core/generic_presenter"
class Chef
class Knife
@@ -32,18 +32,18 @@ class Chef
def self.included(includer)
includer.class_eval do
option :medium_output,
- :short => '-m',
- :long => '--medium',
+ :short => "-m",
+ :long => "--medium",
:boolean => true,
:default => false,
- :description => 'Include normal attributes in the output'
+ :description => "Include normal attributes in the output"
option :long_output,
- :short => '-l',
- :long => '--long',
+ :short => "-l",
+ :long => "--long",
:boolean => true,
:default => false,
- :description => 'Include all attributes in the output'
+ :description => "Include all attributes in the output"
end
end
end
@@ -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
@@ -93,17 +93,18 @@ class Chef
# the volume of output is adjusted accordingly. Uses colors if enabled
# in the ui object.
def summarize(list)
- summarized=''
+ summarized=""
list.each do |data|
node = data
# 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,20 +117,20 @@ 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
- if node['platform']
- platform = node['platform']
- if node['platform_version']
+ if node["platform"]
+ platform = node["platform"]
+ if node["platform_version"]
platform << " #{node['platform_version']}"
end
line_parts << platform
end
- summarized=summarized + line_parts.join(', ') + ".\n"
+ summarized=summarized + line_parts.join(", ") + ".\n"
end
summarized
end
diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb
index 1f59515f44..4a1ed2481a 100644
--- a/lib/chef/knife/core/subcommand_loader.rb
+++ b/lib/chef/knife/core/subcommand_loader.rb
@@ -16,110 +16,125 @@
# limitations under the License.
#
-require 'chef/version'
-require 'chef/util/path_helper'
+require "chef/version"
+require "chef/util/path_helper"
+require "chef/knife/core/gem_glob_loader"
+require "chef/knife/core/hashed_command_loader"
+require "chef/knife/core/custom_manifest_loader"
+
class Chef
class Knife
+ #
+ # Public Methods of a Subcommand Loader
+ #
+ # load_commands - loads all available subcommands
+ # load_command(args) - loads subcommands for the given args
+ # list_commands(args) - lists all available subcommands,
+ # optionally filtering by category
+ # subcommand_files - returns an array of all subcommand files
+ # that could be loaded
+ # commnad_class_from(args) - returns the subcommand class for the
+ # user-requested command
+ #
class SubcommandLoader
-
- MATCHES_CHEF_GEM = %r{/chef-[\d]+\.[\d]+\.[\d]+}.freeze
- MATCHES_THIS_CHEF_GEM = %r{/chef-#{Chef::VERSION}/}.freeze
-
attr_reader :chef_config_dir
attr_reader :env
- def initialize(chef_config_dir, env=nil)
+ # A small factory method. Eventually, this is the only place
+ # where SubcommandLoader should know about its subclasses, but
+ # to maintain backwards compatibility many of the instance
+ # methods in this base class contain default implementations
+ # of the functions sub classes should otherwise provide
+ # or directly instantiate the appropriate subclass
+ def self.for_config(chef_config_dir)
+ if autogenerated_manifest?
+ Chef::Log.debug("Using autogenerated hashed command manifest #{plugin_manifest_path}")
+ Knife::SubcommandLoader::HashedCommandLoader.new(chef_config_dir, plugin_manifest)
+ elsif custom_manifest?
+ Chef.log_deprecation("Using custom manifest #{plugin_manifest_path} is deprecated. Please use a `knife rehash` autogenerated manifest instead.")
+ Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, plugin_manifest)
+ else
+ Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir)
+ end
+ end
+
+ def self.plugin_manifest?
+ plugin_manifest_path && File.exist?(plugin_manifest_path)
+ end
+
+ def self.autogenerated_manifest?
+ plugin_manifest? && plugin_manifest.key?(HashedCommandLoader::KEY)
+ end
+
+ def self.custom_manifest?
+ plugin_manifest? && plugin_manifest.key?("plugins")
+ end
+
+ def self.plugin_manifest
+ Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
+ end
+
+ def self.plugin_manifest_path
+ Chef::Util::PathHelper.home(".chef", "plugin_manifest.json")
+ end
+
+ def initialize(chef_config_dir, env = nil)
@chef_config_dir = chef_config_dir
- @forced_activate = {}
# Deprecated and un-used instance variable.
@env = env
unless env.nil?
- Chef::Log.deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
+ Chef.log_deprecation("The env argument to Chef::Knife::SubcommandLoader is deprecated. If you are using env to inject/mock HOME, consider mocking Chef::Util::PathHelper.home instead.")
end
end
# Load all the sub-commands
def load_commands
+ return true if @loaded
subcommand_files.each { |subcommand| Kernel.load subcommand }
- true
+ @loaded = true
end
- # Returns an Array of paths to knife commands located in chef_config_dir/plugins/knife/
- # and ~/.chef/plugins/knife/
- def site_subcommands
- user_specific_files = []
-
- if chef_config_dir
- user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
- end
-
- # finally search ~/.chef/plugins/knife/*.rb
- Chef::Util::PathHelper.home('.chef', 'plugins', 'knife') do |p|
- user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), '*.rb'))
- end
+ def force_load
+ @loaded=false
+ load_commands
+ end
- user_specific_files
+ def load_command(_command_args)
+ load_commands
end
- # Returns a Hash of paths to knife commands built-in to chef, or installed via gem.
- # If rubygems is not installed, falls back to globbing the knife directory.
- # The Hash is of the form {"relative/path" => "/absolute/path"}
- #--
- # Note: the "right" way to load the plugins is to require the relative path, i.e.,
- # require 'chef/knife/command'
- # but we're getting frustrated by bugs at every turn, and it's slow besides. So
- # subcommand loader has been modified to load the plugins by using Kernel.load
- # with the absolute path.
- def gem_and_builtin_subcommands
- if have_plugin_manifest?
- find_subcommands_via_manifest
+ def list_commands(pref_cat = nil)
+ load_commands
+ if pref_cat && Chef::Knife.subcommands_by_category.key?(pref_cat)
+ { pref_cat => Chef::Knife.subcommands_by_category[pref_cat] }
else
- # search all gems for chef/knife/*.rb
- require 'rubygems'
- find_subcommands_via_rubygems
+ Chef::Knife.subcommands_by_category
end
- rescue LoadError
- find_subcommands_via_dirglob
end
- def subcommand_files
- @subcommand_files ||= (gem_and_builtin_subcommands.values + site_subcommands).flatten.uniq
+ def command_class_from(args)
+ cmd_words = positional_arguments(args)
+ load_command(cmd_words)
+ result = Chef::Knife.subcommands[find_longest_key(Chef::Knife.subcommands,
+ cmd_words, "_")]
+ result || Chef::Knife.subcommands[args.first.gsub("-", "_")]
end
- # If the user has created a ~/.chef/plugin_manifest.json file, we'll use
- # that instead of inspecting the on-system gems to find the plugins. The
- # file format is expected to look like:
- #
- # { "plugins": {
- # "knife-ec2": {
- # "paths": [
- # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_create.rb",
- # "/home/alice/.rubymanagerthing/gems/knife-ec2-x.y.z/lib/chef/knife/ec2_server_delete.rb"
- # ]
- # }
- # }
- # }
- #
- # Extraneous content in this file is ignored. This intentional so that we
- # can adapt the file format for potential behavior changes to knife in
- # the future.
- def find_subcommands_via_manifest
- # Format of subcommand_files is "relative_path" (something you can
- # Kernel.require()) => full_path. The relative path isn't used
- # currently, so we just map full_path => full_path.
- subcommand_files = {}
- plugin_manifest["plugins"].each do |plugin_name, plugin_manifest|
- plugin_manifest["paths"].each do |cmd_path|
- subcommand_files[cmd_path] = cmd_path
- end
- end
- subcommand_files.merge(find_subcommands_via_dirglob)
+ def guess_category(args)
+ category_words = positional_arguments(args)
+ category_words.map! { |w| w.split("-") }.flatten!
+ find_longest_key(Chef::Knife.subcommands_by_category,
+ category_words, " ")
end
+
+ #
+ # This is shared between the custom_manifest_loader and the gem_glob_loader
+ #
def find_subcommands_via_dirglob
# The "require paths" of the core knife subcommands bundled with chef
- files = Dir[File.join(Chef::Util::PathHelper.escape_glob(File.expand_path('../../../knife', __FILE__)), '*.rb')]
+ 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]
@@ -128,95 +143,65 @@ class Chef
subcommand_files
end
- def find_subcommands_via_rubygems
- files = find_files_latest_gems 'chef/knife/*.rb'
- subcommand_files = {}
- files.each do |file|
- rel_path = file[/(#{Regexp.escape File.join('chef', 'knife', '')}.*)\.rb/, 1]
-
- # When not installed as a gem (ChefDK/appbundler in particular), AND
- # a different version of Chef is installed via gems, `files` will
- # include some files from the 'other' Chef install. If this contains
- # a knife command that doesn't exist in this version of Chef, we will
- # get a LoadError later when we try to require it.
- next if from_different_chef_version?(file)
-
- subcommand_files[rel_path] = file
- end
-
- subcommand_files.merge(find_subcommands_via_dirglob)
- end
-
- def have_plugin_manifest?
- plugin_manifest_path && File.exist?(plugin_manifest_path)
- end
-
- def plugin_manifest
- Chef::JSONCompat.from_json(File.read(plugin_manifest_path))
- end
-
- def plugin_manifest_path
- Chef::Util::PathHelper.home('.chef', 'plugin_manifest.json')
+ #
+ # Subclassses should define this themselves. Eventually, this will raise a
+ # NotImplemented error, but for now, we mimic the behavior the user was likely
+ # to get in the past.
+ #
+ def subcommand_files
+ Chef.log_deprecation "Using Chef::Knife::SubcommandLoader directly is deprecated.
+Please use Chef::Knife::SubcommandLoader.for_config(chef_config_dir, env)"
+ @subcommand_files ||= if Chef::Knife::SubcommandLoader.plugin_manifest?
+ Chef::Knife::SubcommandLoader::CustomManifestLoader.new(chef_config_dir, env).subcommand_files
+ else
+ Chef::Knife::SubcommandLoader::GemGlobLoader.new(chef_config_dir, env).subcommand_files
+ end
end
- private
-
- def find_files_latest_gems(glob, check_load_path=true)
- files = []
-
- if check_load_path
- files = $LOAD_PATH.map { |load_path|
- Dir["#{File.expand_path glob, Chef::Util::PathHelper.escape_glob(load_path)}#{Gem.suffix_pattern}"]
- }.flatten.select { |file| File.file? file.untaint }
- end
-
- gem_files = latest_gem_specs.map do |spec|
- # Gem::Specification#matches_for_glob wasn't added until RubyGems 1.8
- if spec.respond_to? :matches_for_glob
- spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}")
+ #
+ # Utility function for finding an element in a hash given an array
+ # of words and a separator. We find the the longest key in the
+ # hash composed of the given words joined by the separator.
+ #
+ def find_longest_key(hash, words, sep = "_")
+ match = nil
+ until match || words.empty?
+ candidate = words.join(sep)
+ if hash.key?(candidate)
+ match = candidate
else
- check_spec_for_glob(spec, glob)
+ words.pop
end
- end.flatten
-
- files.concat gem_files
- files.uniq! if check_load_path
-
- return files
- end
-
- def latest_gem_specs
- @latest_gem_specs ||= if Gem::Specification.respond_to? :latest_specs
- Gem::Specification.latest_specs(true) # find prerelease gems
- else
- Gem.source_index.latest_specs(true)
end
+ match
end
- def check_spec_for_glob(spec, glob)
- dirs = if spec.require_paths.size > 1 then
- "{#{spec.require_paths.join(',')}}"
- else
- spec.require_paths.first
- end
-
- glob = File.join(Chef::Util::PathHelper.escape_glob(spec.full_gem_path, dirs), glob)
-
- Dir[glob].map { |f| f.untaint }
+ #
+ # The positional arguments from the argument list provided by the
+ # users. Used to search for subcommands and categories.
+ #
+ # @return [Array<String>]
+ #
+ def positional_arguments(args)
+ args.select { |arg| arg =~ /^(([[:alnum:]])[[:alnum:]\_\-]+)$/ }
end
- def from_different_chef_version?(path)
- matches_any_chef_gem?(path) && !matches_this_chef_gem?(path)
- end
+ # Returns an Array of paths to knife commands located in
+ # chef_config_dir/plugins/knife/ and ~/.chef/plugins/knife/
+ def site_subcommands
+ user_specific_files = []
- def matches_any_chef_gem?(path)
- path =~ MATCHES_CHEF_GEM
- end
+ if chef_config_dir
+ user_specific_files.concat Dir.glob(File.expand_path("plugins/knife/*.rb", Chef::Util::PathHelper.escape_glob(chef_config_dir)))
+ end
- def matches_this_chef_gem?(path)
- path =~ MATCHES_THIS_CHEF_GEM
- end
+ # finally search ~/.chef/plugins/knife/*.rb
+ Chef::Util::PathHelper.home(".chef", "plugins", "knife") do |p|
+ user_specific_files.concat Dir.glob(File.join(Chef::Util::PathHelper.escape_glob(p), "*.rb"))
+ end
+ user_specific_files
+ end
end
end
end
diff --git a/lib/chef/knife/core/text_formatter.rb b/lib/chef/knife/core/text_formatter.rb
index 60328488b2..6295d42ec2 100644
--- a/lib/chef/knife/core/text_formatter.rb
+++ b/lib/chef/knife/core/text_formatter.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,14 +27,14 @@ class Chef
def initialize(data, ui)
@ui = ui
@data = if data.respond_to?(:display_hash)
- data.display_hash
- elsif data.kind_of?(Array)
- data
- elsif data.respond_to?(:to_hash)
- data.to_hash
- else
- data
- end
+ data.display_hash
+ elsif data.kind_of?(Array)
+ data
+ elsif data.respond_to?(:to_hash)
+ data.to_hash
+ else
+ data
+ end
end
def formatted_data
@@ -42,7 +42,7 @@ class Chef
end
def text_format(data)
- buffer = ''
+ buffer = ""
if data.respond_to?(:keys)
justify_width = data.keys.map {|k| k.to_s.size }.max.to_i + 1
@@ -83,4 +83,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/knife/core/ui.rb b/lib/chef/knife/core/ui.rb
index a54f14afc1..edc51088ec 100644
--- a/lib/chef/knife/core/ui.rb
+++ b/lib/chef/knife/core/ui.rb
@@ -18,10 +18,10 @@
# limitations under the License.
#
-require 'forwardable'
-require 'chef/platform/query_helpers'
-require 'chef/knife/core/generic_presenter'
-require 'tempfile'
+require "forwardable"
+require "chef/platform/query_helpers"
+require "chef/knife/core/generic_presenter"
+require "tempfile"
class Chef
class Knife
@@ -57,7 +57,7 @@ class Chef
def highline
@highline ||= begin
- require 'highline'
+ require "highline"
HighLine.new
end
end
@@ -175,7 +175,7 @@ class Chef
def edit_data(data, parse_output=true)
output = Chef::JSONCompat.to_json_pretty(data)
if (!config[:disable_editing])
- Tempfile.open([ 'knife-edit-', '.json' ]) do |tf|
+ Tempfile.open([ "knife-edit-", ".json" ]) do |tf|
tf.sync = true
tf.puts output
tf.close
@@ -217,11 +217,11 @@ class Chef
def confirmation_instructions(default_choice)
case default_choice
when true
- '? (Y/n) '
+ "? (Y/n) "
when false
- '? (y/N) '
+ "? (y/N) "
else
- '? (Y/N) '
+ "? (Y/N) "
end
end
diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb
index f8a7619a8a..afd7a90b7f 100644
--- a/lib/chef/knife/data_bag_create.rb
+++ b/lib/chef/knife/data_bag_create.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Seth Falcon (<seth@opscode.com>)
-# Copyright:: Copyright (c) 2009-2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2009-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/knife/data_bag_secret_options'
+require "chef/knife"
+require "chef/knife/data_bag_secret_options"
class Chef
class Knife
@@ -26,8 +26,8 @@ class Chef
include DataBagSecretOptions
deps do
- require 'chef/data_bag'
- require 'chef/encrypted_data_bag_item'
+ require "chef/data_bag"
+ require "chef/encrypted_data_bag_item"
end
banner "knife data bag create BAG [ITEM] (options)"
@@ -51,7 +51,7 @@ class Chef
# create the data bag
begin
- rest.post_rest("data", { "name" => @data_bag_name })
+ rest.post("data", { "name" => @data_bag_name })
ui.info("Created data_bag[#{@data_bag_name}]")
rescue Net::HTTPServerException => e
raise unless e.to_s =~ /^409/
@@ -66,9 +66,10 @@ class Chef
Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret)
else
output
- end)
+ end
+ )
item.data_bag(@data_bag_name)
- rest.post_rest("data/#{@data_bag_name}", item)
+ rest.post("data/#{@data_bag_name}", item)
end
end
end
diff --git a/lib/chef/knife/data_bag_delete.rb b/lib/chef/knife/data_bag_delete.rb
index 575e9d604d..4f5efd6ed6 100644
--- a/lib/chef/knife/data_bag_delete.rb
+++ b/lib/chef/knife/data_bag_delete.rb
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class DataBagDelete < Knife
deps do
- require 'chef/data_bag'
+ require "chef/data_bag"
end
banner "knife data bag delete BAG [ITEM] (options)"
@@ -32,11 +32,11 @@ class Chef
def run
if @name_args.length == 2
delete_object(Chef::DataBagItem, @name_args[1], "data_bag_item") do
- rest.delete_rest("data/#{@name_args[0]}/#{@name_args[1]}")
+ rest.delete("data/#{@name_args[0]}/#{@name_args[1]}")
end
elsif @name_args.length == 1
delete_object(Chef::DataBag, @name_args[0], "data_bag") do
- rest.delete_rest("data/#{@name_args[0]}")
+ rest.delete("data/#{@name_args[0]}")
end
else
show_usage
diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb
index 6ef4b33f59..5a722dc278 100644
--- a/lib/chef/knife/data_bag_edit.rb
+++ b/lib/chef/knife/data_bag_edit.rb
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/knife/data_bag_secret_options'
+require "chef/knife"
+require "chef/knife/data_bag_secret_options"
class Chef
class Knife
@@ -26,8 +26,8 @@ class Chef
include DataBagSecretOptions
deps do
- require 'chef/data_bag_item'
- require 'chef/encrypted_data_bag_item'
+ require "chef/data_bag_item"
+ require "chef/encrypted_data_bag_item"
end
banner "knife data bag edit BAG ITEM (options)"
@@ -65,7 +65,7 @@ class Chef
item_to_save = edited_item
end
- rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save)
+ rest.put("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save)
stdout.puts("Saved data_bag_item[#{@name_args[1]}]")
ui.output(edited_item) if config[:print_after]
end
diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb
index d1b7daa4a2..617a187b52 100644
--- a/lib/chef/knife/data_bag_from_file.rb
+++ b/lib/chef/knife/data_bag_from_file.rb
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/util/path_helper'
-require 'chef/knife/data_bag_secret_options'
+require "chef/knife"
+require "chef/util/path_helper"
+require "chef/knife/data_bag_secret_options"
class Chef
class Knife
@@ -27,11 +27,11 @@ class Chef
include DataBagSecretOptions
deps do
- require 'chef/data_bag'
- require 'chef/data_bag_item'
- require 'chef/knife/core/object_loader'
- require 'chef/json_compat'
- require 'chef/encrypted_data_bag_item'
+ require "chef/data_bag"
+ require "chef/data_bag_item"
+ require "chef/knife/core/object_loader"
+ require "chef/json_compat"
+ require "chef/encrypted_data_bag_item"
end
banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)"
diff --git a/lib/chef/knife/data_bag_list.rb b/lib/chef/knife/data_bag_list.rb
index 5e556b60bc..93a28a9b4c 100644
--- a/lib/chef/knife/data_bag_list.rb
+++ b/lib/chef/knife/data_bag_list.rb
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class DataBagList < Knife
deps do
- require 'chef/data_bag'
+ require "chef/data_bag"
end
banner "knife data bag list (options)"
diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb
index 766006089e..2a585bda57 100644
--- a/lib/chef/knife/data_bag_secret_options.rb
+++ b/lib/chef/knife/data_bag_secret_options.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'mixlib/cli'
-require 'chef/config'
-require 'chef/encrypted_data_bag_item/check_encrypted'
+require "mixlib/cli"
+require "chef/config"
+require "chef/encrypted_data_bag_item/check_encrypted"
class Chef
class Knife
@@ -65,7 +65,7 @@ class Chef
def read_secret
# Moving the non 'compile-time' requires into here to speed up knife command loading
# IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item'
- require 'chef/encrypted_data_bag_item'
+ require "chef/encrypted_data_bag_item"
if has_cl_secret?
config[:secret]
diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb
index 36715286e8..1b580ed50b 100644
--- a/lib/chef/knife/data_bag_show.rb
+++ b/lib/chef/knife/data_bag_show.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Seth Falcon (<seth@opscode.com>)
-# Copyright:: Copyright (c) 2009-2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2009-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/knife/data_bag_secret_options'
+require "chef/knife"
+require "chef/knife/data_bag_secret_options"
class Chef
class Knife
@@ -26,8 +26,8 @@ class Chef
include DataBagSecretOptions
deps do
- require 'chef/data_bag'
- require 'chef/encrypted_data_bag_item'
+ require "chef/data_bag"
+ require "chef/encrypted_data_bag_item"
end
banner "knife data bag show BAG [ITEM] (options)"
@@ -35,32 +35,32 @@ class Chef
def run
display = case @name_args.length
- when 2 # Bag and Item names provided
- secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
- raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data
- encrypted = encrypted?(raw_data)
+ when 2 # Bag and Item names provided
+ secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
+ raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data
+ encrypted = encrypted?(raw_data)
- if encrypted && secret
- # Users do not need to pass --encrypt to read data, we simply try to use the provided secret
- ui.info("Encrypted data bag detected, decrypting with provided secret.")
- raw = Chef::EncryptedDataBagItem.load(@name_args[0],
- @name_args[1],
- secret)
- format_for_display(raw.to_hash)
- elsif encrypted && !secret
- ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
- format_for_display(raw_data)
- else
- ui.info("Unencrypted data bag detected, ignoring any provided secret options.")
- format_for_display(raw_data)
- end
+ if encrypted && secret
+ # Users do not need to pass --encrypt to read data, we simply try to use the provided secret
+ ui.info("Encrypted data bag detected, decrypting with provided secret.")
+ raw = Chef::EncryptedDataBagItem.load(@name_args[0],
+ @name_args[1],
+ secret)
+ format_for_display(raw.to_hash)
+ elsif encrypted && !secret
+ ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.")
+ format_for_display(raw_data)
+ else
+ ui.info("Unencrypted data bag detected, ignoring any provided secret options.")
+ format_for_display(raw_data)
+ end
- when 1 # Only Bag name provided
- format_list_for_display(Chef::DataBag.load(@name_args[0]))
- else
- stdout.puts opt_parser
- exit(1)
- end
+ when 1 # Only Bag name provided
+ format_list_for_display(Chef::DataBag.load(@name_args[0]))
+ else
+ stdout.puts opt_parser
+ exit(1)
+ end
output(display)
end
diff --git a/lib/chef/knife/delete.rb b/lib/chef/knife/delete.rb
index 0030c45026..aeb4744873 100644
--- a/lib/chef/knife/delete.rb
+++ b/lib/chef/knife/delete.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,22 +8,22 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/file_system'
+ require "chef/chef_fs/file_system"
end
option :recurse,
- :short => '-r',
- :long => '--[no-]recurse',
+ :short => "-r",
+ :long => "--[no-]recurse",
:boolean => true,
:default => false,
:description => "Delete directories recursively."
option :both,
- :long => '--both',
+ :long => "--both",
:boolean => true,
:default => false,
:description => "Delete both the local and remote copies."
option :local,
- :long => '--local',
+ :long => "--local",
:boolean => true,
:default => false,
:description => "Delete the local copy (leave the remote copy)."
diff --git a/lib/chef/knife/deps.rb b/lib/chef/knife/deps.rb
index 4b23468962..0be83a8bf1 100644
--- a/lib/chef/knife/deps.rb
+++ b/lib/chef/knife/deps.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,20 +8,20 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/file_system'
- require 'chef/run_list'
+ require "chef/chef_fs/file_system"
+ require "chef/run_list"
end
option :recurse,
- :long => '--[no-]recurse',
+ :long => "--[no-]recurse",
:boolean => true,
:description => "List dependencies recursively (default: true). Only works with --tree."
option :tree,
- :long => '--tree',
+ :long => "--tree",
:boolean => true,
:description => "Show dependencies in a visual tree. May show duplicates."
option :remote,
- :long => '--remote',
+ :long => "--remote",
:boolean => true,
:description => "List dependencies on the server instead of the local filesystem"
@@ -73,30 +73,30 @@ class Chef
def get_dependencies(entry)
begin
- if entry.parent && entry.parent.path == '/cookbooks'
+ if entry.parent && entry.parent.path == "/cookbooks"
return entry.chef_object.metadata.dependencies.keys.map { |cookbook| "/cookbooks/#{cookbook}" }
- elsif entry.parent && entry.parent.path == '/nodes'
+ elsif entry.parent && entry.parent.path == "/nodes"
node = Chef::JSONCompat.parse(entry.read)
result = []
- if node['chef_environment'] && node['chef_environment'] != '_default'
+ if node["chef_environment"] && node["chef_environment"] != "_default"
result << "/environments/#{node['chef_environment']}.json"
end
- if node['run_list']
- result += dependencies_from_runlist(node['run_list'])
+ if node["run_list"]
+ result += dependencies_from_runlist(node["run_list"])
end
result
- elsif entry.parent && entry.parent.path == '/roles'
+ elsif entry.parent && entry.parent.path == "/roles"
role = Chef::JSONCompat.parse(entry.read)
result = []
- if role['run_list']
- dependencies_from_runlist(role['run_list']).each do |dependency|
+ if role["run_list"]
+ dependencies_from_runlist(role["run_list"]).each do |dependency|
result << dependency if !result.include?(dependency)
end
end
- if role['env_run_lists']
- role['env_run_lists'].each_pair do |env,run_list|
+ if role["env_run_lists"]
+ role["env_run_lists"].each_pair do |env,run_list|
dependencies_from_runlist(run_list).each do |dependency|
result << dependency if !result.include?(dependency)
end
diff --git a/lib/chef/knife/diff.rb b/lib/chef/knife/diff.rb
index e5eda5228c..580e9e513f 100644
--- a/lib/chef/knife/diff.rb
+++ b/lib/chef/knife/diff.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,33 +8,33 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/command_line'
+ require "chef/chef_fs/command_line"
end
option :recurse,
- :long => '--[no-]recurse',
+ :long => "--[no-]recurse",
:boolean => true,
:default => true,
:description => "List directories recursively."
option :name_only,
- :long => '--name-only',
+ :long => "--name-only",
:boolean => true,
:description => "Only show names of modified files."
option :name_status,
- :long => '--name-status',
+ :long => "--name-status",
:boolean => true,
:description => "Only show names and statuses of modified files: Added, Deleted, Modified, and Type Changed."
option :diff_filter,
- :long => '--diff-filter=[(A|D|M|T)...[*]]',
+ :long => "--diff-filter=[(A|D|M|T)...[*]]",
:description => "Select only files that are Added (A), Deleted (D), Modified (M), or have their type (i.e. regular file, directory) changed (T). Any combination of the filter characters (including none) can be used. When * (All-or-none) is added to the combination, all paths are selected if
there is any file that matches other criteria in the comparison; if there is no file that matches other criteria, nothing is selected."
option :cookbook_version,
- :long => '--cookbook-version VERSION',
- :description => 'Version of cookbook to download (if there are multiple versions and cookbook_versions is false)'
+ :long => "--cookbook-version VERSION",
+ :description => "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)"
def run
if config[:name_only]
diff --git a/lib/chef/knife/download.rb b/lib/chef/knife/download.rb
index 5a432afd47..f2af0e0152 100644
--- a/lib/chef/knife/download.rb
+++ b/lib/chef/knife/download.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,43 +8,43 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/command_line'
+ require "chef/chef_fs/command_line"
end
option :recurse,
- :long => '--[no-]recurse',
+ :long => "--[no-]recurse",
:boolean => true,
:default => true,
:description => "List directories recursively."
option :purge,
- :long => '--[no-]purge',
+ :long => "--[no-]purge",
:boolean => true,
:default => false,
:description => "Delete matching local files and directories that do not exist remotely."
option :force,
- :long => '--[no-]force',
+ :long => "--[no-]force",
:boolean => true,
:default => false,
:description => "Force upload of files even if they match (quicker and harmless, but doesn't print out what it changed)"
option :dry_run,
- :long => '--dry-run',
- :short => '-n',
+ :long => "--dry-run",
+ :short => "-n",
:boolean => true,
:default => false,
:description => "Don't take action, only print what would happen"
option :diff,
- :long => '--[no-]diff',
+ :long => "--[no-]diff",
:boolean => true,
:default => true,
- :description => 'Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff'
+ :description => "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff"
option :cookbook_version,
- :long => '--cookbook-version VERSION',
- :description => 'Version of cookbook to download (if there are multiple versions and cookbook_versions is false)'
+ :long => "--cookbook-version VERSION",
+ :description => "Version of cookbook to download (if there are multiple versions and cookbook_versions is false)"
def run
if name_args.length == 0
diff --git a/lib/chef/knife/edit.rb b/lib/chef/knife/edit.rb
index 7408179ed0..608ce82a8f 100644
--- a/lib/chef/knife/edit.rb
+++ b/lib/chef/knife/edit.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,12 +8,12 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/file_system'
- require 'chef/chef_fs/file_system/not_found_error'
+ require "chef/chef_fs/file_system"
+ require "chef/chef_fs/file_system/not_found_error"
end
option :local,
- :long => '--local',
+ :long => "--local",
:boolean => true,
:description => "Show local files instead of remote"
@@ -51,7 +51,7 @@ class Chef
def edit_text(text, extension)
if (!config[:disable_editing])
- Tempfile.open([ 'knife-edit-', extension ]) do |file|
+ Tempfile.open([ "knife-edit-", extension ]) do |file|
# Write the text to a temporary file
file.write(text)
file.close
diff --git a/lib/chef/knife/environment_compare.rb b/lib/chef/knife/environment_compare.rb
index 792ec444ea..a4f99da255 100644
--- a/lib/chef/knife/environment_compare.rb
+++ b/lib/chef/knife/environment_compare.rb
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class EnvironmentCompare < Knife
deps do
- require 'chef/environment'
+ require "chef/environment"
end
banner "knife environment compare [ENVIRONMENT..] (options)"
@@ -57,10 +57,10 @@ class Chef
end
# Get all cookbooks so we can compare them all
- cookbooks = rest.get_rest("/cookbooks?num_versions=1") if config[:all]
+ cookbooks = rest.get("/cookbooks?num_versions=1") if config[:all]
# display matrix view of in the requested format.
- if config[:format] == 'summary'
+ if config[:format] == "summary"
matrix = matrix_output(cookbooks, constraints)
ui.output(matrix)
else
@@ -99,7 +99,7 @@ class Chef
end
def matrix_output(cookbooks, constraints)
- rows = [ '' ]
+ rows = [ "" ]
environments = []
constraints.each { |e,v| environments << e.to_s }
columns = environments.count + 1
diff --git a/lib/chef/knife/environment_create.rb b/lib/chef/knife/environment_create.rb
index 6bc00d52b9..ea69b9b874 100644
--- a/lib/chef/knife/environment_create.rb
+++ b/lib/chef/knife/environment_create.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class EnvironmentCreate < Knife
deps do
- require 'chef/environment'
- require 'chef/json_compat'
+ require "chef/environment"
+ require "chef/json_compat"
end
banner "knife environment create ENVIRONMENT (options)"
diff --git a/lib/chef/knife/environment_delete.rb b/lib/chef/knife/environment_delete.rb
index e17841f805..3cd0d46cd1 100644
--- a/lib/chef/knife/environment_delete.rb
+++ b/lib/chef/knife/environment_delete.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class EnvironmentDelete < Knife
deps do
- require 'chef/environment'
- require 'chef/json_compat'
+ require "chef/environment"
+ require "chef/json_compat"
end
banner "knife environment delete ENVIRONMENT (options)"
diff --git a/lib/chef/knife/environment_edit.rb b/lib/chef/knife/environment_edit.rb
index 54962ac20d..72d774aee4 100644
--- a/lib/chef/knife/environment_edit.rb
+++ b/lib/chef/knife/environment_edit.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class EnvironmentEdit < Knife
deps do
- require 'chef/environment'
- require 'chef/json_compat'
+ require "chef/environment"
+ require "chef/json_compat"
end
banner "knife environment edit ENVIRONMENT (options)"
diff --git a/lib/chef/knife/environment_from_file.rb b/lib/chef/knife/environment_from_file.rb
index 3812024c9c..9b38fc53e0 100644
--- a/lib/chef/knife/environment_from_file.rb
+++ b/lib/chef/knife/environment_from_file.rb
@@ -21,8 +21,8 @@ class Chef
class EnvironmentFromFile < Knife
deps do
- require 'chef/environment'
- require 'chef/knife/core/object_loader'
+ require "chef/environment"
+ require "chef/knife/core/object_loader"
end
banner "knife environment from file FILE [FILE..] (options)"
diff --git a/lib/chef/knife/environment_list.rb b/lib/chef/knife/environment_list.rb
index 4e70818093..801d14b6c2 100644
--- a/lib/chef/knife/environment_list.rb
+++ b/lib/chef/knife/environment_list.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class EnvironmentList < Knife
deps do
- require 'chef/environment'
- require 'chef/json_compat'
+ require "chef/environment"
+ require "chef/json_compat"
end
banner "knife environment list (options)"
diff --git a/lib/chef/knife/environment_show.rb b/lib/chef/knife/environment_show.rb
index 2dd07fe6e9..fe79c3f08d 100644
--- a/lib/chef/knife/environment_show.rb
+++ b/lib/chef/knife/environment_show.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -25,8 +25,8 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/environment'
- require 'chef/json_compat'
+ require "chef/environment"
+ require "chef/json_compat"
end
banner "knife environment show ENVIRONMENT (options)"
diff --git a/lib/chef/knife/exec.rb b/lib/chef/knife/exec.rb
index ace4ee2300..7788ae8d85 100644
--- a/lib/chef/knife/exec.rb
+++ b/lib/chef/knife/exec.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/util/path_helper'
+require "chef/knife"
+require "chef/util/path_helper"
class Chef::Knife::Exec < Chef::Knife
@@ -35,15 +35,15 @@ class Chef::Knife::Exec < Chef::Knife
:proc => lambda { |o| o.split(":") }
deps do
- require 'chef/shell/ext'
+ require "chef/shell/ext"
end
def run
config[:script_path] ||= Array(Chef::Config[:script_path])
# Default script paths are chef-repo/.chef/scripts and ~/.chef/scripts
- config[:script_path] << File.join(Chef::Knife.chef_config_dir, 'scripts') if Chef::Knife.chef_config_dir
- Chef::Util::PathHelper.home('.chef', 'scripts') { |p| config[:script_path] << p }
+ config[:script_path] << File.join(Chef::Knife.chef_config_dir, "scripts") if Chef::Knife.chef_config_dir
+ Chef::Util::PathHelper.home(".chef", "scripts") { |p| config[:script_path] << p }
scripts = Array(name_args)
context = Object.new
diff --git a/lib/chef/knife/help.rb b/lib/chef/knife/help.rb
index 13fe674704..b8cfab142c 100644
--- a/lib/chef/knife/help.rb
+++ b/lib/chef/knife/help.rb
@@ -40,17 +40,17 @@ class Chef
MOAR_HELP
exit 1
else
- @query = name_args.join('-')
+ @query = name_args.join("-")
end
case @query
- when 'topics', 'list'
+ when "topics", "list"
print_help_topics
exit 1
- when 'intro', 'knife'
- @topic = 'knife'
+ when "intro", "knife"
+ @topic = "knife"
else
@topic = find_manpages_for_query(@query)
end
@@ -67,7 +67,7 @@ MOAR_HELP
def print_help_topics
ui.info "Available help topics are: "
- help_topics.collect {|t| t.gsub(/knife-/, '') }.sort.each do |topic|
+ help_topics.collect {|t| t.gsub(/knife-/, "") }.sort.each do |topic|
ui.msg " #{topic}"
end
end
diff --git a/lib/chef/knife/index_rebuild.rb b/lib/chef/knife/index_rebuild.rb
index 4b9fcdd159..22962c6bec 100644
--- a/lib/chef/knife/index_rebuild.rb
+++ b/lib/chef/knife/index_rebuild.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -38,7 +38,7 @@ class Chef
else
deprecated_server_message
nag
- output rest.post_rest("/search/reindex", {})
+ output rest.post("/search/reindex", {})
end
end
@@ -50,7 +50,7 @@ class Chef
# for a node we know won't exist; the 404 response that comes
# back will give us what we want
dummy_node = "knife_index_rebuild_test_#{rand(1000000)}"
- rest.get_rest("/nodes/#{dummy_node}")
+ rest.get("/nodes/#{dummy_node}")
rescue Net::HTTPServerException => exception
r = exception.response
parse_api_info(r)
diff --git a/lib/chef/knife/key_create.rb b/lib/chef/knife/key_create.rb
new file mode 100644
index 0000000000..e0946fe88d
--- /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..725296b22b
--- /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..e08dd9eb65
--- /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..7a968e85bd
--- /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..4b9ff617ef
--- /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/list.rb b/lib/chef/knife/list.rb
index 137d61f3a5..3d1583b270 100644
--- a/lib/chef/knife/list.rb
+++ b/lib/chef/knife/list.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,33 +8,33 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/file_system'
- require 'highline'
+ require "chef/chef_fs/file_system"
+ require "highline"
end
option :recursive,
- :short => '-R',
+ :short => "-R",
:boolean => true,
:description => "List directories recursively"
option :bare_directories,
- :short => '-d',
+ :short => "-d",
:boolean => true,
:description => "When directories match the pattern, do not show the directories' children"
option :local,
- :long => '--local',
+ :long => "--local",
:boolean => true,
:description => "List local directory instead of remote"
option :flat,
- :short => '-f',
- :long => '--flat',
+ :short => "-f",
+ :long => "--flat",
:boolean => true,
:description => "Show a list of filenames rather than the prettified ls-like output normally produced"
option :one_column,
- :short => '-1',
+ :short => "-1",
:boolean => true,
:description => "Show only one column of results"
option :trailing_slashes,
- :short => '-p',
+ :short => "-p",
:boolean => true,
:description => "Show trailing slashes after directories"
@@ -47,6 +47,7 @@ class Chef
args = pattern_args_from(patterns)
all_results = parallelize(pattern_args_from(patterns)) do |pattern|
pattern_results = Chef::ChefFS::FileSystem.list(config[:local] ? local_fs : chef_fs, pattern).to_a
+
if pattern_results.first && !pattern_results.first.exists? && pattern.exact_path
ui.error "#{format_path(pattern_results.first)}: No such file or directory"
self.exit_code = 1
@@ -129,17 +130,17 @@ class Chef
else
columns = HighLine::SystemExtensions.terminal_size[0]
end
- current_line = ''
+ current_line = ""
results.each do |result|
if current_line.length > 0 && current_line.length + print_space > columns
output current_line.rstrip
- current_line = ''
+ current_line = ""
end
if current_line.length == 0
current_line << indent
end
current_line << result
- current_line << (' ' * (print_space - result.length))
+ current_line << (" " * (print_space - result.length))
end
output current_line.rstrip if current_line.length > 0
end
diff --git a/lib/chef/knife/node_bulk_delete.rb b/lib/chef/knife/node_bulk_delete.rb
index 89b2abe6ae..45291bf033 100644
--- a/lib/chef/knife/node_bulk_delete.rb
+++ b/lib/chef/knife/node_bulk_delete.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeBulkDelete < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node bulk delete REGEX (options)"
diff --git a/lib/chef/knife/node_create.rb b/lib/chef/knife/node_create.rb
index 7f50a30c80..351f407b25 100644
--- a/lib/chef/knife/node_create.rb
+++ b/lib/chef/knife/node_create.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeCreate < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node create NODE (options)"
diff --git a/lib/chef/knife/node_delete.rb b/lib/chef/knife/node_delete.rb
index a645d32035..d9e48f96c4 100644
--- a/lib/chef/knife/node_delete.rb
+++ b/lib/chef/knife/node_delete.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeDelete < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node delete NODE (options)"
diff --git a/lib/chef/knife/node_edit.rb b/lib/chef/knife/node_edit.rb
index 0d6b8fcf6c..fd7637b529 100644
--- a/lib/chef/knife/node_edit.rb
+++ b/lib/chef/knife/node_edit.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -24,9 +24,9 @@ class Chef
class NodeEdit < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
- require 'chef/knife/core/node_editor'
+ require "chef/node"
+ require "chef/json_compat"
+ require "chef/knife/core/node_editor"
end
banner "knife node edit NODE (options)"
diff --git a/lib/chef/knife/node_environment_set.rb b/lib/chef/knife/node_environment_set.rb
index da72aeaab8..b91ede56a5 100644
--- a/lib/chef/knife/node_environment_set.rb
+++ b/lib/chef/knife/node_environment_set.rb
@@ -16,14 +16,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeEnvironmentSet < Knife
deps do
- require 'chef/node'
+ require "chef/node"
end
banner "knife node environment set NODE ENVIRONMENT"
diff --git a/lib/chef/knife/node_from_file.rb b/lib/chef/knife/node_from_file.rb
index 66f2a466fd..3c43c69bbf 100644
--- a/lib/chef/knife/node_from_file.rb
+++ b/lib/chef/knife/node_from_file.rb
@@ -16,16 +16,16 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeFromFile < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
- require 'chef/knife/core/object_loader'
+ require "chef/node"
+ require "chef/json_compat"
+ require "chef/knife/core/object_loader"
end
banner "knife node from file FILE (options)"
@@ -36,7 +36,7 @@ class Chef
def run
@name_args.each do |arg|
- updated = loader.load_from('nodes', arg)
+ updated = loader.load_from("nodes", arg)
updated.save
diff --git a/lib/chef/knife/node_list.rb b/lib/chef/knife/node_list.rb
index 3926d101cf..9348341a21 100644
--- a/lib/chef/knife/node_list.rb
+++ b/lib/chef/knife/node_list.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeList < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node list (options)"
diff --git a/lib/chef/knife/node_run_list_add.rb b/lib/chef/knife/node_run_list_add.rb
index 519c280400..ca7e8c9191 100644
--- a/lib/chef/knife/node_run_list_add.rb
+++ b/lib/chef/knife/node_run_list_add.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeRunListAdd < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node run_list add [NODE] [ENTRY[,ENTRY]] (options)"
@@ -44,11 +44,11 @@ class Chef
if @name_args.size > 2
# Check for nested lists and create a single plain one
entries = @name_args[1..-1].map do |entry|
- entry.split(',').map { |e| e.strip }
+ entry.split(",").map { |e| e.strip }
end.flatten
else
# Convert to array and remove the extra spaces
- entries = @name_args[1].split(',').map { |e| e.strip }
+ entries = @name_args[1].split(",").map { |e| e.strip }
end
if config[:after] && config[:before]
diff --git a/lib/chef/knife/node_run_list_remove.rb b/lib/chef/knife/node_run_list_remove.rb
index 4b8953a264..d2bfcf6c9f 100644
--- a/lib/chef/knife/node_run_list_remove.rb
+++ b/lib/chef/knife/node_run_list_remove.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeRunListRemove < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node run_list remove [NODE] [ENTRY[,ENTRY]] (options)"
@@ -35,14 +35,25 @@ class Chef
if @name_args.size > 2
# Check for nested lists and create a single plain one
entries = @name_args[1..-1].map do |entry|
- entry.split(',').map { |e| e.strip }
+ entry.split(",").map { |e| e.strip }
end.flatten
else
# Convert to array and remove the extra spaces
- entries = @name_args[1].split(',').map { |e| e.strip }
+ entries = @name_args[1].split(",").map { |e| e.strip }
end
- entries.each { |e| node.run_list.remove(e) }
+ # iterate over the list of things to remove,
+ # warning if one of them was not found
+ entries.each do |e|
+ if node.run_list.find { |rli| e == rli.to_s }
+ node.run_list.remove(e)
+ else
+ ui.warn "#{e} is not in the run list"
+ unless e =~ /^(recipe|role)\[/
+ ui.warn "(did you forget recipe[] or role[] around it?)"
+ end
+ end
+ end
node.save
diff --git a/lib/chef/knife/node_run_list_set.rb b/lib/chef/knife/node_run_list_set.rb
index e327d2acee..8d12c1f5bf 100644
--- a/lib/chef/knife/node_run_list_set.rb
+++ b/lib/chef/knife/node_run_list_set.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class NodeRunListSet < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node run_list set NODE ENTRIES (options)"
@@ -37,11 +37,11 @@ class Chef
elsif @name_args.size > 2
# Check for nested lists and create a single plain one
entries = @name_args[1..-1].map do |entry|
- entry.split(',').map { |e| e.strip }
+ entry.split(",").map { |e| e.strip }
end.flatten
else
# Convert to array and remove the extra spaces
- entries = @name_args[1].split(',').map { |e| e.strip }
+ entries = @name_args[1].split(",").map { |e| e.strip }
end
node = Chef::Node.load(@name_args[0])
diff --git a/lib/chef/knife/node_show.rb b/lib/chef/knife/node_show.rb
index dc47da1e8f..115158e52e 100644
--- a/lib/chef/knife/node_show.rb
+++ b/lib/chef/knife/node_show.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/knife/core/node_presenter'
+require "chef/knife"
+require "chef/knife/core/node_presenter"
class Chef
class Knife
@@ -27,8 +27,8 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife node show NODE (options)"
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..c5ccbaa13a
--- /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..4c79f5b69d
--- /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..db958a8852
--- /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..9c0e3a7c00
--- /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..bd983321f5
--- /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..223fefa5e1
--- /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/raw.rb b/lib/chef/knife/raw.rb
index 601cfcef9b..80fbf97dcc 100644
--- a/lib/chef/knife/raw.rb
+++ b/lib/chef/knife/raw.rb
@@ -1,4 +1,5 @@
-require 'chef/knife'
+require "chef/knife"
+require "chef/http"
class Chef
class Knife
@@ -6,34 +7,34 @@ class Chef
banner "knife raw REQUEST_PATH"
deps do
- require 'chef/json_compat'
- require 'chef/config'
- require 'chef/http'
- require 'chef/http/authenticator'
- require 'chef/http/cookie_manager'
- require 'chef/http/decompressor'
- require 'chef/http/json_output'
+ require "chef/json_compat"
+ require "chef/config"
+ require "chef/http"
+ require "chef/http/authenticator"
+ require "chef/http/cookie_manager"
+ require "chef/http/decompressor"
+ require "chef/http/json_output"
end
option :method,
- :long => '--method METHOD',
- :short => '-m METHOD',
+ :long => "--method METHOD",
+ :short => "-m METHOD",
:default => "GET",
:description => "Request method (GET, POST, PUT or DELETE). Default: GET"
option :pretty,
- :long => '--[no-]pretty',
+ :long => "--[no-]pretty",
:boolean => true,
:default => true,
:description => "Pretty-print JSON output. Default: true"
option :input,
- :long => '--input FILE',
- :short => '-i FILE',
+ :long => "--input FILE",
+ :short => "-i FILE",
:description => "Name of file to use for PUT or POST"
option :proxy_auth,
- :long => '--proxy-auth',
+ :long => "--proxy-auth",
:boolean => true,
:default => false,
:description => "Use webui proxy authentication. Client key must be the webui key."
@@ -70,10 +71,10 @@ class Chef
begin
method = config[:method].to_sym
- headers = {'Content-Type' => 'application/json'}
+ headers = {"Content-Type" => "application/json"}
if config[:proxy_auth]
- headers['x-ops-request-source'] = 'web'
+ headers["x-ops-request-source"] = "web"
end
if config[:pretty]
@@ -92,7 +93,7 @@ class Chef
exit 1
rescue Net::HTTPServerException => e
ui.error "Server responded with error #{e.response.code} \"#{e.response.message}\""
- ui.error "Error Body: #{e.response.body}" if e.response.body && e.response.body != ''
+ ui.error "Error Body: #{e.response.body}" if e.response.body && e.response.body != ""
exit 1
end
end
diff --git a/lib/chef/knife/recipe_list.rb b/lib/chef/knife/recipe_list.rb
index ed7d2a9509..756069aebe 100644
--- a/lib/chef/knife/recipe_list.rb
+++ b/lib/chef/knife/recipe_list.rb
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef::Knife::RecipeList < Chef::Knife
banner "knife recipe list [PATTERN]"
def run
- recipes = rest.get_rest('cookbooks/_recipes')
+ recipes = rest.get("cookbooks/_recipes")
if pattern = @name_args.first
recipes = recipes.grep(Regexp.new(pattern))
end
diff --git a/lib/chef/knife/rehash.rb b/lib/chef/knife/rehash.rb
new file mode 100644
index 0000000000..dccf43b4e9
--- /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/role_bulk_delete.rb b/lib/chef/knife/role_bulk_delete.rb
index 8b24f55846..855881c298 100644
--- a/lib/chef/knife/role_bulk_delete.rb
+++ b/lib/chef/knife/role_bulk_delete.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleBulkDelete < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role bulk delete REGEX (options)"
diff --git a/lib/chef/knife/role_create.rb b/lib/chef/knife/role_create.rb
index e9e363e870..8fef2c43cd 100644
--- a/lib/chef/knife/role_create.rb
+++ b/lib/chef/knife/role_create.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleCreate < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role create ROLE (options)"
diff --git a/lib/chef/knife/role_delete.rb b/lib/chef/knife/role_delete.rb
index b823f37359..b3ff69860e 100644
--- a/lib/chef/knife/role_delete.rb
+++ b/lib/chef/knife/role_delete.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleDelete < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role delete ROLE (options)"
diff --git a/lib/chef/knife/role_edit.rb b/lib/chef/knife/role_edit.rb
index b0580988a0..65086d8c6c 100644
--- a/lib/chef/knife/role_edit.rb
+++ b/lib/chef/knife/role_edit.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleEdit < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role edit ROLE (options)"
diff --git a/lib/chef/knife/role_env_run_list_add.rb b/lib/chef/knife/role_env_run_list_add.rb
index faf84ccfdb..7dccd0d0a2 100644
--- a/lib/chef/knife/role_env_run_list_add.rb
+++ b/lib/chef/knife/role_env_run_list_add.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleEnvRunListAdd < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role env_run_list add [ROLE] [ENVIRONMENT] [ENTRY[,ENTRY]] (options)"
@@ -68,11 +68,11 @@ class Chef
if @name_args.size > 2
# Check for nested lists and create a single plain one
entries = @name_args[2..-1].map do |entry|
- entry.split(',').map { |e| e.strip }
+ entry.split(",").map { |e| e.strip }
end.flatten
else
# Convert to array and remove the extra spaces
- entries = @name_args[2].split(',').map { |e| e.strip }
+ entries = @name_args[2].split(",").map { |e| e.strip }
end
add_to_env_run_list(role, environment, entries, config[:after])
diff --git a/lib/chef/knife/role_env_run_list_clear.rb b/lib/chef/knife/role_env_run_list_clear.rb
index 4304f29a38..9994b45471 100644
--- a/lib/chef/knife/role_env_run_list_clear.rb
+++ b/lib/chef/knife/role_env_run_list_clear.rb
@@ -17,15 +17,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleEnvRunListClear < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role env_run_list clear [ROLE] [ENVIRONMENT]"
diff --git a/lib/chef/knife/role_env_run_list_remove.rb b/lib/chef/knife/role_env_run_list_remove.rb
index 9ffc3f520e..07dd61de28 100644
--- a/lib/chef/knife/role_env_run_list_remove.rb
+++ b/lib/chef/knife/role_env_run_list_remove.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleEnvRunListRemove < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role env_run_list remove [ROLE] [ENVIRONMENT] [ENTRIES]"
diff --git a/lib/chef/knife/role_env_run_list_replace.rb b/lib/chef/knife/role_env_run_list_replace.rb
index 146d0c4905..9929820594 100644
--- a/lib/chef/knife/role_env_run_list_replace.rb
+++ b/lib/chef/knife/role_env_run_list_replace.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleEnvRunListReplace < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role env_run_list replace [ROLE] [ENVIRONMENT] [OLD_ENTRY] [NEW_ENTRY] "
diff --git a/lib/chef/knife/role_env_run_list_set.rb b/lib/chef/knife/role_env_run_list_set.rb
index a1618516c0..f414ced51c 100644
--- a/lib/chef/knife/role_env_run_list_set.rb
+++ b/lib/chef/knife/role_env_run_list_set.rb
@@ -17,15 +17,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleEnvRunListSet < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role env_run_list set [ROLE] [ENVIRONMENT] [ENTRIES]"
@@ -52,11 +52,11 @@ class Chef
elsif @name_args.size > 2
# Check for nested lists and create a single plain one
entries = @name_args[2..-1].map do |entry|
- entry.split(',').map { |e| e.strip }
+ entry.split(",").map { |e| e.strip }
end.flatten
else
# Convert to array and remove the extra spaces
- entries = @name_args[2].split(',').map { |e| e.strip }
+ entries = @name_args[2].split(",").map { |e| e.strip }
end
set_env_run_list(role, environment, entries )
diff --git a/lib/chef/knife/role_from_file.rb b/lib/chef/knife/role_from_file.rb
index c80218b753..aacf1211bf 100644
--- a/lib/chef/knife/role_from_file.rb
+++ b/lib/chef/knife/role_from_file.rb
@@ -16,16 +16,16 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleFromFile < Knife
deps do
- require 'chef/role'
- require 'chef/knife/core/object_loader'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/knife/core/object_loader"
+ require "chef/json_compat"
end
banner "knife role from file FILE [FILE..] (options)"
diff --git a/lib/chef/knife/role_list.rb b/lib/chef/knife/role_list.rb
index 0f105b2188..8fa5707eb3 100644
--- a/lib/chef/knife/role_list.rb
+++ b/lib/chef/knife/role_list.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleList < Knife
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife role list (options)"
diff --git a/lib/chef/knife/role_run_list_add.rb b/lib/chef/knife/role_run_list_add.rb
index c9d7785bb7..c7f6b0afdf 100644
--- a/lib/chef/knife/role_run_list_add.rb
+++ b/lib/chef/knife/role_run_list_add.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleRunListAdd < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role run_list add [ROLE] [ENTRY[,ENTRY]] (options)"
@@ -68,11 +68,11 @@ class Chef
if @name_args.size > 1
# Check for nested lists and create a single plain one
entries = @name_args[1..-1].map do |entry|
- entry.split(',').map { |e| e.strip }
+ entry.split(",").map { |e| e.strip }
end.flatten
else
# Convert to array and remove the extra spaces
- entries = @name_args[1].split(',').map { |e| e.strip }
+ entries = @name_args[1].split(",").map { |e| e.strip }
end
add_to_env_run_list(role, environment, entries, config[:after])
diff --git a/lib/chef/knife/role_run_list_clear.rb b/lib/chef/knife/role_run_list_clear.rb
index ed6225d83d..2041c979b7 100644
--- a/lib/chef/knife/role_run_list_clear.rb
+++ b/lib/chef/knife/role_run_list_clear.rb
@@ -17,15 +17,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleRunListClear < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role run_list clear [ROLE]"
diff --git a/lib/chef/knife/role_run_list_remove.rb b/lib/chef/knife/role_run_list_remove.rb
index d783b34ab4..655a6aa15f 100644
--- a/lib/chef/knife/role_run_list_remove.rb
+++ b/lib/chef/knife/role_run_list_remove.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleRunListRemove < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role run_list remove [ROLE] [ENTRY]"
diff --git a/lib/chef/knife/role_run_list_replace.rb b/lib/chef/knife/role_run_list_replace.rb
index 1ab94df111..f28b69db25 100644
--- a/lib/chef/knife/role_run_list_replace.rb
+++ b/lib/chef/knife/role_run_list_replace.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleRunListReplace < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role run_list replace [ROLE] [OLD_ENTRY] [NEW_ENTRY] "
diff --git a/lib/chef/knife/role_run_list_set.rb b/lib/chef/knife/role_run_list_set.rb
index b9675c70dd..30702458bc 100644
--- a/lib/chef/knife/role_run_list_set.rb
+++ b/lib/chef/knife/role_run_list_set.rb
@@ -17,15 +17,15 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class RoleRunListSet < Knife
deps do
- require 'chef/role'
- require 'chef/json_compat'
+ require "chef/role"
+ require "chef/json_compat"
end
banner "knife role run_list set [ROLE] [ENTRIES]"
@@ -52,11 +52,11 @@ class Chef
elsif @name_args.size > 1
# Check for nested lists and create a single plain one
entries = @name_args[1..-1].map do |entry|
- entry.split(',').map { |e| e.strip }
+ entry.split(",").map { |e| e.strip }
end.flatten
else
# Convert to array and remove the extra spaces
- entries = @name_args[1].split(',').map { |e| e.strip }
+ entries = @name_args[1].split(",").map { |e| e.strip }
end
set_env_run_list(role, environment, entries )
diff --git a/lib/chef/knife/role_show.rb b/lib/chef/knife/role_show.rb
index 159571f281..1f8cba7b08 100644
--- a/lib/chef/knife/role_show.rb
+++ b/lib/chef/knife/role_show.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -25,8 +25,8 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/node'
- require 'chef/json_compat'
+ require "chef/node"
+ require "chef/json_compat"
end
banner "knife role show ROLE (options)"
diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb
index 9319d30e7d..642aaf8eef 100644
--- a/lib/chef/knife/search.rb
+++ b/lib/chef/knife/search.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/knife/core/node_presenter'
+require "chef/knife"
+require "chef/knife/core/node_presenter"
class Chef
class Knife
@@ -26,10 +26,10 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/node'
- require 'chef/environment'
- require 'chef/api_client'
- require 'chef/search/query'
+ require "chef/node"
+ require "chef/environment"
+ require "chef/api_client"
+ require "chef/search/query"
end
include Knife::Core::NodeFormattingOptions
@@ -80,7 +80,7 @@ class Chef
read_cli_args
fuzzify_query
- if @type == 'node'
+ if @type == "node"
ui.use_presenter Knife::Core::NodePresenter
end
@@ -136,7 +136,7 @@ class Chef
def read_cli_args
if config[:query]
if @name_args[1]
- ui.error "please specify query as an argument or an option via -q, not both"
+ ui.error "Please specify query as an argument or an option via -q, not both"
ui.msg opt_parser
exit 1
end
@@ -145,7 +145,7 @@ class Chef
else
case name_args.size
when 0
- ui.error "no query specified"
+ ui.error "No query specified"
ui.msg opt_parser
exit 1
when 1
@@ -160,7 +160,7 @@ class Chef
def fuzzify_query
if @query !~ /:/
- @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*"
+ @query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}* OR policy_name:*#{@query}* OR policy_group:*#{@query}*"
end
end
diff --git a/lib/chef/knife/serve.rb b/lib/chef/knife/serve.rb
index 84918e2ef2..3f97962eae 100644
--- a/lib/chef/knife/serve.rb
+++ b/lib/chef/knife/serve.rb
@@ -1,23 +1,23 @@
-require 'chef/knife'
-require 'chef/local_mode'
+require "chef/knife"
+require "chef/local_mode"
class Chef
class Knife
class Serve < Knife
- banner 'knife serve (options)'
+ banner "knife serve (options)"
option :repo_mode,
- :long => '--repo-mode MODE',
+ :long => "--repo-mode MODE",
:description => "Specifies the local repository layout. Values: static (only environments/roles/data_bags/cookbooks), everything (includes nodes/clients/users), hosted_everything (includes acls/groups/etc. for Enterprise/Hosted Chef). Default: everything/hosted_everything"
option :chef_repo_path,
- :long => '--chef-repo-path PATH',
- :description => 'Overrides the location of chef repo. Default is specified by chef_repo_path in the config'
+ :long => "--chef-repo-path PATH",
+ :description => "Overrides the location of chef repo. Default is specified by chef_repo_path in the config"
option :chef_zero_host,
- :long => '--chef-zero-host IP',
- :description => 'Overrides the host upon which chef-zero listens. Default is 127.0.0.1.'
+ :long => "--chef-zero-host IP",
+ :description => "Overrides the host upon which chef-zero listens. Default is 127.0.0.1."
def configure_chef
super
@@ -27,7 +27,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|
+ %w{acl client cookbook container data_bag environment group node role user}.each do |variable_name|
Chef::Config.delete("#{variable_name}_path".to_sym)
end
end
diff --git a/lib/chef/knife/show.rb b/lib/chef/knife/show.rb
index 4684a6ac7e..88a192994e 100644
--- a/lib/chef/knife/show.rb
+++ b/lib/chef/knife/show.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,12 +8,12 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/file_system'
- require 'chef/chef_fs/file_system/not_found_error'
+ require "chef/chef_fs/file_system"
+ require "chef/chef_fs/file_system/not_found_error"
end
option :local,
- :long => '--local',
+ :long => "--local",
:boolean => true,
:description => "Show local files instead of remote"
diff --git a/lib/chef/knife/ssh.rb b/lib/chef/knife/ssh.rb
index 50fedd0e49..2b2a367f18 100644
--- a/lib/chef/knife/ssh.rb
+++ b/lib/chef/knife/ssh.rb
@@ -16,23 +16,23 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/mixin/shell_out"
+require "chef/knife"
class Chef
class Knife
class Ssh < Knife
deps do
- require 'net/ssh'
- require 'net/ssh/multi'
- require 'chef/monkey_patches/net-ssh-multi'
- require 'readline'
- require 'chef/exceptions'
- require 'chef/search/query'
- require 'chef/mixin/shell_out'
- require 'chef/mixin/command'
- require 'chef/util/path_helper'
- require 'mixlib/shellout'
+ require "net/ssh"
+ require "net/ssh/multi"
+ require "chef/monkey_patches/net-ssh-multi"
+ require "readline"
+ require "chef/exceptions"
+ require "chef/search/query"
+ require "chef/mixin/command"
+ require "chef/util/path_helper"
+ require "mixlib/shellout"
end
include Chef::Mixin::ShellOut
@@ -72,7 +72,7 @@ class Chef
:description => "The ssh password - will prompt if flag is specified but no password is given",
# default to a value that can not be a password (boolean)
# so we can effectively test if this parameter was specified
- # without a vlaue
+ # without a value
:default => false
option :ssh_port,
@@ -94,8 +94,12 @@ class Chef
:boolean => true
option :identity_file,
- :short => "-i IDENTITY_FILE",
:long => "--identity-file IDENTITY_FILE",
+ :description => "The SSH identity file used for authentication. [DEPRECATED] Use --ssh-identity-file instead."
+
+ option :ssh_identity_file,
+ :short => "-i IDENTITY_FILE",
+ :long => "--ssh-identity-file IDENTITY_FILE",
:description => "The SSH identity file used for authentication"
option :host_key_verify,
@@ -105,12 +109,18 @@ class Chef
:default => true
option :on_error,
- :short => '-e',
- :long => '--exit-on-error',
+ :short => "-e",
+ :long => "--exit-on-error",
:description => "Immediately exit if an error is encountered",
:boolean => true,
:proc => Proc.new { :raise }
+ option :tmux_split,
+ :long => "--tmux-split",
+ :description => "Split tmux window.",
+ :boolean => true,
+ :default => false
+
def session
config[:on_error] ||= :skip
ssh_error_handler = Proc.new do |server|
@@ -130,17 +140,21 @@ class Chef
def configure_gateway
config[:ssh_gateway] ||= Chef::Config[:knife][:ssh_gateway]
if config[:ssh_gateway]
- gw_host, gw_user = config[:ssh_gateway].split('@').reverse
- gw_host, gw_port = gw_host.split(':')
- gw_opts = gw_port ? { :port => gw_port } : {}
+ gw_host, gw_user = config[:ssh_gateway].split("@").reverse
+ gw_host, gw_port = gw_host.split(":")
+ gw_opts = session_options(gw_host, gw_port, gw_user)
+ user = gw_opts.delete(:user)
- session.via(gw_host, gw_user || config[:ssh_user], gw_opts)
+ begin
+ # Try to connect with a key.
+ session.via(gw_host, user, gw_opts)
+ rescue Net::SSH::AuthenticationFailed
+ prompt = "Enter the password for #{user}@#{gw_host}: "
+ gw_opts[:password] = prompt_for_password(prompt)
+ # Try again with a password.
+ session.via(gw_host, user, gw_opts)
+ end
end
- rescue Net::SSH::AuthenticationFailed
- user = gw_user || config[:ssh_user]
- prompt = "Enter the password for #{user}@#{gw_host}: "
- gw_opts.merge!(:password => prompt_for_password(prompt))
- session.via(gw_host, user, gw_opts)
end
def configure_session
@@ -160,6 +174,31 @@ class Chef
session_from_list(list)
end
+ def get_ssh_attribute(node)
+ # Order of precedence for ssh target
+ # 1) command line attribute
+ # 2) configuration file
+ # 3) cloud attribute
+ # 4) fqdn
+ if config[:attribute]
+ Chef::Log.debug("Using node attribute '#{config[:attribute]}' as the ssh target")
+ attribute = config[:attribute]
+ elsif Chef::Config[:knife][:ssh_attribute]
+ Chef::Log.debug("Using node attribute #{Chef::Config[:knife][:ssh_attribute]}")
+ attribute = Chef::Config[:knife][:ssh_attribute]
+ elsif node[:cloud] &&
+ node[:cloud][:public_hostname] &&
+ !node[:cloud][:public_hostname].empty?
+ Chef::Log.debug("Using node attribute 'cloud[:public_hostname]' automatically as the ssh target")
+ attribute = "cloud.public_hostname"
+ else
+ # falling back to default of fqdn
+ Chef::Log.debug("Using node attribute 'fqdn' as the ssh target")
+ attribute = "fqdn"
+ end
+ attribute
+ end
+
def search_nodes
list = Array.new
query = Chef::Search::Query.new
@@ -168,23 +207,9 @@ class Chef
# we should skip the loop to next iteration if the item
# returned by the search is nil
next if item.nil?
- # if a command line attribute was not passed, and we have a
- # cloud public_hostname, use that. see #configure_attribute
- # for the source of config[:attribute] and
- # config[:attribute_from_cli]
- if config[:attribute_from_cli]
- Chef::Log.debug("Using node attribute '#{config[:attribute_from_cli]}' from the command line as the ssh target")
- host = extract_nested_value(item, config[:attribute_from_cli])
- elsif item[:cloud] && item[:cloud][:public_hostname]
- 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]
@@ -193,32 +218,50 @@ class Chef
list
end
- def session_from_list(list)
- list.each do |item|
- host, ssh_port = item
- Chef::Log.debug("Adding #{host}")
- session_opts = {}
-
- ssh_config = Net::SSH.configuration_for(host)
-
+ # Net::SSH session options hash for global options. These should be
+ # options that will apply to the gateway connection in addition to the
+ # main one.
+ #
+ # @since 12.5.0
+ # @param host [String] Hostname for this session.
+ # @param port [String] SSH port for this session.
+ # @param user [String] Optional username for this session.
+ # @return [Hash<Symbol, Object>]
+ def session_options(host, port, user=nil)
+ ssh_config = Net::SSH.configuration_for(host)
+ {}.tap do |opts|
# Chef::Config[:knife][:ssh_user] is parsed in #configure_user and written to config[:ssh_user]
- user = config[:ssh_user] || ssh_config[:user]
- hostspec = user ? "#{user}@#{host}" : host
- session_opts[:keys] = File.expand_path(config[:identity_file]) if config[:identity_file]
- session_opts[:keys_only] = true if config[:identity_file]
- session_opts[:password] = config[:ssh_password] if config[:ssh_password]
- session_opts[:forward_agent] = config[:forward_agent]
- session_opts[:port] = config[:ssh_port] ||
- ssh_port || # Use cloud port if available
- Chef::Config[:knife][:ssh_port] ||
- ssh_config[:port]
- session_opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
-
+ opts[:user] = user || config[:ssh_user] || ssh_config[:user]
+ if config[:ssh_identity_file]
+ opts[:keys] = File.expand_path(config[:ssh_identity_file])
+ opts[:keys_only] = true
+ elsif config[:ssh_password]
+ opts[:password] = config[:ssh_password]
+ end
+ # Don't set the keys to nil if we don't have them.
+ forward_agent = config[:forward_agent] || ssh_config[:forward_agent]
+ opts[:forward_agent] = forward_agent unless forward_agent.nil?
+ port ||= ssh_config[:port]
+ opts[:port] = port unless port.nil?
+ opts[:logger] = Chef::Log.logger if Chef::Log.level == :debug
if !config[:host_key_verify]
- session_opts[:paranoid] = false
- session_opts[:user_known_hosts_file] = "/dev/null"
+ opts[:paranoid] = false
+ opts[:user_known_hosts_file] = "/dev/null"
end
+ end
+ end
+ def session_from_list(list)
+ list.each do |item|
+ host, ssh_port = item
+ Chef::Log.debug("Adding #{host}")
+ session_opts = session_options(host, ssh_port)
+ # Handle port overrides for the main connection.
+ session_opts[:port] = Chef::Config[:knife][:ssh_port] if Chef::Config[:knife][:ssh_port]
+ session_opts[:port] = config[:ssh_port] if config[:ssh_port]
+ # Create the hostspec.
+ hostspec = session_opts[:user] ? "#{session_opts.delete(:user)}@#{host}" : host
+ # Connect a new session on the multi.
session.use(hostspec, session_opts)
@longest = host.length if host.length > @longest
@@ -258,7 +301,7 @@ class Chef
exit_status = 0
subsession ||= session
command = fixup_sudo(command)
- command.force_encoding('binary') if command.respond_to?(:force_encoding)
+ command.force_encoding("binary") if command.respond_to?(:force_encoding)
subsession.open_channel do |ch|
ch.request_pty
ch.exec command do |ch, success|
@@ -324,8 +367,8 @@ class Chef
loop do
command = read_line
case command
- when 'quit!'
- puts 'Bye!'
+ when "quit!"
+ puts "Bye!"
break
when /^on (.+?); (.+)$/
raw_list = $1.split(" ")
@@ -343,7 +386,7 @@ class Chef
def screen
tf = Tempfile.new("knife-ssh-screen")
- Chef::Util::PathHelper.home('.screenrc') do |screenrc_path|
+ Chef::Util::PathHelper.home(".screenrc") do |screenrc_path|
if File.exist? screenrc_path
tf.puts("source #{screenrc_path}")
end
@@ -353,7 +396,7 @@ class Chef
window = 0
session.servers_for.each do |server|
tf.print("screen -t \"#{server.host}\" #{window} ssh ")
- tf.print("-i #{config[:identity_file]} ") if config[:identity_file]
+ tf.print("-i #{config[:ssh_identity_file]} ") if config[:ssh_identity_file]
server.user ? tf.puts("#{server.user}@#{server.host}") : tf.puts(server.host)
window += 1
end
@@ -363,7 +406,7 @@ class Chef
def tmux
ssh_dest = lambda do |server|
- identity = "-i #{config[:identity_file]} " if config[:identity_file]
+ identity = "-i #{config[:ssh_identity_file]} " if config[:ssh_identity_file]
prefix = server.user ? "#{server.user}@" : ""
"'ssh #{identity}#{prefix}#{server.host}'"
end
@@ -371,7 +414,11 @@ class Chef
new_window_cmds = lambda do
if session.servers_for.size > 1
[""] + session.servers_for[1..-1].map do |server|
- "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}"
+ if config[:tmux_split]
+ "split-window #{ssh_dest.call(server)}; tmux select-layout tiled"
+ else
+ "new-window -a -n '#{server.host}' #{ssh_dest.call(server)}"
+ end
end
else
[]
@@ -392,15 +439,15 @@ class Chef
def macterm
begin
- require 'appscript'
+ require "appscript"
rescue LoadError
- STDERR.puts "you need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install"
+ STDERR.puts "You need the rb-appscript gem to use knife ssh macterm. `(sudo) gem install rb-appscript` to install"
raise
end
Appscript.app("/Applications/Utilities/Terminal.app").windows.first.activate
Appscript.app("System Events").application_processes["Terminal.app"].keystroke("n", :using=>:command_down)
- term = Appscript.app('Terminal')
+ term = Appscript.app("Terminal")
window = term.windows.first.get
(session.servers_for.size - 1).times do |i|
@@ -410,23 +457,13 @@ class Chef
session.servers_for.each_with_index do |server, tab_number|
cmd = "unset PROMPT_COMMAND; echo -e \"\\033]0;#{server.host}\\007\"; ssh #{server.user ? "#{server.user}@#{server.host}" : server.host}"
- Appscript.app('Terminal').do_script(cmd, :in => window.tabs[tab_number + 1].get)
+ Appscript.app("Terminal").do_script(cmd, :in => window.tabs[tab_number + 1].get)
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|
+ %w{csshX cssh}.each do |cmd|
begin
# Unix and Mac only
cssh_cmd = shell_out!("which #{cmd}").stdout.strip
@@ -436,15 +473,15 @@ class Chef
end
raise Chef::Exceptions::Exec, "no command found for cssh" unless cssh_cmd
- # pass in the consolidated itentity file option to cssh(X)
- if config[:identity_file]
- cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:identity_file])}'"
+ # pass in the consolidated identity file option to cssh(X)
+ if config[:ssh_identity_file]
+ cssh_cmd << " --ssh_args '-i #{File.expand_path(config[:ssh_identity_file])}'"
end
session.servers_for.each do |server|
cssh_cmd << " #{server.user ? "#{server.user}@#{server.host}" : server.host}"
end
- Chef::Log.debug("starting cssh session with command: #{cssh_cmd}")
+ Chef::Log.debug("Starting cssh session with command: #{cssh_cmd}")
exec(cssh_cmd)
end
@@ -483,9 +520,9 @@ class Chef
end
end
- def configure_identity_file
- config[:identity_file] = get_stripped_unfrozen_value(config[:identity_file] ||
- Chef::Config[:knife][:ssh_identity_file])
+ def configure_ssh_identity_file
+ # config[:identity_file] is DEPRECATED in favor of :ssh_identity_file
+ config[:ssh_identity_file] = get_stripped_unfrozen_value(config[:ssh_identity_file] || config[:identity_file] || Chef::Config[:knife][:ssh_identity_file])
end
def extract_nested_value(data_structure, path_spec)
@@ -497,10 +534,9 @@ class Chef
@longest = 0
- configure_attribute
configure_user
configure_password
- configure_identity_file
+ configure_ssh_identity_file
configure_gateway
configure_session
diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb
index c5fe4fc1aa..8ed18b8a5b 100644
--- a/lib/chef/knife/ssl_check.rb
+++ b/lib/chef/knife/ssl_check.rb
@@ -16,19 +16,21 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/config'
+require "chef/knife"
+require "chef/config"
class Chef
class Knife
class SslCheck < Chef::Knife
deps do
- require 'pp'
- require 'socket'
- require 'uri'
- require 'chef/http/ssl_policies'
- require 'openssl'
+ require "pp"
+ require "socket"
+ require "uri"
+ require "chef/http/ssl_policies"
+ require "openssl"
+ require "chef/mixin/proxified_socket"
+ include Chef::Mixin::ProxifiedSocket
end
banner "knife ssl check [URL] (options)"
@@ -73,11 +75,12 @@ class Chef
exit 1
end
-
def verify_peer_socket
@verify_peer_socket ||= begin
- tcp_connection = TCPSocket.new(host, port)
- OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ tcp_connection = proxified_socket(host, port)
+ ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context)
+ ssl_client.hostname = host
+ ssl_client
end
end
@@ -92,7 +95,7 @@ class Chef
def noverify_socket
@noverify_socket ||= begin
- tcp_connection = TCPSocket.new(host, port)
+ tcp_connection = proxified_socket(host, port)
OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
end
end
diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb
index fd7d101fd8..f20576b917 100644
--- a/lib/chef/knife/ssl_fetch.rb
+++ b/lib/chef/knife/ssl_fetch.rb
@@ -16,18 +16,20 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/config'
+require "chef/knife"
+require "chef/config"
class Chef
class Knife
class SslFetch < Chef::Knife
deps do
- require 'pp'
- require 'socket'
- require 'uri'
- require 'openssl'
+ require "pp"
+ require "socket"
+ require "uri"
+ require "openssl"
+ require "chef/mixin/proxified_socket"
+ include Chef::Mixin::ProxifiedSocket
end
banner "knife ssl fetch [URL] (options)"
@@ -71,7 +73,7 @@ class Chef
end
def remote_cert_chain
- tcp_connection = TCPSocket.new(host, port)
+ tcp_connection = proxified_socket(host, port)
shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context)
shady_ssl_connection.connect
shady_ssl_connection.peer_cert_chain
@@ -102,7 +104,7 @@ class Chef
# practice.
# https://tools.ietf.org/html/rfc6125#section-6.4.2
def normalize_cn(cn)
- cn.gsub("*", "wildcard").gsub(/[^[:alnum:]\-]/, '_')
+ cn.gsub("*", "wildcard").gsub(/[^[:alnum:]\-]/, "_")
end
def configuration
@@ -145,7 +147,7 @@ TRUST_TRUST
ui.error("The service at the given URI (#{uri}) does not accept SSL connections")
if uri.scheme == "http"
- https_uri = uri.to_s.sub(/^http/, 'https')
+ https_uri = uri.to_s.sub(/^http/, "https")
ui.error("Perhaps you meant to connect to '#{https_uri}'?")
end
exit 1
@@ -155,4 +157,3 @@ TRUST_TRUST
end
end
end
-
diff --git a/lib/chef/knife/status.rb b/lib/chef/knife/status.rb
index 93e81f8f03..29e7aa4c48 100644
--- a/lib/chef/knife/status.rb
+++ b/lib/chef/knife/status.rb
@@ -16,15 +16,17 @@
# limitations under the License.
#
-require 'chef/knife'
-require 'chef/knife/core/status_presenter'
+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'
+ require "chef/search/query"
end
banner "knife status QUERY (options)"
@@ -42,22 +44,58 @@ class Chef
option :hide_healthy,
:short => "-H",
:long => "--hide-healthy",
- :description => "Hide nodes that have run chef in the last hour"
+ :description => "Hide nodes that have run chef in the last hour. [DEPRECATED] Use --hide-by-mins MINS instead"
+
+ option :hide_by_mins,
+ :long => "--hide-by-mins MINS",
+ :description => "Hide nodes that have run chef in the last MINS minutes"
+
+ def append_to_query(term)
+ @query << " AND " unless @query.empty?
+ @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]
+ ui.warn("-H / --hide-healthy is deprecated. Use --hide-by-mins MINS instead")
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 #{time}]"
end
- q.search(:node, query) do |node|
+
+ if config[:hide_by_mins]
+ hidemins = config[:hide_by_mins].to_i
+ time = Time.now.to_i
+ # AND NOT is not valid lucene syntax, so don't use append_to_query
+ @query << " " unless @query.empty?
+ @query << "NOT ohai_time:[#{(time - hidemins*60)} TO #{time}]"
+ end
+
+ @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/tag_create.rb b/lib/chef/knife/tag_create.rb
index d3ca95242d..f568134727 100644
--- a/lib/chef/knife/tag_create.rb
+++ b/lib/chef/knife/tag_create.rb
@@ -18,14 +18,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class TagCreate < Knife
deps do
- require 'chef/node'
+ require "chef/node"
end
banner "knife tag create NODE TAG ..."
diff --git a/lib/chef/knife/tag_delete.rb b/lib/chef/knife/tag_delete.rb
index 10751db216..4f7e059586 100644
--- a/lib/chef/knife/tag_delete.rb
+++ b/lib/chef/knife/tag_delete.rb
@@ -18,14 +18,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class TagDelete < Knife
deps do
- require 'chef/node'
+ require "chef/node"
end
banner "knife tag delete NODE TAG ..."
diff --git a/lib/chef/knife/tag_list.rb b/lib/chef/knife/tag_list.rb
index 499eb8578c..659a6ac77f 100644
--- a/lib/chef/knife/tag_list.rb
+++ b/lib/chef/knife/tag_list.rb
@@ -18,14 +18,14 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class TagList < Knife
deps do
- require 'chef/node'
+ require "chef/node"
end
banner "knife tag list NODE"
diff --git a/lib/chef/knife/upload.rb b/lib/chef/knife/upload.rb
index 8abd22b4dd..90b9a1562b 100644
--- a/lib/chef/knife/upload.rb
+++ b/lib/chef/knife/upload.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,45 +8,45 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/command_line'
+ require "chef/chef_fs/command_line"
end
option :recurse,
- :long => '--[no-]recurse',
+ :long => "--[no-]recurse",
:boolean => true,
:default => true,
:description => "List directories recursively."
option :purge,
- :long => '--[no-]purge',
+ :long => "--[no-]purge",
:boolean => true,
:default => false,
:description => "Delete matching local files and directories that do not exist remotely."
option :force,
- :long => '--[no-]force',
+ :long => "--[no-]force",
:boolean => true,
:default => false,
:description => "Force upload of files even if they match (quicker for many files). Will overwrite frozen cookbooks."
option :freeze,
- :long => '--[no-]freeze',
+ :long => "--[no-]freeze",
:boolean => true,
:default => false,
:description => "Freeze cookbooks that get uploaded."
option :dry_run,
- :long => '--dry-run',
- :short => '-n',
+ :long => "--dry-run",
+ :short => "-n",
:boolean => true,
:default => false,
:description => "Don't take action, only print what would happen"
option :diff,
- :long => '--[no-]diff',
+ :long => "--[no-]diff",
:boolean => true,
:default => true,
- :description => 'Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff'
+ :description => "Turn off to avoid uploading existing files; only new (and possibly deleted) files with --no-diff"
def run
if name_args.length == 0
diff --git a/lib/chef/knife/user_create.rb b/lib/chef/knife/user_create.rb
index 4130f06878..1fa698634f 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");
@@ -16,77 +17,135 @@
# limitations under the License.
#
-require 'chef/knife'
+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/json_compat'
+ 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..b6ea75c5cd 100644
--- a/lib/chef/knife/user_delete.rb
+++ b/lib/chef/knife/user_delete.rb
@@ -16,19 +16,53 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class UserDelete < Knife
deps do
- require 'chef/user'
- require 'chef/json_compat'
+ 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..991b5be272 100644
--- a/lib/chef/knife/user_edit.rb
+++ b/lib/chef/knife/user_edit.rb
@@ -16,19 +16,37 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class UserEdit < Knife
deps do
- require 'chef/user'
- require 'chef/json_compat'
+ 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..e882e34e4d
--- /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..d5063c72ef
--- /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..9c766c8bea
--- /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..9c308c47f8
--- /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..b7e3bcb4d7
--- /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..44c581dd4a 100644
--- a/lib/chef/knife/user_list.rb
+++ b/lib/chef/knife/user_list.rb
@@ -16,15 +16,17 @@
# limitations under the License.
#
-require 'chef/knife'
+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/json_compat'
+ require "chef/user_v1"
+ require "chef/json_compat"
end
banner "knife user list (options)"
@@ -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..8e99ac0906 100644
--- a/lib/chef/knife/user_reregister.rb
+++ b/lib/chef/knife/user_reregister.rb
@@ -16,19 +16,37 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
class UserReregister < Knife
deps do
- require 'chef/user'
- require 'chef/json_compat'
+ 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..8ca181644e 100644
--- a/lib/chef/knife/user_show.rb
+++ b/lib/chef/knife/user_show.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/knife'
+require "chef/knife"
class Chef
class Knife
@@ -25,12 +25,30 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
deps do
- require 'chef/user'
- require 'chef/json_compat'
+ 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/knife/xargs.rb b/lib/chef/knife/xargs.rb
index dd8e848058..f16890cba5 100644
--- a/lib/chef/knife/xargs.rb
+++ b/lib/chef/knife/xargs.rb
@@ -1,4 +1,4 @@
-require 'chef/chef_fs/knife'
+require "chef/chef_fs/knife"
class Chef
class Knife
@@ -8,65 +8,65 @@ class Chef
category "path-based"
deps do
- require 'chef/chef_fs/file_system'
- require 'chef/chef_fs/file_system/not_found_error'
+ require "chef/chef_fs/file_system"
+ require "chef/chef_fs/file_system/not_found_error"
end
# TODO modify to remote-only / local-only pattern (more like delete)
option :local,
- :long => '--local',
+ :long => "--local",
:boolean => true,
:description => "Xargs local files instead of remote"
option :patterns,
- :long => '--pattern [PATTERN]',
- :short => '-p [PATTERN]',
+ :long => "--pattern [PATTERN]",
+ :short => "-p [PATTERN]",
:description => "Pattern on command line (if these are not specified, a list of patterns is expected on standard input). Multiple patterns may be passed in this way.",
:arg_arity => [1,-1]
option :diff,
- :long => '--[no-]diff',
+ :long => "--[no-]diff",
:default => true,
:boolean => true,
:description => "Whether to show a diff when files change (default: true)"
option :dry_run,
- :long => '--dry-run',
+ :long => "--dry-run",
:boolean => true,
:description => "Prevents changes from actually being uploaded to the server."
option :force,
- :long => '--[no-]force',
+ :long => "--[no-]force",
:boolean => true,
:default => false,
:description => "Force upload of files even if they are not changed (quicker and harmless, but doesn't print out what it changed)"
option :replace_first,
- :long => '--replace-first REPLACESTR',
- :short => '-J REPLACESTR',
+ :long => "--replace-first REPLACESTR",
+ :short => "-J REPLACESTR",
:description => "String to replace with filenames. -J will only replace the FIRST occurrence of the replacement string."
option :replace_all,
- :long => '--replace REPLACESTR',
- :short => '-I REPLACESTR',
+ :long => "--replace REPLACESTR",
+ :short => "-I REPLACESTR",
:description => "String to replace with filenames. -I will replace ALL occurrence of the replacement string."
option :max_arguments_per_command,
- :long => '--max-args MAXARGS',
- :short => '-n MAXARGS',
+ :long => "--max-args MAXARGS",
+ :short => "-n MAXARGS",
:description => "Maximum number of arguments per command line."
option :max_command_line,
- :long => '--max-chars LENGTH',
- :short => '-s LENGTH',
+ :long => "--max-chars LENGTH",
+ :short => "-s LENGTH",
:description => "Maximum size of command line, in characters"
option :verbose_commands,
- :short => '-t',
+ :short => "-t",
:description => "Print command to be run on the command line"
option :null_separator,
- :short => '-0',
+ :short => "-0",
:boolean => true,
:description => "Use the NULL character (\0) as a separator, instead of whitespace"
@@ -151,7 +151,7 @@ class Chef
end
def create_command(files)
- command = name_args.join(' ')
+ command = name_args.join(" ")
# Create the (empty) tempfiles
tempfiles = {}
@@ -167,7 +167,7 @@ class Chef
end
# Create the command
- paths = tempfiles.keys.map { |tempfile| tempfile.path }.join(' ')
+ paths = tempfiles.keys.map { |tempfile| tempfile.path }.join(" ")
if config[:replace_all]
final_command = command.gsub(config[:replace_all], paths)
elsif config[:replace_first]
diff --git a/lib/chef/local_mode.rb b/lib/chef/local_mode.rb
index e66acb6b66..53234ec7d5 100644
--- a/lib/chef/local_mode.rb
+++ b/lib/chef/local_mode.rb
@@ -14,10 +14,16 @@
# WITHOUT WARRANTIES OR 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/config"
+if Chef::Platform.windows?
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1")
+ require "chef/monkey_patches/webrick-utils"
+ end
+end
class Chef
module LocalMode
+
# Create a chef local server (if the configuration requires one) for the
# duration of the given block.
#
@@ -48,23 +54,32 @@ class Chef
if Chef::Config.chef_zero.enabled
destroy_server_connectivity
- require 'chef_zero/server'
- require 'chef/chef_fs/chef_fs_data_store'
- require 'chef/chef_fs/config'
+ require "chef_zero/server"
+ require "chef/chef_fs/chef_fs_data_store"
+ require "chef/chef_fs/config"
@chef_fs = Chef::ChefFS::Config.new.local_fs
@chef_fs.write_pretty_json = true
data_store = Chef::ChefFS::ChefFSDataStore.new(@chef_fs)
- data_store = ChefZero::DataStore::V1ToV2Adapter.new(data_store, 'chef')
+ data_store = ChefZero::DataStore::V1ToV2Adapter.new(data_store, "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
@@ -88,9 +103,9 @@ class Chef
def self.parse_port(port)
if port.is_a?(String)
- parts = port.split(',')
+ parts = port.split(",")
if parts.size == 1
- a,b = parts[0].split('-',2)
+ a,b = parts[0].split("-",2)
if b
a.to_i.upto(b.to_i)
else
diff --git a/lib/chef/log.rb b/lib/chef/log.rb
index 682afcea4b..b6efa81cac 100644
--- a/lib/chef/log.rb
+++ b/lib/chef/log.rb
@@ -17,10 +17,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'logger'
-require 'chef/monologger'
-require 'chef/exceptions'
-require 'mixlib/log'
+require "logger"
+require "chef/monologger"
+require "chef/exceptions"
+require "mixlib/log"
+require "chef/log/syslog" unless (RUBY_PLATFORM =~ /mswin|mingw|windows/)
+require "chef/log/winevt"
class Chef
class Log
@@ -35,7 +37,25 @@ class Chef
end
end
- def self.deprecation(msg=nil, &block)
+ #
+ # Get the location of the caller (from the recipe). Grabs the first caller
+ # that is *not* in the chef gem proper (allowing us to weed out internal
+ # calls and give the user a more useful perspective).
+ #
+ # @return [String] The location of the caller (file:line#) from caller(0..20), or nil if no non-chef caller is found.
+ #
+ def self.caller_location
+ # Pick the first caller that is *not* part of the Chef gem, that's the
+ # thing the user wrote.
+ chef_gem_path = File.expand_path("../..", __FILE__)
+ caller(0..20).select { |c| !c.start_with?(chef_gem_path) }.first
+ end
+
+ def self.deprecation(msg=nil, location=caller(2..2)[0], &block)
+ if msg
+ msg << " at #{Array(location).join("\n")}"
+ msg = msg.join("") if msg.respond_to?(:join)
+ end
if Chef::Config[:treat_deprecation_warnings_as_errors]
error(msg, &block)
raise Chef::Exceptions::DeprecatedFeatureError.new(msg)
diff --git a/lib/chef/log/syslog.rb b/lib/chef/log/syslog.rb
new file mode 100644
index 0000000000..2106130b26
--- /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..53127877e1
--- /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/checksum.rb b/lib/chef/mixin/checksum.rb
index 1d9c99ec7e..f805efae9a 100644
--- a/lib/chef/mixin/checksum.rb
+++ b/lib/chef/mixin/checksum.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'digest/sha2'
-require 'chef/digester'
+require "digest/sha2"
+require "chef/digester"
class Chef
module Mixin
diff --git a/lib/chef/mixin/command.rb b/lib/chef/mixin/command.rb
index d9a9c4f006..ea6e5f19fc 100644
--- a/lib/chef/mixin/command.rb
+++ b/lib/chef/mixin/command.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/exceptions'
-require 'tmpdir'
-require 'fcntl'
-require 'etc'
+require "chef/log"
+require "chef/exceptions"
+require "tmpdir"
+require "fcntl"
+require "etc"
class Chef
module Mixin
@@ -50,11 +50,11 @@ class Chef
# NOTE: run_command is deprecated in favor of using Chef::Shellout which now comes from the mixlib-shellout gem. NOTE #
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require 'chef/mixin/command/windows'
+ require "chef/mixin/command/windows"
include ::Chef::Mixin::Command::Windows
extend ::Chef::Mixin::Command::Windows
else
- require 'chef/mixin/command/unix'
+ require "chef/mixin/command/unix"
include ::Chef::Mixin::Command::Unix
extend ::Chef::Mixin::Command::Unix
end
diff --git a/lib/chef/mixin/command/unix.rb b/lib/chef/mixin/command/unix.rb
index 2bad4e6bcf..710324caa0 100644
--- a/lib/chef/mixin/command/unix.rb
+++ b/lib/chef/mixin/command/unix.rb
@@ -104,7 +104,7 @@ class Chef
else
Kernel.exec(cmd)
end
- raise 'forty-two'
+ raise "forty-two"
rescue Exception => e
Marshal.dump(e, ps.last)
ps.last.flush
diff --git a/lib/chef/mixin/command/windows.rb b/lib/chef/mixin/command/windows.rb
index 0147d58039..9cd1162234 100644
--- a/lib/chef/mixin/command/windows.rb
+++ b/lib/chef/mixin/command/windows.rb
@@ -18,7 +18,7 @@
# limitations under the License.
#
-require 'open3'
+require "open3"
class Chef
module Mixin
diff --git a/lib/chef/mixin/convert_to_class_name.rb b/lib/chef/mixin/convert_to_class_name.rb
index 19f229fdd3..8ac42cb218 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{^(.+?)(_(.+))?$}
@@ -44,21 +42,29 @@ class Chef
def convert_to_snake_case(str, namespace=nil)
str = str.dup
- str.sub!(/^#{namespace}(\:\:)?/, '') if namespace
+ str.sub!(/^#{namespace}(\:\:)?/, "") if namespace
str.gsub!(/[A-Z]/) {|s| "_" + s}
str.downcase!
str.sub!(/^\_/, "")
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(/^_/, '')
+ with_namespace.split("::").last.sub(/^_/, "")
end
def filename_to_qualified_string(base, filename)
file_base = File.basename(filename, ".rb")
- base.to_s + (file_base == 'default' ? '' : "_#{file_base}")
+ 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
@@ -85,7 +91,7 @@ class Chef
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
+ names = camel_cased_word.split("::")
# Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
diff --git a/lib/chef/mixin/create_path.rb b/lib/chef/mixin/create_path.rb
index 547224dda9..ee0f80448f 100644
--- a/lib/chef/mixin/create_path.rb
+++ b/lib/chef/mixin/create_path.rb
@@ -35,7 +35,7 @@ class Chef
if file_path.kind_of?(String)
file_path = File.expand_path(file_path).split(File::SEPARATOR)
- file_path.shift if file_path[0] == ''
+ file_path.shift if file_path[0] == ""
# Check if path starts with a separator or drive letter (Windows)
unless file_path[0].match("^#{File::SEPARATOR}|^[a-zA-Z]:")
file_path[0] = "#{File::SEPARATOR}#{file_path[0]}"
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/enforce_ownership_and_permissions.rb b/lib/chef/mixin/enforce_ownership_and_permissions.rb
index 9c1e4dda93..4dbdef0451 100644
--- a/lib/chef/mixin/enforce_ownership_and_permissions.rb
+++ b/lib/chef/mixin/enforce_ownership_and_permissions.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/file_access_control'
+require "chef/file_access_control"
class Chef
module Mixin
diff --git a/lib/chef/mixin/file_class.rb b/lib/chef/mixin/file_class.rb
index f6a663daed..f612cec148 100644
--- a/lib/chef/mixin/file_class.rb
+++ b/lib/chef/mixin/file_class.rb
@@ -2,7 +2,7 @@
# Author:: Mark Mzyk <mmzyk@opscode.com>
# Author:: Seth Chisamore <schisamo@opscode.com>
# Author:: Bryan McLellan <btm@opscode.com>
-# Copyright:: Copyright (c) 2011-2012 Opscode, Inc.
+# Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,14 +24,12 @@ class Chef
def file_class
@host_os_file ||= if Chef::Platform.windows?
- require 'chef/win32/file'
- Chef::ReservedNames::Win32::File
- else
- ::File
- end
+ require "chef/win32/file"
+ Chef::ReservedNames::Win32::File
+ else
+ ::File
+ end
end
end
end
end
-
-
diff --git a/lib/chef/mixin/get_source_from_package.rb b/lib/chef/mixin/get_source_from_package.rb
index 2ed251854a..cb5583b431 100644
--- a/lib/chef/mixin/get_source_from_package.rb
+++ b/lib/chef/mixin/get_source_from_package.rb
@@ -1,5 +1,5 @@
# Author:: Lamont Granquist (<lamont@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Copyright:: Copyright (c) 2008-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,6 +27,12 @@
class Chef
module Mixin
module GetSourceFromPackage
+ # FIXME: this is some bad code that I wrote a long time ago.
+ # - it does too much in the initializer
+ # - it mutates the new_resource
+ # - it does not support multipackage arrays
+ # this code is deprecated, check out the :use_package_names_for_source
+ # subclass directive instead
def initialize(new_resource, run_context)
super
return if new_resource.package_name.is_a?(Array)
@@ -40,4 +46,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/mixin/homebrew_user.rb b/lib/chef/mixin/homebrew_user.rb
index ab6fb19563..6be3536814 100644
--- a/lib/chef/mixin/homebrew_user.rb
+++ b/lib/chef/mixin/homebrew_user.rb
@@ -22,8 +22,8 @@
# This lives here in Chef::Mixin because Chef's namespacing makes it
# awkward to use modules elsewhere (e.g., chef/provider/package/homebrew/owner)
-require 'chef/mixin/shell_out'
-require 'etc'
+require "chef/mixin/shell_out"
+require "etc"
class Chef
module Mixin
@@ -48,7 +48,7 @@ class Chef
private
def calculate_owner
- default_brew_path = '/usr/local/bin/brew'
+ default_brew_path = "/usr/local/bin/brew"
if ::File.exist?(default_brew_path)
# By default, this follows symlinks which is what we want
owner = ::File.stat(default_brew_path).uid
diff --git a/lib/chef/mixin/language.rb b/lib/chef/mixin/language.rb
index f4df86bdc3..45e96472c6 100644
--- a/lib/chef/mixin/language.rb
+++ b/lib/chef/mixin/language.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/dsl/platform_introspection'
-require 'chef/dsl/data_query'
-require 'chef/mixin/deprecation'
+require "chef/dsl/platform_introspection"
+require "chef/dsl/data_query"
+require "chef/mixin/deprecation"
class Chef
module Mixin
diff --git a/lib/chef/mixin/language_include_attribute.rb b/lib/chef/mixin/language_include_attribute.rb
index 0be2614e41..351ce6ad44 100644
--- a/lib/chef/mixin/language_include_attribute.rb
+++ b/lib/chef/mixin/language_include_attribute.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/dsl/include_attribute'
-require 'chef/mixin/deprecation'
+require "chef/dsl/include_attribute"
+require "chef/mixin/deprecation"
class Chef
module Mixin
diff --git a/lib/chef/mixin/language_include_recipe.rb b/lib/chef/mixin/language_include_recipe.rb
index d85e5c682d..8e608516d9 100644
--- a/lib/chef/mixin/language_include_recipe.rb
+++ b/lib/chef/mixin/language_include_recipe.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/dsl/include_recipe'
-require 'chef/mixin/deprecation'
+require "chef/dsl/include_recipe"
+require "chef/mixin/deprecation"
class Chef
module Mixin
diff --git a/lib/chef/mixin/params_validate.rb b/lib/chef/mixin/params_validate.rb
index 78d72dc801..841cf9e6a2 100644
--- a/lib/chef/mixin/params_validate.rb
+++ b/lib/chef/mixin/params_validate.rb
@@ -15,9 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require "chef/constants"
+require "chef/property"
+require "chef/delayed_evaluator"
+
class Chef
- class DelayedEvaluator < Proc
- end
module Mixin
module ParamsValidate
@@ -32,20 +34,55 @@ class Chef
# Would raise an exception if the value of :one above is not a kind_of? string. Valid
# map options are:
#
- # :default:: Sets the default value for this parameter.
- # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
- # The key will be inserted into the error message if the Proc does not return true:
- # "Option #{key}'s value #{value} #{message}!"
- # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
- # that the value is one of those types.
- # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
- # method names.
- # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
- # by default, options are not required.
- # :regex:: Match the value of the parameter against a regular expression.
- # :equal_to:: Match the value of the parameter with ==. An array means it can be equal to any
- # of the values.
+ # @param opts [Hash<Symbol,Object>] Validation opts.
+ # @option opts [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`opts[:is].any? { |v| v === value }`). (See #_pv_is.)
+ # @option opts [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`opts[:is].any? { |v| v == value }`) (See #_pv_equal_to.)
+ # @option opts [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # (See #_pv_regex)
+ # @option opts [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of. (See
+ # #_pv_kind_of.)
+ # @option opts [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches). (See
+ # #_pv_callbacks.)
+ # @option opts [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to. (See
+ # #_pv_respond_to.)
+ # @option opts [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty). (See #_pv_cannot_be.)
+ # @option opts [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # (See #_pv_coerce.) (See #_pv_coerce.)
+ # @option opts [Boolean] :required `true` if this property
+ # must be present and not `nil`; `false` otherwise. This is checked
+ # after the resource is fully initialized. (See #_pv_required.)
+ # @option opts [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set. (See
+ # #_pv_name_property.)
+ # @option opts [Boolean] :name_attribute Same as `name_property`.
+ # @option opts [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties). (See #_pv_default.)
+ #
def validate(opts, map)
+ map = map.validation_options if map.is_a?(Property)
+
#--
# validate works by taking the keys in the validation map, assuming it's a hash, and
# looking for _pv_:symbol as methods. Assuming it find them, it calls the right
@@ -65,7 +102,7 @@ class Chef
true
when Hash
validation.each do |check, carg|
- check_method = "_pv_#{check.to_s}"
+ check_method = "_pv_#{check}"
if self.respond_to?(check_method, true)
self.send(check_method, opts, key, carg)
else
@@ -81,162 +118,352 @@ class Chef
DelayedEvaluator.new(&block)
end
- def set_or_return(symbol, arg, validation)
- iv_symbol = "@#{symbol.to_s}".to_sym
- if arg == nil && self.instance_variable_defined?(iv_symbol) == true
- ivar = self.instance_variable_get(iv_symbol)
- if(ivar.is_a?(DelayedEvaluator))
- validate({ symbol => ivar.call }, { symbol => validation })[symbol]
- else
- ivar
- end
- else
- if(arg.is_a?(DelayedEvaluator))
- val = arg
- else
- val = validate({ symbol => arg }, { symbol => validation })[symbol]
+ def set_or_return(symbol, value, validation)
+ property = SetOrReturnProperty.new(name: symbol, **validation)
+ property.call(self, value)
+ end
- # Handle the case where the "default" was a DelayedEvaluator. In
- # this case, the block yields an optional parameter of +self+,
- # which is the equivalent of "new_resource"
- if val.is_a?(DelayedEvaluator)
- val = val.call(self)
- end
- end
- self.instance_variable_set(iv_symbol, val)
+ private
+
+ def explicitly_allows_nil?(key, validation)
+ validation.has_key?(:is) && _pv_is({ key => nil }, key, validation[:is], raise_error: false)
+ end
+
+ # Return the value of a parameter, or nil if it doesn't exist.
+ def _pv_opts_lookup(opts, key)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s]
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym]
+ else
+ nil
end
end
- private
+ # Raise an exception if the parameter is not found.
+ def _pv_required(opts, key, is_required=true, explicitly_allows_nil=false)
+ if is_required
+ return true if opts.has_key?(key.to_s) && (explicitly_allows_nil || !opts[key.to_s].nil?)
+ return true if opts.has_key?(key.to_sym) && (explicitly_allows_nil || !opts[key.to_sym].nil?)
+ raise Exceptions::ValidationFailed, "Required argument #{key.inspect} is missing!"
+ end
+ true
+ end
- # Return the value of a parameter, or nil if it doesn't exist.
- def _pv_opts_lookup(opts, key)
- if opts.has_key?(key.to_s)
- opts[key.to_s]
- elsif opts.has_key?(key.to_sym)
- opts[key.to_sym]
- else
- nil
+ #
+ # List of things values must be equal to.
+ #
+ # Uses Ruby's `==` to evaluate (equal_to == value). At least one must
+ # match for the value to be valid.
+ #
+ # `nil` passes this validation automatically.
+ #
+ # @return [Array,nil] List of things values must be equal to, or nil if
+ # equal_to is unspecified.
+ #
+ def _pv_equal_to(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value == tb
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
end
+ end
- # Raise an exception if the parameter is not found.
- def _pv_required(opts, key, is_required=true)
- if is_required
- if (opts.has_key?(key.to_s) && !opts[key.to_s].nil?) ||
- (opts.has_key?(key.to_sym) && !opts[key.to_sym].nil?)
- true
- else
- raise Exceptions::ValidationFailed, "Required argument #{key} is missing!"
- end
+ #
+ # List of things values must be instances of.
+ #
+ # Uses value.kind_of?(kind_of) to evaluate. At least one must match for
+ # the value to be valid.
+ #
+ # `nil` automatically passes this validation.
+ #
+ def _pv_kind_of(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ to_be = Array(to_be)
+ to_be.each do |tb|
+ return true if value.kind_of?(tb)
end
+ raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
end
+ end
- def _pv_equal_to(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value == tb
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ #
+ # List of method names values must respond to.
+ #
+ # Uses value.respond_to?(respond_to) to evaluate. At least one must match
+ # for the value to be valid.
+ #
+ def _pv_respond_to(opts, key, method_name_list)
+ value = _pv_opts_lookup(opts, key)
+ unless value.nil?
+ Array(method_name_list).each do |method_name|
+ unless value.respond_to?(method_name)
+ raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
end
end
end
+ end
- # Raise an exception if the parameter is not a kind_of?(to_be)
- def _pv_kind_of(opts, key, to_be)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- passes = false
- Array(to_be).each do |tb|
- passes = true if value.kind_of?(tb)
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ #
+ # List of things that must not be true about the value.
+ #
+ # Calls `value.<thing>?` All responses must be false for the value to be
+ # valid.
+ # Values which do not respond to <thing>? are considered valid (because if
+ # a value doesn't respond to `:readable?`, then it probably isn't
+ # readable.)
+ #
+ # @example
+ # ```ruby
+ # property :x, cannot_be: [ :nil, :empty ]
+ # x [ 1, 2 ] #=> valid
+ # x 1 #=> valid
+ # x [] #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_cannot_be(opts, key, predicate_method_base_name)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(predicate_method_base_name).each do |method_name|
+ predicate_method = :"#{method_name}?"
+
+ if value.respond_to?(predicate_method)
+ if value.send(predicate_method)
+ raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ end
end
end
end
+ end
- # Raise an exception if the parameter does not respond to a given set of methods.
- def _pv_respond_to(opts, key, method_name_list)
- value = _pv_opts_lookup(opts, key)
- unless value.nil?
- Array(method_name_list).each do |method_name|
- unless value.respond_to?(method_name)
- raise Exceptions::ValidationFailed, "Option #{key} must have a #{method_name} method!"
- end
- end
+ #
+ # The default value for a property.
+ #
+ # When the property is not assigned, this will be used.
+ #
+ # If this is a lazy value, it will either be passed the resource as a value,
+ # or if the lazy proc does not take parameters, it will be run in the
+ # context of the instance with instance_eval.
+ #
+ # @example
+ # ```ruby
+ # property :x, default: 10
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { x+2 }
+ # ```
+ #
+ # @example
+ # ```ruby
+ # property :x
+ # property :y, default: lazy { |r| r.x+2 }
+ # ```
+ #
+ def _pv_default(opts, key, default_value)
+ value = _pv_opts_lookup(opts, key)
+ if value.nil?
+ default_value = default_value.freeze if !default_value.is_a?(DelayedEvaluator)
+ opts[key] = default_value
+ end
+ end
+
+ #
+ # List of regexes values that must match.
+ #
+ # Uses regex.match() to evaluate. At least one must match for the value to
+ # be valid.
+ #
+ # `nil` passes regex validation automatically.
+ #
+ # @example
+ # ```ruby
+ # property :x, regex: [ /abc/, /xyz/ ]
+ # ```
+ #
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ Array(regex).flatten.each do |r|
+ return true if r.match(value.to_s)
end
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
end
+ end
- # Assert that parameter returns false when passed a predicate method.
- # For example, :cannot_be => :blank will raise a Exceptions::ValidationFailed
- # error value.blank? returns a 'truthy' (not nil or false) value.
- #
- # Note, this will *PASS* if the object doesn't respond to the method.
- # So, to make sure a value is not nil and not blank, you need to do
- # both :cannot_be => :blank *and* :cannot_be => :nil (or :required => true)
- def _pv_cannot_be(opts, key, predicate_method_base_name)
- value = _pv_opts_lookup(opts, key)
- predicate_method = (predicate_method_base_name.to_s + "?").to_sym
-
- if value.respond_to?(predicate_method)
- if value.send(predicate_method)
- raise Exceptions::ValidationFailed, "Option #{key} cannot be #{predicate_method_base_name}"
+ #
+ # List of procs we pass the value to.
+ #
+ # All procs must return true for the value to be valid. If any procs do
+ # not return true, the key will be used for the message: `"Property x's
+ # value :y <message>"`.
+ #
+ # @example
+ # ```ruby
+ # property :x, callbacks: { "is bigger than 10" => proc { |v| v <= 10 }, "is not awesome" => proc { |v| !v.awesome }}
+ # ```
+ #
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if !value.nil?
+ callbacks.each do |message, zeproc|
+ unless zeproc.call(value)
+ raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
end
end
end
+ end
- # Assign a default value to a parameter.
- def _pv_default(opts, key, default_value)
- value = _pv_opts_lookup(opts, key)
- if value == nil
- opts[key] = default_value
+ #
+ # Allows a parameter to default to the value of the resource name.
+ #
+ # @example
+ # ```ruby
+ # property :x, name_property: true
+ # ```
+ #
+ def _pv_name_property(opts, key, is_name_property=true)
+ if is_name_property
+ if opts[key].nil?
+ opts[key] = self.instance_variable_get(:"@name")
end
end
+ end
+ alias :_pv_name_attribute :_pv_name_property
- # Check a parameter against a regular expression.
- def _pv_regex(opts, key, regex)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- passes = false
- [ regex ].flatten.each do |r|
- if value != nil
- if r.match(value.to_s)
- passes = true
- end
- end
- end
- unless passes
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} does not match regular expression #{regex.inspect}"
- end
+ #
+ # List of valid things values can be.
+ #
+ # Uses Ruby's `===` to evaluate (is === value). At least one must match
+ # for the value to be valid.
+ #
+ # If a proc is passed, it is instance_eval'd in the resource, passed the
+ # value, and must return a truthy or falsey value.
+ #
+ # @example Class
+ # ```ruby
+ # property :x, String
+ # x 'valid' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ #
+ # @example Value
+ # ```ruby
+ # property :x, [ :a, :b, :c, nil ]
+ # x :a #=> valid
+ # x nil #=> valid
+ # ```
+ #
+ # @example Regex
+ # ```ruby
+ # property :x, /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example Proc
+ # ```ruby
+ # property :x, proc { |x| x > y }
+ # property :y, default: 2
+ # x 3 #=> valid
+ # x 1 #=> invalid
+ # ```
+ #
+ # @example Property
+ # ```ruby
+ # type = Property.new(is: String)
+ # property :x, type
+ # x 'foo' #=> valid
+ # x 1 #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ # @example RSpec Matcher
+ # ```ruby
+ # include RSpec::Matchers
+ # property :x, a_string_matching /bar/
+ # x 'foobar' #=> valid
+ # x 'foo' #=> invalid
+ # x nil #=> invalid
+ # ```
+ #
+ def _pv_is(opts, key, to_be, raise_error: true)
+ return true if !opts.has_key?(key.to_s) && !opts.has_key?(key.to_sym)
+ value = _pv_opts_lookup(opts, key)
+ to_be = [ to_be ].flatten(1)
+ to_be.each do |tb|
+ case tb
+ when Proc
+ return true if instance_exec(value, &tb)
+ when Property
+ validate(opts, { key => tb.validation_options })
+ return true
+ else
+ return true if tb === value
end
end
- # Check a parameter against a hash of proc's.
- def _pv_callbacks(opts, key, callbacks)
- raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
- value = _pv_opts_lookup(opts, key)
- if value != nil
- callbacks.each do |message, zeproc|
- if zeproc.call(value) != true
- raise Exceptions::ValidationFailed, "Option #{key}'s value #{value} #{message}!"
- end
- end
+ if raise_error
+ raise Exceptions::ValidationFailed, "Option #{key} must be one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ else
+ false
+ end
+ end
+
+ #
+ # Method to mess with a value before it is validated and stored.
+ #
+ # Allows you to transform values into a canonical form that is easy to
+ # work with.
+ #
+ # This is passed the value to transform, and is run in the context of the
+ # instance (so it has access to other resource properties). It must return
+ # the value that will be stored in the instance.
+ #
+ # @example
+ # ```ruby
+ # property :x, Integer, coerce: { |v| v.to_i }
+ # ```
+ #
+ def _pv_coerce(opts, key, coercer)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s] = instance_exec(opts[key], &coercer)
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym] = instance_exec(opts[key], &coercer)
+ end
+ end
+
+ # Used by #set_or_return to avoid emitting a deprecation warning for
+ # "value nil" and to keep default stickiness working exactly the same
+ # @api private
+ class SetOrReturnProperty < Chef::Property
+ def get(resource)
+ value = super
+ # All values are sticky, frozen or not
+ if !is_set?(resource)
+ set_value(resource, value)
end
+ value
end
- # Allow a parameter to default to @name
- def _pv_name_attribute(opts, key, is_name_attribute=true)
- if is_name_attribute
- if opts[key] == nil
- opts[key] = self.instance_variable_get("@name")
- end
+ def call(resource, value=NOT_PASSED)
+ # setting to nil does a get
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ get(resource)
+ else
+ super
end
end
+ end
end
end
end
-
diff --git a/lib/chef/mixin/path_sanity.rb b/lib/chef/mixin/path_sanity.rb
index ed857ffd36..ef04bf288f 100644
--- a/lib/chef/mixin/path_sanity.rb
+++ b/lib/chef/mixin/path_sanity.rb
@@ -23,7 +23,7 @@ class Chef
def enforce_path_sanity(env=ENV)
if Chef::Config[:enforce_path_sanity]
env["PATH"] = "" if env["PATH"].nil?
- path_separator = Chef::Platform.windows? ? ';' : ':'
+ path_separator = Chef::Platform.windows? ? ";" : ":"
existing_paths = env["PATH"].split(path_separator)
# ensure the Ruby and Gem bindirs are included
# mainly for 'full-stack' Chef installs
@@ -48,15 +48,15 @@ class Chef
def sane_paths
@sane_paths ||= begin
if Chef::Platform.windows?
- %w[]
+ %w{}
else
- %w[/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin]
+ %w{/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin}
end
end
end
def ruby_bindir
- RbConfig::CONFIG['bindir']
+ RbConfig::CONFIG["bindir"]
end
def gem_bindir
diff --git a/lib/chef/mixin/powershell_out.rb b/lib/chef/mixin/powershell_out.rb
new file mode 100644
index 0000000000..5e52c56747
--- /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
index 75b3276c84..e068acab6e 100644
--- a/lib/chef/mixin/powershell_type_coercions.rb
+++ b/lib/chef/mixin/powershell_type_coercions.rb
@@ -25,10 +25,10 @@ class Chef
@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' }},
+ 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)}}
+ Array => {:type => Proc.new { |x| translate_array(x)}},
}
end
@@ -61,7 +61,7 @@ class Chef
end
def unsafe?(s)
- ["'", '#', '`', '"'].any? do |x|
+ ["'", '#', "`", '"'].any? do |x|
s.include? x
end
end
diff --git a/lib/chef/mixin/properties.rb b/lib/chef/mixin/properties.rb
new file mode 100644
index 0000000000..af4e2c2c09
--- /dev/null
+++ b/lib/chef/mixin/properties.rb
@@ -0,0 +1,302 @@
+require "chef/delayed_evaluator"
+require "chef/mixin/params_validate"
+require "chef/property"
+
+class Chef
+ module Mixin
+ module Properties
+ module ClassMethods
+ #
+ # The list of properties defined on this resource.
+ #
+ # Everything defined with `property` is in this list.
+ #
+ # @param include_superclass [Boolean] `true` to include properties defined
+ # on superclasses; `false` or `nil` to return the list of properties
+ # directly on this class.
+ #
+ # @return [Hash<Symbol,Property>] The list of property names and types.
+ #
+ def properties(include_superclass=true)
+ if include_superclass
+ result = {}
+ ancestors.reverse_each { |c| result.merge!(c.properties(false)) if c.respond_to?(:properties) }
+ result
+ else
+ @properties ||= {}
+ end
+ end
+
+ #
+ # Create a property on this resource class.
+ #
+ # If a superclass has this property, or if this property has already been
+ # defined by this resource, this will *override* the previous value.
+ #
+ # @param name [Symbol] The name of the property.
+ # @param type [Object,Array<Object>] The type(s) of this property.
+ # If present, this is prepended to the `is` validation option.
+ # @param options [Hash<Symbol,Object>] Validation options.
+ # @option options [Object,Array] :is An object, or list of
+ # objects, that must match the value using Ruby's `===` operator
+ # (`options[:is].any? { |v| v === value }`).
+ # @option options [Object,Array] :equal_to An object, or list
+ # of objects, that must be equal to the value using Ruby's `==`
+ # operator (`options[:is].any? { |v| v == value }`)
+ # @option options [Regexp,Array<Regexp>] :regex An object, or
+ # list of objects, that must match the value with `regex.match(value)`.
+ # @option options [Class,Array<Class>] :kind_of A class, or
+ # list of classes, that the value must be an instance of.
+ # @option options [Hash<String,Proc>] :callbacks A hash of
+ # messages -> procs, all of which match the value. The proc must
+ # return a truthy or falsey value (true means it matches).
+ # @option options [Symbol,Array<Symbol>] :respond_to A method
+ # name, or list of method names, the value must respond to.
+ # @option options [Symbol,Array<Symbol>] :cannot_be A property,
+ # or a list of properties, that the value cannot have (such as `:nil` or
+ # `:empty`). The method with a questionmark at the end is called on the
+ # value (e.g. `value.empty?`). If the value does not have this method,
+ # it is considered valid (i.e. if you don't respond to `empty?` we
+ # assume you are not empty).
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Boolean] :name_attribute Same as `name_property`.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties).
+ # @option options [Boolean] :desired_state `true` if this property is
+ # part of desired state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property
+ # is part of object identity. Defaults to `false`.
+ #
+ # @example Bare property
+ # property :x
+ #
+ # @example With just a type
+ # property :x, String
+ #
+ # @example With just options
+ # property :x, default: 'hi'
+ #
+ # @example With type and options
+ # property :x, String, default: 'hi'
+ #
+ def property(name, type=NOT_PASSED, **options)
+ name = name.to_sym
+
+ options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
+
+ options[:instance_variable_name] = :"@#{name}" if !options.has_key?(:instance_variable_name)
+ options.merge!(name: name, declared_in: self)
+
+ if type == NOT_PASSED
+ # If a type is not passed, the property derives from the
+ # superclass property (if any)
+ if properties.has_key?(name)
+ property = properties[name].derive(**options)
+ else
+ property = property_type(**options)
+ end
+
+ # If a Property is specified, derive a new one from that.
+ elsif type.is_a?(Property) || (type.is_a?(Class) && type <= Property)
+ property = type.derive(**options)
+
+ # If a primitive type was passed, combine it with "is"
+ else
+ if options[:is]
+ options[:is] = ([ type ] + [ options[:is] ]).flatten(1)
+ else
+ options[:is] = type
+ end
+ property = property_type(**options)
+ end
+
+ local_properties = properties(false)
+ local_properties[name] = property
+
+ property.emit_dsl
+ end
+
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. see #property for
+ # the list of options.
+ #
+ # @example
+ # property_type(default: 'hi')
+ #
+ def property_type(**options)
+ Property.derive(**options)
+ end
+
+ #
+ # Create a lazy value for assignment to a default value.
+ #
+ # @param block The block to run when the value is retrieved.
+ #
+ # @return [Chef::DelayedEvaluator] The lazy value
+ #
+ def lazy(&block)
+ DelayedEvaluator.new(&block)
+ end
+
+ #
+ # Get or set the list of desired state properties for this resource.
+ #
+ # State properties are properties that describe the desired state
+ # of the system, such as file permissions or ownership.
+ # In general, state properties are properties that could be populated by
+ # examining the state of the system (e.g., File.stat can tell you the
+ # permissions on an existing file). Contrarily, properties that are not
+ # "state properties" usually modify the way Chef itself behaves, for example
+ # by providing additional options for a package manager to use when
+ # installing a package.
+ #
+ # This list is used by the Chef client auditing system to extract
+ # information from resources to describe changes made to the system.
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties are added to state_properties by default, and can be turned off
+ # with `desired_state: false`.
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Property>] All properties in desired state.
+ #
+ def state_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }.uniq
+
+ local_properties = properties(false)
+ # Add new properties to the list.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, desired_state: true
+ elsif !property.desired_state?
+ self.property name, desired_state: true
+ end
+ end
+
+ # If state_attrs *excludes* something which is currently desired state,
+ # mark it as desired_state: false.
+ local_properties.each do |name,property|
+ if property.desired_state? && !names.include?(name)
+ self.property name, desired_state: false
+ end
+ end
+ end
+
+ properties.values.select { |property| property.desired_state? }
+ end
+
+ #
+ # Set the identity of this resource to a particular set of properties.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # If no properties are marked as identity, "name" is considered the identity.
+ #
+ # @param names [Array<Symbol>] A list of property names to set as the identity.
+ #
+ # @return [Array<Property>] All identity properties.
+ #
+ def identity_properties(*names)
+ if !names.empty?
+ names = names.map { |name| name.to_sym }
+
+ # Add or change properties that are not part of the identity.
+ names.each do |name|
+ property = properties[name]
+ if !property
+ self.property name, instance_variable_name: false, identity: true
+ elsif !property.identity?
+ self.property name, identity: true
+ end
+ end
+
+ # If identity_properties *excludes* something which is currently part of
+ # the identity, mark it as identity: false.
+ properties.each do |name,property|
+ if property.identity? && !names.include?(name)
+
+ self.property name, identity: false
+ end
+ end
+ end
+
+ result = properties.values.select { |property| property.identity? }
+ result = [ properties[:name] ] if result.empty?
+ result
+ end
+
+ def included(other)
+ other.extend ClassMethods
+ end
+ end
+
+ def self.included(other)
+ other.extend ClassMethods
+ end
+
+ include Chef::Mixin::ParamsValidate
+
+ #
+ # Whether this property has been set (or whether it has a default that has
+ # been retrieved).
+ #
+ # @param name [Symbol] The name of the property.
+ # @return [Boolean] `true` if the property has been set.
+ #
+ def property_is_set?(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.is_set?(self)
+ end
+
+ #
+ # Clear this property as if it had never been set. It will thereafter return
+ # the default.
+ # been retrieved).
+ #
+ # @param name [Symbol] The name of the property.
+ #
+ def reset_property(name)
+ property = self.class.properties[name.to_sym]
+ raise ArgumentError, "Property #{name} is not defined in class #{self}" if !property
+ property.reset(self)
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/provides.rb b/lib/chef/mixin/provides.rb
new file mode 100644
index 0000000000..5885752752
--- /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/proxified_socket.rb b/lib/chef/mixin/proxified_socket.rb
new file mode 100644
index 0000000000..07a6e49928
--- /dev/null
+++ b/lib/chef/mixin/proxified_socket.rb
@@ -0,0 +1,38 @@
+# Author:: Tyler Ball (<tball@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 "proxifier"
+
+class Chef
+ module Mixin
+ module ProxifiedSocket
+
+ # This looks at the environment variables and leverages Proxifier to
+ # make the TCPSocket respect ENV['https_proxy'] or ENV['http_proxy'] if
+ # they are present
+ def proxified_socket(host, port)
+ proxy = ENV["https_proxy"] || ENV["http_proxy"] || false
+ if proxy
+ Proxifier.Proxy(proxy, no_proxy: ENV["no_proxy"]).open(host, port)
+ else
+ TCPSocket.new(host, port)
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/mixin/recipe_definition_dsl_core.rb b/lib/chef/mixin/recipe_definition_dsl_core.rb
index 704ee162be..c998936b69 100644
--- a/lib/chef/mixin/recipe_definition_dsl_core.rb
+++ b/lib/chef/mixin/recipe_definition_dsl_core.rb
@@ -24,7 +24,7 @@
# This constant (module name) will eventually be deprecated and then removed.
###
-require 'chef/mixin/deprecation'
+require "chef/mixin/deprecation"
class Chef
module Mixin
diff --git a/lib/chef/mixin/securable.rb b/lib/chef/mixin/securable.rb
index aaedf0b9ba..e0d3752733 100644
--- a/lib/chef/mixin/securable.rb
+++ b/lib/chef/mixin/securable.rb
@@ -24,7 +24,7 @@ class Chef
set_or_return(
:owner,
arg,
- :regex => Chef::Config[:user_valid_regex]
+ :regex => Chef::Config[:user_valid_regex],
)
end
@@ -34,7 +34,7 @@ class Chef
set_or_return(
:group,
arg,
- :regex => Chef::Config[:group_valid_regex]
+ :regex => Chef::Config[:group_valid_regex],
)
end
@@ -54,8 +54,8 @@ class Chef
else
Integer(m)<=07777 && Integer(m)>=0
end
- },
- }
+ }
+ },
)
end
@@ -112,11 +112,11 @@ class Chef
# equivalent to something like:
# def rights(permissions=nil, principals=nil, args_hash=nil)
define_method(name) do |permissions=nil, principals=nil, args_hash=nil|
- rights = self.instance_variable_get("@#{name.to_s}".to_sym)
+ rights = self.instance_variable_get("@#{name}".to_sym)
unless permissions.nil?
input = {
:permissions => permissions,
- :principals => principals
+ :principals => principals,
}
input.merge!(args_hash) unless args_hash.nil?
@@ -124,7 +124,7 @@ class Chef
:principals => { :required => true, :kind_of => [String, Array] },
:applies_to_children => { :equal_to => [ true, false, :containers_only, :objects_only ]},
:applies_to_self => { :kind_of => [ TrueClass, FalseClass ] },
- :one_level_deep => { :kind_of => [ TrueClass, FalseClass ] }
+ :one_level_deep => { :kind_of => [ TrueClass, FalseClass ] },
}
validate(input, validations)
@@ -158,7 +158,7 @@ class Chef
set_or_return(
name,
rights,
- {}
+ {},
)
end
end
@@ -174,7 +174,7 @@ class Chef
set_or_return(
:inherits,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
end
diff --git a/lib/chef/mixin/shell_out.rb b/lib/chef/mixin/shell_out.rb
index 529023056d..1e6f96d99a 100644
--- a/lib/chef/mixin/shell_out.rb
+++ b/lib/chef/mixin/shell_out.rb
@@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'mixlib/shellout'
+require "mixlib/shellout"
class Chef
module Mixin
@@ -35,15 +35,15 @@ class Chef
env_key = options.has_key?(:env) ? :env : :environment
options[env_key] ||= {}
options[env_key] = options[env_key].dup
- options[env_key]['LC_ALL'] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?('LC_ALL')
- options[env_key]['LANGUAGE'] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?('LANGUAGE')
- options[env_key]['LANG'] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?('LANG')
+ options[env_key]["LC_ALL"] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?("LC_ALL")
+ options[env_key]["LANGUAGE"] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?("LANGUAGE")
+ options[env_key]["LANG"] ||= Chef::Config[:internal_locale] unless options[env_key].has_key?("LANG")
args << options
else
args << { :environment => {
- 'LC_ALL' => Chef::Config[:internal_locale],
- 'LANGUAGE' => Chef::Config[:internal_locale],
- 'LANG' => Chef::Config[:internal_locale],
+ "LC_ALL" => Chef::Config[:internal_locale],
+ "LANGUAGE" => Chef::Config[:internal_locale],
+ "LANG" => Chef::Config[:internal_locale],
} }
end
@@ -114,4 +114,4 @@ class Chef
end
# Break circular dep
-require 'chef/config'
+require "chef/config"
diff --git a/lib/chef/mixin/subclass_directive.rb b/lib/chef/mixin/subclass_directive.rb
new file mode 100644
index 0000000000..0f386b6cb2
--- /dev/null
+++ b/lib/chef/mixin/subclass_directive.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# 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.
+#
+
+class Chef
+ module Mixin
+ module SubclassDirective
+ def subclass_directive(sym)
+ define_singleton_method sym do
+ instance_variable_set(:"@#{sym}", true)
+ end
+
+ define_singleton_method :"#{sym}?" do
+ !!instance_variable_get(:"@#{sym}")
+ end
+
+ define_method :"#{sym}?" do
+ self.class.send(:"#{sym}?")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/mixin/template.rb b/lib/chef/mixin/template.rb
index d705a9e7be..c669dc630b 100644
--- a/lib/chef/mixin/template.rb
+++ b/lib/chef/mixin/template.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'tempfile'
-require 'erubis'
+require "tempfile"
+require "erubis"
class Chef
module Mixin
@@ -44,6 +44,52 @@ class Chef
attr_reader :_extension_modules
+ #
+ # Helpers for adding context of which resource is rendering the template (CHEF-5012)
+ #
+
+ # name of the cookbook containing the template resource, e.g.:
+ # test
+ #
+ # @return [String] cookbook name
+ attr_reader :cookbook_name
+
+ # name of the recipe containing the template resource, e.g.:
+ # default
+ #
+ # @return [String] recipe name
+ attr_reader :recipe_name
+
+ # string representation of the line in the recipe containing the template resource, e.g.:
+ # /Users/lamont/solo/cookbooks/test/recipes/default.rb:2:in `from_file'
+ #
+ # @return [String] recipe line
+ attr_reader :recipe_line_string
+
+ # path to the recipe containing the template resource, e.g.:
+ # /Users/lamont/solo/cookbooks/test/recipes/default.rb
+ #
+ # @return [String] recipe path
+ attr_reader :recipe_path
+
+ # line in the recipe containing the template reosurce, e.g.:
+ # 2
+ #
+ # @return [String] recipe line
+ attr_reader :recipe_line
+
+ # name of the template source itself, e.g.:
+ # foo.erb
+ #
+ # @return [String] template name
+ attr_reader :template_name
+
+ # path to the template source itself, e.g.:
+ # /Users/lamont/solo/cookbooks/test/templates/default/foo.erb
+ #
+ # @return [String] template path
+ attr_reader :template_path
+
def initialize(variables)
super
@_extension_modules = []
@@ -62,6 +108,7 @@ class Chef
"include a node variable if you plan to use it."
end
+
#
# Takes the name of the partial, plus a hash of options. Returns a
# string that contains the result of the evaluation of the partial.
@@ -89,6 +136,7 @@ class Chef
raise "You cannot render partials in this context" unless @template_finder
partial_variables = options.delete(:variables) || _public_instance_variables
+ partial_variables[:template_finder] = @template_finder
partial_context = self.class.new(partial_variables)
partial_context._extend_modules(@_extension_modules)
diff --git a/lib/chef/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..e33250dc2d
--- /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/which.rb b/lib/chef/mixin/which.rb
index 4179c97b62..715b7443dc 100644
--- a/lib/chef/mixin/which.rb
+++ b/lib/chef/mixin/which.rb
@@ -21,14 +21,14 @@ class Chef
def which(cmd, opts = {})
extra_path =
if opts[:extra_path].nil?
- [ '/bin', '/usr/bin', '/sbin', '/usr/sbin' ]
+ [ "/bin", "/usr/bin", "/sbin", "/usr/sbin" ]
else
[ opts[:extra_path] ].flatten
end
- paths = ENV['PATH'].split(File::PATH_SEPARATOR) + extra_path
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR) + extra_path
paths.each do |path|
filename = File.join(path, cmd)
- return filename if File.executable?(filename)
+ return filename if File.executable?(Chef.path_to(filename))
end
false
end
diff --git a/lib/chef/mixin/why_run.rb b/lib/chef/mixin/why_run.rb
index d3acea5490..3539ce1c5f 100644
--- a/lib/chef/mixin/why_run.rb
+++ b/lib/chef/mixin/why_run.rb
@@ -1,7 +1,7 @@
#
# Author:: Dan DeLeo ( <dan@opscode.com> )
# Author:: Marc Paradise ( <marc@opscode.com> )
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Copyright:: Copyright (c) 2012-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -322,7 +322,7 @@ class Chef
a.run(action, events, @resource)
if a.assertion_failed? and a.block_action?
@blocked_actions << action
- return
+ break
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..46c0f128dc
--- /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 a0ac34f627..7ac56ecdaf 100644
--- a/lib/chef/mixin/windows_architecture_helper.rb
+++ b/lib/chef/mixin/windows_architecture_helper.rb
@@ -17,21 +17,15 @@
#
-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/exceptions"
+require "chef/platform/query_helpers"
+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,18 +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']
+ 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
+ n[:kernel][:machine] = os_arch == "AMD64" ? :x86_64 : :i386
end
end
+
+ architecture ||= node_windows_architecture(node)
+
wow64_redirection_state = nil
- if wow64_architecture_override_required?(node, node_windows_architecture(node))
+ if wow64_architecture_override_required?(node, architecture)
wow64_redirection_state = disable_wow64_file_redirection(node)
end
@@ -85,49 +92,21 @@ class Chef
def is_i386_process_on_x86_64_windows?
if Chef::Platform.windows?
- is_64_bit_process_result = FFI::MemoryPointer.new(:int)
-
- # The return value of IsWow64Process is nonzero value if the API call succeeds.
- # The result data are returned in the last parameter, not the return value.
- call_succeeded = IsWow64Process(GetCurrentProcess(), is_64_bit_process_result)
-
- # The result is nonzero if IsWow64Process's calling process, in the case here
- # this process, is running under WOW64, i.e. the result is nonzero if this
- # process is 32-bit (aka :i386).
- result = (call_succeeded != 0) && (is_64_bit_process_result.get_int(0) != 0)
+ Chef::ReservedNames::Win32::Process.is_wow64_process
else
false
end
end
def disable_wow64_file_redirection( node )
- original_redirection_state = ['0'].pack('P')
-
if ( ( node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
- win32_wow_64_disable_wow_64_fs_redirection =
- ::Win32::API.new('Wow64DisableWow64FsRedirection', 'P', 'L', 'kernel32')
-
- succeeded = win32_wow_64_disable_wow_64_fs_redirection.call(original_redirection_state)
-
- if succeeded == 0
- raise Win32APIError "Failed to disable Wow64 file redirection"
- end
-
+ Chef::ReservedNames::Win32::System.wow64_disable_wow64_fs_redirection
end
-
- original_redirection_state
end
def restore_wow64_file_redirection( node, original_redirection_state )
if ( (node_windows_architecture(node) == :x86_64) && ::Chef::Platform.windows?)
- win32_wow_64_revert_wow_64_fs_redirection =
- ::Win32::API.new('Wow64RevertWow64FsRedirection', 'P', 'L', 'kernel32')
-
- succeeded = win32_wow_64_revert_wow_64_fs_redirection.call(original_redirection_state)
-
- if succeeded == 0
- raise Win32APIError "Failed to revert Wow64 file redirection"
- end
+ Chef::ReservedNames::Win32::System.wow64_revert_wow64_fs_redirection(original_redirection_state)
end
end
diff --git a/lib/chef/mixin/windows_env_helper.rb b/lib/chef/mixin/windows_env_helper.rb
index 490b235065..98b5dcf893 100644
--- a/lib/chef/mixin/windows_env_helper.rb
+++ b/lib/chef/mixin/windows_env_helper.rb
@@ -17,14 +17,17 @@
#
-require 'chef/exceptions'
-require 'chef/platform/query_helpers'
-require 'chef/win32/error' if Chef::Platform.windows?
-require 'chef/win32/api/system' if Chef::Platform.windows?
+require "chef/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/mixin/xml_escape.rb b/lib/chef/mixin/xml_escape.rb
index ceb45df3e6..78b0399493 100644
--- a/lib/chef/mixin/xml_escape.rb
+++ b/lib/chef/mixin/xml_escape.rb
@@ -46,10 +46,10 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-require 'chef/log'
+require "chef/log"
begin
- require 'fast_xs'
+ require "fast_xs"
rescue LoadError
Chef::Log.info "The fast_xs gem is not installed, slower pure ruby XML escaping will be used."
end
@@ -93,9 +93,9 @@ class Chef
# http://www.w3.org/TR/REC-xml/#dt-chardata
PREDEFINED = {
- 38 => '&amp;', # ampersand
- 60 => '&lt;', # left angle bracket
- 62 => '&gt;' # right angle bracket
+ 38 => "&amp;", # ampersand
+ 60 => "&lt;", # left angle bracket
+ 62 => "&gt;" # right angle bracket
}
# http://www.w3.org/TR/REC-xml/#charsets
diff --git a/lib/chef/mixins.rb b/lib/chef/mixins.rb
index 17be1622af..7ae7fe659e 100644
--- a/lib/chef/mixins.rb
+++ b/lib/chef/mixins.rb
@@ -1,14 +1,14 @@
-require 'chef/mixin/shell_out'
-require 'chef/mixin/checksum'
-require 'chef/mixin/command'
-require 'chef/mixin/convert_to_class_name'
-require 'chef/mixin/create_path'
-require 'chef/mixin/deep_merge'
-require 'chef/mixin/enforce_ownership_and_permissions'
-require 'chef/mixin/from_file'
-require 'chef/mixin/params_validate'
-require 'chef/mixin/path_sanity'
-require 'chef/mixin/template'
-require 'chef/mixin/securable'
-require 'chef/mixin/xml_escape'
+require "chef/mixin/shell_out"
+require "chef/mixin/checksum"
+require "chef/mixin/command"
+require "chef/mixin/convert_to_class_name"
+require "chef/mixin/create_path"
+require "chef/mixin/deep_merge"
+require "chef/mixin/enforce_ownership_and_permissions"
+require "chef/mixin/from_file"
+require "chef/mixin/params_validate"
+require "chef/mixin/path_sanity"
+require "chef/mixin/template"
+require "chef/mixin/securable"
+require "chef/mixin/xml_escape"
diff --git a/lib/chef/monkey_patches/net-ssh-multi.rb b/lib/chef/monkey_patches/net-ssh-multi.rb
index 0f4dd6655c..0bcb246755 100644
--- a/lib/chef/monkey_patches/net-ssh-multi.rb
+++ b/lib/chef/monkey_patches/net-ssh-multi.rb
@@ -34,11 +34,11 @@
#
# See: https://github.com/net-ssh/net-ssh-multi/pull/4
-require 'net/ssh/multi/version'
+require "net/ssh/multi/version"
if Net::SSH::Multi::Version::STRING == "1.1.0" || Net::SSH::Multi::Version::STRING == "1.2.0"
- require 'net/ssh/multi'
+ require "net/ssh/multi"
module Net
module SSH
diff --git a/lib/chef/monkey_patches/net_http.rb b/lib/chef/monkey_patches/net_http.rb
index 9c8044a9a7..6aad6029c3 100644
--- a/lib/chef/monkey_patches/net_http.rb
+++ b/lib/chef/monkey_patches/net_http.rb
@@ -5,7 +5,7 @@ module ChefNetHTTPExceptionExtensions
attr_accessor :chef_rest_request
end
-require 'net/http'
+require "net/http"
module Net
class HTTPError
include ChefNetHTTPExceptionExtensions
diff --git a/lib/chef/monkey_patches/webrick-utils.rb b/lib/chef/monkey_patches/webrick-utils.rb
new file mode 100644
index 0000000000..de28eaa3c0
--- /dev/null
+++ b/lib/chef/monkey_patches/webrick-utils.rb
@@ -0,0 +1,51 @@
+require "webrick/utils"
+
+module WEBrick
+ module Utils
+ ##
+ # Creates TCP server sockets bound to +address+:+port+ and returns them.
+ #
+ # It will create IPV4 and IPV6 sockets on all interfaces.
+ #
+ # NOTE: We need to monkey patch this method because
+ # create_listeners on Windows with Ruby > 2.0.0 does not
+ # raise an error if we're already listening on a port.
+ #
+ def create_listeners(address, port, logger=nil)
+ #
+ # utils.rb -- Miscellaneous utilities
+ #
+ # Author: IPR -- Internet Programming with Ruby -- writers
+ # Copyright (c) 2001 TAKAHASHI Masayoshi, GOTOU Yuuzou
+ # Copyright (c) 2002 Internet Programming with Ruby writers. All rights
+ # reserved.
+ #
+ # $IPR: utils.rb,v 1.10 2003/02/16 22:22:54 gotoyuzo Exp $
+ unless port
+ raise ArgumentError, "must specify port"
+ end
+ res = Socket::getaddrinfo(address, port,
+ Socket::AF_UNSPEC, # address family
+ Socket::SOCK_STREAM, # socket type
+ 0, # protocol
+ Socket::AI_PASSIVE) # flag
+ last_error = nil
+ sockets = []
+ res.each{|ai|
+ begin
+ logger.debug("TCPServer.new(#{ai[3]}, #{port})") if logger
+ sock = TCPServer.new(ai[3], port)
+ port = sock.addr[1] if port == 0
+ Utils::set_close_on_exec(sock)
+ sockets << sock
+ rescue => ex
+ logger.warn("TCPServer Error: #{ex}") if logger
+ last_error = ex
+ end
+ }
+ raise last_error if sockets.empty?
+ return sockets
+ end
+ module_function :create_listeners
+ end
+end
diff --git a/lib/chef/monkey_patches/win32/registry.rb b/lib/chef/monkey_patches/win32/registry.rb
new file mode 100644
index 0000000000..917746c1e6
--- /dev/null
+++ b/lib/chef/monkey_patches/win32/registry.rb
@@ -0,0 +1,72 @@
+#
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/win32/api/registry"
+require "chef/win32/unicode"
+require "win32/registry"
+
+module Win32
+ class Registry
+
+ module API
+
+ extend Chef::ReservedNames::Win32::API::Registry
+
+ module_function
+
+ if RUBY_VERSION =~ /^2\.1/
+ # ::Win32::Registry#delete_value is broken in Ruby 2.1 (up to Ruby 2.1.6).
+ # This should be resolved in a later release (see note #9 in link below).
+ # https://bugs.ruby-lang.org/issues/10820
+ def DeleteValue(hkey, name)
+ check RegDeleteValueW(hkey, name.to_wstring)
+ end
+ end
+
+ # ::Win32::Registry#delete_key uses RegDeleteKeyW. We need to use
+ # RegDeleteKeyExW to properly support WOW64 systems.
+ def DeleteKey(hkey, name)
+ check RegDeleteKeyExW(hkey, name.to_wstring, 0, 0)
+ end
+
+ end
+
+ if RUBY_VERSION =~ /^2.1/
+ # ::Win32::Registry#write does not correctly handle data in Ruby 2.1 (up to Ruby 2.1.6).
+ # https://bugs.ruby-lang.org/issues/11439
+ def write(name, type, data)
+ case type
+ when REG_SZ, REG_EXPAND_SZ
+ data = data.to_s.encode(WCHAR) + WCHAR_NUL
+ when REG_MULTI_SZ
+ data = data.to_a.map {|s| s.encode(WCHAR)}.join(WCHAR_NUL) << WCHAR_NUL << WCHAR_NUL
+ when REG_BINARY
+ data = data.to_s
+ when REG_DWORD
+ data = API.packdw(data.to_i)
+ when REG_DWORD_BIG_ENDIAN
+ data = [data.to_i].pack("N")
+ when REG_QWORD
+ data = API.packqw(data.to_i)
+ else
+ raise TypeError, "Unsupported type #{type}"
+ end
+ API.SetValue(@hkey, name, type, data, data.bytesize)
+ end
+ end
+ end
+end
diff --git a/lib/chef/monologger.rb b/lib/chef/monologger.rb
index f7d226f82e..e0ca5c5be6 100644
--- a/lib/chef/monologger.rb
+++ b/lib/chef/monologger.rb
@@ -1,5 +1,5 @@
-require 'logger'
-require 'pp'
+require "logger"
+require "pp"
#== MonoLogger
# A subclass of Ruby's stdlib Logger with all the mutex and logrotation stuff
diff --git a/lib/chef/node.rb b/lib/chef/node.rb
index 9823185ede..d7b0bf5948 100644
--- a/lib/chef/node.rb
+++ b/lib/chef/node.rb
@@ -1,9 +1,8 @@
-#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Brown (<cb@opscode.com>)
# Author:: Christopher Walters (<cw@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");
@@ -19,22 +18,22 @@
# limitations under the License.
#
-require 'forwardable'
-require 'chef/config'
-require 'chef/nil_argument'
-require 'chef/mixin/params_validate'
-require 'chef/mixin/from_file'
-require 'chef/mixin/deep_merge'
-require 'chef/dsl/include_attribute'
-require 'chef/dsl/platform_introspection'
-require 'chef/environment'
-require 'chef/rest'
-require 'chef/run_list'
-require 'chef/node/attribute'
-require 'chef/mash'
-require 'chef/json_compat'
-require 'chef/search/query'
-require 'chef/whitelist'
+require "forwardable"
+require "chef/config"
+require "chef/nil_argument"
+require "chef/mixin/params_validate"
+require "chef/mixin/from_file"
+require "chef/mixin/deep_merge"
+require "chef/dsl/include_attribute"
+require "chef/dsl/platform_introspection"
+require "chef/environment"
+require "chef/server_api"
+require "chef/run_list"
+require "chef/node/attribute"
+require "chef/mash"
+require "chef/json_compat"
+require "chef/search/query"
+require "chef/whitelist"
class Chef
class Node
@@ -63,27 +62,49 @@ class Chef
include Chef::Mixin::ParamsValidate
+ NULL_ARG = Object.new
+
# Create a new Chef::Node object.
def initialize(chef_server_rest: nil)
@chef_server_rest = chef_server_rest
@name = nil
- @chef_environment = '_default'
+ @chef_environment = "_default"
@primary_runlist = Chef::RunList.new
@override_runlist = Chef::RunList.new
+ @policy_name = nil
+ @policy_group = nil
+
@attributes = Chef::Node::Attribute.new({}, {}, {}, {})
@run_state = {}
end
+ # after the run_context has been set on the node, go through the cookbook_collection
+ # and setup the node[:cookbooks] attribute so that it is published in the node object
+ def set_cookbook_attribute
+ return unless run_context.cookbook_collection
+ run_context.cookbook_collection.each do |cookbook_name, cookbook|
+ automatic_attrs[:cookbooks][cookbook_name][:version] = cookbook.version
+ end
+ end
+
# Used by DSL
def node
self
end
def chef_server_rest
- @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ # for saving node data we use validate_utf8: false which will not
+ # raise an exception on bad utf8 data, but will replace the bad
+ # characters and render valid JSON.
+ @chef_server_rest ||= Chef::ServerAPI.new(
+ Chef::Config[:chef_server_url],
+ client_name: Chef::Config[:node_name],
+ signing_key_filename: Chef::Config[:client_key],
+ validate_utf8: false,
+ )
end
# Set the name of this Node, or return the current name.
@@ -94,7 +115,7 @@ class Chef
{:name => { :kind_of => String,
:cannot_be => :blank,
:regex => /^[\-[:alnum:]_:.]+$/}
- })
+ },)
@name = arg
else
@name
@@ -115,6 +136,50 @@ class Chef
alias :environment :chef_environment
+ # The `policy_name` for this node. Setting this to a non-nil value will
+ # enable policyfile mode when `chef-client` is run. If set in the config
+ # file or in node json, running `chef-client` will update this value.
+ #
+ # @see Chef::PolicyBuilder::Dynamic
+ # @see Chef::PolicyBuilder::Policyfile
+ #
+ # @param arg [String] the new policy_name value
+ # @return [String] the current policy_name, or the one you just set
+ def policy_name(arg=NULL_ARG)
+ return @policy_name if arg.equal?(NULL_ARG)
+ validate({policy_name: arg}, { policy_name: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
+ @policy_name = arg
+ end
+
+ # A "non-DSL-style" setter for `policy_name`
+ #
+ # @see #policy_name
+ def policy_name=(policy_name)
+ policy_name(policy_name)
+ end
+
+ # The `policy_group` for this node. Setting this to a non-nil value will
+ # enable policyfile mode when `chef-client` is run. If set in the config
+ # file or in node json, running `chef-client` will update this value.
+ #
+ # @see Chef::PolicyBuilder::Dynamic
+ # @see Chef::PolicyBuilder::Policyfile
+ #
+ # @param arg [String] the new policy_group value
+ # @return [String] the current policy_group, or the one you just set
+ def policy_group(arg=NULL_ARG)
+ return @policy_group if arg.equal?(NULL_ARG)
+ validate({policy_group: arg}, { policy_group: { kind_of: [ String, NilClass ], regex: /^[\-:.[:alnum:]_]+$/ } })
+ @policy_group = arg
+ end
+
+ # A "non-DSL-style" setter for `policy_group`
+ #
+ # @see #policy_group
+ def policy_group=(policy_group)
+ policy_group(policy_group)
+ end
+
def attributes
@attributes
end
@@ -244,6 +309,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 +363,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
@@ -304,16 +371,16 @@ class Chef
# Lazy initializer for tags attribute
def tags
- normal[:tags] = [] unless attribute?(:tags)
+ normal[:tags] = Array(normal[:tags])
normal[:tags]
end
- def tag(*tags)
- tags.each do |tag|
- self.normal[:tags].push(tag.to_s) unless self[:tags].include? tag.to_s
+ def tag(*args)
+ args.each do |tag|
+ tags.push(tag.to_s) unless tags.include? tag.to_s
end
- self[:tags]
+ tags
end
# Extracts the run list from +attrs+ and applies it. Returns the remaining attributes
@@ -323,12 +390,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.to_s} from CLI options")
+ Chef::Log.info("Setting the run_list to #{new_run_list} 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
@@ -348,13 +433,14 @@ class Chef
# run_list is mutated? Or perhaps do something smarter like
# on-demand generation of default_attrs and override_attrs,
# invalidated only when run_list is mutated?
- def expand!(data_source = 'server')
+ def expand!(data_source = "server")
expansion = run_list.expand(chef_environment, data_source)
raise Chef::Exceptions::MissingRole, expansion if expansion.errors?
self.tags # make sure they're defined
- automatic_attrs[:recipes] = expansion.recipes
+ automatic_attrs[:recipes] = expansion.recipes.with_duplicate_names
+ automatic_attrs[:expanded_run_list] = expansion.recipes.with_fully_qualified_names_and_version_constraints
automatic_attrs[:roles] = expansion.roles
apply_expansion_attributes(expansion)
@@ -414,15 +500,23 @@ class Chef
result = {
"name" => name,
"chef_environment" => chef_environment,
- 'json_class' => self.class.name,
+ "json_class" => self.class.name,
"automatic" => attributes.automatic,
"normal" => attributes.normal,
"chef_type" => "node",
"default" => attributes.combined_default,
"override" => attributes.combined_override,
#Render correctly for run_list items so malformed json does not result
- "run_list" => @primary_runlist.run_list.map { |item| item.to_s }
+ "run_list" => @primary_runlist.run_list.map { |item| item.to_s },
}
+ # Chef Server rejects node JSON with extra keys; prior to 12.3,
+ # "policy_name" and "policy_group" are unknown; after 12.3 they are
+ # optional, therefore only including them in the JSON if present
+ # maximizes compatibility for most people.
+ unless policy_group.nil? && policy_name.nil?
+ result["policy_name"] = policy_name
+ result["policy_group"] = policy_group
+ end
result
end
@@ -438,6 +532,11 @@ class Chef
# Create a Chef::Node from JSON
def self.json_create(o)
+ from_hash(o)
+ end
+
+ def self.from_hash(o)
+ return o if o.kind_of? Chef::Node
node = new
node.name(o["name"])
node.chef_environment(o["chef_environment"])
@@ -451,9 +550,13 @@ class Chef
if o.has_key?("run_list")
node.run_list.reset!(o["run_list"])
- else
+ elsif o.has_key?("recipes")
o["recipes"].each { |r| node.recipes << r }
end
+
+ node.policy_name = o["policy_name"] if o.has_key?("policy_name")
+ node.policy_group = o["policy_group"] if o.has_key?("policy_group")
+
node
end
@@ -463,7 +566,7 @@ class Chef
Chef::Search::Query.new.search(:node, "chef_environment:#{environment}") {|n| response[n.name] = n unless n.nil?}
response
else
- Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("environments/#{environment}/nodes")
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("environments/#{environment}/nodes")
end
end
@@ -471,18 +574,19 @@ class Chef
if inflate
response = Hash.new
Chef::Search::Query.new.search(:node) do |n|
+ n = Chef::Node.from_hash(n)
response[n.name] = n unless n.nil?
end
response
else
- Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes")
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes")
end
end
def self.find_or_create(node_name)
load(node_name)
rescue Net::HTTPServerException => e
- raise unless e.response.code == '404'
+ raise unless e.response.code == "404"
node = build(node_name)
node.create
end
@@ -496,12 +600,12 @@ class Chef
# Load a node by name
def self.load(name)
- Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes/#{name}")
+ from_hash(Chef::ServerAPI.new(Chef::Config[:chef_server_url]).get("nodes/#{name}"))
end
# Remove this node via the REST API
def destroy
- chef_server_rest.delete_rest("nodes/#{name}")
+ chef_server_rest.delete("nodes/#{name}")
end
# Save this node via the REST API
@@ -510,33 +614,74 @@ class Chef
# so then POST to create.
begin
if Chef::Config[:why_run]
- Chef::Log.warn("In whyrun mode, so NOT performing node save.")
+ Chef::Log.warn("In why-run mode, so NOT performing node save.")
else
- chef_server_rest.put_rest("nodes/#{name}", data_for_save)
+ chef_server_rest.put("nodes/#{name}", data_for_save)
end
rescue Net::HTTPServerException => e
- raise e unless e.response.code == "404"
- chef_server_rest.post_rest("nodes", data_for_save)
+ if e.response.code == "404"
+ chef_server_rest.post("nodes", data_for_save)
+ # Chef Server before 12.3 rejects node JSON with 'policy_name' or
+ # 'policy_group' keys, but 'policy_name' will be detected first.
+ # Backcompat can be removed in 13.0
+ elsif e.response.code == "400" && e.response.body.include?("Invalid key policy_name")
+ save_without_policyfile_attrs
+ else
+ raise
+ end
end
self
end
# Create the node via the REST API
def create
- chef_server_rest.post_rest("nodes", data_for_save)
+ chef_server_rest.post("nodes", data_for_save)
self
+ rescue Net::HTTPServerException => e
+ # Chef Server before 12.3 rejects node JSON with 'policy_name' or
+ # 'policy_group' keys, but 'policy_name' will be detected first.
+ # Backcompat can be removed in 13.0
+ if e.response.code == "400" && e.response.body.include?("Invalid key policy_name")
+ chef_server_rest.post("nodes", data_for_save_without_policyfile_attrs)
+ else
+ raise
+ end
end
def to_s
"node[#{name}]"
end
+ def ==(other)
+ if other.kind_of?(self.class)
+ self.name == other.name
+ else
+ false
+ end
+ end
+
def <=>(other_node)
self.name <=> other_node.name
end
private
+ def save_without_policyfile_attrs
+ trimmed_data = data_for_save_without_policyfile_attrs
+
+ chef_server_rest.put("nodes/#{name}", trimmed_data)
+ rescue Net::HTTPServerException => e
+ raise e unless e.response.code == "404"
+ chef_server_rest.post("nodes", trimmed_data)
+ end
+
+ def data_for_save_without_policyfile_attrs
+ data_for_save.tap do |trimmed_data|
+ trimmed_data.delete("policy_name")
+ trimmed_data.delete("policy_group")
+ end
+ end
+
def data_for_save
data = for_json
["automatic", "default", "normal", "override"].each do |level|
diff --git a/lib/chef/node/attribute.rb b/lib/chef/node/attribute.rb
index 45f2ef01ee..aa2fcdaa8e 100644
--- a/lib/chef/node/attribute.rb
+++ b/lib/chef/node/attribute.rb
@@ -1,7 +1,7 @@
#--
# Author:: Adam Jacob (<adam@opscode.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");
@@ -17,10 +17,10 @@
# limitations under the License.
#
-require 'chef/node/immutable_collections'
-require 'chef/node/attribute_collections'
-require 'chef/mixin/deep_merge'
-require 'chef/log'
+require "chef/node/immutable_collections"
+require "chef/node/attribute_collections"
+require "chef/mixin/deep_merge"
+require "chef/log"
class Chef
class Node
@@ -48,21 +48,21 @@ class Chef
:@role_override,
:@env_override,
:@force_override,
- :@automatic
+ :@automatic,
].freeze
DEFAULT_COMPONENTS = [
:@default,
:@env_default,
:@role_default,
- :@force_default
+ :@force_default,
]
OVERRIDE_COMPONENTS = [
:@override,
:@role_override,
:@env_override,
- :@force_override
+ :@force_override,
]
[:all?,
@@ -482,7 +482,7 @@ class Chef
def inspect
"#<#{self.class} " << (COMPONENTS + [:@merged_attributes, :@properties]).map{|iv|
"#{iv}=#{instance_variable_get(iv).inspect}"
- }.join(', ') << ">"
+ }.join(", ") << ">"
end
def set_unless?
diff --git a/lib/chef/node/attribute_collections.rb b/lib/chef/node/attribute_collections.rb
index b912904534..6a45f49140 100644
--- a/lib/chef/node/attribute_collections.rb
+++ b/lib/chef/node/attribute_collections.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Copyright:: Copyright (c) 2012-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -54,7 +54,7 @@ class Chef
:sort!,
:sort_by!,
:uniq!,
- :unshift
+ :unshift,
]
# For all of the methods that may mutate an Array, we override them to
@@ -117,7 +117,7 @@ class Chef
:reject!,
:replace,
:select!,
- :shift
+ :shift,
]
# For all of the mutating methods on Mash, override them so that they
@@ -148,7 +148,7 @@ class Chef
def []=(key, value)
root.top_level_breadcrumb ||= key
- if set_unless? && key?(key)
+ if set_unless? && key?(key) && !self[key].nil?
self[key]
else
root.reset_cache(root.top_level_breadcrumb)
diff --git a/lib/chef/node/immutable_collections.rb b/lib/chef/node/immutable_collections.rb
index 0e2800641a..c092c8cacc 100644
--- a/lib/chef/node/immutable_collections.rb
+++ b/lib/chef/node/immutable_collections.rb
@@ -63,7 +63,7 @@ class Chef
:sort!,
:sort_by!,
:uniq!,
- :unshift
+ :unshift,
]
def initialize(array_data)
@@ -143,7 +143,7 @@ class Chef
:reject!,
:replace,
:select!,
- :shift
+ :shift,
]
def initialize(mash_data)
diff --git a/lib/chef/node_map.rb b/lib/chef/node_map.rb
index 2ca6d9ba17..55b5942a21 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/org.rb b/lib/chef/org.rb
index 41d74b6186..33a986dc3b 100644
--- a/lib/chef/org.rb
+++ b/lib/chef/org.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/json_compat'
-require 'chef/mixin/params_validate'
-require 'chef/rest'
+require "chef/json_compat"
+require "chef/mixin/params_validate"
+require "chef/server_api"
class Chef
class Org
@@ -27,7 +27,7 @@ class Chef
def initialize(name)
@name = name
- @full_name = ''
+ @full_name = ""
# The Chef API returns the private key of the validator
# client on create
@private_key = nil
@@ -35,7 +35,7 @@ class Chef
end
def chef_rest
- @chef_rest ||= Chef::REST.new(Chef::Config[:chef_server_root])
+ @chef_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_root])
end
def name(arg=nil)
@@ -61,7 +61,7 @@ class Chef
def to_hash
result = {
"name" => @name,
- "full_name" => @full_name
+ "full_name" => @full_name,
}
result["private_key"] = @private_key if @private_key
result["guid"] = @guid if @guid
@@ -74,18 +74,18 @@ class Chef
def create
payload = {:name => self.name, :full_name => self.full_name}
- new_org = chef_rest.post_rest("organizations", payload)
+ new_org = chef_rest.post("organizations", payload)
Chef::Org.from_hash(self.to_hash.merge(new_org))
end
def update
payload = {:name => self.name, :full_name => self.full_name}
- new_org = chef_rest.put_rest("organizations/#{name}", payload)
+ new_org = chef_rest.put("organizations/#{name}", payload)
Chef::Org.from_hash(self.to_hash.merge(new_org))
end
def destroy
- chef_rest.delete_rest("organizations/#{@name}")
+ chef_rest.delete("organizations/#{@name}")
end
def save
@@ -102,21 +102,21 @@ class Chef
def associate_user(username)
request_body = {:user => username}
- response = chef_rest.post_rest "organizations/#{@name}/association_requests", request_body
+ response = chef_rest.post "organizations/#{@name}/association_requests", request_body
association_id = response["uri"].split("/").last
- chef_rest.put_rest "users/#{username}/association_requests/#{association_id}", { :response => 'accept' }
+ chef_rest.put "users/#{username}/association_requests/#{association_id}", { :response => "accept" }
end
def dissociate_user(username)
- chef_rest.delete_rest "organizations/#{name}/users/#{username}"
+ chef_rest.delete "organizations/#{name}/users/#{username}"
end
# Class methods
def self.from_hash(org_hash)
- org = Chef::Org.new(org_hash['name'])
- org.full_name org_hash['full_name']
- org.private_key org_hash['private_key'] if org_hash.key?('private_key')
- org.guid org_hash['guid'] if org_hash.key?('guid')
+ org = Chef::Org.new(org_hash["name"])
+ org.full_name org_hash["full_name"]
+ org.private_key org_hash["private_key"] if org_hash.key?("private_key")
+ org.guid org_hash["guid"] if org_hash.key?("guid")
org
end
@@ -129,12 +129,12 @@ class Chef
end
def self.load(org_name)
- response = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest("organizations/#{org_name}")
+ response = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("organizations/#{org_name}")
Chef::Org.from_hash(response)
end
def self.list(inflate=false)
- orgs = Chef::REST.new(Chef::Config[:chef_server_root]).get_rest('organizations')
+ orgs = Chef::ServerAPI.new(Chef::Config[:chef_server_root]).get("organizations")
if inflate
orgs.inject({}) do |org_map, (name, _url)|
org_map[name] = Chef::Org.load(name)
diff --git a/lib/chef/platform.rb b/lib/chef/platform.rb
index 841aa1b46c..e411aa5b92 100644
--- a/lib/chef/platform.rb
+++ b/lib/chef/platform.rb
@@ -17,8 +17,8 @@
#
# Order of these headers is important: query helpers is needed by many things
-require 'chef/platform/query_helpers'
-require 'chef/platform/provider_mapping'
+require "chef/platform/query_helpers"
+require "chef/platform/provider_mapping"
class Chef
class Platform
diff --git a/lib/chef/platform/handler_map.rb b/lib/chef/platform/handler_map.rb
new file mode 100644
index 0000000000..48e118d599
--- /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..004aa01c16
--- /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..ecd1da1f1e
--- /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 0d7285729f..235fd4d471 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -16,16 +16,11 @@
# limitations under the License.
#
-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'
-
+require "chef/log"
+require "chef/exceptions"
+require "chef/mixin/params_validate"
+require "chef/version_constraint/platform"
+require "chef/provider"
class Chef
class Platform
@@ -34,267 +29,7 @@ class Chef
attr_writer :platforms
def platforms
- @platforms ||= begin
- require 'chef/providers'
-
- {
- :freebsd => {
- :default => {
- :group => Chef::Provider::Group::Pw,
- :user => Chef::Provider::User::Pw,
- }
- },
- :ubuntu => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- },
- ">= 11.10" => {
- :ifconfig => Chef::Provider::Ifconfig::Debian
- }
- # Chef::Provider::Service::Upstart is a candidate to be used in
- # ubuntu versions >= 13.10 but it currently requires all the
- # services to have an entry under /etc/init. We need to update it
- # to use the service ctl apis in order to migrate to using it on
- # ubuntu >= 13.10.
- },
- :gcel => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- }
- },
- :linaro => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- }
- },
- :raspbian => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- }
- },
- :linuxmint => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Upstart,
- }
- },
- :debian => {
- :default => {
- :package => Chef::Provider::Package::Apt,
- :service => Chef::Provider::Service::Debian,
- },
- ">= 6.0" => {
- :service => Chef::Provider::Service::Insserv
- },
- ">= 7.0" => {
- :ifconfig => Chef::Provider::Ifconfig::Debian
- }
- },
- :xenserver => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- }
- },
- :xcp => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- }
- },
- :centos => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :amazon => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- }
- },
- :scientific => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :fedora => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 15" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :opensuse => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Zypper,
- :group => Chef::Provider::Group::Suse
- },
- # Only OpenSuSE 12.3+ should use the Usermod group provider:
- ">= 12.3" => {
- :group => Chef::Provider::Group::Usermod
- }
- },
- :suse => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Zypper,
- :group => Chef::Provider::Group::Gpasswd
- },
- "< 12.0" => {
- :group => Chef::Provider::Group::Suse,
- :service => Chef::Provider::Service::Redhat
- }
- },
- :oracle => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :redhat => {
- :default => {
- :service => Chef::Provider::Service::Systemd,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- },
- "< 7" => {
- :service => Chef::Provider::Service::Redhat
- }
- },
- :ibm_powerkvm => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :cloudlinux => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :parallels => {
- :default => {
- :service => Chef::Provider::Service::Redhat,
- :package => Chef::Provider::Package::Yum,
- :ifconfig => Chef::Provider::Ifconfig::Redhat
- }
- },
- :gentoo => {
- :default => {
- :package => Chef::Provider::Package::Portage,
- :service => Chef::Provider::Service::Gentoo,
- }
- },
- :arch => {
- :default => {
- :package => Chef::Provider::Package::Pacman,
- :service => Chef::Provider::Service::Systemd,
- }
- },
- :solaris => {},
- :openindiana => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :opensolaris => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :nexentacore => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Solaris,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :omnios => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- }
- },
- :solaris2 => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Ips,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- },
- "< 5.11" => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::Solaris,
- :group => Chef::Provider::Group::Usermod,
- :user => Chef::Provider::User::Solaris,
- }
- },
- :smartos => {
- :default => {
- :mount => Chef::Provider::Mount::Solaris,
- :package => Chef::Provider::Package::SmartOS,
- :group => Chef::Provider::Group::Usermod
- }
- },
- :hpux => {
- :default => {
- :group => Chef::Provider::Group::Usermod
- }
- },
- :aix => {
- :default => {
- :group => Chef::Provider::Group::Aix,
- :mount => Chef::Provider::Mount::Aix,
- :ifconfig => Chef::Provider::Ifconfig::Aix,
- :package => Chef::Provider::Package::Aix,
- :user => Chef::Provider::User::Aix,
- :service => Chef::Provider::Service::Aix
- }
- },
- :exherbo => {
- :default => {
- :package => Chef::Provider::Package::Paludis,
- :service => Chef::Provider::Service::Systemd,
- }
- },
- :default => {
- :mount => Chef::Provider::Mount::Mount,
- :user => Chef::Provider::User::Useradd,
- :group => Chef::Provider::Group::Gpasswd,
- :ifconfig => Chef::Provider::Ifconfig,
- }
- }
- end
+ @platforms ||= { default: {} }
end
include Chef::Mixin::ParamsValidate
@@ -304,7 +39,7 @@ class Chef
name_sym = name
if name.kind_of?(String)
- name.downcase!
+ name = name.downcase
name.gsub!(/\s/, "_")
name_sym = name.to_sym
end
@@ -318,15 +53,13 @@ class Chef
begin
version_constraint = Chef::VersionConstraint::Platform.new(platform_version)
if version_constraint.include?(version)
- Chef::Log.debug("Platform #{name.to_s} version #{version} found")
+ Chef::Log.debug("Platform #{name} version #{version} found")
provider_map.merge!(provider)
end
rescue Chef::Exceptions::InvalidPlatformVersion
Chef::Log.debug("Chef::Version::Comparable does not know how to parse the platform version: #{version}")
end
end
- else
- Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)")
end
provider_map
end
@@ -386,12 +119,12 @@ class Chef
:required => false,
},
:resource => {
- :kind_of => Symbol,
+ :kind_of => Symbol
},
:provider => {
- :kind_of => [ String, Symbol, Class ],
- }
- }
+ :kind_of => [ String, Symbol, Class ]
+ },
+ },
)
if args.has_key?(:platform)
if args.has_key?(:version)
@@ -443,7 +176,7 @@ class Chef
platform_provider(platform, version, resource_type) ||
resource_matching_provider(platform, version, resource_type)
- raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
+ raise Chef::Exceptions::ProviderNotFound, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil?
provider_klass
end
@@ -460,16 +193,20 @@ class Chef
pmap.has_key?(rtkey) ? pmap[rtkey] : nil
end
+ include Chef::Mixin::ConvertToClassName
+
def resource_matching_provider(platform, version, resource_type)
if resource_type.kind_of?(Chef::Resource)
- begin
- Chef::Provider.const_get(resource_type.class.to_s.split('::').last)
- rescue NameError
- nil
+ class_name = resource_type.class.name ? resource_type.class.name.split("::").last :
+ convert_to_class_name(resource_type.resource_name.to_s)
+
+ if Chef::Provider.const_defined?(class_name)
+ Chef::Log.warn("Class Chef::Provider::#{class_name} does not declare 'provides #{convert_to_snake_case(class_name).to_sym.inspect}'.")
+ Chef::Log.warn("This will no longer work in Chef 13: you must use 'provides' to use the resource's DSL.")
+ return Chef::Provider.const_get(class_name)
end
- else
- nil
end
+ nil
end
end
diff --git a/lib/chef/platform/provider_priority_map.rb b/lib/chef/platform/provider_priority_map.rb
index 2517f46dfd..0c8a728618 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 f7c85fbe23..3abd1a2a8b 100644
--- a/lib/chef/platform/query_helpers.rb
+++ b/lib/chef/platform/query_helpers.rb
@@ -21,39 +21,93 @@ 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
+ require "wmi-lite/wmi"
wmi = WmiLite::Wmi.new
- host = wmi.first_of('Win32_OperatingSystem')
- is_server_2003 = (host['version'] && host['version'].start_with?("5.2"))
+ host = wmi.first_of("Win32_OperatingSystem")
+ is_server_2003 = (host["version"] && host["version"].start_with?("5.2"))
is_server_2003
end
+ def windows_nano_server?
+ return false unless windows?
+ require "win32/registry"
+
+ # This method may be called before ohai runs (e.g., it may be used to
+ # determine settings in config.rb). Chef::Win32::Registry.new uses
+ # node attributes to verify the machine architecture which aren't
+ # accessible before ohai runs.
+ nano = nil
+ key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels"
+ access = ::Win32::Registry::KEY_QUERY_VALUE | 0x0100 # nano is 64-bit only
+ begin
+ ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg|
+ nano = reg["NanoServer"]
+ end
+ rescue ::Win32::Registry::Error
+ # If accessing the registry key failed, then we're probably not on
+ # nano. Fail through.
+ end
+ return nano == 1
+ end
+
+ def supports_msi?
+ return false unless windows?
+ require "win32/registry"
+
+ key = "System\\CurrentControlSet\\Services\\msiserver"
+ access = ::Win32::Registry::KEY_QUERY_VALUE
+
+ begin
+ ::Win32::Registry::HKEY_LOCAL_MACHINE.open(key, access) do |reg|
+ true
+ end
+ rescue ::Win32::Registry::Error
+ false
+ end
+ end
+
+ def supports_powershell_execution_bypass?(node)
+ node[:languages] && node[:languages][:powershell] &&
+ node[:languages][:powershell][:version].to_i >= 3
+ end
+
def supports_dsc?(node)
node[:languages] && node[:languages][:powershell] &&
node[:languages][:powershell][:version].to_i >= 4
end
def supports_dsc_invoke_resource?(node)
- require 'rubygems'
supports_dsc?(node) &&
- Gem::Version.new(node[:languages][:powershell][:version]) >=
- Gem::Version.new("5.0.10018.0")
+ supported_powershell_version?(node, "5.0.10018.0")
+ end
+
+ def supports_refresh_mode_enabled?(node)
+ supported_powershell_version?(node, "5.0.10586.0")
+ end
+
+ def dsc_refresh_mode_disabled?(node)
+ require "chef/util/powershell/cmdlet"
+ cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object)
+ metadata = cmdlet.run!.return_value
+ metadata["RefreshMode"] == "Disabled"
end
+
+
+ def supported_powershell_version?(node, version_string)
+ return false unless node[:languages] && node[:languages][:powershell]
+ require "rubygems"
+ Gem::Version.new(node[:languages][:powershell][:version]) >=
+ Gem::Version.new(version_string)
+ end
+
end
end
end
diff --git a/lib/chef/platform/rebooter.rb b/lib/chef/platform/rebooter.rb
index b46f0e394c..83d4730075 100644
--- a/lib/chef/platform/rebooter.rb
+++ b/lib/chef/platform/rebooter.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/dsl/reboot_pending'
-require 'chef/log'
-require 'chef/platform'
+require "chef/dsl/reboot_pending"
+require "chef/log"
+require "chef/platform"
class Chef
class Platform
@@ -31,12 +31,12 @@ class Chef
reboot_info = node.run_context.reboot_info
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]}\""
- else
- # probably Linux-only.
- "shutdown -r +#{reboot_info[:delay_mins]} \"#{reboot_info[:reason]}\""
- end
+ # should this do /f as well? do we then need a minimum delay to let apps quit?
+ "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]}\""
+ end
Chef::Log.warn "Rebooting server at a recipe's request. Details: #{reboot_info.inspect}"
shell_out!(cmd)
diff --git a/lib/chef/platform/resource_handler_map.rb b/lib/chef/platform/resource_handler_map.rb
new file mode 100644
index 0000000000..166ee73173
--- /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..1871dcd5c6
--- /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..1a7e739169 100644
--- a/lib/chef/platform/service_helpers.rb
+++ b/lib/chef/platform/service_helpers.rb
@@ -16,18 +16,12 @@
# limitations under the License.
#
-# XXX: mixing shellout into a mixin into classes has to be code smell
-require 'chef/mixin/shell_out'
-require 'chef/mixin/which'
+require "chef/chef_class"
class Chef
class Platform
class ServiceHelpers
class << self
-
- include Chef::Mixin::ShellOut
- include Chef::Mixin::Which
-
# This helper is mostly used to sort out the mess of different
# linux mechanisms that can be used to start services. It does
# not necessarily need to linux-specific, but currently all our
@@ -42,60 +36,59 @@ class Chef
# different services is NOT a design concern of this module.
#
def service_resource_providers
- service_resource_providers = []
+ providers = []
- if ::File.exist?("/usr/sbin/update-rc.d")
- service_resource_providers << :debian
+ if ::File.exist?(Chef.path_to("/usr/sbin/update-rc.d"))
+ providers << :debian
end
- if ::File.exist?("/usr/sbin/invoke-rc.d")
- service_resource_providers << :invokercd
+ if ::File.exist?(Chef.path_to("/usr/sbin/invoke-rc.d"))
+ providers << :invokercd
end
- if ::File.exist?("/sbin/insserv")
- service_resource_providers << :insserv
+ if ::File.exist?(Chef.path_to("/sbin/initctl"))
+ providers << :upstart
end
- # debian >= 6.0 has /etc/init but does not have upstart
- if ::File.exist?("/etc/init") && ::File.exist?("/sbin/start")
- service_resource_providers << :upstart
+ if ::File.exist?(Chef.path_to("/sbin/insserv"))
+ providers << :insserv
end
- if ::File.exist?("/sbin/chkconfig")
- service_resource_providers << :redhat
+ if systemd_is_init?
+ providers << :systemd
end
- if systemd_sanity_check?
- service_resource_providers << :systemd
+ if ::File.exist?(Chef.path_to("/sbin/chkconfig"))
+ providers << :redhat
end
- service_resource_providers
+ providers
end
def config_for_service(service_name)
configs = []
- if ::File.exist?("/etc/init.d/#{service_name}")
+ if ::File.exist?(Chef.path_to("/etc/init.d/#{service_name}"))
configs << :initd
end
- if ::File.exist?("/etc/init/#{service_name}.conf")
+ if ::File.exist?(Chef.path_to("/etc/init/#{service_name}.conf"))
configs << :upstart
end
- if ::File.exist?("/etc/xinetd.d/#{service_name}")
+ if ::File.exist?(Chef.path_to("/etc/xinetd.d/#{service_name}"))
configs << :xinetd
end
- if ::File.exist?("/etc/rc.d/#{service_name}")
+ if ::File.exist?(Chef.path_to("/etc/rc.d/#{service_name}"))
configs << :etc_rcd
end
- if ::File.exist?("/usr/local/etc/rc.d/#{service_name}")
+ if ::File.exist?(Chef.path_to("/usr/local/etc/rc.d/#{service_name}"))
configs << :usr_local_etc_rcd
end
- if systemd_sanity_check? && platform_has_systemd_unit?(service_name)
+ if has_systemd_service_unit?(service_name) || has_systemd_unit?(service_name)
configs << :systemd
end
@@ -104,37 +97,24 @@ class Chef
private
- def systemctl_path
- if @systemctl_path.nil?
- @systemctl_path = which("systemctl")
- end
- @systemctl_path
- end
-
- def systemd_sanity_check?
- systemctl_path && File.exist?("/proc/1/comm") && File.open("/proc/1/comm").gets.chomp == "systemd"
+ def systemd_is_init?
+ ::File.exist?(Chef.path_to("/proc/1/comm")) &&
+ ::File.open(Chef.path_to("/proc/1/comm")).gets.chomp == "systemd"
end
- def extract_systemd_services(command)
- output = shell_out!(command).stdout
- # first line finds e.g. "sshd.service"
- services = []
- output.each_line do |line|
- fields = line.split
- services << fields[0] if fields[1] == "loaded" || fields[1] == "not-found"
+ def has_systemd_service_unit?(svc_name)
+ %w{ /etc /usr/lib /lib /run }.any? do |load_path|
+ ::File.exist?(
+ Chef.path_to("#{load_path}/systemd/system/#{svc_name.gsub(/@.*$/, '@')}.service")
+ )
end
- # this splits off the suffix after the last dot to return "sshd"
- services += services.select {|s| s.match(/\.service$/) }.map { |s| s.sub(/(.*)\.service$/, '\1') }
- rescue Mixlib::ShellOut::ShellCommandFailed
- false
end
- def platform_has_systemd_unit?(service_name)
- services = extract_systemd_services("#{systemctl_path} --all") +
- extract_systemd_services("#{systemctl_path} list-unit-files")
- services.include?(service_name)
- rescue Mixlib::ShellOut::ShellCommandFailed
- false
+ def has_systemd_unit?(svc_name)
+ # TODO: stop supporting non-service units with service resource
+ %w{ /etc /usr/lib /lib /run }.any? do |load_path|
+ ::File.exist?(Chef.path_to("#{load_path}/systemd/system/#{svc_name}"))
+ end
end
end
end
diff --git a/lib/chef/policy_builder.rb b/lib/chef/policy_builder.rb
index 136b2853b0..34816c44a0 100644
--- a/lib/chef/policy_builder.rb
+++ b/lib/chef/policy_builder.rb
@@ -16,8 +16,9 @@
# limitations under the License.
#
-require 'chef/policy_builder/expand_node_object'
-require 'chef/policy_builder/policyfile'
+require "chef/policy_builder/expand_node_object"
+require "chef/policy_builder/policyfile"
+require "chef/policy_builder/dynamic"
class Chef
@@ -37,13 +38,5 @@ class Chef
# * cookbook_hash is stored in run_context
module PolicyBuilder
- def self.strategy
- if Chef::Config[:use_policyfile]
- Policyfile
- else
- ExpandNodeObject
- end
- end
-
end
end
diff --git a/lib/chef/policy_builder/dynamic.rb b/lib/chef/policy_builder/dynamic.rb
new file mode 100644
index 0000000000..6968aac49f
--- /dev/null
+++ b/lib/chef/policy_builder/dynamic.rb
@@ -0,0 +1,185 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "forwardable"
+
+require "chef/log"
+require "chef/run_context"
+require "chef/config"
+require "chef/node"
+require "chef/exceptions"
+
+class Chef
+ module PolicyBuilder
+
+ # PolicyBuilder that selects either a Policyfile or non-Policyfile
+ # implementation based on the content of the node object.
+ class Dynamic
+
+ extend Forwardable
+
+ attr_reader :node
+ attr_reader :node_name
+ attr_reader :ohai_data
+ attr_reader :json_attribs
+ attr_reader :override_runlist
+ attr_reader :events
+
+ def initialize(node_name, ohai_data, json_attribs, override_runlist, events)
+ @implementation = nil
+
+ @node_name = node_name
+ @ohai_data = ohai_data
+ @json_attribs = json_attribs
+ @override_runlist = override_runlist
+ @events = events
+
+ @node = nil
+ end
+
+ ## PolicyBuilder API ##
+
+ # Loads the node state from the server, then picks the correct
+ # implementation class based on the node and json_attribs.
+ #
+ # Calls #finish_load_node on the implementation object to complete the
+ # loading process. All subsequent lifecycle calls are delegated.
+ #
+ # @return [Chef::Node] the loaded node.
+ def load_node
+ events.node_load_start(node_name, config)
+ Chef::Log.debug("Building node object for #{node_name}")
+
+ @node =
+ if Chef::Config[:solo]
+ Chef::Node.build(node_name)
+ else
+ Chef::Node.find_or_create(node_name)
+ end
+ select_implementation(node)
+ implementation.finish_load_node(node)
+ node
+ rescue Exception => e
+ events.node_load_failed(node_name, e, config)
+ raise
+ end
+
+ ## Delegated Public API Methods ##
+
+ ### Accessors ###
+
+ def_delegator :implementation, :original_runlist
+ def_delegator :implementation, :run_context
+ def_delegator :implementation, :run_list_expansion
+
+ ### Lifecycle Methods ###
+
+ # @!method build_node
+ #
+ # Applies external attributes (e.g., from JSON file, environment,
+ # policyfile, etc.) and determines the correct expanded run list for the
+ # run.
+ #
+ # @return [Chef::Node]
+ def_delegator :implementation, :build_node
+
+ # @!method setup_run_context
+ #
+ # Synchronizes cookbooks and initializes the run context object for the
+ # run.
+ #
+ # @return [Chef::RunContext]
+ def_delegator :implementation, :setup_run_context
+
+ # @!method expanded_run_list
+ #
+ # Resolves the run list to a form containing only recipes and sets the
+ # `roles` and `recipes` automatic attributes on the node.
+ #
+ # @return [#recipes, #roles] A RunListExpansion or duck-type.
+ def_delegator :implementation, :expand_run_list
+
+ # @!method sync_cookbooks
+ #
+ # Synchronizes cookbooks. In a normal chef-client run, this is handled by
+ # #setup_run_context, but may be called directly in some circumstances.
+ #
+ # @return [Hash{String => Chef::CookbookManifest}] A map of
+ # CookbookManifest objects by cookbook name.
+ def_delegator :implementation, :sync_cookbooks
+
+ # @!method temporary_policy?
+ #
+ # Indicates whether the policy is temporary, which means an
+ # override_runlist was provided. Chef::Client uses this to decide whether
+ # to do the final node save at the end of the run or not.
+ #
+ # @return [true,false]
+ def_delegator :implementation, :temporary_policy?
+
+ ## Internal Public API ##
+
+ # Returns the selected implementation, or raises if not set. The
+ # implementation is set when #load_node is called.
+ #
+ # @return [PolicyBuilder::Policyfile, PolicyBuilder::ExpandNodeObject]
+ def implementation
+ @implementation or raise Exceptions::InvalidPolicybuilderCall, "#load_node must be called before other policy builder methods"
+ end
+
+ # @api private
+ #
+ # Sets the implementation based on the content of the node, node JSON
+ # (i.e., the `-j JSON_FILE` data), and config. This is only public for
+ # testing purposes; production code should call #load_node instead.
+ def select_implementation(node)
+ if policyfile_set_in_config? ||
+ policyfile_attribs_in_node_json? ||
+ node_has_policyfile_attrs?(node) ||
+ policyfile_compat_mode_config?
+ @implementation = Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events)
+ else
+ @implementation = ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events)
+ end
+ end
+
+ def config
+ Chef::Config
+ end
+
+ private
+
+ def node_has_policyfile_attrs?(node)
+ node.policy_name || node.policy_group
+ end
+
+ def policyfile_attribs_in_node_json?
+ json_attribs.key?("policy_name") || json_attribs.key?("policy_group")
+ end
+
+ def policyfile_set_in_config?
+ config[:use_policyfile] || config[:policy_name] || config[:policy_group]
+ end
+
+ def policyfile_compat_mode_config?
+ config[:deployment_group] && !config[:policy_document_native_api]
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/policy_builder/expand_node_object.rb b/lib/chef/policy_builder/expand_node_object.rb
index 2ee8a23258..6bc5300a35 100644
--- a/lib/chef/policy_builder/expand_node_object.rb
+++ b/lib/chef/policy_builder/expand_node_object.rb
@@ -19,11 +19,12 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/rest'
-require 'chef/run_context'
-require 'chef/config'
-require 'chef/node'
+require "chef/log"
+require "chef/server_api"
+require "chef/run_context"
+require "chef/config"
+require "chef/node"
+require "chef/chef_class"
class Chef
module PolicyBuilder
@@ -32,6 +33,9 @@ class Chef
# expands the run_list on a node object and then queries the chef-server
# to find the correct set of cookbooks, given version constraints of the
# node's environment.
+ #
+ # Note that this class should only be used via PolicyBuilder::Dynamic and
+ # not instantiated directly.
class ExpandNodeObject
attr_reader :events
@@ -54,20 +58,36 @@ class Chef
@run_list_expansion = nil
end
+ # This method injects the run_context and into the Chef class.
+ #
+ # NOTE: This is duplicated with the Policyfile implementation. If
+ # it gets any more complicated, it needs to be moved elsewhere.
+ #
+ # @param run_context [Chef::RunContext] the run_context to inject
+ def setup_chef_class(run_context)
+ 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])
cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
cl.load_cookbooks
cookbook_collection = Chef::CookbookCollection.new(cl)
+ cookbook_collection.validate!
run_context = Chef::RunContext.new(node, cookbook_collection, @events)
else
Chef::Cookbook::FileVendor.fetch_from_remote(api_service)
cookbook_hash = sync_cookbooks
cookbook_collection = Chef::CookbookCollection.new(cookbook_hash)
+ cookbook_collection.validate!
run_context = Chef::RunContext.new(node, cookbook_collection, @events)
end
+ # 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)
@@ -79,25 +99,36 @@ class Chef
run_context
end
-
- # In client-server operation, loads the node state from the server. In
- # chef-solo operation, builds a new node object.
+ # DEPRECATED: As of Chef 12.5, chef selects either policyfile mode or
+ # "expand node" mode dynamically, based on the content of the node
+ # object, first boot JSON, and config. This happens in
+ # PolicyBuilder::Dynamic, which selects the implementation during
+ # #load_node and then delegates to either ExpandNodeObject or Policyfile
+ # implementations as appropriate. Tools authors should update their code
+ # to create a PolicyBuilder::Dynamc policy builder and allow it to select
+ # the proper implementation.
def load_node
- events.node_load_start(node_name, Chef::Config)
+ Chef.log_deprecation("ExpandNodeObject#load_node is deprecated. Please use Chef::PolicyBuilder::Dynamic instead of using ExpandNodeObject directly")
+
+ events.node_load_start(node_name, config)
Chef::Log.debug("Building node object for #{node_name}")
- if Chef::Config[:solo]
- @node = Chef::Node.build(node_name)
- else
- @node = Chef::Node.find_or_create(node_name)
- end
+ @node =
+ if Chef::Config[:solo]
+ Chef::Node.build(node_name)
+ else
+ Chef::Node.find_or_create(node_name)
+ end
+ finish_load_node(node)
+ node
rescue Exception => e
- # TODO: wrap this exception so useful error info can be given to the
- # user.
- events.node_load_failed(node_name, e, Chef::Config)
+ events.node_load_failed(node_name, e, config)
raise
end
+ def finish_load_node(node)
+ @node = node
+ end
# Applies environment, external JSON attributes, and override run list to
# the node, Then expands the run_list.
@@ -125,6 +156,7 @@ class Chef
Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config)
+ events.run_list_expanded(@run_list_expansion)
node
end
@@ -132,10 +164,10 @@ class Chef
# Expands the node's run list. Stores the run_list_expansion object for later use.
def expand_run_list
@run_list_expansion = if Chef::Config[:solo]
- node.expand!('disk')
- else
- node.expand!('server')
- end
+ node.expand!("disk")
+ else
+ node.expand!("server")
+ end
# @run_list_expansion is a RunListExpansion.
#
@@ -166,7 +198,12 @@ class Chef
begin
events.cookbook_resolution_start(@expanded_run_list_with_versions)
cookbook_hash = api_service.post("environments/#{node.chef_environment}/cookbook_versions",
- {:run_list => @expanded_run_list_with_versions})
+ {:run_list => @expanded_run_list_with_versions})
+
+ cookbook_hash = cookbook_hash.inject({}) do |memo, (key, value)|
+ memo[key] = Chef::CookbookVersion.from_hash(value)
+ memo
+ end
rescue Exception => e
# TODO: wrap/munge exception to provide helpful error output
events.cookbook_resolution_failed(@expanded_run_list_with_versions, e)
@@ -212,7 +249,7 @@ class Chef
def runlist_override_sanity_check!
# Convert to array and remove whitespace
if override_runlist.is_a?(String)
- @override_runlist = override_runlist.split(',').map { |e| e.strip }
+ @override_runlist = override_runlist.split(",").map { |e| e.strip }
end
@override_runlist = [override_runlist].flatten.compact
override_runlist.map! do |item|
@@ -225,7 +262,7 @@ class Chef
end
def api_service
- @api_service ||= Chef::REST.new(config[:chef_server_url])
+ @api_service ||= Chef::ServerAPI.new(config[:chef_server_url])
end
def config
diff --git a/lib/chef/policy_builder/policyfile.rb b/lib/chef/policy_builder/policyfile.rb
index 0c7c07f9f3..5c782d88fc 100644
--- a/lib/chef/policy_builder/policyfile.rb
+++ b/lib/chef/policy_builder/policyfile.rb
@@ -19,11 +19,11 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/rest'
-require 'chef/run_context'
-require 'chef/config'
-require 'chef/node'
+require "chef/log"
+require "chef/run_context"
+require "chef/config"
+require "chef/node"
+require "chef/server_api"
class Chef
module PolicyBuilder
@@ -68,22 +68,20 @@ class Chef
@node = nil
- Chef::Log.warn("Using experimental Policyfile feature")
-
if Chef::Config[:solo]
- raise UnsupportedFeature, "Policyfile does not support chef-solo at this time."
+ raise UnsupportedFeature, "Policyfile does not support chef-solo. Use chef-client local mode instead."
end
if override_runlist
- raise UnsupportedFeature, "Policyfile does not support override run lists at this time"
+ raise UnsupportedFeature, "Policyfile does not support override run lists. Use named run_lists instead."
end
if json_attribs && json_attribs.key?("run_list")
- raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data at this time"
+ raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data."
end
if Chef::Config[:environment] && !Chef::Config[:environment].chomp.empty?
- raise UnsupportedFeature, "Policyfile does not work with Chef Environments"
+ raise UnsupportedFeature, "Policyfile does not work with Chef Environments."
end
end
@@ -112,17 +110,11 @@ class Chef
## PolicyBuilder API ##
- # Loads the node state from the server.
- def load_node
- events.node_load_start(node_name, Chef::Config)
- Chef::Log.debug("Building node object for #{node_name}")
-
- @node = Chef::Node.find_or_create(node_name)
+ def finish_load_node(node)
+ @node = node
+ select_policy_name_and_group
validate_policyfile
- node
- rescue Exception => e
- events.node_load_failed(node_name, e, Chef::Config)
- raise
+ events.policyfile_loaded(policy)
end
# Applies environment, external JSON attributes, and override run list to
@@ -153,25 +145,43 @@ class Chef
raise
end
+ # Synchronizes cookbooks and initializes the run context object for the
+ # run.
+ #
+ # @return [Chef::RunContext]
def setup_run_context(specific_recipes=nil)
Chef::Cookbook::FileVendor.fetch_from_remote(http_api)
sync_cookbooks
cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync)
+ cookbook_collection.validate!
run_context = Chef::RunContext.new(node, cookbook_collection, events)
+ setup_chef_class(run_context)
+
run_context.load(run_list_expansion_ish)
+ setup_chef_class(run_context)
run_context
end
+ # Sets `run_list` on the node from the policy, sets `roles` and `recipes`
+ # attributes on the node accordingly.
+ #
+ # @return [RunListExpansionIsh] A RunListExpansion duck-type.
def expand_run_list
+ CookbookCacheCleaner.instance.skip_removal = true if named_run_list_requested?
+
node.run_list(run_list)
node.automatic_attrs[:roles] = []
node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes
run_list_expansion_ish
end
-
+ # Synchronizes cookbooks. In a normal chef-client run, this is handled by
+ # #setup_run_context, but may be called directly in some circumstances.
+ #
+ # @return [Hash{String => Chef::CookbookManifest}] A map of
+ # CookbookManifest objects by cookbook name.
def sync_cookbooks
Chef::Log.debug("Synchronizing cookbooks")
synchronizer = Chef::CookbookSynchronizer.new(cookbooks_to_sync, events)
@@ -185,12 +195,18 @@ class Chef
# Whether or not this is a temporary policy. Since PolicyBuilder doesn't
# support override_runlist, this is always false.
+ #
+ # @return [false]
def temporary_policy?
false
end
## Internal Public API ##
+ # @api private
+ #
+ # Generates an array of strings with recipe names including version and
+ # identifier info.
def run_list_with_versions_for_display
run_list.map do |recipe_spec|
cookbook, recipe = parse_recipe_spec(recipe_spec)
@@ -200,6 +216,11 @@ class Chef
end
end
+ # @api private
+ #
+ # Sets up a RunListExpansionIsh object so that it can be used in place of
+ # a RunListExpansion object, to satisfy the API contract of
+ # #expand_run_list
def run_list_expansion_ish
recipes = run_list.map do |recipe_spec|
cookbook, recipe = parse_recipe_spec(recipe_spec)
@@ -208,11 +229,15 @@ class Chef
RunListExpansionIsh.new(recipes, [])
end
+ # @api private
+ #
+ # Sets attributes from the policyfile on the node, using the role priority.
def apply_policyfile_attributes
node.attributes.role_default = policy["default_attributes"]
node.attributes.role_override = policy["override_attributes"]
end
+ # @api private
def parse_recipe_spec(recipe_spec)
rmatch = recipe_spec.match(/recipe\[([^:]+)::([^:]+)\]/)
if rmatch.nil?
@@ -222,20 +247,31 @@ class Chef
end
end
+ # @api private
def cookbook_lock_for(cookbook_name)
cookbook_locks[cookbook_name]
end
+ # @api private
def run_list
- policy["run_list"]
+ if named_run_list_requested?
+ named_run_list or
+ raise ConfigurationError,
+ "Policy '#{retrieved_policy_name}' revision '#{revision_id}' does not have named_run_list '#{named_run_list_name}'" +
+ "(available named_run_lists: [#{available_named_run_lists.join(', ')}])"
+ else
+ policy["run_list"]
+ end
end
+ # @api private
def policy
@policy ||= http_api.get(policyfile_location)
rescue Net::HTTPServerException => e
raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}"
end
+ # @api private
def policyfile_location
if Chef::Config[:policy_document_native_api]
validate_policy_config!
@@ -272,6 +308,7 @@ class Chef
end
end
+ # @api private
def validate_recipe_spec(recipe_spec)
parse_recipe_spec(recipe_spec)
nil
@@ -281,11 +318,13 @@ class Chef
class ConfigurationError < StandardError; end
+ # @api private
def deployment_group
Chef::Config[:deployment_group] or
raise ConfigurationError, "Setting `deployment_group` is not configured."
end
+ # @api private
def validate_policy_config!
policy_group or
raise ConfigurationError, "Setting `policy_group` is not configured."
@@ -294,14 +333,75 @@ class Chef
raise ConfigurationError, "Setting `policy_name` is not configured."
end
+ # @api private
def policy_group
Chef::Config[:policy_group]
end
+ # @api private
def policy_name
Chef::Config[:policy_name]
end
+ # @api private
+ #
+ # Selects the `policy_name` and `policy_group` from the following sources
+ # in priority order:
+ #
+ # 1. JSON attribs (i.e., `-j JSON_FILE`)
+ # 2. `Chef::Config`
+ # 3. The node object
+ #
+ # The selected values are then copied to `Chef::Config` and the node.
+ def select_policy_name_and_group
+ policy_name_to_set =
+ policy_name_from_json_attribs ||
+ policy_name_from_config ||
+ policy_name_from_node
+
+ policy_group_to_set =
+ policy_group_from_json_attribs ||
+ policy_group_from_config ||
+ policy_group_from_node
+
+ node.policy_name = policy_name_to_set
+ node.policy_group = policy_group_to_set
+
+ Chef::Config[:policy_name] = policy_name_to_set
+ Chef::Config[:policy_group] = policy_group_to_set
+ end
+
+ # @api private
+ def policy_group_from_json_attribs
+ json_attribs["policy_group"]
+ end
+
+ # @api private
+ def policy_name_from_json_attribs
+ json_attribs["policy_name"]
+ end
+
+ # @api private
+ def policy_group_from_config
+ Chef::Config[:policy_group]
+ end
+
+ # @api private
+ def policy_name_from_config
+ Chef::Config[:policy_name]
+ end
+
+ # @api private
+ def policy_group_from_node
+ node.policy_group
+ end
+
+ # @api private
+ def policy_name_from_node
+ node.policy_name
+ end
+
+ # @api private
# Builds a 'cookbook_hash' map of the form
# "COOKBOOK_NAME" => "IDENTIFIER"
#
@@ -329,6 +429,7 @@ class Chef
raise
end
+ # @api private
# Fetches the CookbookVersion object for the given name and identifer
# specified in the lock_data.
# TODO: This only implements Chef 11 compatibility mode, which means that
@@ -342,20 +443,58 @@ class Chef
end
end
+ # @api private
def cookbook_locks
policy["cookbook_locks"]
end
+ # @api private
+ def revision_id
+ policy["revision_id"]
+ end
+
+ # @api private
def http_api
- @api_service ||= Chef::REST.new(config[:chef_server_url])
+ @api_service ||= Chef::ServerAPI.new(config[:chef_server_url])
end
+ # @api private
def config
Chef::Config
end
private
+ # This method injects the run_context and into the Chef class.
+ #
+ # NOTE: This is duplicated with the ExpandNodeObject implementation. If
+ # it gets any more complicated, it needs to be moved elsewhere.
+ #
+ # @param run_context [Chef::RunContext] the run_context to inject
+ def setup_chef_class(run_context)
+ Chef.set_run_context(run_context)
+ end
+
+ def retrieved_policy_name
+ policy["name"]
+ end
+
+ def named_run_list
+ policy["named_run_lists"] && policy["named_run_lists"][named_run_list_name]
+ end
+
+ def available_named_run_lists
+ (policy["named_run_lists"] || {}).keys
+ end
+
+ def named_run_list_requested?
+ !!Chef::Config[:named_run_list]
+ end
+
+ def named_run_list_name
+ Chef::Config[:named_run_list]
+ end
+
def compat_mode_manifest_for(cookbook_name, lock_data)
xyz_version = lock_data["dotted_decimal_identifier"]
rel_url = "cookbooks/#{cookbook_name}/#{xyz_version}"
@@ -385,4 +524,3 @@ class Chef
end
end
end
-
diff --git a/lib/chef/property.rb b/lib/chef/property.rb
new file mode 100644
index 0000000000..8ff4ecc7fc
--- /dev/null
+++ b/lib/chef/property.rb
@@ -0,0 +1,607 @@
+#
+# 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"
+require "chef/chef_class"
+require "chef/log"
+
+class Chef
+ #
+ # Type and validation information for a property on a resource.
+ #
+ # A property named "x" manipulates the "@x" instance variable on a
+ # resource. The *presence* of the variable (`instance_variable_defined?(@x)`)
+ # tells whether the variable is defined; it may have any actual value,
+ # constrained only by validation.
+ #
+ # Properties may have validation, defaults, and coercion, and have full
+ # support for lazy values.
+ #
+ # @see Chef::Resource.property
+ # @see Chef::DelayedEvaluator
+ #
+ class Property
+ #
+ # Create a reusable property type that can be used in multiple properties
+ # in different resources.
+ #
+ # @param options [Hash<Symbol,Object>] Validation options. See Chef::Resource.property for
+ # the list of options.
+ #
+ # @example
+ # Property.derive(default: 'hi')
+ #
+ def self.derive(**options)
+ new(**options)
+ end
+
+ #
+ # Create a new property.
+ #
+ # @param options [Hash<Symbol,Object>] Property options, including
+ # control options here, as well as validation options (see
+ # Chef::Mixin::ParamsValidate#validate for a description of validation
+ # options).
+ # @option options [Symbol] :name The name of this property.
+ # @option options [Class] :declared_in The class this property comes from.
+ # @option options [Symbol] :instance_variable_name The instance variable
+ # tied to this property. Must include a leading `@`. Defaults to `@<name>`.
+ # `nil` means the property is opaque and not tied to a specific instance
+ # variable.
+ # @option options [Boolean] :desired_state `true` if this property is part of desired
+ # state. Defaults to `true`.
+ # @option options [Boolean] :identity `true` if this property is part of object
+ # identity. Defaults to `false`.
+ # @option options [Boolean] :name_property `true` if this
+ # property defaults to the same value as `name`. Equivalent to
+ # `default: lazy { name }`, except that #property_is_set? will
+ # return `true` if the property is set *or* if `name` is set.
+ # @option options [Object] :default The value this property
+ # will return if the user does not set one. If this is `lazy`, it will
+ # be run in the context of the instance (and able to access other
+ # properties) and cached. If not, the value will be frozen with Object#freeze
+ # to prevent users from modifying it in an instance.
+ # @option options [Proc] :coerce A proc which will be called to
+ # transform the user input to canonical form. The value is passed in,
+ # and the transformed value returned as output. Lazy values will *not*
+ # be passed to this method until after they are evaluated. Called in the
+ # context of the resource (meaning you can access other properties).
+ # @option options [Boolean] :required `true` if this property
+ # must be present; `false` otherwise. This is checked after the resource
+ # is fully initialized.
+ #
+ def initialize(**options)
+ options.each { |k,v| options[k.to_sym] = v if k.is_a?(String) }
+
+ # Replace name_attribute with name_property
+ if options.has_key?(:name_attribute)
+ # If we have both name_attribute and name_property and they differ, raise an error
+ if options.has_key?(:name_property)
+ raise ArgumentError, "Cannot specify both name_property and name_attribute together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}."
+ end
+ # replace name_property with name_attribute in place
+ options = Hash[options.map { |k,v| k == :name_attribute ? [ :name_property, v ] : [ k,v ] }]
+ end
+
+ # Only pick the first of :default, :name_property and :name_attribute if
+ # more than one is specified.
+ if options.has_key?(:default) && options[:name_property]
+ if options[:default].nil? || options.keys.index(:name_property) < options.keys.index(:default)
+ options.delete(:default)
+ preferred_default = :name_property
+ else
+ options.delete(:name_property)
+ preferred_default = :default
+ end
+ Chef.log_deprecation("Cannot specify both default and name_property together on property #{options[:name]}#{options[:declared_in] ? " of resource #{options[:declared_in].resource_name}" : ""}. Only one (#{preferred_default}) will be obeyed. In Chef 13, this will become an error.")
+ end
+
+ @options = options
+
+ options[:name] = options[:name].to_sym if options[:name]
+ options[:instance_variable_name] = options[:instance_variable_name].to_sym if options[:instance_variable_name]
+ end
+
+ def to_s
+ name
+ end
+
+ #
+ # The name of this property.
+ #
+ # @return [String]
+ #
+ def name
+ options[:name]
+ end
+
+ #
+ # The class this property was defined in.
+ #
+ # @return [Class]
+ #
+ def declared_in
+ options[:declared_in]
+ end
+
+ #
+ # The instance variable associated with this property.
+ #
+ # Defaults to `@<name>`
+ #
+ # @return [Symbol]
+ #
+ def instance_variable_name
+ if options.has_key?(:instance_variable_name)
+ options[:instance_variable_name]
+ elsif name
+ :"@#{name}"
+ end
+ end
+
+ #
+ # The raw default value for this resource.
+ #
+ # Does not coerce or validate the default. Does not evaluate lazy values.
+ #
+ # Defaults to `lazy { name }` if name_property is true; otherwise defaults to
+ # `nil`
+ #
+ def default
+ return options[:default] if options.has_key?(:default)
+ return Chef::DelayedEvaluator.new { name } if name_property?
+ nil
+ end
+
+ #
+ # Whether this is part of the resource's natural identity or not.
+ #
+ # @return [Boolean]
+ #
+ def identity?
+ options[:identity]
+ end
+
+ #
+ # Whether this is part of desired state or not.
+ #
+ # Defaults to true.
+ #
+ # @return [Boolean]
+ #
+ def desired_state?
+ return true if !options.has_key?(:desired_state)
+ options[:desired_state]
+ end
+
+ #
+ # Whether this is name_property or not.
+ #
+ # @return [Boolean]
+ #
+ def name_property?
+ options[:name_property]
+ end
+
+ #
+ # Whether this property has a default value.
+ #
+ # @return [Boolean]
+ #
+ def has_default?
+ options.has_key?(:default) || name_property?
+ end
+
+ #
+ # Whether this property is required or not.
+ #
+ # @return [Boolean]
+ #
+ def required?
+ options[:required]
+ end
+
+ #
+ # Validation options. (See Chef::Mixin::ParamsValidate#validate.)
+ #
+ # @return [Hash<Symbol,Object>]
+ #
+ def validation_options
+ @validation_options ||= options.reject { |k,v|
+ [:declared_in,:name,:instance_variable_name,:desired_state,:identity,:default,:name_property,:coerce,:required].include?(k)
+ }
+ end
+
+ #
+ # Handle the property being called.
+ #
+ # The base implementation does the property get-or-set:
+ #
+ # ```ruby
+ # resource.myprop # get
+ # resource.myprop value # set
+ # ```
+ #
+ # Subclasses may implement this with any arguments they want, as long as
+ # the corresponding DSL calls it correctly.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ # @param value The value to set (or NOT_PASSED if it is a get).
+ #
+ # @return The current value of the property. If it is a `set`, lazy values
+ # will be returned without running, validating or coercing. If it is a
+ # `get`, the non-lazy, coerced, validated value will always be returned.
+ #
+ def call(resource, value=NOT_PASSED)
+ if value == NOT_PASSED
+ return get(resource)
+ end
+
+ if value.nil? && !explicitly_accepts_nil?(resource)
+ # In Chef 12, value(nil) does a *get* instead of a set, so we
+ # warn if the value would have been changed. In Chef 13, it will be
+ # equivalent to value = nil.
+ result = get(resource)
+ if !result.nil?
+ Chef.log_deprecation("#{name} nil currently does not overwrite the value of #{name}. This will change in Chef 13, and the value will be set to nil instead. Please change your code to explicitly accept nil using \"property :#{name}, [MyType, nil]\", or stop setting this value to nil.")
+ end
+ result
+ else
+ # Anything else, such as myprop(value) is a set
+ set(resource, value)
+ end
+ end
+
+ #
+ # Get the property value from the resource, handling lazy values,
+ # defaults, and validation.
+ #
+ # - If the property's value is lazy, it is evaluated, coerced and validated.
+ # - If the property has no value, and is required, raises ValidationFailed.
+ # - If the property has no value, but has a lazy default, it is evaluated,
+ # coerced and validated. If the evaluated value is frozen, the resulting
+ # - If the property has no value, but has a default, the default value
+ # will be returned and frozen. If the default value is lazy, it will be
+ # evaluated, coerced and validated, and the result stored in the property.
+ # - If the property has no value, but is name_property, `resource.name`
+ # is retrieved, coerced, validated and stored in the property.
+ # - Otherwise, `nil` is returned.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return The value of the property.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property, or if the value is required and not set.
+ #
+ def get(resource)
+ if is_set?(resource)
+ value = get_value(resource)
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+
+ else
+ # If the user does something like this:
+ #
+ # ```
+ # class MyResource < Chef::Resource
+ # property :content
+ # action :create do
+ # file '/x.txt' do
+ # content content
+ # end
+ # end
+ # end
+ # ```
+ #
+ # It won't do what they expect. This checks whether you try to *read*
+ # `content` while we are compiling the resource.
+ if resource.respond_to?(:resource_initializing) &&
+ resource.resource_initializing &&
+ resource.respond_to?(:enclosing_provider) &&
+ resource.enclosing_provider &&
+ resource.enclosing_provider.respond_to?(name)
+ Chef::Log.warn("#{Chef::Log.caller_location}: property #{name} is declared in both #{resource} and #{resource.enclosing_provider}. Use new_resource.#{name} instead. At #{Chef::Log.caller_location}")
+ end
+
+ if has_default?
+ value = default
+ if value.is_a?(DelayedEvaluator)
+ value = exec_in_resource(resource, value)
+ end
+
+ value = coerce(resource, value)
+
+ # We don't validate defaults
+
+ # If the value is mutable (non-frozen), we set it on the instance
+ # so that people can mutate it. (All constant default values are
+ # frozen.)
+ if !value.frozen? && !value.nil?
+ set_value(resource, value)
+ end
+
+ value
+
+ elsif required?
+ raise Chef::Exceptions::ValidationFailed, "#{name} is required"
+ end
+ end
+ end
+
+ #
+ # Set the value of this property in the given resource.
+ #
+ # Non-lazy values are coerced and validated before being set. Coercion
+ # and validation of lazy values is delayed until they are first retrieved.
+ #
+ # @param resource [Chef::Resource] The resource to set this property in.
+ # @param value The value to set.
+ #
+ # @return The value that was set, after coercion (if lazy, still returns
+ # the lazy value)
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def set(resource, value)
+ unless value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ set_value(resource, value)
+ end
+
+ #
+ # Find out whether this property has been set.
+ #
+ # This will be true if:
+ # - The user explicitly set the value
+ # - The property has a default, and the value was retrieved.
+ #
+ # From this point of view, it is worth looking at this as "what does the
+ # user think this value should be." In order words, if the user grabbed
+ # the value, even if it was a default, they probably based calculations on
+ # it. If they based calculations on it and the value changes, the rest of
+ # the world gets inconsistent.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ # @return [Boolean]
+ #
+ def is_set?(resource)
+ value_is_set?(resource)
+ end
+
+ #
+ # Reset the value of this property so that is_set? will return false and the
+ # default will be returned in the future.
+ #
+ # @param resource [Chef::Resource] The resource to get the property from.
+ #
+ def reset(resource)
+ reset_value(resource)
+ end
+
+ #
+ # Coerce an input value into canonical form for the property.
+ #
+ # After coercion, the value is suitable for storage in the resource.
+ # You must validate values after coercion, however.
+ #
+ # Does no special handling for lazy values.
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ # @param value The value to coerce.
+ #
+ # @return The coerced value.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def coerce(resource, value)
+ if options.has_key?(:coerce)
+ value = exec_in_resource(resource, options[:coerce], value)
+ end
+ value
+ end
+
+ #
+ # Validate a value.
+ #
+ # Calls Chef::Mixin::ParamsValidate#validate with #validation_options as
+ # options.
+ #
+ # @param resource [Chef::Resource] The resource we're validating against
+ # (to provide context for the validate).
+ # @param value The value to validate.
+ #
+ # @raise Chef::Exceptions::ValidationFailed If the value is invalid for
+ # this property.
+ #
+ def validate(resource, value)
+ resource.validate({ name => value }, { name => validation_options })
+ end
+
+ #
+ # Derive a new Property that is just like this one, except with some added or
+ # changed options.
+ #
+ # @param options [Hash<Symbol,Object>] List of options that would be passed
+ # to #initialize.
+ #
+ # @return [Property] The new property type.
+ #
+ def derive(**modified_options)
+ # Since name_property, name_attribute and default override each other,
+ # if you specify one of them in modified_options it overrides anything in
+ # the original options.
+ options = self.options
+ if modified_options.has_key?(:name_property) ||
+ modified_options.has_key?(:name_attribute) ||
+ modified_options.has_key?(:default)
+ options = options.reject { |k,v| k == :name_attribute || k == :name_property || k == :default }
+ end
+ self.class.new(options.merge(modified_options))
+ end
+
+ #
+ # Emit the DSL for this property into the resource class (`declared_in`).
+ #
+ # Creates a getter and setter for the property.
+ #
+ def emit_dsl
+ # We don't create the getter/setter if it's a custom property; we will
+ # be using the existing getter/setter to manipulate it instead.
+ return if !instance_variable_name
+
+ # We prefer this form because the property name won't show up in the
+ # stack trace if you use `define_method`.
+ declared_in.class_eval <<-EOM, __FILE__, __LINE__+1
+ def #{name}(value=NOT_PASSED)
+ raise "Property #{name} of \#{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block_given?
+ self.class.properties[#{name.inspect}].call(self, value)
+ end
+ def #{name}=(value)
+ raise "Property #{name} of \#{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block_given?
+ self.class.properties[#{name.inspect}].set(self, value)
+ end
+ EOM
+ rescue SyntaxError
+ # If the name is not a valid ruby name, we use define_method.
+ declared_in.define_method(name) do |value=NOT_PASSED, &block|
+ raise "Property #{name} of #{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block
+ self.class.properties[name].call(self, value)
+ end
+ declared_in.define_method("#{name}=") do |value, &block|
+ raise "Property #{name} of #{self} cannot be passed a block! If you meant to create a resource named #{name} instead, you'll need to first rename the property." if block
+ self.class.properties[name].set(self, value)
+ end
+ end
+
+ protected
+
+ #
+ # The options this Property will use for get/set behavior and validation.
+ #
+ # @see #initialize for a list of valid options.
+ #
+ attr_reader :options
+
+ #
+ # Find out whether this type accepts nil explicitly.
+ #
+ # A type accepts nil explicitly if "is" allows nil, it validates as nil, *and* is not simply
+ # an empty type.
+ #
+ # A type is presumed to accept nil if it does coercion (which must handle nil).
+ #
+ # These examples accept nil explicitly:
+ # ```ruby
+ # property :a, [ String, nil ]
+ # property :a, [ String, NilClass ]
+ # property :a, [ String, proc { |v| v.nil? } ]
+ # ```
+ #
+ # This does not (because the "is" doesn't exist or doesn't have nil):
+ #
+ # ```ruby
+ # property :x, String
+ # ```
+ #
+ # These do not, even though nil would validate fine (because they do not
+ # have "is"):
+ #
+ # ```ruby
+ # property :a
+ # property :a, equal_to: [ 1, 2, 3, nil ]
+ # property :a, kind_of: [ String, NilClass ]
+ # property :a, respond_to: [ ]
+ # property :a, callbacks: { "a" => proc { |v| v.nil? } }
+ # ```
+ #
+ # @param resource [Chef::Resource] The resource we're coercing against
+ # (to provide context for the coerce).
+ #
+ # @return [Boolean] Whether this value explicitly accepts nil.
+ #
+ # @api private
+ def explicitly_accepts_nil?(resource)
+ options.has_key?(:coerce) ||
+ (options.has_key?(:is) && resource.send(:_pv_is, { name => nil }, name, options[:is], raise_error: false))
+ end
+
+ def get_value(resource)
+ if instance_variable_name
+ resource.instance_variable_get(instance_variable_name)
+ else
+ resource.send(name)
+ end
+ end
+
+ def set_value(resource, value)
+ if instance_variable_name
+ resource.instance_variable_set(instance_variable_name, value)
+ else
+ resource.send(name, value)
+ end
+ end
+
+ def value_is_set?(resource)
+ if instance_variable_name
+ resource.instance_variable_defined?(instance_variable_name)
+ else
+ true
+ end
+ end
+
+ def reset_value(resource)
+ if instance_variable_name
+ if value_is_set?(resource)
+ resource.remove_instance_variable(instance_variable_name)
+ end
+ else
+ raise ArgumentError, "Property #{name} has no instance variable defined and cannot be reset"
+ end
+ end
+
+ def exec_in_resource(resource, proc, *args)
+ if resource
+ if proc.arity > args.size
+ value = proc.call(resource, *args)
+ else
+ value = resource.instance_exec(*args, &proc)
+ end
+ else
+ value = proc.call
+ end
+
+ if value.is_a?(DelayedEvaluator)
+ value = coerce(resource, value)
+ validate(resource, value)
+ end
+ value
+ end
+ end
+end
diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb
index 680fe9782f..4d99d316ca 100644
--- a/lib/chef/provider.rb
+++ b/lib/chef/provider.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Walters (<cw@opscode.com>)
-# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
+# Copyright:: Copyright (c) 2008, 2009-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,39 +17,30 @@
# limitations under the License.
#
-require 'chef/mixin/from_file'
-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/platform/service_helpers'
-require 'chef/node_map'
+require "chef/mixin/from_file"
+require "chef/mixin/convert_to_class_name"
+require "chef/mixin/enforce_ownership_and_permissions"
+require "chef/mixin/why_run"
+require "chef/mixin/shell_out"
+require "chef/mixin/powershell_out"
+require "chef/mixin/provides"
+require "chef/platform/service_helpers"
+require "chef/node_map"
+require "forwardable"
class Chef
class Provider
+ require "chef/mixin/why_run"
+ require "chef/mixin/shell_out"
+ require "chef/mixin/provides"
include Chef::Mixin::WhyRun
include Chef::Mixin::ShellOut
- 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,8 +90,11 @@ 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}"
+ raise Chef::Exceptions::Override, "You must override load_current_resource in #{self}"
end
def define_resource_requirements
@@ -109,7 +104,7 @@ class Chef
end
def action_nothing
- Chef::Log.debug("Doing nothing for #{@new_resource.to_s}")
+ Chef::Log.debug("Doing nothing for #{@new_resource}")
true
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,226 @@ class Chef
converge_actions.add_action(descriptions, &block)
end
+ #
+ # Handle patchy convergence safely.
+ #
+ # - Does *not* call the block if the current_resource's properties match
+ # the properties the user specified on the resource.
+ # - Calls the block if current_resource does not exist
+ # - Calls the block if the user has specified any properties in the resource
+ # whose values are *different* from current_resource.
+ # - Does *not* call the block if why-run is enabled (just prints out text).
+ # - Prints out automatic green text saying what properties have changed.
+ #
+ # @param properties An optional list of property names (symbols). If not
+ # specified, `new_resource.class.state_properties` will be used.
+ # @param converge_block The block to do the converging in.
+ #
+ # @return [Boolean] whether the block was executed.
+ #
+ def converge_if_changed(*properties, &converge_block)
+ if !converge_block
+ raise ArgumentError, "converge_if_changed must be passed a block!"
+ end
+
+ properties = new_resource.class.state_properties.map { |p| p.name } if properties.empty?
+ properties = properties.map { |p| p.to_sym }
+ if current_resource
+ # Collect the list of modified properties
+ specified_properties = properties.select { |property| new_resource.property_is_set?(property) }
+ modified = specified_properties.select { |p| new_resource.send(p) != current_resource.send(p) }
+ if modified.empty?
+ properties_str = if sensitive
+ specified_properties.join(", ")
+ else
+ specified_properties.map { |p| "#{p}=#{new_resource.send(p).inspect}" }.join(", ")
+ end
+ Chef::Log.debug("Skipping update of #{new_resource}: has not changed any of the specified properties #{properties_str}.")
+ return false
+ end
+
+ # Print the pretty green text and run the block
+ property_size = modified.map { |p| p.size }.max
+ modified.map! do |p|
+ properties_str = if sensitive
+ "(suppressed sensitive property)"
+ else
+ "#{new_resource.send(p).inspect} (was #{current_resource.send(p).inspect})"
+ end
+ " set #{p.to_s.ljust(property_size)} to #{properties_str}"
+ end
+ converge_by([ "update #{current_resource.identity}" ] + modified, &converge_block)
+
+ else
+ # The resource doesn't exist. Mark that we are *creating* this, and
+ # write down any properties we are setting.
+ property_size = properties.map { |p| p.size }.max
+ created = properties.map do |property|
+ default = " (default value)" unless new_resource.property_is_set?(property)
+ properties_str = if sensitive
+ "(suppressed sensitive property)"
+ else
+ new_resource.send(property).inspect
+ end
+ " set #{property.to_s.ljust(property_size)} to #{properties_str}#{default}"
+ end
+
+ converge_by([ "create #{new_resource.identity}" ] + created, &converge_block)
+ end
+ true
+ end
+
+ def self.provides(short_name, opts={}, &block)
+ Chef.provider_handler_map.set(short_name, self, opts, &block)
+ end
+
+ def self.provides?(node, resource)
+ Chef::ProviderResolver.new(node, resource, :nothing).provided_by?(self)
+ end
+
+ #
+ # Include attributes, public and protected methods from this Resource in
+ # the provider.
+ #
+ # If this is set to true, delegate methods are included in the provider so
+ # that you can call (for example) `attrname` and it will call
+ # `new_resource.attrname`.
+ #
+ # The actual include does not happen until the first time the Provider
+ # is instantiated (so that we don't have to worry about load order issues).
+ #
+ # @param include_resource_dsl [Boolean] Whether to include resource DSL or
+ # not (defaults to `false`).
+ #
+ def self.include_resource_dsl(include_resource_dsl)
+ @include_resource_dsl = include_resource_dsl
+ end
+
+ # Create the resource DSL module that forwards resource methods to new_resource
+ #
+ # @api private
+ def self.include_resource_dsl_module(resource)
+ if @include_resource_dsl && !defined?(@included_resource_dsl_module)
+ provider_class = self
+ @included_resource_dsl_module = Module.new do
+ extend Forwardable
+ define_singleton_method(:to_s) { "forwarder module for #{provider_class}" }
+ define_singleton_method(:inspect) { to_s }
+ # Add a delegator for each explicit property that will get the *current* value
+ # of the property by default instead of the *actual* value.
+ resource.class.properties.each do |name, property|
+ class_eval(<<-EOM, __FILE__, __LINE__)
+ def #{name}(*args, &block)
+ # If no arguments were passed, we process "get" by defaulting
+ # the value to current_resource, not new_resource. This helps
+ # avoid issues where resources accidentally overwrite perfectly
+ # valid stuff with default values.
+ if args.empty? && !block
+ if !new_resource.property_is_set?(__method__) && current_resource
+ return current_resource.public_send(__method__)
+ end
+ end
+ new_resource.public_send(__method__, *args, &block)
+ end
+ EOM
+ end
+ dsl_methods =
+ resource.class.public_instance_methods +
+ resource.class.protected_instance_methods -
+ provider_class.instance_methods -
+ resource.class.properties.keys
+ def_delegators(:new_resource, *dsl_methods)
+ end
+ include @included_resource_dsl_module
+ end
+ end
+
+ # Enables inline evaluation of resources in provider actions.
+ #
+ # Without this option, any resources declared inside the Provider are added
+ # to the resource collection after the current position at the time the
+ # action is executed. Because they are added to the primary resource
+ # collection for the chef run, they can notify other resources outside
+ # the Provider, and potentially be notified by resources outside the Provider
+ # (but this is complicated by the fact that they don't exist until the
+ # provider executes). In this mode, it is impossible to correctly set the
+ # updated_by_last_action flag on the parent Provider resource, since it
+ # executes and returns before its component resources are run.
+ #
+ # With this option enabled, each action creates a temporary run_context
+ # with its own resource collection, evaluates the action's code in that
+ # context, and then converges the resources created. If any resources
+ # were updated, then this provider's new_resource will be marked updated.
+ #
+ # In this mode, resources created within the Provider cannot interact with
+ # external resources via notifies, though notifications to other
+ # resources within the Provider will work. Delayed notifications are executed
+ # at the conclusion of the provider's action, *not* at the end of the
+ # main chef run.
+ #
+ # This mode of evaluation is experimental, but is believed to be a better
+ # set of tradeoffs than the append-after mode, so it will likely become
+ # the default in a future major release of Chef.
+ #
+ def self.use_inline_resources
+ extend InlineResources::ClassMethods
+ include InlineResources
+ end
+
+ # Chef::Provider::InlineResources
+ # Implementation of inline resource convergence for providers. See
+ # Provider.use_inline_resources for a longer explanation.
+ #
+ # This code is restricted to a module so that it can be selectively
+ # applied to providers on an opt-in basis.
+ #
+ # @api private
+ module InlineResources
+
+ # Create a child run_context, compile the block, and converge it.
+ #
+ # @api private
+ def compile_and_converge_action(&block)
+ old_run_context = run_context
+ @run_context = run_context.create_child
+ return_value = instance_eval(&block)
+ Chef::Runner.new(run_context).converge
+ return_value
+ ensure
+ if run_context.resource_collection.any? { |r| r.updated? }
+ new_resource.updated_by_last_action(true)
+ end
+ @run_context = old_run_context
+ end
+
+ # Class methods for InlineResources. Overrides the `action` DSL method
+ # with one that enables inline resource convergence.
+ #
+ # @api private
+ module ClassMethods
+ # Defines an action method on the provider, running the block to
+ # compile the resources, converging them, and then checking if any
+ # were updated (and updating new-resource if so)
+ def action(name, &block)
+ # We need the block directly in a method so that `super` works
+ define_method("compile_action_#{name}", &block)
+ # We try hard to use `def` because define_method doesn't show the method name in the stack.
+ begin
+ class_eval <<-EOM
+ def action_#{name}
+ compile_and_converge_action { compile_action_#{name} }
+ end
+ EOM
+ rescue SyntaxError
+ define_method("action_#{name}") { send("compile_action_#{name}") }
+ end
+ end
+ end
+
+ require "chef/dsl/recipe"
+ include Chef::DSL::Recipe::FullDSL
+ end
+
protected
def converge_actions
@@ -197,14 +412,50 @@ class Chef
# manipulating notifies.
converge_by ("evaluate block and run any associated actions") do
- saved_run_context = @run_context
- @run_context = @run_context.dup
- @run_context.resource_collection = Chef::ResourceCollection.new
- instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- @run_context = saved_run_context
+ saved_run_context = run_context
+ begin
+ @run_context = run_context.create_child
+ instance_eval(&block)
+ Chef::Runner.new(run_context).converge
+ ensure
+ @run_context = saved_run_context
+ end
end
end
+ module DeprecatedLWRPClass
+ def const_missing(class_name)
+ if deprecated_constants[class_name.to_sym]
+ Chef.log_deprecation("Using an LWRP provider by its name (#{class_name}) directly is no longer supported in Chef 12 and will be removed. Use Chef::ProviderResolver.new(node, resource, action) instead.")
+ deprecated_constants[class_name.to_sym]
+ else
+ raise NameError, "uninitialized constant Chef::Provider::#{class_name}"
+ end
+ end
+
+ # @api private
+ def register_deprecated_lwrp_class(provider_class, class_name)
+ # Register Chef::Provider::MyProvider with deprecation warnings if you
+ # try to access it
+ if Chef::Provider.const_defined?(class_name, false)
+ Chef::Log.warn "Chef::Provider::#{class_name} already exists! Cannot create deprecation class for #{provider_class}"
+ else
+ deprecated_constants[class_name.to_sym] = provider_class
+ end
+ end
+
+ private
+
+ def deprecated_constants
+ @deprecated_constants ||= {}
+ end
+ end
+ extend DeprecatedLWRPClass
end
end
+
+# Requiring things at the bottom breaks cycles
+require "chef/chef_class"
+require "chef/mixin/why_run"
+require "chef/resource_collection"
+require "chef/runner"
diff --git a/lib/chef/provider/batch.rb b/lib/chef/provider/batch.rb
index b6b386e5a8..63286f91f6 100644
--- a/lib/chef/provider/batch.rb
+++ b/lib/chef/provider/batch.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/provider/windows_script'
+require "chef/provider/windows_script"
class Chef
class Provider
@@ -25,11 +25,19 @@ class Chef
provides :batch, os: "windows"
def initialize (new_resource, run_context)
- super(new_resource, run_context, '.bat')
+ 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'
+ @new_resource.flags.nil? ? "/c" : new_resource.flags + " /c"
end
end
diff --git a/lib/chef/provider/cookbook_file.rb b/lib/chef/provider/cookbook_file.rb
index b501a9b41d..a537ca5fd5 100644
--- a/lib/chef/provider/cookbook_file.rb
+++ b/lib/chef/provider/cookbook_file.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/provider/file'
-require 'chef/deprecation/provider/cookbook_file'
-require 'chef/deprecation/warnings'
+require "chef/provider/file"
+require "chef/deprecation/provider/cookbook_file"
+require "chef/deprecation/warnings"
class Chef
class Provider
diff --git a/lib/chef/provider/cookbook_file/content.rb b/lib/chef/provider/cookbook_file/content.rb
index 9f49ba885c..97290ff456 100644
--- a/lib/chef/provider/cookbook_file/content.rb
+++ b/lib/chef/provider/cookbook_file/content.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/file_content_management/content_base'
-require 'chef/file_content_management/tempfile'
+require "chef/file_content_management/content_base"
+require "chef/file_content_management/tempfile"
class Chef
class Provider
diff --git a/lib/chef/provider/cron.rb b/lib/chef/provider/cron.rb
index 6d86e336ec..361691aa0a 100644
--- a/lib/chef/provider/cron.rb
+++ b/lib/chef/provider/cron.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/mixin/command'
-require 'chef/provider'
+require "chef/log"
+require "chef/mixin/command"
+require "chef/provider"
class Chef
class Provider
@@ -199,7 +199,7 @@ class Chef
private
def set_environment_var(attr_name, attr_value)
- if %w(MAILTO PATH SHELL HOME).include?(attr_name)
+ if %w{MAILTO PATH SHELL HOME}.include?(attr_name)
@current_resource.send(attr_name.downcase.to_sym, attr_value)
else
@current_resource.environment(@current_resource.environment.merge(attr_name => attr_value))
diff --git a/lib/chef/provider/cron/unix.rb b/lib/chef/provider/cron/unix.rb
index 0750c0420b..0c4235df45 100644
--- a/lib/chef/provider/cron/unix.rb
+++ b/lib/chef/provider/cron/unix.rb
@@ -18,8 +18,9 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/provider'
+require "chef/log"
+require "chef/provider"
+require "chef/provider/cron"
class Chef
class Provider
@@ -27,12 +28,12 @@ class Chef
class Unix < Chef::Provider::Cron
include Chef::Mixin::ShellOut
- provides :cron, os: 'solaris2'
+ provides :cron, os: "solaris2"
private
def read_crontab
- crontab = shell_out('/usr/bin/crontab -l', :user => @new_resource.user)
+ crontab = shell_out("/usr/bin/crontab -l", :user => @new_resource.user)
status = crontab.status.exitstatus
Chef::Log.debug crontab.format_for_exception if status > 0
diff --git a/lib/chef/provider/deploy.rb b/lib/chef/provider/deploy.rb
index 19e7c01ab1..fe6b288eda 100644
--- a/lib/chef/provider/deploy.rb
+++ b/lib/chef/provider/deploy.rb
@@ -201,7 +201,7 @@ class Chef
converge_by("execute migration command #{@new_resource.migration_command}") do
Chef::Log.info "#{@new_resource} migrating #{@new_resource.user} with environment #{env_info}"
- run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path, :log_level => :info))
+ shell_out!(@new_resource.migration_command,run_options(:cwd=>release_path, :log_level => :info))
end
end
end
@@ -221,7 +221,7 @@ class Chef
else
converge_by("restart app using command #{@new_resource.restart_command}") do
Chef::Log.info("#{@new_resource} restarting app")
- run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path))
+ shell_out!(@new_resource.restart_command,run_options(:cwd=>@new_resource.current_path))
end
end
end
@@ -276,7 +276,7 @@ class Chef
def enforce_ownership
converge_by("force ownership of #{@new_resource.deploy_to} to #{@new_resource.group}:#{@new_resource.user}") do
- FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to)
+ FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to, :force => true)
Chef::Log.info("#{@new_resource} set user to #{@new_resource.user}") if @new_resource.user
Chef::Log.info("#{@new_resource} set group to #{@new_resource.group}") if @new_resource.group
end
@@ -365,7 +365,7 @@ class Chef
end
def release_slug
- raise Chef::Exceptions::Override, "You must override release_slug in #{self.to_s}"
+ raise Chef::Exceptions::Override, "You must override release_slug in #{self}"
end
def install_gems
@@ -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/deploy/revision.rb b/lib/chef/provider/deploy/revision.rb
index 62aa0e87f6..c3b61a564e 100644
--- a/lib/chef/provider/deploy/revision.rb
+++ b/lib/chef/provider/deploy/revision.rb
@@ -19,9 +19,9 @@
# limitations under the License.
#
-require 'chef/provider'
-require 'chef/provider/deploy'
-require 'chef/json_compat'
+require "chef/provider"
+require "chef/provider/deploy"
+require "chef/json_compat"
class Chef
class Provider
diff --git a/lib/chef/provider/directory.rb b/lib/chef/provider/directory.rb
index 416393ac60..c9b8b3a844 100644
--- a/lib/chef/provider/directory.rb
+++ b/lib/chef/provider/directory.rb
@@ -16,12 +16,12 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/log'
-require 'chef/resource/directory'
-require 'chef/provider'
-require 'chef/provider/file'
-require 'fileutils'
+require "chef/config"
+require "chef/log"
+require "chef/resource/directory"
+require "chef/provider"
+require "chef/provider/file"
+require "fileutils"
class Chef
class Provider
@@ -43,6 +43,9 @@ class Chef
end
def define_resource_requirements
+ # deep inside FAC we have to assert requirements, so call FACs hook to set that up
+ access_controls.define_resource_requirements
+
requirements.assert(:create) do |a|
# Make sure the parent dir exists, or else fail.
# for why run, print a message explaining the potential error.
@@ -61,7 +64,13 @@ class Chef
is_parent_writable = lambda do |base_dir|
base_dir = ::File.dirname(base_dir)
if ::File.exists?(base_dir)
- Chef::FileAccessControl.writable?(base_dir)
+ if Chef::FileAccessControl.writable?(base_dir)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(base_dir, node)
+ Chef::Util::PathHelper.writable_sip_path?(base_dir)
+ else
+ false
+ end
else
is_parent_writable.call(base_dir)
end
@@ -71,7 +80,13 @@ class Chef
# in why run mode & parent directory does not exist no permissions check is required
# If not in why run, permissions must be valid and we rely on prior assertion that dir exists
if !whyrun_mode? || ::File.exists?(parent_directory)
- Chef::FileAccessControl.writable?(parent_directory)
+ if Chef::FileAccessControl.writable?(parent_directory)
+ true
+ elsif Chef::Util::PathHelper.is_sip_path?(parent_directory, node)
+ Chef::Util::PathHelper.writable_sip_path?(@new_resource.path)
+ else
+ false
+ end
else
true
end
diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb
index 2812c154c6..b2946352fe 100644
--- a/lib/chef/provider/dsc_resource.rb
+++ b/lib/chef/provider/dsc_resource.rb
@@ -15,29 +15,28 @@
# 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'
+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
+ @reboot_resource = nil
end
def action_run
if ! test_resource
converge_by(generate_description) do
result = set_resource
+ reboot_if_required
end
end
end
@@ -53,17 +52,16 @@ class Chef
requirements.assert(:run) do |a|
a.assertion { supports_dsc_invoke_resource? }
err = ["You must have Powershell version >= 5.0.10018.0 to use dsc_resource."]
- a.failure_message Chef::Exceptions::NoProviderAvailable,
+ a.failure_message Chef::Exceptions::ProviderNotFound,
err
a.whyrun err + ["Assuming a previous resource installs Powershell 5.0.10018.0 or higher."]
a.block_action!
end
requirements.assert(:run) do |a|
- a.assertion {
- meta_configuration['RefreshMode'] == 'Disabled'
- }
- err = ["The LCM must have its RefreshMode set to Disabled. "]
- a.failure_message Chef::Exceptions::NoProviderAvailable, err.join(' ')
+ a.assertion { supports_refresh_mode_enabled? || dsc_refresh_mode_disabled? }
+ err = ["The LCM must have its RefreshMode set to Disabled for" \
+ " PowerShell versions before 5.0.10586.0."]
+ a.failure_message Chef::Exceptions::ProviderNotFound, err.join(" ")
a.whyrun err + ["Assuming a previous resource sets the RefreshMode."]
a.block_action!
end
@@ -74,7 +72,7 @@ class Chef
def local_configuration_manager
@local_configuration_manager ||= Chef::Util::DSC::LocalConfigurationManager.new(
node,
- nil
+ nil,
)
end
@@ -86,6 +84,14 @@ class Chef
run_context && Chef::Platform.supports_dsc_invoke_resource?(node)
end
+ def dsc_refresh_mode_disabled?
+ Chef::Platform.dsc_refresh_mode_disabled?(node)
+ end
+
+ def supports_refresh_mode_enabled?
+ Chef::Platform.supports_refresh_mode_enabled?(node)
+ end
+
def generate_description
@converge_description
end
@@ -97,17 +103,16 @@ class Chef
def module_name
@module_name ||= begin
found = resource_store.find(dsc_resource_name)
-
r = case found.length
when 0
raise Chef::Exceptions::ResourceNotFound,
"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?
+ if found[0]["Module"].nil?
:none
else
- found[0]['Module']['Name']
+ found[0]["Module"]["Name"]
end
else
raise Chef::Exceptions::MultipleDscResourcesFound, found
@@ -117,41 +122,73 @@ class Chef
def test_resource
result = invoke_resource(:test)
+ @converge_description = result.stream(:verbose)
+
# We really want this information from the verbose stream,
- # however Invoke-DscResource is not correctly writing to that
- # stream and instead just dumping to stdout
- @converge_description = result.stdout
- result.return_value[0]["InDesiredState"]
+ # however in some versions of WMF, Invoke-DscResource is not correctly
+ # writing to that stream and instead just dumping to stdout
+ if @converge_description.empty?
+ @converge_description = result.stdout
+ end
+
+ return_dsc_resource_result(result, "InDesiredState")
end
def set_resource
result = invoke_resource(:set)
+ if return_dsc_resource_result(result, "RebootRequired")
+ create_reboot_resource
+ end
result.return_value
end
def invoke_resource(method, output_format=:object)
properties = translate_type(@new_resource.properties)
- switches = "-Method #{method.to_s} -Name #{@new_resource.resource}"\
+ switches = "-Method #{method} -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
+ output_format,
)
- cmdlet.run!
+ cmdlet.run!({}, {:timeout => new_resource.timeout})
end
- def meta_configuration
- cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object)
- result = cmdlet.run!
- result.return_value
+ def return_dsc_resource_result(result, property_name)
+ if result.return_value.is_a?(Array)
+ # WMF Feb 2015 Preview
+ result.return_value[0][property_name]
+ else
+ # WMF April 2015 Preview
+ result.return_value[property_name]
+ end
end
+ def create_reboot_resource
+ @reboot_resource = Chef::Resource::Reboot.new(
+ "Reboot for #{@new_resource.name}",
+ run_context,
+ ).tap do |r|
+ r.reason("Reboot for #{@new_resource.resource}.")
+ end
+ end
+
+ def reboot_if_required
+ reboot_action = @new_resource.reboot_action
+ unless @reboot_resource.nil?
+ case reboot_action
+ when :nothing
+ Chef::Log.debug("A reboot was requested by the DSC resource, but reboot_action is :nothing.")
+ Chef::Log.debug("This dsc_resource will not reboot the node.")
+ else
+ Chef::Log.debug("Requesting node reboot with #{reboot_action}.")
+ @reboot_resource.run_action(reboot_action)
+ end
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb
index a75e68a475..3577c23e7b 100644
--- a/lib/chef/provider/dsc_script.rb
+++ b/lib/chef/provider/dsc_script.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/util/powershell/cmdlet'
-require 'chef/util/dsc/configuration_generator'
-require 'chef/util/dsc/local_configuration_manager'
-require 'chef/util/path_helper'
+require "chef/util/powershell/cmdlet"
+require "chef/util/dsc/configuration_generator"
+require "chef/util/dsc/local_configuration_manager"
+require "chef/util/path_helper"
class Chef
class Provider
@@ -65,12 +65,12 @@ class Chef
def define_resource_requirements
requirements.assert(:run) do |a|
err = [
- 'Could not find PowerShell DSC support on the system',
+ "Could not find PowerShell DSC support on the system",
powershell_info_str,
"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
@@ -92,14 +92,14 @@ class Chef
shellout_flags = {
:cwd => @dsc_resource.cwd,
:environment => @dsc_resource.environment,
- :timeout => @dsc_resource.timeout
+ :timeout => @dsc_resource.timeout,
}
begin
configuration_document = generate_configuration_document(config_directory, configuration_flags)
@operations[operation].call(config_manager, configuration_document, shellout_flags)
rescue Exception => e
- Chef::Log.error("DSC operation failed: #{e.message.to_s}")
+ Chef::Log.error("DSC operation failed: #{e.message}")
raise e
ensure
::FileUtils.rm_rf(config_directory)
@@ -119,7 +119,7 @@ class Chef
shellout_flags = {
:cwd => @dsc_resource.cwd,
:environment => @dsc_resource.environment,
- :timeout => @dsc_resource.timeout
+ :timeout => @dsc_resource.timeout,
}
generator = Chef::Util::DSC::ConfigurationGenerator.new(@run_context.node, config_directory)
@@ -129,7 +129,7 @@ class Chef
else
# If code is also not provided, we mimic what the other script resources do (execute nothing)
Chef::Log.warn("Neither code or command were provided for dsc_resource[#{@dsc_resource.name}].") unless @dsc_resource.code
- generator.configuration_document_from_script_code(@dsc_resource.code || '', configuration_flags, @dsc_resource.imports, shellout_flags)
+ generator.configuration_document_from_script_code(@dsc_resource.code || "", configuration_flags, @dsc_resource.imports, shellout_flags)
end
end
@@ -138,7 +138,7 @@ class Chef
@dsc_resource.configuration_data_script
elsif @dsc_resource.configuration_data
configuration_data_path = "#{config_directory}/chef_dsc_config_data.psd1"
- ::File.open(configuration_data_path, 'wt') do | script |
+ ::File.open(configuration_data_path, "wt") do | script |
script.write(@dsc_resource.configuration_data)
end
configuration_data_path
@@ -164,7 +164,7 @@ class Chef
@dsc_resources_info.map do |resource|
if resource.changes_state?
# We ignore the last log message because it only contains the time it took, which looks weird
- cleaned_messages = resource.change_log[0..-2].map { |c| c.sub(/^#{Regexp.escape(resource.name)}/, '').strip }
+ cleaned_messages = resource.change_log[0..-2].map { |c| c.sub(/^#{Regexp.escape(resource.name)}/, "").strip }
"converge DSC resource #{resource.name} by #{cleaned_messages.find_all{ |c| c != ''}.join("\n")}"
else
# This is needed because a dsc script can have resources that are both converged and not
@@ -177,7 +177,7 @@ class Chef
if run_context && run_context.node[:languages] && run_context.node[:languages][:powershell]
install_info = "Powershell #{run_context.node[:languages][:powershell][:version]} was found on the system."
else
- install_info = 'Powershell was not found.'
+ install_info = "Powershell was not found."
end
end
end
diff --git a/lib/chef/provider/env.rb b/lib/chef/provider/env.rb
index cf75ff7d85..e8ac88e8c4 100644
--- a/lib/chef/provider/env.rb
+++ b/lib/chef/provider/env.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/provider'
-require 'chef/mixin/command'
-require 'chef/resource/env'
+require "chef/provider"
+require "chef/mixin/command"
+require "chef/resource/env"
class Chef
class Provider
@@ -48,7 +48,7 @@ class Chef
end
def env_value(key_name)
- raise Chef::Exceptions::Env, "#{self.to_s} provider does not implement env_value!"
+ raise Chef::Exceptions::Env, "#{self} provider does not implement env_value!"
end
def env_key_exists(key_name)
@@ -108,15 +108,15 @@ class Chef
not new_values.include?(item)
end.join(@new_resource.delim)
- if new_value.empty?
- return false #nothing left here, delete the key
- else
- old_value = @new_resource.value(new_value)
- create_env
- Chef::Log.debug("#{@new_resource} deleted #{old_value} element")
- @new_resource.updated_by_last_action(true)
- return true #we removed the element and updated; do not delete the key
- end
+ if new_value.empty?
+ return false #nothing left here, delete the key
+ else
+ old_value = @new_resource.value(new_value)
+ create_env
+ Chef::Log.debug("#{@new_resource} deleted #{old_value} element")
+ @new_resource.updated_by_last_action(true)
+ return true #we removed the element and updated; do not delete the key
+ end
end
end
@@ -141,11 +141,11 @@ class Chef
end
def create_env
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :#{@new_resource.action}"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :#{@new_resource.action}"
end
def delete_env
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :delete"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :delete"
end
def modify_env
@@ -164,6 +164,6 @@ class Chef
def new_values
@new_values ||= @new_resource.value.split(@new_resource.delim)
end
- end
+ end
end
end
diff --git a/lib/chef/provider/env/windows.rb b/lib/chef/provider/env/windows.rb
index 56cebdb888..ec05420471 100644
--- a/lib/chef/provider/env/windows.rb
+++ b/lib/chef/provider/env/windows.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/mixin/windows_env_helper'
+require "chef/mixin/windows_env_helper"
class Chef
class Provider
@@ -36,7 +36,7 @@ class Chef
obj.variablevalue = @new_resource.value
obj.put_
value = @new_resource.value
- value = expand_path(value) if @new_resource.key_name.upcase == 'PATH'
+ value = expand_path(value) if @new_resource.key_name.upcase == "PATH"
ENV[@new_resource.key_name] = value
broadcast_env_change
end
diff --git a/lib/chef/provider/erl_call.rb b/lib/chef/provider/erl_call.rb
index f5855bcce6..dc3a8ea50e 100644
--- a/lib/chef/provider/erl_call.rb
+++ b/lib/chef/provider/erl_call.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/mixin/command'
-require 'chef/provider'
+require "chef/log"
+require "chef/mixin/command"
+require "chef/provider"
class Chef
class Provider
@@ -89,7 +89,7 @@ class Chef
end
# fail if the first 4 characters aren't "{ok,"
- unless stdout_output[0..3].include?('{ok,')
+ unless stdout_output[0..3].include?("{ok,")
raise Chef::Exceptions::ErlCall, stdout_output
end
diff --git a/lib/chef/provider/execute.rb b/lib/chef/provider/execute.rb
index b44112c19e..b291786391 100644
--- a/lib/chef/provider/execute.rb
+++ b/lib/chef/provider/execute.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/provider'
-require 'forwardable'
+require "chef/log"
+require "chef/provider"
+require "forwardable"
class Chef
class Provider
@@ -41,7 +41,7 @@ class Chef
def define_resource_requirements
# @todo: this should change to raise in some appropriate major version bump.
if creates && creates_relative? && !cwd
- Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail (CHEF-3819)"
+ Chef::Log.warn "Providing a relative path for the creates attribute without the cwd is deprecated and will be changed to fail in the future (CHEF-3819)"
end
end
@@ -58,7 +58,16 @@ class Chef
end
converge_by("execute #{description}") do
- result = shell_out!(command, opts)
+ begin
+ shell_out!(command, opts)
+ rescue Mixlib::ShellOut::ShellCommandFailed
+ if sensitive?
+ raise Mixlib::ShellOut::ShellCommandFailed,
+ "Command execution failed. STDOUT/STDERR suppressed for sensitive resource"
+ else
+ raise
+ end
+ end
Chef::Log.info("#{new_resource} ran successfully")
end
end
@@ -69,6 +78,14 @@ class Chef
!!new_resource.sensitive
end
+ def live_stream?
+ Chef::Config[:stream_execute_output] || !!new_resource.live_stream
+ end
+
+ def stream_to_stdout?
+ STDOUT.tty? && !Chef::Config[:daemon]
+ end
+
def opts
opts = {}
opts[:timeout] = timeout
@@ -80,8 +97,12 @@ class Chef
opts[:umask] = umask if umask
opts[:log_level] = :info
opts[:log_tag] = new_resource.to_s
- if STDOUT.tty? && !Chef::Config[:daemon] && Chef::Log.info? && !sensitive?
- opts[:live_stream] = STDOUT
+ if (Chef::Log.info? || live_stream?) && !sensitive?
+ if run_context.events.formatter?
+ opts[:live_stream] = Chef::EventDispatch::EventsOutputStream.new(run_context.events, :name => :execute)
+ elsif stream_to_stdout?
+ opts[:live_stream] = STDOUT
+ end
end
opts
end
diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb
index c070d29458..a605682718 100644
--- a/lib/chef/provider/file.rb
+++ b/lib/chef/provider/file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Lamont Granquist (<lamont@opscode.com>)
-# Copyright:: Copyright (c) 2008-2013 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,20 +17,22 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/log'
-require 'chef/resource/file'
-require 'chef/provider'
-require 'etc'
-require 'fileutils'
-require 'chef/scan_access_control'
-require 'chef/mixin/checksum'
-require 'chef/mixin/file_class'
-require 'chef/util/backup'
-require 'chef/util/diff'
-require 'chef/deprecation/provider/file'
-require 'chef/deprecation/warnings'
-require 'chef/file_content_management/deploy'
+require "chef/config"
+require "chef/log"
+require "chef/resource/file"
+require "chef/provider"
+require "etc"
+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"
# The Tao of File Providers:
# - the content provider must always return a tempfile that we can delete/mv
@@ -244,7 +246,7 @@ class Chef
else
[ Chef::Exceptions::FileTypeMismatch,
"File #{path} exists, but is a #{file_type_string(@new_resource.path)}, set force_unlink to true to remove",
- "Assuming #{file_type_string(@new_resource.path)} at #{@new_resource.path} would have been removed by a previous resource"
+ "Assuming #{file_type_string(@new_resource.path)} at #{@new_resource.path} would have been removed by a previous resource",
]
end
end
@@ -265,7 +267,7 @@ class Chef
[ Chef::Exceptions::FileTypeMismatch,
"File #{path} exists, but is a symlink to #{real_path} which is a #{file_type_string(real_path)}. " +
"Disable manage_symlink_source and set force_unlink to remove it.",
- "Assuming symlink #{path} or source file #{real_path} would have been fixed by a previous resource"
+ "Assuming symlink #{path} or source file #{real_path} would have been fixed by a previous resource",
]
end
rescue Errno::ELOOP
@@ -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/file/content.rb b/lib/chef/provider/file/content.rb
index f82bc49db4..96dda1bcbc 100644
--- a/lib/chef/provider/file/content.rb
+++ b/lib/chef/provider/file/content.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/file_content_management/content_base'
-require 'chef/file_content_management/tempfile'
+require "chef/file_content_management/content_base"
+require "chef/file_content_management/tempfile"
class Chef
class Provider
diff --git a/lib/chef/provider/git.rb b/lib/chef/provider/git.rb
index 8418f22933..a0f6d076df 100644
--- a/lib/chef/provider/git.rb
+++ b/lib/chef/provider/git.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/exceptions'
-require 'chef/log'
-require 'chef/provider'
-require 'fileutils'
+require "chef/exceptions"
+require "chef/log"
+require "chef/provider"
+require "fileutils"
class Chef
class Provider
@@ -105,7 +105,7 @@ class Chef
end
def git_minor_version
- @git_minor_version ||= Gem::Version.new(shell_out!('git --version', run_options).stdout.split.last)
+ @git_minor_version ||= Gem::Version.new(shell_out!("git --version", run_options).stdout.split.last)
end
def existing_git_clone?
@@ -113,14 +113,14 @@ class Chef
end
def target_dir_non_existent_or_empty?
- !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
+ !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == [".",".."]
end
def find_current_revision
Chef::Log.debug("#{@new_resource} finding current git revision")
if ::File.exist?(::File.join(cwd, ".git"))
# 128 is returned when we're not in a git repo. this is fine
- result = shell_out!('git rev-parse HEAD', :cwd => cwd, :returns => [0,128]).stdout.strip
+ result = shell_out!("git rev-parse HEAD", :cwd => cwd, :returns => [0,128]).stdout.strip
end
sha_hash?(result) ? result : nil
end
@@ -141,9 +141,9 @@ class Chef
remote = @new_resource.remote
args = []
- args << "-o #{remote}" unless remote == 'origin'
+ args << "-o #{remote}" unless remote == "origin"
args << "--depth #{@new_resource.depth}" if @new_resource.depth
- args << "--no-single-branch" if @new_resource.depth and git_minor_version >= Gem::Version.new('1.7.10')
+ args << "--no-single-branch" if @new_resource.depth and git_minor_version >= Gem::Version.new("1.7.10")
Chef::Log.info "#{@new_resource} cloning repo #{@new_resource.repository} to #{@new_resource.destination}"
@@ -250,18 +250,18 @@ class Chef
# Using such a degenerate annotated tag would be very
# confusing. We avoid the issue by disallowing the use of
# annotated tags named 'HEAD'.
- if rev_search_pattern != 'HEAD'
- found = find_revision(refs, @new_resource.revision, '^{}')
+ if rev_search_pattern != "HEAD"
+ found = find_revision(refs, @new_resource.revision, "^{}")
else
- found = refs_search(refs, 'HEAD')
+ found = refs_search(refs, "HEAD")
end
found = find_revision(refs, @new_resource.revision) if found.empty?
found.size == 1 ? found.first[0] : nil
end
def find_revision(refs, revision, suffix="")
- found = refs_search(refs, rev_match_pattern('refs/tags/', revision) + suffix)
- found = refs_search(refs, rev_match_pattern('refs/heads/', revision) + suffix) if found.empty?
+ found = refs_search(refs, rev_match_pattern("refs/tags/", revision) + suffix)
+ found = refs_search(refs, rev_match_pattern("refs/heads/", revision) + suffix) if found.empty?
found = refs_search(refs, revision + suffix) if found.empty?
found
end
@@ -275,15 +275,15 @@ class Chef
end
def rev_search_pattern
- if ['', 'HEAD'].include? @new_resource.revision
- 'HEAD'
+ if ["", "HEAD"].include? @new_resource.revision
+ "HEAD"
else
- @new_resource.revision + '*'
+ @new_resource.revision + "*"
end
end
def git_ls_remote(rev_pattern)
- command = git(%Q(ls-remote "#{@new_resource.repository}" "#{rev_pattern}"))
+ command = git(%Q{ls-remote "#{@new_resource.repository}" "#{rev_pattern}"})
shell_out!(command, run_options).stdout
end
@@ -300,15 +300,15 @@ class Chef
# Certain versions of `git` misbehave if git configuration is
# inaccessible in $HOME. We need to ensure $HOME matches the
# user who is executing `git` not the user running Chef.
- env['HOME'] = begin
- require 'etc'
+ env["HOME"] = begin
+ require "etc"
Etc.getpwnam(@new_resource.user).dir
rescue ArgumentError # user not found
raise Chef::Exceptions::User, "Could not determine HOME for specified user '#{@new_resource.user}' for resource '#{@new_resource.name}'"
end
end
run_opts[:group] = @new_resource.group if @new_resource.group
- env['GIT_SSH'] = @new_resource.ssh_wrapper if @new_resource.ssh_wrapper
+ env["GIT_SSH"] = @new_resource.ssh_wrapper if @new_resource.ssh_wrapper
run_opts[:log_tag] = @new_resource.to_s
run_opts[:timeout] = @new_resource.timeout if @new_resource.timeout
env.merge!(@new_resource.environment) if @new_resource.environment
diff --git a/lib/chef/provider/group.rb b/lib/chef/provider/group.rb
index a802758dce..2aa9889b17 100644
--- a/lib/chef/provider/group.rb
+++ b/lib/chef/provider/group.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/provider'
-require 'chef/mixin/shell_out'
-require 'chef/mixin/command'
-require 'etc'
+require "chef/provider"
+require "chef/mixin/shell_out"
+require "chef/mixin/command"
+require "etc"
class Chef
class Provider
@@ -125,7 +125,7 @@ class Chef
def action_create
case @group_exists
when false
- converge_by("create #{@new_resource.group_name}") do
+ converge_by("create group #{@new_resource.group_name}") do
create_group
Chef::Log.info("#{@new_resource} created")
end
diff --git a/lib/chef/provider/group/aix.rb b/lib/chef/provider/group/aix.rb
index 6ac9d03357..1059208ed8 100644
--- a/lib/chef/provider/group/aix.rb
+++ b/lib/chef/provider/group/aix.rb
@@ -16,13 +16,14 @@
# limitations under the License.
#
-require 'chef/provider/group/groupadd'
-require 'chef/mixin/shell_out'
+require "chef/provider/group/groupadd"
+require "chef/mixin/shell_out"
class Chef
class Provider
class Group
class Aix < Chef::Provider::Group::Groupadd
+ provides :group, platform: "aix"
def required_binaries
[ "/usr/bin/mkgroup",
@@ -71,7 +72,7 @@ class Chef
{ :gid => "id" }.sort { |a,b| a[0] <=> b[0] }.each do |field, option|
if @current_resource.send(field) != @new_resource.send(field)
if @new_resource.send(field)
- Chef::Log.debug("#{@new_resource} setting #{field.to_s} to #{@new_resource.send(field)}")
+ Chef::Log.debug("#{@new_resource} setting #{field} to #{@new_resource.send(field)}")
opts << " '#{option}=#{@new_resource.send(field)}'"
end
end
diff --git a/lib/chef/provider/group/dscl.rb b/lib/chef/provider/group/dscl.rb
index d7e8f2e827..0b39571458 100644
--- a/lib/chef/provider/group/dscl.rb
+++ b/lib/chef/provider/group/dscl.rb
@@ -53,14 +53,14 @@ class Chef
if group_info
group_info.each_line do |line|
- key, val = line.split(': ')
+ key, val = line.split(": ")
val.strip! if val
case key.downcase
- when 'primarygroupid'
+ when "primarygroupid"
@new_resource.gid(val) unless @new_resource.gid
@current_resource.gid(val)
- when 'groupmembership'
- @current_resource.members(val.split(' '))
+ when "groupmembership"
+ @current_resource.members(val.split(" "))
end
end
end
diff --git a/lib/chef/provider/group/gpasswd.rb b/lib/chef/provider/group/gpasswd.rb
index 521affac11..011a9d1e63 100644
--- a/lib/chef/provider/group/gpasswd.rb
+++ b/lib/chef/provider/group/gpasswd.rb
@@ -16,12 +16,13 @@
# limitations under the License.
#
-require 'chef/provider/group/groupadd'
+require "chef/provider/group/groupadd"
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/groupadd.rb b/lib/chef/provider/group/groupadd.rb
index cb480aab54..684df8455e 100644
--- a/lib/chef/provider/group/groupadd.rb
+++ b/lib/chef/provider/group/groupadd.rb
@@ -96,15 +96,15 @@ class Chef
end
def add_member(member)
- raise Chef::Exceptions::Group, "you must override add_member in #{self.to_s}"
+ raise Chef::Exceptions::Group, "you must override add_member in #{self}"
end
def remove_member(member)
- raise Chef::Exceptions::Group, "you must override remove_member in #{self.to_s}"
+ raise Chef::Exceptions::Group, "you must override remove_member in #{self}"
end
def set_members(members)
- raise Chef::Exceptions::Group, "you must override set_members in #{self.to_s}"
+ raise Chef::Exceptions::Group, "you must override set_members in #{self}"
end
# Little bit of magic as per Adam's useradd provider to pull the assign the command line flags
@@ -117,7 +117,7 @@ class Chef
if @current_resource.send(field) != @new_resource.send(field)
if @new_resource.send(field)
opts << " #{option} '#{@new_resource.send(field)}'"
- Chef::Log.debug("#{@new_resource} set #{field.to_s} to #{@new_resource.send(field)}")
+ Chef::Log.debug("#{@new_resource} set #{field} to #{@new_resource.send(field)}")
end
end
end
@@ -125,7 +125,7 @@ class Chef
end
def groupadd_options
- opts = ''
+ opts = ""
opts << " -r" if @new_resource.system
opts << " -o" if @new_resource.non_unique
opts
diff --git a/lib/chef/provider/group/pw.rb b/lib/chef/provider/group/pw.rb
index 7a66ab4d69..e4c129ba9f 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..42e525169f 100644
--- a/lib/chef/provider/group/suse.rb
+++ b/lib/chef/provider/group/suse.rb
@@ -16,12 +16,14 @@
# limitations under the License.
#
-require 'chef/provider/group/groupadd'
+require "chef/provider/group/groupadd"
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 e50e13c443..f4f3ac55ae 100644
--- a/lib/chef/provider/group/usermod.rb
+++ b/lib/chef/provider/group/usermod.rb
@@ -16,14 +16,15 @@
# limitations under the License.
#
-require 'chef/provider/group/groupadd'
+require "chef/provider/group/groupadd"
class Chef
class Provider
class Group
class Usermod < Chef::Provider::Group::Groupadd
- provides :group, os: "openbsd"
+ provides :group, os: %w{openbsd solaris2 hpux}
+ provides :group, platform: "opensuse"
def load_current_resource
super
@@ -40,13 +41,13 @@ class Chef
requirements.assert(:modify, :manage) do |a|
a.assertion { @new_resource.members.empty? || @new_resource.append }
- a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self.to_s}, must set append true in group"
+ a.failure_message Chef::Exceptions::Group, "setting group members directly is not supported by #{self}, must set append true in group"
# No whyrun alternative - this action is simply not supported.
end
requirements.assert(:all_actions) do |a|
a.assertion { @new_resource.excluded_members.empty? }
- a.failure_message Chef::Exceptions::Group, "excluded_members is not supported by #{self.to_s}"
+ a.failure_message Chef::Exceptions::Group, "excluded_members is not supported by #{self}"
# No whyrun alternative - this action is simply not supported.
end
end
@@ -61,7 +62,7 @@ class Chef
add_member(member)
end
else
- raise Chef::Exceptions::UnsupportedAction, "Setting members directly is not supported by #{self.to_s}"
+ raise Chef::Exceptions::UnsupportedAction, "Setting members directly is not supported by #{self}"
end
end
@@ -72,7 +73,7 @@ class Chef
def remove_member(member)
# This provider only supports adding members with
# append. This function should never be called.
- raise Chef::Exceptions::UnsupportedAction, "Removing members members is not supported by #{self.to_s}"
+ raise Chef::Exceptions::UnsupportedAction, "Removing members members is not supported by #{self}"
end
def append_flags
diff --git a/lib/chef/provider/group/windows.rb b/lib/chef/provider/group/windows.rb
index 54e49b0e06..a665757df4 100644
--- a/lib/chef/provider/group/windows.rb
+++ b/lib/chef/provider/group/windows.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/provider/user'
+require "chef/provider/user"
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require 'chef/util/windows/net_group'
+ require "chef/util/windows/net_group"
end
class Chef
diff --git a/lib/chef/provider/http_request.rb b/lib/chef/provider/http_request.rb
index 61aff434ed..62dc0b0a46 100644
--- a/lib/chef/provider/http_request.rb
+++ b/lib/chef/provider/http_request.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'tempfile'
-require 'chef/http/simple'
+require "tempfile"
+require "chef/http/simple"
class Chef
class Provider
@@ -42,7 +42,7 @@ class Chef
# and false for a "304 Not Modified" response
modified = @http.head(
"#{@new_resource.url}",
- @new_resource.headers
+ @new_resource.headers,
)
Chef::Log.info("#{@new_resource} HEAD to #{@new_resource.url} successful")
Chef::Log.debug("#{@new_resource} HEAD request response: #{modified}")
@@ -59,7 +59,7 @@ class Chef
message = check_message(@new_resource.message)
body = @http.get(
"#{@new_resource.url}",
- @new_resource.headers
+ @new_resource.headers,
)
Chef::Log.info("#{@new_resource} GET to #{@new_resource.url} successful")
Chef::Log.debug("#{@new_resource} GET request response: #{body}")
@@ -73,7 +73,7 @@ class Chef
body = @http.put(
"#{@new_resource.url}",
message,
- @new_resource.headers
+ @new_resource.headers,
)
Chef::Log.info("#{@new_resource} PUT to #{@new_resource.url} successful")
Chef::Log.debug("#{@new_resource} PUT request response: #{body}")
@@ -87,7 +87,7 @@ class Chef
body = @http.post(
"#{@new_resource.url}",
message,
- @new_resource.headers
+ @new_resource.headers,
)
Chef::Log.info("#{@new_resource} POST to #{@new_resource.url} message: #{message.inspect} successful")
Chef::Log.debug("#{@new_resource} POST request response: #{body}")
@@ -99,7 +99,7 @@ class Chef
converge_by("#{@new_resource} DELETE to #{@new_resource.url}") do
body = @http.delete(
"#{@new_resource.url}",
- @new_resource.headers
+ @new_resource.headers,
)
@new_resource.updated_by_last_action(true)
Chef::Log.info("#{@new_resource} DELETE to #{@new_resource.url} successful")
diff --git a/lib/chef/provider/ifconfig.rb b/lib/chef/provider/ifconfig.rb
index 06080c90c3..95f4b979f5 100644
--- a/lib/chef/provider/ifconfig.rb
+++ b/lib/chef/provider/ifconfig.rb
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/mixin/command'
-require 'chef/mixin/shell_out'
-require 'chef/provider'
-require 'chef/resource/file'
-require 'chef/exceptions'
-require 'erb'
+require "chef/log"
+require "chef/mixin/command"
+require "chef/mixin/shell_out"
+require "chef/provider"
+require "chef/resource/file"
+require "chef/exceptions"
+require "erb"
# Recipe example:
#
@@ -39,6 +39,8 @@ require 'erb'
class Chef
class Provider
class Ifconfig < Chef::Provider
+ provides :ifconfig
+
include Chef::Mixin::ShellOut
include Chef::Mixin::Command
@@ -107,7 +109,7 @@ class Chef
command = add_command
converge_by ("run #{command} to add #{@new_resource}") do
run_command(
- :command => command
+ :command => command,
)
Chef::Log.info("#{@new_resource} added")
end
@@ -125,7 +127,7 @@ class Chef
command = enable_command
converge_by ("run #{command} to enable #{@new_resource}") do
run_command(
- :command => command
+ :command => command,
)
Chef::Log.info("#{@new_resource} enabled")
end
@@ -139,7 +141,7 @@ class Chef
command = delete_command
converge_by ("run #{command} to delete #{@new_resource}") do
run_command(
- :command => command
+ :command => command,
)
Chef::Log.info("#{@new_resource} deleted")
end
@@ -156,7 +158,7 @@ class Chef
command = disable_command
converge_by ("run #{command} to disable #{@new_resource}") do
run_command(
- :command => command
+ :command => command,
)
Chef::Log.info("#{@new_resource} disabled")
end
@@ -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
@@ -216,7 +218,7 @@ class Chef
end
def loopback_device
- 'lo'
+ "lo"
end
end
end
diff --git a/lib/chef/provider/ifconfig/aix.rb b/lib/chef/provider/ifconfig/aix.rb
index 8fead44bc6..30e702fe10 100644
--- a/lib/chef/provider/ifconfig/aix.rb
+++ b/lib/chef/provider/ifconfig/aix.rb
@@ -16,12 +16,13 @@
# limitations under the License.
#
-require 'chef/provider/ifconfig'
+require "chef/provider/ifconfig"
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..3885e55998 100644
--- a/lib/chef/provider/ifconfig/debian.rb
+++ b/lib/chef/provider/ifconfig/debian.rb
@@ -16,13 +16,15 @@
# limitations under the License.
#
-require 'chef/provider/ifconfig'
-require 'chef/util/file_edit'
+require "chef/provider/ifconfig"
+require "chef/util/file_edit"
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..8c02c1be07 100644
--- a/lib/chef/provider/ifconfig/redhat.rb
+++ b/lib/chef/provider/ifconfig/redhat.rb
@@ -16,12 +16,13 @@
# limitations under the License.
#
-require 'chef/provider/ifconfig'
+require "chef/provider/ifconfig"
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/link.rb b/lib/chef/provider/link.rb
index c811c13cdf..571610aeed 100644
--- a/lib/chef/provider/link.rb
+++ b/lib/chef/provider/link.rb
@@ -16,13 +16,13 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/log'
-require 'chef/mixin/file_class'
-require 'chef/resource/link'
-require 'chef/provider'
-require 'chef/scan_access_control'
-require 'chef/util/path_helper'
+require "chef/config"
+require "chef/log"
+require "chef/mixin/file_class"
+require "chef/resource/link"
+require "chef/provider"
+require "chef/scan_access_control"
+require "chef/util/path_helper"
class Chef
class Provider
@@ -75,18 +75,18 @@ class Chef
a.assertion do
if @current_resource.to
@current_resource.link_type == @new_resource.link_type and
- (@current_resource.link_type == :symbolic or @current_resource.to != '')
+ (@current_resource.link_type == :symbolic or @current_resource.to != "")
else
true
end
end
- a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type.to_s} link."
+ a.failure_message Chef::Exceptions::Link, "Cannot delete #{@new_resource} at #{@new_resource.target_file}! Not a #{@new_resource.link_type} link."
a.whyrun("Would assume the link at #{@new_resource.target_file} was previously created")
end
end
def canonicalize(path)
- Chef::Platform.windows? ? path.gsub('/', '\\') : path
+ Chef::Platform.windows? ? path.gsub("/", '\\') : path
end
def action_create
diff --git a/lib/chef/provider/lwrp_base.rb b/lib/chef/provider/lwrp_base.rb
index 492ddda6da..4298a4aa98 100644
--- a/lib/chef/provider/lwrp_base.rb
+++ b/lib/chef/provider/lwrp_base.rb
@@ -18,7 +18,9 @@
# limitations under the License.
#
-require 'chef/provider'
+require "chef/provider"
+require "chef/dsl/recipe"
+require "chef/dsl/include_recipe"
class Chef
class Provider
@@ -27,124 +29,71 @@ class Chef
# Base class from which LWRP providers inherit.
class LWRPBase < Provider
- # Chef::Provider::LWRPBase::InlineResources
- # Implementation of inline resource convergence for LWRP providers. See
- # Provider::LWRPBase.use_inline_resources for a longer explanation.
- #
- # This code is restricted to a module so that it can be selectively
- # applied to providers on an opt-in basis.
- module InlineResources
-
- # Class methods for InlineResources. Overrides the `action` DSL method
- # with one that enables inline resource convergence.
- module ClassMethods
- # Defines an action method on the provider, using
- # recipe_eval_with_update_check to execute the given block.
- def action(name, &block)
- define_method("action_#{name}") do
- recipe_eval_with_update_check(&block)
- end
- end
- end
-
- # Executes the given block in a temporary run_context with its own
- # resource collection. After the block is executed, any resources
- # declared inside are converged, and if any are updated, the
- # new_resource will be marked updated.
- def recipe_eval_with_update_check(&block)
- saved_run_context = @run_context
- temp_run_context = @run_context.dup
- @run_context = temp_run_context
- @run_context.resource_collection = Chef::ResourceCollection.new
-
- return_value = instance_eval(&block)
- Chef::Runner.new(@run_context).converge
- return_value
- ensure
- @run_context = saved_run_context
- if temp_run_context.resource_collection.any? {|r| r.updated? }
- new_resource.updated_by_last_action(true)
- end
- end
-
- end
-
- extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::FromFile
-
include Chef::DSL::Recipe
# These were previously provided by Chef::Mixin::RecipeDefinitionDSLCore.
- # They are not included by its replacment, Chef::DSL::Recipe, but
+ # They are not included by its replacement, Chef::DSL::Recipe, but
# they may be used in existing LWRPs.
include Chef::DSL::PlatformIntrospection
include Chef::DSL::DataQuery
- def self.build_from_file(cookbook_name, filename, run_context)
- provider_class = nil
- provider_name = filename_to_qualified_string(cookbook_name, filename)
+ # Allow include_recipe from within LWRP provider code
+ include Chef::DSL::IncludeRecipe
+
+ # no-op `load_current_resource`. Allows simple LWRP providers to work
+ # without defining this method explicitly (silences
+ # Chef::Exceptions::Override exception)
+ def load_current_resource
+ end
+
+ # class methods
+ class <<self
+ include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::FromFile
+
+ def build_from_file(cookbook_name, filename, run_context)
+ if LWRPBase.loaded_lwrps[filename]
+ Chef::Log.debug("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 325f1b5977..b55e6abc1a 100644
--- a/lib/chef/provider/mdadm.rb
+++ b/lib/chef/provider/mdadm.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/provider'
+require "chef/log"
+require "chef/provider"
class Chef
class Provider
diff --git a/lib/chef/provider/mount.rb b/lib/chef/provider/mount.rb
index 1631d87033..ede36417d1 100644
--- a/lib/chef/provider/mount.rb
+++ b/lib/chef/provider/mount.rb
@@ -17,14 +17,13 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/mixin/shell_out'
-require 'chef/provider'
+require "chef/log"
+require "chef/mixin/shell_out"
+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
@@ -115,12 +118,12 @@ class Chef
# should actually check if the filesystem is mounted (not just return current_resource) and return true/false
def mounted?
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not implement #mounted?"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mounted?"
end
# should check new_resource against current_resource to see if mount options need updating, returns true/false
def mount_options_unchanged?
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not implement #mount_options_unchanged?"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not implement #mount_options_unchanged?"
end
#
@@ -131,28 +134,28 @@ class Chef
# should implement mounting of the filesystem, raises if action does not succeed
def mount_fs
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :mount"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :mount"
end
# should implement unmounting of the filesystem, raises if action does not succeed
def umount_fs
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :umount"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :umount"
end
# should implement remounting of the filesystem (via a -o remount or some other atomic-ish action that isn't
# simply a umount/mount style remount), raises if action does not succeed
def remount_fs
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remount"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remount"
end
# should implement enabling of the filesystem (e.g. in /etc/fstab), raises if action does not succeed
def enable_fs
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :enable"
end
# should implement disabling of the filesystem (e.g. in /etc/fstab), raises if action does not succeed
def disable_fs
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :disable"
end
private
diff --git a/lib/chef/provider/mount/aix.rb b/lib/chef/provider/mount/aix.rb
index 0d7e11a1b8..a6fccfadf4 100644
--- a/lib/chef/provider/mount/aix.rb
+++ b/lib/chef/provider/mount/aix.rb
@@ -16,12 +16,13 @@
# limitations under the License.
#
-require 'chef/provider/mount'
+require "chef/provider/mount"
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
@@ -98,13 +99,13 @@ class Chef
end
command << case @new_resource.device_type
- when :device
- " #{device_real}"
- when :label
- " -L #{@new_resource.device}"
- when :uuid
- " -U #{@new_resource.device}"
- end
+ when :device
+ " #{device_real}"
+ when :label
+ " -L #{@new_resource.device}"
+ when :uuid
+ " -U #{@new_resource.device}"
+ end
command << " #{@new_resource.mount_point}"
shell_out!(command)
Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
@@ -173,7 +174,7 @@ class Chef
end
end
+ end
end
- end
end
end
diff --git a/lib/chef/provider/mount/mount.rb b/lib/chef/provider/mount/mount.rb
index 0a6e269d2d..e0bddb454a 100644
--- a/lib/chef/provider/mount/mount.rb
+++ b/lib/chef/provider/mount/mount.rb
@@ -16,14 +16,16 @@
# limitations under the License.
#
-require 'chef/provider/mount'
-require 'chef/log'
+require "chef/provider/mount"
+require "chef/log"
class Chef
class Provider
class Mount
class Mount < Chef::Provider::Mount
+ provides :mount
+
def initialize(new_resource, run_context)
super
@real_device = nil
@@ -102,13 +104,13 @@ class Chef
command = "mount -t #{@new_resource.fstype}"
command << " -o #{@new_resource.options.join(',')}" unless @new_resource.options.nil? || @new_resource.options.empty?
command << case @new_resource.device_type
- when :device
- " #{device_real}"
- when :label
- " -L #{@new_resource.device}"
- when :uuid
- " -U #{@new_resource.device}"
- end
+ when :device
+ " #{device_real}"
+ when :label
+ " -L #{@new_resource.device}"
+ when :uuid
+ " -U #{@new_resource.device}"
+ end
command << " #{@new_resource.mount_point}"
shell_out!(command)
Chef::Log.debug("#{@new_resource} is mounted at #{@new_resource.mount_point}")
@@ -168,7 +170,7 @@ class Chef
found = false
::File.readlines("/etc/fstab").reverse_each do |line|
if !found && line =~ /^#{device_fstab_regex}\s+#{Regexp.escape(@new_resource.mount_point)}\s/
- found = true
+ found = true
Chef::Log.debug("#{@new_resource} is removed from fstab")
next
else
@@ -191,7 +193,7 @@ class Chef
def device_should_exist?
( @new_resource.device != "none" ) &&
( not network_device? ) &&
- ( not %w[ cgroup tmpfs fuse ].include? @new_resource.fstype )
+ ( not %w{ cgroup tmpfs fuse }.include? @new_resource.fstype )
end
private
@@ -257,9 +259,9 @@ class Chef
def mount_options_unchanged?
@current_resource.fstype == @new_resource.fstype and
- @current_resource.options == @new_resource.options and
- @current_resource.dump == @new_resource.dump and
- @current_resource.pass == @new_resource.pass
+ @current_resource.options == @new_resource.options and
+ @current_resource.dump == @new_resource.dump and
+ @current_resource.pass == @new_resource.pass
end
end
diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb
index d8cec24138..76f1ac0137 100644
--- a/lib/chef/provider/mount/solaris.rb
+++ b/lib/chef/provider/mount/solaris.rb
@@ -18,18 +18,20 @@
# limitations under the License.
#
-require 'chef/provider/mount'
-require 'chef/log'
-require 'forwardable'
+require "chef/provider/mount"
+require "chef/log"
+require "forwardable"
class Chef
class Provider
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
+ VFSTAB = "/etc/vfstab".freeze
def_delegator :@new_resource, :device, :device
def_delegator :@new_resource, :device_type, :device_type
@@ -56,7 +58,7 @@ class Chef
a.whyrun("Assuming device #{device} would have been created")
end
- unless fsck_device == '-'
+ unless fsck_device == "-"
requirements.assert(:mount, :remount) do |a|
a.assertion { ::File.exist?(fsck_device) }
a.failure_message(Chef::Exceptions::Mount, "Device #{fsck_device} does not exist")
@@ -73,7 +75,7 @@ class Chef
def mount_fs
actual_options = options || []
- actual_options.delete('noauto')
+ actual_options.delete("noauto")
command = "mount -F #{fstype}"
command << " -o #{actual_options.join(',')}" unless actual_options.empty?
command << " #{device} #{mount_point}"
@@ -87,8 +89,8 @@ class Chef
def remount_fs
# FIXME: Should remount always do the remount or only if the options change?
actual_options = options || []
- actual_options.delete('noauto')
- mount_options = actual_options.empty? ? '' : ",#{actual_options.join(',')}"
+ actual_options.delete("noauto")
+ mount_options = actual_options.empty? ? "" : ",#{actual_options.join(',')}"
shell_out!("mount -o remount#{mount_options} #{mount_point}")
end
@@ -115,7 +117,7 @@ class Chef
end
def etc_tempfile
- yield Tempfile.open('vfstab', '/etc')
+ yield Tempfile.open("vfstab", "/etc")
end
def mount_options_unchanged?
@@ -127,7 +129,7 @@ class Chef
current_options == new_options &&
current_resource.dump == dump &&
current_resource.pass == pass &&
- current_resource.options.include?('noauto') == !mount_at_boot?
+ current_resource.options.include?("noauto") == !mount_at_boot?
end
def update_current_resource_state
@@ -148,7 +150,7 @@ class Chef
# /dev/dsk/c1t0d0s0 on / type ufs read/write/setuid/devices/intr/largefiles/logging/xattr/onerror=panic/dev=700040 on Tue May 1 11:33:55 2012
def mounted?
mounted = false
- shell_out!('mount -v').stdout.each_line do |line|
+ shell_out!("mount -v").stdout.each_line do |line|
case line
when /^#{device_regex}\s+on\s+#{Regexp.escape(mount_point)}\s+/
Chef::Log.debug("Special device #{device} is mounted as #{mount_point}")
@@ -180,14 +182,14 @@ class Chef
options = Regexp.last_match[4]
# Store the 'mount at boot' column from vfstab as the 'noauto' option
# in current_resource.options (linux style)
- if Regexp.last_match[3] == 'no'
+ if Regexp.last_match[3] == "no"
if options.nil? || options.empty?
- options = 'noauto'
+ options = "noauto"
else
- options += ',noauto'
+ options += ",noauto"
end
end
- pass = (Regexp.last_match[2] == '-') ? 0 : Regexp.last_match[2].to_i
+ pass = (Regexp.last_match[2] == "-") ? 0 : Regexp.last_match[2].to_i
Chef::Log.debug("Found mount #{device} to #{mount_point} in #{VFSTAB}")
next
when /^[-\/\w]+\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+/
@@ -200,16 +202,16 @@ class Chef
end
def device_should_exist?
- !%w(tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs vxfs).include?(fstype)
+ !%w{tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs vxfs}.include?(fstype)
end
def mount_at_boot?
- options.nil? || !options.include?('noauto')
+ options.nil? || !options.include?("noauto")
end
def vfstab_write(contents)
etc_tempfile do |f|
- f.write(contents.join(''))
+ f.write(contents.join(""))
f.close
# move, preserving modes of destination file
mover = Chef::FileContentManagement::Deploy.strategy(true)
@@ -219,13 +221,13 @@ class Chef
def vfstab_entry
actual_options = unless options.nil?
- tempops = options.dup
- tempops.delete('noauto')
- tempops
- end
- autostr = mount_at_boot? ? 'yes' : 'no'
- passstr = pass == 0 ? '-' : pass
- optstr = (actual_options.nil? || actual_options.empty?) ? '-' : actual_options.join(',')
+ tempops = options.dup
+ tempops.delete("noauto")
+ tempops
+ end
+ autostr = mount_at_boot? ? "yes" : "no"
+ passstr = pass == 0 ? "-" : pass
+ optstr = (actual_options.nil? || actual_options.empty?) ? "-" : actual_options.join(",")
"\n#{device}\t#{fsck_device}\t#{mount_point}\t#{fstype}\t#{passstr}\t#{autostr}\t#{optstr}\n"
end
@@ -234,7 +236,7 @@ class Chef
found = false
::File.readlines(VFSTAB).reverse_each do |line|
if !found && line =~ /^#{device_regex}\s+\S+\s+#{Regexp.escape(mount_point)}/
- found = true
+ found = true
Chef::Log.debug("#{new_resource} is removed from vfstab")
next
end
@@ -252,7 +254,7 @@ class Chef
def options_remove_noauto(temp_options)
new_options = []
new_options += temp_options.nil? ? [] : temp_options
- new_options.delete('noauto')
+ new_options.delete("noauto")
new_options
end
diff --git a/lib/chef/provider/mount/windows.rb b/lib/chef/provider/mount/windows.rb
index 87873474b3..0cf50d07bf 100644
--- a/lib/chef/provider/mount/windows.rb
+++ b/lib/chef/provider/mount/windows.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/provider/mount'
+require "chef/provider/mount"
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require 'chef/util/windows/net_use'
- require 'chef/util/windows/volume'
+ require "chef/util/windows/net_use"
+ require "chef/util/windows/volume"
end
class Chef
diff --git a/lib/chef/provider/ohai.rb b/lib/chef/provider/ohai.rb
index a6b5ab5daa..6535805013 100644
--- a/lib/chef/provider/ohai.rb
+++ b/lib/chef/provider/ohai.rb
@@ -16,11 +16,12 @@
# limitations under the License.
#
-require 'ohai'
+require "ohai"
class Chef
class Provider
class Ohai < Chef::Provider
+ provides :ohai
def whyrun_supported?
true
diff --git a/lib/chef/provider/osx_profile.rb b/lib/chef/provider/osx_profile.rb
new file mode 100644
index 0000000000..071b4b0462
--- /dev/null
+++ b/lib/chef/provider/osx_profile.rb
@@ -0,0 +1,254 @@
+#
+# Author:: Nate Walck (<nate.walck@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/log"
+require "chef/provider"
+require "chef/resource"
+require "chef/resource/file"
+require "uuidtools"
+
+class Chef
+ class Provider
+ class OsxProfile < Chef::Provider
+ include Chef::Mixin::Command
+ provides :osx_profile, os: "darwin"
+ provides :osx_config_profile, os: "darwin"
+
+ def whyrun_supported?
+ true
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::OsxProfile.new(@new_resource.name)
+ @current_resource.profile_name(@new_resource.profile_name)
+
+ all_profiles = get_installed_profiles
+ @new_resource.profile(
+ @new_resource.profile ||
+ @new_resource.profile_name
+ )
+
+ @new_profile_hash = get_profile_hash(@new_resource.profile)
+ @new_profile_hash["PayloadUUID"] =
+ config_uuid(@new_profile_hash) if @new_profile_hash
+
+ if @new_profile_hash
+ @new_profile_identifier = @new_profile_hash["PayloadIdentifier"]
+ else
+ @new_profile_identifier = @new_resource.identifier ||
+ @new_resource.profile_name
+ end
+
+ current_profile = all_profiles["_computerlevel"].find {
+ |item| item["ProfileIdentifier"] ==
+ @new_profile_identifier
+ }
+ @current_resource.profile(current_profile)
+
+ end
+
+ def define_resource_requirements
+ requirements.assert(:remove) do |a|
+ if @new_profile_identifier
+ a.assertion {
+ !@new_profile_identifier.nil? and
+ !@new_profile_identifier.end_with?(".mobileconfig") and
+ /^\w+(?:\.\w+)+$/.match(@new_profile_identifier)
+ }
+ a.failure_message RuntimeError, "when removing using the identifier attribute, it must match the profile identifier"
+ else
+ new_profile_name = @new_resource.profile_name
+ a.assertion {
+ !new_profile_name.end_with?(".mobileconfig") and
+ /^\w+(?:\.\w+)+$/.match(new_profile_name)
+ }
+ a.failure_message RuntimeError, "When removing by resource name, it must match the profile identifier "
+ end
+ end
+
+ requirements.assert(:install) do |a|
+ if @new_profile_hash.is_a?(Hash)
+ a.assertion {
+ @new_profile_hash.include?("PayloadIdentifier")
+ }
+ a.failure_message RuntimeError, "The specified profile does not seem to be valid"
+ end
+ if @new_profile_hash.is_a?(String)
+ a.assertion {
+ @new_profile_hash.end_with?(".mobileconfig")
+ }
+ a.failure_message RuntimeError, "#{new_profile_hash}' is not a valid profile"
+ end
+ end
+ end
+
+ def action_install
+ unless profile_installed?
+ converge_by("install profile #{@new_profile_identifier}") do
+ profile_path = write_profile_to_disk
+ install_profile(profile_path)
+ get_installed_profiles(true)
+ end
+ end
+ end
+
+ def action_remove
+ # Clean up profile after removing it
+ if profile_installed?
+ converge_by("remove profile #{@new_profile_identifier}") do
+ remove_profile
+ get_installed_profiles(true)
+ end
+ end
+ end
+
+ def load_profile_hash(new_profile)
+ # file must exist in cookbook
+ if new_profile.end_with?(".mobileconfig")
+ unless cookbook_file_available?(new_profile)
+ error_string = "#{self}: '#{new_profile}' not found in cookbook"
+ raise Chef::Exceptions::FileNotFound, error_string
+ end
+ cookbook_profile = cache_cookbook_profile(new_profile)
+ return read_plist(cookbook_profile)
+ else
+ return nil
+ end
+ end
+
+ def cookbook_file_available?(cookbook_file)
+ run_context.has_cookbook_file_in_cookbook?(
+ @new_resource.cookbook_name, cookbook_file
+ )
+ end
+
+ def get_cache_dir
+ cache_dir = Chef::FileCache.create_cache_path(
+ "profiles/#{@new_resource.cookbook_name}"
+ )
+ end
+
+ def cache_cookbook_profile(cookbook_file)
+ Chef::FileCache.create_cache_path(
+ ::File.join(
+ "profiles",
+ @new_resource.cookbook_name,
+ ::File.dirname(cookbook_file),
+ )
+ )
+ remote_file = Chef::Resource::CookbookFile.new(
+ ::File.join(
+ get_cache_dir,
+ "#{cookbook_file}.remote",
+ ),
+ run_context,
+ )
+ remote_file.cookbook_name = @new_resource.cookbook_name
+ remote_file.source(cookbook_file)
+ remote_file.backup(false)
+ remote_file.run_action(:create)
+ remote_file.path
+ end
+
+ def get_profile_hash(new_profile)
+ if new_profile.is_a?(Hash)
+ return new_profile
+ elsif new_profile.is_a?(String)
+ return load_profile_hash(new_profile)
+ end
+ end
+
+ def config_uuid(profile)
+ # Make a UUID of the profile contents and return as string
+ UUIDTools::UUID.sha1_create(
+ UUIDTools::UUID_DNS_NAMESPACE,
+ profile.to_s,
+ ).to_s
+ end
+
+ def write_profile_to_disk
+ @new_resource.path(Chef::FileCache.create_cache_path("profiles"))
+ tempfile = Chef::FileContentManagement::Tempfile.new(@new_resource).tempfile
+ tempfile.write(@new_profile_hash.to_plist)
+ tempfile.close
+ tempfile.path
+ end
+
+ def install_profile(profile_path)
+ cmd = "profiles -I -F '#{profile_path}'"
+ Chef::Log.debug("cmd: #{cmd}")
+ shellout_results = shell_out(cmd)
+ shellout_results.exitstatus
+ end
+
+ def remove_profile
+ cmd = "profiles -R -p '#{@new_profile_identifier}'"
+ Chef::Log.debug("cmd: #{cmd}")
+ shellout_results = shell_out(cmd)
+ shellout_results.exitstatus
+ end
+
+ def get_installed_profiles(update=nil)
+ if update
+ node.run_state[:config_profiles] = query_installed_profiles
+ else
+ node.run_state[:config_profiles] ||= query_installed_profiles
+ end
+ end
+
+ def query_installed_profiles
+ # Dump all profile metadata to a tempfile
+ tempfile = generate_tempfile
+ write_installed_profiles(tempfile)
+ installed_profiles = read_plist(tempfile)
+ Chef::Log.debug("Saved profiles to run_state")
+ # Clean up the temp file as we do not need it anymore
+ ::File.unlink(tempfile)
+ installed_profiles
+ end
+
+ def generate_tempfile
+ tempfile = ::Dir::Tmpname.create("allprofiles.plist") {}
+ end
+
+ def write_installed_profiles(tempfile)
+ cmd = "profiles -P -o '#{tempfile}'"
+ shell_out!(cmd)
+ end
+
+ def read_plist(xml_file)
+ Plist::parse_xml(xml_file)
+ end
+
+ def profile_installed?
+ # Profile Identifier and UUID must match a currently installed profile
+ if @current_resource.profile.nil? or @current_resource.profile.empty?
+ false
+ else
+ if @new_resource.action.include?(:remove)
+ true
+ else
+ @current_resource.profile["ProfileUUID"] ==
+ @new_profile_hash["PayloadUUID"]
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/provider/package.rb b/lib/chef/provider/package.rb
index 2e8e29981b..ba256f6bea 100644
--- a/lib/chef/provider/package.rb
+++ b/lib/chef/provider/package.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");
@@ -16,17 +16,24 @@
# limitations under the License.
#
-require 'chef/mixin/shell_out'
-require 'chef/mixin/command'
-require 'chef/log'
-require 'chef/file_cache'
-require 'chef/platform'
+require "chef/mixin/shell_out"
+require "chef/mixin/command"
+require "chef/mixin/subclass_directive"
+require "chef/log"
+require "chef/file_cache"
+require "chef/platform"
class Chef
class Provider
class Package < Chef::Provider
include Chef::Mixin::Command
include Chef::Mixin::ShellOut
+ extend Chef::Mixin::SubclassDirective
+
+ # subclasses declare this if they want all their arguments as arrays of packages and names
+ subclass_directive :use_multipackage_api
+ # subclasses declare this if they want sources (filenames) pulled from their package names
+ subclass_directive :use_package_name_for_source
#
# Hook that subclasses use to populate the candidate_version(s)
@@ -43,6 +50,14 @@ class Chef
true
end
+ def check_resource_semantics!
+ # FIXME: this is not universally true and subclasses are needing to override this and no-ops it. It should be turned into
+ # another "subclass_directive" and the apt and yum providers should declare that they need this behavior.
+ if new_resource.package_name.is_a?(Array) && new_resource.source != nil
+ raise Chef::Exceptions::InvalidResourceSpecification, "You may not specify both multipackage and source"
+ end
+ end
+
def load_current_resource
end
@@ -80,11 +95,10 @@ class Chef
end
end
- # XXX: mutating the new resource is generally bad
- @new_resource.version(versions_for_new_resource)
-
converge_by(install_description) do
- install_package(package_names_for_targets, versions_for_targets)
+ multipackage_api_adapter(package_names_for_targets, versions_for_targets) do |name, version|
+ install_package(name, version)
+ end
Chef::Log.info("#{@new_resource} installed #{package_names_for_targets} at #{versions_for_targets}")
end
end
@@ -107,18 +121,17 @@ class Chef
return
end
- # XXX: mutating the new resource is generally bad
- @new_resource.version(versions_for_new_resource)
-
converge_by(upgrade_description) do
- upgrade_package(package_names_for_targets, versions_for_targets)
- log_allow_downgrade = allow_downgrade ? '(allow_downgrade)' : ''
+ multipackage_api_adapter(package_names_for_targets, versions_for_targets) do |name, version|
+ upgrade_package(name, version)
+ end
+ log_allow_downgrade = allow_downgrade ? "(allow_downgrade)" : ""
Chef::Log.info("#{@new_resource} upgraded#{log_allow_downgrade} #{package_names_for_targets} to #{versions_for_targets}")
end
end
def upgrade_description
- log_allow_downgrade = allow_downgrade ? '(allow_downgrade)' : ''
+ log_allow_downgrade = allow_downgrade ? "(allow_downgrade)" : ""
description = []
target_version_array.each_with_index do |target_version, i|
next if target_version.nil?
@@ -132,12 +145,13 @@ class Chef
private :upgrade_description
- # @todo: ability to remove an array of packages
def action_remove
if removing_package?
description = @new_resource.version ? "version #{@new_resource.version} of " : ""
- converge_by("remove #{description} package #{@current_resource.package_name}") do
- remove_package(@current_resource.package_name, @new_resource.version)
+ converge_by("remove #{description}package #{@current_resource.package_name}") do
+ multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+ remove_package(name, version)
+ end
Chef::Log.info("#{@new_resource} removed")
end
else
@@ -166,18 +180,18 @@ class Chef
end
end
- # @todo: ability to purge an array of packages
def action_purge
if removing_package?
description = @new_resource.version ? "version #{@new_resource.version} of" : ""
converge_by("purge #{description} package #{@current_resource.package_name}") do
- purge_package(@current_resource.package_name, @new_resource.version)
+ multipackage_api_adapter(@current_resource.package_name, @new_resource.version) do |name, version|
+ purge_package(name, version)
+ end
Chef::Log.info("#{@new_resource} purged")
end
end
end
- # @todo: ability to reconfigure an array of packages
def action_reconfig
if @current_resource.version == nil then
Chef::Log.debug("#{@new_resource} is NOT installed - nothing to do")
@@ -192,7 +206,10 @@ class Chef
if preseed_file = get_preseed_file(@new_resource.package_name, @current_resource.version)
converge_by("reconfigure package #{@new_resource.package_name}") do
preseed_package(preseed_file)
- reconfig_package(@new_resource.package_name, @current_resource.version)
+ multipackage_api_adapter(@new_resource.package_name, @current_resource.version) do |name, version|
+ reconfig_package(name, version)
+
+ end
Chef::Log.info("#{@new_resource} reconfigured")
end
else
@@ -201,31 +218,40 @@ class Chef
end
# @todo use composition rather than inheritance
+
+ def multipackage_api_adapter(name, version)
+ if use_multipackage_api?
+ yield [name].flatten, [version].flatten
+ else
+ yield name, version
+ end
+ end
+
def install_package(name, version)
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :install"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :install"
end
def upgrade_package(name, version)
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :upgrade"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :upgrade"
end
def remove_package(name, version)
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :remove"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :remove"
end
def purge_package(name, version)
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :purge"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :purge"
end
def preseed_package(file)
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support pre-seeding package install/upgrade instructions"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support pre-seeding package install/upgrade instructions"
end
def reconfig_package(name, version)
- raise( Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reconfig" )
+ raise( Chef::Exceptions::UnsupportedAction, "#{self} does not support :reconfig" )
end
- # this is heavily used by subclasses
+ # used by subclasses. deprecated. use #a_to_s instead.
def expand_options(options)
options ? " #{options}" : ""
end
@@ -316,18 +342,6 @@ class Chef
multipackage? ? versions_for_targets : versions_for_targets[0]
end
- # We need to mutate @new_resource.version() for some reason and this is a helper so that we inject the right
- # class (String or Array) into that attribute based on if we're handling an array of package names or not.
- #
- # @return [String, Array<String>] target_versions coerced into the correct type for back-compat
- def versions_for_new_resource
- if multipackage?
- target_version_array
- else
- target_version_array[0]
- end
- end
-
# Return an array indexed the same as *_version_array which contains either the target version to install/upgrade to
# or else nil if the package is not being modified.
#
@@ -464,10 +478,38 @@ class Chef
# @return [Array] new_version(s) as an array
def new_version_array
- @new_version_array ||=
- [ new_resource.version ].flatten.map do |v|
- ( v.nil? || v.empty? ) ? nil : v
+ [ new_resource.version ].flatten.map { |v| v.to_s.empty? ? nil : v }
+ end
+
+ # TIP: less error prone to simply always call resolved_source_array, even if you
+ # don't think that you need to.
+ #
+ # @return [Array] new_resource.source as an array
+ def source_array
+ if new_resource.source.nil?
+ package_name_array.map { nil }
+ else
+ [ new_resource.source ].flatten
+ end
+ end
+
+ # Helper to handle use_package_name_for_source to convert names into local packages to install.
+ #
+ # @return [Array] Array of sources with package_names converted to sources
+ def resolved_source_array
+ @resolved_source_array ||=
+ begin
+ source_array.each_with_index.map do |source, i|
+ package_name = package_name_array[i]
+ # we require at least one '/' in the package_name to avoid [XXX_]package 'foo' breaking due to a random 'foo' file in cwd
+ if use_package_name_for_source? && source.nil? && package_name.match(/#{::File::SEPARATOR}/) && ::File.exist?(package_name)
+ Chef::Log.debug("No package source specified, but #{package_name} exists on filesystem, using #{package_name} as source.")
+ package_name
+ else
+ source
+ end
end
+ end
end
# @todo: extract apt/dpkg specific preseeding to a helper class
@@ -487,6 +529,37 @@ class Chef
false
end
end
+
+ def shell_out_with_timeout(*command_args)
+ shell_out(*add_timeout_option(command_args))
+ end
+
+ def shell_out_with_timeout!(*command_args)
+ shell_out!(*add_timeout_option(command_args))
+ end
+
+ def add_timeout_option(command_args)
+ args = command_args.dup
+ if args.last.is_a?(Hash)
+ options = args.pop.dup
+ options[:timeout] = new_resource.timeout if new_resource.timeout
+ options[:timeout] = 900 unless options.has_key?(:timeout)
+ args << options
+ else
+ args << { :timeout => new_resource.timeout ? new_resource.timeout : 900 }
+ end
+ args
+ end
+
+ # Helper for sublcasses to convert an array of string args into a string. It
+ # will compact nil or empty strings in the array and will join the array elements
+ # with spaces, without introducing any double spaces for nil/empty elements.
+ #
+ # @param args [String] variable number of string arguments
+ # @return [String] nicely concatenated string or empty string
+ def a_to_s(*args)
+ args.reject {|i| i.nil? || i == "" }.join(" ")
+ end
end
end
end
diff --git a/lib/chef/provider/package/aix.rb b/lib/chef/provider/package/aix.rb
index 107f914c05..29e2d1eb94 100644
--- a/lib/chef/provider/package/aix.rb
+++ b/lib/chef/provider/package/aix.rb
@@ -16,16 +16,17 @@
# limitations under the License.
#
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
-require 'chef/mixin/get_source_from_package'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
+require "chef/mixin/get_source_from_package"
class Chef
class Provider
class Package
class Aix < Chef::Provider::Package
+ provides :package, os: "aix"
provides :bff_package, os: "aix"
include Chef::Mixin::GetSourceFromPackage
@@ -46,13 +47,12 @@ class Chef
def load_current_resource
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @new_resource.version(nil)
if @new_resource.source
@package_source_found = ::File.exists?(@new_resource.source)
if @package_source_found
Chef::Log.debug("#{@new_resource} checking pkg status")
- 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 +60,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 +84,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 +110,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 +122,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..03cbff4d1c 100644
--- a/lib/chef/provider/package/apt.rb
+++ b/lib/chef/provider/package/apt.rb
@@ -16,15 +16,16 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
class Chef
class Provider
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
@@ -47,7 +48,7 @@ class Chef
requirements.assert(:all_actions) do |a|
a.assertion { !@new_resource.source }
- a.failure_message(Chef::Exceptions::Package, 'apt package provider cannot handle source attribute. Use dpkg provider instead')
+ a.failure_message(Chef::Exceptions::Package, "apt package provider cannot handle source attribute. Use dpkg provider instead")
end
end
@@ -62,11 +63,11 @@ 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
- if installed_version == '(none)'
+ if installed_version == "(none)"
Chef::Log.debug("#{@new_resource} current version is nil")
installed_version = nil
else
@@ -75,10 +76,10 @@ class Chef
end
when /^\s{2}Candidate: (.+)$/
candidate_version = $1
- if candidate_version == '(none)'
+ 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
@@ -140,7 +141,7 @@ class Chef
version_array = [ version ].flatten
package_name = name_array.zip(version_array).map do |n, v|
is_virtual_package[n] ? n : "#{n}=#{v}"
- end.join(' ')
+ end.join(" ")
run_noninteractive("apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}")
end
@@ -149,12 +150,12 @@ class Chef
end
def remove_package(name, version)
- package_name = [ name ].flatten.join(' ')
+ package_name = [ name ].flatten.join(" ")
run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}")
end
def purge_package(name, version)
- package_name = [ name ].flatten.join(' ')
+ package_name = [ name ].flatten.join(" ")
run_noninteractive("apt-get -q -y#{expand_options(@new_resource.options)} purge #{package_name}")
end
@@ -164,7 +165,7 @@ class Chef
end
def reconfig_package(name, version)
- package_name = [ name ].flatten.join(' ')
+ package_name = [ name ].flatten.join(" ")
Chef::Log.info("#{@new_resource} reconfiguring")
run_noninteractive("dpkg-reconfigure #{package_name}")
end
@@ -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/chocolatey.rb b/lib/chef/provider/package/chocolatey.rb
new file mode 100644
index 0000000000..87abe299b6
--- /dev/null
+++ b/lib/chef/provider/package/chocolatey.rb
@@ -0,0 +1,257 @@
+#
+# 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/provider/package"
+require "chef/resource/chocolatey_package"
+require "chef/mixin/powershell_out"
+
+class Chef
+ class Provider
+ class Package
+ class Chocolatey < Chef::Provider::Package
+ include Chef::Mixin::PowershellOut
+
+ provides :chocolatey_package, os: "windows"
+
+ # Declare that our arguments should be arrays
+ use_multipackage_api
+
+ # Responsible for building the current_resource.
+ #
+ # @return [Chef::Resource::ChocolateyPackage] the current_resource
+ def load_current_resource
+ @current_resource = Chef::Resource::ChocolateyPackage.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+ current_resource.version(build_current_versions)
+ current_resource
+ end
+
+ def define_resource_requirements
+ super
+
+ # Chocolatey source attribute points to an alternate feed
+ # and not a package specific alternate source like other providers
+ # so we want to assert candidates exist for the alternate source
+ requirements.assert(:upgrade, :install) do |a|
+ a.assertion { candidates_exist_for_all_uninstalled? }
+ a.failure_message(Chef::Exceptions::Package, "No candidate version available for #{packages_missing_candidates.join(", ")}")
+ a.whyrun("Assuming a repository that offers #{packages_missing_candidates.join(", ")} would have been configured")
+ end
+ end
+
+ # Lazy initializer for candidate_version. A nil value means that there is no candidate
+ # version and the package is not installable (generally an error).
+ #
+ # @return [Array] list of candidate_versions indexed same as new_resource.package_name/version
+ def candidate_version
+ @candidate_version ||= build_candidate_versions
+ end
+
+ # Install multiple packages via choco.exe
+ #
+ # @param names [Array<String>] array of package names to install
+ # @param versions [Array<String>] array of versions to install
+ def install_package(names, versions)
+ name_versions_to_install = desired_name_versions.select { |n, v| lowercase_names(names).include?(n) }
+
+ name_nil_versions = name_versions_to_install.select { |n,v| v.nil? }
+ name_has_versions = name_versions_to_install.reject { |n,v| v.nil? }
+
+ # choco does not support installing multiple packages with version pins
+ name_has_versions.each do |name, version|
+ choco_command("install -y -version", version, cmd_args, name)
+ end
+
+ # but we can do all the ones without version pins at once
+ unless name_nil_versions.empty?
+ cmd_names = name_nil_versions.keys
+ choco_command("install -y", cmd_args, *cmd_names)
+ end
+ end
+
+ # Upgrade multiple packages via choco.exe
+ #
+ # @param names [Array<String>] array of package names to install
+ # @param versions [Array<String>] array of versions to install
+ def upgrade_package(names, versions)
+ name_versions_to_install = desired_name_versions.select { |n, v| lowercase_names(names).include?(n) }
+
+ name_nil_versions = name_versions_to_install.select { |n,v| v.nil? }
+ name_has_versions = name_versions_to_install.reject { |n,v| v.nil? }
+
+ # choco does not support installing multiple packages with version pins
+ name_has_versions.each do |name, version|
+ choco_command("upgrade -y -version", version, cmd_args, name)
+ end
+
+ # but we can do all the ones without version pins at once
+ unless name_nil_versions.empty?
+ cmd_names = name_nil_versions.keys
+ choco_command("upgrade -y", cmd_args, *cmd_names)
+ end
+ end
+
+ # Remove multiple packages via choco.exe
+ #
+ # @param names [Array<String>] array of package names to install
+ # @param versions [Array<String>] array of versions to install
+ def remove_package(names, versions)
+ choco_command("uninstall -y", cmd_args, *names)
+ end
+
+ # Support :uninstall as an action in order for users to easily convert
+ # from the `chocolatey` provider in the cookbook. It is, however,
+ # already deprecated.
+ def action_uninstall
+ Chef::Log.deprecation "The use of action :uninstall on the chocolatey_package provider is deprecated, please use :remove"
+ action_remove
+ end
+
+ # Choco does not have dpkg's distinction between purge and remove
+ alias_method :purge_package, :remove_package
+
+ # Override the superclass check. The semantics for our new_resource.source is not files to
+ # install from, but like the rubygem provider's sources which are more like repos.
+ def check_resource_semantics!
+ end
+
+ private
+
+ # Magic to find where chocolatey is installed in the system, and to
+ # return the full path of choco.exe
+ #
+ # @return [String] full path of choco.exe
+ def choco_exe
+ @choco_exe ||=
+ ::File.join(
+ powershell_out!(
+ "[System.Environment]::GetEnvironmentVariable('ChocolateyInstall', 'MACHINE')"
+ ).stdout.chomp,
+ "bin",
+ "choco.exe",
+ )
+ end
+
+ # Helper to dispatch a choco command through shell_out using the timeout
+ # set on the new resource, with nice command formatting.
+ #
+ # @param args [String] variable number of string arguments
+ # @return [Mixlib::ShellOut] object returned from shell_out!
+ def choco_command(*args)
+ shell_out_with_timeout!(args_to_string(choco_exe, *args))
+ end
+
+ # Use the available_packages Hash helper to create an array suitable for
+ # using in candidate_version
+ #
+ # @return [Array] list of candidate_version, same index as new_resource.package_name/version
+ def build_candidate_versions
+ new_resource.package_name.map do |package_name|
+ available_packages[package_name.downcase]
+ end
+ end
+
+ # Use the installed_packages Hash helper to create an array suitable for
+ # using in current_resource.version
+ #
+ # @return [Array] list of candidate_version, same index as new_resource.package_name/version
+ def build_current_versions
+ new_resource.package_name.map do |package_name|
+ installed_packages[package_name.downcase]
+ end
+ end
+
+ # Helper to construct Hash of names-to-versions, requested on the new_resource.
+ # If new_resource.version is nil, then all values will be nil.
+ #
+ # @return [Hash] Mapping of requested names to versions
+ def desired_name_versions
+ desired_versions = new_resource.version || new_resource.package_name.map { nil }
+ Hash[*lowercase_names(new_resource.package_name).zip(desired_versions).flatten]
+ end
+
+ # Helper to construct optional args out of new_resource
+ #
+ # @return [String] options from new_resource or empty string
+ def cmd_args
+ cmd_args = [ new_resource.options ]
+ cmd_args.push( "-source #{new_resource.source}" ) if new_resource.source
+ args_to_string(*cmd_args)
+ end
+
+ # Helper to nicely convert variable string args into a single command line. It
+ # will compact nulls or empty strings and join arguments with single spaces, without
+ # introducing any double-spaces for missing args.
+ #
+ # @param args [String] variable number of string arguments
+ # @return [String] nicely concatenated string or empty string
+ def args_to_string(*args)
+ args.reject {|i| i.nil? || i == "" }.join(" ")
+ end
+
+ # Available packages in chocolatey as a Hash of names mapped to versions
+ # If pinning a package to a specific version, filter out all non matching versions
+ # (names are downcased for case-insensitive matching)
+ #
+ # @return [Hash] name-to-version mapping of available packages
+ def available_packages
+ @available_packages ||=
+ begin
+ cmd = [ "list -ar #{package_name_array.join ' '}" ]
+ cmd.push( "-source #{new_resource.source}" ) if new_resource.source
+ parse_list_output(*cmd).each_with_object({}) do |name_version, available|
+ name, version = name_version
+ if desired_name_versions[name].nil? || desired_name_versions[name] == version
+ available[name] = version
+ end
+ end
+ end
+ end
+
+ # Installed packages in chocolatey as a Hash of names mapped to versions
+ # (names are downcased for case-insensitive matching)
+ #
+ # @return [Hash] name-to-version mapping of installed packages
+ def installed_packages
+ @installed_packages ||= Hash[*parse_list_output("list -l -r").flatten]
+ end
+
+ # Helper to convert choco.exe list output to a Hash
+ # (names are downcased for case-insenstive matching)
+ #
+ # @param cmd [String] command to run
+ # @return [Array] list output converted to ruby Hash
+ def parse_list_output(*args)
+ list = []
+ choco_command(*args).stdout.each_line do |line|
+ name, version = line.split("|")
+ list << [ name.downcase, version.chomp ]
+ end
+ list
+ end
+
+ # Helper to downcase all names in an array
+ #
+ # @param names [Array] original mixed case names
+ # @return [Array] same names in lower case
+ def lowercase_names(names)
+ names.map { |name| name.downcase }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
index 11691a2479..bcf7cff4e4 100644
--- a/lib/chef/provider/package/dpkg.rb
+++ b/lib/chef/provider/package/dpkg.rb
@@ -16,131 +16,208 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
-require 'chef/mixin/get_source_from_package'
+require "chef/provider/package"
+require "chef/resource/package"
class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
- # http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version
- DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~:-]+)/
+ DPKG_REMOVED = /^Status: deinstall ok config-files/
DPKG_INSTALLED = /^Status: install ok installed/
DPKG_VERSION = /^Version: (.+)$/
provides :dpkg_package, os: "linux"
- include Chef::Mixin::GetSourceFromPackage
+ use_multipackage_api
+ use_package_name_for_source
def define_resource_requirements
super
- requirements.assert(:install) do |a|
- a.assertion{ not @new_resource.source.nil? }
- a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+
+ requirements.assert(:install, :upgrade) do |a|
+ a.assertion { !resolved_source_array.compact.empty? }
+ a.failure_message Chef::Exceptions::Package, "#{new_resource} the source property is required for action :install or :upgrade"
end
- # TODO this was originally written for any action in which .source is provided
- # but would it make more sense to only look at source if the action is :install?
- requirements.assert(:all_actions) do |a|
- a.assertion { @source_exists }
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
- a.whyrun "Assuming it would have been previously downloaded."
+ requirements.assert(:install, :upgrade) do |a|
+ a.assertion { source_files_exist? }
+ a.failure_message Chef::Exceptions::Package, "#{new_resource} source file(s) do not exist: #{missing_sources}"
+ a.whyrun "Assuming they would have been previously created."
end
end
def load_current_resource
- @source_exists = true
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
- @current_resource.package_name(@new_resource.package_name)
- @new_resource.version(nil)
-
- if @new_resource.source
- @source_exists = ::File.exists?(@new_resource.source)
- if @source_exists
- # Get information from the package if supplied
- Chef::Log.debug("#{@new_resource} checking dpkg status")
-
- shell_out("dpkg-deb -W #{@new_resource.source}").stdout.each_line do |line|
- if pkginfo = DPKG_INFO.match(line)
- @current_resource.package_name(pkginfo[1])
- @new_resource.version(pkginfo[2])
- @candidate_version = pkginfo[2]
- end
- end
- else
- # Source provided but not valid means we can't safely do further processing
- return
- end
-
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
+ current_resource.package_name(new_resource.package_name)
+
+ if source_files_exist?
+ @candidate_version = get_candidate_version
+ current_resource.package_name(get_package_name)
+ # if the source file exists then our package_name is right
+ current_resource.version(get_current_version_from(current_package_name_array))
+ elsif !installing?
+ # we can't do this if we're installing with no source, because our package_name
+ # is probably not right.
+ #
+ # if we're removing or purging we don't use source, and our package_name must
+ # be right so we can do this.
+ #
+ # we don't error here on the dpkg command since we'll handle the exception or
+ # the why-run message in define_resource_requirements.
+ current_resource.version(get_current_version_from(current_package_name_array))
end
- # Check to see if it is installed
- package_installed = nil
- Chef::Log.debug("#{@new_resource} checking install state")
- status = shell_out("dpkg -s #{@current_resource.package_name}")
+ current_resource
+ end
+
+ def install_package(name, version)
+ sources = name.map { |n| name_sources[n] }
+ Chef::Log.info("#{new_resource} installing package(s): #{name.join(' ')}")
+ run_noninteractive("dpkg -i", new_resource.options, *sources)
+ end
+
+ def remove_package(name, version)
+ Chef::Log.info("#{new_resource} removing package(s): #{name.join(' ')}")
+ run_noninteractive("dpkg -r", new_resource.options, *name)
+ end
+
+ def purge_package(name, version)
+ Chef::Log.info("#{new_resource} purging packages(s): #{name.join(' ')}")
+ run_noninteractive("dpkg -P", new_resource.options, *name)
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def preseed_package(preseed_file)
+ Chef::Log.info("#{new_resource} pre-seeding package installation instructions")
+ run_noninteractive("debconf-set-selections", *preseed_file)
+ end
+
+ def reconfig_package(name, version)
+ Chef::Log.info("#{new_resource} reconfiguring")
+ run_noninteractive("dpkg-reconfigure", *name)
+ end
+
+ # Override the superclass check. Multiple sources are required here.
+ def check_resource_semantics!
+ end
+
+ private
+
+ def read_current_version_of_package(package_name)
+ Chef::Log.debug("#{new_resource} checking install state of #{package_name}")
+ status = shell_out_with_timeout!("dpkg -s #{package_name}", returns: [0, 1])
+ package_installed = false
status.stdout.each_line do |line|
case line
+ when DPKG_REMOVED
+ # if we are 'purging' then we consider 'removed' to be 'installed'
+ package_installed = true if action == :purge
when DPKG_INSTALLED
package_installed = true
when DPKG_VERSION
if package_installed
- Chef::Log.debug("#{@new_resource} current version is #{$1}")
- @current_resource.version($1)
+ Chef::Log.debug("#{new_resource} current version is #{$1}")
+ return $1
end
end
end
+ return nil
+ end
- unless status.exitstatus == 0 || status.exitstatus == 1
- raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!"
+ def get_current_version_from(array)
+ array.map do |name|
+ read_current_version_of_package(name)
end
+ end
- @current_resource
+ # Runs command via shell_out_with_timeout with magic environment to disable
+ # interactive prompts.
+ def run_noninteractive(*command)
+ shell_out_with_timeout!(a_to_s(*command), :env => { "DEBIAN_FRONTEND" => "noninteractive" })
end
- def install_package(name, version)
- Chef::Log.info("#{@new_resource} installing #{@new_resource.source}")
- run_noninteractive(
- "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}"
- )
+ # Returns true if all sources exist. Returns false if any do not, or if no
+ # sources were specified.
+ #
+ # @return [Boolean] True if all sources exist
+ def source_files_exist?
+ resolved_source_array.all? {|s| s && ::File.exist?(s) }
end
- def remove_package(name, version)
- Chef::Log.info("#{@new_resource} removing #{@new_resource.package_name}")
- run_noninteractive(
- "dpkg -r#{expand_options(@new_resource.options)} #{@new_resource.package_name}"
- )
+ # Helper to return all the nanes of the missing sources for error messages.
+ #
+ # @return [Array<String>] Array of missing sources
+ def missing_sources
+ resolved_source_array.select {|s| s.nil? || !::File.exist?(s) }
end
- def purge_package(name, version)
- Chef::Log.info("#{@new_resource} purging #{@new_resource.package_name}")
- run_noninteractive(
- "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}"
- )
+ def current_package_name_array
+ [ current_resource.package_name ].flatten
end
- def upgrade_package(name, version)
- install_package(name, version)
+ # Helper to construct Hash of names-to-sources.
+ #
+ # @return [Hash] Mapping of package names to sources
+ def name_sources
+ @name_sources =
+ begin
+ Hash[*package_name_array.zip(resolved_source_array).flatten]
+ end
end
- def preseed_package(preseed_file)
- Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
- run_noninteractive("debconf-set-selections #{preseed_file}")
+ # Helper to construct Hash of names-to-package-information.
+ #
+ # @return [Hash] Mapping of package names to package information
+ def name_pkginfo
+ @name_pkginfo ||=
+ begin
+ pkginfos = resolved_source_array.map do |src|
+ Chef::Log.debug("#{new_resource} checking #{src} dpkg status")
+ status = shell_out_with_timeout!("dpkg-deb -W #{src}")
+ status.stdout
+ end
+ Hash[*package_name_array.zip(pkginfos).flatten]
+ end
end
- def reconfig_package(name, version)
- Chef::Log.info("#{@new_resource} reconfiguring")
- run_noninteractive("dpkg-reconfigure #{name}")
+ def name_candidate_version
+ @name_candidate_version ||=
+ begin
+ Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[1].strip : nil] }]
+ end
+ end
+
+ def name_package_name
+ @name_package_name ||=
+ begin
+ Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[0] : nil] }]
+ end
+ end
+
+ # Return candidate version array from pkg-deb -W against the source file(s).
+ #
+ # @return [Array] Array of candidate versions read from the source files
+ def get_candidate_version
+ package_name_array.map { |name| name_candidate_version[name] }
+ end
+
+ # Return package names from the candidate source file(s).
+ #
+ # @return [Array] Array of actual package names read from the source files
+ def get_package_name
+ package_name_array.map { |name| name_package_name[name] }
end
- # Runs command via shell_out with magic environment to disable
- # interactive prompts. Command is run with default localization rather
- # than forcing locale to "C", so command output may not be stable.
+ # Since upgrade just calls install, this is a helper to determine
+ # if our action means that we'll be calling install_package.
#
- # FIXME: This should be "LC_ALL" => "en_US.UTF-8" in order to stabilize the output and get UTF-8
- def run_noninteractive(command)
- shell_out!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
+ # @return [Boolean] true if we're doing :install or :upgrade
+ def installing?
+ [:install, :upgrade].include?(action)
end
end
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
index 90727b738d..91bb54999b 100644
--- a/lib/chef/provider/package/easy_install.rb
+++ b/lib/chef/provider/package/easy_install.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
class Chef
class Provider
@@ -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
@@ -51,12 +51,12 @@ class Chef
def easy_install_binary_path
path = @new_resource.easy_install_binary
- path ? path : 'easy_install'
+ path ? path : "easy_install"
end
def python_binary_path
path = @new_resource.python_binary
- path ? path : 'python'
+ path ? path : "python"
end
def module_name
@@ -67,18 +67,17 @@ class Chef
def load_current_resource
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @current_resource.version(nil)
# get the currently installed version if installed
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*/)
+ output_array = output.gsub(/[\[\]]/,"").split(/\s*,\s*/)
package_path = ""
output_array.each do |entry|
@@ -107,7 +106,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..4957812e99 100644
--- a/lib/chef/provider/package/freebsd/base.rb
+++ b/lib/chef/provider/package/freebsd/base.rb
@@ -19,9 +19,9 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package'
-require 'chef/mixin/get_source_from_package'
+require "chef/resource/package"
+require "chef/provider/package"
+require "chef/mixin/get_source_from_package"
class Chef
class Provider
@@ -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..2d13a0d2c1 100644
--- a/lib/chef/provider/package/freebsd/pkg.rb
+++ b/lib/chef/provider/package/freebsd/pkg.rb
@@ -19,8 +19,8 @@
# limitations under the License.
#
-require 'chef/provider/package/freebsd/base'
-require 'chef/util/path_helper'
+require "chef/provider/package/freebsd/base"
+require "chef/util/path_helper"
class Chef
class Provider
@@ -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..2d23620a66 100644
--- a/lib/chef/provider/package/freebsd/pkgng.rb
+++ b/lib/chef/provider/package/freebsd/pkgng.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/provider/package/freebsd/base'
+require "chef/provider/package/freebsd/base"
class Chef
class Provider
@@ -28,23 +28,23 @@ 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
def remove_package(name, version)
- options = @new_resource.options && @new_resource.options.sub(repo_regex, '')
+ 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..89220e38b4 100644
--- a/lib/chef/provider/package/freebsd/port.rb
+++ b/lib/chef/provider/package/freebsd/port.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/provider/package/freebsd/base'
+require "chef/provider/package/freebsd/base"
class Chef
class Provider
@@ -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 603899646f..661236a56c 100644
--- a/lib/chef/provider/package/homebrew.rb
+++ b/lib/chef/provider/package/homebrew.rb
@@ -18,16 +18,16 @@
# limitations under the License.
#
-require 'etc'
-require 'chef/mixin/homebrew_user'
+require "etc"
+require "chef/mixin/homebrew_user"
class Chef
class Provider
class Package
class Homebrew < Chef::Provider::Package
+ provides :package, os: "darwin", override: true
provides :homebrew_package
- provides :package, os: "darwin"
include Chef::Mixin::HomebrewUser
@@ -46,7 +46,7 @@ class Chef
def install_package(name, version)
unless current_resource.version == version
- brew('install', new_resource.options, name)
+ brew("install", new_resource.options, name)
end
end
@@ -56,19 +56,19 @@ class Chef
if current_version.nil? or current_version.empty?
install_package(name, version)
elsif current_version != version
- brew('upgrade', new_resource.options, name)
+ brew("upgrade", new_resource.options, name)
end
end
def remove_package(name, version)
if current_resource.version
- brew('uninstall', new_resource.options, name)
+ brew("uninstall", new_resource.options, name)
end
end
# Homebrew doesn't really have a notion of purging, do a "force remove"
def purge_package(name, version)
- new_resource.options((new_resource.options || '') << ' --force').strip
+ new_resource.options((new_resource.options || "") << " --force").strip
remove_package(name, version)
end
@@ -85,7 +85,7 @@ class Chef
#
# https://github.com/Homebrew/homebrew/wiki/Querying-Brew
def brew_info
- @brew_info ||= Chef::JSONCompat.from_json(brew('info', '--json=v1', new_resource.package_name)).first
+ @brew_info ||= Chef::JSONCompat.from_json(brew("info", "--json=v1", new_resource.package_name)).first
end
# Some packages (formula) are "keg only" and aren't linked,
@@ -95,14 +95,14 @@ class Chef
# that brew thinks is linked as the current version.
#
def current_installed_version
- if brew_info['keg_only']
- if brew_info['installed'].empty?
+ if brew_info["keg_only"]
+ if brew_info["installed"].empty?
nil
else
- brew_info['installed'].last['version']
+ brew_info["installed"].last["version"]
end
else
- brew_info['linked_keg']
+ brew_info["linked_keg"]
end
end
@@ -116,7 +116,7 @@ class Chef
#
# https://github.com/Homebrew/homebrew/wiki/Acceptable-Formulae#stable-versions
def candidate_version
- brew_info['versions']['stable']
+ brew_info["versions"]["stable"]
end
private
@@ -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..a69a2e24e0 100644
--- a/lib/chef/provider/package/ips.rb
+++ b/lib/chef/provider/package/ips.rb
@@ -17,16 +17,17 @@
# limitations under the License.
#
-require 'open3'
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
+require "open3"
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
class Chef
class Provider
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
@@ -69,11 +70,11 @@ class Chef
normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}"
command =
if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license
- normal_command.gsub('-q', '-q --accept')
+ normal_command.gsub("-q", "-q --accept")
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 82048c3bd4..539af1c409 100644
--- a/lib/chef/provider/package/openbsd.rb
+++ b/lib/chef/provider/package/openbsd.rb
@@ -20,11 +20,10 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package'
-require 'chef/mixin/shell_out'
-require 'chef/mixin/get_source_from_package'
-require 'chef/exceptions'
+require "chef/resource/package"
+require "chef/provider/package"
+require "chef/mixin/get_source_from_package"
+require "chef/exceptions"
class Chef
class Provider
@@ -32,6 +31,7 @@ class Chef
class Openbsd < Chef::Provider::Package
provides :package, os: "openbsd"
+ provides :openbsd_package
include Chef::Mixin::ShellOut
include Chef::Mixin::GetSourceFromPackage
@@ -53,7 +53,7 @@ class Chef
# 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')
+ 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
@@ -63,7 +63,7 @@ class Chef
true
end
end
- a.failure_message(Chef::Exceptions::Package, 'The openbsd package provider does not support providing a version and flavor')
+ a.failure_message(Chef::Exceptions::Package, "The openbsd package provider does not support providing a version and flavor")
end
end
@@ -72,18 +72,16 @@ class Chef
if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add
name = parts[1]
end
- shell_out!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => pkg_path}).status
+ shell_out_with_timeout!("pkg_add -r #{name}#{version_string(version)}", :env => {"PKG_PATH" => pkg_path}).status
Chef::Log.debug("#{new_resource.package_name} installed")
end
end
def remove_package(name, version)
- version_string = ''
- version_string += "-#{version}" if version
if parts = name.match(/^(.+?)--(.+)/)
name = parts[1]
end
- shell_out!("pkg_delete #{name}#{version_string}", :env => nil).status
+ shell_out_with_timeout!("pkg_delete #{name}#{version_string(version)}", :env => nil).status
end
private
@@ -94,7 +92,7 @@ class Chef
else
name = new_resource.package_name
end
- pkg_info = shell_out!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
+ pkg_info = shell_out_with_timeout!("pkg_info -e \"#{name}->0\"", :env => nil, :returns => [0,1])
result = pkg_info.stdout[/^inst:#{Regexp.escape(name)}-(.+?)\s/, 1]
Chef::Log.debug("installed_version of '#{new_resource.package_name}' is '#{result}'")
result
@@ -103,7 +101,7 @@ class Chef
def candidate_version
@candidate_version ||= begin
results = []
- shell_out!("pkg_info -I \"#{new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
+ shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string(new_resource.version)}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
else
@@ -111,7 +109,7 @@ class Chef
end
end
results = results.reject(&:nil?)
- Chef::Log.debug("candidate versions of '#{new_resource.package_name}' are '#{results}'")
+ Chef::Log.debug("Candidate versions of '#{new_resource.package_name}' are '#{results}'")
case results.length
when 0
[]
@@ -123,13 +121,13 @@ class Chef
end
end
- def version_string
- ver = ''
- ver += "-#{new_resource.version}" if new_resource.version
+ def version_string(version)
+ ver = ""
+ ver += "-#{version}" if version
end
def pkg_path
- ENV['PKG_PATH'] || "http://ftp.OpenBSD.org/pub/#{node.kernel.name}/#{node.kernel.release}/packages/#{node.kernel.machine}/"
+ ENV["PKG_PATH"] || "http://ftp.OpenBSD.org/pub/#{node.kernel.name}/#{node.kernel.release}/packages/#{node.kernel.machine}/"
end
end
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
index f16fc811f5..6bb2250da5 100644
--- a/lib/chef/provider/package/pacman.rb
+++ b/lib/chef/provider/package/pacman.rb
@@ -16,25 +16,24 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
class Chef
class Provider
class Package
class Pacman < Chef::Provider::Package
+ provides :package, platform: "arch"
provides :pacman_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.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?)*: (.+)$/
@@ -60,9 +59,9 @@ class Chef
repos = pacman.scan(/\[(.+)\]/).flatten
end
- package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
+ 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 +84,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 +92,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..c2b1069e1e 100644
--- a/lib/chef/provider/package/paludis.rb
+++ b/lib/chef/provider/package/paludis.rb
@@ -16,38 +16,36 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/resource/package'
+require "chef/provider/package"
+require "chef/resource/package"
class Chef
class Provider
class Package
class Paludis < Chef::Provider::Package
+ provides :package, platform: "exherbo"
provides :paludis_package, os: "linux"
def load_current_resource
@current_resource = Chef::Resource::Package.new(@new_resource.package_name)
@current_resource.package_name(@new_resource.package_name)
- @current_resource.version(nil)
-
Chef::Log.debug("Checking package status for #{@new_resource.package_name}")
installed = false
- re = Regexp.new('(.*)[[:blank:]](.*)[[:blank:]](.*)$')
+ re = Regexp.new("(.*)[[:blank:]](.*)[[:blank:]](.*)$")
shell_out!("cave -L warning print-ids -M none -m \"#{@new_resource.package_name}\" -f \"%c/%p %v %r\n\"").stdout.each_line do |line|
res = re.match(line)
unless res.nil?
case res[3]
- when 'accounts', 'installed-accounts'
+ when "accounts", "installed-accounts"
next
- when 'installed'
+ when "installed"
installed = true
@current_resource.version(res[2])
else
@candidate_version = res[2]
- @current_resource.version(nil)
end
end
end
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
index bb047ad2fa..4a1559637a 100644
--- a/lib/chef/provider/package/portage.rb
+++ b/lib/chef/provider/package/portage.rb
@@ -16,23 +16,25 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
-require 'chef/util/path_helper'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
+require "chef/util/path_helper"
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
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @current_resource.version(nil)
-
category, pkg = %r{^#{PACKAGE_NAME_PATTERN}$}.match(@new_resource.package_name)[1,2]
globsafe_category = category ? Chef::Util::PathHelper.escape_glob(category) : nil
@@ -46,7 +48,7 @@ class Chef
if versions.size > 1
atoms = versions.map {|v| v.first }.sort
- categories = atoms.map {|v| v.split('/')[0] }.uniq
+ categories = atoms.map {|v| v.split("/")[0] }.uniq
if !category && categories.size > 1
raise Chef::Exceptions::Package, "Multiple packages found for #{@new_resource.package_name}: #{atoms.join(" ")}. Specify a category."
end
@@ -64,7 +66,7 @@ class Chef
txt.each_line do |line|
if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
- found_package_name = $&.gsub(/\*/, '').strip
+ found_package_name = $&.gsub(/\*/, "").strip
if package =~ /\// #the category is specified
if found_package_name == package
availables[found_package_name] = nil
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
index f10fe23c71..9ab10b062d 100644
--- a/lib/chef/provider/package/rpm.rb
+++ b/lib/chef/provider/package/rpm.rb
@@ -15,11 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/mixin/shell_out'
-require 'chef/resource/package'
-require 'chef/mixin/get_source_from_package'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
+require "chef/mixin/get_source_from_package"
class Chef
class Provider
@@ -51,7 +50,6 @@ class Chef
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @new_resource.version(nil)
if @new_resource.source
unless uri_scheme?(@new_resource.source) || ::File.exists?(@new_resource.source)
@@ -60,9 +58,9 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}").stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^(\S+)\s(\S+)$/
@current_resource.package_name($1)
@new_resource.version($2)
@candidate_version = $2
@@ -76,10 +74,10 @@ class Chef
end
Chef::Log.debug("#{@new_resource} checking install state")
- @rpm_status = shell_out("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
+ @rpm_status = shell_out_with_timeout("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}")
@rpm_status.stdout.each_line do |line|
case line
- when /^([\w\d+_.-]+)\s([\w\d_.-]+)$/
+ when /^(\S+)\s(\S+)$/
Chef::Log.debug("#{@new_resource} current version is #{$2}")
@current_resource.version($2)
end
@@ -90,12 +88,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 +102,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
@@ -115,7 +113,7 @@ class Chef
def uri_scheme?(str)
scheme = URI.split(str).first
return false unless scheme
- %w(http https ftp file).include?(scheme.downcase)
+ %w{http https ftp file}.include?(scheme.downcase)
rescue URI::InvalidURIError
return false
end
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
index c53aa8934a..85796e8b9a 100644
--- a/lib/chef/provider/package/rubygems.rb
+++ b/lib/chef/provider/package/rubygems.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2008, 2010-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,32 +17,25 @@
# limitations under the License.
#
-require 'uri'
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
-require 'chef/mixin/get_source_from_package'
+require "uri"
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
+require "chef/mixin/get_source_from_package"
# Class methods on Gem are defined in rubygems
-require 'rubygems'
+require "rubygems"
# Ruby 1.9's gem_prelude can interact poorly with loading the full rubygems
# explicitly like this. Make sure rubygems/specification is always last in this
# list
-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/dependency_installer'
-require 'rubygems/uninstaller'
-require 'rubygems/specification'
+require "rubygems/version"
+require "rubygems/dependency"
+require "rubygems/spec_fetcher"
+require "rubygems/platform"
+require "rubygems/package"
+require "rubygems/dependency_installer"
+require "rubygems/uninstaller"
+require "rubygems/specification"
class Chef
class Provider
@@ -93,7 +86,7 @@ class Chef
# === Returns
# [Gem::Specification] an array of Gem::Specification objects
def installed_versions(gem_dep)
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new("1.8.0")
gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement)
else
gem_source_index.search(gem_dep)
@@ -144,7 +137,7 @@ class Chef
spec.version
else
# This is probably going to end badly...
- logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency.to_s}" }
+ logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency}" }
nil
end
end
@@ -168,17 +161,17 @@ class Chef
def find_newest_remote_version(gem_dependency, *sources)
available_gems = dependency_installer.find_gems_with_sources(gem_dependency)
spec, source = if available_gems.respond_to?(:last)
- # DependencyInstaller sorts the results such that the last one is
- # always the one it considers best.
- spec_with_source = available_gems.last
- spec_with_source && spec_with_source
- else
- # Rubygems 2.0 returns a Gem::Available set, which is a
- # collection of AvailableSet::Tuple structs
- available_gems.pick_best!
- best_gem = available_gems.set.first
- best_gem && [best_gem.spec, best_gem.source]
- end
+ # DependencyInstaller sorts the results such that the last one is
+ # always the one it considers best.
+ spec_with_source = available_gems.last
+ spec_with_source && spec_with_source
+ else
+ # Rubygems 2.0 returns a Gem::Available set, which is a
+ # collection of AvailableSet::Tuple structs
+ available_gems.pick_best!
+ best_gem = available_gems.set.first
+ best_gem && [best_gem.spec, best_gem.source]
+ end
version = spec && spec.version
if version
@@ -295,7 +288,7 @@ class Chef
end
def gem_source_index
- @source_index ||= Gem::SourceIndex.from_gems_in(*gem_paths.map { |p| p + '/specifications' })
+ @source_index ||= Gem::SourceIndex.from_gems_in(*gem_paths.map { |p| p + "/specifications" })
end
def gem_specification
@@ -327,7 +320,7 @@ class Chef
else
gem_environment = shell_out!("#{@gem_binary_location} env").stdout
if jruby = gem_environment[JRUBY_PLATFORM]
- self.class.platform_cache[@gem_binary_location] = ['ruby', Gem::Platform.new(jruby)]
+ self.class.platform_cache[@gem_binary_location] = ["ruby", Gem::Platform.new(jruby)]
else
self.class.platform_cache[@gem_binary_location] = Gem.platforms
end
@@ -401,11 +394,11 @@ 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
- elsif RbConfig::CONFIG['bindir'].sub(/^[\w]:/, '') == "/opscode/chef/embedded/bin"
+ elsif RbConfig::CONFIG["bindir"].sub(/^[\w]:/, "") == "/opscode/chef/embedded/bin"
Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
# windows, with the drive letter removed
true
@@ -417,7 +410,7 @@ class Chef
def find_gem_by_path
Chef::Log.debug("#{@new_resource} searching for 'gem' binary in path: #{ENV['PATH']}")
separator = ::File::ALT_SEPARATOR ? ::File::ALT_SEPARATOR : ::File::SEPARATOR
- path_to_first_gem = ENV['PATH'].split(::File::PATH_SEPARATOR).select { |path| ::File.exists?(path + separator + "gem") }.first
+ path_to_first_gem = ENV["PATH"].split(::File::PATH_SEPARATOR).select { |path| ::File.exists?(path + separator + "gem") }.first
raise Chef::Exceptions::FileNotFound, "Unable to find 'gem' binary in path: #{ENV['PATH']}" if path_to_first_gem.nil?
path_to_first_gem + separator + "gem"
end
@@ -445,14 +438,14 @@ class Chef
gemspec = matching_installed_versions.last
logger.debug { "#{@new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}"}
gemspec
- # If no version matching the requirements exists, the latest installed
- # version is the current version.
+ # If no version matching the requirements exists, the latest installed
+ # version is the current version.
elsif !all_installed_versions.empty?
gemspec = all_installed_versions.last
logger.debug { "#{@new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
gemspec
else
- logger.debug { "#{@new_resource} no installed version found for #{gem_dependency.to_s}"}
+ logger.debug { "#{@new_resource} no installed version found for #{gem_dependency}"}
nil
end
end
@@ -463,8 +456,8 @@ class Chef
def all_installed_versions
@all_installed_versions ||= begin
- @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, '>= 0'))
- end
+ @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, ">= 0"))
+ end
end
def gem_sources
@@ -489,14 +482,14 @@ class Chef
def candidate_version
@candidate_version ||= begin
- if target_version_already_installed?(@current_resource.version, @new_resource.version)
- nil
- elsif source_is_remote?
- @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
- else
- @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
- end
- end
+ if target_version_already_installed?(@current_resource.version, @new_resource.version)
+ nil
+ elsif source_is_remote?
+ @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
+ else
+ @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
+ end
+ end
end
def target_version_already_installed?(current_version, new_version)
@@ -532,22 +525,22 @@ class Chef
end
def gem_binary_path
- @new_resource.gem_binary || 'gem'
+ @new_resource.gem_binary || "gem"
end
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}" || '')
+ src = " --clear-sources"
+ src << (@new_resource.source && " --source=#{@new_resource.source}" || "")
else
src = @new_resource.source && " --source=#{@new_resource.source} --source=https://rubygems.org"
end
if !version.nil? && version.length > 0
- shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
else
- shell_out!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} install \"#{name}\" -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
end
end
@@ -571,9 +564,9 @@ class Chef
def uninstall_via_gem_command(name, version)
if version
- shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
else
- shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
+ shell_out_with_timeout!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
end
end
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
index 7cef91953a..98264ecd1e 100644
--- a/lib/chef/provider/package/smartos.rb
+++ b/lib/chef/provider/package/smartos.rb
@@ -19,9 +19,9 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/resource/package'
-require 'chef/mixin/get_source_from_package'
+require "chef/provider/package"
+require "chef/resource/package"
+require "chef/mixin/get_source_from_package"
class Chef
class Provider
@@ -29,13 +29,13 @@ 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
Chef::Log.debug("#{@new_resource} loading current resource")
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @current_resource.version(nil)
check_package_state(@new_resource.package_name)
@current_resource # modified by check_package_state
end
@@ -43,15 +43,13 @@ 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]
end
- if !version
- @current_resource.version(nil)
- else
+ if version
@current_resource.version(version)
end
end
@@ -60,11 +58,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 +72,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 +83,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..8442e57dbb 100644
--- a/lib/chef/provider/package/solaris.rb
+++ b/lib/chef/provider/package/solaris.rb
@@ -15,10 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
-require 'chef/mixin/get_source_from_package'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
+require "chef/mixin/get_source_from_package"
class Chef
class Provider
@@ -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)
@@ -49,13 +51,12 @@ class Chef
def load_current_resource
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
- @new_resource.version(nil)
if @new_resource.source
@package_source_found = ::File.exists?(@new_resource.source)
if @package_source_found
Chef::Log.debug("#{@new_resource} checking pkg status")
- 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 +66,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+(.+)/
@@ -78,16 +79,12 @@ class Chef
raise Chef::Exceptions::Package, "pkginfo failed - #{status.inspect}!"
end
- unless @current_resource.version.nil?
- @current_resource.version(nil)
- end
-
@current_resource
end
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 +107,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 +115,19 @@ class Chef
else
command = "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
end
- shell_out!(command)
+ shell_out_with_timeout!(command)
Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
end
end
+ alias_method :upgrade_package, :install_package
+
def remove_package(name, version)
if @new_resource.options.nil?
- shell_out!( "pkgrm -n #{name}" )
+ shell_out_with_timeout!( "pkgrm -n #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
else
- shell_out!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
+ shell_out_with_timeout!( "pkgrm -n#{expand_options(@new_resource.options)} #{name}" )
Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
end
end
diff --git a/lib/chef/provider/package/windows.rb b/lib/chef/provider/package/windows.rb
index 143d82f111..87fadc27cf 100644
--- a/lib/chef/provider/package/windows.rb
+++ b/lib/chef/provider/package/windows.rb
@@ -16,70 +16,244 @@
# limitations under the License.
#
-require 'chef/resource/windows_package'
-require 'chef/provider/package'
-require 'chef/util/path_helper'
+require "chef/mixin/uris"
+require "chef/resource/windows_package"
+require "chef/provider/package"
+require "chef/util/path_helper"
+require "chef/mixin/checksum"
class Chef
class Provider
class Package
class Windows < Chef::Provider::Package
+ include Chef::Mixin::Uris
+ include Chef::Mixin::Checksum
provides :package, os: "windows"
provides :windows_package, os: "windows"
- # Depending on the installer, we may need to examine installer_type or
- # source attributes, or search for text strings in the installer file
- # binary to determine the installer type for the user. Since the file
- # must be on disk to do so, we have to make this choice in the provider.
- require 'chef/provider/package/windows/msi.rb'
+ require "chef/provider/package/windows/registry_uninstall_entry.rb"
+
+ def define_resource_requirements
+ requirements.assert(:install) do |a|
+ a.assertion { new_resource.source unless package_provider == :msi }
+ a.failure_message Chef::Exceptions::NoWindowsPackageSource, "Source for package #{new_resource.name} must be specified in the resource's source property for package to be installed because the package_name property is used to test for the package installation state for this package type."
+ end
+ end
# 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)
+ 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) if package_provider.package_version
+ end
- @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
+ current_resource
end
def package_provider
@package_provider ||= begin
case installer_type
when :msi
- Chef::Provider::Package::Windows::MSI.new(@new_resource)
+ Chef::Log.debug("#{new_resource} is MSI")
+ require "chef/provider/package/windows/msi"
+ Chef::Provider::Package::Windows::MSI.new(resource_for_provider, uninstall_registry_entries)
else
- raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
+ Chef::Log.debug("#{new_resource} is EXE with type '#{installer_type}'")
+ require "chef/provider/package/windows/exe"
+ Chef::Provider::Package::Windows::Exe.new(resource_for_provider, installer_type, uninstall_registry_entries)
end
end
end
def installer_type
+ # Depending on the installer, we may need to examine installer_type or
+ # source attributes, or search for text strings in the installer file
+ # binary to determine the installer type for the user. Since the file
+ # must be on disk to do so, we have to make this choice in the provider.
@installer_type ||= begin
- if @new_resource.installer_type
- @new_resource.installer_type
+ if new_resource.installer_type
+ new_resource.installer_type
+ elsif source_location.nil?
+ inferred_registry_type
else
- file_extension = ::File.basename(@new_resource.source).split(".").last.downcase
+ basename = ::File.basename(source_location)
+ file_extension = basename.split(".").last.downcase
if file_extension == "msi"
:msi
else
- raise ArgumentError, "Installer type for Windows Package '#{@new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'"
+ # search the binary file for installer type
+ ::Kernel.open(::File.expand_path(source_location), "rb") do |io|
+ filesize = io.size
+ bufsize = 4096 # read 4K buffers
+ overlap = 16 # bytes to overlap between buffer reads
+
+ until io.eof
+ contents = io.read(bufsize)
+
+ case contents
+ when /inno/i # Inno Setup
+ return :inno
+ when /wise/i # Wise InstallMaster
+ return :wise
+ when /nullsoft/i # Nullsoft Scriptable Install System
+ return :nsis
+ end
+
+ if (io.tell() < filesize)
+ io.seek(io.tell() - overlap)
+ end
+ end
+ end
+
+ # if file is named 'setup.exe' assume installshield
+ if basename == "setup.exe"
+ :installshield
+ else
+ raise Chef::Exceptions::CannotDetermineWindowsInstallerType, "Installer type for Windows Package '#{new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'"
+ end
end
end
end
end
+ def action_install
+ if uri_scheme?(new_resource.source)
+ download_source_file
+ load_current_resource
+ else
+ validate_content!
+ end
+
+ super
+ end
+
# Chef::Provider::Package action_install + action_remove call install_package + remove_package
# Pass those calls to the correct sub-provider
def install_package(name, version)
- package_provider.install_package(name, version)
+ package_provider.install_package
end
def remove_package(name, version)
- package_provider.remove_package(name, version)
+ package_provider.remove_package
+ end
+
+ # @return [Array] new_version(s) as an array
+ def new_version_array
+ # Because the one in the parent caches things
+ [new_resource.version]
+ end
+
+ # @return [String] candidate_version
+ def candidate_version
+ @candidate_version ||= (new_resource.version || "latest")
+ end
+
+ # @return [Array] current_version(s) as an array
+ # this package provider does not support package arrays
+ # However, There may be multiple versions for a single
+ # package so the first element may be a nested array
+ def current_version_array
+ [ current_resource.version ]
+ end
+
+ # @param current_version<String> one or more versions currently installed
+ # @param new_version<String> version of the new resource
+ #
+ # @return [Boolean] true if new_version is equal to or included in current_version
+ def target_version_already_installed?(current_version, new_version)
+ Chef::Log.debug("Checking if #{new_resource} version '#{new_version}' is already installed. #{current_version} is currently installed")
+ if current_version.is_a?(Array)
+ current_version.include?(new_version)
+ else
+ new_version == current_version
+ end
+ end
+
+ def have_any_matching_version?
+ target_version_already_installed?(current_resource.version, new_resource.version)
+ end
+
+ private
+
+ def uninstall_registry_entries
+ @uninstall_registry_entries ||= Chef::Provider::Package::Windows::RegistryUninstallEntry.find_entries(new_resource.package_name)
end
+
+ def inferred_registry_type
+ uninstall_registry_entries.each do |entry|
+ return :inno if entry.key.end_with?("_is1")
+ return :msi if entry.uninstall_string.downcase.start_with?("msiexec.exe ")
+ return :nsis if entry.uninstall_string.downcase.end_with?("uninst.exe\"")
+ end
+ nil
+ end
+
+ def downloadable_file_missing?
+ !new_resource.source.nil? && uri_scheme?(new_resource.source) && !::File.exists?(source_location)
+ end
+
+ def resource_for_provider
+ @resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r|
+ r.source(Chef::Util::PathHelper.validate_path(source_location)) unless source_location.nil?
+ r.version(new_resource.version)
+ r.timeout(new_resource.timeout)
+ r.returns(new_resource.returns)
+ r.options(new_resource.options)
+ end
+ end
+
+ def download_source_file
+ source_resource.run_action(:create)
+ Chef::Log.debug("#{new_resource} fetched source file to #{source_resource.path}")
+ end
+
+ def source_resource
+ @source_resource ||= Chef::Resource::RemoteFile.new(default_download_cache_path, run_context).tap do |r|
+ r.source(new_resource.source)
+ r.checksum(new_resource.checksum)
+ r.backup(false)
+
+ if new_resource.remote_file_attributes
+ new_resource.remote_file_attributes.each do |(k,v)|
+ r.send(k.to_sym, v)
+ end
+ end
+ end
+ end
+
+ def default_download_cache_path
+ uri = ::URI.parse(new_resource.source)
+ filename = ::File.basename(::URI.unescape(uri.path))
+ file_cache_dir = Chef::FileCache.create_cache_path("package/")
+ Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
+ end
+
+ def source_location
+ if new_resource.source.nil?
+ nil
+ elsif uri_scheme?(new_resource.source)
+ source_resource.path
+ else
+ new_source = Chef::Util::PathHelper.cleanpath(new_resource.source)
+ ::File.exist?(new_source) ? new_source : nil
+ end
+ end
+
+ def validate_content!
+ if new_resource.checksum
+ source_checksum = checksum(source_location)
+ if new_resource.checksum != source_checksum
+ raise Chef::Exceptions::ChecksumMismatch.new(short_cksum(new_resource.checksum), short_cksum(source_checksum))
+ end
+ end
+ end
+
end
end
end
diff --git a/lib/chef/provider/package/windows/exe.rb b/lib/chef/provider/package/windows/exe.rb
new file mode 100644
index 0000000000..e510755281
--- /dev/null
+++ b/lib/chef/provider/package/windows/exe.rb
@@ -0,0 +1,117 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+# Author:: Matt Wrock <matt@mattwrock.com>
+# Copyright:: Copyright (c) 2011, 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/mixin/shell_out"
+
+class Chef
+ class Provider
+ class Package
+ class Windows
+ class Exe
+ include Chef::Mixin::ShellOut
+
+ def initialize(resource, installer_type, uninstall_entries)
+ @new_resource = resource
+ @installer_type = installer_type
+ @uninstall_entries = uninstall_entries
+ end
+
+ attr_reader :new_resource
+ attr_reader :installer_type
+ attr_reader :uninstall_entries
+
+ # From Chef::Provider::Package
+ def expand_options(options)
+ options ? " #{options}" : ""
+ end
+
+ # Returns a version if the package is installed or nil if it is not.
+ def installed_version
+ Chef::Log.debug("#{new_resource} checking package version")
+ current_installed_version
+ end
+
+ def package_version
+ new_resource.version
+ end
+
+ def install_package
+ Chef::Log.debug("#{new_resource} installing #{new_resource.installer_type} package '#{new_resource.source}'")
+ shell_out!(
+ [
+ "start",
+ "\"\"",
+ "/wait",
+ "\"#{new_resource.source}\"",
+ unattended_flags,
+ expand_options(new_resource.options),
+ "& exit %%%%ERRORLEVEL%%%%",
+ ].join(" "), timeout: new_resource.timeout, returns: new_resource.returns
+ )
+ end
+
+ def remove_package
+ uninstall_version = new_resource.version || current_installed_version
+ uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) }
+ .map { |version| version.uninstall_string }.uniq.each do |uninstall_string|
+ Chef::Log.debug("Registry provided uninstall string for #{new_resource} is '#{uninstall_string}'")
+ shell_out!(uninstall_command(uninstall_string), { returns: new_resource.returns })
+ end
+ end
+
+ private
+
+ def uninstall_command(uninstall_string)
+ uninstall_string.delete!('"')
+ uninstall_string = [
+ %q{/d"},
+ ::File.dirname(uninstall_string),
+ %q{" },
+ ::File.basename(uninstall_string),
+ expand_options(new_resource.options),
+ " ",
+ unattended_flags,
+ ].join
+ %Q{start "" /wait #{uninstall_string} & exit %%%%ERRORLEVEL%%%%}
+ end
+
+ def current_installed_version
+ @current_installed_version ||= uninstall_entries.count == 0 ? nil : begin
+ uninstall_entries.map { |entry| entry.display_version }.uniq
+ end
+ end
+
+ # http://unattended.sourceforge.net/installers.php
+ def unattended_flags
+ case installer_type
+ when :installshield
+ "/s /sms"
+ when :nsis
+ "/S /NCRC"
+ when :inno
+ "/VERYSILENT /SUPPRESSMSGBOXES /NORESTART"
+ when :wise
+ "/s"
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/windows/msi.rb b/lib/chef/provider/package/windows/msi.rb
index 938452945e..106dc496b6 100644
--- a/lib/chef/provider/package/windows/msi.rb
+++ b/lib/chef/provider/package/windows/msi.rb
@@ -18,21 +18,25 @@
# TODO: Allow @new_resource.source to be a Product Code as a GUID for uninstall / network install
-require 'chef/win32/api/installer' if RUBY_PLATFORM =~ /mswin|mingw32|windows/
-require 'chef/mixin/shell_out'
+require "chef/win32/api/installer" if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi?
+require "chef/mixin/shell_out"
class Chef
class Provider
class Package
class Windows
class MSI
- include Chef::ReservedNames::Win32::API::Installer if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ include Chef::ReservedNames::Win32::API::Installer if (RUBY_PLATFORM =~ /mswin|mingw32|windows/) && Chef::Platform.supports_msi?
include Chef::Mixin::ShellOut
- def initialize(resource)
+ def initialize(resource, uninstall_entries)
@new_resource = resource
+ @uninstall_entries = uninstall_entries
end
+ attr_reader :new_resource
+ attr_reader :uninstall_entries
+
# From Chef::Provider::Package
def expand_options(options)
options ? " #{options}" : ""
@@ -40,27 +44,47 @@ class Chef
# Returns a version if the package is installed or nil if it is not.
def installed_version
- Chef::Log.debug("#{@new_resource} getting product code for package at #{@new_resource.source}")
- product_code = get_product_property(@new_resource.source, "ProductCode")
- Chef::Log.debug("#{@new_resource} checking package status and version for #{product_code}")
- get_installed_version(product_code)
+ if !new_resource.source.nil? && ::File.exist?(new_resource.source)
+ Chef::Log.debug("#{new_resource} getting product code for package at #{new_resource.source}")
+ product_code = get_product_property(new_resource.source, "ProductCode")
+ Chef::Log.debug("#{new_resource} checking package status and version for #{product_code}")
+ get_installed_version(product_code)
+ else
+ uninstall_entries.count == 0 ? nil : begin
+ uninstall_entries.map { |entry| entry.display_version }.uniq
+ end
+ end
end
def package_version
- Chef::Log.debug("#{@new_resource} getting product version for package at #{@new_resource.source}")
- get_product_property(@new_resource.source, "ProductVersion")
+ return new_resource.version if new_resource.version
+ if !new_resource.source.nil? && ::File.exist?(new_resource.source)
+ Chef::Log.debug("#{new_resource} getting product version for package at #{new_resource.source}")
+ get_product_property(new_resource.source, "ProductVersion")
+ end
end
- def install_package(name, version)
+ def install_package
# We could use MsiConfigureProduct here, but we'll start off with msiexec
- Chef::Log.debug("#{@new_resource} installing MSI package '#{@new_resource.source}'")
- shell_out!("msiexec /qn /i \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
+ Chef::Log.debug("#{new_resource} installing MSI package '#{new_resource.source}'")
+ shell_out!("msiexec /qn /i \"#{new_resource.source}\" #{expand_options(new_resource.options)}", {:timeout => new_resource.timeout, :returns => new_resource.returns})
end
-
- def remove_package(name, version)
+
+ def remove_package
# We could use MsiConfigureProduct here, but we'll start off with msiexec
- Chef::Log.debug("#{@new_resource} removing MSI package '#{@new_resource.source}'")
- shell_out!("msiexec /qn /x \"#{@new_resource.source}\" #{expand_options(@new_resource.options)}", {:timeout => @new_resource.timeout, :returns => @new_resource.returns})
+ if !new_resource.source.nil? && ::File.exist?(new_resource.source)
+ Chef::Log.debug("#{new_resource} removing MSI package '#{new_resource.source}'")
+ shell_out!("msiexec /qn /x \"#{new_resource.source}\" #{expand_options(new_resource.options)}", {:timeout => new_resource.timeout, :returns => new_resource.returns})
+ else
+ uninstall_version = new_resource.version || installed_version
+ uninstall_entries.select { |entry| [uninstall_version].flatten.include?(entry.display_version) }
+ .map { |version| version.uninstall_string }.uniq.each do |uninstall_string|
+ Chef::Log.debug("#{new_resource} removing MSI package version using '#{uninstall_string}'")
+ uninstall_string += expand_options(new_resource.options)
+ uninstall_string += " /Q" unless uninstall_string =~ / \/Q\b/
+ shell_out!(uninstall_string, {:timeout => new_resource.timeout, :returns => new_resource.returns})
+ end
+ end
end
end
end
diff --git a/lib/chef/provider/package/windows/registry_uninstall_entry.rb b/lib/chef/provider/package/windows/registry_uninstall_entry.rb
new file mode 100644
index 0000000000..145f799e05
--- /dev/null
+++ b/lib/chef/provider/package/windows/registry_uninstall_entry.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Seth Chisamore (<schisamo@chef.io>)
+# Author:: Matt Wrock <matt@mattwrock.com>
+# Copyright:: Copyright (c) 2011, 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "win32/registry" if (RUBY_PLATFORM =~ /mswin|mingw32|windows/)
+
+class Chef
+ class Provider
+ class Package
+ class Windows
+ class RegistryUninstallEntry
+
+ def self.find_entries(package_name)
+ Chef::Log.debug("Finding uninstall entries for #{package_name}")
+ entries = []
+ [
+ [::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0100)],
+ [::Win32::Registry::HKEY_LOCAL_MACHINE, (::Win32::Registry::Constants::KEY_READ | 0x0200)],
+ [::Win32::Registry::HKEY_CURRENT_USER],
+ ].each do |hkey|
+ desired = hkey.length > 1 ? hkey[1] : ::Win32::Registry::Constants::KEY_READ
+ begin
+ ::Win32::Registry.open(hkey[0], UNINSTALL_SUBKEY, desired) do |reg|
+ reg.each_key do |key, _wtime|
+ begin
+ entry = reg.open(key, desired)
+ display_name = read_registry_property(entry, "DisplayName")
+ if display_name == package_name
+ entries.push(RegistryUninstallEntry.new(hkey, key, entry))
+ end
+ rescue ::Win32::Registry::Error => ex
+ Chef::Log.debug("Registry error opening key '#{key}' on node #{desired}: #{ex}")
+ end
+ end
+ end
+ rescue ::Win32::Registry::Error => ex
+ Chef::Log.debug("Registry error opening hive '#{hkey[0]}' :: #{desired}: #{ex}")
+ end
+ end
+ entries
+ end
+
+ def self.read_registry_property(data, property)
+ data[property]
+ rescue ::Win32::Registry::Error => ex
+ Chef::Log.debug("Failure to read property '#{property}'")
+ nil
+ end
+
+ def initialize(hive, key, registry_data)
+ Chef::Log.debug("Creating uninstall entry for #{hive}::#{key}")
+ @hive = hive
+ @key = key
+ @data = registry_data
+ @display_name = RegistryUninstallEntry.read_registry_property(registry_data, "DisplayName")
+ @display_version = RegistryUninstallEntry.read_registry_property(registry_data, "DisplayVersion")
+ @uninstall_string = RegistryUninstallEntry.read_registry_property(registry_data, "UninstallString")
+ end
+
+ attr_reader :hive
+ attr_reader :key
+ attr_reader :display_name
+ attr_reader :display_version
+ attr_reader :uninstall_string
+ attr_reader :data
+
+ private
+
+ UNINSTALL_SUBKEY = 'Software\Microsoft\Windows\CurrentVersion\Uninstall'.freeze
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
index 49c6f6beb5..c3fd3f69ec 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");
@@ -16,18 +16,19 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/provider/package'
-require 'chef/mixin/shell_out'
-require 'chef/resource/package'
-require 'singleton'
-require 'chef/mixin/get_source_from_package'
+require "chef/config"
+require "chef/provider/package"
+require "chef/mixin/which"
+require "chef/resource/package"
+require "singleton"
+require "chef/mixin/get_source_from_package"
class Chef
class Provider
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
@@ -709,11 +712,11 @@ class Chef
one_line = false
error = nil
- helper = ::File.join(::File.dirname(__FILE__), 'yum-dump.py')
+ helper = ::File.join(::File.dirname(__FILE__), "yum-dump.py")
status = nil
begin
- status = shell_out!("/usr/bin/python #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
+ status = shell_out!("#{python_bin} #{helper}#{opts}", :timeout => Chef::Config[:yum_timeout])
status.stdout.each_line do |line|
one_line = true
@@ -779,6 +782,42 @@ class Chef
@next_refresh = :none
end
+ def python_bin
+ yum_executable = which(yum_binary)
+ if yum_executable && shabang?(yum_executable)
+ shabang_or_fallback(extract_interpreter(yum_executable))
+ else
+ Chef::Log.warn("Yum executable not found or doesn't start with #!. Using default python.")
+ "/usr/bin/python"
+ end
+ rescue StandardError => e
+ Chef::Log.warn("An error occurred attempting to determine correct python executable. Using default.")
+ Chef::Log.debug(e)
+ "/usr/bin/python"
+ end
+
+ def extract_interpreter(file)
+ ::File.open(file, "r", &:readline)[2..-1].strip
+ end
+
+ # dnf based systems have a yum shim that has /bin/bash as the interpreter. Don't use this.
+ def shabang_or_fallback(interpreter)
+ if interpreter == "/bin/bash"
+ Chef::Log.warn("Yum executable interpreter is /bin/bash. Falling back to default python.")
+ "/usr/bin/python"
+ else
+ interpreter
+ end
+ end
+
+ def shabang?(file)
+ ::File.open(file, "r") do |f|
+ f.read(2) == '#!'
+ end
+ rescue Errno::ENOENT
+ false
+ end
+
def reload
@next_refresh = :all
end
@@ -953,11 +992,31 @@ class Chef
super
@yum = YumCache.instance
+ @yum.yum_binary = yum_binary
+ end
+
+ def yum_binary
+ @yum_binary ||=
+ begin
+ yum_binary = new_resource.yum_binary if new_resource.is_a?(Chef::Resource::YumPackage)
+ yum_binary ||= ::File.exist?("/usr/bin/yum-deprecated") ? "yum-deprecated" : "yum"
+ end
end
# Extra attributes
#
+ def arch_for_name(n)
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch
+ elsif @arch
+ idx = package_name_array.index(n)
+ as_array(@arch)[idx]
+ else
+ nil
+ end
+ end
+
def arch
if @new_resource.respond_to?("arch")
@new_resource.arch
@@ -966,6 +1025,12 @@ class Chef
end
end
+ def set_arch(arch)
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch(arch)
+ end
+ end
+
def flush_cache
if @new_resource.respond_to?("flush_cache")
@new_resource.flush_cache
@@ -977,12 +1042,14 @@ class Chef
# Helpers
#
- def yum_arch
+ def yum_arch(arch)
arch ? ".#{arch}" : nil
end
def yum_command(command)
- status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+ command = "#{yum_binary} #{command}"
+ Chef::Log.debug("#{@new_resource}: yum command: \"#{command}\"")
+ status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
# This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
# considered fatal - meaning the rpm is still successfully installed. These issue
@@ -999,7 +1066,7 @@ class Chef
if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
"so running install again to verify.")
- status = shell_out(command, {:timeout => Chef::Config[:yum_timeout]})
+ status = shell_out_with_timeout(command, {:timeout => Chef::Config[:yum_timeout]})
break
end
end
@@ -1059,23 +1126,20 @@ class Chef
end
end
- # Don't overwrite an existing arch
- unless arch
- parse_arch
- end
@current_resource = Chef::Resource::Package.new(@new_resource.name)
@current_resource.package_name(@new_resource.package_name)
installed_version = []
@candidate_version = []
+ @arch = []
if @new_resource.source
unless ::File.exists?(@new_resource.source)
raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
end
Chef::Log.debug("#{@new_resource} checking rpm status")
- shell_out!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
+ shell_out_with_timeout!("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}", :timeout => Chef::Config[:yum_timeout]).stdout.each_line do |line|
case line
when /([\w\d_.-]+)\s([\w\d_.-]+)/
@current_resource.package_name($1)
@@ -1085,24 +1149,43 @@ class Chef
@candidate_version << @new_resource.version
installed_version << @yum.installed_version(@current_resource.package_name, arch)
else
- if @new_resource.version
- new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch}"
- else
- new_resource = "#{@new_resource.package_name}#{yum_arch}"
- end
- Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+ package_name_array.each_with_index do |pkg, idx|
+ # Don't overwrite an existing arch
+ if arch
+ name, parch = pkg, arch
+ else
+ name, parch = parse_arch(pkg)
+ # if we parsed an arch from the name, update the name
+ # to be just the package name.
+ if parch
+ if @new_resource.package_name.is_a?(Array)
+ @new_resource.package_name[idx] = name
+ else
+ @new_resource.package_name(name)
+ # only set the arch if it's a single package
+ set_arch(parch)
+ end
+ end
+ end
- package_name_array.each do |pkg|
- installed_version << @yum.installed_version(pkg, arch)
- @candidate_version << @yum.candidate_version(pkg, arch)
+ if @new_resource.version
+ new_resource =
+ "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch(parch)}"
+ else
+ new_resource = "#{@new_resource.package_name}#{yum_arch(parch)}"
+ end
+ Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+ installed_version << @yum.installed_version(name, parch)
+ @candidate_version << @yum.candidate_version(name, parch)
+ @arch << parch
end
-
end
if installed_version.size == 1
@current_resource.version(installed_version[0])
@candidate_version = @candidate_version[0]
+ @arch = @arch[0]
else
@current_resource.version(installed_version)
end
@@ -1117,7 +1200,7 @@ class Chef
# Work around yum not exiting with an error if a package doesn't exist
# for CHEF-2062
all_avail = as_array(name).zip(as_array(version)).any? do |n, v|
- @yum.version_available?(n, v, arch)
+ @yum.version_available?(n, v, arch_for_name(n))
end
method = log_method = nil
methods = []
@@ -1159,20 +1242,20 @@ class Chef
repos = []
pkg_string_bits = []
- index = 0
as_array(name).zip(as_array(version)).each do |n, v|
- s = ''
- unless v == current_version_array[index]
- s = "#{n}-#{v}#{yum_arch}"
- repo = @yum.package_repository(n, v, arch)
+ idx = package_name_array.index(n)
+ a = arch_for_name(n)
+ s = ""
+ 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(' ')
+ pkg_string = pkg_string_bits.join(" ")
Chef::Log.info("#{@new_resource} #{log_method} #{repos.join(' ')}")
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{pkg_string}")
else
raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
"and release? (version-release, e.g. 1.84-10.fc6)"
@@ -1181,7 +1264,7 @@ class Chef
def install_package(name, version)
if @new_resource.source
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
else
install_remote_package(name, version)
end
@@ -1219,13 +1302,17 @@ class Chef
def remove_package(name, version)
if version
- remove_str = as_array(name).zip(as_array(version)).map do |x|
- "#{x.join('-')}#{yum_arch}"
- end.join(' ')
+ remove_str = as_array(name).zip(as_array(version)).map do |n, v|
+ a = arch_for_name(n)
+ "#{[n, v].join('-')}#{yum_arch(a)}"
+ end.join(" ")
else
- remove_str = as_array(name).map { |n| "#{n}#{yum_arch}" }.join(' ')
+ remove_str = as_array(name).map do |n|
+ a = arch_for_name(n)
+ "#{n}#{yum_arch(a)}"
+ end.join(" ")
end
- yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
+ yum_command("-d0 -e0 -y#{expand_options(@new_resource.options)} remove #{remove_str}")
if flush_cache[:after]
@yum.reload
@@ -1240,22 +1327,26 @@ class Chef
private
- def parse_arch
+ def parse_arch(package_name)
# Allow for foo.x86_64 style package_name like yum uses in it's output
#
- if @new_resource.package_name =~ %r{^(.*)\.(.*)$}
+ if package_name =~ %r{^(.*)\.(.*)$}
new_package_name = $1
new_arch = $2
# foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
# Ensure we don't have an existing package matching package_name, then ensure we at
# least have a match for the new_package+new_arch before we overwrite. If neither
# then fall through to standard package handling.
- if (@yum.installed_version(@new_resource.package_name).nil? and @yum.candidate_version(@new_resource.package_name).nil?) and
- (@yum.installed_version(new_package_name, new_arch) or @yum.candidate_version(new_package_name, new_arch))
- @new_resource.package_name(new_package_name)
- @new_resource.arch(new_arch)
+ old_installed = @yum.installed_version(package_name)
+ old_candidate = @yum.candidate_version(package_name)
+ new_installed = @yum.installed_version(new_package_name, new_arch)
+ new_candidate = @yum.candidate_version(new_package_name, new_arch)
+ if (old_installed.nil? and old_candidate.nil?) and (new_installed or new_candidate)
+ Chef::Log.debug("Parsed out arch #{new_arch}, new package name is #{new_package_name}")
+ return new_package_name, new_arch
end
end
+ return package_name, nil
end
# If we don't have the package we could have been passed a 'whatprovides' feature
@@ -1300,7 +1391,7 @@ class Chef
new_package_name = packages.first.name
new_package_version = packages.first.version.to_s
debug_msg = "#{name}: Unable to match package '#{name}' but matched #{packages.size} "
- debug_msg << packages.size == 1 ? "package" : "packages"
+ debug_msg << (packages.size == 1 ? "package" : "packages")
debug_msg << ", selected '#{new_package_name}' version '#{new_package_version}'"
Chef::Log.debug(debug_msg)
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
index 2cd321660b..9b0aaf322a 100644
--- a/lib/chef/provider/package/zypper.rb
+++ b/lib/chef/provider/package/zypper.rb
@@ -19,56 +19,58 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/mixin/command'
-require 'chef/resource/package'
-require 'singleton'
+require "chef/provider/package"
+require "chef/mixin/command"
+require "chef/resource/package"
+require "singleton"
class Chef
class Provider
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}")
+ version=""
+ oud_version=""
+ 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)
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 +79,7 @@ class Chef
raise Chef::Exceptions::Package, "zypper failed - #{status.inspect}!"
end
- @current_resource
+ current_resource
end
def zypper_version()
@@ -104,9 +106,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 f9dcd6d80c..d3b586e75d 100644
--- a/lib/chef/provider/powershell_script.rb
+++ b/lib/chef/provider/powershell_script.rb
@@ -16,7 +16,8 @@
# limitations under the License.
#
-require 'chef/provider/windows_script'
+require "chef/platform/query_helpers"
+require "chef/provider/windows_script"
class Chef
class Provider
@@ -24,71 +25,191 @@ class Chef
provides :powershell_script, os: "windows"
+ def initialize (new_resource, run_context)
+ super(new_resource, run_context, ".ps1")
+ add_exit_status_wrapper
+ end
+
+ def action_run
+ validate_script_syntax!
+ super
+ end
+
+ def command
+ basepath = is_forced_32bit ? wow64_directory : run_context.node.kernel.os_info.system_directory
+
+ # Powershell.exe is always in "v1.0" folder (for backwards compatibility)
+ interpreter_path = Chef::Util::PathHelper.join(basepath, "WindowsPowerShell", "v1.0", interpreter)
+
+ # Must use -File rather than -Command to launch the script
+ # file created by the base class that contains the script
+ # code -- otherwise, powershell.exe does not propagate the
+ # error status of a failed Windows process that ran at the
+ # end of the script, it gets changed to '1'.
+ #
+ # Nano only supports -Command
+ cmd = "\"#{interpreter_path}\" #{flags}"
+ if Chef::Platform.windows_nano_server?
+ cmd << " -Command \". '#{script_file.path}'\""
+ else
+ cmd << " -File \"#{script_file.path}\""
+ end
+ cmd
+ end
+
+ def flags
+ interpreter_flags = [*default_interpreter_flags].join(" ")
+
+ if ! (@new_resource.flags.nil?)
+ interpreter_flags = [@new_resource.flags, interpreter_flags].join(" ")
+ end
+
+ interpreter_flags
+ end
+
protected
- EXIT_STATUS_EXCEPTION_HANDLER = "\ntrap [Exception] {write-error -exception ($_.Exception.Message);exit 1}".freeze
- EXIT_STATUS_NORMALIZATION_SCRIPT = "\nif ($? -ne $true) { if ( $LASTEXITCODE ) {exit $LASTEXITCODE} else { exit 1 }}".freeze
- EXIT_STATUS_RESET_SCRIPT = "\n$global:LASTEXITCODE=$null".freeze
- # Process exit codes are strange with PowerShell. Unless you
- # explicitly call exit in Powershell, the powershell.exe
- # interpreter returns only 0 for success or 1 for failure. Since
- # we'd like to get specific exit codes from executable tools run
- # with Powershell, we do some work using the automatic variables
- # $? and $LASTEXITCODE to return the process exit code of the
- # last process run in the script if it is the last command
- # executed, otherwise 0 or 1 based on whether $? is set to true
- # (success, where we return 0) or false (where we return 1).
- def normalize_script_exit_status( code )
- target_code = ( EXIT_STATUS_EXCEPTION_HANDLER +
- EXIT_STATUS_RESET_SCRIPT +
- "\n" +
- code.to_s +
- EXIT_STATUS_NORMALIZATION_SCRIPT )
- convert_boolean_return = @new_resource.convert_boolean_return
- self.code = <<EOH
-new-variable -name interpolatedexitcode -visibility private -value $#{convert_boolean_return}
-new-variable -name chefscriptresult -visibility private
-$chefscriptresult = {
-#{target_code}
-}.invokereturnasis()
-if ($interpolatedexitcode -and $chefscriptresult.gettype().name -eq 'boolean') { exit [int32](!$chefscriptresult) } else { exit 0 }
-EOH
- Chef::Log.debug("powershell_script provider called with script code:\n\n#{code}\n")
+ # Process exit codes are strange with PowerShell and require
+ # special handling to cover common use cases.
+ def add_exit_status_wrapper
+ self.code = wrapper_script
+ Chef::Log.debug("powershell_script provider called with script code:\n\n#{@new_resource.code}\n")
Chef::Log.debug("powershell_script provider will execute transformed code:\n\n#{self.code}\n")
end
- public
+ def validate_script_syntax!
+ interpreter_arguments = default_interpreter_flags.join(" ")
+ Tempfile.open(["chef_powershell_script-user-code", ".ps1"]) do | user_script_file |
+ # Wrap the user's code in a PowerShell script block so that
+ # it isn't executed. However, syntactically invalid script
+ # in that block will still trigger a syntax error which is
+ # exactly what we want here -- verify the syntax without
+ # actually running the script.
+ user_code_wrapped_in_powershell_script_block = <<-EOH
+{
+ #{@new_resource.code}
+}
+EOH
+ user_script_file.puts user_code_wrapped_in_powershell_script_block
- def initialize (new_resource, run_context)
- super(new_resource, run_context, '.ps1')
- normalize_script_exit_status(new_resource.code)
+ # A .close or explicit .flush required to ensure the file is
+ # written to the file system at this point, which is required since
+ # the intent is to execute the code just written to it.
+ user_script_file.close
+ validation_command = "\"#{interpreter}\" #{interpreter_arguments} -Command \". '#{user_script_file.path}'\""
+
+ # Note that other script providers like bash allow syntax errors
+ # to be suppressed by setting 'returns' to a value that the
+ # interpreter would return as a status code in the syntax
+ # error case. We explicitly don't do this here -- syntax
+ # errors will not be suppressed, since doing so could make
+ # it harder for users to detect / debug invalid scripts.
+
+ # Therefore, the only return value for a syntactically valid
+ # script is 0. If an exception is raised by shellout, this
+ # means a non-zero return and thus a syntactically invalid script.
+
+ with_os_architecture(node, architecture: new_resource.architecture) do
+ shell_out!(validation_command, {returns: [0]})
+ end
+ end
end
- def flags
- default_flags = [
+ def default_interpreter_flags
+ return [] if Chef::Platform.windows_nano_server?
+
+ # Execution policy 'Bypass' is preferable since it doesn't require
+ # user input confirmation for files such as PowerShell modules
+ # downloaded from the Internet. However, 'Bypass' is not supported
+ # prior to PowerShell 3.0, so the fallback is 'Unrestricted'
+ execution_policy = Chef::Platform.supports_powershell_execution_bypass?(run_context.node) ? "Bypass" : "Unrestricted"
+
+ [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
- "-ExecutionPolicy Unrestricted",
+ "-ExecutionPolicy #{execution_policy}",
# Powershell will hang if STDIN is redirected
# http://connect.microsoft.com/PowerShell/feedback/details/572313/powershell-exe-can-hang-if-stdin-is-redirected
"-InputFormat None",
- # Must use -File rather than -Command to launch the script
- # file created by the base class that contains the script
- # code -- otherwise, powershell.exe does not propagate the
- # error status of a failed Windows process that ran at the
- # end of the script, it gets changed to '1'.
- "-File"
]
+ end
- interpreter_flags = default_flags.join(' ')
+ # A wrapper script is used to launch user-supplied script while
+ # still obtaining useful process exit codes. Unless you
+ # explicitly call exit in Powershell, the powershell.exe
+ # interpreter returns only 0 for success or 1 for failure. Since
+ # we'd like to get specific exit codes from executable tools run
+ # with Powershell, we do some work using the automatic variables
+ # $? and $LASTEXITCODE to return the process exit code of the
+ # last process run in the script if it is the last command
+ # executed, otherwise 0 or 1 based on whether $? is set to true
+ # (success, where we return 0) or false (where we return 1).
+ def wrapper_script
+<<-EOH
+# Chef Client wrapper for powershell_script resources
- if ! (@new_resource.flags.nil?)
- interpreter_flags = [@new_resource.flags, interpreter_flags].join(' ')
- end
+# LASTEXITCODE can be uninitialized -- make it explictly 0
+# to avoid incorrect detection of failure (non-zero) codes
+$global:LASTEXITCODE = 0
- interpreter_flags
+# Catch any exceptions -- without this, exceptions will result
+# In a zero return code instead of the desired non-zero code
+# that indicates a failure
+trap [Exception] {write-error ($_.Exception.Message);exit 1}
+
+# Variable state that should not be accessible to the user code
+new-variable -name interpolatedexitcode -visibility private -value $#{@new_resource.convert_boolean_return}
+new-variable -name chefscriptresult -visibility private
+
+# Initialize a variable we use to capture $? inside a block
+$global:lastcmdlet = $null
+
+# Execute the user's code in a script block --
+$chefscriptresult =
+{
+ #{@new_resource.code}
+
+ # This assignment doesn't affect the block's return value
+ $global:lastcmdlet = $?
+}.invokereturnasis()
+
+# Assume failure status of 1 -- success cases
+# will have to override this
+$exitstatus = 1
+
+# If convert_boolean_return is enabled, the block's return value
+# gets precedence in determining our exit status
+if ($interpolatedexitcode -and $chefscriptresult -ne $null -and $chefscriptresult.gettype().name -eq 'boolean')
+{
+ $exitstatus = [int32](!$chefscriptresult)
+}
+elseif ($lastcmdlet)
+{
+ # Otherwise, a successful cmdlet execution defines the status
+ $exitstatus = 0
+}
+elseif ( $LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0 )
+{
+ # If the cmdlet status is failed, allow the Win32 status
+ # in $LASTEXITCODE to define exit status. This handles the case
+ # where no cmdlets, only Win32 processes have run since $?
+ # will be set to $false whenever a Win32 process returns a non-zero
+ # status.
+ $exitstatus = $LASTEXITCODE
+}
+
+# Print STDOUT for the script execution
+Write-Output $chefscriptresult
+
+# If this script is launched with -File, the process exit
+# status of PowerShell.exe will be $exitstatus. If it was
+# launched with -Command, it will be 0 if $exitstatus was 0,
+# 1 (i.e. failed) otherwise.
+exit $exitstatus
+EOH
end
+
end
end
end
diff --git a/lib/chef/provider/reboot.rb b/lib/chef/provider/reboot.rb
index 8dde4653ec..3f64955d21 100644
--- a/lib/chef/provider/reboot.rb
+++ b/lib/chef/provider/reboot.rb
@@ -16,12 +16,13 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/provider'
+require "chef/log"
+require "chef/provider"
class Chef
class Provider
class Reboot < Chef::Provider
+ provides :reboot
def whyrun_supported?
true
@@ -39,7 +40,7 @@ class Chef
:delay_mins => @new_resource.delay_mins,
:reason => @new_resource.reason,
:timestamp => Time.now,
- :requested_by => @new_resource.name
+ :requested_by => @new_resource.name,
)
end
diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb
index 94f4e2655b..c6a06e0974 100644
--- a/lib/chef/provider/registry_key.rb
+++ b/lib/chef/provider/registry_key.rb
@@ -17,20 +17,22 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/log'
-require 'chef/resource/file'
-require 'chef/mixin/checksum'
-require 'chef/provider'
-require 'etc'
-require 'fileutils'
-require 'chef/scan_access_control'
-require 'chef/win32/registry'
+require "chef/config"
+require "chef/log"
+require "chef/resource/file"
+require "chef/mixin/checksum"
+require "chef/provider"
+require "etc"
+require "fileutils"
+require "chef/scan_access_control"
+require "chef/win32/registry"
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..02270201cb 100644
--- a/lib/chef/provider/remote_directory.rb
+++ b/lib/chef/provider/remote_directory.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");
@@ -16,178 +16,266 @@
# limitations under the License.
#
-require 'chef/provider/file'
-require 'chef/provider/directory'
-require 'chef/resource/directory'
-require 'chef/resource/remote_file'
-require 'chef/mixin/file_class'
-require 'chef/platform'
-require 'uri'
-require 'tempfile'
-require 'net/https'
-require 'set'
-require 'chef/util/path_helper'
+require "chef/provider/directory"
+require "chef/resource/file"
+require "chef/resource/directory"
+require "chef/resource/cookbook_file"
+require "chef/mixin/file_class"
+require "chef/platform/query_helpers"
+require "chef/util/path_helper"
+require "chef/deprecation/warnings"
+require "chef/deprecation/provider/remote_directory"
+
+require "forwardable"
class Chef
class Provider
class RemoteDirectory < Chef::Provider::Directory
+ extend Forwardable
+ include Chef::Mixin::FileClass
provides :remote_directory
- include Chef::Mixin::FileClass
+ def_delegators :@new_resource, :purge, :path, :source, :cookbook, :cookbook_name
+ def_delegators :@new_resource, :files_rights, :files_mode, :files_group, :files_owner, :files_backup
+ def_delegators :@new_resource, :rights, :mode, :group, :owner
+
+ # The overwrite property on the resource. Delegates to new_resource but can be mutated.
+ #
+ # @return [Boolean] if we are overwriting
+ #
+ def overwrite?
+ @overwrite = new_resource.overwrite if @overwrite.nil?
+ !!@overwrite
+ end
+
+ attr_accessor :managed_files
+
+ # Hash containing keys of the paths for all the files that we sync, plus all their
+ # parent directories.
+ #
+ # @return [Set] Ruby Set of the files that we manage
+ #
+ def managed_files
+ @managed_files ||= Set.new
+ end
+ # Handle action :create.
+ #
def action_create
super
- # Mark all files as needing to be purged
- files_to_purge = Set.new(ls(@new_resource.path)) # Make sure each path is clean
# Transfer files
files_to_transfer.each do |cookbook_file_relative_path|
create_cookbook_file(cookbook_file_relative_path)
- # parent directories and file being transferred are removed from the purge list
- Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(@new_resource.path, cookbook_file_relative_path))).descend do |d|
- files_to_purge.delete(d.to_s)
- end
+ # parent directories and file being transferred need to not be removed in the purge
+ add_managed_file(cookbook_file_relative_path)
end
- purge_unmanaged_files(files_to_purge)
+ purge_unmanaged_files
end
+ # Handle action :create_if_missing.
+ #
def action_create_if_missing
# if this action is called, ignore the existing overwrite flag
- @new_resource.overwrite(false)
+ @overwrite = false
action_create
end
- protected
+ private
- # List all excluding . and ..
- def ls(path)
- files = Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), '**', '*'),
- ::File::FNM_DOTMATCH)
-
- # Remove current directory and previous directory
- files.reject! do |name|
- basename = Pathname.new(name).basename().to_s
- ['.', '..'].include?(basename)
+ # Add a file and its parent directories to the managed_files Hash.
+ #
+ # @param [String] cookbook_file_relative_path relative path to the file
+ # @api private
+ #
+ def add_managed_file(cookbook_file_relative_path)
+ if purge
+ Pathname.new(Chef::Util::PathHelper.cleanpath(::File.join(path, cookbook_file_relative_path))).descend do |d|
+ managed_files.add(d.to_s)
+ end
end
-
- # Clean all the paths... this is required because of the join
- files.map {|f| Chef::Util::PathHelper.cleanpath(f)}
end
- def purge_unmanaged_files(unmanaged_files)
- if @new_resource.purge
- unmanaged_files.sort.reverse.each do |f|
- # file_class comes from Chef::Mixin::FileClass
- if ::File.directory?(f) && !Chef::Platform.windows? && !file_class.symlink?(f.dup)
- # Linux treats directory symlinks as files
- # Remove a directory as a directory when not on windows if it is not a symlink
- purge_directory(f)
- elsif ::File.directory?(f) && Chef::Platform.windows?
- # Windows treats directory symlinks as directories so we delete them here
- purge_directory(f)
- else
- converge_by("delete unmanaged file #{f}") do
- ::File.delete(f)
- Chef::Log.debug("#{@new_resource} deleted file #{f}")
+ # Remove all files not in the managed_files Set.
+ #
+ # @api private
+ #
+ def purge_unmanaged_files
+ if purge
+ Dir.glob(::File.join(Chef::Util::PathHelper.escape_glob(path), "**", "*"), ::File::FNM_DOTMATCH).sort!.reverse!.each do |file|
+ # skip '.' and '..'
+ next if [".",".."].include?(Pathname.new(file).basename().to_s)
+
+ # Clean the path. This is required because of the ::File.join
+ file = Chef::Util::PathHelper.cleanpath(file)
+
+ # Skip files that we've sync'd and their parent dirs
+ next if managed_files.include?(file)
+
+ if ::File.directory?(file)
+ if !Chef::Platform.windows? && file_class.symlink?(file.dup)
+ # Unix treats dir symlinks as files
+ purge_file(file)
+ else
+ # Unix dirs are dirs, Windows dirs and dir symlinks are dirs
+ purge_directory(file)
end
+ else
+ purge_file(file)
end
end
end
end
+ # Use a Chef directory sub-resource to remove a directory.
+ #
+ # @param [String] dir The path of the directory to remove
+ # @api private
+ #
def purge_directory(dir)
- converge_by("delete unmanaged directory #{dir}") do
- Dir::rmdir(dir)
- Chef::Log.debug("#{@new_resource} removed directory #{dir}")
- end
+ res = Chef::Resource::Directory.new(dir, run_context)
+ res.run_action(:delete)
+ new_resource.updated_by_last_action(true) if res.updated?
end
+ # Use a Chef file sub-resource to remove a file.
+ #
+ # @param [String] file The path of the file to remove
+ # @api private
+ #
+ def purge_file(file)
+ res = Chef::Resource::File.new(file, run_context)
+ res.run_action(:delete)
+ new_resource.updated_by_last_action(true) if res.updated?
+ end
+
+ # Get the files to tranfer. This returns files in lexicographical sort order.
+ #
+ # FIXME: it should do breadth-first, see CHEF-5080 (please use a performant sort)
+ #
+ # @return Array<String> The list of files to transfer
+ # @api private
+ #
def files_to_transfer
cookbook = run_context.cookbook_collection[resource_cookbook]
- files = cookbook.relative_filenames_in_preferred_directory(node, :files, @new_resource.source)
- files.sort.reverse
+ files = cookbook.relative_filenames_in_preferred_directory(node, :files, source)
+ files.sort_by! { |x| x.count(::File::SEPARATOR) }
end
- def directory_root_in_cookbook_cache
- @directory_root_in_cookbook_cache ||= begin
- cookbook = run_context.cookbook_collection[resource_cookbook]
- cookbook.preferred_filename_on_disk_location(node, :files, @new_resource.source, @new_resource.path)
- end
+ # Either the explicit cookbook that the user sets on the resource, or the implicit
+ # cookbook_name that the resource was declared in.
+ #
+ # @return [String] Cookbook to get file from.
+ # @api private
+ #
+ def resource_cookbook
+ cookbook || cookbook_name
end
- # Determine the cookbook to get the file from. If new resource sets an
- # explicit cookbook, use it, otherwise fall back to the implicit cookbook
- # i.e., the cookbook the resource was declared in.
- def resource_cookbook
- @new_resource.cookbook || @new_resource.cookbook_name
+ # If we are overwriting, then cookbook_file sub-resources should all be action :create,
+ # otherwise they should be :create_if_missing
+ #
+ # @return [Symbol] Action to take on cookbook_file sub-resources
+ # @api private
+ #
+ def action_for_cookbook_file
+ overwrite? ? :create : :create_if_missing
end
+ # This creates and uses a cookbook_file resource to sync a single file from the cookbook.
+ #
+ # @param [String] cookbook_file_relative_path The relative path to the cookbook file
+ # @api private
+ #
def create_cookbook_file(cookbook_file_relative_path)
- full_path = ::File.join(@new_resource.path, cookbook_file_relative_path)
+ full_path = ::File.join(path, cookbook_file_relative_path)
ensure_directory_exists(::File.dirname(full_path))
- file_to_fetch = cookbook_file_resource(full_path, cookbook_file_relative_path)
- if @new_resource.overwrite
- file_to_fetch.run_action(:create)
- else
- file_to_fetch.run_action(:create_if_missing)
- end
- @new_resource.updated_by_last_action(true) if file_to_fetch.updated?
+ res = cookbook_file_resource(full_path, cookbook_file_relative_path)
+ res.run_action(action_for_cookbook_file)
+ new_resource.updated_by_last_action(true) if res.updated?
end
+ # This creates the cookbook_file resource for use by create_cookbook_file.
+ #
+ # @param [String] target_path Path on the system to create
+ # @param [String] relative_source_path Relative path in the cookbook to the base source
+ # @return [Chef::Resource::CookbookFile] The built cookbook_file resource
+ # @api private
+ #
def cookbook_file_resource(target_path, relative_source_path)
- cookbook_file = Chef::Resource::CookbookFile.new(target_path, run_context)
- cookbook_file.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name
- cookbook_file.source(::File.join(@new_resource.source, relative_source_path))
- if Chef::Platform.windows? && @new_resource.files_rights
- @new_resource.files_rights.each_pair do |permission, *args|
- cookbook_file.rights(permission, *args)
+ res = Chef::Resource::CookbookFile.new(target_path, run_context)
+ res.cookbook_name = resource_cookbook
+ res.source(::File.join(source, relative_source_path))
+ if Chef::Platform.windows? && files_rights
+ files_rights.each_pair do |permission, *args|
+ res.rights(permission, *args)
end
end
- cookbook_file.mode(@new_resource.files_mode) if @new_resource.files_mode
- cookbook_file.group(@new_resource.files_group) if @new_resource.files_group
- cookbook_file.owner(@new_resource.files_owner) if @new_resource.files_owner
- cookbook_file.backup(@new_resource.files_backup) if @new_resource.files_backup
+ res.mode(files_mode) if files_mode
+ res.group(files_group) if files_group
+ res.owner(files_owner) if files_owner
+ res.backup(files_backup) if files_backup
- cookbook_file
+ res
end
- def ensure_directory_exists(path)
- unless ::File.directory?(path)
- directory_to_create = resource_for_directory(path)
- directory_to_create.run_action(:create)
- @new_resource.updated_by_last_action(true) if directory_to_create.updated?
+ # This creates and uses a directory resource to create a directory if it is needed.
+ #
+ # @param [String] dir The path to the directory to create.
+ # @api private
+ #
+ def ensure_directory_exists(dir)
+ # doing the check here and skipping the resource should be more performant
+ unless ::File.directory?(dir)
+ res = directory_resource(dir)
+ res.run_action(:create)
+ new_resource.updated_by_last_action(true) if res.updated?
end
end
- def resource_for_directory(path)
- dir = Chef::Resource::Directory.new(path, run_context)
- dir.cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name
- if Chef::Platform.windows? && @new_resource.rights
+ # This creates the directory resource for ensure_directory_exists.
+ #
+ # @param [String] dir Directory path on the system
+ # @return [Chef::Resource::Directory] The built directory resource
+ # @api private
+ #
+ def directory_resource(dir)
+ res = Chef::Resource::Directory.new(dir, run_context)
+ res.cookbook_name = resource_cookbook
+ if Chef::Platform.windows? && rights
# rights are only meant to be applied to the toppest-level directory;
# Windows will handle inheritance.
- if path == @new_resource.path
- @new_resource.rights.each do |rights| #rights is a hash
- permissions = rights.delete(:permissions) #delete will return the value or nil if not found
- principals = rights.delete(:principals)
- dir.rights(permissions, principals, rights)
+ if dir == path
+ rights.each do |r|
+ r = r.dup # do not update the new_resource
+ permissions = r.delete(:permissions)
+ principals = r.delete(:principals)
+ res.rights(permissions, principals, r)
end
end
end
- dir.mode(@new_resource.mode) if @new_resource.mode
- dir.group(@new_resource.group)
- dir.owner(@new_resource.owner)
- dir.recursive(true)
- dir
- end
+ res.mode(mode) if mode
+ res.group(group) if group
+ res.owner(owner) if owner
+ res.recursive(true)
- def whyrun_supported?
- true
+ res
end
+ #
+ # Add back deprecated methods and aliases that are internally unused and should be removed in Chef-13
+ #
+ extend Chef::Deprecation::Warnings
+ include Chef::Deprecation::Provider::RemoteDirectory
+ add_deprecation_warnings_for(Chef::Deprecation::Provider::RemoteDirectory.instance_methods)
+
+ alias_method :resource_for_directory, :directory_resource
+ add_deprecation_warnings_for([:resource_for_directory])
+
end
end
end
diff --git a/lib/chef/provider/remote_file.rb b/lib/chef/provider/remote_file.rb
index da2573dacb..6548300c5d 100644
--- a/lib/chef/provider/remote_file.rb
+++ b/lib/chef/provider/remote_file.rb
@@ -17,13 +17,14 @@
# limitations under the License.
#
-require 'chef/provider/file'
-require 'chef/deprecation/provider/remote_file'
-require 'chef/deprecation/warnings'
+require "chef/provider/file"
+require "chef/deprecation/provider/remote_file"
+require "chef/deprecation/warnings"
class Chef
class Provider
class RemoteFile < Chef::Provider::File
+ provides :remote_file
extend Chef::Deprecation::Warnings
include Chef::Deprecation::Provider::RemoteFile
diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb
index f9b729362c..85b723ece4 100644
--- a/lib/chef/provider/remote_file/cache_control_data.rb
+++ b/lib/chef/provider/remote_file/cache_control_data.rb
@@ -19,11 +19,11 @@
# limitations under the License.
#
-require 'stringio'
-require 'chef/file_cache'
-require 'chef/json_compat'
-require 'chef/digester'
-require 'chef/exceptions'
+require "stringio"
+require "chef/file_cache"
+require "chef/json_compat"
+require "chef/digester"
+require "chef/exceptions"
class Chef
class Provider
@@ -145,18 +145,51 @@ class Chef
end
def load_json_data
- Chef::FileCache.load("remote_file/#{sanitized_cache_file_basename}")
+ path = sanitized_cache_file_path(sanitized_cache_file_basename)
+ if Chef::FileCache.has_key?(path)
+ Chef::FileCache.load(path)
+ else
+ old_path = sanitized_cache_file_path(sanitized_cache_file_basename_md5)
+ if Chef::FileCache.has_key?(old_path)
+ # We found an old cache control data file. We started using sha256 instead of md5
+ # to name these. Upgrade the file to the new name.
+ Chef::Log.debug("Found old cache control data file at #{old_path}. Moving to #{path}.")
+ Chef::FileCache.load(old_path).tap do |data|
+ Chef::FileCache.store(path, data)
+ Chef::FileCache.delete(old_path)
+ end
+ else
+ raise Chef::Exceptions::FileNotFound
+ end
+ end
end
- def sanitized_cache_file_basename
+ def sanitized_cache_file_path(basename)
+ "remote_file/#{basename}"
+ end
+
+ def scrubbed_uri
# Scrub and truncate in accordance with the goals of keeping the name
# human-readable but within the bounds of local file system
# path length limits
- scrubbed_uri = uri.gsub(/\W/, '_')[0..63]
+ uri.gsub(/\W/, "_")[0..63]
+ end
+
+ def sanitized_cache_file_basename
+ uri_sha2 = Chef::Digester.instance.generate_checksum(StringIO.new(uri))
+ cache_file_basename(uri_sha2[0,32])
+ end
+
+
+ def sanitized_cache_file_basename_md5
+ # Old way of creating the file basename
uri_md5 = Chef::Digester.instance.generate_md5_checksum(StringIO.new(uri))
- "#{scrubbed_uri}-#{uri_md5}.json"
+ cache_file_basename(uri_md5)
end
+ def cache_file_basename(checksum)
+ "#{scrubbed_uri}-#{checksum}.json"
+ end
end
end
end
diff --git a/lib/chef/provider/remote_file/content.rb b/lib/chef/provider/remote_file/content.rb
index ef55dd77cd..02c0cff457 100644
--- a/lib/chef/provider/remote_file/content.rb
+++ b/lib/chef/provider/remote_file/content.rb
@@ -1,7 +1,7 @@
#
# Author:: Jesse Campbell (<hikeit@gmail.com>)
# Author:: Lamont Granquist (<lamont@opscode.com>)
-# Copyright:: Copyright (c) 2013 Opscode, Inc.
+# Copyright:: Copyright (c) 2013-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,9 +17,10 @@
# limitations under the License.
#
-require 'uri'
-require 'tempfile'
-require 'chef/file_content_management/content_base'
+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,10 +48,14 @@ 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}")
+ Chef::Log.warn("#{@new_resource} cannot be downloaded from #{source}: #{e}")
if source = sources.shift
Chef::Log.info("#{@new_resource} trying to download from another mirror")
retry
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/ftp.rb b/lib/chef/provider/remote_file/ftp.rb
index 3f78286aa3..a81126f27f 100644
--- a/lib/chef/provider/remote_file/ftp.rb
+++ b/lib/chef/provider/remote_file/ftp.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'uri'
-require 'tempfile'
-require 'net/ftp'
-require 'chef/provider/remote_file'
-require 'chef/file_content_management/tempfile'
+require "uri"
+require "tempfile"
+require "net/ftp"
+require "chef/provider/remote_file"
+require "chef/file_content_management/tempfile"
class Chef
class Provider
@@ -59,7 +59,7 @@ class Chef
if uri.userinfo
URI.unescape(uri.user)
else
- 'anonymous'
+ "anonymous"
end
end
@@ -94,11 +94,11 @@ class Chef
private
def with_proxy_env
- saved_socks_env = ENV['SOCKS_SERVER']
- ENV['SOCKS_SERVER'] = proxy_uri(@uri).to_s
+ saved_socks_env = ENV["SOCKS_SERVER"]
+ ENV["SOCKS_SERVER"] = proxy_uri(@uri).to_s
yield
ensure
- ENV['SOCKS_SERVER'] = saved_socks_env
+ ENV["SOCKS_SERVER"] = saved_socks_env
end
def with_connection
@@ -162,7 +162,7 @@ class Chef
end
def parse_path
- path = uri.path.sub(%r{\A/}, '%2F') # re-encode the beginning slash because uri library decodes it.
+ path = uri.path.sub(%r{\A/}, "%2F") # re-encode the beginning slash because uri library decodes it.
directories = path.split(%r{/}, -1)
directories.each {|d|
d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
diff --git a/lib/chef/provider/remote_file/http.rb b/lib/chef/provider/remote_file/http.rb
index f17ab5a56d..218d7ef223 100644
--- a/lib/chef/provider/remote_file/http.rb
+++ b/lib/chef/provider/remote_file/http.rb
@@ -17,10 +17,10 @@
# limitations under the License.
#
-require 'chef/http/simple'
-require 'chef/digester'
-require 'chef/provider/remote_file'
-require 'chef/provider/remote_file/cache_control_data'
+require "chef/http/simple"
+require "chef/digester"
+require "chef/provider/remote_file"
+require "chef/provider/remote_file/cache_control_data"
class Chef
class Provider
@@ -87,11 +87,11 @@ class Chef
end
def last_modified_time_from(response)
- response['last_modified'] || response['date']
+ response["last_modified"] || response["date"]
end
def etag_from(response)
- response['etag']
+ response["etag"]
end
def http_client_opts
@@ -105,7 +105,7 @@ class Chef
# case you'd end up with a tar archive (no gzip) named, e.g., foo.tgz,
# which is not what you wanted.
if uri.to_s =~ /gz$/
- Chef::Log.debug("turning gzip compression off due to filename ending in gz")
+ Chef::Log.debug("Turning gzip compression off due to filename ending in gz")
opts[:disable_gzip] = true
end
opts
diff --git a/lib/chef/provider/remote_file/local_file.rb b/lib/chef/provider/remote_file/local_file.rb
index e78311f2c3..2e99886a00 100644
--- a/lib/chef/provider/remote_file/local_file.rb
+++ b/lib/chef/provider/remote_file/local_file.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'uri'
-require 'tempfile'
-require 'chef/provider/remote_file'
+require "uri"
+require "tempfile"
+require "chef/provider/remote_file"
class Chef
class Provider
@@ -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..7c066cb052
--- /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/route.rb b/lib/chef/provider/route.rb
index 72a5029a94..4abfd806b9 100644
--- a/lib/chef/provider/route.rb
+++ b/lib/chef/provider/route.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/mixin/command'
-require 'chef/provider'
-require 'ipaddr'
+require "chef/log"
+require "chef/mixin/command"
+require "chef/provider"
+require "ipaddr"
class Chef::Provider::Route < Chef::Provider
include Chef::Mixin::Command
@@ -28,52 +28,52 @@ class Chef::Provider::Route < Chef::Provider
attr_accessor :is_running
- MASK = {'0.0.0.0' => '0',
- '128.0.0.0' => '1',
- '192.0.0.0' => '2',
- '224.0.0.0' => '3',
- '240.0.0.0' => '4',
- '248.0.0.0' => '5',
- '252.0.0.0' => '6',
- '254.0.0.0' => '7',
- '255.0.0.0' => '8',
- '255.128.0.0' => '9',
- '255.192.0.0' => '10',
- '255.224.0.0' => '11',
- '255.240.0.0' => '12',
- '255.248.0.0' => '13',
- '255.252.0.0' => '14',
- '255.254.0.0' => '15',
- '255.255.0.0' => '16',
- '255.255.128.0' => '17',
- '255.255.192.0' => '18',
- '255.255.224.0' => '19',
- '255.255.240.0' => '20',
- '255.255.248.0' => '21',
- '255.255.252.0' => '22',
- '255.255.254.0' => '23',
- '255.255.255.0' => '24',
- '255.255.255.128' => '25',
- '255.255.255.192' => '26',
- '255.255.255.224' => '27',
- '255.255.255.240' => '28',
- '255.255.255.248' => '29',
- '255.255.255.252' => '30',
- '255.255.255.254' => '31',
- '255.255.255.255' => '32' }
+ MASK = {"0.0.0.0" => "0",
+ "128.0.0.0" => "1",
+ "192.0.0.0" => "2",
+ "224.0.0.0" => "3",
+ "240.0.0.0" => "4",
+ "248.0.0.0" => "5",
+ "252.0.0.0" => "6",
+ "254.0.0.0" => "7",
+ "255.0.0.0" => "8",
+ "255.128.0.0" => "9",
+ "255.192.0.0" => "10",
+ "255.224.0.0" => "11",
+ "255.240.0.0" => "12",
+ "255.248.0.0" => "13",
+ "255.252.0.0" => "14",
+ "255.254.0.0" => "15",
+ "255.255.0.0" => "16",
+ "255.255.128.0" => "17",
+ "255.255.192.0" => "18",
+ "255.255.224.0" => "19",
+ "255.255.240.0" => "20",
+ "255.255.248.0" => "21",
+ "255.255.252.0" => "22",
+ "255.255.254.0" => "23",
+ "255.255.255.0" => "24",
+ "255.255.255.128" => "25",
+ "255.255.255.192" => "26",
+ "255.255.255.224" => "27",
+ "255.255.255.240" => "28",
+ "255.255.255.248" => "29",
+ "255.255.255.252" => "30",
+ "255.255.255.254" => "31",
+ "255.255.255.255" => "32" }
def hex2ip(hex_data)
# Cleanup hex data
- hex_ip = hex_data.to_s.downcase.gsub(/[^0-9a-f]/, '')
+ hex_ip = hex_data.to_s.downcase.gsub(/[^0-9a-f]/, "")
# Check hex data format (IP is a 32bit integer, so should be 8 chars long)
return nil if hex_ip.length != hex_data.length || hex_ip.length != 8
# Extract octets from hex data
- octets = hex_ip.scan(/../).reverse.collect { |octet| [octet].pack('H2').unpack("C").first }
+ octets = hex_ip.scan(/../).reverse.collect { |octet| [octet].pack("H2").unpack("C").first }
# Validate IP
- ip = octets.join('.')
+ ip = octets.join(".")
begin
IPAddr.new(ip, Socket::AF_INET).to_s
rescue ArgumentError
@@ -197,7 +197,7 @@ class Chef::Provider::Route < Chef::Provider
end
def generate_command(action)
- common_route_items = ''
+ common_route_items = ""
common_route_items << "/#{MASK[@new_resource.netmask.to_s]}" if @new_resource.netmask
common_route_items << " via #{@new_resource.gateway} " if @new_resource.gateway
@@ -215,7 +215,7 @@ class Chef::Provider::Route < Chef::Provider
end
def config_file_contents(action, options={})
- content = ''
+ content = ""
case action
when :add
content << "#{options[:target]}"
diff --git a/lib/chef/provider/script.rb b/lib/chef/provider/script.rb
index e8b5235b7a..d95962a15b 100644
--- a/lib/chef/provider/script.rb
+++ b/lib/chef/provider/script.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'tempfile'
-require 'chef/provider/execute'
-require 'forwardable'
+require "tempfile"
+require "chef/provider/execute"
+require "forwardable"
class Chef
class Provider
@@ -27,6 +27,7 @@ class Chef
provides :bash
provides :csh
+ provides :ksh
provides :perl
provides :python
provides :ruby
diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb
index 75da2ddb31..6e1d0b4064 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");
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/mixin/command'
-require 'chef/provider'
+require "chef/mixin/command"
+require "chef/provider"
class Chef
class Provider
@@ -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,24 +38,33 @@ class Chef
true
end
- def load_new_resource_state
- # If the user didn't specify a change in enabled state,
- # it will be the same as the old resource
- if ( @new_resource.enabled.nil? )
- @new_resource.enabled(@current_resource.enabled)
- end
- if ( @new_resource.running.nil? )
- @new_resource.running(@current_resource.running)
- end
- end
+ def load_current_resource
+ supports[:status] = false if supports[:status].nil?
+ supports[:reload] = false if supports[:reload].nil?
+ supports[:restart] = false if supports[:restart].nil?
+ end
+
+ # the new_resource#enabled and #running variables are not user input, but when we
+ # do (e.g.) action_enable we want to set new_resource.enabled so that the comparison
+ # between desired and current state produces the correct change in reporting.
+ # XXX?: the #nil? check below will likely fail if this is a cloned resource or if
+ # we just run multiple actions.
+ def load_new_resource_state
+ if ( @new_resource.enabled.nil? )
+ @new_resource.enabled(@current_resource.enabled)
+ end
+ if ( @new_resource.running.nil? )
+ @new_resource.running(@current_resource.running)
+ end
+ end
def shared_resource_requirements
end
def define_resource_requirements
requirements.assert(:reload) do |a|
- a.assertion { @new_resource.supports[:reload] || @new_resource.reload_command }
- a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
+ a.assertion { supports[:reload] || @new_resource.reload_command }
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload"
# if a service is not declared to support reload, that won't
# typically change during the course of a run - so no whyrun
# alternative here.
@@ -130,27 +143,27 @@ class Chef
end
def enable_service
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :enable"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :enable"
end
def disable_service
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :disable"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :disable"
end
def start_service
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :start"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :start"
end
def stop_service
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :stop"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :stop"
end
def restart_service
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :restart"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :restart"
end
def reload_service
- raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :reload"
+ raise Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload"
end
protected
@@ -168,6 +181,32 @@ class Chef
@new_resource.respond_to?(method_name) &&
!!@new_resource.send(method_name)
end
+
+ module ServicePriorityInit
+
+ #
+ # Platform-specific versions
+ #
+
+ #
+ # Linux
+ #
+
+ require "chef/chef_class"
+ require "chef/provider/service/systemd"
+ require "chef/provider/service/insserv"
+ require "chef/provider/service/redhat"
+ require "chef/provider/service/arch"
+ require "chef/provider/service/gentoo"
+ require "chef/provider/service/upstart"
+ require "chef/provider/service/debian"
+ require "chef/provider/service/invokercd"
+
+ Chef.set_provider_priority_array :service, [ Systemd, Arch ], platform_family: "arch"
+ Chef.set_provider_priority_array :service, [ Systemd, Gentoo ], platform_family: "gentoo"
+ Chef.set_provider_priority_array :service, [ Systemd, Upstart, Insserv, Debian, Invokercd ], platform_family: "debian"
+ Chef.set_provider_priority_array :service, [ Systemd, Insserv, Redhat ], platform_family: %w{rhel fedora suse}
+ end
end
end
end
diff --git a/lib/chef/provider/service/aix.rb b/lib/chef/provider/service/aix.rb
index 0aef62c62e..1968f8f3de 100644
--- a/lib/chef/provider/service/aix.rb
+++ b/lib/chef/provider/service/aix.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/provider/service'
+require "chef/provider/service"
class Chef
class Provider
@@ -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/aixinit.rb b/lib/chef/provider/service/aixinit.rb
index 19beac79f0..66d85984fa 100644
--- a/lib/chef/provider/service/aixinit.rb
+++ b/lib/chef/provider/service/aixinit.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/provider/service/init'
+require "chef/provider/service/init"
class Chef
class Provider
@@ -60,14 +60,14 @@ class Chef
Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).each { |f| ::File.delete(f)}
if @new_resource.priority.is_a? Integer
- create_symlink(2, 'S', @new_resource.priority)
+ create_symlink(2, "S", @new_resource.priority)
elsif @new_resource.priority.is_a? Hash
@new_resource.priority.each do |level,o|
- create_symlink(level,(o[0] == :start ? 'S' : 'K'),o[1])
+ create_symlink(level,(o[0] == :start ? "S" : "K"),o[1])
end
else
- create_symlink(2, 'S', '')
+ create_symlink(2, "S", "")
end
end
@@ -75,13 +75,13 @@ class Chef
Dir.glob(["/etc/rc.d/rc2.d/[SK][0-9][0-9]#{@new_resource.service_name}", "/etc/rc.d/rc2.d/[SK]#{@new_resource.service_name}"]).each { |f| ::File.delete(f) }
if @new_resource.priority.is_a? Integer
- create_symlink(2, 'K',100 - @new_resource.priority)
+ create_symlink(2, "K",100 - @new_resource.priority)
elsif @new_resource.priority.is_a? Hash
@new_resource.priority.each do |level,o|
- create_symlink(level, 'K', 100 - o[1]) if o[0] == :stop
+ create_symlink(level, "K", 100 - o[1]) if o[0] == :stop
end
else
- create_symlink(2, 'K', '')
+ create_symlink(2, "K", "")
end
end
@@ -98,7 +98,7 @@ class Chef
files.each do |file|
if (RC_D_SCRIPT_NAME =~ file)
- priority[2] = [($1 == "S" ? :start : :stop), ($2.empty? ? '' : $2.to_i)]
+ priority[2] = [($1 == "S" ? :start : :stop), ($2.empty? ? "" : $2.to_i)]
if $1 == "S"
is_enabled = true
end
diff --git a/lib/chef/provider/service/arch.rb b/lib/chef/provider/service/arch.rb
index e7fbcc820c..3e7b9fcaca 100644
--- a/lib/chef/provider/service/arch.rb
+++ b/lib/chef/provider/service/arch.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/provider/service/init'
+require "chef/provider/service/init"
class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
@@ -50,7 +50,7 @@ class Chef::Provider::Service::Arch < Chef::Provider::Service::Init
def daemons
entries = []
if ::File.read("/etc/rc.conf").match(/DAEMONS=\((.*)\)/m)
- entries += $1.gsub(/\\?[\r\n]/, ' ').gsub(/# *[^ ]+/,' ').split(' ') if $1.length > 0
+ entries += $1.gsub(/\\?[\r\n]/, " ").gsub(/# *[^ ]+/," ").split(" ") if $1.length > 0
end
yield(entries) if block_given?
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb
index 01505924cb..559ad48418 100644
--- a/lib/chef/provider/service/debian.rb
+++ b/lib/chef/provider/service/debian.rb
@@ -16,21 +16,19 @@
# limitations under the License.
#
-require 'chef/provider/service/init'
+require "chef/provider/service/init"
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
@@ -111,7 +109,7 @@ class Chef
priority.each { |runlevel, arguments|
Chef::Log.debug("#{new_resource} runlevel #{runlevel}, action #{arguments[0]}, priority #{arguments[1]}")
# if we are in a update-rc.d default startup runlevel && we start in this runlevel
- if %w[ 1 2 3 4 5 S ].include?(runlevel) && arguments[0] == :start
+ if %w{ 1 2 3 4 5 S }.include?(runlevel) && arguments[0] == :start
enabled = true
end
}
diff --git a/lib/chef/provider/service/freebsd.rb b/lib/chef/provider/service/freebsd.rb
index 9204e3ef92..4aa9eb09f6 100644
--- a/lib/chef/provider/service/freebsd.rb
+++ b/lib/chef/provider/service/freebsd.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/resource/service'
-require 'chef/provider/service/init'
-require 'chef/mixin/command'
+require "chef/resource/service"
+require "chef/provider/service/init"
+require "chef/mixin/command"
class Chef
class Provider
@@ -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
@@ -119,11 +119,11 @@ class Chef
private
def read_rc_conf
- ::File.open("/etc/rc.conf", 'r') { |file| file.readlines }
+ ::File.open("/etc/rc.conf", "r") { |file| file.readlines }
end
def write_rc_conf(lines)
- ::File.open("/etc/rc.conf", 'w') do |file|
+ ::File.open("/etc/rc.conf", "w") do |file|
lines.each { |line| file.puts(line) }
end
end
@@ -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..28fd43a709 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");
@@ -17,18 +17,18 @@
# limitations under the License.
#
-require 'chef/provider/service/init'
-require 'chef/mixin/command'
-require 'chef/util/path_helper'
+require "chef/provider/service/init"
+require "chef/mixin/command"
+require "chef/util/path_helper"
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..90c0ec2b34 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");
@@ -16,8 +16,9 @@
# limitations under the License.
#
-require 'chef/provider/service/simple'
-require 'chef/mixin/command'
+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..1c4d294053 100644
--- a/lib/chef/provider/service/insserv.rb
+++ b/lib/chef/provider/service/insserv.rb
@@ -16,18 +16,16 @@
# limitations under the License.
#
-require 'chef/provider/service/init'
-require 'chef/util/path_helper'
+require "chef/provider/service/init"
+require "chef/util/path_helper"
class Chef
class Provider
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..a48e2ac815 100644
--- a/lib/chef/provider/service/invokercd.rb
+++ b/lib/chef/provider/service/invokercd.rb
@@ -16,17 +16,15 @@
# limitations under the License.
#
-require 'chef/provider/service/init'
+require "chef/provider/service/init"
class Chef
class Provider
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 df5be54fda..b939f78b03 100644
--- a/lib/chef/provider/service/macosx.rb
+++ b/lib/chef/provider/service/macosx.rb
@@ -16,16 +16,19 @@
# limitations under the License.
#
-require 'rexml/document'
-require 'chef/resource/service'
-require 'chef/provider/service/simple'
-require 'chef/util/path_helper'
+require "etc"
+require "rexml/document"
+require "chef/resource/service"
+require "chef/resource/macosx_service"
+require "chef/provider/service/simple"
+require "chef/util/path_helper"
class Chef
class Provider
class Service
class Macosx < Chef::Provider::Service::Simple
+ provides :macosx_service, os: "darwin"
provides :service, os: "darwin"
def self.gather_plist_dirs
@@ -33,27 +36,45 @@ class Chef
/Library/LaunchDaemons
/System/Library/LaunchAgents
/System/Library/LaunchDaemons }
- Chef::Util::PathHelper.home('Library', 'LaunchAgents') { |p| locations << p }
+ 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"
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload"
end
requirements.assert(:all_actions) do |a|
@@ -61,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,
@@ -69,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)
@@ -85,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
@@ -97,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
@@ -106,9 +133,9 @@ class Chef
if @new_resource.restart_command
super
else
- stop_service
+ unload_service
sleep 1
- start_service
+ load_service
end
end
@@ -121,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
@@ -132,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
@@ -171,13 +206,16 @@ class Chef
end
end
- private
+ private
def find_service_label
# CHEF-5223 "you can't glob for a file that hasn't been converged
# 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
@@ -185,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..10d7b21953 100644
--- a/lib/chef/provider/service/openbsd.rb
+++ b/lib/chef/provider/service/openbsd.rb
@@ -16,35 +16,36 @@
# limitations under the License.
#
-require 'chef/mixin/command'
-require 'chef/mixin/shell_out'
-require 'chef/provider/service/init'
-require 'chef/resource/service'
+require "chef/mixin/command"
+require "chef/mixin/shell_out"
+require "chef/provider/service/init"
+require "chef/resource/service"
class Chef
class Provider
class Service
class Openbsd < Chef::Provider::Service::Init
- provides :service, os: [ "openbsd" ]
+ provides :service, os: "openbsd"
include Chef::Mixin::ShellOut
attr_reader :init_command, :rc_conf, :rc_conf_local, :enabled_state_found
- RC_CONF_PATH = '/etc/rc.conf'
- RC_CONF_LOCAL_PATH = '/etc/rc.conf.local'
+ RC_CONF_PATH = "/etc/rc.conf"
+ RC_CONF_LOCAL_PATH = "/etc/rc.conf.local"
def initialize(new_resource, run_context)
super
- @rc_conf = ::File.read(RC_CONF_PATH) rescue ''
- @rc_conf_local = ::File.read(RC_CONF_LOCAL_PATH) rescue ''
+ @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)
@@ -81,7 +82,7 @@ class Chef
if !is_enabled?
if is_builtin?
if is_enabled_by_default?
- update_rcl rc_conf_local.sub(/^#{Regexp.escape(builtin_service_enable_variable_name)}=.*/, '')
+ update_rcl rc_conf_local.sub(/^#{Regexp.escape(builtin_service_enable_variable_name)}=.*/, "")
else
# add line with blank string, which means enable
update_rcl rc_conf_local + "\n" + "#{builtin_service_enable_variable_name}=\"\"\n"
@@ -89,7 +90,7 @@ class Chef
else
# add to pkg_scripts, most recent addition goes last
old_services_list = rc_conf_local.match(/^pkg_scripts="(.*)"/)
- old_services_list = old_services_list ? old_services_list[1].split(' ') : []
+ old_services_list = old_services_list ? old_services_list[1].split(" ") : []
new_services_list = old_services_list + [new_resource.service_name]
if rc_conf_local.match(/^pkg_scripts="(.*)"/)
new_rcl = rc_conf_local.sub(/^pkg_scripts="(.*)"/, "pkg_scripts=\"#{new_services_list.join(' ')}\"")
@@ -109,12 +110,12 @@ class Chef
update_rcl rc_conf_local + "\n" + "#{builtin_service_enable_variable_name}=\"NO\"\n"
else
# remove line to disable
- update_rcl rc_conf_local.sub(/^#{Regexp.escape(builtin_service_enable_variable_name)}=.*/, '')
+ update_rcl rc_conf_local.sub(/^#{Regexp.escape(builtin_service_enable_variable_name)}=.*/, "")
end
else
# remove from pkg_scripts
old_list = rc_conf_local.match(/^pkg_scripts="(.*)"/)
- old_list = old_list ? old_list[1].split(' ') : []
+ old_list = old_list ? old_list[1].split(" ") : []
new_list = old_list - [new_resource.service_name]
update_rcl rc_conf_local.sub(/^pkg_scripts="(.*)"/, pkg_scripts="#{new_list.join(' ')}")
end
diff --git a/lib/chef/provider/service/redhat.rb b/lib/chef/provider/service/redhat.rb
index 850953125e..9cc4258b70 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");
@@ -16,31 +16,39 @@
# limitations under the License.
#
-require 'chef/provider/service/init'
+require "chef/provider/service/init"
class Chef
class Provider
class Service
class Redhat < Chef::Provider::Service::Init
- CHKCONFIG_ON = /\d:on/
- CHKCONFIG_MISSING = /No such/
-
- provides :service, platform_family: [ "rhel", "fedora", "suse" ]
+ # @api private
+ attr_accessor :service_missing
+ # @api private
+ attr_accessor :current_run_levels
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
+ provides :service, platform_family: %w{rhel fedora suse} do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:redhat)
end
+ CHKCONFIG_ON = /\d:on/
+ CHKCONFIG_MISSING = /No such/
+
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:initd)
end
def initialize(new_resource, run_context)
super
- @init_command = "/sbin/service #{@new_resource.service_name}"
- @new_resource.supports[:status] = true
+ @init_command = "/sbin/service #{new_resource.service_name}"
@service_missing = false
+ @current_run_levels = []
+ end
+
+ # @api private
+ def run_levels
+ new_resource.run_levels
end
def define_resource_requirements
@@ -49,34 +57,62 @@ class Chef
requirements.assert(:all_actions) do |a|
chkconfig_file = "/sbin/chkconfig"
a.assertion { ::File.exists? chkconfig_file }
- a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} does not exist!"
+ a.failure_message Chef::Exceptions::Service, "#{chkconfig_file} dbleoes not exist!"
end
requirements.assert(:start, :enable, :reload, :restart) do |a|
- a.assertion { !@service_missing }
- a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the init.d script!"
+ a.assertion do
+ custom_command_for_action?(action) || !@service_missing
+ end
+ a.failure_message Chef::Exceptions::Service, "#{new_resource}: No custom command for #{action} specified and unable to locate the init.d script!"
a.whyrun "Assuming service would be disabled. The init script is not presently installed."
end
end
def load_current_resource
+ supports[:status] = true if supports[:status].nil?
+
super
if ::File.exists?("/sbin/chkconfig")
- chkconfig = shell_out!("/sbin/chkconfig --list #{@current_resource.service_name}", :returns => [0,1])
- @current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+ chkconfig = shell_out!("/sbin/chkconfig --list #{current_resource.service_name}", :returns => [0,1])
+ unless run_levels.nil? or run_levels.empty?
+ all_levels_match = true
+ chkconfig.stdout.split(/\s+/)[1..-1].each do |level|
+ index = level.split(":").first
+ status = level.split(":").last
+ if level =~ CHKCONFIG_ON
+ @current_run_levels << index.to_i
+ all_levels_match = false unless run_levels.include?(index.to_i)
+ else
+ all_levels_match = false if run_levels.include?(index.to_i)
+ end
+ end
+ current_resource.enabled(all_levels_match)
+ else
+ current_resource.enabled(!!(chkconfig.stdout =~ CHKCONFIG_ON))
+ end
@service_missing = !!(chkconfig.stderr =~ CHKCONFIG_MISSING)
end
- @current_resource
+ current_resource
+ end
+
+ # @api private
+ def levels
+ (run_levels.nil? or run_levels.empty?) ? "" : "--level #{run_levels.join('')} "
end
def enable_service()
- shell_out! "/sbin/chkconfig #{@new_resource.service_name} on"
+ unless run_levels.nil? or run_levels.empty?
+ disable_levels = current_run_levels - run_levels
+ shell_out! "/sbin/chkconfig --level #{disable_levels.join('')} #{new_resource.service_name} off" unless disable_levels.empty?
+ end
+ shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} on"
end
def disable_service()
- shell_out! "/sbin/chkconfig #{@new_resource.service_name} off"
+ shell_out! "/sbin/chkconfig #{levels}#{new_resource.service_name} off"
end
end
end
diff --git a/lib/chef/provider/service/simple.rb b/lib/chef/provider/service/simple.rb
index ee403ee163..a096c9dfc0 100644
--- a/lib/chef/provider/service/simple.rb
+++ b/lib/chef/provider/service/simple.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/provider/service'
-require 'chef/resource/service'
-require 'chef/mixin/command'
+require "chef/provider/service"
+require "chef/resource/service"
+require "chef/mixin/command"
class Chef
class Provider
@@ -58,25 +58,25 @@ class Chef
shared_resource_requirements
requirements.assert(:start) do |a|
a.assertion { @new_resource.start_command }
- a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that start_command be set"
+ a.failure_message Chef::Exceptions::Service, "#{self} requires that start_command be set"
end
requirements.assert(:stop) do |a|
a.assertion { @new_resource.stop_command }
- a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires that stop_command be set"
+ a.failure_message Chef::Exceptions::Service, "#{self} requires that stop_command be set"
end
requirements.assert(:restart) do |a|
a.assertion { @new_resource.restart_command || ( @new_resource.start_command && @new_resource.stop_command ) }
- a.failure_message Chef::Exceptions::Service, "#{self.to_s} requires a restart_command or both start_command and stop_command be set in order to perform a restart"
+ a.failure_message Chef::Exceptions::Service, "#{self} requires a restart_command or both start_command and stop_command be set in order to perform a restart"
end
requirements.assert(:reload) do |a|
a.assertion { @new_resource.reload_command }
- a.failure_message Chef::Exceptions::UnsupportedAction, "#{self.to_s} requires a reload_command be set in order to perform a reload"
+ a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} requires a reload_command be set in order to perform a reload"
end
requirements.assert(:all_actions) do |a|
- a.assertion { @new_resource.status_command or @new_resource.supports[:status] or
+ a.assertion { @new_resource.status_command or supports[:status] or
(!ps_cmd.nil? and !ps_cmd.empty?) }
a.failure_message Chef::Exceptions::Service, "#{@new_resource} could not determine how to inspect the process table, please set this node's 'command.ps' attribute"
end
@@ -127,7 +127,7 @@ class Chef
nil
end
- elsif @new_resource.supports[:status]
+ elsif supports[:status]
Chef::Log.debug("#{@new_resource} supports status, running")
begin
if shell_out("#{default_init_command} status").exitstatus == 0
diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb
index eaea6bb1ab..c43a25258c 100644
--- a/lib/chef/provider/service/solaris.rb
+++ b/lib/chef/provider/service/solaris.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/provider/service'
-require 'chef/resource/service'
-require 'chef/mixin/command'
+require "chef/provider/service"
+require "chef/resource/service"
+require "chef/mixin/command"
class Chef
class Provider
@@ -30,35 +30,39 @@ class Chef
def initialize(new_resource, run_context=nil)
super
- @init_command = "/usr/sbin/svcadm"
- @status_command = "/bin/svcs -l"
+ @init_command = "/usr/sbin/svcadm"
+ @status_command = "/bin/svcs"
@maintenace = false
end
def load_current_resource
@current_resource = Chef::Resource::Service.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
- unless ::File.exists? "/bin/svcs"
- raise Chef::Exceptions::Service, "/bin/svcs does not exist!"
+
+ [@init_command, @status_command].each do |cmd|
+ unless ::File.executable? cmd then
+ raise Chef::Exceptions::Service, "#{cmd} not executable!"
+ end
end
@status = service_status.enabled
+
@current_resource
end
def enable_service
- shell_out!("#{default_init_command} clear #{@new_resource.service_name}") if @maintenance
- shell_out!("#{default_init_command} enable -s #{@new_resource.service_name}")
+ shell_out!(default_init_command, "clear", @new_resource.service_name) if @maintenance
+ shell_out!(default_init_command, "enable", "-s", @new_resource.service_name)
end
def disable_service
- shell_out!("#{default_init_command} disable -s #{@new_resource.service_name}")
+ shell_out!(default_init_command, "disable", "-s", @new_resource.service_name)
end
alias_method :stop_service, :disable_service
alias_method :start_service, :enable_service
def reload_service
- shell_out_with_systems_locale!("#{default_init_command} refresh #{@new_resource.service_name}")
+ shell_out!(default_init_command, "refresh", @new_resource.service_name)
end
def restart_service
@@ -68,16 +72,38 @@ class Chef
end
def service_status
- status = shell_out!("#{@status_command} #{@current_resource.service_name}", :returns => [0, 1])
- status.stdout.each_line do |line|
- case line
- when /state\s+online/
- @current_resource.enabled(true)
- @current_resource.running(true)
- when /state\s+maintenance/
- @maintenance = true
- end
+ cmd = shell_out!(@status_command, "-l", @current_resource.service_name, :returns => [0, 1])
+ # Example output
+ # $ svcs -l rsyslog
+ # fmri svc:/application/rsyslog:default
+ # name rsyslog logging utility
+ # enabled true
+ # state online
+ # next_state none
+ # state_time April 2, 2015 04:25:19 PM EDT
+ # logfile /var/svc/log/application-rsyslog:default.log
+ # restarter svc:/system/svc/restarter:default
+ # contract_id 1115271
+ # dependency require_all/error svc:/milestone/multi-user:default (online)
+ # $
+
+ # load output into hash
+ status = {}
+ cmd.stdout.each_line do |line|
+ key, value = line.strip.split(/\s+/, 2)
+ status[key] = value
+ end
+
+ # check service state
+ @maintenance = false
+ case status["state"]
+ when "online"
+ @current_resource.enabled(true)
+ @current_resource.running(true)
+ when "maintenance"
+ @maintenance = true
end
+
unless @current_resource.enabled
@current_resource.enabled(false)
@current_resource.running(false)
diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb
index 9085ffde2e..bf4b324dce 100644
--- a/lib/chef/provider/service/systemd.rb
+++ b/lib/chef/provider/service/systemd.rb
@@ -16,22 +16,20 @@
# limitations under the License.
#
-require 'chef/resource/service'
-require 'chef/provider/service/simple'
-require 'chef/mixin/which'
+require "chef/resource/service"
+require "chef/provider/service/simple"
+require "chef/mixin/which"
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..c0f7e020f6 100644
--- a/lib/chef/provider/service/upstart.rb
+++ b/lib/chef/provider/service/upstart.rb
@@ -16,23 +16,22 @@
# limitations under the License.
#
-require 'chef/resource/service'
-require 'chef/provider/service/simple'
-require 'chef/mixin/command'
-require 'chef/util/file_edit'
+require "chef/resource/service"
+require "chef/provider/service/simple"
+require "chef/mixin/command"
+require "chef/util/file_edit"
class Chef
class Provider
class Service
class Upstart < Chef::Provider::Service::Simple
- UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
- provides :service, os: "linux"
-
- def self.provides?(node, resource)
- super && Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
+ provides :service, platform_family: "debian", override: true do |node|
+ Chef::Platform::ServiceHelpers.service_resource_providers.include?(:upstart)
end
+ UPSTART_STATE_FORMAT = /\S+ \(?(start|stop)?\)? ?[\/ ](\w+)/
+
def self.supports?(resource, action)
Chef::Platform::ServiceHelpers.config_for_service(resource.service_name).include?(:upstart)
end
@@ -107,7 +106,7 @@ class Chef
Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
begin
- if shell_out!(@new_resource.status_command) == 0
+ if shell_out!(@new_resource.status_command).exitstatus == 0
@current_resource.running true
end
rescue
@@ -131,7 +130,7 @@ class Chef
# Get enabled/disabled state by reading job configuration file
if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
- ::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}",'r') do |file|
+ ::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}","r") do |file|
while line = file.gets
case line
when /^start on/
@@ -225,10 +224,10 @@ class Chef
command = "/sbin/status #{@job}"
status = popen4(command) do |pid, stdin, stdout, stderr|
stdout.each_line do |line|
- # rsyslog stop/waiting
# service goal/state
# OR
- # rsyslog (stop) waiting
+ # service (instance) goal/state
+ # OR
# service (goal) state
line =~ UPSTART_STATE_FORMAT
data = Regexp.last_match
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index ba53f0a3c3..0ae3c5b09f 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -18,14 +18,13 @@
# limitations under the License.
#
-require 'chef/provider/service/simple'
+require "chef/provider/service/simple"
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require 'chef/win32/error'
- require 'win32/service'
+ require "chef/win32/error"
+ require "win32/service"
end
class Chef::Provider::Service::Windows < Chef::Provider::Service
-
provides :service, os: "windows"
provides :windows_service, os: "windows"
@@ -33,21 +32,23 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
include Chef::ReservedNames::Win32::API::Error rescue LoadError
#Win32::Service.get_start_type
- AUTO_START = 'auto start'
- MANUAL = 'demand start'
- DISABLED = 'disabled'
+ AUTO_START = "auto start"
+ MANUAL = "demand start"
+ DISABLED = "disabled"
#Win32::Service.get_current_state
- RUNNING = 'running'
- STOPPED = 'stopped'
- CONTINUE_PENDING = 'continue pending'
- PAUSE_PENDING = 'pause pending'
- PAUSED = 'paused'
- START_PENDING = 'start pending'
- STOP_PENDING = 'stop pending'
+ RUNNING = "running"
+ STOPPED = "stopped"
+ CONTINUE_PENDING = "continue pending"
+ PAUSE_PENDING = "pause pending"
+ PAUSED = "paused"
+ START_PENDING = "start pending"
+ STOP_PENDING = "stop pending"
TIMEOUT = 60
+ SERVICE_RIGHT = "SeServiceLogonRight"
+
def whyrun_supported?
false
end
@@ -79,10 +80,10 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
Win32::Service.configure(new_config)
Chef::Log.info "#{@new_resource} configured with #{new_config.inspect}"
- # it would be nice to check if the user already has the logon privilege, but that turns out to be
- # nontrivial.
if new_config.has_key?(:service_start_name)
- grant_service_logon(new_config[:service_start_name])
+ unless Chef::ReservedNames::Win32::Security.get_account_right(canonicalize_username(new_config[:service_start_name])).include?(SERVICE_RIGHT)
+ grant_service_logon(new_config[:service_start_name])
+ end
end
state = current_state
@@ -237,74 +238,25 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
private
- def make_policy_text(username)
- text = <<-EOS
-[Unicode]
-Unicode=yes
-[Privilege Rights]
-SeServiceLogonRight = \\\\#{canonicalize_username(username)},*S-1-5-80-0
-[Version]
-signature="$CHICAGO$"
-Revision=1
-EOS
- end
-
- def grant_logfile_name(username)
- Chef::Util::PathHelper.canonical_path("#{Dir.tmpdir}/logon_grant-#{clean_username_for_path(username)}-#{$$}.log", prefix=false)
- end
-
- def grant_policyfile_name(username)
- Chef::Util::PathHelper.canonical_path("#{Dir.tmpdir}/service_logon_policy-#{clean_username_for_path(username)}-#{$$}.inf", prefix=false)
- end
-
- def grant_dbfile_name(username)
- "#{ENV['TEMP']}\\secedit.sdb"
- end
-
def grant_service_logon(username)
- logfile = grant_logfile_name(username)
- policy_file = ::File.new(grant_policyfile_name(username), 'w')
- policy_text = make_policy_text(username)
- dbfile = grant_dbfile_name(username) # this is just an audit file.
-
begin
- Chef::Log.debug "Policy file text:\n#{policy_text}"
- policy_file.puts(policy_text)
- policy_file.close # need to flush the buffer.
-
- # it would be nice to do this with APIs instead, but the LSA_* APIs are
- # particularly onerous and life is short.
- cmd = %Q{secedit.exe /configure /db "#{dbfile}" /cfg "#{policy_file.path}" /areas USER_RIGHTS SECURITYPOLICY SERVICES /log "#{logfile}"}
- Chef::Log.debug "Granting logon-as-service privilege with: #{cmd}"
- runner = shell_out(cmd)
-
- if runner.exitstatus != 0
- Chef::Log.fatal "Logon-as-service grant failed with output: #{runner.stdout}"
- raise Chef::Exceptions::Service, <<-EOS
-Logon-as-service grant failed with policy file #{policy_file.path}.
-You can look at #{logfile} for details, or do `secedit /analyze #{dbfile}`.
-The failed command was `#{cmd}`.
-EOS
- end
-
- Chef::Log.info "Grant logon-as-service to user '#{username}' successful."
-
- ::File.delete(dbfile) rescue nil
- ::File.delete(policy_file)
- ::File.delete(logfile) rescue nil # logfile is not always present at end.
+ Chef::ReservedNames::Win32::Security.add_account_right(canonicalize_username(username), SERVICE_RIGHT)
+ rescue Chef::Exceptions::Win32APIError => err
+ Chef::Log.fatal "Logon-as-service grant failed with output: #{err}"
+ raise Chef::Exceptions::Service, "Logon-as-service grant failed for #{username}: #{err}"
end
+
+ Chef::Log.info "Grant logon-as-service to user '#{username}' successful."
true
end
# remove characters that make for broken or wonky filenames.
def clean_username_for_path(username)
- username.gsub(/[\/\\. ]+/, '_')
+ username.gsub(/[\/\\. ]+/, "_")
end
- # the security policy file only seems to accept \\username, so fix .\username or .\\username.
- # TODO: this probably has to be fixed to handle various valid Windows names correctly.
def canonicalize_username(username)
- username.sub(/^\.?\\+/, '')
+ username.sub(/^\.?\\+/, "")
end
def current_state
@@ -353,7 +305,7 @@ EOS
Chef::Log.debug "#{@new_resource.name} setting start_type to #{type}"
Win32::Service.configure(
:service_name => @new_resource.service_name,
- :start_type => allowed_types[type]
+ :start_type => allowed_types[type],
)
@new_resource.updated_by_last_action(true)
end
diff --git a/lib/chef/provider/subversion.rb b/lib/chef/provider/subversion.rb
index 5f36483c32..2cc0da65a0 100644
--- a/lib/chef/provider/subversion.rb
+++ b/lib/chef/provider/subversion.rb
@@ -18,10 +18,10 @@
#TODO subversion and git should both extend from a base SCM provider.
-require 'chef/log'
-require 'chef/provider'
-require 'chef/mixin/command'
-require 'fileutils'
+require "chef/log"
+require "chef/provider"
+require "chef/mixin/command"
+require "fileutils"
class Chef
class Provider
@@ -130,8 +130,8 @@ class Chef
@new_resource.revision
else
command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}")
- status, svn_info, error_message = output_of_command(command, run_options)
- handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}")
+ svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0,1])).stdout
+
extract_revision_info(svn_info)
end
end
@@ -142,11 +142,8 @@ class Chef
def find_current_revision
return nil unless ::File.exist?(::File.join(@new_resource.destination, ".svn"))
command = scm(:info)
- status, svn_info, error_message = output_of_command(command, run_options(:cwd => cwd))
+ svn_info = shell_out!(command, run_options(:cwd => cwd, :returns => [0,1])).stdout
- unless [0,1].include?(status.exitstatus)
- handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}")
- end
extract_revision_info(svn_info)
end
@@ -179,7 +176,8 @@ class Chef
end
attrs
end
- rev = (repo_attrs['Last Changed Rev'] || repo_attrs['Revision'])
+ rev = (repo_attrs["Last Changed Rev"] || repo_attrs["Revision"])
+ rev.strip! if rev
raise "Could not parse `svn info` data: #{svn_info}" if repo_attrs.empty?
Chef::Log.debug "#{@new_resource} resolved revision #{@new_resource.revision} to #{rev}"
rev
@@ -197,12 +195,20 @@ class Chef
end
def scm(*args)
- ['svn', *args].compact.join(" ")
+ binary = svn_binary
+ binary = "\"#{binary}\"" if binary =~ /\s/
+ [binary, *args].compact.join(" ")
end
def target_dir_non_existent_or_empty?
- !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == ['.','..']
+ !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination).sort == [".",".."]
end
+
+ def svn_binary
+ @new_resource.svn_binary ||
+ (Chef::Platform.windows? ? "svn.exe" : "svn")
+ end
+
def assert_target_directory_valid!
target_parent_directory = ::File.dirname(@new_resource.destination)
unless ::File.directory?(target_parent_directory)
diff --git a/lib/chef/provider/template.rb b/lib/chef/provider/template.rb
index 1e759074b9..7cbfca025f 100644
--- a/lib/chef/provider/template.rb
+++ b/lib/chef/provider/template.rb
@@ -17,10 +17,10 @@
# limitations under the License.
#
-require 'chef/provider/template_finder'
-require 'chef/provider/file'
-require 'chef/deprecation/provider/template'
-require 'chef/deprecation/warnings'
+require "chef/provider/template_finder"
+require "chef/provider/file"
+require "chef/deprecation/provider/template"
+require "chef/deprecation/warnings"
class Chef
class Provider
diff --git a/lib/chef/provider/template/content.rb b/lib/chef/provider/template/content.rb
index 7fc680ec85..891861de8b 100644
--- a/lib/chef/provider/template/content.rb
+++ b/lib/chef/provider/template/content.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/mixin/template'
-require 'chef/file_content_management/content_base'
+require "chef/mixin/template"
+require "chef/file_content_management/content_base"
class Chef
class Provider
@@ -29,20 +29,30 @@ class Chef
def template_location
@template_file_cache_location ||= begin
- template_finder.find(@new_resource.source, :local => @new_resource.local, :cookbook => @new_resource.cookbook)
+ template_finder.find(new_resource.source, :local => new_resource.local, :cookbook => new_resource.cookbook)
end
end
private
def file_for_provider
- context = TemplateContext.new(@new_resource.variables)
- context[:node] = @run_context.node
+ context = TemplateContext.new(new_resource.variables)
+ context[:node] = run_context.node
context[:template_finder] = template_finder
- context._extend_modules(@new_resource.helper_modules)
+
+ # helper variables
+ context[:cookbook_name] = new_resource.cookbook_name unless context.keys.include?(:coookbook_name)
+ context[:recipe_name] = new_resource.recipe_name unless context.keys.include?(:recipe_name)
+ context[:recipe_line_string] = new_resource.source_line unless context.keys.include?(:recipe_line_string)
+ context[:recipe_path] = new_resource.source_line_file unless context.keys.include?(:recipe_path)
+ context[:recipe_line] = new_resource.source_line_number unless context.keys.include?(:recipe_line)
+ context[:template_name] = new_resource.source unless context.keys.include?(:template_name)
+ context[:template_path] = template_location unless context.keys.include?(:template_path)
+
+ context._extend_modules(new_resource.helper_modules)
output = context.render_template(template_location)
- tempfile = Tempfile.open("chef-rendered-template")
+ tempfile = Chef::FileContentManagement::Tempfile.new(new_resource).tempfile
tempfile.binmode
tempfile.write(output)
tempfile.close
@@ -51,7 +61,7 @@ class Chef
def template_finder
@template_finder ||= begin
- TemplateFinder.new(run_context, @new_resource.cookbook_name, @run_context.node)
+ TemplateFinder.new(run_context, new_resource.cookbook_name, run_context.node)
end
end
end
diff --git a/lib/chef/provider/user.rb b/lib/chef/provider/user.rb
index f6ac72448e..b819401731 100644
--- a/lib/chef/provider/user.rb
+++ b/lib/chef/provider/user.rb
@@ -16,14 +16,13 @@
# limitations under the License.
#
-require 'chef/provider'
-require 'chef/mixin/command'
-require 'etc'
+require "chef/provider"
+require "chef/mixin/command"
+require "etc"
class Chef
class Provider
class User < Chef::Provider
-
include Chef::Mixin::Command
attr_accessor :user_exists, :locked
@@ -72,9 +71,9 @@ class Chef
end
@current_resource.comment(user_info.gecos)
- if @new_resource.password && @current_resource.password == 'x'
+ if @new_resource.password && @current_resource.password == "x"
begin
- require 'shadow'
+ require "shadow"
rescue LoadError
@shadow_lib_ok = false
else
@@ -90,7 +89,7 @@ class Chef
end
def define_resource_requirements
- requirements.assert(:all_actions) do |a|
+ requirements.assert(:create, :modify, :manage, :lock, :unlock) do |a|
a.assertion { @group_name_resolved }
a.failure_message Chef::Exceptions::User, "Couldn't lookup integer GID for group name #{@new_resource.gid}"
a.whyrun "group name #{@new_resource.gid} does not exist. This will cause group assignment to fail. Assuming this group will have been created previously."
@@ -208,7 +207,6 @@ class Chef
def unlock_user
raise NotImplementedError
end
-
end
end
end
diff --git a/lib/chef/provider/user/aix.rb b/lib/chef/provider/user/aix.rb
index af08ab4364..83bd900f79 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 0c0c85e18b..563d56dce7 100644
--- a/lib/chef/provider/user/dscl.rb
+++ b/lib/chef/provider/user/dscl.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'mixlib/shellout'
-require 'chef/provider/user'
-require 'openssl'
-require 'plist'
-require 'chef/util/path_helper'
+require "mixlib/shellout"
+require "chef/provider/user"
+require "openssl"
+require "plist"
+require "chef/util/path_helper"
class Chef
class Provider
@@ -44,6 +44,10 @@ class Chef
# This provider only supports Mac OSX versions 10.7 and above
class Dscl < Chef::Provider::User
+ attr_accessor :user_info
+ attr_accessor :authentication_authority
+ attr_accessor :password_shadow_conversion_algorithm
+
provides :user, os: "darwin"
def define_resource_requirements
@@ -56,19 +60,19 @@ class Chef
requirements.assert(:all_actions) do |a|
a.assertion { ::File.exists?("/usr/bin/dscl") }
- a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{@new_resource}!")
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{new_resource}!")
end
requirements.assert(:all_actions) do |a|
a.assertion { ::File.exists?("/usr/bin/plutil") }
- a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{@new_resource}!")
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{new_resource}!")
end
requirements.assert(:create, :modify, :manage) do |a|
a.assertion do
- if @new_resource.password && mac_osx_version_greater_than_10_7?
+ if new_resource.password && mac_osx_version_greater_than_10_7?
# SALTED-SHA512 password shadow hashes are not supported on 10.8 and above.
- !salted_sha512?(@new_resource.password)
+ !salted_sha512?(new_resource.password)
else
true
end
@@ -80,10 +84,10 @@ in 'password', with the associated 'salt' and 'iterations'.")
requirements.assert(:create, :modify, :manage) do |a|
a.assertion do
- if @new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(@new_resource.password)
+ if new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(new_resource.password)
# salt and iterations should be specified when
# SALTED-SHA512-PBKDF2 password shadow hash is given
- !@new_resource.salt.nil? && !@new_resource.iterations.nil?
+ !new_resource.salt.nil? && !new_resource.iterations.nil?
else
true
end
@@ -94,9 +98,9 @@ in 'password', with the associated 'salt' and 'iterations'.")
requirements.assert(:create, :modify, :manage) do |a|
a.assertion do
- if @new_resource.password && !mac_osx_version_greater_than_10_7?
+ if new_resource.password && !mac_osx_version_greater_than_10_7?
# On 10.7 SALTED-SHA512-PBKDF2 is not supported
- !salted_sha512_pbkdf2?(@new_resource.password)
+ !salted_sha512_pbkdf2?(new_resource.password)
else
true
end
@@ -109,21 +113,21 @@ user password using shadow hash.")
end
def load_current_resource
- @current_resource = Chef::Resource::User.new(@new_resource.username)
- @current_resource.username(@new_resource.username)
+ @current_resource = Chef::Resource::User.new(new_resource.username)
+ current_resource.username(new_resource.username)
@user_info = read_user_info
- if @user_info
- @current_resource.uid(dscl_get(@user_info, :uid))
- @current_resource.gid(dscl_get(@user_info, :gid))
- @current_resource.home(dscl_get(@user_info, :home))
- @current_resource.shell(dscl_get(@user_info, :shell))
- @current_resource.comment(dscl_get(@user_info, :comment))
- @authentication_authority = dscl_get(@user_info, :auth_authority)
-
- if @new_resource.password && dscl_get(@user_info, :password) == "********"
+ if user_info
+ current_resource.uid(dscl_get(user_info, :uid))
+ current_resource.gid(dscl_get(user_info, :gid))
+ current_resource.home(dscl_get(user_info, :home))
+ current_resource.shell(dscl_get(user_info, :shell))
+ current_resource.comment(dscl_get(user_info, :comment))
+ @authentication_authority = dscl_get(user_info, :auth_authority)
+
+ if new_resource.password && dscl_get(user_info, :password) == "********"
# A password is set. Let's get the password information from shadow file
- shadow_hash_binary = dscl_get(@user_info, :shadow_hash)
+ shadow_hash_binary = dscl_get(user_info, :shadow_hash)
# Calling shell_out directly since we want to give an input stream
shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string)
@@ -132,26 +136,26 @@ user password using shadow hash.")
if shadow_hash["SALTED-SHA512"]
# Convert the shadow value from Base64 encoding to hex before consuming them
@password_shadow_conversion_algorithm = "SALTED-SHA512"
- @current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first)
+ current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack("H*").first)
elsif shadow_hash["SALTED-SHA512-PBKDF2"]
@password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2"
# Convert the entropy from Base64 encoding to hex before consuming them
- @current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first)
- @current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
+ current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first)
+ current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
# Convert the salt from Base64 encoding to hex before consuming them
- @current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first)
+ current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack("H*").first)
else
raise(Chef::Exceptions::User,"Unknown shadow_hash format: #{shadow_hash.keys.join(' ')}")
end
end
- convert_group_name if @new_resource.gid
+ convert_group_name if new_resource.gid
else
@user_exists = false
- Chef::Log.debug("#{@new_resource} user does not exist")
+ Chef::Log.debug("#{new_resource} user does not exist")
end
- @current_resource
+ current_resource
end
#
@@ -190,15 +194,16 @@ user password using shadow hash.")
# Create a user using dscl
#
def dscl_create_user
- run_dscl("create /Users/#{@new_resource.username}")
+ run_dscl("create /Users/#{new_resource.username}")
end
#
# Saves the specified Chef user `comment` into RealName attribute
- # of Mac user.
+ # of Mac user. If `comment` is not specified, it takes `username` value.
#
def dscl_create_comment
- run_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
+ comment = new_resource.comment || new_resource.username
+ run_dscl("create /Users/#{new_resource.username} RealName '#{comment}'")
end
#
@@ -207,13 +212,14 @@ user password using shadow hash.")
# from 200 if `system` is set, 500 otherwise.
#
def dscl_set_uid
- @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
+ # XXX: mutates the new resource
+ new_resource.uid(get_free_uid) if (new_resource.uid.nil? || new_resource.uid == "")
- if uid_used?(@new_resource.uid)
- raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
+ if uid_used?(new_resource.uid)
+ raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{new_resource.uid} is already in use")
end
- run_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
+ run_dscl("create /Users/#{new_resource.username} UniqueID #{new_resource.uid}")
end
#
@@ -222,7 +228,7 @@ user password using shadow hash.")
#
def get_free_uid(search_limit=1000)
uid = nil
- base_uid = @new_resource.system ? 200 : 500
+ base_uid = new_resource.system ? 200 : 500
next_uid_guess = base_uid
users_uids = run_dscl("list /Users uid")
while(next_uid_guess < search_limit + base_uid)
@@ -248,7 +254,7 @@ user password using shadow hash.")
tmap
end
if uid_map[uid.to_s]
- unless uid_map[uid.to_s] == @new_resource.username.to_s
+ unless uid_map[uid.to_s] == new_resource.username.to_s
return true
end
end
@@ -257,18 +263,23 @@ user password using shadow hash.")
#
# Sets the group id for the user using dscl. Fails if a group doesn't
- # exist on the system with given group id.
+ # exist on the system with given group id. If `gid` is not specified, it
+ # sets a default Mac user group "staff", with id 20.
#
def dscl_set_gid
- unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
+ if new_resource.gid.nil?
+ # XXX: mutates the new resource
+ new_resource.gid(20)
+ elsif !new_resource.gid.to_s.match(/^\d+$/)
begin
- possible_gid = run_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
+ possible_gid = run_dscl("read /Groups/#{new_resource.gid} PrimaryGroupID").split(" ").last
rescue Chef::Exceptions::DsclCommandFailed => e
- raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
+ raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{new_resource.gid} when creating user #{new_resource.username}")
end
- @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
+ # XXX: mutates the new resource
+ new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
end
- run_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
+ run_dscl("create /Users/#{new_resource.username} PrimaryGroupID '#{new_resource.gid}'")
end
#
@@ -276,15 +287,15 @@ user password using shadow hash.")
# directory is managed (moved / created) for the user.
#
def dscl_set_home
- if @new_resource.home.nil? || @new_resource.home.empty?
- run_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory")
+ if new_resource.home.nil? || new_resource.home.empty?
+ run_dscl("delete /Users/#{new_resource.username} NFSHomeDirectory")
return
end
- if @new_resource.supports[:manage_home]
+ if new_resource.supports[:manage_home]
validate_home_dir_specification!
- if (@current_resource.home == @new_resource.home) && !new_home_exists?
+ if (current_resource.home == new_resource.home) && !new_home_exists?
ditto_home
elsif !current_home_exists? && !new_home_exists?
ditto_home
@@ -292,49 +303,49 @@ user password using shadow hash.")
move_home
end
end
- run_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
+ run_dscl("create /Users/#{new_resource.username} NFSHomeDirectory '#{new_resource.home}'")
end
def validate_home_dir_specification!
- unless @new_resource.home =~ /^\//
- raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
+ unless new_resource.home =~ /^\//
+ raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{new_resource.username}', home directory: '#{new_resource.home}'")
end
end
def current_home_exists?
- ::File.exist?("#{@current_resource.home}")
+ ::File.exist?("#{current_resource.home}")
end
def new_home_exists?
- ::File.exist?("#{@new_resource.home}")
+ ::File.exist?("#{new_resource.home}")
end
def ditto_home
skel = "/System/Library/User Template/English.lproj"
raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
- shell_out! "ditto '#{skel}' '#{@new_resource.home}'"
- ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
+ shell_out! "ditto '#{skel}' '#{new_resource.home}'"
+ ::FileUtils.chown_R(new_resource.username,new_resource.gid.to_s,new_resource.home)
end
def move_home
- Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
+ Chef::Log.debug("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}")
- src = @current_resource.home
- FileUtils.mkdir_p(@new_resource.home)
+ src = current_resource.home
+ FileUtils.mkdir_p(new_resource.home)
files = ::Dir.glob("#{Chef::Util::PathHelper.escape_glob(src)}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
- ::FileUtils.mv(files,@new_resource.home, :force => true)
+ ::FileUtils.mv(files,new_resource.home, :force => true)
::FileUtils.rmdir(src)
- ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
+ ::FileUtils.chown_R(new_resource.username,new_resource.gid.to_s,new_resource.home)
end
#
# Sets the shell for the user using dscl.
#
def dscl_set_shell
- if @new_resource.shell || ::File.exists?("#{@new_resource.shell}")
- run_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
+ if new_resource.shell || ::File.exists?("#{new_resource.shell}")
+ run_dscl("create /Users/#{new_resource.username} UserShell '#{new_resource.shell}'")
else
- run_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
+ run_dscl("create /Users/#{new_resource.username} UserShell '/usr/bin/false'")
end
end
@@ -345,17 +356,17 @@ user password using shadow hash.")
#
def set_password
# Return if there is no password to set
- return if @new_resource.password.nil?
+ return if new_resource.password.nil?
shadow_info = prepare_password_shadow_info
# Shadow info is saved as binary plist. Convert the info to binary plist.
shadow_info_binary = StringIO.new
command = Mixlib::ShellOut.new("plutil -convert binary1 -o - -",
- :input => shadow_info.to_plist, :live_stream => shadow_info_binary)
+ :input => shadow_info.to_plist, :live_stream => shadow_info_binary)
command.run_command
- if @user_info.nil?
+ if user_info.nil?
# User is just created. read_user_info() will read the fresh information
# for the user with a cache flush. However with experimentation we've seen
# that dscl cache is not immediately updated after the creation of the user
@@ -365,8 +376,8 @@ user password using shadow hash.")
end
# Replace the shadow info in user's plist
- dscl_set(@user_info, :shadow_hash, shadow_info_binary)
- save_user_info(@user_info)
+ dscl_set(user_info, :shadow_hash, shadow_info_binary)
+ save_user_info(user_info)
end
#
@@ -379,33 +390,33 @@ user password using shadow hash.")
iterations = nil
if mac_osx_version_10_7?
- hash_value = if salted_sha512?(@new_resource.password)
- @new_resource.password
- else
- # Create a random 4 byte salt
- salt = OpenSSL::Random.random_bytes(4)
- encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + @new_resource.password)
- hash_value = salt.unpack('H*').first + encoded_password
- end
+ hash_value = if salted_sha512?(new_resource.password)
+ new_resource.password
+ else
+ # Create a random 4 byte salt
+ salt = OpenSSL::Random.random_bytes(4)
+ encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + new_resource.password)
+ hash_value = salt.unpack("H*").first + encoded_password
+ end
shadow_info["SALTED-SHA512"] = StringIO.new
shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value)
shadow_info
else
- if salted_sha512_pbkdf2?(@new_resource.password)
- entropy = convert_to_binary(@new_resource.password)
- salt = convert_to_binary(@new_resource.salt)
- iterations = @new_resource.iterations
+ if salted_sha512_pbkdf2?(new_resource.password)
+ entropy = convert_to_binary(new_resource.password)
+ salt = convert_to_binary(new_resource.salt)
+ iterations = new_resource.iterations
else
salt = OpenSSL::Random.random_bytes(32)
- iterations = @new_resource.iterations # Use the default if not specified by the user
+ iterations = new_resource.iterations # Use the default if not specified by the user
entropy = OpenSSL::PKCS5::pbkdf2_hmac(
- @new_resource.password,
+ new_resource.password,
salt,
iterations,
128,
- OpenSSL::Digest::SHA512.new
+ OpenSSL::Digest::SHA512.new,
)
end
@@ -427,43 +438,43 @@ user password using shadow hash.")
# and deleting home directory if needed.
#
def remove_user
- if @new_resource.supports[:manage_home]
+ if new_resource.supports[:manage_home]
# Remove home directory
- FileUtils.rm_rf(@current_resource.home)
+ FileUtils.rm_rf(current_resource.home)
end
# Remove the user from its groups
run_dscl("list /Groups").each_line do |group|
if member_of_group?(group.chomp)
- run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{@new_resource.username}'")
+ run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{new_resource.username}'")
end
end
# Remove user account
- run_dscl("delete /Users/#{@new_resource.username}")
+ run_dscl("delete /Users/#{new_resource.username}")
end
#
# Locks the user.
#
def lock_user
- run_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
+ run_dscl("append /Users/#{new_resource.username} AuthenticationAuthority ';DisabledUser;'")
end
#
# Unlocks the user
#
def unlock_user
- auth_string = @authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip
- run_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
+ auth_string = authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip
+ run_dscl("create /Users/#{new_resource.username} AuthenticationAuthority '#{auth_string}'")
end
#
# Returns true if the user is locked, false otherwise.
#
def locked?
- if @authentication_authority
- !!(@authentication_authority =~ /DisabledUser/ )
+ if authentication_authority
+ !!(authentication_authority =~ /DisabledUser/ )
else
false
end
@@ -485,11 +496,11 @@ user password using shadow hash.")
# given attribute.
#
def diverged?(parameter)
- parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
+ parameter_updated?(parameter) && (not new_resource.send(parameter).nil?)
end
def parameter_updated?(parameter)
- not (@new_resource.send(parameter) == @current_resource.send(parameter))
+ not (new_resource.send(parameter) == current_resource.send(parameter))
end
#
@@ -500,11 +511,11 @@ user password using shadow hash.")
# type of the password specified.
#
def diverged_password?
- return false if @new_resource.password.nil?
+ return false if new_resource.password.nil?
# Dscl provider supports both plain text passwords and shadow hashes.
if mac_osx_version_10_7?
- if salted_sha512?(@new_resource.password)
+ if salted_sha512?(new_resource.password)
diverged?(:password)
else
!salted_sha512_password_match?
@@ -514,14 +525,14 @@ user password using shadow hash.")
# will be updated when the user logs in. So it's possible that we will have
# SALTED-SHA512 password in the current_resource. In that case we will force
# password to be updated.
- return true if salted_sha512?(@current_resource.password)
+ return true if salted_sha512?(current_resource.password)
# Some system users don't have salts; this can happen if the system is
# upgraded and the user hasn't logged in yet. In this case, we will force
# the password to be updated.
- return true if @current_resource.salt.nil?
+ return true if current_resource.salt.nil?
- if salted_sha512_pbkdf2?(@new_resource.password)
+ if salted_sha512_pbkdf2?(new_resource.password)
diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
else
!salted_sha512_pbkdf2_password_match?
@@ -543,7 +554,7 @@ user password using shadow hash.")
# GroupMembership: root admin etc
members = membership_info.split(" ")
members.shift # Get rid of GroupMembership: string
- members.include?(@new_resource.username)
+ members.include?(new_resource.username)
end
#
@@ -559,7 +570,7 @@ user password using shadow hash.")
:comment => "realname",
:password => "passwd",
:auth_authority => "authentication_authority",
- :shadow_hash => "ShadowHashData"
+ :shadow_hash => "ShadowHashData",
}.freeze
# Directory where the user plist files are stored for versions 10.7 and above
@@ -577,7 +588,7 @@ user password using shadow hash.")
shell_out("dscacheutil '-flushcache'")
begin
- user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}")
user_info = Plist::parse_xml(user_plist_info)
rescue Chef::Exceptions::PlistUtilCommandFailed
@@ -591,7 +602,7 @@ user password using shadow hash.")
# in DSCL_PROPERTY_MAP to the disk.
#
def save_user_info(user_info)
- user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
Plist::Emit.save_plist(user_info, user_plist_file)
run_plutil("convert binary1 #{user_plist_file}")
end
@@ -653,7 +664,7 @@ user password using shadow hash.")
result = shell_out("plutil -#{args.join(' ')}")
raise(Chef::Exceptions::PlistUtilCommandFailed,"plutil error: #{result.inspect}") unless result.exitstatus == 0
if result.stdout.encoding == Encoding::ASCII_8BIT
- result.stdout.encode("utf-8", "binary", :undef => :replace, :invalid => :replace, :replace => '?')
+ result.stdout.encode("utf-8", "binary", :undef => :replace, :invalid => :replace, :replace => "?")
else
result.stdout
end
@@ -664,7 +675,7 @@ user password using shadow hash.")
end
def convert_to_binary(string)
- string.unpack('a2'*(string.size/2)).collect { |i| i.hex.chr }.join
+ string.unpack("a2"*(string.size/2)).collect { |i| i.hex.chr }.join
end
def salted_sha512?(string)
@@ -673,9 +684,9 @@ user password using shadow hash.")
def salted_sha512_password_match?
# Salt is included in the first 4 bytes of shadow data
- salt = @current_resource.password.slice(0,8)
- shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + @new_resource.password)
- @current_resource.password == salt + shadow
+ salt = current_resource.password.slice(0,8)
+ shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + new_resource.password)
+ current_resource.password == salt + shadow
end
def salted_sha512_pbkdf2?(string)
@@ -683,15 +694,15 @@ user password using shadow hash.")
end
def salted_sha512_pbkdf2_password_match?
- salt = convert_to_binary(@current_resource.salt)
+ salt = convert_to_binary(current_resource.salt)
OpenSSL::PKCS5::pbkdf2_hmac(
- @new_resource.password,
+ new_resource.password,
salt,
- @current_resource.iterations,
+ current_resource.iterations,
128,
- OpenSSL::Digest::SHA512.new
- ).unpack('H*').first == @current_resource.password
+ OpenSSL::Digest::SHA512.new,
+ ).unpack("H*").first == current_resource.password
end
end
diff --git a/lib/chef/provider/user/pw.rb b/lib/chef/provider/user/pw.rb
index fe71e93561..60df7d55f8 100644
--- a/lib/chef/provider/user/pw.rb
+++ b/lib/chef/provider/user/pw.rb
@@ -16,12 +16,13 @@
# limitations under the License.
#
-require 'chef/provider/user'
+require "chef/provider/user"
class Chef
class Provider
class User
class Pw < Chef::Provider::User
+ provides :user, platform: %w{freebsd}
def load_current_resource
super
@@ -70,11 +71,11 @@ class Chef
opts = " #{@new_resource.username}"
field_list = {
- 'comment' => "-c",
- 'home' => "-d",
- 'gid' => "-g",
- 'uid' => "-u",
- 'shell' => "-s"
+ "comment" => "-c",
+ "home" => "-d",
+ "gid" => "-g",
+ "uid" => "-u",
+ "shell" => "-s",
}
field_list.sort{ |a,b| a[0] <=> b[0] }.each do |field, option|
field_symbol = field.to_sym
diff --git a/lib/chef/provider/user/solaris.rb b/lib/chef/provider/user/solaris.rb
index d480acaced..18f44523ff 100644
--- a/lib/chef/provider/user/solaris.rb
+++ b/lib/chef/provider/user/solaris.rb
@@ -1,7 +1,9 @@
#
# Author:: Stephen Nelson-Smith (<sns@opscode.com>)
# Author:: Jon Ramsey (<jonathon.ramsey@gmail.com>)
+# Author:: Dave Eddy (<dave@daveeddy.com>)
# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Copyright:: Copyright 2015, Dave Eddy
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,12 +18,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/provider/user/useradd'
+require "chef/provider/user/useradd"
class Chef
class Provider
class User
class Solaris < Chef::Provider::User::Useradd
+ provides :user, platform: %w{omnios solaris2}
UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
attr_writer :password_file
@@ -41,6 +44,32 @@ class Chef
super
end
+ def check_lock
+ shadow_line = shell_out!("getent", "shadow", new_resource.username).stdout.strip rescue nil
+
+ # if the command fails we return nil, this can happen if the user
+ # in question doesn't exist
+ return nil if shadow_line.nil?
+
+ # convert "dave:NP:16507::::::\n" to "NP"
+ fields = shadow_line.split(":")
+
+ # '*LK*...' and 'LK' are both considered locked,
+ # so look for LK at the beginning of the shadow entry
+ # optionally surrounded by '*'
+ @locked = !!fields[1].match(/^\*?LK\*?/)
+
+ @locked
+ end
+
+ def lock_user
+ shell_out!("passwd", "-l", new_resource.username)
+ end
+
+ def unlock_user
+ shell_out!("passwd", "-u", new_resource.username)
+ end
+
private
def manage_password
@@ -65,9 +94,10 @@ class Chef
buffer.close
# FIXME: mostly duplicates code with file provider deploying a file
- mode = ::File.stat(@password_file).mode & 07777
- uid = ::File.stat(@password_file).uid
- gid = ::File.stat(@password_file).gid
+ s = ::File.stat(@password_file)
+ mode = s.mode & 07777
+ uid = s.uid
+ gid = s.gid
FileUtils.chown uid, gid, buffer.path
FileUtils.chmod mode, buffer.path
diff --git a/lib/chef/provider/user/useradd.rb b/lib/chef/provider/user/useradd.rb
index cc770c0be2..da6606bc14 100644
--- a/lib/chef/provider/user/useradd.rb
+++ b/lib/chef/provider/user/useradd.rb
@@ -16,13 +16,14 @@
# limitations under the License.
#
-require 'pathname'
-require 'chef/provider/user'
+require "pathname"
+require "chef/provider/user"
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"]]
@@ -62,7 +63,7 @@ class Chef
raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if passwd_s.stdout.empty?
- status_line = passwd_s.stdout.split(' ')
+ status_line = passwd_s.stdout.split(" ")
case status_line[1]
when /^P/
@locked = false
@@ -74,11 +75,11 @@ class Chef
unless passwd_s.exitstatus == 0
raise_lock_error = false
- if ['redhat', 'centos'].include?(node[:platform])
- passwd_version_check = shell_out!('rpm -q passwd')
+ if ["redhat", "centos"].include?(node[:platform])
+ passwd_version_check = shell_out!("rpm -q passwd")
passwd_version = passwd_version_check.stdout.chomp
- unless passwd_version == 'passwd-0.73-1'
+ unless passwd_version == "passwd-0.73-1"
raise_lock_error = true
end
else
diff --git a/lib/chef/provider/user/windows.rb b/lib/chef/provider/user/windows.rb
index e282a11d45..0851c2272a 100644
--- a/lib/chef/provider/user/windows.rb
+++ b/lib/chef/provider/user/windows.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/provider/user'
-require 'chef/exceptions'
+require "chef/provider/user"
+require "chef/exceptions"
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require 'chef/util/windows/net_user'
+ require "chef/util/windows/net_user"
end
class Chef
@@ -35,6 +35,10 @@ class Chef
end
def load_current_resource
+ if @new_resource.gid
+ Chef::Log.warn("The 'gid' attribute is not implemented by the Windows platform. Please use the 'group' resource to assign a user to a group.")
+ end
+
@current_resource = Chef::Resource::User.new(@new_resource.name)
@current_resource.username(@new_resource.username)
user_info = nil
@@ -42,7 +46,6 @@ class Chef
user_info = @net_user.get_info
@current_resource.uid(user_info[:user_id])
- @current_resource.gid(user_info[:primary_group_id])
@current_resource.comment(user_info[:full_name])
@current_resource.home(user_info[:home_dir])
@current_resource.shell(user_info[:script_path])
@@ -65,7 +68,7 @@ class Chef
Chef::Log.debug("#{@new_resource} password has changed")
return true
end
- [ :uid, :gid, :comment, :home, :shell ].any? do |user_attrib|
+ [ :uid, :comment, :home, :shell ].any? do |user_attrib|
!@new_resource.send(user_attrib).nil? && @new_resource.send(user_attrib) != @current_resource.send(user_attrib)
end
end
@@ -98,12 +101,11 @@ class Chef
opts = {:name => @new_resource.username}
field_list = {
- 'comment' => 'full_name',
- 'home' => 'home_dir',
- 'gid' => 'primary_group_id',
- 'uid' => 'user_id',
- 'shell' => 'script_path',
- 'password' => 'password'
+ "comment" => "full_name",
+ "home" => "home_dir",
+ "uid" => "user_id",
+ "shell" => "script_path",
+ "password" => "password",
}
field_list.sort{ |a,b| a[0] <=> b[0] }.each do |field, option|
diff --git a/lib/chef/provider/windows_script.rb b/lib/chef/provider/windows_script.rb
index e600bb2837..342158fa06 100644
--- a/lib/chef/provider/windows_script.rb
+++ b/lib/chef/provider/windows_script.rb
@@ -16,18 +16,20 @@
# limitations under the License.
#
-require 'chef/provider/script'
-require 'chef/mixin/windows_architecture_helper'
+require "chef/provider/script"
+require "chef/mixin/windows_architecture_helper"
class Chef
class Provider
class WindowsScript < Chef::Provider::Script
+ attr_reader :is_forced_32bit
+
protected
include Chef::Mixin::WindowsArchitectureHelper
- def initialize( new_resource, run_context, script_extension='')
+ def initialize( new_resource, run_context, script_extension="")
super( new_resource, run_context )
@script_extension = script_extension
@@ -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..bdf7f58b37 100644
--- a/lib/chef/provider_resolver.rb
+++ b/lib/chef/provider_resolver.rb
@@ -16,10 +16,34 @@
# limitations under the License.
#
-require 'chef/exceptions'
-require 'chef/platform/provider_priority_map'
+require "chef/exceptions"
+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 a5f5386de3..b024be5b62 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,122 +16,125 @@
# limitations under the License.
#
-require 'chef/provider/batch'
-require 'chef/provider/breakpoint'
-require 'chef/provider/cookbook_file'
-require 'chef/provider/cron'
-require 'chef/provider/cron/solaris'
-require 'chef/provider/cron/aix'
-require 'chef/provider/deploy'
-require 'chef/provider/directory'
-require 'chef/provider/dsc_script'
-require 'chef/provider/dsc_resource'
-require 'chef/provider/env'
-require 'chef/provider/erl_call'
-require 'chef/provider/execute'
-require 'chef/provider/file'
-require 'chef/provider/git'
-require 'chef/provider/group'
-require 'chef/provider/http_request'
-require 'chef/provider/ifconfig'
-require 'chef/provider/link'
-require 'chef/provider/log'
-require 'chef/provider/ohai'
-require 'chef/provider/mdadm'
-require 'chef/provider/mount'
-require 'chef/provider/package'
-require 'chef/provider/powershell_script'
-require 'chef/provider/reboot'
-require 'chef/provider/remote_directory'
-require 'chef/provider/remote_file'
-require 'chef/provider/route'
-require 'chef/provider/ruby_block'
-require 'chef/provider/script'
-require 'chef/provider/service'
-require 'chef/provider/subversion'
-require 'chef/provider/template'
-require 'chef/provider/user'
-require 'chef/provider/whyrun_safe_ruby_block'
+require "chef/provider/batch"
+require "chef/provider/breakpoint"
+require "chef/provider/cookbook_file"
+require "chef/provider/cron"
+require "chef/provider/cron/solaris"
+require "chef/provider/cron/aix"
+require "chef/provider/deploy"
+require "chef/provider/directory"
+require "chef/provider/dsc_script"
+require "chef/provider/dsc_resource"
+require "chef/provider/env"
+require "chef/provider/erl_call"
+require "chef/provider/execute"
+require "chef/provider/file"
+require "chef/provider/git"
+require "chef/provider/group"
+require "chef/provider/http_request"
+require "chef/provider/ifconfig"
+require "chef/provider/link"
+require "chef/provider/log"
+require "chef/provider/ohai"
+require "chef/provider/mdadm"
+require "chef/provider/mount"
+require "chef/provider/package"
+require "chef/provider/powershell_script"
+require "chef/provider/osx_profile"
+require "chef/provider/reboot"
+require "chef/provider/remote_directory"
+require "chef/provider/remote_file"
+require "chef/provider/route"
+require "chef/provider/ruby_block"
+require "chef/provider/script"
+require "chef/provider/service"
+require "chef/provider/subversion"
+require "chef/provider/template"
+require "chef/provider/user"
+require "chef/provider/whyrun_safe_ruby_block"
-require 'chef/provider/env/windows'
+require "chef/provider/env/windows"
-require 'chef/provider/package/apt'
-require 'chef/provider/package/dpkg'
-require 'chef/provider/package/easy_install'
-require 'chef/provider/package/freebsd/port'
-require 'chef/provider/package/freebsd/pkg'
-require 'chef/provider/package/freebsd/pkgng'
-require 'chef/provider/package/homebrew'
-require 'chef/provider/package/ips'
-require 'chef/provider/package/macports'
-require 'chef/provider/package/openbsd'
-require 'chef/provider/package/pacman'
-require 'chef/provider/package/portage'
-require 'chef/provider/package/paludis'
-require 'chef/provider/package/rpm'
-require 'chef/provider/package/rubygems'
-require 'chef/provider/package/yum'
-require 'chef/provider/package/zypper'
-require 'chef/provider/package/solaris'
-require 'chef/provider/package/smartos'
-require 'chef/provider/package/aix'
+require "chef/provider/package/apt"
+require "chef/provider/package/chocolatey"
+require "chef/provider/package/dpkg"
+require "chef/provider/package/easy_install"
+require "chef/provider/package/freebsd/port"
+require "chef/provider/package/freebsd/pkg"
+require "chef/provider/package/freebsd/pkgng"
+require "chef/provider/package/homebrew"
+require "chef/provider/package/ips"
+require "chef/provider/package/macports"
+require "chef/provider/package/openbsd"
+require "chef/provider/package/pacman"
+require "chef/provider/package/portage"
+require "chef/provider/package/paludis"
+require "chef/provider/package/rpm"
+require "chef/provider/package/rubygems"
+require "chef/provider/package/yum"
+require "chef/provider/package/zypper"
+require "chef/provider/package/solaris"
+require "chef/provider/package/smartos"
+require "chef/provider/package/aix"
-require 'chef/provider/service/arch'
-require 'chef/provider/service/freebsd'
-require 'chef/provider/service/gentoo'
-require 'chef/provider/service/init'
-require 'chef/provider/service/invokercd'
-require 'chef/provider/service/debian'
-require 'chef/provider/service/openbsd'
-require 'chef/provider/service/redhat'
-require 'chef/provider/service/insserv'
-require 'chef/provider/service/simple'
-require 'chef/provider/service/systemd'
-require 'chef/provider/service/upstart'
-require 'chef/provider/service/windows'
-require 'chef/provider/service/solaris'
-require 'chef/provider/service/macosx'
-require 'chef/provider/service/aixinit'
-require 'chef/provider/service/aix'
+require "chef/provider/service/arch"
+require "chef/provider/service/freebsd"
+require "chef/provider/service/gentoo"
+require "chef/provider/service/init"
+require "chef/provider/service/invokercd"
+require "chef/provider/service/debian"
+require "chef/provider/service/openbsd"
+require "chef/provider/service/redhat"
+require "chef/provider/service/insserv"
+require "chef/provider/service/simple"
+require "chef/provider/service/systemd"
+require "chef/provider/service/upstart"
+require "chef/provider/service/windows"
+require "chef/provider/service/solaris"
+require "chef/provider/service/macosx"
+require "chef/provider/service/aixinit"
+require "chef/provider/service/aix"
-require 'chef/provider/user/dscl'
-require 'chef/provider/user/pw'
-require 'chef/provider/user/useradd'
-require 'chef/provider/user/windows'
-require 'chef/provider/user/solaris'
-require 'chef/provider/user/aix'
+require "chef/provider/user/dscl"
+require "chef/provider/user/pw"
+require "chef/provider/user/useradd"
+require "chef/provider/user/windows"
+require "chef/provider/user/solaris"
+require "chef/provider/user/aix"
-require 'chef/provider/group/aix'
-require 'chef/provider/group/dscl'
-require 'chef/provider/group/gpasswd'
-require 'chef/provider/group/groupadd'
-require 'chef/provider/group/groupmod'
-require 'chef/provider/group/pw'
-require 'chef/provider/group/suse'
-require 'chef/provider/group/usermod'
-require 'chef/provider/group/windows'
+require "chef/provider/group/aix"
+require "chef/provider/group/dscl"
+require "chef/provider/group/gpasswd"
+require "chef/provider/group/groupadd"
+require "chef/provider/group/groupmod"
+require "chef/provider/group/pw"
+require "chef/provider/group/suse"
+require "chef/provider/group/usermod"
+require "chef/provider/group/windows"
-require 'chef/provider/mount/mount'
-require 'chef/provider/mount/aix'
-require 'chef/provider/mount/solaris'
-require 'chef/provider/mount/windows'
+require "chef/provider/mount/mount"
+require "chef/provider/mount/aix"
+require "chef/provider/mount/solaris"
+require "chef/provider/mount/windows"
-require 'chef/provider/deploy/revision'
-require 'chef/provider/deploy/timestamped'
+require "chef/provider/deploy/revision"
+require "chef/provider/deploy/timestamped"
-require 'chef/provider/remote_file/ftp'
-require 'chef/provider/remote_file/http'
-require 'chef/provider/remote_file/local_file'
-require 'chef/provider/remote_file/fetcher'
+require "chef/provider/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"
-require 'chef/provider/registry_key'
+require "chef/provider/registry_key"
-require 'chef/provider/file/content'
-require 'chef/provider/remote_file/content'
-require 'chef/provider/cookbook_file/content'
-require 'chef/provider/template/content'
+require "chef/provider/file/content"
+require "chef/provider/remote_file/content"
+require "chef/provider/cookbook_file/content"
+require "chef/provider/template/content"
-require 'chef/provider/ifconfig/redhat'
-require 'chef/provider/ifconfig/debian'
-require 'chef/provider/ifconfig/aix'
+require "chef/provider/ifconfig/redhat"
+require "chef/provider/ifconfig/debian"
+require "chef/provider/ifconfig/aix"
diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb
index b4d37c2d61..f2717d40bc 100644
--- a/lib/chef/recipe.rb
+++ b/lib/chef/recipe.rb
@@ -18,32 +18,25 @@
#
-require 'chef/dsl/recipe'
-require 'chef/dsl/data_query'
-require 'chef/dsl/platform_introspection'
-require 'chef/dsl/include_recipe'
-require 'chef/dsl/registry_helper'
-require 'chef/dsl/reboot_pending'
-require 'chef/dsl/audit'
-require 'chef/dsl/powershell'
+require "chef/dsl/recipe"
+require "chef/dsl/data_query"
+require "chef/dsl/platform_introspection"
+require "chef/dsl/include_recipe"
+require "chef/dsl/registry_helper"
+require "chef/dsl/reboot_pending"
+require "chef/dsl/audit"
+require "chef/dsl/powershell"
-require 'chef/mixin/from_file'
+require "chef/mixin/from_file"
-require 'chef/mixin/deprecation'
+require "chef/mixin/deprecation"
class Chef
# == Chef::Recipe
# A Recipe object is the context in which Chef recipes are evaluated.
class Recipe
- include Chef::DSL::DataQuery
- include Chef::DSL::PlatformIntrospection
- include Chef::DSL::IncludeRecipe
- include Chef::DSL::Recipe
- include Chef::DSL::RegistryHelper
- include Chef::DSL::RebootPending
- include Chef::DSL::Audit
- include Chef::DSL::Powershell
+ include Chef::DSL::Recipe::FullDSL
include Chef::Mixin::FromFile
include Chef::Mixin::Deprecation
@@ -104,10 +97,8 @@ class Chef
# true<TrueClass>:: If all the parameters are present
# false<FalseClass>:: If any of the parameters are missing
def tagged?(*tags)
- return false if run_context.node[:tags].nil?
-
tags.each do |tag|
- return false unless run_context.node[:tags].include?(tag)
+ return false unless run_context.node.tags.include?(tag)
end
true
end
@@ -118,10 +109,10 @@ class Chef
# tags<Array>:: A list of tags
#
# === Returns
- # tags<Array>:: The current list of run_context.node[:tags]
+ # tags<Array>:: The current list of run_context.node.tags
def untag(*tags)
tags.each do |tag|
- run_context.node.normal[:tags].delete(tag)
+ run_context.node.tags.delete(tag)
end
end
diff --git a/lib/chef/request_id.rb b/lib/chef/request_id.rb
index 2c7af01879..9037f9f182 100644
--- a/lib/chef/request_id.rb
+++ b/lib/chef/request_id.rb
@@ -15,8 +15,8 @@
# limitations under the License.
#
-require 'securerandom'
-require 'singleton'
+require "securerandom"
+require "singleton"
class Chef
class RequestID
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb
index ea220b6c70..9661f22de3 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,32 @@
# limitations under the License.
#
-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/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_collection'
-require 'chef/node_map'
-require 'chef/node'
-require 'chef/platform'
-require 'chef/resource/resource_notification'
-
-require 'chef/mixin/deprecation'
-require 'chef/mixin/descendants_tracker'
+require "chef/exceptions"
+require "chef/dsl/platform_introspection"
+require "chef/dsl/data_query"
+require "chef/dsl/registry_helper"
+require "chef/dsl/reboot_pending"
+require "chef/dsl/resources"
+require "chef/mixin/convert_to_class_name"
+require "chef/guard_interpreter/resource_guard_interpreter"
+require "chef/resource/conditional"
+require "chef/resource/conditional_action_not_nothing"
+require "chef/resource/action_class"
+require "chef/resource_collection"
+require "chef/node_map"
+require "chef/node"
+require "chef/platform"
+require "chef/resource/resource_notification"
+require "chef/provider_resolver"
+require "chef/resource_resolver"
+require "chef/provider"
+require "set"
+
+require "chef/mixin/deprecation"
+require "chef/mixin/properties"
+require "chef/mixin/provides"
+require "chef/mixin/shell_out"
+require "chef/mixin/powershell_out"
class Chef
class Resource
@@ -46,6 +56,39 @@ 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
+
+ # Bring in `property` and `property_type`
+ include Chef::Mixin::Properties
+
+ #
+ # The name of this particular resource.
+ #
+ # This special resource attribute is set automatically from the declaration
+ # of the resource, e.g.
+ #
+ # execute 'Vitruvius' do
+ # command 'ls'
+ # end
+ #
+ # Will set the name to "Vitruvius".
+ #
+ # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
+ #
+ # This is also used for resource notifications and subscribes in the same manner.
+ #
+ # This will coerce any object into a string via #to_s. Arrays are a special case
+ # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
+ # awkward `package[["foo", "bar"]]` that #to_s would produce.
+ #
+ # @param name [Object] The name to set, typically a String or Array
+ # @return [String] The name of this Resource.
+ #
+ property :name, String, coerce: proc { |v| v.is_a?(Array) ? v.join(", ") : v.to_s }, desired_state: false
#
# The node the current Chef run is using.
@@ -78,7 +121,6 @@ class Chef
run_context.resource_collection.find(*args)
end
-
#
# Resource User Interface (for users)
#
@@ -91,14 +133,14 @@ class Chef
# @param run_context The context of the Chef run. Corresponds to #run_context.
#
def initialize(name, run_context=nil)
- name(name)
+ name(name) unless name.nil?
@run_context = run_context
@noop = nil
@before = nil
@params = Hash.new
@provider = nil
- @allowed_actions = [ :nothing ]
- @action = :nothing
+ @allowed_actions = self.class.allowed_actions.to_a
+ @action = self.class.default_action
@updated = false
@updated_by_last_action = false
@supports = {}
@@ -120,40 +162,6 @@ class Chef
end
#
- # The name of this particular resource.
- #
- # This special resource attribute is set automatically from the declaration
- # of the resource, e.g.
- #
- # execute 'Vitruvius' do
- # command 'ls'
- # end
- #
- # Will set the name to "Vitruvius".
- #
- # This is also used in to_s to show the resource name, e.g. `execute[Vitruvius]`.
- #
- # This is also used for resource notifications and subscribes in the same manner.
- #
- # This will coerce any object into a string via #to_s. Arrays are a special case
- # so that `package ["foo", "bar"]` becomes package[foo, bar] instead of the more
- # awkward `package[["foo", "bar"]]` that #to_s would produce.
- #
- # @param name [Object] The name to set, typically a String or Array
- # @return [String] The name of this Resource.
- #
- def name(name=nil)
- if !name.nil?
- if name.is_a?(Array)
- @name = name.join(', ')
- else
- @name = name.to_s
- end
- end
- @name
- end
-
- #
# The action or actions that will be taken when this resource is run.
#
# @param arg [Array[Symbol], Symbol] A list of actions (e.g. `:create`)
@@ -161,26 +169,27 @@ class Chef
#
def action(arg=nil)
if arg
- action_list = arg.kind_of?(Array) ? arg : [ arg ]
- action_list = action_list.collect { |a| a.to_sym }
- action_list.each do |action|
+ arg = Array(arg).map(&:to_sym)
+ arg.each do |action|
validate(
{ action: action },
- { action: { kind_of: Symbol, equal_to: @allowed_actions } }
+ { action: { kind_of: Symbol, equal_to: allowed_actions } },
)
end
- @action = action_list
+ @action = arg
else
@action
end
end
+ # Alias for normal assigment syntax.
+ alias_method :action=, :action
+
#
# Sets up a notification that will run a particular action on another resource
# if and when *this* resource is updated by an action.
#
- # If the action does nothing--does not update this resource, the
- # notification never triggers.)
+ # If the action does not update this resource, the notification never triggers.
#
# Only one resource may be specified per notification.
#
@@ -200,6 +209,8 @@ class Chef
# actions have been run. This is the default.
# - `immediate`, `immediately`: Will run the action on the other resource
# immediately (before any other action is run).
+ # - `before`: Will run the action on the other resource
+ # immediately *before* the action is actually run.
#
# @example Resource by string
# file '/foo.txt' do
@@ -238,13 +249,15 @@ class Chef
resources.each do |resource|
case timing.to_s
- when 'delayed'
+ when "delayed"
notifies_delayed(action, resource)
- when 'immediate', 'immediately'
+ when "immediate", "immediately"
notifies_immediately(action, resource)
+ when "before"
+ notifies_before(action, resource)
else
raise ArgumentError, "invalid timing: #{timing} for notifies(#{action}, #{resources.inspect}, #{timing}) resource #{self} "\
- "Valid timings are: :delayed, :immediate, :immediately"
+ "Valid timings are: :delayed, :immediate, :immediately, :before"
end
end
@@ -268,6 +281,8 @@ class Chef
# actions have been run. This is the default.
# - `immediate`, `immediately`: The action will run immediately following
# the other resource being updated.
+ # - `before`: The action will run immediately before the
+ # other resource is updated.
#
# @example Resources by string
# file '/foo.txt' do
@@ -464,27 +479,49 @@ class Chef
#
# Get the value of the state attributes in this resource as a hash.
#
+ # Does not include properties that are not set (unless they are identity
+ # properties).
+ #
# @return [Hash{Symbol => Object}] A Hash of attribute => value for the
# Resource class's `state_attrs`.
- def state
- self.class.state_attrs.inject({}) do |state_attrs, attr_name|
- state_attrs[attr_name] = send(attr_name)
- state_attrs
+ #
+ def state_for_resource_reporter
+ state = {}
+ state_properties = self.class.state_properties
+ state_properties.each do |property|
+ if property.identity? || property.is_set?(self)
+ state[property.name] = send(property.name)
+ end
end
+ state
end
#
- # The value of the identity attribute, if declared. Falls back to #name if
- # no identity attribute is declared.
+ # Since there are collisions with LWRP parameters named 'state' this
+ # method is not used by the resource_reporter and is most likely unused.
+ # It certainly cannot be relied upon and cannot be fixed.
#
- # @return The value of the identity attribute.
+ # @deprecated
+ #
+ alias_method :state, :state_for_resource_reporter
+
+ #
+ # The value of the identity of this resource.
+ #
+ # - If there are no identity properties on the resource, `name` is returned.
+ # - If there is exactly one identity property on the resource, it is returned.
+ # - If there are more than one, they are returned in a hash.
+ #
+ # @return [Object,Hash<Symbol,Object>] The identity of this resource.
#
def identity
- if identity_attr = self.class.identity_attr
- send(identity_attr)
- else
- name
+ result = {}
+ identity_properties = self.class.identity_properties
+ identity_properties.each do |property|
+ result[property.name] = send(property.name)
end
+ return result.values.first if identity_properties.size == 1
+ result
end
#
@@ -506,9 +543,7 @@ class Chef
#
# Equivalent to #ignore_failure.
#
- def epic_fail(arg=nil)
- ignore_failure(arg)
- end
+ alias :epic_fail :ignore_failure
#
# Make this resource into an exact (shallow) copy of the other resource.
@@ -573,12 +608,26 @@ class Chef
events.resource_failed(self, action, e)
raise customize_exception(e)
end
- ensure
- @elapsed_time = Time.now - start_time
- # Reporting endpoint doesn't accept a negative resource duration so set it to 0.
- # A negative value can occur when a resource changes the system time backwards
- @elapsed_time = 0 if @elapsed_time < 0
- events.resource_completed(self)
+ end
+ ensure
+ @elapsed_time = Time.now - start_time
+ # Reporting endpoint doesn't accept a negative resource duration so set it to 0.
+ # A negative value can occur when a resource changes the system time backwards
+ @elapsed_time = 0 if @elapsed_time < 0
+ events.resource_completed(self)
+ end
+
+ #
+ # If we are currently initializing the resource, this will be true.
+ #
+ # Do NOT use this. It may be removed. It is for internal purposes only.
+ # @api private
+ attr_reader :resource_initializing
+ def resource_initializing=(value)
+ if value
+ @resource_initializing = true
+ else
+ remove_instance_variable(:@resource_initializing)
end
end
@@ -587,14 +636,14 @@ class Chef
#
def to_s
- "#{@resource_name}[#{@name}]"
+ "#{resource_name}[#{name}]"
end
def to_text
return "suppressed sensitive resource output" if sensitive
ivars = instance_variables.map { |ivar| ivar.to_sym } - HIDDEN_IVARS
text = "# Declared in #{@source_line}\n\n"
- text << self.class.dsl_name + "(\"#{name}\") do\n"
+ text << "#{resource_name}(\"#{name}\") do\n"
ivars.each do |ivar|
if (value = instance_variable_get(ivar)) && !(value.respond_to?(:empty?) && value.empty?)
value_string = value.respond_to?(:to_text) ? value.to_text : value.inspect
@@ -609,7 +658,7 @@ class Chef
def inspect
ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
- ivars.inject("<#{to_s}") do |str, ivar|
+ ivars.inject("<#{self}") do |str, ivar|
str << " #{ivar}: #{instance_variable_get(ivar).inspect}"
end << ">"
end
@@ -621,11 +670,11 @@ class Chef
safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
instance_vars = Hash.new
safe_ivars.each do |iv|
- instance_vars[iv.to_s.sub(/^@/, '')] = instance_variable_get(iv)
+ instance_vars[iv.to_s.sub(/^@/, "")] = instance_variable_get(iv)
end
{
- 'json_class' => self.class.name,
- 'instance_vars' => instance_vars
+ "json_class" => self.class.name,
+ "instance_vars" => instance_vars,
}
end
@@ -636,13 +685,18 @@ class Chef
end
def to_hash
+ # Grab all current state, then any other ivars (backcompat)
+ result = {}
+ self.class.state_properties.each do |p|
+ result[p.name] = p.get(self)
+ end
safe_ivars = instance_variables.map { |ivar| ivar.to_sym } - FORBIDDEN_IVARS
- instance_vars = Hash.new
safe_ivars.each do |iv|
- key = iv.to_s.sub(/^@/,'').to_sym
- instance_vars[key] = instance_variable_get(iv)
+ key = iv.to_s.sub(/^@/,"").to_sym
+ next if result.has_key?(key)
+ result[key] = instance_variable_get(iv)
end
- instance_vars
+ result
end
def self.json_create(o)
@@ -657,72 +711,112 @@ class Chef
# Resource Definition Interface (for resource developers)
#
- include Chef::Mixin::ParamsValidate
include Chef::Mixin::Deprecation
#
# The provider class for this resource.
#
+ # If `action :x do ... end` has been declared on this resource or its
+ # superclasses, this will return the `action_class`.
+ #
# If this is not set, `provider_for_action` will dynamically determine the
# provider.
#
# @param arg [String, Symbol, Class] Sets the provider class for this resource.
# If passed a String or Symbol, e.g. `:file` or `"file"`, looks up the
# provider based on the name.
+ #
# @return The provider class for this resource.
#
+ # @see Chef::Resource.action_class
+ #
def provider(arg=nil)
klass = if arg.kind_of?(String) || arg.kind_of?(Symbol)
- lookup_provider_constant(arg)
- else
- arg
- end
- set_or_return(:provider, klass, kind_of: [ Class ])
+ lookup_provider_constant(arg)
+ else
+ arg
+ end
+ set_or_return(:provider, klass, kind_of: [ Class ]) ||
+ self.class.action_class
end
def provider=(arg)
provider(arg)
end
- # Set or return the list of "state attributes" implemented by the Resource
- # subclass. State attributes are attributes that describe the desired state
- # of the system, such as file permissions or ownership. In general, state
- # attributes are attributes that could be populated by examining the state
- # of the system (e.g., File.stat can tell you the permissions on an
- # existing file). Contrarily, attributes that are not "state attributes"
- # usually modify the way Chef itself behaves, for example by providing
- # additional options for a package manager to use when installing a
- # package.
- #
- # This list is used by the Chef client auditing system to extract
- # information from resources to describe changes made to the system.
- def self.state_attrs(*attr_names)
- @state_attrs ||= []
- @state_attrs = attr_names unless attr_names.empty?
-
- # Return *all* state_attrs that this class has, including inherited ones
- if superclass.respond_to?(:state_attrs)
- superclass.state_attrs + @state_attrs
- else
- @state_attrs
- end
+ #
+ # Set or return the list of "state properties" implemented by the Resource
+ # subclass.
+ #
+ # Equivalent to calling #state_properties and getting `state_properties.keys`.
+ #
+ # @deprecated Use state_properties.keys instead. Note that when you declare
+ # properties with `property`: properties are added to state_properties by
+ # default, and can be turned off with `desired_state: false`
+ #
+ # ```ruby
+ # property :x # part of desired state
+ # property :y, desired_state: false # not part of desired state
+ # ```
+ #
+ # @param names [Array<Symbol>] A list of property names to set as desired
+ # state.
+ #
+ # @return [Array<Symbol>] All property names with desired state.
+ #
+ def self.state_attrs(*names)
+ state_properties(*names).map { |property| property.name }
end
- # Set or return the "identity attribute" for this resource class. This is
- # generally going to be the "name attribute" for this resource. In other
- # words, the resource type plus this attribute uniquely identify a given
- # bit of state that chef manages. For a File resource, this would be the
- # path, for a package resource, it will be the package name. This will show
- # up in chef-client's audit records as a searchable field.
- def self.identity_attr(attr_name=nil)
- @identity_attr ||= nil
- @identity_attr = attr_name if attr_name
-
- # If this class doesn't have an identity attr, we'll defer to the superclass:
- if @identity_attr || !superclass.respond_to?(:identity_attr)
- @identity_attr
- else
- superclass.identity_attr
+ #
+ # Set the identity of this resource to a particular property.
+ #
+ # This drives #identity, which returns data that uniquely refers to a given
+ # resource on the given node (in such a way that it can be correlated
+ # across Chef runs).
+ #
+ # This method is unnecessary when declaring properties with `property`;
+ # properties can be added to identity during declaration with
+ # `identity: true`.
+ #
+ # ```ruby
+ # property :x, identity: true # part of identity
+ # property :y # not part of identity
+ # ```
+ #
+ # @param name [Symbol] A list of property names to set as the identity.
+ #
+ # @return [Symbol] The identity property if there is only one; or `nil` if
+ # there are more than one.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_property(name=nil)
+ result = identity_properties(*Array(name))
+ if result.size > 1
+ raise Chef::Exceptions::MultipleIdentityError, "identity_property cannot be called on an object with more than one identity property (#{result.map { |r| r.name }.join(", ")})."
end
+ result.first
+ end
+
+ #
+ # Set a property as the "identity attribute" for this resource.
+ #
+ # Identical to calling #identity_property.first.key.
+ #
+ # @param name [Symbol] The name of the property to set.
+ #
+ # @return [Symbol]
+ #
+ # @deprecated `identity_property` should be used instead.
+ #
+ # @raise [ArgumentError] If no arguments are passed and the resource has
+ # more than one identity property.
+ #
+ def self.identity_attr(name=nil)
+ property = identity_property(name)
+ return nil if !property
+ property.name
end
#
@@ -748,6 +842,12 @@ class Chef
# have.
#
attr_accessor :allowed_actions
+ def allowed_actions(value=NOT_PASSED)
+ if value != NOT_PASSED
+ self.allowed_actions = value
+ end
+ @allowed_actions
+ end
#
# Whether or not this resource was updated during an action. If multiple
@@ -806,19 +906,15 @@ class Chef
end
#
- # The DSL name of this resource (e.g. `package` or `yum_package`)
+ # The display name of this resource type, for printing purposes.
#
- # @return [String] The DSL name of this resource.
- def self.dsl_name
- convert_to_snake_case(name, 'Chef::Resource')
- end
-
- #
- # The name of this resource (e.g. `file`)
+ # Will be used to print out the resource in messages, e.g. resource_name[name]
#
- # @return [String] The name of this resource.
+ # @return [Symbol] The name of this resource type (e.g. `:execute`).
#
- attr_reader :resource_name
+ def resource_name
+ @resource_name || self.class.resource_name
+ end
#
# Sets a list of capabilities of the real resource. For example, `:remount`
@@ -851,6 +947,73 @@ class Chef
end
#
+ # The DSL name of this resource (e.g. `package` or `yum_package`)
+ #
+ # @return [String] The DSL name of this resource.
+ #
+ # @deprecated Use resource_name instead.
+ #
+ def self.dsl_name
+ Chef.log_deprecation "Resource.dsl_name is deprecated and will be removed in Chef 13. Use resource_name instead."
+ if name
+ name = self.name.split("::")[-1]
+ convert_to_snake_case(name)
+ end
+ end
+
+ #
+ # The display name of this resource type, for printing purposes.
+ #
+ # This also automatically calls "provides" to provide DSL with the given
+ # name.
+ #
+ # resource_name defaults to your class name.
+ #
+ # Call `resource_name nil` to remove the resource name (and any
+ # corresponding DSL).
+ #
+ # @param value [Symbol] The desired name of this resource type (e.g.
+ # `execute`), or `nil` if this class is abstract and has no resource_name.
+ #
+ # @return [Symbol] The name of this resource type (e.g. `:execute`).
+ #
+ def self.resource_name(name=NOT_PASSED)
+ # Setter
+ if name != NOT_PASSED
+ remove_canonical_dsl
+
+ # Set the resource_name and call provides
+ if name
+ name = name.to_sym
+ # If our class is not already providing this name, provide it.
+ if !Chef::ResourceResolver.includes_handler?(name, self)
+ provides name, canonical: true
+ end
+ @resource_name = name
+ else
+ @resource_name = nil
+ end
+ end
+ @resource_name
+ end
+ def self.resource_name=(name)
+ resource_name(name)
+ end
+
+ #
+ # Use the class name as the resource name.
+ #
+ # Munges the last part of the class name from camel case to snake case,
+ # and sets the resource_name to that:
+ #
+ # A::B::BlahDBlah -> blah_d_blah
+ #
+ def self.use_automatic_resource_name
+ automatic_name = convert_to_snake_case(self.name.split("::")[-1])
+ resource_name automatic_name
+ end
+
+ #
# The module where Chef should look for providers for this resource.
# The provider for `MyResource` will be looked up using
# `provider_base::MyResource`. Defaults to `Chef::Provider`.
@@ -864,11 +1027,191 @@ class Chef
# # ...other stuff
# end
#
+ # @deprecated Use `provides` on the provider, or `provider` on the resource, instead.
+ #
def self.provider_base(arg=nil)
- @provider_base ||= arg
- @provider_base ||= Chef::Provider
+ if arg
+ Chef.log_deprecation("Resource.provider_base is deprecated and will be removed in Chef 13. Use provides on the provider, or provider on the resource, instead.")
+ end
+ @provider_base ||= arg || Chef::Provider
+ end
+
+ #
+ # The list of allowed actions for the resource.
+ #
+ # @param actions [Array<Symbol>] The list of actions to add to allowed_actions.
+ #
+ # @return [Array<Symbol>] The list of actions, as symbols.
+ #
+ def self.allowed_actions(*actions)
+ @allowed_actions ||=
+ if superclass.respond_to?(:allowed_actions)
+ superclass.allowed_actions.dup
+ else
+ [ :nothing ]
+ end
+ @allowed_actions |= actions.flatten
+ end
+ def self.allowed_actions=(value)
+ @allowed_actions = value.uniq
+ end
+
+ #
+ # The action that will be run if no other action is specified.
+ #
+ # Setting default_action will automatially add the action to
+ # allowed_actions, if it isn't already there.
+ #
+ # Defaults to [:nothing].
+ #
+ # @param action_name [Symbol,Array<Symbol>] The default action (or series
+ # of actions) to use.
+ #
+ # @return [Array<Symbol>] The default actions for the resource.
+ #
+ def self.default_action(action_name=NOT_PASSED)
+ unless action_name.equal?(NOT_PASSED)
+ @default_action = Array(action_name).map(&:to_sym)
+ self.allowed_actions |= @default_action
+ end
+
+ if @default_action
+ @default_action
+ elsif superclass.respond_to?(:default_action)
+ superclass.default_action
+ else
+ [:nothing]
+ end
+ end
+ def self.default_action=(action_name)
+ default_action action_name
+ end
+
+ #
+ # Define an action on this resource.
+ #
+ # The action is defined as a *recipe* block that will be compiled and then
+ # converged when the action is taken (when Resource is converged). The recipe
+ # has access to the resource's attributes and methods, as well as the Chef
+ # recipe DSL.
+ #
+ # Resources in the action recipe may notify and subscribe to other resources
+ # within the action recipe, but cannot notify or subscribe to resources
+ # in the main Chef run.
+ #
+ # Resource actions are *inheritable*: if resource A defines `action :create`
+ # and B is a subclass of A, B gets all of A's actions. Additionally,
+ # resource B can define `action :create` and call `super()` to invoke A's
+ # action code.
+ #
+ # The first action defined (besides `:nothing`) will become the default
+ # action for the resource.
+ #
+ # @param name [Symbol] The action name to define.
+ # @param recipe_block The recipe to run when the action is taken. This block
+ # takes no parameters, and will be evaluated in a new context containing:
+ #
+ # - The resource's public and protected methods (including attributes)
+ # - The Chef Recipe DSL (file, etc.)
+ # - super() referring to the parent version of the action (if any)
+ #
+ # @return The Action class implementing the action
+ #
+ def self.action(action, &recipe_block)
+ action = action.to_sym
+ declare_action_class
+ action_class.action(action, &recipe_block)
+ self.allowed_actions += [ action ]
+ default_action action if Array(default_action) == [:nothing]
+ end
+
+ #
+ # Define a method to load up this resource's properties with the current
+ # actual values.
+ #
+ # @param load_block The block to load. Will be run in the context of a newly
+ # created resource with its identity values filled in.
+ #
+ def self.load_current_value(&load_block)
+ define_method(:load_current_value!, &load_block)
+ end
+
+ #
+ # Call this in `load_current_value` to indicate that the value does not
+ # exist and that `current_resource` should therefore be `nil`.
+ #
+ # @raise Chef::Exceptions::CurrentValueDoesNotExist
+ #
+ def current_value_does_not_exist!
+ raise Chef::Exceptions::CurrentValueDoesNotExist
+ end
+
+ #
+ # Get the current actual value of this resource.
+ #
+ # This does not cache--a new value will be returned each time.
+ #
+ # @return A new copy of the resource, with values filled in from the actual
+ # current value.
+ #
+ def current_value
+ provider = provider_for_action(Array(action).first)
+ if provider.whyrun_mode? && !provider.whyrun_supported?
+ raise "Cannot retrieve #{self.class.current_resource} in why-run mode: #{provider} does not support why-run"
+ end
+ provider.load_current_resource
+ provider.current_resource
+ end
+
+ #
+ # The action class is an automatic `Provider` created to handle
+ # actions declared by `action :x do ... end`.
+ #
+ # This class will be returned by `resource.provider` if `resource.provider`
+ # is not set. `provider_for_action` will also use this instead of calling
+ # out to `Chef::ProviderResolver`.
+ #
+ # If the user has not declared actions on this class or its superclasses
+ # using `action :x do ... end`, then there is no need for this class and
+ # `action_class` will be `nil`.
+ #
+ # If a block is passed, the action_class is always created and the block is
+ # run inside it.
+ #
+ # @api private
+ #
+ def self.action_class(&block)
+ return @action_class if @action_class && !block
+ # If the superclass needed one, then we need one as well.
+ if block || (superclass.respond_to?(:action_class) && superclass.action_class)
+ @action_class = declare_action_class(&block)
+ end
+ @action_class
end
+ #
+ # Ensure the action class actually gets created. This is called
+ # when the user does `action :x do ... end`.
+ #
+ # If a block is passed, it is run inside the action_class.
+ #
+ # @api private
+ def self.declare_action_class(&block)
+ @action_class ||= begin
+ if superclass.respond_to?(:action_class)
+ base_provider = superclass.action_class
+ end
+ base_provider ||= Chef::Provider
+
+ resource_class = self
+ Class.new(base_provider) do
+ include ActionClass
+ self.resource_class = resource_class
+ end
+ end
+ @action_class.class_eval(&block) if block
+ @action_class
+ end
#
# Internal Resource Interface (for Chef)
@@ -879,7 +1222,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.
@@ -922,6 +1264,9 @@ class Chef
# resolve_resource_reference on each in turn, causing them to
# resolve lazy/forward references.
def resolve_notification_references
+ run_context.before_notifications(self).each { |n|
+ n.resolve_resource_reference(run_context.resource_collection)
+ }
run_context.immediate_notifications(self).each { |n|
n.resolve_resource_reference(run_context.resource_collection)
}
@@ -931,6 +1276,11 @@ class Chef
end
# Helper for #notifies
+ def notifies_before(action, resource_spec)
+ run_context.notifies_before(Notification.new(resource_spec, action, self))
+ end
+
+ # Helper for #notifies
def notifies_immediately(action, resource_spec)
run_context.notifies_immediately(Notification.new(resource_spec, action, self))
end
@@ -942,13 +1292,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.
@@ -956,10 +1332,36 @@ class Chef
if enclosing_provider && enclosing_provider.respond_to?(method_symbol)
enclosing_provider.send(method_symbol, *args, &block)
else
- raise NoMethodError, "undefined method `#{method_symbol.to_s}' for #{self.class.to_s}"
+ raise NoMethodError, "undefined method `#{method_symbol}' for #{self.class}"
end
end
+ #
+ # Mark this resource as providing particular DSL.
+ #
+ # Resources have an automatic DSL based on their resource_name, equivalent to
+ # `provides :resource_name` (providing the resource on all OS's). If you
+ # declare a `provides` with the given resource_name, it *replaces* that
+ # provides (so that you can provide your resource DSL only on certain OS's).
+ #
+ def self.provides(name, **options, &block)
+ name = name.to_sym
+
+ # `provides :resource_name, os: 'linux'`) needs to remove the old
+ # canonical DSL before adding the new one.
+ if @resource_name && name == @resource_name
+ remove_canonical_dsl
+ end
+
+ result = Chef.resource_handler_map.set(name, self, options, &block)
+ Chef::DSL::Resources.add_resource_dsl(name)
+ result
+ end
+
+ def self.provides?(node, resource_name)
+ Chef::ResourceResolver.new(node, resource_name).provided_by?(self)
+ end
+
# Helper for #notifies
def validate_resource_spec!(resource_spec)
run_context.resource_collection.validate_lookup_spec!(resource_spec)
@@ -973,6 +1375,10 @@ class Chef
"#{declared_type}[#{@name}]"
end
+ def before_notifications
+ run_context.before_notifications(self)
+ end
+
def immediate_notifications
run_context.immediate_notifications(self)
end
@@ -981,16 +1387,31 @@ class Chef
run_context.delayed_notifications(self)
end
+ def source_line_file
+ if source_line
+ source_line.match(/(.*):(\d+):?.*$/).to_a[1]
+ else
+ nil
+ end
+ end
+
+ def source_line_number
+ if source_line
+ source_line.match(/(.*):(\d+):?.*$/).to_a[2]
+ else
+ nil
+ end
+ end
+
def defined_at
# The following regexp should match these two sourceline formats:
# /some/path/to/file.rb:80:in `wombat_tears'
# C:/some/path/to/file.rb:80 in 1`wombat_tears'
# extracting the path to the source file and the line number.
- (file, line_no) = source_line.match(/(.*):(\d+):?.*$/).to_a[1,2] if source_line
if cookbook_name && recipe_name && source_line
- "#{cookbook_name}::#{recipe_name} line #{line_no}"
+ "#{cookbook_name}::#{recipe_name} line #{source_line_number}"
elsif source_line
- "#{file} line #{line_no}"
+ "#{source_line_file} line #{source_line_number}"
else
"dynamically defined"
end
@@ -1016,7 +1437,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
@@ -1080,33 +1502,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
@@ -1116,34 +1511,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
@@ -1154,9 +1559,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_class.rb b/lib/chef/resource/action_class.rb
new file mode 100644
index 0000000000..cf7327e968
--- /dev/null
+++ b/lib/chef/resource/action_class.rb
@@ -0,0 +1,87 @@
+#
+# 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 ActionClass
+ def to_s
+ "#{new_resource || "<no resource>"} action #{action ? action.inspect : "<no action>"}"
+ end
+
+ #
+ # If load_current_value! is defined on the resource, use that.
+ #
+ def load_current_resource
+ if new_resource.respond_to?(:load_current_value!)
+ # dup the resource and then reset desired-state properties.
+ current_resource = new_resource.dup
+
+ # We clear desired state in the copy, because it is supposed to be actual state.
+ # We keep identity properties and non-desired-state, which are assumed to be
+ # "control" values like `recurse: true`
+ current_resource.class.properties.each do |name,property|
+ if property.desired_state? && !property.identity? && !property.name_property?
+ property.reset(current_resource)
+ end
+ end
+
+ # Call the actual load_current_value! method. If it raises
+ # CurrentValueDoesNotExist, set current_resource to `nil`.
+ begin
+ # If the user specifies load_current_value do |desired_resource|, we
+ # pass in the desired resource as well as the current one.
+ if current_resource.method(:load_current_value!).arity > 0
+ current_resource.load_current_value!(new_resource)
+ else
+ current_resource.load_current_value!
+ end
+ rescue Chef::Exceptions::CurrentValueDoesNotExist
+ current_resource = nil
+ end
+ end
+
+ @current_resource = current_resource
+ end
+
+ def self.included(other)
+ other.extend(ClassMethods)
+ other.use_inline_resources
+ other.include_resource_dsl true
+ end
+
+ module ClassMethods
+ #
+ # The Chef::Resource class this ActionClass was declared against.
+ #
+ # @return [Class] The Chef::Resource class this ActionClass was declared against.
+ #
+ attr_accessor :resource_class
+
+ def to_s
+ "#{resource_class} action provider"
+ end
+
+ def inspect
+ to_s
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/resource/apt_package.rb b/lib/chef/resource/apt_package.rb
index f944825ac3..ab43f02ce3 100644
--- a/lib/chef/resource/apt_package.rb
+++ b/lib/chef/resource/apt_package.rb
@@ -16,29 +16,16 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/apt'
+require "chef/resource/package"
+require "chef/provider/package/apt"
class Chef
class Resource
class AptPackage < Chef::Resource::Package
-
- provides :apt_package
+ resource_name :apt_package
provides :package, os: "linux", platform_family: [ "debian" ]
- def initialize(name, run_context=nil)
- super
- @resource_name = :apt_package
- @default_release = nil
- end
-
- def default_release(arg=nil)
- set_or_return(
- :default_release,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :default_release, String, desired_state: false
end
end
diff --git a/lib/chef/resource/bash.rb b/lib/chef/resource/bash.rb
index 0add0ce501..f0233f0b4a 100644
--- a/lib/chef/resource/bash.rb
+++ b/lib/chef/resource/bash.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/resource/script'
-require 'chef/provider/script'
+require "chef/resource/script"
+require "chef/provider/script"
class Chef
class Resource
@@ -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 c091ec56b6..057e4596b3 100644
--- a/lib/chef/resource/batch.rb
+++ b/lib/chef/resource/batch.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/resource/windows_script'
+require "chef/resource/windows_script"
class Chef
class Resource
@@ -25,7 +25,7 @@ class Chef
provides :batch, os: "windows"
def initialize(name, run_context=nil)
- super(name, run_context, :batch, "cmd.exe")
+ super(name, run_context, nil, "cmd.exe")
end
end
diff --git a/lib/chef/resource/bff_package.rb b/lib/chef/resource/bff_package.rb
index 917f0d1d50..f6ddf3cd8f 100644
--- a/lib/chef/resource/bff_package.rb
+++ b/lib/chef/resource/bff_package.rb
@@ -16,20 +16,12 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/aix'
+require "chef/resource/package"
+require "chef/provider/package/aix"
class Chef
class Resource
class BffPackage < Chef::Resource::Package
-
- def initialize(name, run_context=nil)
- super
- @resource_name = :bff_package
- end
-
end
end
end
-
-
diff --git a/lib/chef/resource/breakpoint.rb b/lib/chef/resource/breakpoint.rb
index b2210262d2..10285ea757 100644
--- a/lib/chef/resource/breakpoint.rb
+++ b/lib/chef/resource/breakpoint.rb
@@ -17,19 +17,17 @@
#
-require 'chef/resource'
+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..233ae66026 100644
--- a/lib/chef/resource/chef_gem.rb
+++ b/lib/chef/resource/chef_gem.rb
@@ -16,46 +16,27 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/resource/gem_package'
+require "chef/resource/package"
+require "chef/resource/gem_package"
class Chef
class Resource
class ChefGem < Chef::Resource::Package::GemPackage
+ resource_name :chef_gem
- 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
-
- # The chef_gem resources is for installing gems to the current gem environment only for use by Chef cookbooks.
- def gem_binary(arg=nil)
- if arg
- raise ArgumentError, "The chef_gem resource is restricted to the current gem environment, use gem_package to install to other environments."
- end
-
- @gem_binary
- end
-
- def compile_time(arg=nil)
- set_or_return(
- :compile_time,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :gem_binary, default: "#{RbConfig::CONFIG['bindir']}/gem",
+ callbacks: {
+ "The chef_gem resource is restricted to the current gem environment, use gem_package to install to other environments." => proc { false }
+ }
+ property :compile_time, [ true, false ], default: lazy { Chef::Config[:chef_gem_compile_time] }, desired_state: false
def after_created
# 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/chocolatey_package.rb b/lib/chef/resource/chocolatey_package.rb
new file mode 100644
index 0000000000..3ee082349b
--- /dev/null
+++ b/lib/chef/resource/chocolatey_package.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# 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/resource/package"
+
+class Chef
+ class Resource
+ class ChocolateyPackage < Chef::Resource::Package
+
+ provides :chocolatey_package, os: "windows"
+
+ allowed_actions :install, :upgrade, :remove, :uninstall, :purge, :reconfig
+
+ def initialize(name, run_context=nil)
+ super
+ @resource_name = :chocolatey_package
+ end
+
+ property :package_name, [String, Array], coerce: proc { |x| [x].flatten }
+
+ property :version, [String, Array], coerce: proc { |x| [x].flatten }
+ end
+ end
+end
diff --git a/lib/chef/resource/conditional.rb b/lib/chef/resource/conditional.rb
index 35bdae8d69..9e2e635bde 100644
--- a/lib/chef/resource/conditional.rb
+++ b/lib/chef/resource/conditional.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/mixin/shell_out'
-require 'chef/guard_interpreter'
+require "chef/mixin/shell_out"
+require "chef/guard_interpreter"
class Chef
class Resource
diff --git a/lib/chef/resource/cookbook_file.rb b/lib/chef/resource/cookbook_file.rb
index 7be353b648..78d98e43f1 100644
--- a/lib/chef/resource/cookbook_file.rb
+++ b/lib/chef/resource/cookbook_file.rb
@@ -18,22 +18,20 @@
# limitations under the License.
#
-require 'chef/resource/file'
-require 'chef/provider/cookbook_file'
-require 'chef/mixin/securable'
+require "chef/resource/file"
+require "chef/provider/cookbook_file"
+require "chef/mixin/securable"
class Chef
class Resource
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 cb16506012..3ae9409756 100644
--- a/lib/chef/resource/cron.rb
+++ b/lib/chef/resource/cron.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
class Chef
class Resource
@@ -27,13 +27,11 @@ class Chef
state_attrs :minute, :hour, :day, :month, :weekday, :user
- provides :cron
+ default_action :create
+ allowed_actions :create, :delete
def initialize(name, run_context=nil)
super
- @resource_name = :cron
- @action = :create
- @allowed_actions.push(:create, :delete)
@minute = "*"
@hour = "*"
@day = "*"
@@ -62,7 +60,7 @@ class Chef
set_or_return(
:minute,
converted_arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -79,7 +77,7 @@ class Chef
set_or_return(
:hour,
converted_arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -96,7 +94,7 @@ class Chef
set_or_return(
:day,
converted_arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -113,7 +111,7 @@ class Chef
set_or_return(
:month,
converted_arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -125,7 +123,7 @@ class Chef
end
begin
error_message = "You provided '#{arg}' as a weekday, acceptable values are "
- error_message << Provider::Cron::WEEKDAY_SYMBOLS.map {|sym| ":#{sym.to_s}"}.join(', ')
+ error_message << Provider::Cron::WEEKDAY_SYMBOLS.map {|sym| ":#{sym}"}.join(", ")
error_message << " and a string in crontab format"
if (arg.is_a?(Symbol) && !Provider::Cron::WEEKDAY_SYMBOLS.include?(arg)) ||
(!arg.is_a?(Symbol) && integerize(arg) > 7) ||
@@ -137,7 +135,7 @@ class Chef
set_or_return(
:weekday,
converted_arg,
- :kind_of => [String, Symbol]
+ :kind_of => [String, Symbol],
)
end
@@ -145,7 +143,7 @@ class Chef
set_or_return(
:time,
arg,
- :equal_to => Chef::Provider::Cron::SPECIAL_TIME_VALUES
+ :equal_to => Chef::Provider::Cron::SPECIAL_TIME_VALUES,
)
end
@@ -153,7 +151,7 @@ class Chef
set_or_return(
:mailto,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -161,7 +159,7 @@ class Chef
set_or_return(
:path,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -169,7 +167,7 @@ class Chef
set_or_return(
:home,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -177,7 +175,7 @@ class Chef
set_or_return(
:shell,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -185,7 +183,7 @@ class Chef
set_or_return(
:command,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -193,7 +191,7 @@ class Chef
set_or_return(
:user,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -201,7 +199,7 @@ class Chef
set_or_return(
:environment,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
diff --git a/lib/chef/resource/csh.rb b/lib/chef/resource/csh.rb
index 36659c349b..fd033b10a0 100644
--- a/lib/chef/resource/csh.rb
+++ b/lib/chef/resource/csh.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/resource/script'
-require 'chef/provider/script'
+require "chef/resource/script"
+require "chef/provider/script"
class Chef
class Resource
@@ -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 4252aa230f..613d0cba24 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,33 +52,32 @@ 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'
+ @repository_cache = "cached-copy"
@copy_exclude = []
@purge_before_symlink = %w{log tmp/pids public/system}
@create_dirs_before_symlink = %w{tmp public config}
@symlink_before_migrate = {"config/database.yml" => "config/database.yml"}
@symlinks = {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}
- @revision = 'HEAD'
- @action = :deploy
+ @revision = "HEAD"
@migrate = false
@rollback_on_error = false
@remote = "origin"
@enable_submodules = false
@shallow_clone = false
+ @depth = nil
@scm_provider = Chef::Provider::Git
@svn_force_export = false
- @allowed_actions.push(:force_deploy, :deploy, :rollback)
@additional_remotes = Hash[]
@keep_releases = 5
@enable_checkout = true
@@ -99,8 +99,12 @@ class Chef
@current_path ||= @deploy_to + "/current"
end
- def depth
- @shallow_clone ? "5" : nil
+ def depth(arg=@shallow_clone ? 5 : nil)
+ set_or_return(
+ :depth,
+ arg,
+ :kind_of => [ Integer ],
+ )
end
# note: deploy_to is your application "meta-root."
@@ -108,7 +112,7 @@ class Chef
set_or_return(
:deploy_to,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -116,7 +120,7 @@ class Chef
set_or_return(
:repo,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
alias :repository :repo
@@ -125,7 +129,7 @@ class Chef
set_or_return(
:remote,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -133,7 +137,7 @@ class Chef
set_or_return(
:role,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -142,7 +146,7 @@ class Chef
set_or_return(
:restart_command,
arg,
- :kind_of => [ String, Proc ]
+ :kind_of => [ String, Proc ],
)
end
alias :restart :restart_command
@@ -151,7 +155,7 @@ class Chef
set_or_return(
:migrate,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -159,7 +163,7 @@ class Chef
set_or_return(
:migration_command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -167,7 +171,7 @@ class Chef
set_or_return(
:rollback_on_error,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -175,7 +179,7 @@ class Chef
set_or_return(
:user,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -183,7 +187,7 @@ class Chef
set_or_return(
:group,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -191,7 +195,7 @@ class Chef
set_or_return(
:enable_submodules,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -199,7 +203,7 @@ class Chef
set_or_return(
:shallow_clone,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -207,7 +211,7 @@ class Chef
set_or_return(
:repository_cache,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -215,7 +219,7 @@ class Chef
set_or_return(
:copy_exclude,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -223,7 +227,7 @@ class Chef
set_or_return(
:revision,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
alias :branch :revision
@@ -232,7 +236,7 @@ class Chef
set_or_return(
:git_ssh_wrapper,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
alias :ssh_wrapper :git_ssh_wrapper
@@ -241,7 +245,7 @@ class Chef
set_or_return(
:svn_username,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -249,7 +253,7 @@ class Chef
set_or_return(
:svn_password,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -257,7 +261,7 @@ class Chef
set_or_return(
:svn_arguments,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -277,15 +281,21 @@ class Chef
set_or_return(
:scm_provider,
klass,
- :kind_of => [ Class ]
+ :kind_of => [ Class ],
)
end
+ # This is to support "provider :revision" without deprecation warnings.
+ # Do NOT copy this.
+ def self.provider_base
+ Chef::Provider::Deploy
+ end
+
def svn_force_export(arg=nil)
set_or_return(
:svn_force_export,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -298,7 +308,7 @@ class Chef
set_or_return(
:environment,
arg,
- :kind_of => [ Hash ]
+ :kind_of => [ Hash ],
)
end
@@ -318,7 +328,7 @@ class Chef
set_or_return(
:purge_before_symlink,
arg,
- :kind_of => Array
+ :kind_of => Array,
)
end
@@ -334,7 +344,7 @@ class Chef
set_or_return(
:create_dirs_before_symlink,
arg,
- :kind_of => Array
+ :kind_of => Array,
)
end
@@ -348,7 +358,7 @@ class Chef
set_or_return(
:symlinks,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
@@ -363,7 +373,7 @@ class Chef
set_or_return(
:symlink_before_migrate,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
@@ -395,7 +405,7 @@ class Chef
set_or_return(
:additional_remotes,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
@@ -403,7 +413,7 @@ class Chef
set_or_return(
:enable_checkout,
arg,
- :kind_of => [TrueClass, FalseClass]
+ :kind_of => [TrueClass, FalseClass],
)
end
@@ -411,7 +421,7 @@ class Chef
set_or_return(
:checkout_branch,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -424,7 +434,7 @@ class Chef
set_or_return(
:timeout,
arg,
- :kind_of => Integer
+ :kind_of => Integer,
)
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..608471b6a1 100644
--- a/lib/chef/resource/directory.rb
+++ b/lib/chef/resource/directory.rb
@@ -18,9 +18,9 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/provider/directory'
-require 'chef/mixin/securable'
+require "chef/resource"
+require "chef/provider/directory"
+require "chef/mixin/securable"
class Chef
class Resource
@@ -32,22 +32,20 @@ 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)
set_or_return(
:recursive,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -55,7 +53,7 @@ class Chef
set_or_return(
:path,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
diff --git a/lib/chef/resource/dpkg_package.rb b/lib/chef/resource/dpkg_package.rb
index 35a47e8a82..158b66d492 100644
--- a/lib/chef/resource/dpkg_package.rb
+++ b/lib/chef/resource/dpkg_package.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");
@@ -16,20 +16,15 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/dpkg'
+require "chef/resource/package"
class Chef
class Resource
class DpkgPackage < Chef::Resource::Package
-
+ resource_name :dpkg_package
provides :dpkg_package, os: "linux"
- def initialize(name, run_context=nil)
- super
- @resource_name = :dpkg_package
- end
-
+ property :source, [ String, Array, nil ]
end
end
end
diff --git a/lib/chef/resource/dsc_resource.rb b/lib/chef/resource/dsc_resource.rb
index 912b683434..35bb585a7b 100644
--- a/lib/chef/resource/dsc_resource.rb
+++ b/lib/chef/resource/dsc_resource.rb
@@ -1,83 +1,120 @@
-#
-# 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
-
- def initialize(name, run_context)
- super
- @properties = {}
- @resource_name = :dsc_resource
- @resource = nil
- @allowed_actions.push(:run)
- @action = :run
- 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
+#
+# 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"
+
+ # This class will check if the object responds to
+ # to_text. If it does, it will call that as opposed
+ # to inspect. This is useful for properties that hold
+ # objects such as PsCredential, where we do not want
+ # to dump the actual ivars
+ class ToTextHash < Hash
+ def to_text
+ descriptions = self.map do |(property, obj)|
+ obj_text = if obj.respond_to?(:to_text)
+ obj.to_text
+ else
+ obj.inspect
+ end
+ "#{property}=>#{obj_text}"
+ end
+ "{#{descriptions.join(', ')}}"
+ end
+ end
+
+ include Chef::DSL::Powershell
+
+ default_action :run
+
+ def initialize(name, run_context)
+ super
+ @properties = ToTextHash.new
+ @resource = nil
+ @reboot_action = :nothing
+ 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}' of type #{property_name.class} 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
+
+ # This property takes the action message for the reboot resource
+ # If the set method of the DSC resource indicate that a reboot
+ # is necessary, reboot_action provides the mechanism for a reboot to
+ # be requested.
+ def reboot_action(value=nil)
+ if value
+ @reboot_action = value
+ else
+ @reboot_action
+ end
+ end
+
+ def timeout(arg=nil)
+ set_or_return(
+ :timeout,
+ arg,
+ :kind_of => [ Integer ],
+ )
+ end
+ private
+
+ def value_of(value)
+ 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..b9b66539ac 100644
--- a/lib/chef/resource/dsc_script.rb
+++ b/lib/chef/resource/dsc_script.rb
@@ -16,19 +16,20 @@
# limitations under the License.
#
-require 'chef/exceptions'
+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
@@ -42,7 +43,7 @@ class Chef
set_or_return(
:code,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -53,7 +54,7 @@ class Chef
set_or_return(
:configuration_name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -64,7 +65,7 @@ class Chef
set_or_return(
:command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -75,7 +76,7 @@ class Chef
set_or_return(
:configuration_data,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -86,7 +87,7 @@ class Chef
set_or_return(
:configuration_data_script,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -94,7 +95,7 @@ class Chef
if module_name
@imports[module_name] ||= []
if args.length == 0
- @imports[module_name] << '*'
+ @imports[module_name] << "*"
else
@imports[module_name].push(*args)
end
@@ -107,7 +108,7 @@ class Chef
set_or_return(
:flags,
arg,
- :kind_of => [ Hash ]
+ :kind_of => [ Hash ],
)
end
@@ -115,7 +116,7 @@ class Chef
set_or_return(
:cwd,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -123,7 +124,7 @@ class Chef
set_or_return(
:environment,
arg,
- :kind_of => [ Hash ]
+ :kind_of => [ Hash ],
)
end
@@ -131,7 +132,7 @@ class Chef
set_or_return(
:timeout,
arg,
- :kind_of => [ Integer ]
+ :kind_of => [ Integer ],
)
end
end
diff --git a/lib/chef/resource/easy_install_package.rb b/lib/chef/resource/easy_install_package.rb
index 5286e9a289..89761b5735 100644
--- a/lib/chef/resource/easy_install_package.rb
+++ b/lib/chef/resource/easy_install_package.rb
@@ -16,42 +16,16 @@
# limitations under the License.
#
-require 'chef/resource/package'
+require "chef/resource/package"
class Chef
class Resource
class EasyInstallPackage < Chef::Resource::Package
+ resource_name :easy_install_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,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def python_binary(arg=nil)
- set_or_return(
- :python_install_binary,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def module_name(arg=nil)
- set_or_return(
- :module_name,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :easy_install_binary, String, desired_state: false
+ property :python_binary, String, desired_state: false
+ property :module_name, String, desired_state: false
end
end
diff --git a/lib/chef/resource/env.rb b/lib/chef/resource/env.rb
index 2072ae5d80..32c12dba09 100644
--- a/lib/chef/resource/env.rb
+++ b/lib/chef/resource/env.rb
@@ -27,21 +27,21 @@ class Chef
provides :env, os: "windows"
+ default_action :create
+ allowed_actions :create, :delete, :modify
+
def initialize(name, run_context=nil)
super
- @resource_name = :env
@key_name = name
@value = nil
- @action = :create
@delim = nil
- @allowed_actions.push(:create, :delete, :modify)
end
def key_name(arg=nil)
set_or_return(
:key_name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -49,7 +49,7 @@ class Chef
set_or_return(
:value,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -57,7 +57,7 @@ class Chef
set_or_return(
:delim,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
end
diff --git a/lib/chef/resource/erl_call.rb b/lib/chef/resource/erl_call.rb
index 24009d51c7..3322d1151a 100644
--- a/lib/chef/resource/erl_call.rb
+++ b/lib/chef/resource/erl_call.rb
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/provider/erl_call'
+require "chef/resource"
+require "chef/provider/erl_call"
class Chef
class Resource
@@ -28,25 +28,23 @@ 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)
set_or_return(
:code,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -54,7 +52,7 @@ class Chef
set_or_return(
:cookie,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -62,7 +60,7 @@ class Chef
set_or_return(
:distributed,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -70,7 +68,7 @@ class Chef
set_or_return(
:name_type,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -78,7 +76,7 @@ class Chef
set_or_return(
:node_name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
diff --git a/lib/chef/resource/execute.rb b/lib/chef/resource/execute.rb
index 9f8b629fb8..fe9a3d5e26 100644
--- a/lib/chef/resource/execute.rb
+++ b/lib/chef/resource/execute.rb
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/provider/execute'
+require "chef/resource"
+require "chef/provider/execute"
class Chef
class Resource
@@ -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,17 +46,17 @@ class Chef
@returns = 0
@timeout = nil
@user = nil
- @allowed_actions.push(:run)
@umask = nil
@default_guard_interpreter = :execute
@is_guard_interpreter = false
+ @live_stream = false
end
def umask(arg=nil)
set_or_return(
:umask,
arg,
- :kind_of => [ String, Integer ]
+ :kind_of => [ String, Integer ],
)
end
@@ -64,7 +64,7 @@ class Chef
set_or_return(
:command,
arg,
- :kind_of => [ String, Array ]
+ :kind_of => [ String, Array ],
)
end
@@ -72,7 +72,7 @@ class Chef
set_or_return(
:creates,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -80,7 +80,7 @@ class Chef
set_or_return(
:cwd,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -88,7 +88,7 @@ class Chef
set_or_return(
:environment,
arg,
- :kind_of => [ Hash ]
+ :kind_of => [ Hash ],
)
end
@@ -98,17 +98,24 @@ class Chef
set_or_return(
:group,
arg,
- :kind_of => [ String, Integer ]
+ :kind_of => [ String, Integer ],
)
end
+ def live_stream(arg=nil)
+ set_or_return(
+ :live_stream,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ])
+ end
+
def path(arg=nil)
- Chef::Log.warn "'path' attribute of 'execute' is not used by any provider in Chef 11 and Chef 12. Use 'environment' attribute to configure 'PATH'. This attribute will be removed in Chef 13."
+ Chef::Log.warn "The 'path' attribute of 'execute' is not used by any provider in Chef 11 or Chef 12. Use 'environment' attribute to configure 'PATH'. This attribute will be removed in Chef 13."
set_or_return(
:path,
arg,
- :kind_of => [ Array ]
+ :kind_of => [ Array ],
)
end
@@ -116,7 +123,7 @@ class Chef
set_or_return(
:returns,
arg,
- :kind_of => [ Integer, Array ]
+ :kind_of => [ Integer, Array ],
)
end
@@ -124,7 +131,7 @@ class Chef
set_or_return(
:timeout,
arg,
- :kind_of => [ Integer, Float ]
+ :kind_of => [ Integer, Float ],
)
end
@@ -132,7 +139,7 @@ class Chef
set_or_return(
:user,
arg,
- :kind_of => [ String, Integer ]
+ :kind_of => [ String, Integer ],
)
end
@@ -157,7 +164,7 @@ class Chef
:environment,
:group,
:user,
- :umask
+ :umask,
)
end
diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb
index 53a6a160af..0a530e27c9 100644
--- a/lib/chef/resource/file.rb
+++ b/lib/chef/resource/file.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Seth Chisamore (<schisamo@opscode.com>)
-# Copyright:: Copyright (c) 2008, 2011 Opscode, Inc.
+# Copyright:: Copyright (c) 2008, 2011-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,18 +17,16 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/platform/query_helpers'
-require 'chef/mixin/securable'
-require 'chef/resource/file/verification'
+require "chef/resource"
+require "chef/platform/query_helpers"
+require "chef/mixin/securable"
+require "chef/resource/file/verification"
class Chef
class Resource
class File < Chef::Resource
include Chef::Mixin::Securable
- identity_attr :path
-
if Platform.windows?
# Use Windows rights instead of standard *nix permissions
state_attrs :checksum, :rights, :deny_rights
@@ -38,85 +36,27 @@ class Chef
attr_writer :checksum
- provides :file
-
- 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
- @diff = nil
- @verifications = []
- end
-
- def content(arg=nil)
- set_or_return(
- :content,
- arg,
- :kind_of => String
- )
- end
-
- def backup(arg=nil)
- set_or_return(
- :backup,
- arg,
- :kind_of => [ Integer, FalseClass ]
- )
- end
-
- def checksum(arg=nil)
- set_or_return(
- :checksum,
- arg,
- :regex => /^[a-zA-Z0-9]{64}$/
- )
- end
-
- def path(arg=nil)
- set_or_return(
- :path,
- arg,
- :kind_of => String
- )
- end
-
- def diff(arg=nil)
- set_or_return(
- :diff,
- arg,
- :kind_of => String
- )
- end
-
- def atomic_update(arg=nil)
- set_or_return(
- :atomic_update,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def force_unlink(arg=nil)
- set_or_return(
- :force_unlink,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
-
- def manage_symlink_source(arg=nil)
- set_or_return(
- :manage_symlink_source,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ #
+ # 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
+
+ property :path, String, name_property: true, identity: true
+ property :atomic_update, [ true, false ], desired_state: false, default: Chef::Config[:file_atomic_update]
+ property :backup, [ Integer, false ], desired_state: false, default: 5
+ property :checksum, [ /^[a-zA-Z0-9]{64}$/, nil ]
+ property :content, [ String, nil ], desired_state: false
+ property :diff, [ String, nil ], desired_state: false
+ property :force_unlink, [ true, false ], desired_state: false, default: false
+ property :manage_symlink_source, [ true, false ], desired_state: false
+ property :verifications, Array, default: lazy { [] }
def verify(command=nil, opts={}, &block)
if ! (command.nil? || [String, Symbol].include?(command.class))
@@ -124,10 +64,19 @@ class Chef
end
if command || block_given?
- @verifications << Verification.new(self, command, opts, &block)
+ verifications << Verification.new(self, command, opts, &block)
else
- @verifications
+ 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
diff --git a/lib/chef/resource/file/verification.rb b/lib/chef/resource/file/verification.rb
index f1ca0f1883..1237d09c99 100644
--- a/lib/chef/resource/file/verification.rb
+++ b/lib/chef/resource/file/verification.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/exceptions'
-require 'chef/guard_interpreter'
-require 'chef/mixin/descendants_tracker'
+require "chef/exceptions"
+require "chef/guard_interpreter"
+require "chef/mixin/descendants_tracker"
class Chef
class Resource
@@ -28,7 +28,7 @@ class Chef
# See RFC 027 for a full specification
#
# File verifications allow user-supplied commands a means of
- # preventing file reosurce content deploys. Their intended use
+ # preventing file resource content deploys. Their intended use
# is to verify the contents of a temporary file before it is
# deployed onto the system.
#
@@ -106,7 +106,13 @@ class Chef
# We reuse Chef::GuardInterpreter in order to support
# the same set of options that the not_if/only_if blocks do
def verify_command(path, opts)
- command = @command % {:file => path}
+ # First implementation interpolated `file`; docs & RFC claim `path`
+ # is interpolated. Until `file` can be deprecated, interpolate both.
+ Chef.log_deprecation(
+ "%{file} is deprecated in verify command and will not be "\
+ "supported in Chef 13. Please use %{path} instead."
+ ) if @command.include?("%{file}")
+ command = @command % {:file => path, :path => path}
interpreter = Chef::GuardInterpreter.for_resource(@parent_resource, command, @command_opts)
interpreter.evaluate
end
diff --git a/lib/chef/resource/freebsd_package.rb b/lib/chef/resource/freebsd_package.rb
index 9c8db506f8..a64e3204f2 100644
--- a/lib/chef/resource/freebsd_package.rb
+++ b/lib/chef/resource/freebsd_package.rb
@@ -18,24 +18,20 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/freebsd/port'
-require 'chef/provider/package/freebsd/pkg'
-require 'chef/provider/package/freebsd/pkgng'
-require 'chef/mixin/shell_out'
+require "chef/resource/package"
+require "chef/provider/package/freebsd/port"
+require "chef/provider/package/freebsd/pkg"
+require "chef/provider/package/freebsd/pkgng"
+require "chef/mixin/shell_out"
class Chef
class Resource
class FreebsdPackage < Chef::Resource::Package
include Chef::Mixin::ShellOut
+ resource_name :freebsd_package
provides :package, platform: "freebsd"
- def initialize(name, run_context=nil)
- super
- @resource_name = :freebsd_package
- end
-
def after_created
assign_provider
end
@@ -53,7 +49,7 @@ class Chef
end
def assign_provider
- @provider = if @source.to_s =~ /^ports$/i
+ @provider = if source.to_s =~ /^ports$/i
Chef::Provider::Package::Freebsd::Port
elsif supports_pkgng?
Chef::Provider::Package::Freebsd::Pkgng
diff --git a/lib/chef/resource/gem_package.rb b/lib/chef/resource/gem_package.rb
index 0e838ca040..a78e5c6ad7 100644
--- a/lib/chef/resource/gem_package.rb
+++ b/lib/chef/resource/gem_package.rb
@@ -16,32 +16,17 @@
# limitations under the License.
#
-require 'chef/resource/package'
+require "chef/resource/package"
class Chef
class Resource
class GemPackage < Chef::Resource::Package
+ resource_name :gem_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
-
+ property :source, [ String, Array ]
+ property :clear_sources, [ true, false ], default: false, desired_state: false
# 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 ])
- end
+ property :gem_binary, String, desired_state: false
##
# Options for the gem install, either a Hash or a String. When a hash is
@@ -49,10 +34,7 @@ class Chef
# gem will be installed via the gems API. When a String is given, the gem
# will be installed by shelling out to the gem command. Using a Hash of
# options with an explicit gem_binary will result in undefined behavior.
- def options(opts=nil)
- set_or_return(:options,opts,:kind_of => [String,Hash])
- end
-
+ property :options, [ String, Hash, nil ], desired_state: false
end
end
diff --git a/lib/chef/resource/git.rb b/lib/chef/resource/git.rb
index 7156873315..62c7bc2600 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
@@ -34,7 +31,7 @@ class Chef
set_or_return(
:additional_remotes,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
diff --git a/lib/chef/resource/group.rb b/lib/chef/resource/group.rb
index 9e8f1309b0..2f829a4b44 100644
--- a/lib/chef/resource/group.rb
+++ b/lib/chef/resource/group.rb
@@ -25,26 +25,24 @@ class Chef
state_attrs :members
- provides :group
+ allowed_actions :create, :remove, :modify, :manage
+ default_action :create
def initialize(name, run_context=nil)
super
- @resource_name = :group
@group_name = name
@gid = nil
@members = []
@excluded_members = []
- @action = :create
@append = false
@non_unique = false
- @allowed_actions.push(:create, :remove, :modify, :manage)
end
def group_name(arg=nil)
set_or_return(
:group_name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -52,7 +50,7 @@ class Chef
set_or_return(
:gid,
arg,
- :kind_of => [ String, Integer ]
+ :kind_of => [ String, Integer ],
)
end
@@ -61,7 +59,7 @@ class Chef
set_or_return(
:members,
converted_members,
- :kind_of => [ Array ]
+ :kind_of => [ Array ],
)
end
@@ -72,7 +70,7 @@ class Chef
set_or_return(
:excluded_members,
converted_members,
- :kind_of => [ Array ]
+ :kind_of => [ Array ],
)
end
@@ -81,7 +79,7 @@ class Chef
set_or_return(
:append,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -89,7 +87,7 @@ class Chef
set_or_return(
:system,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -97,7 +95,7 @@ class Chef
set_or_return(
:non_unique,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
end
diff --git a/lib/chef/resource/homebrew_package.rb b/lib/chef/resource/homebrew_package.rb
index 73409b13ac..705b136dc2 100644
--- a/lib/chef/resource/homebrew_package.rb
+++ b/lib/chef/resource/homebrew_package.rb
@@ -18,29 +18,16 @@
# limitations under the License.
#
-require 'chef/provider/package'
-require 'chef/resource/package'
+require "chef/provider/package"
+require "chef/resource/package"
class Chef
class Resource
class HomebrewPackage < Chef::Resource::Package
-
- provides :homebrew_package
+ resource_name :homebrew_package
provides :package, os: "darwin"
- def initialize(name, run_context=nil)
- super
- @resource_name = :homebrew_package
- @homebrew_user = nil
- end
-
- def homebrew_user(arg=nil)
- set_or_return(
- :homebrew_user,
- arg,
- :kind_of => [ String, Integer ]
- )
- end
+ property :homebrew_user, [ String, Integer ]
end
end
diff --git a/lib/chef/resource/http_request.rb b/lib/chef/resource/http_request.rb
index ccb0a26629..623d14430b 100644
--- a/lib/chef/resource/http_request.rb
+++ b/lib/chef/resource/http_request.rb
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/provider/http_request'
+require "chef/resource"
+require "chef/provider/http_request"
class Chef
class Resource
@@ -26,21 +26,21 @@ 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)
set_or_return(
:url,
args,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -49,7 +49,7 @@ class Chef
set_or_return(
:message,
args,
- :kind_of => Object
+ :kind_of => Object,
)
end
@@ -57,7 +57,7 @@ class Chef
set_or_return(
:headers,
args,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
diff --git a/lib/chef/resource/ifconfig.rb b/lib/chef/resource/ifconfig.rb
index c289ddadbe..79d8e4af4c 100644
--- a/lib/chef/resource/ifconfig.rb
+++ b/lib/chef/resource/ifconfig.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
class Chef
class Resource
@@ -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
@@ -50,7 +50,7 @@ class Chef
set_or_return(
:target,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -58,7 +58,7 @@ class Chef
set_or_return(
:device,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -66,7 +66,7 @@ class Chef
set_or_return(
:hwaddr,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -74,7 +74,7 @@ class Chef
set_or_return(
:inet_addr,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -82,7 +82,7 @@ class Chef
set_or_return(
:bcast,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -90,7 +90,7 @@ class Chef
set_or_return(
:mask,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -98,7 +98,7 @@ class Chef
set_or_return(
:mtu,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -106,7 +106,7 @@ class Chef
set_or_return(
:metric,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -114,7 +114,7 @@ class Chef
set_or_return(
:onboot,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -122,7 +122,7 @@ class Chef
set_or_return(
:network,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -130,7 +130,7 @@ class Chef
set_or_return(
:bootproto,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -138,12 +138,10 @@ class Chef
set_or_return(
:onparent,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
end
end
end
-
-
diff --git a/lib/chef/resource/ips_package.rb b/lib/chef/resource/ips_package.rb
index c0e699e31a..908f458b1e 100644
--- a/lib/chef/resource/ips_package.rb
+++ b/lib/chef/resource/ips_package.rb
@@ -16,29 +16,19 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/ips'
+require "chef/resource/package"
+require "chef/provider/package/ips"
class Chef
class Resource
class IpsPackage < ::Chef::Resource::Package
-
+ resource_name :ips_package
+ provides :package, os: "solaris2"
provides :ips_package, os: "solaris2"
- def initialize(name, run_context = nil)
- super(name, run_context)
- @resource_name = :ips_package
- @allowed_actions.push(:install, :remove, :upgrade)
- @accept_license = false
- end
+ allowed_actions :install, :remove, :upgrade
- def accept_license(arg=nil)
- set_or_return(
- :purge,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :accept_license, [ true, false ], default: false, desired_state: false
end
end
end
diff --git a/lib/chef/resource/ksh.rb b/lib/chef/resource/ksh.rb
new file mode 100644
index 0000000000..e41ae51551
--- /dev/null
+++ b/lib/chef/resource/ksh.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Nolan Davidson (<nolan.davidson@gmail.com>)
+# Copyright:: Copyright (c) 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/resource/script"
+
+class Chef
+ class Resource
+ class Ksh < Chef::Resource::Script
+
+ def initialize(name, run_context=nil)
+ super
+ @interpreter = "ksh"
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb
index 30f8ec86d1..a067027c1f 100644
--- a/lib/chef/resource/link.rb
+++ b/lib/chef/resource/link.rb
@@ -17,36 +17,34 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/mixin/securable'
+require "chef/resource"
+require "chef/mixin/securable"
class Chef
class Resource
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)
set_or_return(
:to,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -54,7 +52,7 @@ class Chef
set_or_return(
:target_file,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -63,7 +61,7 @@ class Chef
set_or_return(
:link_type,
real_arg,
- :equal_to => [ :symbolic, :hard ]
+ :equal_to => [ :symbolic, :hard ],
)
end
@@ -71,7 +69,7 @@ class Chef
set_or_return(
:group,
arg,
- :regex => Chef::Config[:group_valid_regex]
+ :regex => Chef::Config[:group_valid_regex],
)
end
@@ -79,7 +77,7 @@ class Chef
set_or_return(
:owner,
arg,
- :regex => Chef::Config[:user_valid_regex]
+ :regex => Chef::Config[:user_valid_regex],
)
end
@@ -94,7 +92,7 @@ class Chef
# sure we are not on such a platform.
if Chef::Platform.windows?
- require 'chef/win32/file'
+ require "chef/win32/file"
begin
Chef::ReservedNames::Win32::File.verify_links_supported!
rescue Chef::Exceptions::Win32APIFunctionNotImplemented => e
diff --git a/lib/chef/resource/log.rb b/lib/chef/resource/log.rb
index 7f970a87a4..bf7d21215d 100644
--- a/lib/chef/resource/log.rb
+++ b/lib/chef/resource/log.rb
@@ -17,8 +17,8 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/provider/log'
+require "chef/resource"
+require "chef/provider/log"
class Chef
class Resource
@@ -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
@@ -59,7 +58,7 @@ class Chef
set_or_return(
:message,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -68,12 +67,10 @@ class Chef
set_or_return(
:level,
arg,
- :equal_to => [ :debug, :info, :warn, :error, :fatal ]
+ :equal_to => [ :debug, :info, :warn, :error, :fatal ],
)
end
end
end
end
-
-
diff --git a/lib/chef/resource/lwrp_base.rb b/lib/chef/resource/lwrp_base.rb
index ce72e98028..ad586da9e8 100644
--- a/lib/chef/resource/lwrp_base.rb
+++ b/lib/chef/resource/lwrp_base.rb
@@ -1,8 +1,8 @@
#
-# Author:: Adam Jacob (<adam@opscode.com>)
-# Author:: Christopher Walters (<cw@opscode.com>)
-# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2008-2012 Opscode, Inc.
+# Author:: Adam Jacob (<adam@chef.io>)
+# Author:: Christopher Walters (<cw@chef.io>)
+# Author:: Daniel DeLeo (<dan@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");
@@ -18,7 +18,14 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
+require "chef/resource_resolver"
+require "chef/node"
+require "chef/log"
+require "chef/exceptions"
+require "chef/mixin/convert_to_class_name"
+require "chef/mixin/from_file"
+require "chef/mixin/params_validate" # for DelayedEvaluator
class Chef
class Resource
@@ -28,138 +35,90 @@ class Chef
# so attributes, default action, etc. can be defined with pleasing syntax.
class LWRPBase < Resource
- NULL_ARG = Object.new
+ # Class methods
+ class <<self
- extend Chef::Mixin::ConvertToClassName
- extend Chef::Mixin::FromFile
+ include Chef::Mixin::ConvertToClassName
+ include Chef::Mixin::FromFile
- # Evaluates the LWRP resource file and instantiates a new Resource class.
- def self.build_from_file(cookbook_name, filename, run_context)
- resource_class = nil
- rname = filename_to_qualified_string(cookbook_name, filename)
+ attr_accessor :loaded_lwrps
- class_name = convert_to_class_name(rname)
- if Resource.const_defined?(class_name, false)
- Chef::Log.info("#{class_name} light-weight resource is already initialized -- Skipping loading #{filename}!")
- Chef::Log.debug("Overriding already defined LWRPs is not supported anymore starting with Chef 12.")
- resource_class = Resource.const_get(class_name)
- else
- resource_class = Class.new(self)
+ def build_from_file(cookbook_name, filename, run_context)
+ if LWRPBase.loaded_lwrps[filename]
+ Chef::Log.debug("Custom resource #{filename} from cookbook #{cookbook_name} has already been loaded! Skipping the reload.")
+ return loaded_lwrps[filename]
+ end
- Chef::Resource.const_set(class_name, resource_class)
- resource_class.resource_name = rname
+ resource_name = filename_to_qualified_string(cookbook_name, filename)
+
+ # We load the class first to give it a chance to set its own name
+ resource_class = Class.new(self)
+ resource_class.resource_name resource_name.to_sym
resource_class.run_context = run_context
resource_class.class_from_file(filename)
- Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}")
- end
-
- resource_class
- end
+ # Make a useful string for the class (rather than <Class:312894723894>)
+ resource_class.instance_eval do
+ define_singleton_method(:to_s) do
+ "Custom resource #{resource_name} from cookbook #{cookbook_name}"
+ end
+ define_singleton_method(:inspect) { to_s }
+ end
- # Set the resource name for this LWRP
- def self.resource_name(arg = NULL_ARG)
- if arg.equal?(NULL_ARG)
- @resource_name
- else
- @resource_name = arg
- end
- end
+ Chef::Log.debug("Loaded contents of #{filename} into resource #{resource_name} (#{resource_class})")
- class << self
- alias_method :resource_name=, :resource_name
- end
+ LWRPBase.loaded_lwrps[filename] = true
- # Define an attribute on this resource, including optional validation
- # parameters.
- def self.attribute(attr_name, validation_opts={})
- define_method(attr_name) do |arg=nil|
- set_or_return(attr_name.to_sym, arg, validation_opts)
+ # Create the deprecated Chef::Resource::LwrpFoo class
+ Chef::Resource.register_deprecated_lwrp_class(resource_class, convert_to_class_name(resource_name))
+ resource_class
end
- end
- # Sets the default action
- def self.default_action(action_name=NULL_ARG)
- unless action_name.equal?(NULL_ARG)
- @actions ||= []
- if action_name.is_a?(Array)
- action = action_name.map { |arg| arg.to_sym }
- @actions = actions | action
- @default_action = action
- else
- action = action_name.to_sym
- @actions.push(action) unless @actions.include?(action)
- @default_action = action
- end
- end
+ alias :attribute :property
- @default_action ||= from_superclass(:default_action)
- end
-
- # Adds +action_names+ to the list of valid actions for this resource.
- def self.actions(*action_names)
- if action_names.empty?
- defined?(@actions) ? @actions : from_superclass(:actions, []).dup
- else
- # BC-compat way for checking if actions have already been defined
- if defined?(@actions)
- @actions.push(*action_names)
+ # Adds +action_names+ to the list of valid actions for this resource.
+ # Does not include superclass's action list when appending.
+ def actions(*action_names)
+ action_names = action_names.flatten
+ if !action_names.empty? && !@allowed_actions
+ self.allowed_actions = ([ :nothing ] + action_names).uniq
else
- @actions = action_names
+ allowed_actions(*action_names)
end
end
- end
-
- # @deprecated
- def self.valid_actions(*args)
- Chef::Log.warn("`valid_actions' is deprecated, please use actions `instead'!")
- actions(*args)
- end
+ alias :actions= :allowed_actions=
- # Set the run context on the class. Used to provide access to the node
- # during class definition.
- def self.run_context=(run_context)
- @run_context = run_context
- end
+ # @deprecated
+ def valid_actions(*args)
+ Chef::Log.warn("`valid_actions' is deprecated, please use allowed_actions `instead'!")
+ allowed_actions(*args)
+ end
- def self.run_context
- @run_context
- end
+ # Set the run context on the class. Used to provide access to the node
+ # during class definition.
+ attr_accessor :run_context
- def self.node
- run_context.node
- end
-
- def self.lazy(&block)
- DelayedEvaluator.new(&block)
- end
+ def node
+ run_context ? run_context.node : nil
+ end
- private
+ protected
- # Get the value from the superclass, if it responds, otherwise return
- # +nil+. Since class instance variables are **not** inherited upon
- # subclassing, this is a required check to ensure Chef pulls the
- # +default_action+ and other DSL-y methods when extending LWRP::Base.
- def self.from_superclass(m, default = nil)
- return default if superclass == Chef::Resource::LWRPBase
- superclass.respond_to?(m) ? superclass.send(m) : default
- end
+ def loaded_lwrps
+ @loaded_lwrps ||= {}
+ end
- # Default initializer. Sets the default action and allowed actions.
- def initialize(name, run_context=nil)
- super(name, run_context)
+ private
- # Raise an exception if the resource_name was not defined
- if self.class.resource_name.nil?
- raise Chef::Exceptions::InvalidResourceSpecification,
- "You must specify `resource_name'!"
+ # Get the value from the superclass, if it responds, otherwise return
+ # +nil+. Since class instance variables are **not** inherited upon
+ # subclassing, this is a required check to ensure Chef pulls the
+ # +default_action+ and other DSL-y methods when extending LWRP::Base.
+ def from_superclass(m, default = nil)
+ return default if superclass == Chef::Resource::LWRPBase
+ superclass.respond_to?(m) ? superclass.send(m) : default
end
-
- @resource_name = self.class.resource_name.to_sym
- @action = self.class.default_action
- allowed_actions.push(self.class.actions).flatten!
end
-
end
end
end
diff --git a/lib/chef/resource/macosx_service.rb b/lib/chef/resource/macosx_service.rb
new file mode 100644
index 0000000000..e8cdf9c6f4
--- /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..3c95c8d05e 100644
--- a/lib/chef/resource/macports_package.rb
+++ b/lib/chef/resource/macports_package.rb
@@ -16,17 +16,12 @@
# 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
+ resource_name :macports_package
end
end
end
diff --git a/lib/chef/resource/mdadm.rb b/lib/chef/resource/mdadm.rb
index 971b6c51b4..41123a8c71 100644
--- a/lib/chef/resource/mdadm.rb
+++ b/lib/chef/resource/mdadm.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
class Chef
class Resource
@@ -27,11 +27,11 @@ class Chef
state_attrs :devices, :level, :chunk
- provides :mdadm
+ default_action :create
+ allowed_actions :create, :assemble, :stop
def initialize(name, run_context=nil)
super
- @resource_name = :mdadm
@chunk = 16
@devices = []
@@ -40,16 +40,13 @@ class Chef
@metadata = "0.90"
@bitmap = nil
@raid_device = name
-
- @action = :create
- @allowed_actions.push(:create, :assemble, :stop)
end
def chunk(arg=nil)
set_or_return(
:chunk,
arg,
- :kind_of => [ Integer ]
+ :kind_of => [ Integer ],
)
end
@@ -57,7 +54,7 @@ class Chef
set_or_return(
:devices,
arg,
- :kind_of => [ Array ]
+ :kind_of => [ Array ],
)
end
@@ -65,7 +62,7 @@ class Chef
set_or_return(
:exists,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -73,7 +70,7 @@ class Chef
set_or_return(
:level,
arg,
- :kind_of => [ Integer ]
+ :kind_of => [ Integer ],
)
end
@@ -81,7 +78,7 @@ class Chef
set_or_return(
:metadata,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -89,7 +86,7 @@ class Chef
set_or_return(
:bitmap,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -97,7 +94,7 @@ class Chef
set_or_return(
:raid_device,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb
index 142dec87f7..31efcb0b16 100644
--- a/lib/chef/resource/mount.rb
+++ b/lib/chef/resource/mount.rb
@@ -17,7 +17,7 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
class Chef
class Resource
@@ -27,24 +27,22 @@ class Chef
state_attrs :mount_point, :device_type, :fstype, :username, :password, :domain
- provides :mount
+ default_action :mount
+ allowed_actions :mount, :umount, :remount, :enable, :disable
def initialize(name, run_context=nil)
super
- @resource_name = :mount
@mount_point = name
@device = nil
@device_type = :device
- @fsck_device = '-'
+ @fsck_device = "-"
@fstype = "auto"
@options = ["defaults"]
@dump = 0
@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
@@ -54,7 +52,7 @@ class Chef
set_or_return(
:mount_point,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -62,7 +60,7 @@ class Chef
set_or_return(
:device,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -76,7 +74,7 @@ class Chef
set_or_return(
:device_type,
real_arg,
- :equal_to => valid_devices
+ :equal_to => valid_devices,
)
end
@@ -84,7 +82,7 @@ class Chef
set_or_return(
:fsck_device,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -92,7 +90,7 @@ class Chef
set_or_return(
:fstype,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -100,11 +98,11 @@ class Chef
ret = set_or_return(
:options,
arg,
- :kind_of => [ Array, String ]
+ :kind_of => [ Array, String ],
)
if ret.is_a? String
- ret.gsub(/,/, ' ').split(/ /)
+ ret.gsub(/,/, " ").split(/ /)
else
ret
end
@@ -114,7 +112,7 @@ class Chef
set_or_return(
:dump,
arg,
- :kind_of => [ Integer, FalseClass ]
+ :kind_of => [ Integer, FalseClass ],
)
end
@@ -122,7 +120,7 @@ class Chef
set_or_return(
:pass,
arg,
- :kind_of => [ Integer, FalseClass ]
+ :kind_of => [ Integer, FalseClass ],
)
end
@@ -130,7 +128,7 @@ class Chef
set_or_return(
:mounted,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -138,7 +136,7 @@ class Chef
set_or_return(
:enabled,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -156,7 +154,7 @@ class Chef
set_or_return(
:username,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -164,7 +162,7 @@ class Chef
set_or_return(
:password,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -172,10 +170,18 @@ class Chef
set_or_return(
:domain,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
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..8be334fea9 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
@@ -38,7 +37,7 @@ class Chef
set_or_return(
:plugin,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -46,7 +45,7 @@ class Chef
set_or_return(
:name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
end
diff --git a/lib/chef/resource/openbsd_package.rb b/lib/chef/resource/openbsd_package.rb
index 20a2523e3a..a7b027540f 100644
--- a/lib/chef/resource/openbsd_package.rb
+++ b/lib/chef/resource/openbsd_package.rb
@@ -19,33 +19,17 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/openbsd'
-require 'chef/mixin/shell_out'
+require "chef/resource/package"
+require "chef/provider/package/openbsd"
+require "chef/mixin/shell_out"
class Chef
class Resource
class OpenbsdPackage < Chef::Resource::Package
include Chef::Mixin::ShellOut
+ resource_name :openbsd_package
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/osx_profile.rb b/lib/chef/resource/osx_profile.rb
new file mode 100644
index 0000000000..57c1d9979c
--- /dev/null
+++ b/lib/chef/resource/osx_profile.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Nate Walck (<nate.walck@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"
+
+class Chef
+ class Resource
+ class OsxProfile < Chef::Resource
+ provides :osx_profile, os: "darwin"
+ provides :osx_config_profile, os: "darwin"
+
+ identity_attr :profile_name
+
+ default_action :install
+ allowed_actions :install, :remove
+
+ def initialize(name, run_context=nil)
+ super
+ @profile_name = name
+ @profile = nil
+ @identifier = nil
+ @path = nil
+ end
+
+ def profile_name(arg=nil)
+ set_or_return(
+ :profile_name,
+ arg,
+ :kind_of => [ String ],
+ )
+ end
+
+ def profile(arg=nil)
+ set_or_return(
+ :profile,
+ arg,
+ :kind_of => [ String, Hash ],
+ )
+ end
+
+ def identifier(arg=nil)
+ set_or_return(
+ :identifier,
+ arg,
+ :kind_of => [ String ],
+ )
+ end
+
+ def path(arg=nil)
+ set_or_return(
+ :path,
+ arg,
+ :kind_of => [ String ],
+ )
+ end
+
+ end
+ end
+end
diff --git a/lib/chef/resource/package.rb b/lib/chef/resource/package.rb
index f4f49b543b..6fd3fcd548 100644
--- a/lib/chef/resource/package.rb
+++ b/lib/chef/resource/package.rb
@@ -1,7 +1,7 @@
#
# Author:: Adam Jacob (<adam@opscode.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");
@@ -17,86 +17,30 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
class Chef
class Resource
class Package < Chef::Resource
+ resource_name :package
- identity_attr :package_name
+ default_action :install
+ allowed_actions :install, :upgrade, :remove, :purge, :reconfig
- state_attrs :version, :options
-
- def initialize(name, run_context=nil)
+ def initialize(name, *args)
+ # We capture name here, before it gets coerced to name
+ package_name name
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
- end
-
- def package_name(arg=nil)
- set_or_return(
- :package_name,
- arg,
- :kind_of => [ String, Array ]
- )
end
- def version(arg=nil)
- set_or_return(
- :version,
- arg,
- :kind_of => [ String, Array ]
- )
- end
+ property :package_name, [ String, Array ], identity: true
- def response_file(arg=nil)
- set_or_return(
- :response_file,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def response_file_variables(arg=nil)
- set_or_return(
- :response_file_variables,
- arg,
- :kind_of => [ Hash ]
- )
- end
-
- def source(arg=nil)
- set_or_return(
- :source,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def options(arg=nil)
- set_or_return(
- :options,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def timeout(arg=nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => [String, Integer]
- )
- end
+ property :version, [ String, Array ]
+ property :options, String
+ property :response_file, String, desired_state: false
+ property :response_file_variables, Hash, default: lazy { {} }, desired_state: false
+ property :source, String, desired_state: false
+ property :timeout, [ String, Integer ], desired_state: false
end
end
diff --git a/lib/chef/resource/pacman_package.rb b/lib/chef/resource/pacman_package.rb
index 4c45dd004f..1db0a8aac2 100644
--- a/lib/chef/resource/pacman_package.rb
+++ b/lib/chef/resource/pacman_package.rb
@@ -16,19 +16,13 @@
# limitations under the License.
#
-require 'chef/resource/package'
+require "chef/resource/package"
class Chef
class Resource
class PacmanPackage < Chef::Resource::Package
-
+ resource_name :pacman_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..ce25ac93f3 100644
--- a/lib/chef/resource/paludis_package.rb
+++ b/lib/chef/resource/paludis_package.rb
@@ -16,21 +16,18 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/paludis'
+require "chef/resource/package"
+require "chef/provider/package/paludis"
class Chef
class Resource
class PaludisPackage < Chef::Resource::Package
-
+ resource_name :paludis_package
provides :paludis_package, os: "linux"
- def initialize(name, run_context=nil)
- super(name, run_context)
- @resource_name = :paludis_package
- @allowed_actions.push(:install, :remove, :upgrade)
- @timeout = 3600
- end
+ allowed_actions :install, :remove, :upgrade
+
+ property :timeout, default: 3600
end
end
end
diff --git a/lib/chef/resource/perl.rb b/lib/chef/resource/perl.rb
index c4bdb6e130..1874cbea3f 100644
--- a/lib/chef/resource/perl.rb
+++ b/lib/chef/resource/perl.rb
@@ -16,16 +16,14 @@
# limitations under the License.
#
-require 'chef/resource/script'
-require 'chef/provider/script'
+require "chef/resource/script"
+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..2bee9e1a90 100644
--- a/lib/chef/resource/portage_package.rb
+++ b/lib/chef/resource/portage_package.rb
@@ -16,15 +16,14 @@
# limitations under the License.
#
-require 'chef/resource/package'
+require "chef/resource/package"
class Chef
class Resource
class PortagePackage < Chef::Resource::Package
-
+ resource_name :portage_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 43aafe4df2..32636edb4f 100644
--- a/lib/chef/resource/powershell_script.rb
+++ b/lib/chef/resource/powershell_script.rb
@@ -15,16 +15,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/resource/windows_script'
+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
@@ -32,7 +31,7 @@ class Chef
set_or_return(
:convert_boolean_return,
arg,
- :kind_of => [ FalseClass, TrueClass ]
+ :kind_of => [ FalseClass, TrueClass ],
)
end
diff --git a/lib/chef/resource/python.rb b/lib/chef/resource/python.rb
index b1f23d13ce..c571a24baf 100644
--- a/lib/chef/resource/python.rb
+++ b/lib/chef/resource/python.rb
@@ -15,16 +15,14 @@
# limitations under the License.
#
-require 'chef/resource/script'
-require 'chef/provider/script'
+require "chef/resource/script"
+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..ebbdb58268 100644
--- a/lib/chef/resource/reboot.rb
+++ b/lib/chef/resource/reboot.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
# In using this resource via notifications, it's important to *only* use
# immediate notifications. Delayed notifications produce unintuitive and
@@ -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..8bec0721e7 100644
--- a/lib/chef/resource/registry_key.rb
+++ b/lib/chef/resource/registry_key.rb
@@ -15,17 +15,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/provider/registry_key'
-require 'chef/resource'
-require 'chef/digester'
+require "chef/provider/registry_key"
+require "chef/resource"
+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,20 +61,17 @@ 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)
set_or_return(
:key,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -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)
@@ -106,7 +105,7 @@ class Chef
set_or_return(
:recursive,
arg,
- :kind_of => [TrueClass, FalseClass]
+ :kind_of => [TrueClass, FalseClass],
)
end
@@ -114,7 +113,7 @@ class Chef
set_or_return(
:architecture,
arg,
- :kind_of => Symbol
+ :kind_of => Symbol,
)
end
@@ -126,7 +125,7 @@ class Chef
scrubbed_value = value.dup
if needs_checksum?(scrubbed_value)
data_io = StringIO.new(scrubbed_value[:data].to_s)
- scrubbed_value[:data] = Chef::Digester.instance.generate_md5_checksum(data_io)
+ scrubbed_value[:data] = Chef::Digester.instance.generate_checksum(data_io)
end
scrubbed << scrubbed_value
end
diff --git a/lib/chef/resource/remote_directory.rb b/lib/chef/resource/remote_directory.rb
index d4108da47a..cae8f9c256 100644
--- a/lib/chef/resource/remote_directory.rb
+++ b/lib/chef/resource/remote_directory.rb
@@ -17,28 +17,27 @@
# limitations under the License.
#
-require 'chef/resource/directory'
-require 'chef/provider/remote_directory'
-require 'chef/mixin/securable'
+require "chef/resource/directory"
+require "chef/provider/remote_directory"
+require "chef/mixin/securable"
class Chef
class Resource
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
@@ -60,7 +58,7 @@ class Chef
set_or_return(
:source,
args,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -68,7 +66,7 @@ class Chef
set_or_return(
:files_backup,
arg,
- :kind_of => [ Integer, FalseClass ]
+ :kind_of => [ Integer, FalseClass ],
)
end
@@ -76,7 +74,7 @@ class Chef
set_or_return(
:purge,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -84,7 +82,7 @@ class Chef
set_or_return(
:files_group,
arg,
- :regex => Chef::Config[:group_valid_regex]
+ :regex => Chef::Config[:group_valid_regex],
)
end
@@ -92,7 +90,7 @@ class Chef
set_or_return(
:files_mode,
arg,
- :regex => /^\d{3,4}$/
+ :regex => /^\d{3,4}$/,
)
end
@@ -100,7 +98,7 @@ class Chef
set_or_return(
:files_owner,
arg,
- :regex => Chef::Config[:user_valid_regex]
+ :regex => Chef::Config[:user_valid_regex],
)
end
@@ -108,7 +106,7 @@ class Chef
set_or_return(
:overwrite,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -116,7 +114,7 @@ class Chef
set_or_return(
:cookbook,
args,
- :kind_of => String
+ :kind_of => String,
)
end
diff --git a/lib/chef/resource/remote_file.rb b/lib/chef/resource/remote_file.rb
index e56f69941d..a722c519b1 100644
--- a/lib/chef/resource/remote_file.rb
+++ b/lib/chef/resource/remote_file.rb
@@ -17,22 +17,19 @@
# limitations under the License.
#
-require 'uri'
-require 'chef/resource/file'
-require 'chef/provider/remote_file'
-require 'chef/mixin/securable'
+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
@@ -79,7 +76,7 @@ class Chef
set_or_return(
:checksum,
args,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -95,7 +92,7 @@ class Chef
set_or_return(
:use_etag,
args,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -105,7 +102,7 @@ class Chef
set_or_return(
:use_last_modified,
args,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -113,7 +110,7 @@ class Chef
set_or_return(
:ftp_active_mode,
args,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -121,12 +118,14 @@ class Chef
set_or_return(
:headers,
args,
- :kind_of => Hash
+ :kind_of => Hash,
)
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?
@@ -140,7 +139,7 @@ class Chef
end
def absolute_uri?(source)
- source.kind_of?(String) and URI.parse(source).absolute?
+ Chef::Provider::RemoteFile::Fetcher.network_share?(source) or (source.kind_of?(String) and as_uri(source).absolute?)
rescue URI::InvalidURIError
false
end
diff --git a/lib/chef/resource/resource_notification.rb b/lib/chef/resource/resource_notification.rb
index a27ed961c7..e1945ceaee 100644
--- a/lib/chef/resource/resource_notification.rb
+++ b/lib/chef/resource/resource_notification.rb
@@ -16,11 +16,19 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
class Chef
class Resource
- class Notification < Struct.new(:resource, :action, :notifying_resource)
+ class Notification
+
+ attr_accessor :resource, :action, :notifying_resource
+
+ def initialize(resource, action, notifying_resource)
+ @resource = resource
+ @action = action
+ @notifying_resource = notifying_resource
+ end
def duplicates?(other_notification)
unless other_notification.respond_to?(:resource) && other_notification.respond_to?(:action)
@@ -104,6 +112,11 @@ is defined near #{resource.source_line}
raise err
end
+ def ==(other)
+ return false unless other.is_a?(self.class)
+ other.resource == resource && other.action == action && other.notifying_resource == notifying_resource
+ end
+
end
end
end
diff --git a/lib/chef/resource/route.rb b/lib/chef/resource/route.rb
index 942905d138..5f876f47ec 100644
--- a/lib/chef/resource/route.rb
+++ b/lib/chef/resource/route.rb
@@ -17,22 +17,21 @@
# limitations under the License.
#
-require 'chef/resource'
+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
@@ -49,7 +48,7 @@ class Chef
set_or_return(
:networking,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -57,7 +56,7 @@ class Chef
set_or_return(
:networking_ipv6,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -65,7 +64,7 @@ class Chef
set_or_return(
:hostname,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -73,7 +72,7 @@ class Chef
set_or_return(
:domainname,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -81,7 +80,7 @@ class Chef
set_or_return(
:domain,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -89,7 +88,7 @@ class Chef
set_or_return(
:target,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -97,7 +96,7 @@ class Chef
set_or_return(
:netmask,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -105,7 +104,7 @@ class Chef
set_or_return(
:gateway,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -113,7 +112,7 @@ class Chef
set_or_return(
:metric,
arg,
- :kind_of => Integer
+ :kind_of => Integer,
)
end
@@ -121,7 +120,7 @@ class Chef
set_or_return(
:device,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -130,11 +129,9 @@ class Chef
set_or_return(
:route_type,
real_arg,
- :equal_to => [ :host, :net ]
+ :equal_to => [ :host, :net ],
)
end
end
end
end
-
-
diff --git a/lib/chef/resource/rpm_package.rb b/lib/chef/resource/rpm_package.rb
index f00121dd69..ff4432cee2 100644
--- a/lib/chef/resource/rpm_package.rb
+++ b/lib/chef/resource/rpm_package.rb
@@ -16,28 +16,16 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/rpm'
+require "chef/resource/package"
+require "chef/provider/package/rpm"
class Chef
class Resource
class RpmPackage < Chef::Resource::Package
-
+ resource_name :rpm_package
provides :rpm_package, os: [ "linux", "aix" ]
- def initialize(name, run_context=nil)
- super
- @resource_name = :rpm_package
- @allow_downgrade = false
- end
-
- def allow_downgrade(arg=nil)
- set_or_return(
- :allow_downgrade,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ property :allow_downgrade, [ true, false ], default: false, desired_state: false
end
end
diff --git a/lib/chef/resource/ruby.rb b/lib/chef/resource/ruby.rb
index 2b2aa0249d..057502df7d 100644
--- a/lib/chef/resource/ruby.rb
+++ b/lib/chef/resource/ruby.rb
@@ -16,19 +16,16 @@
# limitations under the License.
#
-require 'chef/resource/script'
-require 'chef/provider/script'
+require "chef/resource/script"
+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..547c90cc65 100644
--- a/lib/chef/resource/ruby_block.rb
+++ b/lib/chef/resource/ruby_block.rb
@@ -17,20 +17,19 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/provider/ruby_block'
+require "chef/resource"
+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
@@ -46,7 +45,7 @@ class Chef
set_or_return(
:block_name,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
end
diff --git a/lib/chef/resource/scm.rb b/lib/chef/resource/scm.rb
index 87c217b4cc..380bbdb6f4 100644
--- a/lib/chef/resource/scm.rb
+++ b/lib/chef/resource/scm.rb
@@ -17,28 +17,27 @@
#
-require 'chef/resource'
+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
@@ -47,7 +46,7 @@ class Chef
set_or_return(
:destination,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -55,7 +54,7 @@ class Chef
set_or_return(
:repository,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -63,7 +62,7 @@ class Chef
set_or_return(
:revision,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -71,7 +70,7 @@ class Chef
set_or_return(
:user,
arg,
- :kind_of => [String, Integer]
+ :kind_of => [String, Integer],
)
end
@@ -79,7 +78,7 @@ class Chef
set_or_return(
:group,
arg,
- :kind_of => [String, Integer]
+ :kind_of => [String, Integer],
)
end
@@ -87,7 +86,7 @@ class Chef
set_or_return(
:svn_username,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -95,7 +94,7 @@ class Chef
set_or_return(
:svn_password,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -104,7 +103,7 @@ class Chef
set_or_return(
:svn_arguments,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -121,7 +120,7 @@ class Chef
set_or_return(
:depth,
arg,
- :kind_of => Integer
+ :kind_of => Integer,
)
end
@@ -129,7 +128,7 @@ class Chef
set_or_return(
:enable_submodules,
arg,
- :kind_of => [TrueClass, FalseClass]
+ :kind_of => [TrueClass, FalseClass],
)
end
@@ -137,7 +136,7 @@ class Chef
set_or_return(
:enable_checkout,
arg,
- :kind_of => [TrueClass, FalseClass]
+ :kind_of => [TrueClass, FalseClass],
)
end
@@ -145,7 +144,7 @@ class Chef
set_or_return(
:remote,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -153,7 +152,7 @@ class Chef
set_or_return(
:ssh_wrapper,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -161,7 +160,7 @@ class Chef
set_or_return(
:timeout,
arg,
- :kind_of => Integer
+ :kind_of => Integer,
)
end
@@ -169,7 +168,7 @@ class Chef
set_or_return(
:checkout_branch,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -177,7 +176,7 @@ class Chef
set_or_return(
:environment,
arg,
- :kind_of => [ Hash ]
+ :kind_of => [ Hash ],
)
end
diff --git a/lib/chef/resource/script.rb b/lib/chef/resource/script.rb
index fd0fd5a7fd..96f59585e4 100644
--- a/lib/chef/resource/script.rb
+++ b/lib/chef/resource/script.rb
@@ -17,19 +17,17 @@
# limitations under the License.
#
-require 'chef/resource/execute'
-require 'chef/provider/script'
+require "chef/resource/execute"
+require "chef/provider/script"
class Chef
class Resource
class Script < Chef::Resource::Execute
-
# Chef-13: go back to using :name as the identity attr
identity_attr :command
def initialize(name, run_context=nil)
super
- @resource_name = :script
# Chef-13: the command variable should be initialized to nil
@command = name
@code = nil
@@ -42,7 +40,7 @@ class Chef
unless arg.nil?
# Chef-13: change this to raise if the user is trying to set a value here
Chef::Log.warn "Specifying command attribute on a script resource is a coding error, use the 'code' attribute, or the execute resource"
- Chef::Log.warn "This attribute is deprecated and must be fixed or this code will fail on Chef-13"
+ Chef::Log.warn "This attribute is deprecated and must be fixed or this code will fail on Chef 13"
end
super
end
@@ -51,7 +49,7 @@ class Chef
set_or_return(
:code,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -59,7 +57,7 @@ class Chef
set_or_return(
:interpreter,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -67,7 +65,7 @@ class Chef
set_or_return(
:flags,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index 36df7c859a..490ad40f7b 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");
@@ -17,19 +17,20 @@
# limitations under the License.
#
-require 'chef/resource'
+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,16 +44,15 @@ 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)
set_or_return(
:service_name,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -61,7 +61,7 @@ class Chef
set_or_return(
:pattern,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -70,7 +70,7 @@ class Chef
set_or_return(
:start_command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -79,7 +79,7 @@ class Chef
set_or_return(
:stop_command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -88,7 +88,7 @@ class Chef
set_or_return(
:status_command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -97,7 +97,7 @@ class Chef
set_or_return(
:restart_command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -105,7 +105,7 @@ class Chef
set_or_return(
:reload_command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -118,7 +118,7 @@ class Chef
set_or_return(
:init_command,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -127,7 +127,7 @@ class Chef
set_or_return(
:enabled,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -136,7 +136,7 @@ class Chef
set_or_return(
:running,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -154,7 +154,7 @@ class Chef
set_or_return(
:priority,
arg,
- :kind_of => [ Integer, String, Hash ]
+ :kind_of => [ Integer, String, Hash ],
)
end
@@ -163,7 +163,7 @@ class Chef
set_or_return(
:timeout,
arg,
- :kind_of => Integer
+ :kind_of => Integer,
)
end
@@ -171,10 +171,17 @@ class Chef
set_or_return(
:parameters,
arg,
- :kind_of => [ Hash ]
+ :kind_of => [ Hash ],
)
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..715a60bc96 100644
--- a/lib/chef/resource/smartos_package.rb
+++ b/lib/chef/resource/smartos_package.rb
@@ -16,22 +16,14 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/smartos'
+require "chef/resource/package"
+require "chef/provider/package/smartos"
class Chef
class Resource
class SmartosPackage < Chef::Resource::Package
-
- provides :smartos_package
+ resource_name :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..2e6a4de8b4 100644
--- a/lib/chef/resource/solaris_package.rb
+++ b/lib/chef/resource/solaris_package.rb
@@ -17,27 +17,15 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/solaris'
+require "chef/resource/package"
+require "chef/provider/package/solaris"
class Chef
class Resource
class SolarisPackage < Chef::Resource::Package
-
- provides :solaris_package
+ resource_name :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..2bac41a2ff 100644
--- a/lib/chef/resource/subversion.rb
+++ b/lib/chef/resource/subversion.rb
@@ -22,19 +22,23 @@ require "chef/resource/scm"
class Chef
class Resource
class Subversion < Chef::Resource::Scm
+ allowed_actions :force_export
def initialize(name, run_context=nil)
super
- @svn_arguments = '--no-auth-cache'
- @svn_info_args = '--no-auth-cache'
- @resource_name = :subversion
- allowed_actions << :force_export
+ @svn_arguments = "--no-auth-cache"
+ @svn_info_args = "--no-auth-cache"
+ @svn_binary = nil
end
# Override exception to strip password if any, so it won't appear in logs and different Chef notifications
def custom_exception_message(e)
"#{self} (#{defined_at}) had an error: #{e.class.name}: #{svn_password ? e.message.gsub(svn_password, "[hidden_password]") : e.message}"
end
+
+ def svn_binary(arg=nil)
+ set_or_return(:svn_binary, arg, :kind_of => [String])
+ end
end
end
end
diff --git a/lib/chef/resource/template.rb b/lib/chef/resource/template.rb
index 67a9e6a418..02b4bcfe69 100644
--- a/lib/chef/resource/template.rb
+++ b/lib/chef/resource/template.rb
@@ -18,24 +18,20 @@
# limitations under the License.
#
-require 'chef/resource/file'
-require 'chef/provider/template'
-require 'chef/mixin/securable'
+require "chef/resource/file"
+require "chef/provider/template"
+require "chef/mixin/securable"
class Chef
class Resource
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
@@ -49,7 +45,7 @@ class Chef
set_or_return(
:source,
file,
- :kind_of => [ String, Array ]
+ :kind_of => [ String, Array ],
)
end
@@ -57,7 +53,7 @@ class Chef
set_or_return(
:variables,
args,
- :kind_of => [ Hash ]
+ :kind_of => [ Hash ],
)
end
@@ -65,7 +61,7 @@ class Chef
set_or_return(
:cookbook,
args,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -73,7 +69,7 @@ class Chef
set_or_return(
:local,
args,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
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 7d2ec25596..bf4b62bcb1 100644
--- a/lib/chef/resource/user.rb
+++ b/lib/chef/resource/user.rb
@@ -16,21 +16,20 @@
# limitations under the License.
#
-require 'chef/resource'
+require "chef/resource"
class Chef
class Resource
class User < Chef::Resource
-
identity_attr :username
state_attrs :uid, :gid, :home
- provides :user
+ default_action :create
+ allowed_actions :create, :remove, :modify, :manage, :lock, :unlock
def initialize(name, run_context=nil)
super
- @resource_name = :user
@username = name
@comment = nil
@uid = nil
@@ -42,21 +41,19 @@ class Chef
@manage_home = false
@force = false
@non_unique = false
- @action = :create
@supports = {
:manage_home => false,
- :non_unique => false
+ :non_unique => false,
}
@iterations = 27855
@salt = nil
- @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
end
def username(arg=nil)
set_or_return(
:username,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -64,7 +61,7 @@ class Chef
set_or_return(
:comment,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -72,7 +69,7 @@ class Chef
set_or_return(
:uid,
arg,
- :kind_of => [ String, Integer ]
+ :kind_of => [ String, Integer ],
)
end
@@ -80,7 +77,7 @@ class Chef
set_or_return(
:gid,
arg,
- :kind_of => [ String, Integer ]
+ :kind_of => [ String, Integer ],
)
end
@@ -90,7 +87,7 @@ class Chef
set_or_return(
:home,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -98,7 +95,7 @@ class Chef
set_or_return(
:shell,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -106,7 +103,7 @@ class Chef
set_or_return(
:password,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -114,7 +111,7 @@ class Chef
set_or_return(
:salt,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -122,7 +119,7 @@ class Chef
set_or_return(
:iterations,
arg,
- :kind_of => [ Integer ]
+ :kind_of => [ Integer ],
)
end
@@ -130,7 +127,7 @@ class Chef
set_or_return(
:system,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -138,7 +135,7 @@ class Chef
set_or_return(
:manage_home,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -146,7 +143,7 @@ class Chef
set_or_return(
:force,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
@@ -154,7 +151,7 @@ class Chef
set_or_return(
:non_unique,
arg,
- :kind_of => [ TrueClass, FalseClass ]
+ :kind_of => [ TrueClass, FalseClass ],
)
end
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..cee82d3b63 100644
--- a/lib/chef/resource/windows_package.rb
+++ b/lib/chef/resource/windows_package.rb
@@ -16,63 +16,40 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/windows'
-require 'chef/win32/error' if RUBY_PLATFORM =~ /mswin|mingw|windows/
+require "chef/mixin/uris"
+require "chef/resource/package"
+require "chef/provider/package/windows"
+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"
+ resource_name :windows_package
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
- @installer_type = nil
- @timeout = 600
- # In the past we accepted return code 127 for an unknown reason and 42 because of a bug
- @returns = [ 0 ]
- end
-
- def installer_type(arg=nil)
- set_or_return(
- :installer_type,
- arg,
- :kind_of => [ Symbol ]
- )
+ @source ||= source(@package_name) if @package_name.downcase.end_with?(".msi")
end
- def timeout(arg=nil)
- set_or_return(
- :timeout,
- arg,
- :kind_of => [ String, Integer ]
- )
- end
-
- def returns(arg=nil)
- set_or_return(
- :returns,
- arg,
- :kind_of => [ String, Integer, Array ]
- )
- end
-
- def source(arg=nil)
- if arg == nil && self.instance_variable_defined?(:@source) == true
- @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)
- end
- end
+ # Unique to this resource
+ property :installer_type, Symbol
+ property :timeout, [ String, Integer ], default: 600
+ # In the past we accepted return code 127 for an unknown reason and 42 because of a bug
+ property :returns, [ String, Integer, Array ], default: [ 0 ], desired_state: false
+ property :source, String,
+ coerce: (proc do |s|
+ unless s.nil?
+ uri_scheme?(s) ? s : Chef::Util::PathHelper.canonical_path(s, false)
+ end
+ end)
+ property :checksum, String, desired_state: false
+ property :remote_file_attributes, Hash, desired_state: false
end
end
end
diff --git a/lib/chef/resource/windows_script.rb b/lib/chef/resource/windows_script.rb
index 6b0827b77c..67ad3c8040 100644
--- a/lib/chef/resource/windows_script.rb
+++ b/lib/chef/resource/windows_script.rb
@@ -16,12 +16,14 @@
# limitations under the License.
#
-require 'chef/resource/script'
-require 'chef/mixin/windows_architecture_helper'
+require "chef/platform/query_helpers"
+require "chef/resource/script"
+require "chef/mixin/windows_architecture_helper"
class Chef
class Resource
class WindowsScript < Chef::Resource::Script
+ # This is an abstract resource meant to be subclasses; thus no 'provides'
set_guard_inherited_attributes(:architecture)
@@ -30,8 +32,8 @@ class Chef
def initialize(name, run_context, resource_name, interpreter_command)
super(name, run_context)
@interpreter = interpreter_command
- @resource_name = resource_name
- @default_guard_interpreter = resource_name
+ @resource_name = resource_name if resource_name
+ @default_guard_interpreter = self.resource_name
end
include Chef::Mixin::WindowsArchitectureHelper
@@ -43,16 +45,19 @@ class Chef
result = set_or_return(
:architecture,
arg,
- :kind_of => Symbol
+ :kind_of => Symbol,
)
end
protected
def assert_architecture_compatible!(desired_architecture)
- if ! node_supports_windows_architecture?(node, desired_architecture)
+ if desired_architecture == :i386 && Chef::Platform.windows_nano_server?
raise Chef::Exceptions::Win32ArchitectureIncorrect,
- "cannot execute script with requested architecture '#{desired_architecture.to_s}' on a system with architecture '#{node_windows_architecture(node)}'"
+ "cannot execute script with requested architecture 'i386' on Windows Nano Server"
+ elsif ! node_supports_windows_architecture?(node, desired_architecture)
+ raise Chef::Exceptions::Win32ArchitectureIncorrect,
+ "cannot execute script with requested architecture '#{desired_architecture}' on a system with architecture '#{node_windows_architecture(node)}'"
end
end
end
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
index 8090adceb0..76ef32514f 100644
--- a/lib/chef/resource/windows_service.rb
+++ b/lib/chef/resource/windows_service.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/resource/service'
+require "chef/resource/service"
class Chef
class Resource
@@ -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 = ""
@@ -47,7 +47,7 @@ class Chef
set_or_return(
:startup_type,
arg,
- :equal_to => [ :automatic, :manual, :disabled ]
+ :equal_to => [ :automatic, :manual, :disabled ],
)
end
@@ -55,7 +55,7 @@ class Chef
set_or_return(
:run_as_user,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
@@ -63,7 +63,7 @@ class Chef
set_or_return(
:run_as_password,
arg,
- :kind_of => [ String ]
+ :kind_of => [ String ],
)
end
end
diff --git a/lib/chef/resource/yum_package.rb b/lib/chef/resource/yum_package.rb
index 8fbca9b097..6a7a1035c9 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");
@@ -16,49 +16,31 @@
# limitations under the License.
#
-require 'chef/resource/package'
-require 'chef/provider/package/yum'
+require "chef/resource/package"
+require "chef/provider/package/yum"
class Chef
class Resource
class YumPackage < Chef::Resource::Package
-
- provides :yum_package
+ resource_name :yum_package
provides :package, os: "linux", platform_family: [ "rhel", "fedora" ]
- def initialize(name, run_context=nil)
- super
- @resource_name = :yum_package
- @flush_cache = { :before => false, :after => false }
- @allow_downgrade = false
- end
-
# Install a specific arch
- def arch(arg=nil)
- set_or_return(
- :arch,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def flush_cache(args={})
- if args.is_a? Array
- args.each { |arg| @flush_cache[arg] = true }
- elsif args.any?
- @flush_cache = args
+ property :arch, [ String, Array ]
+ property :flush_cache, Hash, default: { before: false, after: false }, coerce: proc { |v|
+ # TODO these append rather than set. This is probably wrong behavior, but we're preserving it until we know
+ if v.is_a?(Array)
+ v.each { |arg| flush_cache[arg] = true }
+ flush_cache
+ elsif v.any?
+ v
else
- @flush_cache
+ # TODO calling flush_cache({}) does a get instead of a set. This is probably wrong behavior, but we're preserving it until we know
+ flush_cache
end
- end
-
- def allow_downgrade(arg=nil)
- set_or_return(
- :allow_downgrade,
- arg,
- :kind_of => [ TrueClass, FalseClass ]
- )
- end
+ }
+ property :allow_downgrade, [ true, false ], default: false
+ property :yum_binary, String
end
end
diff --git a/lib/chef/resource/zypper_package.rb b/lib/chef/resource/zypper_package.rb
new file mode 100644
index 0000000000..62f8049193
--- /dev/null
+++ b/lib/chef/resource/zypper_package.rb
@@ -0,0 +1,28 @@
+#
+# 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
+ resource_name :zypper_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..bb9e6808e0 100644
--- a/lib/chef/resource_builder.rb
+++ b/lib/chef/resource_builder.rb
@@ -1,6 +1,6 @@
#
# Author:: Lamont Granquist (<lamont@chef.io>)
-# Copyright:: Copyright (c) 2015 Opscode, Inc.
+# Copyright:: Copyright (c) 2015-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -46,6 +46,9 @@ class Chef
raise ArgumentError, "You must supply a name when declaring a #{type} resource" if name.nil?
@resource = resource_class.new(name, run_context)
+ if resource.resource_name.nil?
+ raise Chef::Exceptions::InvalidResourceSpecification, "#{resource}.resource_name is `nil`! Did you forget to put `provides :blah` or `resource_name :blah` in your resource class?"
+ end
resource.source_line = created_at
resource.declared_type = type
@@ -67,7 +70,14 @@ class Chef
resource.params = params
# Evaluate resource attribute DSL
- resource.instance_eval(&block) if block_given?
+ if block_given?
+ resource.resource_initializing = true
+ begin
+ resource.instance_eval(&block)
+ ensure
+ resource.resource_initializing = false
+ end
+ end
# emit a cloned resource warning if it is warranted
if prior_resource
@@ -127,7 +137,7 @@ class Chef
@prior_resource ||=
begin
key = "#{type}[#{name}]"
- prior_resource = run_context.resource_collection.lookup(key)
+ run_context.resource_collection.lookup(key)
rescue Chef::Exceptions::ResourceNotFound
nil
end
@@ -135,3 +145,7 @@ class Chef
end
end
+
+require "chef/exceptions"
+require "chef/resource"
+require "chef/log"
diff --git a/lib/chef/resource_collection.rb b/lib/chef/resource_collection.rb
index 4fd6fcad24..9bf79da7fc 100644
--- a/lib/chef/resource_collection.rb
+++ b/lib/chef/resource_collection.rb
@@ -17,11 +17,11 @@
# limitations under the License.
#
-require 'chef/resource_collection/resource_set'
-require 'chef/resource_collection/resource_list'
-require 'chef/resource_collection/resource_collection_serialization'
-require 'chef/log'
-require 'forwardable'
+require "chef/resource_collection/resource_set"
+require "chef/resource_collection/resource_list"
+require "chef/resource_collection/resource_collection_serialization"
+require "chef/log"
+require "forwardable"
##
# ResourceCollection currently handles two tasks:
diff --git a/lib/chef/resource_collection/resource_collection_serialization.rb b/lib/chef/resource_collection/resource_collection_serialization.rb
index 3651fb2a2a..ec3031e2fa 100644
--- a/lib/chef/resource_collection/resource_collection_serialization.rb
+++ b/lib/chef/resource_collection/resource_collection_serialization.rb
@@ -25,8 +25,8 @@ class Chef
instance_vars[iv] = self.instance_variable_get(iv)
end
{
- 'json_class' => self.class.name,
- 'instance_vars' => instance_vars
+ "json_class" => self.class.name,
+ "instance_vars" => instance_vars,
}
end
diff --git a/lib/chef/resource_collection/resource_list.rb b/lib/chef/resource_collection/resource_list.rb
index a26bd347aa..a3ff276d6c 100644
--- a/lib/chef/resource_collection/resource_list.rb
+++ b/lib/chef/resource_collection/resource_list.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/resource_collection/stepable_iterator'
-require 'chef/resource_collection/resource_collection_serialization'
-require 'forwardable'
+require "chef/resource"
+require "chef/resource_collection/stepable_iterator"
+require "chef/resource_collection/resource_collection_serialization"
+require "forwardable"
# This class keeps the list of all known Resources in the order they are to be executed in. It also keeps a pointer
# to the most recently executed resource so we can add resources-to-execute after this point.
diff --git a/lib/chef/resource_collection/resource_set.rb b/lib/chef/resource_collection/resource_set.rb
index 1b39298cb4..4bf78d6f59 100644
--- a/lib/chef/resource_collection/resource_set.rb
+++ b/lib/chef/resource_collection/resource_set.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/resource'
-require 'chef/resource_collection/resource_collection_serialization'
+require "chef/resource"
+require "chef/resource_collection/resource_collection_serialization"
class Chef
class ResourceCollection
diff --git a/lib/chef/resource_definition.rb b/lib/chef/resource_definition.rb
index 9d6844129c..1d76d99c4f 100644
--- a/lib/chef/resource_definition.rb
+++ b/lib/chef/resource_definition.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/mixin/from_file'
-require 'chef/mixin/params_validate'
+require "chef/mixin/from_file"
+require "chef/mixin/params_validate"
class Chef
class ResourceDefinition
@@ -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
@@ -61,7 +62,7 @@ class Chef
end
def to_s
- "#{name.to_s}"
+ "#{name}"
end
end
end
diff --git a/lib/chef/resource_definition_list.rb b/lib/chef/resource_definition_list.rb
index 55014090d4..be13dc1263 100644
--- a/lib/chef/resource_definition_list.rb
+++ b/lib/chef/resource_definition_list.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/mixin/from_file'
-require 'chef/resource_definition'
+require "chef/mixin/from_file"
+require "chef/resource_definition"
class Chef
class ResourceDefinitionList
diff --git a/lib/chef/resource_reporter.rb b/lib/chef/resource_reporter.rb
index 1816fc857d..69d0a288ac 100644
--- a/lib/chef/resource_reporter.rb
+++ b/lib/chef/resource_reporter.rb
@@ -19,9 +19,9 @@
# limitations under the License.
#
-require 'uri'
-require 'securerandom'
-require 'chef/event_dispatch/base'
+require "uri"
+require "securerandom"
+require "chef/event_dispatch/base"
class Chef
class ResourceReporter < EventDispatch::Base
@@ -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
@@ -104,7 +97,7 @@ class Chef
attr_reader :run_id
attr_reader :error_descriptions
- PROTOCOL_VERSION = '0.1.0'
+ PROTOCOL_VERSION = "0.1.0"
def initialize(rest_client)
if Chef::Config[:enable_reporting] && !Chef::Config[:why_run]
@@ -119,6 +112,7 @@ class Chef
@exception = nil
@rest_client = rest_client
@error_descriptions = {}
+ @expanded_run_list = {}
end
def run_started(run_status)
@@ -127,7 +121,7 @@ class Chef
if reporting_enabled?
begin
resource_history_url = "reports/nodes/#{node_name}/runs"
- server_response = @rest_client.post_rest(resource_history_url, {:action => :start, :run_id => run_id,
+ server_response = @rest_client.post(resource_history_url, {:action => :start, :run_id => run_id,
:start_time => start_time.to_s}, headers)
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
handle_error_starting_run(e, resource_history_url)
@@ -220,10 +214,14 @@ class Chef
# If we failed before we received the run_started callback, there's not much we can do
# in terms of reporting
if @run_status
- post_reporting_data
+ post_reporting_data
end
end
+ def run_list_expanded(run_list_expansion)
+ @expanded_run_list = run_list_expansion
+ end
+
def post_reporting_data
if reporting_enabled?
run_data = prepare_run_data
@@ -232,10 +230,9 @@ class Chef
Chef::Log.debug run_data.inspect
compressed_data = encode_gzip(Chef::JSONCompat.to_json(run_data))
Chef::Log.debug("Sending compressed run data...")
- # Since we're posting compressed data we can not directly call post_rest which expects JSON
- reporting_url = @rest_client.create_url(resource_history_url)
+ # Since we're posting compressed data we can not directly call post which expects JSON
begin
- @rest_client.raw_http_request(:POST, reporting_url, headers({'Content-Encoding' => 'gzip'}), compressed_data)
+ @rest_client.raw_request(:POST, resource_history_url, headers({"Content-Encoding" => "gzip"}), compressed_data)
rescue StandardError => e
if e.respond_to? :response
Chef::FileCache.store("failed-reporting-data.json", Chef::JSONCompat.to_json_pretty(run_data), 0640)
@@ -250,7 +247,7 @@ class Chef
end
def headers(additional_headers = {})
- options = {'X-Ops-Reporting-Protocol-Version' => PROTOCOL_VERSION}
+ options = {"X-Ops-Reporting-Protocol-Version" => PROTOCOL_VERSION}
options.merge(additional_headers)
end
@@ -278,6 +275,7 @@ class Chef
run_data["data"] = {}
run_data["start_time"] = start_time.to_s
run_data["end_time"] = end_time.to_s
+ run_data["expanded_run_list"] = Chef::JSONCompat.to_json(@expanded_run_list)
if exception
exception_data = {}
diff --git a/lib/chef/resource_resolver.rb b/lib/chef/resource_resolver.rb
new file mode 100644
index 0000000000..d3440439a9
--- /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 40b12a7c5f..dc1b195d25 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -1,6 +1,6 @@
#
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2010 Opscode, Inc.
+# Copyright:: Copyright (c) 2010-2015 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,74 +16,71 @@
# limitations under the License.
#
-require 'chef/resource/apt_package'
-require 'chef/resource/bash'
-require 'chef/resource/batch'
-require 'chef/resource/breakpoint'
-require 'chef/resource/cookbook_file'
-require 'chef/resource/chef_gem'
-require 'chef/resource/cron'
-require 'chef/resource/csh'
-require 'chef/resource/deploy'
-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'
-require 'chef/resource/execute'
-require 'chef/resource/file'
-require 'chef/resource/freebsd_package'
-require 'chef/resource/ips_package'
-require 'chef/resource/gem_package'
-require 'chef/resource/git'
-require 'chef/resource/group'
-require 'chef/resource/http_request'
-require 'chef/resource/homebrew_package'
-require 'chef/resource/ifconfig'
-require 'chef/resource/link'
-require 'chef/resource/log'
-require 'chef/resource/macports_package'
-require 'chef/resource/mdadm'
-require 'chef/resource/mount'
-require 'chef/resource/ohai'
-require 'chef/resource/openbsd_package'
-require 'chef/resource/package'
-require 'chef/resource/pacman_package'
-require 'chef/resource/paludis_package'
-require 'chef/resource/perl'
-require 'chef/resource/portage_package'
-require 'chef/resource/powershell_script'
-require 'chef/resource/python'
-require 'chef/resource/reboot'
-require 'chef/resource/registry_key'
-require 'chef/resource/remote_directory'
-require 'chef/resource/remote_file'
-require 'chef/resource/rpm_package'
-require 'chef/resource/solaris_package'
-require 'chef/resource/route'
-require 'chef/resource/ruby'
-require 'chef/resource/ruby_block'
-require 'chef/resource/scm'
-require 'chef/resource/script'
-require 'chef/resource/service'
-require 'chef/resource/windows_service'
-require 'chef/resource/subversion'
-require 'chef/resource/smartos_package'
-require 'chef/resource/template'
-require 'chef/resource/timestamped_deploy'
-require 'chef/resource/user'
-require 'chef/resource/whyrun_safe_ruby_block'
-require 'chef/resource/windows_package'
-require 'chef/resource/yum_package'
-require 'chef/resource/lwrp_base'
-require 'chef/resource/bff_package'
-
-begin
- # Optional resources chef_node, chef_client, machine, machine_image, etc.
- require 'cheffish'
- require 'chef/provisioning'
-rescue LoadError
-end
+require "chef/resource/apt_package"
+require "chef/resource/bash"
+require "chef/resource/batch"
+require "chef/resource/breakpoint"
+require "chef/resource/cookbook_file"
+require "chef/resource/chef_gem"
+require "chef/resource/chocolatey_package"
+require "chef/resource/cron"
+require "chef/resource/csh"
+require "chef/resource/deploy"
+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"
+require "chef/resource/execute"
+require "chef/resource/file"
+require "chef/resource/freebsd_package"
+require "chef/resource/ips_package"
+require "chef/resource/gem_package"
+require "chef/resource/git"
+require "chef/resource/group"
+require "chef/resource/http_request"
+require "chef/resource/homebrew_package"
+require "chef/resource/ifconfig"
+require "chef/resource/ksh"
+require "chef/resource/link"
+require "chef/resource/log"
+require "chef/resource/macports_package"
+require "chef/resource/mdadm"
+require "chef/resource/mount"
+require "chef/resource/ohai"
+require "chef/resource/openbsd_package"
+require "chef/resource/package"
+require "chef/resource/pacman_package"
+require "chef/resource/paludis_package"
+require "chef/resource/perl"
+require "chef/resource/portage_package"
+require "chef/resource/powershell_script"
+require "chef/resource/osx_profile"
+require "chef/resource/python"
+require "chef/resource/reboot"
+require "chef/resource/registry_key"
+require "chef/resource/remote_directory"
+require "chef/resource/remote_file"
+require "chef/resource/rpm_package"
+require "chef/resource/solaris_package"
+require "chef/resource/route"
+require "chef/resource/ruby"
+require "chef/resource/ruby_block"
+require "chef/resource/scm"
+require "chef/resource/script"
+require "chef/resource/service"
+require "chef/resource/windows_service"
+require "chef/resource/subversion"
+require "chef/resource/smartos_package"
+require "chef/resource/template"
+require "chef/resource/timestamped_deploy"
+require "chef/resource/user"
+require "chef/resource/whyrun_safe_ruby_block"
+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"
diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb
index f0de443058..96a563e034 100644
--- a/lib/chef/rest.rb
+++ b/lib/chef/rest.rb
@@ -20,25 +20,26 @@
# limitations under the License.
#
-require 'tempfile'
-require 'chef/http'
+require "tempfile"
+require "chef/http"
class Chef
class HTTP; end
class REST < HTTP; end
end
-require 'chef/http/authenticator'
-require 'chef/http/decompressor'
-require 'chef/http/json_input'
-require 'chef/http/json_to_model_output'
-require 'chef/http/cookie_manager'
-require 'chef/http/validate_content_length'
-require 'chef/config'
-require 'chef/exceptions'
-require 'chef/platform/query_helpers'
-require 'chef/http/remote_request_id'
+require "chef/http/authenticator"
+require "chef/http/decompressor"
+require "chef/http/json_input"
+require "chef/http/json_to_model_output"
+require "chef/http/cookie_manager"
+require "chef/http/validate_content_length"
+require "chef/config"
+require "chef/exceptions"
+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)
@@ -161,7 +166,7 @@ class Chef
def retriable_http_request(method, url, req_body, headers)
rest_request = Chef::HTTP::HTTPRequest.new(method, url, req_body, headers)
- Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
+ Chef::Log.debug("Sending HTTP request via #{method} to #{url.host}:#{url.port}#{rest_request.path}")
retrying_http_errors(url) do
yield rest_request
@@ -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/role.rb b/lib/chef/role.rb
index c085d1d714..fa76129af2 100644
--- a/lib/chef/role.rb
+++ b/lib/chef/role.rb
@@ -18,13 +18,14 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/mixin/params_validate'
-require 'chef/mixin/from_file'
-require 'chef/run_list'
-require 'chef/mash'
-require 'chef/json_compat'
-require 'chef/search/query'
+require "chef/config"
+require "chef/mixin/params_validate"
+require "chef/mixin/from_file"
+require "chef/run_list"
+require "chef/mash"
+require "chef/json_compat"
+require "chef/server_api"
+require "chef/search/query"
class Chef
class Role
@@ -36,8 +37,8 @@ class Chef
# Create a new Chef::Role object.
def initialize(chef_server_rest: nil)
- @name = ''
- @description = ''
+ @name = ""
+ @description = ""
@default_attributes = Mash.new
@override_attributes = Mash.new
@env_run_lists = {"_default" => Chef::RunList.new}
@@ -45,18 +46,18 @@ class Chef
end
def chef_server_rest
- @chef_server_rest ||= Chef::REST.new(Chef::Config[:chef_server_url])
+ @chef_server_rest ||= Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def self.chef_server_rest
- Chef::REST.new(Chef::Config[:chef_server_url])
+ Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
def name(arg=nil)
set_or_return(
:name,
arg,
- :regex => /^[\-[:alnum:]_]+$/
+ :regex => /^[\-[:alnum:]_]+$/,
)
end
@@ -64,7 +65,7 @@ class Chef
set_or_return(
:description,
arg,
- :kind_of => String
+ :kind_of => String,
)
end
@@ -87,7 +88,7 @@ class Chef
end
def active_run_list_for(environment)
- @env_run_lists.has_key?(environment) ? environment : '_default'
+ @env_run_lists.has_key?(environment) ? environment : "_default"
end
# Per environment run lists
@@ -120,7 +121,7 @@ class Chef
set_or_return(
:default_attributes,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
@@ -128,7 +129,7 @@ class Chef
set_or_return(
:override_attributes,
arg,
- :kind_of => Hash
+ :kind_of => Hash,
)
end
@@ -138,7 +139,7 @@ class Chef
result = {
"name" => @name,
"description" => @description,
- 'json_class' => self.class.name,
+ "json_class" => self.class.name,
"default_attributes" => @default_attributes,
"override_attributes" => @override_attributes,
"chef_type" => "role",
@@ -149,7 +150,7 @@ class Chef
"env_run_lists" => env_run_lists_without_default.inject({}) do |accumulator, (k, v)|
accumulator[k] = v.map { |x| x.to_s }
accumulator
- end
+ end,
}
result
end
@@ -170,6 +171,10 @@ class Chef
# Create a Chef::Role from JSON
def self.json_create(o)
+ from_hash(o)
+ end
+
+ def self.from_hash(o)
role = new
role.name(o["name"])
role.description(o["description"])
@@ -199,42 +204,42 @@ class Chef
end
response
else
- chef_server_rest.get_rest("roles")
+ chef_server_rest.get("roles")
end
end
# Load a role by name from the API
def self.load(name)
- chef_server_rest.get_rest("roles/#{name}")
+ from_hash(chef_server_rest.get("roles/#{name}"))
end
def environment(env_name)
- chef_server_rest.get_rest("roles/#{@name}/environments/#{env_name}")
+ chef_server_rest.get("roles/#{@name}/environments/#{env_name}")
end
def environments
- chef_server_rest.get_rest("roles/#{@name}/environments")
+ chef_server_rest.get("roles/#{@name}/environments")
end
# Remove this role via the REST API
def destroy
- chef_server_rest.delete_rest("roles/#{@name}")
+ chef_server_rest.delete("roles/#{@name}")
end
# Save this role via the REST API
def save
begin
- chef_server_rest.put_rest("roles/#{@name}", self)
+ chef_server_rest.put("roles/#{@name}", self)
rescue Net::HTTPServerException => e
raise e unless e.response.code == "404"
- chef_server_rest.post_rest("roles", self)
+ chef_server_rest.post("roles", self)
end
self
end
# Create the role via the REST API
def create
- chef_server_rest.post_rest("roles", self)
+ chef_server_rest.post("roles", self)
self
end
@@ -258,7 +263,8 @@ class Chef
if js_path && File.exists?(js_path)
# from_json returns object.class => json_class in the JSON.
- return Chef::JSONCompat.from_json(IO.read(js_path))
+ hsh = Chef::JSONCompat.parse(IO.read(js_path))
+ return from_hash(hsh)
elsif rb_path && File.exists?(rb_path)
role = Chef::Role.new
role.name(name)
diff --git a/lib/chef/run_context.rb b/lib/chef/run_context.rb
index 4f0215bfd4..6d3f53c6d5 100644
--- a/lib/chef/run_context.rb
+++ b/lib/chef/run_context.rb
@@ -17,125 +17,271 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/resource_collection'
-require 'chef/cookbook_version'
-require 'chef/node'
-require 'chef/role'
-require 'chef/log'
-require 'chef/recipe'
-require 'chef/run_context/cookbook_compiler'
-require 'chef/event_dispatch/events_output_stream'
+require "chef/resource_collection"
+require "chef/cookbook_version"
+require "chef/node"
+require "chef/role"
+require "chef/log"
+require "chef/recipe"
+require "chef/run_context/cookbook_compiler"
+require "chef/event_dispatch/events_output_stream"
+require "forwardable"
class Chef
# == Chef::RunContext
# Value object that loads and tracks the context of a Chef run
class RunContext
+ #
+ # Global state
+ #
- # Chef::Node object for this run
+ #
+ # The node for this run
+ #
+ # @return [Chef::Node]
+ #
attr_reader :node
- # Chef::CookbookCollection for this run
+ #
+ # The set of cookbooks involved in this run
+ #
+ # @return [Chef::CookbookCollection]
+ #
attr_reader :cookbook_collection
+ #
# Resource Definitions for this run. Populated when the files in
# +definitions/+ are evaluated (this is triggered by #load).
+ #
+ # @return [Array[Chef::ResourceDefinition]]
+ #
attr_reader :definitions
- ###
- # These need to be settable so deploy can run a resource_collection
- # independent of any cookbooks via +recipe_eval+
+ #
+ # Event dispatcher for this run.
+ #
+ # @return [Chef::EventDispatch::Dispatcher]
+ #
+ attr_reader :events
+
+ #
+ # Hash of factoids for a reboot request.
+ #
+ # @return [Hash]
+ #
+ attr_accessor :reboot_info
- # The Chef::ResourceCollection for this run. Populated by evaluating
- # recipes, which is triggered by #load. (See also: CookbookCompiler)
- attr_accessor :resource_collection
+ #
+ # Scoped state
+ #
+ #
+ # The parent run context.
+ #
+ # @return [Chef::RunContext] The parent run context, or `nil` if this is the
+ # root context.
+ #
+ attr_reader :parent_run_context
+
+ #
+ # The collection of resources intended to be converged (and able to be
+ # notified).
+ #
+ # @return [Chef::ResourceCollection]
+ #
+ # @see CookbookCompiler
+ #
+ attr_reader :resource_collection
+
+ #
# The list of control groups to execute during the audit phase
- attr_accessor :audits
+ #
+ attr_reader :audits
+
+ #
+ # Notification handling
+ #
+ #
+ # A Hash containing the before notifications triggered by resources
+ # during the converge phase of the chef run.
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :before_notification_collection
+
+ #
# A Hash containing the immediate notifications triggered by resources
# during the converge phase of the chef run.
- attr_accessor :immediate_notification_collection
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :immediate_notification_collection
+ #
# A Hash containing the delayed (end of run) notifications triggered by
# resources during the converge phase of the chef run.
- attr_accessor :delayed_notification_collection
-
- # Event dispatcher for this run.
- attr_reader :events
-
- # Hash of factoids for a reboot request.
- attr_reader :reboot_info
+ #
+ # @return [Hash[String, Array[Chef::Resource::Notification]]] A hash from
+ # <notifying resource name> => <list of notifications it sent>
+ #
+ attr_reader :delayed_notification_collection
# Creates a new Chef::RunContext object and populates its fields. This object gets
# used by the Chef Server to generate a fully compiled recipe list for a node.
#
- # === Returns
- # object<Chef::RunContext>:: Duh. :)
+ # @param node [Chef::Node] The node to run against.
+ # @param cookbook_collection [Chef::CookbookCollection] The cookbooks
+ # involved in this run.
+ # @param events [EventDispatch::Dispatcher] The event dispatcher for this
+ # run.
+ #
def initialize(node, cookbook_collection, events)
@node = node
@cookbook_collection = cookbook_collection
- @resource_collection = Chef::ResourceCollection.new
- @audits = {}
- @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
- @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
- @definitions = Hash.new
- @loaded_recipes = {}
- @loaded_attributes = {}
@events = events
- @reboot_info = {}
- @node.run_context = self
+ node.run_context = self
+ node.set_cookbook_attribute
+
+ @definitions = Hash.new
+ @loaded_recipes_hash = {}
+ @loaded_attributes_hash = {}
+ @reboot_info = {}
@cookbook_compiler = nil
+
+ initialize_child_state
end
- # Triggers the compile phase of the chef run. Implemented by
- # Chef::RunContext::CookbookCompiler
+ #
+ # Triggers the compile phase of the chef run.
+ #
+ # @param run_list_expansion [Chef::RunList::RunListExpansion] The run list.
+ # @see Chef::RunContext::CookbookCompiler
+ #
def load(run_list_expansion)
@cookbook_compiler = CookbookCompiler.new(self, run_list_expansion, events)
- @cookbook_compiler.compile
+ cookbook_compiler.compile
end
- # Adds an immediate notification to the
- # +immediate_notification_collection+. The notification should be a
- # Chef::Resource::Notification or duck type.
+ #
+ # Initialize state that applies to both Chef::RunContext and Chef::ChildRunContext
+ #
+ def initialize_child_state
+ @audits = {}
+ @resource_collection = Chef::ResourceCollection.new
+ @before_notification_collection = Hash.new {|h,k| h[k] = []}
+ @immediate_notification_collection = Hash.new {|h,k| h[k] = []}
+ @delayed_notification_collection = Hash.new {|h,k| h[k] = []}
+ end
+
+ #
+ # Adds an before notification to the +before_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
+ def notifies_before(notification)
+ nr = notification.notifying_resource
+ if nr.instance_of?(Chef::Resource)
+ before_notification_collection[nr.name] << notification
+ else
+ before_notification_collection[nr.declared_key] << notification
+ end
+ end
+
+ #
+ # Adds an immediate notification to the +immediate_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_immediately(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @immediate_notification_collection[nr.name] << notification
+ immediate_notification_collection[nr.name] << notification
else
- @immediate_notification_collection[nr.declared_key] << notification
+ immediate_notification_collection[nr.declared_key] << notification
end
end
- # Adds a delayed notification to the +delayed_notification_collection+. The
- # notification should be a Chef::Resource::Notification or duck type.
+ #
+ # Adds a delayed notification to the +delayed_notification_collection+.
+ #
+ # @param [Chef::Resource::Notification] The notification to add.
+ #
def notifies_delayed(notification)
nr = notification.notifying_resource
if nr.instance_of?(Chef::Resource)
- @delayed_notification_collection[nr.name] << notification
+ delayed_notification_collection[nr.name] << notification
else
- @delayed_notification_collection[nr.declared_key] << notification
+ delayed_notification_collection[nr.declared_key] << notification
end
end
+ #
+ # Get the list of before notifications sent by the given resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
+ def before_notifications(resource)
+ if resource.instance_of?(Chef::Resource)
+ return before_notification_collection[resource.name]
+ else
+ return before_notification_collection[resource.declared_key]
+ end
+ end
+
+ #
+ # Get the list of immediate notifications sent by the given resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def immediate_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @immediate_notification_collection[resource.name]
+ return immediate_notification_collection[resource.name]
else
- return @immediate_notification_collection[resource.declared_key]
+ return immediate_notification_collection[resource.declared_key]
end
end
+ #
+ # Get the list of delayed (end of run) notifications sent by the given
+ # resource.
+ #
+ # TODO seriously, this is actually wrong. resource.name is not unique,
+ # you need the type as well.
+ #
+ # @return [Array[Notification]]
+ #
def delayed_notifications(resource)
if resource.instance_of?(Chef::Resource)
- return @delayed_notification_collection[resource.name]
+ return delayed_notification_collection[resource.name]
else
- return @delayed_notification_collection[resource.declared_key]
+ return delayed_notification_collection[resource.declared_key]
end
end
+ #
+ # Cookbook and recipe loading
+ #
+
+ #
# Evaluates the recipes +recipe_names+. Used by DSL::IncludeRecipe
+ #
+ # @param recipe_names [Array[String]] The list of recipe names (e.g.
+ # 'my_cookbook' or 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @see DSL::IncludeRecipe#include_recipe
+ #
def include_recipe(*recipe_names, current_cookbook: nil)
result_recipes = Array.new
recipe_names.flatten.each do |recipe_name|
@@ -146,9 +292,23 @@ class Chef
result_recipes
end
+ #
# Evaluates the recipe +recipe_name+. Used by DSL::IncludeRecipe
+ #
+ # TODO I am sort of confused why we have both this and include_recipe ...
+ # I don't see anything different beyond accepting and returning an
+ # array of recipes.
+ #
+ # @param recipe_names [Array[String]] The recipe name (e.g 'my_cookbook' or
+ # 'my_cookbook::my_resource').
+ # @param current_cookbook The cookbook we are currently running in.
+ #
+ # @return A truthy value if the load occurred; `false` if already loaded.
+ #
+ # @see DSL::IncludeRecipe#load_recipe
+ #
def load_recipe(recipe_name, current_cookbook: nil)
- Chef::Log.debug("Loading Recipe #{recipe_name} via include_recipe")
+ Chef::Log.debug("Loading recipe #{recipe_name} via include_recipe")
cookbook_name, recipe_short_name = Chef::Recipe.parse_recipe_name(recipe_name, current_cookbook: current_cookbook)
@@ -174,19 +334,39 @@ ERROR_MESSAGE
end
end
+ #
+ # Load the given recipe from a filename.
+ #
+ # @param recipe_file [String] The recipe filename.
+ #
+ # @return [Chef::Recipe] The loaded recipe.
+ #
+ # @raise [Chef::Exceptions::RecipeNotFound] If the file does not exist.
+ #
def load_recipe_file(recipe_file)
if !File.exist?(recipe_file)
raise Chef::Exceptions::RecipeNotFound, "could not find recipe file #{recipe_file}"
end
- Chef::Log.debug("Loading Recipe File #{recipe_file}")
- recipe = Chef::Recipe.new('@recipe_files', recipe_file, self)
+ Chef::Log.debug("Loading recipe file #{recipe_file}")
+ recipe = Chef::Recipe.new("@recipe_files", recipe_file, self)
recipe.from_file(recipe_file)
recipe
end
- # Looks up an attribute file given the +cookbook_name+ and
- # +attr_file_name+. Used by DSL::IncludeAttribute
+ #
+ # Look up an attribute filename.
+ #
+ # @param cookbook_name [String] The cookbook name of the attribute file.
+ # @param attr_file_name [String] The attribute file's name (not path).
+ #
+ # @return [String] The filename.
+ #
+ # @see DSL::IncludeAttribute#include_attribute
+ #
+ # @raise [Chef::Exceptions::CookbookNotFound] If the cookbook could not be found.
+ # @raise [Chef::Exceptions::AttributeNotFound] If the attribute file could not be found.
+ #
def resolve_attribute(cookbook_name, attr_file_name)
cookbook = cookbook_collection[cookbook_name]
raise Chef::Exceptions::CookbookNotFound, "could not find cookbook #{cookbook_name} while loading attribute #{name}" unless cookbook
@@ -197,76 +377,152 @@ ERROR_MESSAGE
attribute_filename
end
- # An Array of all recipes that have been loaded. This is stored internally
- # as a Hash, so ordering is predictable.
#
- # Recipe names are given in fully qualified form, e.g., the recipe "nginx"
- # will be given as "nginx::default"
+ # A list of all recipes that have been loaded.
+ #
+ # This is stored internally as a Hash, so ordering is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of recipes in fully qualified form, e.g.
+ # the recipe "nginx" will be given as "nginx::default".
+ #
+ # @see #loaded_recipe? To determine if a particular recipe has been loaded.
#
- # To determine if a particular recipe has been loaded, use #loaded_recipe?
def loaded_recipes
- @loaded_recipes.keys
+ loaded_recipes_hash.keys
end
- # An Array of all attributes files that have been loaded. Stored internally
- # using a Hash, so order is predictable.
#
- # Attribute file names are given in fully qualified form, e.g.,
- # "nginx::default" instead of "nginx".
+ # A list of all attributes files that have been loaded.
+ #
+ # Stored internally using a Hash, so order is predictable.
+ #
+ # TODO is the above statement true in a 1.9+ ruby world? Is it relevant?
+ #
+ # @return [Array[String]] A list of attribute file names in fully qualified
+ # form, e.g. the "nginx" will be given as "nginx::default".
+ #
def loaded_attributes
- @loaded_attributes.keys
+ loaded_attributes_hash.keys
end
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_recipe?(cookbook, recipe)
- @loaded_recipes.has_key?("#{cookbook}::#{recipe}")
+ loaded_recipes_hash.has_key?("#{cookbook}::#{recipe}")
end
- # Returns true if +recipe+ has been loaded, false otherwise. Default recipe
- # names are expanded, so `loaded_recipe?("nginx")` and
- # `loaded_recipe?("nginx::default")` are valid and give identical results.
+ #
+ # Find out if a given recipe has been loaded.
+ #
+ # @param recipe [String] Recipe name. "nginx" and "nginx::default" yield
+ # the same results.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_recipe?(recipe)
cookbook, recipe_name = Chef::Recipe.parse_recipe_name(recipe)
loaded_fully_qualified_recipe?(cookbook, recipe_name)
end
+ #
+ # Mark a given recipe as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param recipe [String] Recipe name.
+ #
+ def loaded_recipe(cookbook, recipe)
+ loaded_recipes_hash["#{cookbook}::#{recipe}"] = true
+ end
+
+ #
+ # Find out if a given attribute file has been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
+ # @return [Boolean] `true` if the recipe has been loaded, `false` otherwise.
+ #
def loaded_fully_qualified_attribute?(cookbook, attribute_file)
- @loaded_attributes.has_key?("#{cookbook}::#{attribute_file}")
+ loaded_attributes_hash.has_key?("#{cookbook}::#{attribute_file}")
end
+ #
+ # Mark a given attribute file as having been loaded.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param attribute_file [String] Attribute file name.
+ #
def loaded_attribute(cookbook, attribute_file)
- @loaded_attributes["#{cookbook}::#{attribute_file}"] = true
+ loaded_attributes_hash["#{cookbook}::#{attribute_file}"] = true
end
##
# Cookbook File Introspection
+ #
+ # Find out if the cookbook has the given template.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param template_name [String] Template name.
+ #
+ # @return [Boolean] `true` if the template is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_template_for_node?
+ #
def has_template_in_cookbook?(cookbook, template_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_template_for_node?(node, template_name)
end
+ #
+ # Find out if the cookbook has the given file.
+ #
+ # @param cookbook [String] Cookbook name.
+ # @param cb_file_name [String] File name.
+ #
+ # @return [Boolean] `true` if the file is in the cookbook, `false`
+ # otherwise.
+ # @see Chef::CookbookVersion#has_cookbook_file_for_node?
+ #
def has_cookbook_file_in_cookbook?(cookbook, cb_file_name)
cookbook = cookbook_collection[cookbook]
cookbook.has_cookbook_file_for_node?(node, cb_file_name)
end
- # Delegates to CookbookCompiler#unreachable_cookbook?
- # Used to raise an error when attempting to load a recipe belonging to a
- # cookbook that is not in the dependency graph. See also: CHEF-4367
+ #
+ # Find out whether the given cookbook is in the cookbook dependency graph.
+ #
+ # @param cookbook_name [String] Cookbook name.
+ #
+ # @return [Boolean] `true` if the cookbook is reachable, `false` otherwise.
+ #
+ # @see Chef::CookbookCompiler#unreachable_cookbook?
def unreachable_cookbook?(cookbook_name)
- @cookbook_compiler.unreachable_cookbook?(cookbook_name)
+ cookbook_compiler.unreachable_cookbook?(cookbook_name)
end
+ #
# Open a stream object that can be printed into and will dispatch to events
#
- # == Arguments
- # options is a hash with these possible options:
- # - name: a string that identifies the stream to the user. Preferably short.
+ # @param name [String] The name of the stream.
+ # @param options [Hash] Other options for the stream.
+ #
+ # @return [EventDispatch::EventsOutputStream] The created stream.
#
- # Pass a block and the stream will be yielded to it, and close on its own
- # at the end of the block.
- def open_stream(options = {})
- stream = EventDispatch::EventsOutputStream.new(events, options)
+ # @yield If a block is passed, it will be run and the stream will be closed
+ # afterwards.
+ # @yieldparam stream [EventDispatch::EventsOutputStream] The created stream.
+ #
+ def open_stream(name: nil, **options)
+ stream = EventDispatch::EventsOutputStream.new(events, name: name, **options)
if block_given?
begin
yield stream
@@ -279,31 +535,138 @@ ERROR_MESSAGE
end
# there are options for how to handle multiple calls to these functions:
- # 1. first call always wins (never change @reboot_info once set).
- # 2. last call always wins (happily change @reboot_info whenever).
+ # 1. first call always wins (never change reboot_info once set).
+ # 2. last call always wins (happily change reboot_info whenever).
# 3. raise an exception on the first conflict.
# 4. disable reboot after this run if anyone ever calls :cancel.
# 5. raise an exception on any second call.
# 6. ?
def request_reboot(reboot_info)
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to #{reboot_info.inspect}"
+ Chef::Log::info "Changing reboot status from #{self.reboot_info.inspect} to #{reboot_info.inspect}"
@reboot_info = reboot_info
end
def cancel_reboot
- Chef::Log::info "Changing reboot status from #{@reboot_info.inspect} to {}"
+ Chef::Log::info "Changing reboot status from #{reboot_info.inspect} to {}"
@reboot_info = {}
end
def reboot_requested?
- @reboot_info.size > 0
+ reboot_info.size > 0
+ end
+
+ #
+ # Create a child RunContext.
+ #
+ def create_child
+ ChildRunContext.new(self)
end
- private
+ # @api private
+ attr_writer :resource_collection
- def loaded_recipe(cookbook, recipe)
- @loaded_recipes["#{cookbook}::#{recipe}"] = true
+ protected
+
+ attr_reader :cookbook_compiler
+ attr_reader :loaded_attributes_hash
+ attr_reader :loaded_recipes_hash
+
+ module Deprecated
+ ###
+ # These need to be settable so deploy can run a resource_collection
+ # independent of any cookbooks via +recipe_eval+
+ def audits=(value)
+ Chef.log_deprecation("Setting run_context.audits will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @audits = value
+ end
+
+ def immediate_notification_collection=(value)
+ Chef.log_deprecation("Setting run_context.immediate_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @immediate_notification_collection = value
+ end
+
+ def delayed_notification_collection=(value)
+ Chef.log_deprecation("Setting run_context.delayed_notification_collection will be removed in a future Chef. Use run_context.create_child to create a new RunContext instead.")
+ @delayed_notification_collection = value
+ end
end
+ prepend Deprecated
+
+
+ #
+ # A child run context. Delegates all root context calls to its parent.
+ #
+ # @api private
+ #
+ class ChildRunContext < RunContext
+ extend Forwardable
+ def_delegators :parent_run_context, *%w{
+ cancel_reboot
+ config
+ cookbook_collection
+ cookbook_compiler
+ definitions
+ events
+ has_cookbook_file_in_cookbook?
+ has_template_in_cookbook?
+ load
+ loaded_attribute
+ loaded_attributes
+ loaded_attributes_hash
+ loaded_fully_qualified_attribute?
+ loaded_fully_qualified_recipe?
+ loaded_recipe
+ loaded_recipe?
+ loaded_recipes
+ loaded_recipes_hash
+ node
+ open_stream
+ reboot_info
+ reboot_info=
+ reboot_requested?
+ request_reboot
+ resolve_attribute
+ unreachable_cookbook?
+ }
+
+ def initialize(parent_run_context)
+ @parent_run_context = parent_run_context
+
+ # We don't call super, because we don't bother initializing stuff we're
+ # going to delegate to the parent anyway. Just initialize things that
+ # every instance needs.
+ initialize_child_state
+ end
+ CHILD_STATE = %w{
+ audits
+ audits=
+ create_child
+ delayed_notification_collection
+ delayed_notification_collection=
+ delayed_notifications
+ immediate_notification_collection
+ immediate_notification_collection=
+ immediate_notifications
+ before_notification_collection
+ before_notifications
+ include_recipe
+ initialize_child_state
+ load_recipe
+ load_recipe_file
+ notifies_before
+ notifies_immediately
+ notifies_delayed
+ parent_run_context
+ resource_collection
+ resource_collection=
+ }.map { |x| x.to_sym }
+
+ # Verify that we didn't miss any methods
+ missing_methods = superclass.instance_methods(false) - instance_methods(false) - CHILD_STATE
+ if !missing_methods.empty?
+ raise "ERROR: not all methods of RunContext accounted for in ChildRunContext! All methods must be marked as child methods with CHILD_STATE or delegated to the parent_run_context. Missing #{missing_methods.join(", ")}."
+ end
+ end
end
end
diff --git a/lib/chef/run_context/cookbook_compiler.rb b/lib/chef/run_context/cookbook_compiler.rb
index abe5afa7ae..22dcef29df 100644
--- a/lib/chef/run_context/cookbook_compiler.rb
+++ b/lib/chef/run_context/cookbook_compiler.rb
@@ -16,12 +16,12 @@
# limitations under the License.
#
-require 'set'
-require 'chef/log'
-require 'chef/recipe'
-require 'chef/resource/lwrp_base'
-require 'chef/provider/lwrp_base'
-require 'chef/resource_definition_list'
+require "set"
+require "chef/log"
+require "chef/recipe"
+require "chef/resource/lwrp_base"
+require "chef/provider/lwrp_base"
+require "chef/resource_definition_list"
class Chef
class RunContext
diff --git a/lib/chef/run_list.rb b/lib/chef/run_list.rb
index 01e32ffc98..b04dcb6d74 100644
--- a/lib/chef/run_list.rb
+++ b/lib/chef/run_list.rb
@@ -19,10 +19,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/run_list/run_list_item'
-require 'chef/run_list/run_list_expansion'
-require 'chef/run_list/versioned_recipe_list'
-require 'chef/mixin/params_validate'
+require "chef/run_list/run_list_item"
+require "chef/run_list/run_list_expansion"
+require "chef/run_list/versioned_recipe_list"
+require "chef/mixin/params_validate"
class Chef
class RunList
@@ -138,7 +138,7 @@ class Chef
# Expands this run_list: recursively expand roles into their included
# recipes.
# Returns a RunListExpansion object.
- def expand(environment, data_source='server', expansion_opts={})
+ def expand(environment, data_source="server", expansion_opts={})
expansion = expansion_for_data_source(environment, data_source, expansion_opts)
expansion.expand
expansion
@@ -155,9 +155,9 @@ class Chef
def expansion_for_data_source(environment, data_source, opts={})
case data_source.to_s
- when 'disk'
+ when "disk"
RunListExpansionFromDisk.new(environment, @run_list_items)
- when 'server'
+ when "server"
RunListExpansionFromAPI.new(environment, @run_list_items, opts[:rest])
end
end
diff --git a/lib/chef/run_list/run_list_expansion.rb b/lib/chef/run_list/run_list_expansion.rb
index 46b45f1d9e..fa3b6730b3 100644
--- a/lib/chef/run_list/run_list_expansion.rb
+++ b/lib/chef/run_list/run_list_expansion.rb
@@ -16,12 +16,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/mash'
+require "chef/mash"
-require 'chef/mixin/deep_merge'
+require "chef/mixin/deep_merge"
-require 'chef/role'
-require 'chef/rest'
+require "chef/role"
+require "chef/server_api"
+require "chef/json_compat"
class Chef
class RunList
@@ -44,7 +45,7 @@ class Chef
attr_reader :missing_roles_with_including_role
# The data source passed to the constructor. Not used in this class.
- # In subclasses, this is a couchdb or Chef::REST object pre-configured
+ # In subclasses, this is a Chef::ServerAPI object pre-configured
# to fetch roles from their correct location.
attr_reader :source
@@ -54,6 +55,13 @@ class Chef
# * Duplicate roles are not shown.
attr_reader :run_list_trace
+ # Like run list trace but instead of saving the entries as strings it saves their objects
+ # The to_json method uses this list to construct json.
+ attr_reader :better_run_list_trace
+
+ attr_reader :all_missing_roles
+ attr_reader :role_errors
+
def initialize(environment, run_list_items, source=nil)
@environment = environment
@missing_roles_with_including_role = Array.new
@@ -68,6 +76,9 @@ class Chef
@applied_roles = {}
@run_list_trace = Hash.new {|h, key| h[key] = [] }
+ @better_run_list_trace = Hash.new {|h, key| h[key] = [] }
+ @all_missing_roles = {}
+ @role_errors = {}
end
# Did we find any errors (expanding roles)?
@@ -124,6 +135,7 @@ class Chef
def role_not_found(name, included_by)
Chef::Log.error("Role #{name} (included by '#{included_by}') is in the runlist but does not exist. Skipping expand.")
@missing_roles_with_including_role << [name, included_by]
+ @all_missing_roles[name] = true
nil
end
@@ -131,6 +143,15 @@ class Chef
@missing_roles_with_including_role.map {|item| item.first }
end
+ def to_json(*a)
+ Chef::JSONCompat.to_json(to_hash, *a)
+ end
+
+ def to_hash
+ seen_items = {:recipe => {}, :role => {}}
+ {:id => @environment, :run_list => convert_run_list_trace("top level", seen_items)}
+ end
+
private
# these methods modifies internal state based on arguments, so hide it.
@@ -140,8 +161,10 @@ class Chef
end
def expand_run_list_items(items, included_by="top level")
+
if entry = items.shift
@run_list_trace[included_by.to_s] << entry.to_s
+ @better_run_list_trace[included_by.to_s] << entry
case entry.type
when :recipe
@@ -156,8 +179,26 @@ class Chef
end
end
+ # Recursive helper to decode the non-nested hash form back into a tree
+ def convert_run_list_trace(base, seen_items)
+ @better_run_list_trace[base].map do |item|
+ skipped = seen_items[item.type][item.name]
+ seen_items[item.type][item.name] = true
+ case item.type
+ when :recipe
+ {:type => "recipe", :name => item.name, :version => item.version, :skipped => !!skipped}
+ when :role
+ error = @role_errors[item.name]
+ missing = @all_missing_roles[item.name]
+ {:type => :role, :name => item.name, :children => (missing || error || skipped) ? [] : convert_run_list_trace(item.to_s, seen_items),
+ :missing => missing, :error => error, :skipped => skipped}
+ end
+ end
+ end
+
end
+
# Expand a run list from disk. Suitable for chef-solo
class RunListExpansionFromDisk < RunListExpansion
@@ -173,19 +214,25 @@ class Chef
class RunListExpansionFromAPI < RunListExpansion
def rest
- @rest ||= (source || Chef::REST.new(Chef::Config[:chef_server_url]))
+ @rest ||= (source || Chef::ServerAPI.new(Chef::Config[:chef_server_url]))
end
def fetch_role(name, included_by)
- rest.get_rest("roles/#{name}")
+ Chef::Role.from_hash(rest.get("roles/#{name}"))
rescue Net::HTTPServerException => e
if e.message == '404 "Not Found"'
role_not_found(name, included_by)
else
raise
end
+ rescue Exception => e
+ @role_errors[name] = e.to_s
+ raise
end
+
end
end
end
+
+
diff --git a/lib/chef/run_list/run_list_item.rb b/lib/chef/run_list/run_list_item.rb
index 43bb239754..c6ab17bdeb 100644
--- a/lib/chef/run_list/run_list_item.rb
+++ b/lib/chef/run_list/run_list_item.rb
@@ -31,10 +31,10 @@ class Chef
case item
when Hash
assert_hash_is_valid_run_list_item!(item)
- @type = (item['type'] || item[:type]).to_sym
- @name = item['name'] || item[:name]
- if (item.has_key?('version') || item.has_key?(:version))
- @version = item['version'] || item[:version]
+ @type = (item["type"] || item[:type]).to_sym
+ @name = item["name"] || item[:name]
+ if (item.has_key?("version") || item.has_key?(:version))
+ @version = item["version"] || item[:version]
end
when String
if match = QUALIFIED_RECIPE.match(item)
@@ -89,7 +89,7 @@ class Chef
end
def assert_hash_is_valid_run_list_item!(item)
- unless (item.key?('type')|| item.key?(:type)) && (item.key?('name') || item.key?(:name))
+ unless (item.key?("type")|| item.key?(:type)) && (item.key?("name") || item.key?(:name))
raise ArgumentError, "Initializing a #{self.class} from a hash requires that it have a 'type' and 'name' key"
end
end
diff --git a/lib/chef/run_list/versioned_recipe_list.rb b/lib/chef/run_list/versioned_recipe_list.rb
index 0eefded964..b6a2a8402f 100644
--- a/lib/chef/run_list/versioned_recipe_list.rb
+++ b/lib/chef/run_list/versioned_recipe_list.rb
@@ -1,7 +1,7 @@
#
# Author:: Stephen Delano (<stephen@opscode.com>)
# Author:: Seth Falcon (<seth@opscode.com>)
-# Copyright:: Copyright 2010 Opscode, Inc.
+# Copyright:: Copyright 2010-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,8 +15,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/version_class'
-require 'chef/version_constraint'
+require "chef/version_class"
+require "chef/version_constraint"
# Why does this class exist?
# Why did we not just modify RunList/RunListItem?
@@ -63,6 +63,40 @@ class Chef
end
end
end
+
+ # Get an array of strings of the fully-qualified recipe names (with ::default appended) and
+ # with the versions in "NAME@VERSION" format.
+ #
+ # @return [Array] Array of strings with fully-qualified recipe names
+ def with_fully_qualified_names_and_version_constraints
+ self.map do |recipe_name|
+ qualified_recipe = if recipe_name.include?("::")
+ recipe_name
+ else
+ "#{recipe_name}::default"
+ end
+
+ version = @versions[recipe_name]
+ qualified_recipe = "#{qualified_recipe}@#{version}" if version
+
+ qualified_recipe
+ end
+ end
+
+ # Get an array of strings of both fully-qualified and unexpanded recipe names
+ # in response to chef/chef#3767
+ # Chef-13 will revert to the behaviour of just including the fully-qualified name
+ #
+ # @return [Array] Array of strings with fully-qualified and unexpanded recipe names
+ def with_duplicate_names
+ self.map do |recipe_name|
+ if recipe_name.include?("::")
+ recipe_name
+ else
+ [recipe_name, "#{recipe_name}::default"]
+ end
+ end.flatten
+ end
end
end
end
diff --git a/lib/chef/run_lock.rb b/lib/chef/run_lock.rb
index cefe637db6..42b99440bc 100644
--- a/lib/chef/run_lock.rb
+++ b/lib/chef/run_lock.rb
@@ -15,14 +15,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/mixin/create_path'
-require 'fcntl'
+require "chef/mixin/create_path"
+require "fcntl"
if Chef::Platform.windows?
- require 'chef/win32/mutex'
+ require "chef/win32/mutex"
end
-require 'chef/config'
-require 'chef/exceptions'
-require 'timeout'
+require "chef/config"
+require "chef/exceptions"
+require "timeout"
class Chef
@@ -87,27 +87,8 @@ class Chef
# Either acquire() or test() methods should be called in order to
# get the ownership of run_lock.
def test
- # ensure the runlock_file path exists
- create_path(File.dirname(runlock_file))
- @runlock = File.open(runlock_file,'a+')
-
- if Chef::Platform.windows?
- acquire_win32_mutex
- else
- # If we support FD_CLOEXEC, then use it.
- # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not
- # ruby-1.8.7/1.9.3
- if Fcntl.const_defined?('F_SETFD') && Fcntl.const_defined?('FD_CLOEXEC')
- runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC)
- end
- # Flock will return 0 if it can acquire the lock otherwise it
- # will return false
- if runlock.flock(File::LOCK_NB|File::LOCK_EX) == 0
- true
- else
- false
- end
- end
+ create_lock
+ acquire_lock
end
#
@@ -147,6 +128,34 @@ class Chef
end
end
+ # @api private solely for race condition tests
+ def create_lock
+ # ensure the runlock_file path exists
+ create_path(File.dirname(runlock_file))
+ @runlock = File.open(runlock_file,"a+")
+ end
+
+ # @api private solely for race condition tests
+ def acquire_lock
+ if Chef::Platform.windows?
+ acquire_win32_mutex
+ else
+ # If we support FD_CLOEXEC, then use it.
+ # NB: ruby-2.0.0-p195 sets FD_CLOEXEC by default, but not
+ # ruby-1.8.7/1.9.3
+ if Fcntl.const_defined?("F_SETFD") && Fcntl.const_defined?("FD_CLOEXEC")
+ runlock.fcntl(Fcntl::F_SETFD, runlock.fcntl(Fcntl::F_GETFD, 0) | Fcntl::FD_CLOEXEC)
+ end
+ # Flock will return 0 if it can acquire the lock otherwise it
+ # will return false
+ if runlock.flock(File::LOCK_NB|File::LOCK_EX) == 0
+ true
+ else
+ false
+ end
+ end
+ end
+
private
def reset
diff --git a/lib/chef/run_status.rb b/lib/chef/run_status.rb
index 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/runner.rb b/lib/chef/runner.rb
index 6125fe59e1..04252b1087 100644
--- a/lib/chef/runner.rb
+++ b/lib/chef/runner.rb
@@ -18,10 +18,10 @@
# limitations under the License.
#
-require 'chef/exceptions'
-require 'chef/mixin/params_validate'
-require 'chef/node'
-require 'chef/resource_collection'
+require "chef/exceptions"
+require "chef/mixin/params_validate"
+require "chef/node"
+require "chef/resource_collection"
class Chef
# == Chef::Runner
@@ -46,6 +46,31 @@ class Chef
# Determine the appropriate provider for the given resource, then
# execute it.
def run_action(resource, action, notification_type=nil, notifying_resource=nil)
+
+ # If there are any before notifications, why-run the resource
+ # and notify anyone who needs notifying
+ # TODO cheffish has a bug where it passes itself instead of the run_context to us, so doesn't have before_notifications. Fix there, update dependency requirement, and remove this if statement.
+ before_notifications = run_context.before_notifications(resource) if run_context.respond_to?(:before_notifications)
+ if before_notifications && !before_notifications.empty?
+ whyrun_before = Chef::Config[:why_run]
+ begin
+ Chef::Config[:why_run] = true
+ Chef::Log.info("#{resource} running why-run #{action} action to support before action")
+ resource.run_action(action, notification_type, notifying_resource)
+ ensure
+ Chef::Config[:why_run] = whyrun_before
+ end
+
+ if resource.updated_by_last_action?
+ before_notifications.each do |notification|
+ Chef::Log.info("#{resource} sending #{notification.action} action to #{notification.resource} (before)")
+ run_action(notification.resource, notification.action, :before, resource)
+ end
+ end
+
+ end
+
+ # Actually run the action for realsies
resource.run_action(action, notification_type, notifying_resource)
# Execute any immediate and queue up any delayed notifications
diff --git a/lib/chef/scan_access_control.rb b/lib/chef/scan_access_control.rb
index 01630a7254..591324277c 100644
--- a/lib/chef/scan_access_control.rb
+++ b/lib/chef/scan_access_control.rb
@@ -1,6 +1,6 @@
#--
# Author:: Daniel DeLeo (<dan@opscode.com>)
-# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# Copyright:: Copyright (c) 2012-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -128,11 +128,11 @@ class Chef
def stat
@stat ||= if @new_resource.instance_of?(Chef::Resource::Link)
- ::File.lstat(@new_resource.path)
- else
- realpath = ::File.realpath(@new_resource.path)
- ::File.stat(realpath)
- end
+ ::File.lstat(@new_resource.path)
+ else
+ realpath = ::File.realpath(@new_resource.path)
+ ::File.stat(realpath)
+ end
end
end
end
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index 8656e810db..06be3fafe9 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/config'
-require 'chef/exceptions'
-require 'chef/rest'
+require "chef/config"
+require "chef/exceptions"
+require "chef/server_api"
-require 'uri'
+require "uri"
class Chef
class Search
@@ -35,12 +35,12 @@ class Chef
end
def rest
- @rest ||= Chef::REST.new(@url || @config[:chef_server_url])
+ @rest ||= Chef::ServerAPI.new(@url || @config[:chef_server_url])
end
# Backwards compatability for cookbooks.
# This can be removed in Chef > 12.
- def partial_search(type, query='*:*', *args, &block)
+ def partial_search(type, query="*:*", *args, &block)
Chef::Log.warn(<<-WARNDEP)
DEPRECATED: The 'partial_search' API is deprecated and will be removed in
future releases. Please use 'search' with a :filter_result argument to get
@@ -80,7 +80,7 @@ WARNDEP
# an example of the returned json may be:
# {"ip_address":"127.0.0.1", "ruby_version": "1.9.3"}
#
- def search(type, query='*:*', *args, &block)
+ def search(type, query="*:*", *args, &block)
validate_type(type)
args_h = hashify_args(*args)
@@ -88,8 +88,21 @@ WARNDEP
if block
response["rows"].each { |row| block.call(row) if row }
- unless (response["start"] + response["rows"].length) >= response["total"]
- args_h[:start] = response["start"] + (args_h[:rows] || 0)
+ #
+ # args_h[:rows] and args_h[:start] are the page size and
+ # start position requested of the search index backing the
+ # search API.
+ #
+ # The response may contain fewer rows than arg_h[:rows] if
+ # the page of index results included deleted nodes which
+ # have been filtered from the returned data. In this case,
+ # we still want to start the next page at start +
+ # args_h[:rows] to avoid asking the search backend for
+ # overlapping pages (which could result in duplicates).
+ #
+ next_start = response["start"] + (args_h[:rows] || response["rows"].length)
+ unless next_start >= response["total"]
+ args_h[:start] = next_start
search(type, query, args_h, &block)
end
true
@@ -99,6 +112,7 @@ WARNDEP
end
private
+
def validate_type(t)
unless t.kind_of?(String) || t.kind_of?(Symbol)
msg = "Invalid search object type #{t.inspect} (#{t.class}), must be a String or Symbol." +
@@ -132,16 +146,30 @@ WARNDEP
qstr
end
- def call_rest_service(type, query:'*:*', rows:nil, start:0, sort:'X_CHEF_id_CHEF_X asc', filter_result:nil)
+ def call_rest_service(type, query:"*:*", rows:nil, start:0, sort:"X_CHEF_id_CHEF_X asc", filter_result:nil)
query_string = create_query_string(type, query, rows, start, sort)
if filter_result
- response = rest.post_rest(query_string, filter_result)
+ response = rest.post(query_string, filter_result)
# response returns rows in the format of
# { "url" => url_to_node, "data" => filter_result_hash }
- response['rows'].map! { |row| row['data'] }
+ response["rows"].map! { |row| row["data"] }
else
- response = rest.get_rest(query_string)
+ response = rest.get(query_string)
+ response["rows"].map! do |row|
+ case type.to_s
+ when "node"
+ Chef::Node.from_hash(row)
+ when "role"
+ Chef::Role.from_hash(row)
+ when "environment"
+ Chef::Environment.from_hash(row)
+ when "client"
+ Chef::ApiClient.from_hash(row)
+ else
+ Chef::DataBagItem.from_hash(row)
+ end
+ end
end
response
diff --git a/lib/chef/server_api.rb b/lib/chef/server_api.rb
index 8cdcd7a09d..b7e460fa6e 100644
--- a/lib/chef/server_api.rb
+++ b/lib/chef/server_api.rb
@@ -16,13 +16,14 @@
# limitations under the License.
#
-require 'chef/http'
-require 'chef/http/authenticator'
-require 'chef/http/cookie_manager'
-require 'chef/http/decompressor'
-require 'chef/http/json_input'
-require 'chef/http/json_output'
-require 'chef/http/remote_request_id'
+require "chef/http"
+require "chef/http/authenticator"
+require "chef/http/cookie_manager"
+require "chef/http/decompressor"
+require "chef/http/json_input"
+require "chef/http/json_output"
+require "chef/http/remote_request_id"
+require "chef/http/validate_content_length"
class Chef
class ServerAPI < Chef::HTTP
@@ -30,6 +31,8 @@ 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)
+ options[:inflate_json_class] = false
super(url, options)
end
@@ -39,5 +42,31 @@ class Chef
use Chef::HTTP::Decompressor
use Chef::HTTP::Authenticator
use Chef::HTTP::RemoteRequestID
+
+ # ValidateContentLength should come after Decompressor
+ # because the order of middlewares is reversed when handling
+ # responses.
+ use Chef::HTTP::ValidateContentLength
+
+ # Makes an HTTP request to +path+ with the given +method+, +headers+, and
+ # +data+ (if applicable). Does not apply any middleware, besides that
+ # needed for Authentication.
+ def raw_request(method, path, headers={}, data=false)
+ url = create_url(path)
+ method, url, headers, data = Chef::HTTP::Authenticator.new(options).handle_request(method, url, headers, data)
+ method, url, headers, data = Chef::HTTP::RemoteRequestID.new(options).handle_request(method, url, headers, data)
+ response, rest_request, return_value = send_http_request(method, url, headers, data)
+ response.error! unless success_response?(response)
+ return_value
+ rescue Exception => exception
+ log_failed_request(response, return_value) unless response.nil?
+
+ if exception.respond_to?(:chef_rest_request=)
+ exception.chef_rest_request = rest_request
+ end
+ raise
+ end
end
end
+
+require "chef/config"
diff --git a/lib/chef/shell.rb b/lib/chef/shell.rb
index ee4fe78808..f5a5f5af10 100644
--- a/lib/chef/shell.rb
+++ b/lib/chef/shell.rb
@@ -15,21 +15,21 @@
# limitations under the License.
#
-require 'singleton'
-require 'pp'
-require 'etc'
-require 'mixlib/cli'
-
-require 'chef'
-require 'chef/version'
-require 'chef/client'
-require 'chef/config'
-require 'chef/config_fetcher'
-
-require 'chef/shell/shell_session'
-require 'chef/shell/ext'
-require 'chef/json_compat'
-require 'chef/util/path_helper'
+require "singleton"
+require "pp"
+require "etc"
+require "mixlib/cli"
+
+require "chef"
+require "chef/version"
+require "chef/client"
+require "chef/config"
+require "chef/config_fetcher"
+
+require "chef/shell/shell_session"
+require "chef/shell/ext"
+require "chef/json_compat"
+require "chef/util/path_helper"
# = Shell
# Shell is Chef in an IRB session. Shell can interact with a Chef server via the
@@ -110,7 +110,7 @@ module Shell
conf.prompt_c = "chef#{leader(m)} > "
conf.return_format = " => %s \n"
- conf.prompt_i = "chef#{leader(m)} > "
+ conf.prompt_i = "chef#{leader(m)} (#{Chef::VERSION})> "
conf.prompt_n = "chef#{leader(m)} ?> "
conf.prompt_s = "chef#{leader(m)}%l> "
conf.use_tracer = false
@@ -180,7 +180,7 @@ module Shell
end
def self.editor
- @editor || Chef::Config[:editor] || ENV['EDITOR']
+ @editor || Chef::Config[:editor] || ENV["EDITOR"]
end
class Options
@@ -216,7 +216,7 @@ FOOTER
option :log_level,
:short => "-l LOG_LEVEL",
- :long => '--log-level LOG_LEVEL',
+ :long => "--log-level LOG_LEVEL",
:description => "Set the logging level",
:proc => proc { |level| Chef::Config.log_level = level.to_sym; Shell.setup_logger }
@@ -264,7 +264,7 @@ FOOTER
:short => "-o RunlistItem,RunlistItem...",
:long => "--override-runlist RunlistItem,RunlistItem...",
:description => "Replace current run list with specified items",
- :proc => lambda { |items| items.split(',').map { |item| Chef::RunList::RunListItem.new(item) }}
+ :proc => lambda { |items| items.split(",").map { |item| Chef::RunList::RunListItem.new(item) }}
def self.print_help
instance = new
@@ -296,19 +296,19 @@ FOOTER
private
def config_file_for_shell_mode(environment)
- dot_chef_dir = Chef::Util::PathHelper.home('.chef')
+ dot_chef_dir = Chef::Util::PathHelper.home(".chef")
if config[:config_file]
config[:config_file]
elsif environment
Shell.env = environment
- config_file_to_try = ::File.join(dot_chef_dir, 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 dot_chef_dir && ::File.exist?(File.join(dot_chef_dir, 'chef_shell.rb'))
- File.join(dot_chef_dir, '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/shell/ext.rb b/lib/chef/shell/ext.rb
index d516524765..86efaae35a 100644
--- a/lib/chef/shell/ext.rb
+++ b/lib/chef/shell/ext.rb
@@ -16,15 +16,15 @@
# limitations under the License.
#
-require 'tempfile'
-require 'chef/recipe'
-require 'fileutils'
-require 'chef/dsl/platform_introspection'
-require 'chef/version'
-require 'chef/shell/shell_session'
-require 'chef/shell/model_wrapper'
-require 'chef/shell/shell_rest'
-require 'chef/json_compat'
+require "tempfile"
+require "chef/recipe"
+require "fileutils"
+require "chef/dsl/platform_introspection"
+require "chef/version"
+require "chef/shell/shell_session"
+require "chef/shell/model_wrapper"
+require "chef/server_api"
+require "chef/json_compat"
module Shell
module Extensions
@@ -47,10 +47,10 @@ module Shell
unless jobs.respond_to?(:session_select)
def jobs.select_shell_session(target_context)
session = if target_context.kind_of?(Class)
- select_session_by_context { |main| main.kind_of?(target_context) }
- else
- select_session_by_context { |main| main.equal?(target_context) }
- end
+ select_session_by_context { |main| main.kind_of?(target_context) }
+ else
+ select_session_by_context { |main| main.equal?(target_context) }
+ end
Array(session.first)[1]
end
end
@@ -196,7 +196,7 @@ module Shell
chef-shell commands. When called with an argument COMMAND, +help+
prints a detailed explanation of the command if available, or the
description if no explanation is available.
-E
+ E
def help(commmand=nil)
if commmand
explain_command(commmand)
@@ -210,9 +210,9 @@ E
desc "prints information about chef"
def version
puts "This is the chef-shell.\n" +
- " Chef Version: #{::Chef::VERSION}\n" +
- " http://www.chef.io/\n" +
- " http://docs.chef.io/"
+ " Chef Version: #{::Chef::VERSION}\n" +
+ " http://www.chef.io/\n" +
+ " http://docs.chef.io/"
:ucanhaz_automation
end
alias :shell :version
@@ -240,9 +240,9 @@ E
desc "returns an object to control a paused chef run"
subcommands :resume => "resume the chef run",
- :step => "run only the next resource",
- :skip_back => "move back in the run list",
- :skip_forward => "move forward in the run list"
+ :step => "run only the next resource",
+ :skip_back => "move back in the run list",
+ :skip_forward => "move forward in the run list"
def chef_run
Shell.session.resource_collection.iterator
end
@@ -314,7 +314,7 @@ E
1. Looks for an EDITOR set by Shell.editor = "EDITOR"
2. Looks for an EDITOR configured in your chef-shell config file
3. Uses the value of the EDITOR environment variable
-E
+ E
def edit(object)
unless Shell.editor
puts "Please set your editor with Shell.editor = \"vim|emacs|mate|ed\""
@@ -331,7 +331,7 @@ E
edited_data = Tempfile.open([filename, ".js"]) do |tempfile|
tempfile.sync = true
tempfile.puts Chef::JSONCompat.to_json(object)
- system("#{Shell.editor.to_s} #{tempfile.path}")
+ system("#{Shell.editor} #{tempfile.path}")
tempfile.rewind
tempfile.read
end
@@ -392,19 +392,19 @@ E
end
This will strip the admin privileges from any client named after borat.
-E
+ E
subcommands :all => "list all api clients",
- :show => "load an api client by name",
- :search => "search for API clients",
- :transform => "edit all api clients via a code block and save them"
+ :show => "load an api client by name",
+ :search => "search for API clients",
+ :transform => "edit all api clients via a code block and save them"
def clients
@clients ||= Shell::ModelWrapper.new(Chef::ApiClient, :client)
end
desc "Find and edit cookbooks"
subcommands :all => "list all cookbooks",
- :show => "load a cookbook by name",
- :transform => "edit all cookbooks via a code block and save them"
+ :show => "load a cookbook by name",
+ :transform => "edit all cookbooks via a code block and save them"
def cookbooks
@cookbooks ||= Shell::ModelWrapper.new(Chef::CookbookVersion)
end
@@ -454,11 +454,11 @@ E
end
This will assign the attribute to every node with a FQDN matching the regex.
-E
+ E
subcommands :all => "list all nodes",
- :show => "load a node by name",
- :search => "search for nodes",
- :transform => "edit all nodes via a code block and save them"
+ :show => "load a node by name",
+ :search => "search for nodes",
+ :transform => "edit all nodes via a code block and save them"
def nodes
@nodes ||= Shell::ModelWrapper.new(Chef::Node)
end
@@ -476,11 +476,11 @@ E
## SEE ALSO ##
See the help for +nodes+ for more information about the subcommands.
-E
+ E
subcommands :all => "list all roles",
- :show => "load a role by name",
- :search => "search for roles",
- :transform => "edit all roles via a code block and save them"
+ :show => "load a role by name",
+ :search => "search for roles",
+ :transform => "edit all roles via a code block and save them"
def roles
@roles ||= Shell::ModelWrapper.new(Chef::Role)
end
@@ -502,11 +502,11 @@ E
## SEE ALSO ##
See the help for +nodes+ for more information about the subcommands.
-E
+ E
subcommands :all => "list all items in the data bag",
- :show => "load a data bag item by id",
- :search => "search for items in the data bag",
- :transform => "edit all items via a code block and save them"
+ :show => "load a data bag item by id",
+ :search => "search for items in the data bag",
+ :transform => "edit all items via a code block and save them"
def databags(databag_name)
@named_databags_wrappers ||= {}
@named_databags_wrappers[databag_name] ||= Shell::NamedDataBagWrapper.new(databag_name)
@@ -525,18 +525,18 @@ E
## SEE ALSO ##
See the help for +nodes+ for more information about the subcommands.
-E
+ E
subcommands :all => "list all environments",
- :show => "load an environment by name",
- :search => "search for environments",
- :transform => "edit all environments via a code block and save them"
+ :show => "load an environment by name",
+ :search => "search for environments",
+ :transform => "edit all environments via a code block and save them"
def environments
@environments ||= Shell::ModelWrapper.new(Chef::Environment)
end
desc "A REST Client configured to authenticate with the API"
def api
- @rest = Shell::ShellREST.new(Chef::Config[:chef_server_url])
+ @rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url])
end
end
diff --git a/lib/chef/shell/model_wrapper.rb b/lib/chef/shell/model_wrapper.rb
index 7ee39de7eb..7d1324d62e 100644
--- a/lib/chef/shell/model_wrapper.rb
+++ b/lib/chef/shell/model_wrapper.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/mixin/convert_to_class_name'
-require 'chef/mixin/language'
+require "chef/mixin/convert_to_class_name"
+require "chef/mixin/language"
module Shell
class ModelWrapper
diff --git a/lib/chef/shell/shell_session.rb b/lib/chef/shell/shell_session.rb
index 73e6c34ebb..beea923aef 100644
--- a/lib/chef/shell/shell_session.rb
+++ b/lib/chef/shell/shell_session.rb
@@ -18,16 +18,16 @@
# limitations under the License.
#
-require 'chef/recipe'
-require 'chef/run_context'
-require 'chef/config'
-require 'chef/client'
-require 'chef/cookbook/cookbook_collection'
-require 'chef/cookbook_loader'
-require 'chef/run_list/run_list_expansion'
-require 'chef/formatters/base'
-require 'chef/formatters/doc'
-require 'chef/formatters/minimal'
+require "chef/recipe"
+require "chef/run_context"
+require "chef/config"
+require "chef/client"
+require "chef/cookbook/cookbook_collection"
+require "chef/cookbook_loader"
+require "chef/run_list/run_list_expansion"
+require "chef/formatters/base"
+require "chef/formatters/doc"
+require "chef/formatters/minimal"
module Shell
class ShellSession
@@ -201,7 +201,7 @@ module Shell
def rebuild_context
@run_status = Chef::RunStatus.new(@node, @events)
- Chef::Cookbook::FileVendor.fetch_from_remote(Chef::REST.new(Chef::Config[:chef_server_url]))
+ Chef::Cookbook::FileVendor.fetch_from_remote(Chef::ServerAPI.new(Chef::Config[:chef_server_url]))
cookbook_hash = @client.sync_cookbooks
cookbook_collection = Chef::CookbookCollection.new(cookbook_hash)
@run_context = Chef::RunContext.new(node, cookbook_collection, @events)
@@ -235,7 +235,7 @@ module Shell
# Run the very smallest amount of ohai we can get away with and still
# hope to have things work. Otherwise we're not very good doppelgangers
def run_ohai
- @ohai.require_plugin('os')
+ @ohai.require_plugin("os")
end
# DoppelGanger implementation of build_node. preserves as many of the node's
@@ -245,7 +245,7 @@ module Shell
@node = Chef::Node.find_or_create(node_name)
ohai_data = @ohai.data.merge(@node.automatic_attrs)
@node.consume_external_attrs(ohai_data,nil)
- @run_list_expansion = @node.expand!('server')
+ @run_list_expansion = @node.expand!("server")
@expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
Chef::Log.info("Run List is [#{@node.run_list}]")
Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
@@ -253,7 +253,8 @@ module Shell
end
def register
- @rest = Chef::REST.new(Chef::Config[:chef_server_url], Chef::Config[:node_name], Chef::Config[:client_key])
+ @rest = Chef::ServerAPI.new(Chef::Config[:chef_server_url], :client_name => Chef::Config[:node_name],
+ :signing_key_filename => Chef::Config[:client_key])
end
end
diff --git a/lib/chef/shell_out.rb b/lib/chef/shell_out.rb
index 3febe366bd..bf6018df52 100644
--- a/lib/chef/shell_out.rb
+++ b/lib/chef/shell_out.rb
@@ -1,4 +1,4 @@
-require 'mixlib/shellout'
+require "mixlib/shellout"
class Chef
class ShellOut < Mixlib::ShellOut
diff --git a/lib/chef/tasks/chef_repo.rake b/lib/chef/tasks/chef_repo.rake
index 14a5bcc0c1..6dce0a5f70 100644
--- a/lib/chef/tasks/chef_repo.rake
+++ b/lib/chef/tasks/chef_repo.rake
@@ -16,8 +16,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-TOPDIR = '.'
-require 'rake'
+TOPDIR = "."
+require "rake"
desc "By default, print deprecation notice"
task :default do
@@ -27,37 +27,37 @@ end
desc "Install the latest copy of the repository on this Chef Server"
task :install do
puts deprecation_notice
- puts 'The `install` rake task, which included the `update`, `roles`, and'
- puts '`upload_cookbooks` rake tasks is replaced by the `knife upload`'
+ puts "The `install` rake task, which included the `update`, `roles`, and"
+ puts "`upload_cookbooks` rake tasks is replaced by the `knife upload`"
puts 'sub-command. The notion of "installing" the chef-repo to the Chef'
- puts 'Server. Previously the `install` task would manage server and'
- puts 'client configuration. This will not work at all on Chef Server 11+'
- puts 'and client configuration should be managed with the `chef-client`'
- puts 'cookbook.'
+ puts "Server. Previously the `install` task would manage server and"
+ puts "client configuration. This will not work at all on Chef Server 11+"
+ puts "and client configuration should be managed with the `chef-client`"
+ puts "cookbook."
end
desc "Update your repository from source control"
task :update do
puts deprecation_notice
- puts 'The `update` rake task previously updated the chef-repo from'
- puts 'the detected version control system, either svn or git. However,'
- puts 'it has not been recommended for users for years. Most users in'
- puts 'the community use `git`, so the Subversion functionality is not'
- puts 'required, and `git pull` is sufficient for many workflows. The'
- puts 'world of git workflows is rather different now than it was when'
- puts 'this rake task was created.'
+ puts "The `update` rake task previously updated the chef-repo from"
+ puts "the detected version control system, either svn or git. However,"
+ puts "it has not been recommended for users for years. Most users in"
+ puts "the community use `git`, so the Subversion functionality is not"
+ puts "required, and `git pull` is sufficient for many workflows. The"
+ puts "world of git workflows is rather different now than it was when"
+ puts "this rake task was created."
end
desc "Create a new cookbook (with COOKBOOK=name, optional CB_PREFIX=site-)"
task :new_cookbook do
- cb = ENV['COOKBOOK'] || 'my_cookbook_name'
+ cb = ENV["COOKBOOK"] || "my_cookbook_name"
puts deprecation_notice
- puts 'The `new_cookbook` rake task is replaced by the ChefDK cookbook'
- puts 'generator. To generate a new cookbook run:'
+ puts "The `new_cookbook` rake task is replaced by the ChefDK cookbook"
+ puts "generator. To generate a new cookbook run:"
puts
puts "chef generate cookbook #{ENV['COOKBOOK']}"
puts
- puts 'Or, if you are not using ChefDK, use `knife cookbook create`:'
+ puts "Or, if you are not using ChefDK, use `knife cookbook create`:"
puts
puts "knife cookbook create #{ENV['COOKBOOK']}"
end
@@ -65,46 +65,46 @@ end
desc "Create a new self-signed SSL certificate for FQDN=foo.example.com"
task :ssl_cert do
puts deprecation_notice
- puts 'The `ssl_cert` rake task is superseded by using the CHEF-maintained'
+ puts "The `ssl_cert` rake task is superseded by using the CHEF-maintained"
puts '`openssl` cookbook\'s `openssl_x509` resource which can generate'
- puts 'self-signed certificate chains as convergent resources.'
+ puts "self-signed certificate chains as convergent resources."
puts
- puts 'https://supermarket.getchef.com/cookbooks/openssl'
+ puts "https://supermarket.getchef.com/cookbooks/openssl"
end
desc "Build cookbook metadata.json from metadata.rb"
task :metadata do
puts deprecation_notice
- puts 'The `metadata` rake task is not recommended. Cookbook'
- puts '`metadata.json` is automatically generated from `metadata.rb`'
- puts 'by `knife` when uploading cookbooks to the Chef Server.'
+ puts "The `metadata` rake task is not recommended. Cookbook"
+ puts "`metadata.json` is automatically generated from `metadata.rb`"
+ puts "by `knife` when uploading cookbooks to the Chef Server."
end
desc "Update roles"
task :roles do
puts deprecation_notice
- puts 'The `roles` rake task is not recommended. If you are using Ruby'
- puts 'role files (roles/*.rb), you can upload them all with:'
+ puts "The `roles` rake task is not recommended. If you are using Ruby"
+ puts "role files (roles/*.rb), you can upload them all with:"
puts
- puts 'knife role from file roles/*'
+ puts "knife role from file roles/*"
puts
- puts 'If you are using JSON role files (roles/*.json), you can upload'
- puts 'them all with:'
+ puts "If you are using JSON role files (roles/*.json), you can upload"
+ puts "them all with:"
puts
- puts 'knife upload roles/*.json'
+ puts "knife upload roles/*.json"
end
desc "Update a specific role"
task :role do
puts deprecation_notice
- puts 'The `role` rake task is not recommended. If you are using Ruby'
- puts 'role files, you can upload a single role with:'
+ puts "The `role` rake task is not recommended. If you are using Ruby"
+ puts "role files, you can upload a single role with:"
puts
- puts 'knife role from file rolename.rb'
+ puts "knife role from file rolename.rb"
puts
- puts 'If you are using JSON role files, you can upload a single role with'
+ puts "If you are using JSON role files, you can upload a single role with"
puts
- puts 'knife upload roles/rolename.json'
+ puts "knife upload roles/rolename.json"
end
desc "Upload all cookbooks"
@@ -122,18 +122,18 @@ end
desc "Test all cookbooks"
task :test_cookbooks do
puts deprecation_notice
- puts 'The `test_cookbooks` rake task is no longer recommended. Previously'
- puts 'it only performed a syntax check, and did no other kind of testing,'
- puts 'and the Chef Community has a rich ecosystem of testing tools for'
- puts 'various purposes:'
+ puts "The `test_cookbooks` rake task is no longer recommended. Previously"
+ puts "it only performed a syntax check, and did no other kind of testing,"
+ puts "and the Chef Community has a rich ecosystem of testing tools for"
+ puts "various purposes:"
puts
- puts '- knife cookbook test will perform a syntax check, as this task did'
- puts ' before.'
- puts '- rubocop and foodcritic will perform lint checking for Ruby and'
- puts ' Chef cookbook style according to community standards.'
- puts '- ChefSpec will perform unit testing'
- puts '- Test Kitchen will perform convergence and post-convergence'
- puts ' testing on virtual machines.'
+ puts "- knife cookbook test will perform a syntax check, as this task did"
+ puts " before."
+ puts "- rubocop and foodcritic will perform lint checking for Ruby and"
+ puts " Chef cookbook style according to community standards."
+ puts "- ChefSpec will perform unit testing"
+ puts "- Test Kitchen will perform convergence and post-convergence"
+ puts " testing on virtual machines."
end
desc "Test a single cookbook"
@@ -143,19 +143,19 @@ namespace :databag do
desc "Upload a single databag"
task :upload do
puts deprecation_notice
- puts 'The `data_bags:upload` task is not recommended. You should use'
- puts 'the `knife upload` sub-command for uploading data bag items.'
+ puts "The `data_bags:upload` task is not recommended. You should use"
+ puts "the `knife upload` sub-command for uploading data bag items."
puts
- puts 'knife upload data_bags/bagname/itemname.json'
+ puts "knife upload data_bags/bagname/itemname.json"
end
desc "Upload all databags"
task :upload_all do
puts deprecation_notice
- puts 'The `data_bags:upload_all` task is not recommended. You should'
- puts 'use the `knife upload` sub-command for uploading data bag items.'
+ puts "The `data_bags:upload_all` task is not recommended. You should"
+ puts "use the `knife upload` sub-command for uploading data bag items."
puts
- puts 'knife upload data_bags/*'
+ puts "knife upload data_bags/*"
end
desc "Create a databag"
@@ -172,23 +172,23 @@ namespace :databag do
end
def deprecation_notice
- %Q[*************************************************
+ %Q{*************************************************
NOTICE: Chef Repository Rake Tasks Are Deprecated
*************************************************
-]
+}
end
def deprecated_cookbook_upload
- %Q[
+ %Q{
The `upload_cookbook` and `upload_cookbooks` rake tasks are not
recommended. These tasks are replaced by other, better workflow
tools, such as `knife cookbook upload`, `knife upload`, or `berks`
-]
+}
end
def deprecated_data_bag_creation
- %Q[
+ %Q{
The `data_bags:create` and `data_bags:create_item` tasks are not
recommended. You should create data bag items as JSON files in the data_bags
directory, with a sub-directory for each bag, and use `knife upload` to
@@ -197,5 +197,5 @@ upload them. For example, if you have a data bags named `users`, with
./data_bags/users/finn.json
./data-bags/users/jake.json
-]
+}
end
diff --git a/lib/chef/user.rb b/lib/chef/user.rb
index 42fa6b5fa1..37a104a537 100644
--- a/lib/chef/user.rb
+++ b/lib/chef/user.rb
@@ -1,6 +1,6 @@
#
# Author:: Steven Danna (steve@opscode.com)
-# Copyright:: Copyright 2012 Opscode, Inc.
+# Copyright:: Copyright 2012-2016 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,13 +15,25 @@
# 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/config"
+require "chef/mixin/params_validate"
+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
@@ -29,13 +41,17 @@ class Chef
include Chef::Mixin::ParamsValidate
def initialize
- @name = ''
+ @name = ""
@public_key = nil
@private_key = nil
@password = nil
@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\-_]+$/)
@@ -65,7 +81,7 @@ class Chef
result = {
"name" => @name,
"public_key" => @public_key,
- "admin" => @admin
+ "admin" => @admin,
}
result["private_key"] = @private_key if @private_key
result["password"] = @password if @password
@@ -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
@@ -120,18 +135,18 @@ class Chef
def inspect
"Chef::User name:'#{name}' admin:'#{admin.inspect}'" +
- "public_key:'#{public_key}' private_key:#{private_key}"
+ "public_key:'#{public_key}' private_key:#{private_key}"
end
# Class Methods
def self.from_hash(user_hash)
user = Chef::User.new
- user.name user_hash['name']
- user.private_key user_hash['private_key'] if user_hash.key?('private_key')
- user.password user_hash['password'] if user_hash.key?('password')
- user.public_key user_hash['public_key']
- user.admin user_hash['admin']
+ user.name user_hash["name"]
+ user.private_key user_hash["private_key"] if user_hash.key?("private_key")
+ user.password user_hash["password"] if user_hash.key?("password")
+ user.public_key user_hash["public_key"]
+ user.admin user_hash["admin"]
user
end
@@ -144,12 +159,12 @@ 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
- response # OSC
- end
+ transform_ohc_list_response(response) # OHC/OPC
+ else
+ response # OSC
+ end
if inflate
users.inject({}) do |user_map, (name, _url)|
user_map[name] = Chef::User.load(name)
@@ -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
@@ -172,7 +187,7 @@ class Chef
def self.transform_ohc_list_response(response)
new_response = Hash.new
response.each do |u|
- name = u['user']['username']
+ name = u["user"]["username"]
new_response[name] = Chef::Config[:chef_server_url] + "/users/#{name}"
end
new_response
diff --git a/lib/chef/user_v1.rb b/lib/chef/user_v1.rb
new file mode 100644
index 0000000000..133087a089
--- /dev/null
+++ b/lib/chef/user_v1.rb
@@ -0,0 +1,329 @@
+#
+# Author:: Steven Danna (steve@opscode.com)
+# Copyright:: Copyright 2012-2016 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/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
+ 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 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::ServerAPI.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::ServerAPI.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::ServerAPI.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..2142869e36 100644
--- a/lib/chef/util/backup.rb
+++ b/lib/chef/util/backup.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/util/path_helper'
+require "chef/util/path_helper"
class Chef
class Util
@@ -49,7 +49,7 @@ class Chef
def backup_filename
@backup_filename ||= begin
time = Time.now
- nanoseconds = sprintf("%6f", time.to_f).split('.')[1]
+ nanoseconds = sprintf("%6f", time.to_f).split(".")[1]
savetime = time.strftime("%Y%m%d%H%M%S.#{nanoseconds}")
backup_filename = "#{path}.chef-#{savetime}"
backup_filename = backup_filename.sub(/^([A-Za-z]:)/, "") #strip drive letter on Windows
@@ -78,8 +78,16 @@ class Chef
Chef::Log.info("#{@new_resource} removed backup at #{backup_file}")
end
+ def unsorted_backup_files
+ # If you replace this with Dir[], you will probably break Windows.
+ fn = Regexp.escape(::File.basename(path))
+ Dir.entries(::File.dirname(backup_path)).select do |f|
+ !!(f =~ /\A#{fn}.chef-[0-9.]*\B/)
+ end.map {|f| ::File.join(::File.dirname(backup_path), f)}
+ end
+
def sorted_backup_files
- Dir[Chef::Util::PathHelper.escape_glob(prefix, ".#{path}") + ".chef-*"].sort { |a,b| b <=> a }
+ unsorted_backup_files.sort { |a,b| b <=> a }
end
end
end
diff --git a/lib/chef/util/diff.rb b/lib/chef/util/diff.rb
index c2dc6e045c..3b42f576c0 100644
--- a/lib/chef/util/diff.rb
+++ b/lib/chef/util/diff.rb
@@ -40,8 +40,8 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OF OTHER DEALINGS IN THE
# SOFTWARE.
-require 'diff/lcs'
-require 'diff/lcs/hunk'
+require "diff/lcs"
+require "diff/lcs/hunk"
class Chef
class Util
@@ -64,7 +64,7 @@ class Chef
def use_tempfile_if_missing(file)
tempfile = nil
unless File.exists?(file)
- Chef::Log.debug("file #{file} does not exist to diff against, using empty tempfile")
+ Chef::Log.debug("File #{file} does not exist to diff against, using empty tempfile")
tempfile = Tempfile.new("chef-diff")
file = tempfile.path
end
@@ -97,9 +97,9 @@ class Chef
return "No differences encountered\n" if diff_data.empty?
# write diff header (standard unified format)
- ft = File.stat(old_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
+ ft = File.stat(old_file).mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.%N %z")
diff_str << "--- #{old_file}\t#{ft}\n"
- ft = File.stat(new_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
+ ft = File.stat(new_file).mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.%N %z")
diff_str << "+++ #{new_file}\t#{ft}\n"
# loop over diff hunks. if a hunk overlaps with the last hunk,
@@ -139,7 +139,7 @@ class Chef
return "(new content is binary, diff output suppressed)" if is_binary?(new_file)
begin
- Chef::Log.debug("running: diff -u #{old_file} #{new_file}")
+ Chef::Log.debug("Running: diff -u #{old_file} #{new_file}")
diff_str = udiff(old_file, new_file)
rescue Exception => e
@@ -176,7 +176,7 @@ class Chef
end
def encode_diff_for_json(diff_str)
- diff_str.encode!('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?')
+ diff_str.encode!("UTF-8", :invalid => :replace, :undef => :replace, :replace => "?")
end
end
diff --git a/lib/chef/util/dsc/configuration_generator.rb b/lib/chef/util/dsc/configuration_generator.rb
index 0d7296eae9..e2c09aeea8 100644
--- a/lib/chef/util/dsc/configuration_generator.rb
+++ b/lib/chef/util/dsc/configuration_generator.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/util/powershell/cmdlet'
+require "chef/util/powershell/cmdlet"
class Chef::Util::DSC
class ConfigurationGenerator
@@ -27,9 +27,9 @@ class Chef::Util::DSC
def configuration_document_from_script_code(code, configuration_flags, imports, shellout_flags)
Chef::Log.debug("DSC: DSC code:\n '#{code}'")
- generated_script_path = write_document_generation_script(code, 'chef_dsc', imports)
+ generated_script_path = write_document_generation_script(code, "chef_dsc", imports)
begin
- configuration_document_from_script_path(generated_script_path, 'chef_dsc', configuration_flags, shellout_flags)
+ configuration_document_from_script_path(generated_script_path, "chef_dsc", configuration_flags, shellout_flags)
ensure
::FileUtils.rm(generated_script_path)
end
@@ -72,7 +72,7 @@ class Chef::Util::DSC
if configuration_flags
configuration_flags.map do | switch, value |
if merged_configuration_flags.key?(switch.to_s.downcase.to_sym)
- raise ArgumentError, "The `flags` attribute for the dsc_script resource contained a command line switch :#{switch.to_s} that is disallowed."
+ raise ArgumentError, "The `flags` attribute for the dsc_script resource contained a command line switch :#{switch} that is disallowed."
end
merged_configuration_flags[switch.to_s.downcase.to_sym] = value
end
@@ -97,7 +97,7 @@ Configuration '#{configuration_name}'
def generate_import_resource_statements(imports)
if imports
imports.map do |resource_module, resources|
- if resources.length == 0 || resources.include?('*')
+ if resources.length == 0 || resources.include?("*")
"Import-DscResource -ModuleName #{resource_module}"
else
"Import-DscResource -ModuleName #{resource_module} -Name #{resources.join(',')}"
@@ -114,7 +114,7 @@ Configuration '#{configuration_name}'
def write_document_generation_script(code, configuration_name, imports)
script_path = "#{@config_directory}/chef_dsc_config.ps1"
- ::File.open(script_path, 'wt') do | script |
+ ::File.open(script_path, "wt") do | script |
script.write(configuration_code(code, configuration_name, imports))
end
script_path
@@ -131,7 +131,7 @@ Configuration '#{configuration_name}'
end
def get_configuration_document(document_path)
- ::File.open(document_path, 'rb') do | file |
+ ::File.open(document_path, "rb") do | file |
file.read
end
end
diff --git a/lib/chef/util/dsc/lcm_output_parser.rb b/lib/chef/util/dsc/lcm_output_parser.rb
index 754fde3e8b..ac847adffa 100644
--- a/lib/chef/util/dsc/lcm_output_parser.rb
+++ b/lib/chef/util/dsc/lcm_output_parser.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/log'
-require 'chef/util/dsc/resource_info'
-require 'chef/exceptions'
+require "chef/log"
+require "chef/util/dsc/resource_info"
+require "chef/exceptions"
class Chef
class Util
@@ -109,7 +109,7 @@ class Chef
# What If: [machinename]: LCM: [op_action op_type] message
# extract op_action, op_type, and message
operation, info = match.captures
- op_action, op_type = operation.strip.split(' ').map {|m| m.downcase.to_sym}
+ op_action, op_type = operation.strip.split(" ").map {|m| m.downcase.to_sym}
else
op_action = op_type = :info
if match = line.match(/^.*?:.*?: \s+(.*)/)
diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb
index f8398341e5..deaa83ef09 100644
--- a/lib/chef/util/dsc/local_configuration_manager.rb
+++ b/lib/chef/util/dsc/local_configuration_manager.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/util/powershell/cmdlet'
-require 'chef/util/dsc/lcm_output_parser'
+require "chef/util/powershell/cmdlet"
+require "chef/util/dsc/lcm_output_parser"
class Chef::Util::DSC
class LocalConfigurationManager
@@ -47,7 +47,7 @@ class Chef::Util::DSC
def run_configuration_cmdlet(configuration_document, apply_configuration, shellout_flags)
Chef::Log.debug("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.")
- test_only_parameters = ! apply_configuration ? '-whatif; if (! $?) { exit 1 }' : ''
+ test_only_parameters = ! apply_configuration ? "-whatif; if (! $?) { exit 1 }" : ""
start_operation_timing
command_code = lcm_command_code(@configuration_path, test_only_parameters)
@@ -90,7 +90,7 @@ EOH
end
def whatif_not_supported?(what_if_exception_output)
- !! (what_if_exception_output.gsub(/[\r\n]+/, '').gsub(/\s+/, ' ') =~ /A parameter cannot be found that matches parameter name 'Whatif'/i)
+ !! (what_if_exception_output.gsub(/[\r\n]+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i)
end
def dsc_module_import_failure?(what_if_output)
@@ -105,13 +105,13 @@ EOH
Parser::parse(what_if_output)
rescue Chef::Exceptions::LCMParser => e
Chef::Log::warn("Could not parse LCM output: #{e}")
- [Chef::Util::DSC::ResourceInfo.new('Unknown DSC Resources', true, ['Unknown changes because LCM output was not parsable.'])]
+ [Chef::Util::DSC::ResourceInfo.new("Unknown DSC Resources", true, ["Unknown changes because LCM output was not parsable."])]
end
end
def save_configuration_document(configuration_document)
::FileUtils.mkdir_p(@configuration_path)
- ::File.open(configuration_document_path, 'wb') do | file |
+ ::File.open(configuration_document_path, "wb") do | file |
file.write(configuration_document)
end
end
@@ -121,7 +121,7 @@ EOH
end
def configuration_document_path
- File.join(@configuration_path,'..mof')
+ File.join(@configuration_path,"..mof")
end
def clear_execution_time
diff --git a/lib/chef/util/dsc/resource_store.rb b/lib/chef/util/dsc/resource_store.rb
index fdcecc2b3c..761ade9989 100644
--- a/lib/chef/util/dsc/resource_store.rb
+++ b/lib/chef/util/dsc/resource_store.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/util/powershell/cmdlet'
-require 'chef/util/powershell/cmdlet_result'
-require 'chef/exceptions'
+require "chef/util/powershell/cmdlet"
+require "chef/util/powershell/cmdlet_result"
+require "chef/exceptions"
class Chef
class Util
@@ -53,7 +53,7 @@ class DSC
def add_resource(new_r)
count = resources.count do |r|
- r['ResourceType'].casecmp(new_r['ResourceType']) == 0
+ r["ResourceType"].casecmp(new_r["ResourceType"]) == 0
end
if count == 0
resources << new_r
@@ -72,9 +72,9 @@ class DSC
def find_resources(name, module_name, rs)
found = rs.find_all do |r|
- name_matches = r['Name'].casecmp(name) == 0
+ name_matches = r["Name"].casecmp(name) == 0
if name_matches
- module_name == nil || (r['Module'] and r['Module']['Name'].casecmp(module_name) == 0)
+ module_name == nil || (r["Module"] and r["Module"]["Name"].casecmp(module_name) == 0)
else
false
end
@@ -84,7 +84,7 @@ class DSC
# Returns a list of dsc resources
def query_resources
- cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, 'get-dscresource',
+ cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource",
:object)
result = cmdlet.run
result.return_value
diff --git a/lib/chef/util/file_edit.rb b/lib/chef/util/file_edit.rb
index 4d2a9c03eb..d321764a0a 100644
--- a/lib/chef/util/file_edit.rb
+++ b/lib/chef/util/file_edit.rb
@@ -15,8 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/util/editor'
-require 'fileutils'
+require "chef/util/editor"
+require "fileutils"
class Chef
class Util
@@ -61,7 +61,7 @@ class Chef
#search the file line by line and match each line with the given regex
#if matched, delete the match (all occurrences) from the line
def search_file_delete(regex)
- search_file_replace(regex, '')
+ search_file_replace(regex, "")
end
#search the file line by line and match each line with the given regex
diff --git a/lib/chef/util/path_helper.rb b/lib/chef/util/path_helper.rb
index 66c2e3f19f..4a079d3419 100644
--- a/lib/chef/util/path_helper.rb
+++ b/lib/chef/util/path_helper.rb
@@ -16,212 +16,10 @@
# limitations under the License.
#
+require "chef-config/path_helper"
+
class Chef
class Util
- class PathHelper
- # Maximum characters in a standard Windows path (260 including drive letter and NUL)
- WIN_MAX_PATH = 259
-
- def self.dirname(path)
- if Chef::Platform.windows?
- # Find the first slash, not counting trailing slashes
- end_slash = path.size
- loop do
- slash = path.rindex(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]/, end_slash - 1)
- if !slash
- return end_slash == path.size ? '.' : path_separator
- elsif slash == end_slash - 1
- end_slash = slash
- else
- return path[0..slash-1]
- end
- end
- else
- ::File.dirname(path)
- end
- end
-
- BACKSLASH = '\\'.freeze
-
- def self.path_separator
- if Chef::Platform.windows?
- File::ALT_SEPARATOR || BACKSLASH
- else
- File::SEPARATOR
- end
- end
-
- def self.join(*args)
- args.flatten.inject do |joined_path, component|
- # Joined path ends with /
- joined_path = joined_path.sub(/[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+$/, '')
- component = component.sub(/^[#{Regexp.escape(File::SEPARATOR)}#{Regexp.escape(path_separator)}]+/, '')
- joined_path += "#{path_separator}#{component}"
- end
- end
-
- def self.validate_path(path)
- if Chef::Platform.windows?
- unless printable?(path)
- msg = "Path '#{path}' contains non-printable characters. Check that backslashes are escaped with another backslash (e.g. C:\\\\Windows) in double-quoted strings."
- Chef::Log.error(msg)
- raise Chef::Exceptions::ValidationFailed, msg
- end
-
- if windows_max_length_exceeded?(path)
- Chef::Log.debug("Path '#{path}' is longer than #{WIN_MAX_PATH}, prefixing with'\\\\?\\'")
- path.insert(0, "\\\\?\\")
- end
- end
-
- path
- end
-
- def self.windows_max_length_exceeded?(path)
- # Check to see if paths without the \\?\ prefix are over the maximum allowed length for the Windows API
- # http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
- unless path =~ /^\\\\?\\/
- if path.length > WIN_MAX_PATH
- return true
- end
- end
-
- false
- end
-
- def self.printable?(string)
- # returns true if string is free of non-printable characters (escape sequences)
- # this returns false for whitespace escape sequences as well, e.g. \n\t
- if string =~ /[^[:print:]]/
- false
- else
- true
- end
- end
-
- # Produces a comparable path.
- def self.canonical_path(path, add_prefix=true)
- # First remove extra separators and resolve any relative paths
- abs_path = File.absolute_path(path)
-
- if Chef::Platform.windows?
- # Add the \\?\ API prefix on Windows unless add_prefix is false
- # Downcase on Windows where paths are still case-insensitive
- abs_path.gsub!(::File::SEPARATOR, path_separator)
- if add_prefix && abs_path !~ /^\\\\?\\/
- abs_path.insert(0, "\\\\?\\")
- end
-
- abs_path.downcase!
- end
-
- abs_path
- end
-
- def self.cleanpath(path)
- path = Pathname.new(path).cleanpath.to_s
- # ensure all forward slashes are backslashes
- if Chef::Platform.windows?
- path = path.gsub(File::SEPARATOR, path_separator)
- end
- path
- end
-
- def self.paths_eql?(path1, path2)
- canonical_path(path1) == canonical_path(path2)
- end
-
- # Paths which may contain glob-reserved characters need
- # to be escaped before globbing can be done.
- # http://stackoverflow.com/questions/14127343
- def self.escape_glob(*parts)
- path = cleanpath(join(*parts))
- path.gsub(/[\\\{\}\[\]\*\?]/) { |x| "\\"+x }
- end
-
- def self.relative_path_from(from, to)
- pathname = Pathname.new(Chef::Util::PathHelper.cleanpath(to)).relative_path_from(Pathname.new(Chef::Util::PathHelper.cleanpath(from)))
- end
-
- # Retrieves the "home directory" of the current user while trying to ascertain the existence
- # of said directory. The path returned uses / for all separators (the ruby standard format).
- # If the home directory doesn't exist or an error is otherwise encountered, nil is returned.
- #
- # If a set of path elements is provided, they are appended as-is to the home path if the
- # homepath exists.
- #
- # If an optional block is provided, the joined path is passed to that block if the home path is
- # valid and the result of the block is returned instead.
- #
- # Home-path discovery is performed once. If a path is discovered, that value is memoized so
- # that subsequent calls to home_dir don't bounce around.
- #
- # See self.all_homes.
- def self.home(*args)
- @@home_dir ||= self.all_homes { |p| break p }
- if @@home_dir
- path = File.join(@@home_dir, *args)
- block_given? ? (yield path) : path
- end
- end
-
- # See self.home. This method performs a similar operation except that it yields all the different
- # possible values of 'HOME' that one could have on this platform. Hence, on windows, if
- # HOMEDRIVE\HOMEPATH and USERPROFILE are different, the provided block will be called twice.
- # This method goes out and checks the existence of each location at the time of the call.
- #
- # The return is a list of all the returned values from each block invocation or a list of paths
- # if no block is provided.
- def self.all_homes(*args)
- paths = []
- if Chef::Platform.windows?
- # By default, Ruby uses the the following environment variables to determine Dir.home:
- # HOME
- # HOMEDRIVE HOMEPATH
- # USERPROFILE
- # Ruby only checks to see if the variable is specified - not if the directory actually exists.
- # On Windows, HOMEDRIVE HOMEPATH can point to a different location (such as an unavailable network mounted drive)
- # while USERPROFILE points to the location where the user application settings and profile are stored. HOME
- # is not defined as an environment variable (usually). If the home path actually uses UNC, then the prefix is
- # HOMESHARE instead of HOMEDRIVE.
- #
- # We instead walk down the following and only include paths that actually exist.
- # HOME
- # HOMEDRIVE HOMEPATH
- # HOMESHARE HOMEPATH
- # USERPROFILE
-
- paths << ENV['HOME']
- paths << ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
- paths << ENV['HOMESHARE'] + ENV['HOMEPATH'] if ENV['HOMESHARE'] && ENV['HOMEPATH']
- paths << ENV['USERPROFILE']
- end
- paths << Dir.home if ENV['HOME']
-
- # Depending on what environment variables we're using, the slashes can go in any which way.
- # Just change them all to / to keep things consistent.
- # Note: Maybe this is a bad idea on some unixy systems where \ might be a valid character depending on
- # the particular brand of kool-aid you consume. This code assumes that \ and / are both
- # path separators on any system being used.
- paths = paths.map { |home_path| home_path.gsub(path_separator, ::File::SEPARATOR) if home_path }
-
- # Filter out duplicate paths and paths that don't exist.
- valid_paths = paths.select { |home_path| home_path && Dir.exists?(home_path) }
- valid_paths = valid_paths.uniq
-
- # Join all optional path elements at the end.
- # If a block is provided, invoke it - otherwise just return what we've got.
- joined_paths = valid_paths.map { |home_path| File.join(home_path, *args) }
- if block_given?
- joined_paths.each { |p| yield p }
- else
- joined_paths
- end
- end
- end
+ PathHelper = ChefConfig::PathHelper
end
end
-
-# Break a require loop when require chef/util/path_helper
-require 'chef/platform'
-require 'chef/exceptions'
diff --git a/lib/chef/util/powershell/cmdlet.rb b/lib/chef/util/powershell/cmdlet.rb
index 47d63a2b85..6d9bd67afa 100644
--- a/lib/chef/util/powershell/cmdlet.rb
+++ b/lib/chef/util/powershell/cmdlet.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'mixlib/shellout'
-require 'chef/mixin/windows_architecture_helper'
-require 'chef/util/powershell/cmdlet_result'
+require "mixlib/shellout"
+require "chef/mixin/windows_architecture_helper"
+require "chef/util/powershell/cmdlet_result"
class Chef
class Util
@@ -38,7 +38,7 @@ class Powershell
when :object
@json_format = true
else
- raise ArgumentError, "Invalid output format #{output_format.to_s} specified"
+ raise ArgumentError, "Invalid output format #{output_format} specified"
end
@cmdlet = cmdlet
@@ -48,11 +48,11 @@ class Powershell
attr_reader :output_format
def run(switches={}, execution_options={}, *arguments)
- streams = { :json => CmdletStream.new('json'),
- :verbose => CmdletStream.new('verbose'),
+ streams = { :json => CmdletStream.new("json"),
+ :verbose => CmdletStream.new("verbose"),
}
- arguments_string = arguments.join(' ')
+ arguments_string = arguments.join(" ")
switches_string = command_switches_string(switches)
@@ -114,12 +114,12 @@ class Powershell
def command_switches_string(switches)
command_switches = switches.map do | switch_name, switch_value |
if switch_name.class != Symbol
- raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name.to_s}'. The switch must be specified as a Symbol'"
+ raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name}'. The switch must be specified as a Symbol'"
end
validate_switch_name!(switch_name)
- switch_argument = ''
+ switch_argument = ""
switch_present = true
case switch_value
@@ -133,13 +133,13 @@ class Powershell
when String
switch_argument = escape_string_parameter_value(switch_value)
else
- raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name.to_s}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`"
+ raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`"
end
- switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(' ').strip : ''
+ switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(" ").strip : ""
end
- command_switches.join(' ')
+ command_switches.join(" ")
end
class CmdletStream
@@ -154,8 +154,8 @@ class Powershell
def read
if File.exist? @filename
- File.open(@filename, 'rb:bom|UTF-16LE') do |f|
- f.read.encode('UTF-8')
+ File.open(@filename, "rb:bom|UTF-16LE") do |f|
+ f.read.encode("UTF-8")
end
end
end
diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb
index f1fdd968b1..531636a4ff 100644
--- a/lib/chef/util/powershell/cmdlet_result.rb
+++ b/lib/chef/util/powershell/cmdlet_result.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/json_compat'
+require "chef/json_compat"
class Chef
class Util
diff --git a/lib/chef/util/powershell/ps_credential.rb b/lib/chef/util/powershell/ps_credential.rb
index 01f8c27b6c..660ef32472 100644
--- a/lib/chef/util/powershell/ps_credential.rb
+++ b/lib/chef/util/powershell/ps_credential.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/crypto' if Chef::Platform.windows?
+require "chef/win32/crypto" if Chef::Platform.windows?
class Chef::Util::Powershell
class PSCredential
@@ -29,6 +29,9 @@ class Chef::Util::Powershell
"New-Object System.Management.Automation.PSCredential('#{@username}',('#{encrypt(@password)}' | ConvertTo-SecureString))"
end
+ alias to_s to_psobject
+ alias to_text to_psobject
+
private
def encrypt(str)
diff --git a/lib/chef/util/selinux.rb b/lib/chef/util/selinux.rb
index 778da042e3..f9fbe82922 100644
--- a/lib/chef/util/selinux.rb
+++ b/lib/chef/util/selinux.rb
@@ -20,8 +20,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/mixin/shell_out'
-require 'chef/mixin/which'
+require "chef/mixin/shell_out"
+require "chef/mixin/which"
class Chef
class Util
diff --git a/lib/chef/util/threaded_job_queue.rb b/lib/chef/util/threaded_job_queue.rb
index 824cd0a3c4..d01ecd81a6 100644
--- a/lib/chef/util/threaded_job_queue.rb
+++ b/lib/chef/util/threaded_job_queue.rb
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'thread'
+require "thread"
class Chef
class Util
diff --git a/lib/chef/util/windows.rb b/lib/chef/util/windows.rb
index 777fe4adbb..7d29a67ac5 100644
--- a/lib/chef/util/windows.rb
+++ b/lib/chef/util/windows.rb
@@ -15,42 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-#requires: gem install windows-pr
-require 'windows/api'
-require 'windows/error'
-require 'windows/handle'
-require 'windows/unicode'
-require 'windows/msvcrt/buffer'
-require 'windows/msvcrt/string'
-require 'windows/network/management'
class Chef
class Util
class Windows
- protected
-
- include ::Windows::Error
- include ::Windows::Unicode
- include ::Windows::MSVCRT::Buffer
- include ::Windows::MSVCRT::String
- include ::Windows::Network::Management
-
- PTR_SIZE = 4 #XXX 64-bit
-
- def lpwstr_to_s(buffer, offset)
- str = 0.chr * (256 * 2) #XXX unhardcode this length (*2 for WCHAR)
- wcscpy str, buffer[offset*PTR_SIZE,PTR_SIZE].unpack('L')[0]
- wide_to_multi str
- end
-
- def dword_to_i(buffer, offset)
- buffer[offset*PTR_SIZE,PTR_SIZE].unpack('i')[0] || 0
- end
-
- #return pointer for use with pack('L')
- def str_to_ptr(v)
- [v].pack('p*').unpack('L')[0]
- end
end
end
end
diff --git a/lib/chef/util/windows/net_group.rb b/lib/chef/util/windows/net_group.rb
index 924bd392f9..5f6bc6a03b 100644
--- a/lib/chef/util/windows/net_group.rb
+++ b/lib/chef/util/windows/net_group.rb
@@ -16,91 +16,70 @@
# limitations under the License.
#
-require 'chef/util/windows'
+require "chef/util/windows"
+require "chef/win32/net"
#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
+class Chef::Util::Windows::NetGroup
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
+ def groupname
+ @groupname
end
public
def initialize(groupname)
- @name = multi_to_wide(groupname)
+ @groupname = 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
+ begin
+ Chef::ReservedNames::Win32::NetUser::net_local_group_get_members(nil, groupname)
+ rescue Chef::Exceptions::Win32NetAPIError => e
+ raise ArgumentError, e.msg
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)
+ 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)
- modify_members(members, NetLocalGroupSetMembers)
+ 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)
- modify_members(members, NetLocalGroupAddMembers)
+ 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)
- modify_members(members, NetLocalGroupDelMembers)
+ 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
- rc = NetLocalGroupDel.call(nil, @name)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ 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..e70df6e42b 100644
--- a/lib/chef/util/windows/net_use.rb
+++ b/lib/chef/util/windows/net_use.rb
@@ -20,62 +20,19 @@
#see also: WNetAddConnection2 and WNetAddConnection3
#see also cmd.exe: net use /?
-require 'chef/util/windows'
+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..b8e9b63571 100644
--- a/lib/chef/util/windows/net_user.rb
+++ b/lib/chef/util/windows/net_user.rb
@@ -16,100 +16,71 @@
# limitations under the License.
#
-require 'chef/util/windows'
-require 'chef/exceptions'
+require "chef/util/windows"
+require "chef/exceptions"
+require "chef/win32/net"
+require "chef/win32/security"
#wrapper around a subset of the NetUser* APIs.
#nothing Chef specific, but not complete enough to be its own gem, so util for now.
class Chef::Util::Windows::NetUser < Chef::Util::Windows
private
-
- LogonUser = Windows::API.new('LogonUser', 'SSSLLP', 'I', 'advapi32')
-
- DOMAIN_GROUP_RID_USERS = 0x00000201
-
- UF_SCRIPT = 0x000001
- UF_ACCOUNTDISABLE = 0x000002
- UF_PASSWD_CANT_CHANGE = 0x000040
- UF_NORMAL_ACCOUNT = 0x000200
- UF_DONT_EXPIRE_PASSWD = 0x010000
-
- #[:symbol_name, default_val]
- #default_val duals as field type
- #array index duals as structure offset
-
- #OC-8391
- #Changing [:password, nil], to [:password, ""],
- #if :password is set to nil, windows user creation api ignores the password policy applied
- #thus initializing it with empty string value.
- USER_INFO_3 = [
- [:name, nil],
- [:password, ""],
- [:password_age, 0],
- [:priv, 0], #"The NetUserAdd and NetUserSetInfo functions ignore this member"
- [:home_dir, nil],
- [:comment, nil],
- [:flags, UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_NORMAL_ACCOUNT],
- [:script_path, nil],
- [:auth_flags, 0],
- [:full_name, nil],
- [:user_comment, nil],
- [:parms, nil],
- [:workstations, nil],
- [:last_logon, 0],
- [:last_logoff, 0],
- [:acct_expires, -1],
- [:max_storage, -1],
- [:units_per_week, 0],
- [:logon_hours, nil],
- [:bad_pw_count, 0],
- [:num_logons, 0],
- [:logon_server, nil],
- [:country_code, 0],
- [:code_page, 0],
- [:user_id, 0],
- [:primary_group_id, DOMAIN_GROUP_RID_USERS],
- [:profile, nil],
- [:home_dir_drive, nil],
- [:password_expired, 0]
- ]
-
- USER_INFO_3_TEMPLATE =
- USER_INFO_3.collect { |field| field[1].class == Fixnum ? 'i' : 'L' }.join
-
- SIZEOF_USER_INFO_3 = #sizeof(USER_INFO_3)
- USER_INFO_3.inject(0){|sum,item|
- sum + (item[1].class == Fixnum ? 4 : PTR_SIZE)
- }
-
- def user_info_3(args)
- USER_INFO_3.collect { |field|
- args.include?(field[0]) ? args[field[0]] : field[1]
- }
- end
-
- def user_info_3_pack(user)
- user.collect { |v|
- v.class == Fixnum ? v : str_to_ptr(multi_to_wide(v))
- }.pack(USER_INFO_3_TEMPLATE)
+ NetUser = Chef::ReservedNames::Win32::NetUser
+ Security = Chef::ReservedNames::Win32::Security
+
+ USER_INFO_3_TRANSFORM = {
+ name: :usri3_name,
+ password: :usri3_password,
+ password_age: :usri3_password_age,
+ priv: :usri3_priv,
+ home_dir: :usri3_home_dir,
+ comment: :usri3_comment,
+ flags: :usri3_flags,
+ script_path: :usri3_script_path,
+ auth_flags: :usri3_auth_flags,
+ full_name: :usri3_full_name,
+ user_comment: :usri3_usr_comment,
+ parms: :usri3_parms,
+ workstations: :usri3_workstations,
+ last_logon: :usri3_last_logon,
+ last_logoff: :usri3_last_logoff,
+ acct_expires: :usri3_acct_expires,
+ max_storage: :usri3_max_storage,
+ units_per_week: :usri3_units_per_week,
+ logon_hours: :usri3_logon_hours,
+ bad_pw_count: :usri3_bad_pw_count,
+ num_logons: :usri3_num_logons,
+ logon_server: :usri3_logon_server,
+ country_code: :usri3_country_code,
+ code_page: :usri3_code_page,
+ user_id: :usri3_user_id,
+ primary_group_id: :usri3_primary_group_id,
+ profile: :usri3_profile,
+ home_dir_drive: :usri3_home_dir_drive,
+ password_expired: :usri3_password_expired,
+ }
+
+ def transform_usri3(args)
+ args.inject({}) do |memo, (k,v)|
+ memo[USER_INFO_3_TRANSFORM[k]] = v
+ memo
+ end
end
- def user_info_3_unpack(buffer)
- user = Hash.new
- USER_INFO_3.each_with_index do |field,offset|
- user[field[0]] = field[1].class == Fixnum ?
- dword_to_i(buffer, offset) : lpwstr_to_s(buffer, offset)
+ def usri3_to_hash(usri3)
+ t = USER_INFO_3_TRANSFORM.invert
+ usri3.inject({}) do |memo, (k,v)|
+ memo[t[k]] = v
+ memo
end
- user
end
def set_info(args)
- user = user_info_3(args)
- buffer = user_info_3_pack(user)
- rc = NetUserSetInfo.call(nil, @name, 3, buffer, nil)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ rc = NetUser::net_user_set_info_l3(nil, @username, transform_usri3(args))
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
@@ -117,52 +88,34 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
def initialize(username)
@username = username
- @name = multi_to_wide(username)
end
- LOGON32_PROVIDER_DEFAULT = 0
- LOGON32_LOGON_NETWORK = 3
+ LOGON32_PROVIDER_DEFAULT = Security::LOGON32_PROVIDER_DEFAULT
+ LOGON32_LOGON_NETWORK = Security::LOGON32_LOGON_NETWORK
#XXX for an extra painful alternative, see: http://support.microsoft.com/kb/180548
def validate_credentials(passwd)
- token = 0.chr * PTR_SIZE
- res = LogonUser.call(@username, nil, passwd,
- LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token)
- if res == 0
+ begin
+ token = Security::logon_user(@username, nil, passwd,
+ LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT)
+ return true
+ rescue Chef::Exceptions::Win32APIError
return false
end
- ::Windows::Handle::CloseHandle.call(token.unpack('L')[0])
- return true
end
def get_info
- ptr = 0.chr * PTR_SIZE
- rc = NetUserGetInfo.call(nil, @name, 3, ptr)
-
- if rc == NERR_UserNotFound
- raise Chef::Exceptions::UserIDNotFound, get_last_error(rc)
- elsif rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ ui3 = NetUser::net_user_get_info_l3(nil, @username)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
-
- ptr = ptr.unpack('L')[0]
- buffer = 0.chr * SIZEOF_USER_INFO_3
- memcpy(buffer, ptr, buffer.size)
- NetApiBufferFree(ptr)
- user_info_3_unpack(buffer)
+ usri3_to_hash(ui3)
end
def add(args)
- user = user_info_3(args)
- buffer = user_info_3_pack(user)
-
- rc = NetUserAdd.call(nil, 3, buffer, rc)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
- end
-
- #usri3_primary_group_id:
- #"When you call the NetUserAdd function, this member must be DOMAIN_GROUP_RID_USERS"
- NetLocalGroupAddMembers(nil, multi_to_wide("Users"), 3, buffer[0,PTR_SIZE], 1)
+ transformed_args = transform_usri3(args)
+ NetUser::net_user_add_l3(nil, transformed_args)
+ NetUser::net_local_group_add_member(nil, "Users", args[:name])
end
def user_modify(&proc)
@@ -182,15 +135,16 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def delete
- rc = NetUserDel.call(nil, @name)
- if rc != NERR_Success
- raise ArgumentError, get_last_error(rc)
+ begin
+ NetUser::net_user_del(nil, @username)
+ rescue Chef::Exceptions::Win32APIError => e
+ raise ArgumentError, e
end
end
def disable_account
user_modify do |user|
- user[:flags] |= UF_ACCOUNTDISABLE
+ user[:flags] |= NetUser::UF_ACCOUNTDISABLE
#This does not set the password to nil. It (for some reason) means to ignore updating the field.
#See similar behavior for the logon_hours field documented at
#http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -200,7 +154,7 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
def enable_account
user_modify do |user|
- user[:flags] &= ~UF_ACCOUNTDISABLE
+ user[:flags] &= ~NetUser::UF_ACCOUNTDISABLE
#This does not set the password to nil. It (for some reason) means to ignore updating the field.
#See similar behavior for the logon_hours field documented at
#http://msdn.microsoft.com/en-us/library/windows/desktop/aa371338%28v=vs.85%29.aspx
@@ -209,6 +163,6 @@ class Chef::Util::Windows::NetUser < Chef::Util::Windows
end
def check_enabled
- (get_info()[:flags] & UF_ACCOUNTDISABLE) != 0
+ (get_info()[:flags] & NetUser::UF_ACCOUNTDISABLE) != 0
end
end
diff --git a/lib/chef/util/windows/volume.rb b/lib/chef/util/windows/volume.rb
index 08c3a53793..7b24ec37aa 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/util/windows'
-require 'windows/volume'
+require "chef/win32/api/file"
+require "chef/util/windows"
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 3fa3939a62..0a7b5f66e0 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.3.0.dev.0'
+ VERSION = "12.6.0"
end
#
@@ -25,6 +29,6 @@ end
#
# NOTE: DO NOT Use the Chef::Version class on Chef::VERSIONs. The
# Chef::Version class is for _cookbooks_ only, and cannot handle
-# pre-release chef-client versions like "10.14.0.rc.2". Please
-# use Rubygem's Gem::Version class instead.
+# pre-release versions like "10.14.0.rc.2". Please use Rubygem's
+# Gem::Version class instead.
#
diff --git a/lib/chef/version/platform.rb b/lib/chef/version/platform.rb
index 81e7614646..f8d5b1ce35 100644
--- a/lib/chef/version/platform.rb
+++ b/lib/chef/version/platform.rb
@@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/version_class'
+require "chef/version_class"
class Chef
class Version
@@ -34,7 +34,7 @@ class Chef
when /^(\d+).(\d+)-[a-z]+\d?(-p(\d+))?$/i # Match FreeBSD
[ $1.to_i, $2.to_i, ($4 ? $4.to_i : 0)]
else
- msg = "'#{str.to_s}' does not match 'x.y.z', 'x.y' or 'x'"
+ msg = "'#{str}' does not match 'x.y.z', 'x.y' or 'x'"
raise Chef::Exceptions::InvalidPlatformVersion.new( msg )
end
end
diff --git a/lib/chef/version_class.rb b/lib/chef/version_class.rb
index 01af6f1f55..913c50442d 100644
--- a/lib/chef/version_class.rb
+++ b/lib/chef/version_class.rb
@@ -61,7 +61,7 @@ class Chef
when /^(\d+)\.(\d+)$/
[ $1.to_i, $2.to_i, 0 ]
else
- msg = "'#{str.to_s}' does not match 'x.y.z' or 'x.y'"
+ msg = "'#{str}' does not match 'x.y.z' or 'x.y'"
raise Chef::Exceptions::InvalidCookbookVersion.new( msg )
end
end
diff --git a/lib/chef/version_constraint.rb b/lib/chef/version_constraint.rb
index a78e32e94f..ef0849a120 100644
--- a/lib/chef/version_constraint.rb
+++ b/lib/chef/version_constraint.rb
@@ -14,13 +14,13 @@
# WITHOUT WARRANTIES OR 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'
+require "chef/version_class"
class Chef
class VersionConstraint
DEFAULT_CONSTRAINT = ">= 0.0.0"
- STANDARD_OPS = %w(< > <= >=)
- OPS = %w(< > = <= >= ~>)
+ STANDARD_OPS = %w{< > <= >=}
+ OPS = %w{< > = <= >= ~>}
PATTERN = /^(#{OPS.join('|')}) *([0-9].*)$/
VERSION_CLASS = Chef::Version
@@ -50,7 +50,7 @@ class Chef
end
def inspect
- "(#{to_s})"
+ "(#{self})"
end
def to_s
@@ -67,9 +67,9 @@ class Chef
def do_op(other_version)
if STANDARD_OPS.include? @op
other_version.send(@op.to_sym, @version)
- elsif @op == '='
+ elsif @op == "="
other_version == @version
- elsif @op == '~>'
+ elsif @op == "~>"
if @missing_patch_level
(other_version.major == @version.major &&
other_version.minor >= @version.minor)
@@ -106,7 +106,7 @@ class Chef
@op = $1
@raw_version = $2
@version = self.class::VERSION_CLASS.new(@raw_version)
- if @raw_version.split('.').size <= 2
+ if @raw_version.split(".").size <= 2
@missing_patch_level = true
end
else
diff --git a/lib/chef/version_constraint/platform.rb b/lib/chef/version_constraint/platform.rb
index ada4f29b70..423ae4f0c5 100644
--- a/lib/chef/version_constraint/platform.rb
+++ b/lib/chef/version_constraint/platform.rb
@@ -13,8 +13,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-require 'chef/version_constraint'
-require 'chef/version/platform'
+require "chef/version_constraint"
+require "chef/version/platform"
class Chef
class VersionConstraint
diff --git a/lib/chef/whitelist.rb b/lib/chef/whitelist.rb
index 3682f7187e..ca4be38d7f 100644
--- a/lib/chef/whitelist.rb
+++ b/lib/chef/whitelist.rb
@@ -1,5 +1,5 @@
-require 'chef/exceptions'
+require "chef/exceptions"
class Chef
class Whitelist
diff --git a/lib/chef/win32/api.rb b/lib/chef/win32/api.rb
index efa632f454..63f7dbdab7 100644
--- a/lib/chef/win32/api.rb
+++ b/lib/chef/win32/api.rb
@@ -17,9 +17,9 @@
# limitations under the License.
#
-require 'ffi'
-require 'chef/reserved_names'
-require 'chef/exceptions'
+require "ffi"
+require "chef/reserved_names"
+require "chef/exceptions"
class Chef
module ReservedNames::Win32
@@ -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
@@ -146,6 +147,8 @@ class Chef
host.typedef :long, :LRESULT # Signed result of message processing. WinDef.h: host.typedef LONG_PTR LRESULT;
host.typedef :pointer, :LPWIN32_FIND_DATA # Pointer to WIN32_FIND_DATA struct
host.typedef :pointer, :LPBY_HANDLE_FILE_INFORMATION # Point to a BY_HANDLE_FILE_INFORMATION struct
+ host.typedef :pointer, :LSA_HANDLE # A handle to a Policy object
+ host.typedef :ulong, :NTSTATUS # An NTSTATUS code returned by an LSA function call.
host.typedef :pointer, :PBOOL # Pointer to a BOOL.
host.typedef :pointer, :PBOOLEAN # Pointer to a BOOL.
host.typedef :pointer, :PBYTE # Pointer to a BYTE.
@@ -173,12 +176,16 @@ class Chef
host.typedef :pointer, :PLONG_PTR # Pointer to a LONG_PTR.
host.typedef :pointer, :PLONG32 # Pointer to a LONG32.
host.typedef :pointer, :PLONG64 # Pointer to a LONG64.
+ host.typedef :pointer, :PLSA_HANDLE # Pointer to an LSA_HANDLE
+ host.typedef :pointer, :PLSA_OBJECT_ATTRIBUTES # Pointer to an LSA_OBJECT_ATTRIBUTES
+ host.typedef :pointer, :PLSA_UNICODE_STRING # Pointer to LSA_UNICODE_STRING
host.typedef :pointer, :PLUID # Pointer to a LUID.
host.typedef :pointer, :POINTER_32 # 32-bit pointer. On a 32-bit system, this is a native pointer. On a 64-bit system, this is a truncated 64-bit pointer.
host.typedef :pointer, :POINTER_64 # 64-bit pointer. On a 64-bit system, this is a native pointer. On a 32-bit system, this is a sign-extended 32-bit pointer.
host.typedef :pointer, :POINTER_SIGNED # A signed pointer.
host.typedef :pointer, :POINTER_UNSIGNED # An unsigned pointer.
host.typedef :pointer, :PSHORT # Pointer to a SHORT.
+ host.typedef :pointer, :PSID # Pointer to an account SID
host.typedef :pointer, :PSIZE_T # Pointer to a SIZE_T.
host.typedef :pointer, :PSSIZE_T # Pointer to a SSIZE_T.
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.
diff --git a/lib/chef/win32/api/crypto.rb b/lib/chef/win32/api/crypto.rb
index 1837a57557..9cf8387433 100644
--- a/lib/chef/win32/api/crypto.rb
+++ b/lib/chef/win32/api/crypto.rb
@@ -1,63 +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
+#
+# 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/error.rb b/lib/chef/win32/api/error.rb
index d1f9a309fe..0ea3633202 100644
--- a/lib/chef/win32/api/error.rb
+++ b/lib/chef/win32/api/error.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -877,7 +877,7 @@ class Chef
# Win32 API Bindings
###############################################
- ffi_lib 'kernel32', 'user32'
+ ffi_lib "kernel32", "user32"
=begin
DWORD WINAPI FormatMessage(
diff --git a/lib/chef/win32/api/file.rb b/lib/chef/win32/api/file.rb
index 86b2b942c2..51ae3b47c7 100644
--- a/lib/chef/win32/api/file.rb
+++ b/lib/chef/win32/api/file.rb
@@ -17,9 +17,10 @@
# limitations under the License.
#
-require 'chef/win32/api'
-require 'chef/win32/api/security'
-require 'chef/win32/api/system'
+require "chef/win32/api"
+require "chef/win32/api/security"
+require "chef/win32/api/system"
+require "chef/win32/unicode"
class Chef
module ReservedNames::Win32
@@ -181,7 +182,14 @@ class Chef
# Win32 API Bindings
###############################################
- ffi_lib 'kernel32'
+ ffi_lib "kernel32", "version"
+
+ # Does not map directly to a win32 struct
+ # see https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx
+ class Translation < FFI::Struct
+ layout :w_lang, :WORD,
+ :w_code_page, :WORD
+ end
=begin
typedef struct _FILETIME {
@@ -450,6 +458,53 @@ BOOL WINAPI DeviceIoControl(
=end
safe_attach_function :DeviceIoControl, [:HANDLE, :DWORD, :LPVOID, :DWORD, :LPVOID, :DWORD, :LPDWORD, :pointer], :BOOL
+
+#BOOL WINAPI DeleteVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint
+#);
+ safe_attach_function :DeleteVolumeMountPointW, [:LPCTSTR], :BOOL
+
+#BOOL WINAPI SetVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint,
+ #_In_ LPCTSTR lpszVolumeName
+#);
+ safe_attach_function :SetVolumeMountPointW, [:LPCTSTR, :LPCTSTR], :BOOL
+
+#BOOL WINAPI GetVolumeNameForVolumeMountPoint(
+ #_In_ LPCTSTR lpszVolumeMountPoint,
+ #_Out_ LPTSTR lpszVolumeName,
+ #_In_ DWORD cchBufferLength
+#);
+ safe_attach_function :GetVolumeNameForVolumeMountPointW, [:LPCTSTR, :LPTSTR, :DWORD], :BOOL
+
+=begin
+BOOL WINAPI GetFileVersionInfo(
+ _In_ LPCTSTR lptstrFilename,
+ _Reserved_ DWORD dwHandle,
+ _In_ DWORD dwLen,
+ _Out_ LPVOID lpData
+);
+=end
+ safe_attach_function :GetFileVersionInfoW, [:LPCTSTR, :DWORD, :DWORD, :LPVOID], :BOOL
+
+=begin
+DWORD WINAPI GetFileVersionInfoSize(
+ _In_ LPCTSTR lptstrFilename,
+ _Out_opt_ LPDWORD lpdwHandle
+);
+=end
+ safe_attach_function :GetFileVersionInfoSizeW, [:LPCTSTR, :LPDWORD], :DWORD
+
+=begin
+BOOL WINAPI VerQueryValue(
+ _In_ LPCVOID pBlock,
+ _In_ LPCTSTR lpSubBlock,
+ _Out_ LPVOID *lplpBuffer,
+ _Out_ PUINT puLen
+);
+=end
+ safe_attach_function :VerQueryValueW, [:LPCVOID, :LPCTSTR, :LPVOID, :PUINT], :BOOL
+
###############################################
# Helpers
###############################################
@@ -545,6 +600,21 @@ BOOL WINAPI DeviceIoControl(
file_information
end
+ def retrieve_file_version_info(file_name)
+ file_name = encode_path(file_name)
+ file_size = GetFileVersionInfoSizeW(file_name, nil)
+ if file_size == 0
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+
+ version_info = FFI::MemoryPointer.new(file_size)
+ unless GetFileVersionInfoW(file_name, 0, file_size, version_info)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+
+ version_info
+ end
+
end
end
end
diff --git a/lib/chef/win32/api/installer.rb b/lib/chef/win32/api/installer.rb
index 6864a26e7d..20d021b9ab 100644
--- a/lib/chef/win32/api/installer.rb
+++ b/lib/chef/win32/api/installer.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/exceptions'
-require 'chef/win32/api'
-require 'chef/win32/error'
-require 'pathname'
+require "chef/exceptions"
+require "chef/win32/api"
+require "chef/win32/error"
+require "pathname"
class Chef
module ReservedNames::Win32
@@ -37,7 +37,7 @@ class Chef
# Win32 API Bindings
###############################################
- ffi_lib 'msi'
+ ffi_lib "msi"
=begin
UINT MsiOpenPackage(
@@ -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/memory.rb b/lib/chef/win32/api/memory.rb
index abd1191718..2c3e305f08 100644
--- a/lib/chef/win32/api/memory.rb
+++ b/lib/chef/win32/api/memory.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -48,7 +48,7 @@ class Chef
# Win32 API Bindings
###############################################
- ffi_lib 'kernel32'
+ ffi_lib "kernel32"
=begin
HLOCAL WINAPI LocalAlloc(
diff --git a/lib/chef/win32/api/net.rb b/lib/chef/win32/api/net.rb
index eeb2b078a4..a3710bdaf5 100644
--- a/lib/chef/win32/api/net.rb
+++ b/lib/chef/win32/api/net.rb
@@ -16,7 +16,8 @@
# limitations under the License.
#
-require 'chef/win32/api'
+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,24 +132,192 @@ 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 NetUserEnum(
-# _In_ LPCWSTR servername,
-# _In_ DWORD level,
-# _In_ DWORD filter,
-# _Out_ LPBYTE *bufptr,
-# _In_ DWORD prefmaxlen,
-# _Out_ LPDWORD entriesread,
-# _Out_ LPDWORD totalentries,
-# _Inout_ LPDWORD resume_handle
-# );
- 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
+
+ #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,
+ # _In_ DWORD filter,
+ # _Out_ LPBYTE *bufptr,
+ # _In_ DWORD prefmaxlen,
+ # _Out_ LPDWORD entriesread,
+ # _Out_ LPDWORD totalentries,
+ # _Inout_ LPDWORD resume_handle
+ # );
+ 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
+
+ #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/process.rb b/lib/chef/win32/api/process.rb
index 217880b737..6c4e567f74 100644
--- a/lib/chef/win32/api/process.rb
+++ b/lib/chef/win32/api/process.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -28,7 +28,7 @@ class Chef
# Win32 API Bindings
###############################################
- ffi_lib 'kernel32'
+ ffi_lib "kernel32"
safe_attach_function :GetCurrentProcess, [], :HANDLE
safe_attach_function :GetProcessHandleCount, [ :HANDLE, :LPDWORD ], :BOOL
diff --git a/lib/chef/win32/api/psapi.rb b/lib/chef/win32/api/psapi.rb
index 3a5df3f179..347396b50a 100644
--- a/lib/chef/win32/api/psapi.rb
+++ b/lib/chef/win32/api/psapi.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -41,7 +41,7 @@ class Chef
:PeakPagefileUsage, :SIZE_T
end
- ffi_lib 'psapi'
+ ffi_lib "psapi"
safe_attach_function :GetProcessMemoryInfo, [ :HANDLE, :pointer, :DWORD ], :BOOL
diff --git a/lib/chef/win32/api/registry.rb b/lib/chef/win32/api/registry.rb
new file mode 100644
index 0000000000..d01700f159
--- /dev/null
+++ b/lib/chef/win32/api/registry.rb
@@ -0,0 +1,51 @@
+#
+# 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
+
+ # LONG WINAPI RegDeleteValue(
+ # _In_ HKEY hKey,
+ # _In_opt_ LPCTSTR lpValueName
+ # );
+ safe_attach_function :RegDeleteValueW, [ :HKEY, :LPCTSTR ], :LONG
+
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb
index 229f2ace10..13b86fb670 100644
--- a/lib/chef/win32/api/security.rb
+++ b/lib/chef/win32/api/security.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -193,6 +193,35 @@ 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;
+
+ # LSA access policy
+ POLICY_VIEW_LOCAL_INFORMATION = 0x00000001
+ POLICY_VIEW_AUDIT_INFORMATION = 0x00000002
+ POLICY_GET_PRIVATE_INFORMATION = 0x00000004
+ POLICY_TRUST_ADMIN = 0x00000008
+ POLICY_CREATE_ACCOUNT = 0x00000010
+ POLICY_CREATE_SECRET = 0x00000020
+ POLICY_CREATE_PRIVILEGE = 0x00000040
+ POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080
+ POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100
+ POLICY_AUDIT_LOG_ADMIN = 0x00000200
+ POLICY_SERVER_ADMIN = 0x00000400
+ POLICY_LOOKUP_NAMES = 0x00000800
+ POLICY_NOTIFICATION = 0x00001000
+
###############################################
# Win32 API Bindings
###############################################
@@ -210,7 +239,7 @@ class Chef
:SE_DS_OBJECT_ALL,
:SE_PROVIDER_DEFINED_OBJECT,
:SE_WMIGUID_OBJECT,
- :SE_REGISTRY_WOW64_32KEY
+ :SE_REGISTRY_WOW64_32KEY,
]
SID_NAME_USE = enum :SID_NAME_USE, [
@@ -270,12 +299,20 @@ 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,
:SecurityIdentification,
:SecurityImpersonation,
- :SecurityDelegation
+ :SecurityDelegation,
]
@@ -314,7 +351,7 @@ class Chef
ACCESS_ALLOWED_ACE_TYPE,
ACCESS_DENIED_ACE_TYPE,
SYSTEM_AUDIT_ACE_TYPE,
- SYSTEM_ALARM_ACE_TYPE
+ SYSTEM_ALARM_ACE_TYPE,
].include?(ace_type)
end
end
@@ -359,6 +396,23 @@ class Chef
end
end
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/ms721829(v=vs.85).aspx
+ class LSA_OBJECT_ATTRIBUTES < FFI::Struct
+ layout :Length, :ULONG,
+ :RootDirectory, :HANDLE,
+ :ObjectName, :pointer,
+ :Attributes, :ULONG,
+ :SecurityDescriptor, :PVOID,
+ :SecurityQualityOfService, :PVOID
+ end
+
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/ms721841(v=vs.85).aspx
+ class LSA_UNICODE_STRING < FFI::Struct
+ layout :Length, :USHORT,
+ :MaximumLength, :USHORT,
+ :Buffer, :PWSTR
+ end
+
ffi_lib "advapi32"
safe_attach_function :AccessCheck, [:pointer, :HANDLE, :DWORD, :pointer, :pointer, :pointer, :pointer, :pointer], :BOOL
@@ -393,6 +447,12 @@ class Chef
safe_attach_function :LookupPrivilegeNameW, [ :LPCWSTR, :PLUID, :LPWSTR, :LPDWORD ], :BOOL
safe_attach_function :LookupPrivilegeDisplayNameW, [ :LPCWSTR, :LPCWSTR, :LPWSTR, :LPDWORD, :LPDWORD ], :BOOL
safe_attach_function :LookupPrivilegeValueW, [ :LPCWSTR, :LPCWSTR, :PLUID ], :BOOL
+ safe_attach_function :LsaAddAccountRights, [ :pointer, :pointer, :pointer, :ULONG ], :NTSTATUS
+ safe_attach_function :LsaClose, [ :LSA_HANDLE ], :NTSTATUS
+ safe_attach_function :LsaEnumerateAccountRights, [ :LSA_HANDLE, :PSID, :PLSA_UNICODE_STRING, :PULONG ], :NTSTATUS
+ safe_attach_function :LsaFreeMemory, [ :PVOID ], :NTSTATUS
+ safe_attach_function :LsaNtStatusToWinError, [ :NTSTATUS ], :ULONG
+ safe_attach_function :LsaOpenPolicy, [ :PLSA_UNICODE_STRING, :PLSA_OBJECT_ATTRIBUTES, :DWORD, :PLSA_HANDLE ], :NTSTATUS
safe_attach_function :MakeAbsoluteSD, [ :pointer, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD, :pointer, :LPDWORD], :BOOL
safe_attach_function :MapGenericMask, [ :PDWORD, :PGENERICMAPPING ], :void
safe_attach_function :OpenProcessToken, [ :HANDLE, :DWORD, :PHANDLE ], :BOOL
@@ -405,6 +465,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/synchronization.rb b/lib/chef/win32/api/synchronization.rb
index 9c148d7e2b..b541e5aaf3 100644
--- a/lib/chef/win32/api/synchronization.rb
+++ b/lib/chef/win32/api/synchronization.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -24,7 +24,7 @@ class Chef
module Synchronization
extend Chef::ReservedNames::Win32::API
- ffi_lib 'kernel32'
+ ffi_lib "kernel32"
# Constant synchronization functions use to indicate wait
# forever.
diff --git a/lib/chef/win32/api/system.rb b/lib/chef/win32/api/system.rb
index d57699acb4..218a9b95e7 100644
--- a/lib/chef/win32/api/system.rb
+++ b/lib/chef/win32/api/system.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -132,7 +132,7 @@ class Chef
# Win32 API Bindings
###############################################
- ffi_lib 'kernel32', 'user32'
+ ffi_lib "kernel32", "user32"
class OSVERSIONINFOEX < FFI::Struct
layout :dw_os_version_info_size, :DWORD,
@@ -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..29cdfba5fd 100644
--- a/lib/chef/win32/api/unicode.rb
+++ b/lib/chef/win32/api/unicode.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/api'
+require "chef/win32/api"
class Chef
module ReservedNames::Win32
@@ -92,7 +92,7 @@ class Chef
# Win32 API Bindings
###############################################
- ffi_lib 'kernel32', 'advapi32'
+ ffi_lib "kernel32", "advapi32"
=begin
BOOL IsTextUnicode(
@@ -129,49 +129,6 @@ int WideCharToMultiByte(
=end
safe_attach_function :WideCharToMultiByte, [:UINT, :DWORD, :LPCWSTR, :int, :LPSTR, :int, :LPCSTR, :LPBOOL], :int
- ###############################################
- # Helpers
- ###############################################
-
- def utf8_to_wide(ustring)
- # ensure it is actually UTF-8
- # Ruby likes to mark binary data as ASCII-8BIT
- ustring = (ustring + "").force_encoding('UTF-8') if ustring.respond_to?(:force_encoding) && ustring.encoding.name != "UTF-8"
-
- # ensure we have the double-null termination Windows Wide likes
- ustring = ustring + "\000\000" if ustring[-1].chr != "\000"
-
- # encode it all as UTF-16LE AKA Windows Wide Character AKA Windows Unicode
- ustring = begin
- if ustring.respond_to?(:encode)
- ustring.encode('UTF-16LE')
- else
- require 'iconv'
- Iconv.conv("UTF-16LE", "UTF-8", ustring)
- end
- end
- ustring
- end
-
- def wide_to_utf8(wstring)
- # ensure it is actually UTF-16LE
- # Ruby likes to mark binary data as ASCII-8BIT
- wstring = wstring.force_encoding('UTF-16LE') if wstring.respond_to?(:force_encoding)
-
- # encode it all as UTF-8
- wstring = begin
- if wstring.respond_to?(:encode)
- wstring.encode('UTF-8')
- else
- require 'iconv'
- Iconv.conv("UTF-8", "UTF-16LE", wstring)
- end
- end
- # remove trailing CRLF and NULL characters
- wstring.strip!
- wstring
- end
-
end
end
end
diff --git a/lib/chef/win32/crypto.rb b/lib/chef/win32/crypto.rb
index 79cf51b002..8804872ee1 100644
--- a/lib/chef/win32/crypto.rb
+++ b/lib/chef/win32/crypto.rb
@@ -1,49 +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 '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
+#
+# 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, CRYPTPROTECT_LOCAL_MACHINE, 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/error.rb b/lib/chef/win32/error.rb
index 2175608eeb..75b65854a9 100644
--- a/lib/chef/win32/error.rb
+++ b/lib/chef/win32/error.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/win32/api/error'
-require 'chef/win32/memory'
-require 'chef/win32/unicode'
-require 'chef/exceptions'
+require "chef/win32/api/error"
+require "chef/win32/memory"
+require "chef/win32/unicode"
+require "chef/exceptions"
class Chef
module ReservedNames::Win32
@@ -57,8 +57,7 @@ class Chef
# nil::: always returns nil when it does not raise
# === Raises
# Chef::Exceptions::Win32APIError:::
- def self.raise!(message = nil)
- code = get_last_error
+ def self.raise!(message = nil, code = get_last_error)
msg = format_message(code).strip
formatted_message = ""
formatted_message << message if message
diff --git a/lib/chef/win32/eventlog.rb b/lib/chef/win32/eventlog.rb
new file mode 100644
index 0000000000..a056900101
--- /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..3279e22ecf 100644
--- a/lib/chef/win32/file.rb
+++ b/lib/chef/win32/file.rb
@@ -17,9 +17,11 @@
# limitations under the License.
#
-require 'chef/win32/api/file'
-require 'chef/win32/api/security'
-require 'chef/win32/error'
+require "chef/mixin/wide_string"
+require "chef/win32/api/file"
+require "chef/win32/api/security"
+require "chef/win32/error"
+require "chef/win32/unicode"
class Chef
module ReservedNames::Win32
@@ -27,6 +29,9 @@ class Chef
include Chef::ReservedNames::Win32::API::File
extend Chef::ReservedNames::Win32::API::File
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
# Creates a symbolic link called +new_name+ for the file or directory
# +old_name+.
#
@@ -145,6 +150,10 @@ class Chef
Info.new(file_name)
end
+ def self.version_info(file_name)
+ VersionInfo.new(file_name)
+ end
+
def self.verify_links_supported!
begin
CreateSymbolicLinkW(nil)
@@ -157,9 +166,9 @@ class Chef
def self.file_access_check(path, desired_access)
security_descriptor = Chef::ReservedNames::Win32::Security.get_file_security(path)
- token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE |
+ token_rights = Chef::ReservedNames::Win32::Security::TOKEN_IMPERSONATE |
Chef::ReservedNames::Win32::Security::TOKEN_QUERY |
- Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE |
+ Chef::ReservedNames::Win32::Security::TOKEN_DUPLICATE |
Chef::ReservedNames::Win32::Security::STANDARD_RIGHTS_READ
token = Chef::ReservedNames::Win32::Security.open_process_token(
Chef::ReservedNames::Win32::Process.get_current_process,
@@ -172,10 +181,30 @@ class Chef
mapping[:GenericExecute] = Chef::ReservedNames::Win32::Security::FILE_GENERIC_EXECUTE
mapping[:GenericAll] = Chef::ReservedNames::Win32::Security::FILE_ALL_ACCESS
- Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
+ Chef::ReservedNames::Win32::Security.access_check(security_descriptor, duplicate_token,
desired_access, mapping)
end
+ def self.delete_volume_mount_point(mount_point)
+ unless DeleteVolumeMountPointW(wstring(mount_point))
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ end
+
+ def self.set_volume_mount_point(mount_point, name)
+ unless SetVolumeMountPointW(wstring(mount_point), wstring(name))
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ end
+
+ def self.get_volume_name_for_volume_mount_point(mount_point)
+ buffer = FFI::MemoryPointer.new(2, 128)
+ unless GetVolumeNameForVolumeMountPointW(wstring(mount_point), buffer, buffer.size/buffer.type_size)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+ buffer.read_wstring
+ end
+
# ::File compat
class << self
alias :stat :info
@@ -185,4 +214,5 @@ class Chef
end
end
-require 'chef/win32/file/info'
+require "chef/win32/file/info"
+require "chef/win32/file/version_info"
diff --git a/lib/chef/win32/file/info.rb b/lib/chef/win32/file/info.rb
index 0f07428106..70d04df9b8 100644
--- a/lib/chef/win32/file/info.rb
+++ b/lib/chef/win32/file/info.rb
@@ -16,7 +16,7 @@
# limitations under the License.
#
-require 'chef/win32/file'
+require "chef/win32/file"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/file/version_info.rb b/lib/chef/win32/file/version_info.rb
new file mode 100644
index 0000000000..bd7ed511a5
--- /dev/null
+++ b/lib/chef/win32/file/version_info.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Matt Wrock (<matt@mattwrock.com>)
+# Copyright:: Copyright 2015 Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/win32/file"
+
+class Chef
+ module ReservedNames::Win32
+ class File
+
+ class VersionInfo
+
+ include Chef::ReservedNames::Win32::API::File
+
+ def initialize(file_name)
+ raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
+ @file_version_info = retrieve_file_version_info(file_name)
+ end
+
+ # defining method for each predefined version resource string
+ # see https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx
+ [
+ :Comments,
+ :CompanyName,
+ :FileDescription,
+ :FileVersion,
+ :InternalName,
+ :LegalCopyright,
+ :LegalTrademarks,
+ :OriginalFilename,
+ :ProductName,
+ :ProductVersion,
+ :PrivateBuild,
+ :SpecialBuild,
+ ].each do |method|
+ define_method method do
+ begin
+ get_version_info_string(method.to_s)
+ rescue Chef::Exceptions::Win32APIError
+ return nil
+ end
+ end
+ end
+
+ private
+
+ def translation
+ @translation ||= begin
+ info_ptr = FFI::MemoryPointer.new(:pointer)
+ unless VerQueryValueW(@file_version_info, "\\VarFileInfo\\Translation".to_wstring, info_ptr, FFI::MemoryPointer.new(:int))
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+
+ # there can potentially be multiple translations but most installers just have one
+ # we use the first because we use this for the version strings which are language
+ # agnostic. If/when we need other fields, we should we should add logic to find
+ # the "best" translation
+ trans = Translation.new(info_ptr.read_pointer)
+ to_hex(trans[:w_lang]) + to_hex(trans[:w_code_page])
+ end
+ end
+
+ def to_hex(integer)
+ integer.to_s(16).rjust(4,"0")
+ end
+
+ def get_version_info_string(string_key)
+ info_ptr = FFI::MemoryPointer.new(:pointer)
+ size_ptr = FFI::MemoryPointer.new(:int)
+ unless VerQueryValueW(@file_version_info, "\\StringFileInfo\\#{translation}\\#{string_key}".to_wstring, info_ptr, size_ptr)
+ Chef::ReservedNames::Win32::Error.raise!
+ end
+
+ info_ptr.read_pointer.read_wstring(size_ptr.read_uint)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/win32/handle.rb b/lib/chef/win32/handle.rb
index 21a8fdf339..66a06f45ad 100644
--- a/lib/chef/win32/handle.rb
+++ b/lib/chef/win32/handle.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/win32/api/process'
-require 'chef/win32/api/psapi'
-require 'chef/win32/api/system'
-require 'chef/win32/error'
+require "chef/win32/api/process"
+require "chef/win32/api/psapi"
+require "chef/win32/api/system"
+require "chef/win32/error"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/memory.rb b/lib/chef/win32/memory.rb
index 8a61d27ef0..9c52b59b68 100644
--- a/lib/chef/win32/memory.rb
+++ b/lib/chef/win32/memory.rb
@@ -16,8 +16,8 @@
# limitations under the License.
#
-require 'chef/win32/error'
-require 'chef/win32/api/memory'
+require "chef/win32/error"
+require "chef/win32/api/memory"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/mutex.rb b/lib/chef/win32/mutex.rb
index 0b7d99f111..b7b9f8fd51 100644
--- a/lib/chef/win32/mutex.rb
+++ b/lib/chef/win32/mutex.rb
@@ -16,7 +16,8 @@
# limitations under the License.
#
-require 'chef/win32/api/synchronization'
+require "chef/win32/api/synchronization"
+require "chef/win32/unicode"
class Chef
module ReservedNames::Win32
@@ -78,7 +79,7 @@ class Chef
# of the process goes away and this class is only being used
# to synchronize chef-clients runs on a node.
Chef::Log.error("Can not release mutex '#{name}'. This might cause issues \
-if the mutex is attempted to be acquired by other threads.")
+if other threads attempt to acquire the mutex.")
Chef::ReservedNames::Win32::Error.raise!
end
end
@@ -113,5 +114,3 @@ if the mutex is attempted to be acquired by other threads.")
end
end
end
-
-
diff --git a/lib/chef/win32/net.rb b/lib/chef/win32/net.rb
new file mode 100644
index 0000000000..c3c57e7009
--- /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..9df686cc5f 100644
--- a/lib/chef/win32/process.rb
+++ b/lib/chef/win32/process.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/win32/api/process'
-require 'chef/win32/api/psapi'
-require 'chef/win32/error'
-require 'chef/win32/handle'
-require 'ffi'
+require "chef/win32/api/process"
+require "chef/win32/api/psapi"
+require "chef/win32/error"
+require "chef/win32/handle"
+require "ffi"
class Chef
module ReservedNames::Win32
@@ -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..e55539167a 100644
--- a/lib/chef/win32/registry.rb
+++ b/lib/chef/win32/registry.rb
@@ -16,17 +16,29 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-require 'chef/reserved_names'
+require "chef/reserved_names"
+require "chef/win32/api"
+require "chef/mixin/wide_string"
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
- require 'win32/registry'
- require 'win32/api'
+ require "chef/monkey_patches/win32/registry"
+ require "chef/win32/api/registry"
+ require "win32/registry"
+ require "win32/api"
end
class Chef
class Win32
class Registry
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ include Chef::ReservedNames::Win32::API::Registry
+ extend Chef::ReservedNames::Win32::API::Registry
+ end
+
+ include Chef::Mixin::WideString
+ extend Chef::Mixin::WideString
+
attr_accessor :run_context
attr_accessor :architecture
@@ -115,38 +127,23 @@ class Chef
Chef::Log.debug("Registry key #{key_path}, does not exist, not deleting")
return true
end
- #key_path is in the form "HKLM\Software\Opscode" for example, extracting
- #hive = HKLM,
- #hive_namespace = ::Win32::Registry::HKEY_LOCAL_MACHINE
- hive = key_path.split("\\").shift
- hive_namespace, key_including_parent = get_hive_and_key(key_path)
- if has_subkeys?(key_path)
- if recursive == true
- subkeys = get_subkeys(key_path)
- subkeys.each do |key|
- keypath_to_check = hive+"\\"+key_including_parent+"\\"+key
- Chef::Log.debug("Deleting registry key #{key_path} recursively")
- delete_key(keypath_to_check, true)
- end
- delete_key_ex(hive_namespace, key_including_parent)
- else
- raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified"
- end
- else
- delete_key_ex(hive_namespace, key_including_parent)
- return true
+ if has_subkeys?(key_path) && !recursive
+ raise Chef::Exceptions::Win32RegNoRecursive, "Registry key #{key_path} has subkeys, and recursive not specified"
end
+ hive, key_including_parent = get_hive_and_key(key_path)
+ # key_including_parent: Software\\Root\\Branch\\Fruit
+ # key => Fruit
+ # key_parent => Software\\Root\\Branch
+ key_parts = key_including_parent.split("\\")
+ key = key_parts.pop
+ key_parent = key_parts.join("\\")
+ hive.open(key_parent, ::Win32::Registry::KEY_WRITE | registry_system_architecture) do |reg|
+ reg.delete_key(key, recursive)
+ end
+ Chef::Log.debug("Registry key #{key_path} deleted")
true
end
- #Using the 'RegDeleteKeyEx' Windows API that correctly supports WOW64 systems (Win2003)
- #instead of the 'RegDeleteKey'
- def delete_key_ex(hive, key)
- regDeleteKeyEx = ::Win32::API.new('RegDeleteKeyEx', 'LPLL', 'L', 'advapi32')
- hive_num = hive.hkey - (1 << 32)
- regDeleteKeyEx.call(hive_num, key, ::Win32::Registry::KEY_WRITE | registry_system_architecture, 0)
- end
-
def key_exists?(key_path)
hive, key = get_hive_and_key(key_path)
begin
@@ -203,7 +200,7 @@ class Chef
key_exists!(key_path)
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
- return true if reg.any? {|val| val == value[:name] }
+ return true if reg.any? {|val| safely_downcase(val) == safely_downcase(value[:name]) }
end
return false
end
@@ -213,7 +210,7 @@ class Chef
hive, key = get_hive_and_key(key_path)
hive.open(key, ::Win32::Registry::KEY_READ | registry_system_architecture) do |reg|
reg.each do |val_name, val_type, val_data|
- if val_name == value[:name] &&
+ if safely_downcase(val_name) == safely_downcase(value[:name]) &&
val_type == get_type_from_name(value[:type]) &&
val_data == value[:data]
return true
@@ -259,19 +256,6 @@ class Chef
end
end
- def get_type_from_name(val_type)
- value = {
- :binary => ::Win32::Registry::REG_BINARY,
- :string => ::Win32::Registry::REG_SZ,
- :multi_string => ::Win32::Registry::REG_MULTI_SZ,
- :expand_string => ::Win32::Registry::REG_EXPAND_SZ,
- :dword => ::Win32::Registry::REG_DWORD,
- :dword_big_endian => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
- :qword => ::Win32::Registry::REG_QWORD
- }[val_type]
- return value
- end
-
def keys_missing?(key_path)
missing_key_arr = key_path.split("\\")
missing_key_arr.pop
@@ -289,6 +273,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
@@ -334,7 +326,7 @@ class Chef
:expand_string => ::Win32::Registry::REG_EXPAND_SZ,
:dword => ::Win32::Registry::REG_DWORD,
:dword_big_endian => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
- :qword => ::Win32::Registry::REG_QWORD
+ :qword => ::Win32::Registry::REG_QWORD,
}
end
@@ -350,7 +342,7 @@ class Chef
2 => ::Win32::Registry::REG_EXPAND_SZ,
4 => ::Win32::Registry::REG_DWORD,
5 => ::Win32::Registry::REG_DWORD_BIG_ENDIAN,
- 11 => ::Win32::Registry::REG_QWORD
+ 11 => ::Win32::Registry::REG_QWORD,
}[val_type]
return value
end
diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb
index 3902d8caaf..31128ad083 100644
--- a/lib/chef/win32/security.rb
+++ b/lib/chef/win32/security.rb
@@ -16,12 +16,13 @@
# limitations under the License.
#
-require 'chef/win32/api/security'
-require 'chef/win32/error'
-require 'chef/win32/memory'
-require 'chef/win32/process'
-require 'chef/win32/unicode'
-require 'chef/win32/security/token'
+require "chef/win32/api/security"
+require "chef/win32/error"
+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
@@ -101,6 +104,22 @@ class Chef
end
end
+ def self.add_account_right(name, privilege)
+ privilege_pointer = FFI::MemoryPointer.new LSA_UNICODE_STRING, 1
+ privilege_lsa_string = LSA_UNICODE_STRING.new(privilege_pointer)
+ privilege_lsa_string[:Buffer] = FFI::MemoryPointer.from_string(privilege.to_wstring)
+ privilege_lsa_string[:Length] = privilege.length * 2
+ privilege_lsa_string[:MaximumLength] = (privilege.length + 1) * 2
+
+ with_lsa_policy(name) do |policy_handle, sid|
+ result = LsaAddAccountRights(policy_handle.read_pointer, sid, privilege_pointer, 1)
+ win32_error = LsaNtStatusToWinError(result)
+ if win32_error != 0
+ Chef::ReservedNames::Win32::Error.raise!(nil, win32_error)
+ end
+ end
+ end
+
def self.adjust_token_privileges(token, privileges)
token = token.handle if token.respond_to?(:handle)
old_privileges_size = FFI::Buffer.new(:long).write_long(privileges.size_with_privileges)
@@ -162,6 +181,29 @@ class Chef
end
end
+ def self.get_account_right(name)
+ privileges = []
+ privilege_pointer = FFI::MemoryPointer.new(:pointer)
+ privilege_length = FFI::MemoryPointer.new(:ulong)
+
+ with_lsa_policy(name) do |policy_handle, sid|
+ result = LsaEnumerateAccountRights(policy_handle.read_pointer, sid, privilege_pointer, privilege_length)
+ win32_error = LsaNtStatusToWinError(result)
+ return [] if win32_error == 2 # FILE_NOT_FOUND - No rights assigned
+ if win32_error != 0
+ Chef::ReservedNames::Win32::Error.raise!(nil, win32_error)
+ end
+
+ privilege_length.read_ulong.times do |i|
+ privilege = LSA_UNICODE_STRING.new(privilege_pointer.read_pointer + i * LSA_UNICODE_STRING.size)
+ privileges << privilege[:Buffer].read_wstring
+ end
+ LsaFreeMemory(privilege_pointer)
+ end
+
+ privileges
+ end
+
def self.get_ace(acl, index)
acl = acl.pointer if acl.respond_to?(:pointer)
ace = FFI::Buffer.new :pointer
@@ -270,6 +312,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 +487,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)
@@ -511,9 +587,33 @@ class Chef
end
end
+ def self.with_lsa_policy(username)
+ sid = lookup_account_name(username)[1]
+
+ access = 0
+ access |= POLICY_CREATE_ACCOUNT
+ access |= POLICY_LOOKUP_NAMES
+
+ policy_handle = FFI::MemoryPointer.new(:pointer)
+ result = LsaOpenPolicy(nil, LSA_OBJECT_ATTRIBUTES.new, access, policy_handle)
+ win32_error = LsaNtStatusToWinError(result)
+ if win32_error != 0
+ Chef::ReservedNames::Win32::Error.raise!(nil, win32_error)
+ end
+
+ begin
+ yield policy_handle, sid.pointer
+ ensure
+ win32_error = LsaNtStatusToWinError(LsaClose(policy_handle.read_pointer))
+ if win32_error != 0
+ Chef::ReservedNames::Win32::Error.raise!(nil, win32_error)
+ end
+ end
+ end
+
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 +633,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,12 +643,24 @@ 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
-require 'chef/win32/security/ace'
-require 'chef/win32/security/acl'
-require 'chef/win32/security/securable_object'
-require 'chef/win32/security/security_descriptor'
-require 'chef/win32/security/sid'
+require "chef/win32/security/ace"
+require "chef/win32/security/acl"
+require "chef/win32/security/securable_object"
+require "chef/win32/security/security_descriptor"
+require "chef/win32/security/sid"
diff --git a/lib/chef/win32/security/ace.rb b/lib/chef/win32/security/ace.rb
index 1f54b1cafb..f23db2abcc 100644
--- a/lib/chef/win32/security/ace.rb
+++ b/lib/chef/win32/security/ace.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/win32/security'
-require 'chef/win32/security/sid'
-require 'chef/win32/memory'
+require "chef/win32/security"
+require "chef/win32/security/sid"
+require "chef/win32/memory"
-require 'ffi'
+require "ffi"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/security/acl.rb b/lib/chef/win32/security/acl.rb
index e129d5c9a0..aecf17ad1c 100644
--- a/lib/chef/win32/security/acl.rb
+++ b/lib/chef/win32/security/acl.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/win32/security'
-require 'chef/win32/security/ace'
-require 'ffi'
+require "chef/win32/security"
+require "chef/win32/security/ace"
+require "ffi"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/security/securable_object.rb b/lib/chef/win32/security/securable_object.rb
index 00655c9bab..73d4a695e0 100644
--- a/lib/chef/win32/security/securable_object.rb
+++ b/lib/chef/win32/security/securable_object.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/win32/security'
-require 'chef/win32/security/acl'
-require 'chef/win32/security/sid'
+require "chef/win32/security"
+require "chef/win32/security/acl"
+require "chef/win32/security/sid"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/security/security_descriptor.rb b/lib/chef/win32/security/security_descriptor.rb
index 658e9104b4..87ef90ecd5 100644
--- a/lib/chef/win32/security/security_descriptor.rb
+++ b/lib/chef/win32/security/security_descriptor.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/win32/security'
-require 'chef/win32/security/acl'
-require 'chef/win32/security/sid'
+require "chef/win32/security"
+require "chef/win32/security/acl"
+require "chef/win32/security/sid"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/security/sid.rb b/lib/chef/win32/security/sid.rb
index 8e9407dc80..5e7edf2733 100644
--- a/lib/chef/win32/security/sid.rb
+++ b/lib/chef/win32/security/sid.rb
@@ -16,11 +16,11 @@
# limitations under the License.
#
-require 'chef/win32/security'
-require 'chef/win32/api/net'
-require 'chef/win32/api/error'
+require "chef/win32/security"
+require "chef/win32/api/net"
+require "chef/win32/api/error"
-require 'wmi-lite/wmi'
+require "wmi-lite/wmi"
class Chef
module ReservedNames::Win32
@@ -78,115 +78,115 @@ class Chef
# Well-known SIDs
def self.Null
- SID.from_string_sid('S-1-0')
+ SID.from_string_sid("S-1-0")
end
def self.Nobody
- SID.from_string_sid('S-1-0-0')
+ SID.from_string_sid("S-1-0-0")
end
def self.World
- SID.from_string_sid('S-1-1')
+ SID.from_string_sid("S-1-1")
end
def self.Everyone
- SID.from_string_sid('S-1-1-0')
+ SID.from_string_sid("S-1-1-0")
end
def self.Local
- SID.from_string_sid('S-1-2')
+ SID.from_string_sid("S-1-2")
end
def self.Creator
- SID.from_string_sid('S-1-3')
+ SID.from_string_sid("S-1-3")
end
def self.CreatorOwner
- SID.from_string_sid('S-1-3-0')
+ SID.from_string_sid("S-1-3-0")
end
def self.CreatorGroup
- SID.from_string_sid('S-1-3-1')
+ SID.from_string_sid("S-1-3-1")
end
def self.CreatorOwnerServer
- SID.from_string_sid('S-1-3-2')
+ SID.from_string_sid("S-1-3-2")
end
def self.CreatorGroupServer
- SID.from_string_sid('S-1-3-3')
+ SID.from_string_sid("S-1-3-3")
end
def self.NonUnique
- SID.from_string_sid('S-1-4')
+ SID.from_string_sid("S-1-4")
end
def self.Nt
- SID.from_string_sid('S-1-5')
+ SID.from_string_sid("S-1-5")
end
def self.Dialup
- SID.from_string_sid('S-1-5-1')
+ SID.from_string_sid("S-1-5-1")
end
def self.Network
- SID.from_string_sid('S-1-5-2')
+ SID.from_string_sid("S-1-5-2")
end
def self.Batch
- SID.from_string_sid('S-1-5-3')
+ SID.from_string_sid("S-1-5-3")
end
def self.Interactive
- SID.from_string_sid('S-1-5-4')
+ SID.from_string_sid("S-1-5-4")
end
def self.Service
- SID.from_string_sid('S-1-5-6')
+ SID.from_string_sid("S-1-5-6")
end
def self.Anonymous
- SID.from_string_sid('S-1-5-7')
+ SID.from_string_sid("S-1-5-7")
end
def self.Proxy
- SID.from_string_sid('S-1-5-8')
+ SID.from_string_sid("S-1-5-8")
end
def self.EnterpriseDomainControllers
- SID.from_string_sid('S-1-5-9')
+ SID.from_string_sid("S-1-5-9")
end
def self.PrincipalSelf
- SID.from_string_sid('S-1-5-10')
+ SID.from_string_sid("S-1-5-10")
end
def self.AuthenticatedUsers
- SID.from_string_sid('S-1-5-11')
+ SID.from_string_sid("S-1-5-11")
end
def self.RestrictedCode
- SID.from_string_sid('S-1-5-12')
+ SID.from_string_sid("S-1-5-12")
end
def self.TerminalServerUsers
- SID.from_string_sid('S-1-5-13')
+ SID.from_string_sid("S-1-5-13")
end
def self.LocalSystem
- SID.from_string_sid('S-1-5-18')
+ SID.from_string_sid("S-1-5-18")
end
def self.NtLocal
- SID.from_string_sid('S-1-5-19')
+ SID.from_string_sid("S-1-5-19")
end
def self.NtNetwork
- SID.from_string_sid('S-1-5-20')
+ SID.from_string_sid("S-1-5-20")
end
def self.BuiltinAdministrators
- SID.from_string_sid('S-1-5-32-544')
+ SID.from_string_sid("S-1-5-32-544")
end
def self.BuiltinUsers
- SID.from_string_sid('S-1-5-32-545')
+ SID.from_string_sid("S-1-5-32-545")
end
def self.Guests
- SID.from_string_sid('S-1-5-32-546')
+ SID.from_string_sid("S-1-5-32-546")
end
def self.PowerUsers
- SID.from_string_sid('S-1-5-32-547')
+ SID.from_string_sid("S-1-5-32-547")
end
def self.AccountOperators
- SID.from_string_sid('S-1-5-32-548')
+ SID.from_string_sid("S-1-5-32-548")
end
def self.ServerOperators
- SID.from_string_sid('S-1-5-32-549')
+ SID.from_string_sid("S-1-5-32-549")
end
def self.PrintOperators
- SID.from_string_sid('S-1-5-32-550')
+ SID.from_string_sid("S-1-5-32-550")
end
def self.BackupOperators
- SID.from_string_sid('S-1-5-32-551')
+ SID.from_string_sid("S-1-5-32-551")
end
def self.Replicators
- SID.from_string_sid('S-1-5-32-552')
+ SID.from_string_sid("S-1-5-32-552")
end
def self.Administrators
- SID.from_string_sid('S-1-5-32-544')
+ SID.from_string_sid("S-1-5-32-544")
end
def self.None
@@ -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..3184bc449a 100644
--- a/lib/chef/win32/security/token.rb
+++ b/lib/chef/win32/security/token.rb
@@ -16,10 +16,10 @@
# limitations under the License.
#
-require 'chef/win32/security'
-require 'chef/win32/api/security'
-
-require 'ffi'
+require "chef/win32/security"
+require "chef/win32/api/security"
+require "chef/win32/unicode"
+require "ffi"
class Chef
module ReservedNames::Win32
diff --git a/lib/chef/win32/system.rb b/lib/chef/win32/system.rb
new file mode 100755
index 0000000000..e91285568c
--- /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..da38e92cdd 100644
--- a/lib/chef/win32/unicode.rb
+++ b/lib/chef/win32/unicode.rb
@@ -17,7 +17,8 @@
# limitations under the License.
#
-require 'chef/win32/api/unicode'
+require "chef/mixin/wide_string"
+require "chef/win32/api/unicode"
class Chef
module ReservedNames::Win32
@@ -30,6 +31,8 @@ end
module FFI
class Pointer
+ include Chef::Mixin::WideString
+
def read_wstring(num_wchars = nil)
if num_wchars.nil?
# Find the length of the string
@@ -43,13 +46,15 @@ module FFI
num_wchars = length
end
- Chef::ReservedNames::Win32::Unicode.wide_to_utf8(self.get_bytes(0, num_wchars*2))
+ wide_to_utf8(self.get_bytes(0, num_wchars*2))
end
end
end
class String
+ include Chef::Mixin::WideString
+
def to_wstring
- Chef::ReservedNames::Win32::Unicode.utf8_to_wide(self)
+ utf8_to_wide(self)
end
end
diff --git a/lib/chef/win32/version.rb b/lib/chef/win32/version.rb
index 17c27e4780..85a71491df 100644
--- a/lib/chef/win32/version.rb
+++ b/lib/chef/win32/version.rb
@@ -16,9 +16,9 @@
# limitations under the License.
#
-require 'chef/win32/api'
-require 'chef/win32/api/system'
-require 'wmi-lite/wmi'
+require "chef/win32/api"
+require "chef/win32/api/system"
+require "wmi-lite/wmi"
class Chef
module ReservedNames::Win32
@@ -62,7 +62,7 @@ class Chef
"Windows Home Server" => {:major => 5, :minor => 2, :callable => lambda{ |product_type, suite_mask| (suite_mask & VER_SUITE_WH_SERVER) == VER_SUITE_WH_SERVER }},
"Windows Server 2003" => {:major => 5, :minor => 2, :callable => lambda{ |product_type, suite_mask| get_system_metrics(SM_SERVERR2) == 0 }},
"Windows XP" => {:major => 5, :minor => 1},
- "Windows 2000" => {:major => 5, :minor => 0}
+ "Windows 2000" => {:major => 5, :minor => 0},
}
def initialize
@@ -122,18 +122,14 @@ 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']
+ os_info = wmi.first_of("Win32_OperatingSystem")
+ os_version = os_info["version"]
# The operating system version is a string in the following form
# that can be split into components based on the '.' delimiter:
# MajorVersionNumber.MinorVersionNumber.BuildNumber
- os_version.split('.').collect { | version_string | version_string.to_i }
+ os_version.split(".").collect { | version_string | version_string.to_i }
end
def get_version_ex
diff --git a/lib/chef/workstation_config_loader.rb b/lib/chef/workstation_config_loader.rb
index 2454c9cccf..89080c1777 100644
--- a/lib/chef/workstation_config_loader.rb
+++ b/lib/chef/workstation_config_loader.rb
@@ -1,5 +1,5 @@
#
-# Author:: Daniel DeLeo (<dan@getchef.com>)
+# Author:: Claire McQuin (<claire@chef.io>)
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
@@ -16,163 +16,8 @@
# limitations under the License.
#
-require 'chef/config_fetcher'
-require 'chef/config'
-require 'chef/null_logger'
-require 'chef/util/path_helper'
+require "chef-config/workstation_config_loader"
class Chef
-
- class WorkstationConfigLoader
-
- # Path to a config file requested by user, (e.g., via command line option). Can be nil
- attr_accessor :explicit_config_file
-
- # TODO: initialize this with a logger for Chef and Knife
- def initialize(explicit_config_file, logger=nil)
- @explicit_config_file = explicit_config_file
- @config_location = nil
- @logger = logger || NullLogger.new
- end
-
- def no_config_found?
- config_location.nil?
- end
-
- def config_location
- @config_location ||= (explicit_config_file || locate_local_config)
- end
-
- def chef_config_dir
- if @chef_config_dir.nil?
- @chef_config_dir = false
- full_path = working_directory.split(File::SEPARATOR)
- (full_path.length - 1).downto(0) do |i|
- candidate_directory = File.join(full_path[0..i] + [".chef" ])
- if File.exist?(candidate_directory) && File.directory?(candidate_directory)
- @chef_config_dir = candidate_directory
- break
- end
- end
- end
- @chef_config_dir
- end
-
- def load
- # Ignore it if there's no explicit_config_file and can't find one at a
- # default path.
- return false if config_location.nil?
-
- if explicit_config_file && !path_exists?(config_location)
- raise Exceptions::ConfigurationError, "Specified config file #{config_location} does not exist"
- end
-
- # Have to set Chef::Config.config_file b/c other config is derived from it.
- Chef::Config.config_file = config_location
- read_config(IO.read(config_location), config_location)
- end
-
- # (Private API, public for test purposes)
- def env
- ENV
- end
-
- # (Private API, public for test purposes)
- def path_exists?(path)
- Pathname.new(path).expand_path.exist?
- end
-
- private
-
- def have_config?(path)
- if path_exists?(path)
- logger.info("Using config at #{path}")
- true
- else
- logger.debug("Config not found at #{path}, trying next option")
- false
- end
- end
-
- def locate_local_config
- candidate_configs = []
-
- # Look for $KNIFE_HOME/knife.rb (allow multiple knives config on same machine)
- if env['KNIFE_HOME']
- candidate_configs << File.join(env['KNIFE_HOME'], 'config.rb')
- candidate_configs << File.join(env['KNIFE_HOME'], 'knife.rb')
- end
- # Look for $PWD/knife.rb
- if Dir.pwd
- candidate_configs << File.join(Dir.pwd, 'config.rb')
- candidate_configs << File.join(Dir.pwd, 'knife.rb')
- end
- # Look for $UPWARD/.chef/knife.rb
- if chef_config_dir
- candidate_configs << File.join(chef_config_dir, 'config.rb')
- candidate_configs << File.join(chef_config_dir, 'knife.rb')
- end
- # Look for $HOME/.chef/knife.rb
- Chef::Util::PathHelper.home('.chef') do |dot_chef_dir|
- candidate_configs << File.join(dot_chef_dir, 'config.rb')
- candidate_configs << File.join(dot_chef_dir, 'knife.rb')
- end
-
- candidate_configs.find do | candidate_config |
- have_config?(candidate_config)
- end
- end
-
- def working_directory
- a = if Chef::Platform.windows?
- env['CD']
- else
- env['PWD']
- end || Dir.pwd
-
- a
- end
-
- def read_config(config_content, config_file_path)
- Chef::Config.from_string(config_content, config_file_path)
- rescue SignalException
- raise
- rescue SyntaxError => e
- message = ""
- message << "You have invalid ruby syntax in your config file #{config_file_path}\n\n"
- message << "#{e.class.name}: #{e.message}\n"
- if file_line = e.message[/#{Regexp.escape(config_file_path)}:[\d]+/]
- line = file_line[/:([\d]+)$/, 1].to_i
- message << highlight_config_error(config_file_path, line)
- end
- raise Exceptions::ConfigurationError, message
- rescue Exception => e
- message = "You have an error in your config file #{config_file_path}\n\n"
- message << "#{e.class.name}: #{e.message}\n"
- filtered_trace = e.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
- filtered_trace.each {|bt_line| message << " " << bt_line << "\n" }
- if !filtered_trace.empty?
- line_nr = filtered_trace.first[/#{Regexp.escape(config_file_path)}:([\d]+)/, 1]
- message << highlight_config_error(config_file_path, line_nr.to_i)
- end
- raise Exceptions::ConfigurationError, message
- end
-
-
- def highlight_config_error(file, line)
- config_file_lines = []
- IO.readlines(file).each_with_index {|l, i| config_file_lines << "#{(i + 1).to_s.rjust(3)}: #{l.chomp}"}
- if line == 1
- lines = config_file_lines[0..3]
- else
- lines = config_file_lines[Range.new(line - 2, line)]
- end
- "Relevant file content:\n" + lines.join("\n") + "\n"
- end
-
- def logger
- @logger
- end
-
- end
+ WorkstationConfigLoader = ChefConfig::WorkstationConfigLoader
end